From b621c21be56c14e29cca43721ec494c23495d5fb Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:53:46 +0100 Subject: [PATCH 1/9] chore: remove the vendored affinescript/ tree affinescript/ (616 tracked files) was copied in when the repository was scaffolded from another checkout. No workspace member, build recipe, test or workflow references it (checked: Cargo.toml members, Justfile, .github/workflows/*, crates/, src/, scripts/, tools/), and it carried the only remaining .machine_readable/6a2/ copy in the tree. AffineScript lives upstream at hyperpolymath/affinescript. Co-Authored-By: Claude Opus 5.5 --- affinescript/.build/.ocamlformat | 5 - affinescript/.build/Justfile | 79 - affinescript/.build/Mustfile | 14 - affinescript/.build/affinescript.opam | 51 - affinescript/.build/dune-project | 42 - affinescript/.claude/CLAUDE.md | 89 - affinescript/.editorconfig | 68 - affinescript/.gitattributes | 54 - affinescript/.github/FUNDING.yml | 7 - .../.github/ISSUE_TEMPLATE/bug_report.md | 42 - .../.github/ISSUE_TEMPLATE/documentation.md | 70 - .../.github/ISSUE_TEMPLATE/feature_request.md | 24 - .../.github/ISSUE_TEMPLATE/question.md | 59 - affinescript/.github/dependabot.yml | 11 - .../.github/workflows/casket-pages.yml | 88 - affinescript/.github/workflows/ci.yml | 51 - affinescript/.github/workflows/codeql.yml | 37 - .../.github/workflows/guix-nix-policy.yml | 34 - .../.github/workflows/hypatia-scan.yml | 125 - .../.github/workflows/instant-sync.yml | 30 - affinescript/.github/workflows/mirror.yml | 127 - .../.github/workflows/npm-bun-blocker.yml | 19 - affinescript/.github/workflows/quality.yml | 51 - affinescript/.github/workflows/release.yml | 33 - .../.github/workflows/scorecard-enforcer.yml | 51 - affinescript/.github/workflows/scorecard.yml | 29 - .../.github/workflows/secret-scanner.yml | 51 - .../.github/workflows/security-policy.yml | 42 - affinescript/.github/workflows/semgrep.yml | 31 - affinescript/.github/workflows/ts-blocker.yml | 24 - .../workflows/wellknown-enforcement.yml | 90 - .../.github/workflows/workflow-linter.yml | 145 - affinescript/.gitignore | 85 - .../.machine_readable/6a2/AGENTIC.a2ml | 34 - .../.machine_readable/6a2/ECOSYSTEM.a2ml | 27 - affinescript/.machine_readable/6a2/META.a2ml | 671 ----- .../.machine_readable/6a2/NEUROSYM.a2ml | 21 - .../.machine_readable/6a2/PLAYBOOK.a2ml | 26 - affinescript/.machine_readable/6a2/STATE.a2ml | 215 -- affinescript/.machine_readable/CLADE.a2ml | 38 - .../.machine_readable/anchors/ANCHOR.a2ml | 179 -- .../bot_directives/README.adoc | 38 - .../bot_directives/coverage.a2ml | 34 - .../bot_directives/debt.a2ml | 73 - .../bot_directives/methodology.a2ml | 59 - .../contractiles/Adjustfile.a2ml | 15 - .../contractiles/Intentfile.a2ml | 19 - .../contractiles/Mustfile.a2ml | 14 - .../contractiles/README.adoc | 21 - .../contractiles/Trustfile.a2ml | 14 - .../contractiles/adjust/Adjustfile.a2ml | 51 - .../contractiles/dust/Dustfile | 29 - .../contractiles/intend/Intentfile.a2ml | 54 - .../contractiles/must/Mustfile | 35 - .../integrations/verisimdb.a2ml | 29 - .../integrations/vexometer.a2ml | 25 - affinescript/.well-known/dnt-policy.txt | 18 - affinescript/.well-known/groove/manifest.json | 26 - affinescript/.well-known/keybase.txt | 12 - affinescript/.well-known/security.txt | 53 - affinescript/.well-known/sellers.json | 7 - affinescript/.well-known/trust.txt | 57 - affinescript/ABI-FFI-README.adoc | 409 --- affinescript/AI.a2ml | 22 - affinescript/AI.djot | 100 - affinescript/ALPHA-1-RELEASE-NOTES.adoc | 248 -- affinescript/BACKEND-ANALYSIS.adoc | 190 -- affinescript/BACKEND-IMPLEMENTATION.adoc | 399 --- affinescript/CNAME | 1 - affinescript/CODE_OF_CONDUCT.adoc | 339 --- affinescript/COMPILER-CAPABILITIES.adoc | 431 --- affinescript/CONTRIBUTING.adoc | 109 - affinescript/CONTROLS-REFERENCE.adoc | 587 ---- affinescript/DAMAGE-SYSTEM.adoc | 1109 ------- affinescript/EXPLAINME.adoc | 77 - affinescript/FIXES_2026-01-23.adoc | 362 --- affinescript/GAME-BUNDLING-STRATEGY.adoc | 594 ---- affinescript/Justfile | 179 -- affinescript/LICENSE | 153 - affinescript/LICENSE-AGPL-3.0 | 647 ----- affinescript/LICENSES/LICENSE | 154 - affinescript/LICENSES/LICENSE-AGPL-3.0 | 647 ----- affinescript/LICENSES/LICENSE-PMPL-1.0 | 153 - affinescript/LICENSING-GUIDE.adoc | 482 --- affinescript/MAINTAINERS.adoc | 48 - affinescript/MODULE-SYSTEM-PROGRESS.adoc | 369 --- affinescript/NAVIGATION.adoc | 199 -- affinescript/PHASE3-ASSESSMENT.adoc | 568 ---- affinescript/PHASE3-COMPLETE.adoc | 406 --- affinescript/PHASE3-SESSION-SUMMARY.adoc | 294 -- affinescript/PROOF-NEEDS.adoc | 13 - affinescript/README.adoc | 494 ---- affinescript/REPLY-SUM-TYPES.adoc | 276 -- affinescript/ROADMAP.adoc | 323 --- affinescript/RSR_OUTLINE.adoc | 220 -- affinescript/SECURITY-SETUP.adoc | 244 -- affinescript/SECURITY.adoc | 452 --- affinescript/SESSION-2026-01-23.adoc | 301 -- affinescript/SESSION-COMPLETE.adoc | 583 ---- affinescript/SPARK-experiment/README.adoc | 53 - affinescript/TRAIT_SYSTEM_STATUS.adoc | 425 --- affinescript/ads.txt | 12 - affinescript/affinescript.opam | 52 - affinescript/affinescript.runbook.a2ml | 36 - affinescript/app-ads.txt | 7 - affinescript/bench/dune | 11 - affinescript/conformance/README.adoc | 65 - .../invalid/001_unclosed_brace.affine | 5 - .../invalid/001_unclosed_brace.expected | 8 - .../invalid/002_unclosed_string.affine | 5 - .../invalid/002_unclosed_string.expected | 8 - .../conformance/invalid/003_bad_number.affine | 5 - .../invalid/003_bad_number.expected | 8 - .../invalid/004_unexpected_token.affine | 5 - .../invalid/004_unexpected_token.expected | 8 - .../invalid/005_missing_arrow.affine | 5 - .../invalid/005_missing_arrow.expected | 8 - .../invalid/006_bad_operator.affine | 5 - .../invalid/006_bad_operator.expected | 8 - .../invalid/007_incomplete_effect.affine | 4 - .../invalid/007_incomplete_effect.expected | 8 - .../invalid/008_mismatched_parens.affine | 5 - .../invalid/008_mismatched_parens.expected | 8 - .../invalid/009_reserved_keyword.affine | 5 - .../invalid/009_reserved_keyword.expected | 8 - .../invalid/010_empty_match.affine | 6 - .../invalid/010_empty_match.expected | 8 - .../conformance/invalid/011_bad_escape.affine | 5 - .../invalid/011_bad_escape.expected | 8 - .../invalid/012_trailing_comma.affine | 5 - .../invalid/012_trailing_comma.expected | 5 - .../conformance/valid/001_empty.affine | 2 - .../conformance/valid/001_empty.expected | 1 - .../conformance/valid/002_comments.affine | 11 - .../conformance/valid/002_comments.expected | 1 - .../conformance/valid/003_let_bindings.affine | 9 - .../valid/003_let_bindings.expected | 155 - .../conformance/valid/004_literals.affine | 26 - .../conformance/valid/004_literals.expected | 267 -- .../conformance/valid/005_functions.affine | 21 - .../conformance/valid/005_functions.expected | 287 -- .../conformance/valid/006_type_alias.affine | 7 - .../conformance/valid/006_type_alias.expected | 166 -- .../conformance/valid/007_struct_def.affine | 21 - .../conformance/valid/007_struct_def.expected | 302 -- .../conformance/valid/008_enum_def.affine | 19 - .../conformance/valid/008_enum_def.expected | 270 -- .../conformance/valid/009_effect_def.affine | 21 - .../conformance/valid/009_effect_def.expected | 341 --- .../valid/010_pattern_match.affine | 27 - .../valid/010_pattern_match.expected | 494 ---- .../conformance/valid/011_rows.affine | 16 - .../conformance/valid/011_rows.expected | 445 --- .../conformance/valid/012_ownership.affine | 20 - .../conformance/valid/012_ownership.expected | 223 -- .../distributions/rattlescript/Cargo.toml | 28 - .../distributions/rattlescript/Justfile | 50 - .../distributions/rattlescript/README.adoc | 143 - .../distributions/rattlescript/build.rs | 51 - .../rattlescript/examples/hello.rattle | 13 - .../rattlescript/examples/ownership.rattle | 21 - .../distributions/rattlescript/manifest.scm | 24 - .../distributions/rattlescript/src/main.rs | 106 - affinescript/docs/.well-known/dnt-policy.txt | 18 - affinescript/docs/.well-known/keybase.txt | 12 - affinescript/docs/.well-known/security.txt | 53 - affinescript/docs/.well-known/sellers.json | 7 - affinescript/docs/.well-known/trust.txt | 57 - affinescript/docs/ALIB-INTEGRATION.adoc | 505 ---- affinescript/docs/BRIDGE-DECISIONS.adoc | 159 - affinescript/docs/CITATIONS.adoc | 38 - affinescript/docs/CNAME | 1 - affinescript/docs/DESIGN-VISION.adoc | 257 -- affinescript/docs/EFFECTS-IMPLEMENTATION.adoc | 100 - affinescript/docs/README.adoc | 105 - .../docs/WASM-EFFECT-HANDLER-WORKAROUND.adoc | 398 --- affinescript/docs/academic/README.adoc | 129 - .../axiomatic-semantics.adoc | 584 ---- .../denotational-semantics.adoc | 501 ---- .../operational-semantics.adoc | 699 ----- .../formal-verification/solo-core/Context.idr | 102 - .../solo-core/Quantity.idr | 315 -- .../formal-verification/solo-core/README.adoc | 112 - .../solo-core/Soundness.idr | 102 - .../formal-verification/solo-core/Syntax.idr | 67 - .../formal-verification/solo-core/Typing.idr | 127 - .../categorical-semantics.adoc | 478 --- .../complexity-analysis.adoc | 370 --- .../logic-foundations.adoc | 532 ---- .../docs/academic/mechanized/agda/README.adoc | 166 -- .../docs/academic/mechanized/coq/README.adoc | 171 -- .../docs/academic/mechanized/lean/README.adoc | 163 -- .../proofs/coherence-parametricity.adoc | 422 --- .../docs/academic/proofs/dependent-types.adoc | 751 ----- .../academic/proofs/effect-soundness.adoc | 681 ----- .../academic/proofs/inference-algorithm.adoc | 474 --- .../academic/proofs/ownership-soundness.adoc | 681 ----- .../academic/proofs/quantitative-types.adoc | 518 ---- .../academic/proofs/row-polymorphism.adoc | 637 ---- .../docs/academic/proofs/type-soundness.adoc | 676 ----- .../TropicalSessionTypes.lean | 149 - .../tropical-unification-2026-04-04.adoc | 80 - .../white-papers/language-design.adoc | 486 ---- affinescript/docs/ads.txt | 12 - affinescript/docs/alib/README.adoc | 183 -- affinescript/docs/app-ads.txt | 7 - .../docs/governance/CODE_OF_CONDUCT.adoc | 24 - .../docs/governance/CONTRIBUTING.adoc | 21 - affinescript/docs/governance/MAINTAINERS.adoc | 48 - affinescript/docs/governance/SECURITY.adoc | 37 - affinescript/docs/guides/TESTING-REPORT.adoc | 279 -- affinescript/docs/guides/TESTING-REPORT.scm | 287 -- .../docs/guides/WHAT-MAKES-IT-BRILLIANT.adoc | 327 --- affinescript/docs/guides/frontier-guide.adoc | 386 --- .../frontier-programming-practices/AI.a2ml | 169 -- .../Human_Programming_Guide.adoc | 353 --- .../guides/lessons/01-hello-affinescript.adoc | 373 --- affinescript/docs/guides/lessons/README.adoc | 141 - .../docs/guides/warmup/01_basics.affine | 53 - .../docs/guides/warmup/02_ownership.affine | 71 - .../docs/guides/warmup/03_effects.affine | 77 - .../docs/guides/warmup/04_rows.affine | 70 - affinescript/docs/guides/warmup/README.adoc | 58 - .../TYPECHECKER-COMPLETION-2026-01-23.adoc | 405 --- affinescript/docs/humans.txt | 40 - affinescript/docs/spec.adoc | 2020 ------------- .../docs/specs/SETTLED-DECISIONS.adoc | 135 - affinescript/docs/specs/SPEC.adoc | 570 ---- affinescript/docs/specs/faces.adoc | 336 --- affinescript/docs/standards/DECISIONS.adoc | 271 -- affinescript/docs/standards/PALIMPSEST.adoc | 43 - affinescript/docs/standards/RELEASE.adoc | 279 -- affinescript/docs/standards/ROADMAP.adoc | 550 ---- .../docs/tutorial/lesson-02-functions.adoc | 46 - .../docs/tutorial/lesson-03-data.adoc | 36 - .../docs/tutorial/lesson-04-patterns.adoc | 27 - .../docs/tutorial/lesson-05-types.adoc | 27 - .../docs/tutorial/lesson-06-errors.adoc | 30 - .../docs/tutorial/lesson-07-effects.adoc | 40 - .../docs/tutorial/lesson-08-generics.adoc | 27 - .../docs/tutorial/lesson-09-modules.adoc | 36 - .../docs/tutorial/lesson-10-building.adoc | 47 - affinescript/dune-project | 42 - .../tree-sitter-affinescript/README.adoc | 87 - .../tree-sitter-affinescript/grammar.js | 505 ---- .../tree-sitter-affinescript/package.json | 30 - .../queries/highlights.scm | 166 -- affinescript/editors/vscode/README.adoc | 147 - .../vscode/language-configuration.json | 55 - affinescript/editors/vscode/package.json | 152 - .../editors/vscode/src/extension.affine | 161 -- .../syntaxes/affinescript.tmLanguage.json | 207 -- .../examples/comprehensive_test.affine | 36 - affinescript/examples/counter.afs | 77 - affinescript/examples/effects.affine | 20 - affinescript/examples/exception_test.affine | 16 - affinescript/examples/hello.affine | 14 - affinescript/examples/lessons/01_hello.affine | 112 - affinescript/examples/ownership.affine | 14 - affinescript/examples/rows.affine | 19 - affinescript/examples/simple_test.affine | 6 - affinescript/examples/titlescreen.afs | 93 - affinescript/examples/traits.affine | 33 - .../examples/typecheck_complete_test.affine | 38 - .../examples/typecheck_features_test.affine | 31 - affinescript/examples/web-project-deno.json | 20 - affinescript/ffi/zig/build.zig | 95 - affinescript/ffi/zig/src/main.zig | 275 -- .../ffi/zig/test/integration_test.zig | 183 -- affinescript/hooks/validate-codeql.sh | 34 - affinescript/hooks/validate-permissions.sh | 14 - affinescript/hooks/validate-sha-pins.sh | 33 - affinescript/hooks/validate-spdx.sh | 25 - affinescript/humans.txt | 40 - affinescript/js/dune | 21 - affinescript/js/js_api.ml | 101 - affinescript/js/playground.ml | 12 - affinescript/js/runtime.js | 22 - affinescript/lib/ast.ml | 378 --- affinescript/lib/borrow.ml | 885 ------ affinescript/lib/codegen.ml | 1949 ------------- affinescript/lib/codegen_gc.ml | 1226 -------- affinescript/lib/desugar_traits.ml | 238 -- affinescript/lib/dune | 28 - affinescript/lib/effect.ml | 122 - affinescript/lib/effect.mli | 28 - affinescript/lib/error.ml | 218 -- affinescript/lib/error_collector.ml | 121 - affinescript/lib/error_formatter.ml | 102 - affinescript/lib/error_formatter.mli | 46 - affinescript/lib/face.ml | 54 - affinescript/lib/face.mli | 25 - affinescript/lib/formatter.ml | 9 - affinescript/lib/formatter.mli.disabled | 22 - affinescript/lib/interp.ml | 1043 ------- affinescript/lib/js_face.ml | 307 -- affinescript/lib/json_output.ml | 723 ----- affinescript/lib/julia_codegen.ml | 412 --- affinescript/lib/lexer.ml | 335 --- affinescript/lib/linter.ml | 438 --- affinescript/lib/linter.mli.disabled | 29 - affinescript/lib/lsp_server.ml | 562 ---- affinescript/lib/module_loader.ml | 192 -- affinescript/lib/opt.ml | 158 - affinescript/lib/parse.ml | 219 -- affinescript/lib/parse_driver.ml | 221 -- affinescript/lib/parser.mly | 865 ------ affinescript/lib/parser_errors.ml | 18 - affinescript/lib/python_face.ml | 284 -- affinescript/lib/quantity.ml | 723 ----- affinescript/lib/repl.ml | 360 --- affinescript/lib/resolve.ml | 724 ----- affinescript/lib/sexpr_dump.ml | 616 ---- affinescript/lib/span.ml | 35 - affinescript/lib/symbol.ml | 202 -- affinescript/lib/tea_bridge.ml | 296 -- affinescript/lib/tea_cs_bridge.ml | 311 -- affinescript/lib/tea_router.ml | 467 --- affinescript/lib/token.ml | 231 -- affinescript/lib/trait.ml | 553 ---- affinescript/lib/tw_interface.ml | 276 -- affinescript/lib/tw_verify.ml | 280 -- affinescript/lib/typecheck.ml | 1399 --------- affinescript/lib/types.ml | 325 --- affinescript/lib/unify.ml | 317 -- affinescript/lib/value.ml | 287 -- affinescript/lib/wasi_runtime.ml | 290 -- affinescript/lib/wasm.ml | 266 -- affinescript/lib/wasm_encode.ml | 373 --- affinescript/lib/wasm_gc.ml | 316 -- affinescript/lib/wasm_gc_encode.ml | 368 --- affinescript/manifest.scm | 24 - affinescript/opsm.toml | 23 - affinescript/packages/affine-js/README.adoc | 185 -- affinescript/packages/affine-js/marshal.js | 298 -- affinescript/packages/affine-js/mod.js | 252 -- affinescript/packages/affine-js/runtime.js | 150 - .../packages/affine-js/types.d.affine | 161 -- affinescript/packages/affine-res/README.adoc | 99 - .../affine-res/src/AffineScript.affine | 7 - .../packages/affine-res/src/AffineScript.resi | 42 - .../affine-res/src/AffineScriptValue.affine | 7 - affinescript/packages/affine-ts/README.adoc | 101 - affinescript/packages/affine-ts/mod.js | 188 -- .../packages/affine-ts/types.d.affine | 71 - affinescript/panic-attack.toml | 30 - affinescript/run_wasm.js | 26 - affinescript/runtime/Cargo.toml | 47 - affinescript/runtime/src/alloc.rs | 167 -- affinescript/runtime/src/effects.rs | 234 -- affinescript/runtime/src/ffi.rs | 278 -- affinescript/runtime/src/gc.rs | 283 -- affinescript/runtime/src/lib.rs | 76 - affinescript/runtime/src/panic.rs | 243 -- affinescript/site/index.adoc | 27 - affinescript/stdlib/Core.affine | 77 - affinescript/stdlib/Math.affine | 111 - affinescript/stdlib/Option.affine | 117 - affinescript/stdlib/README.adoc | 174 -- affinescript/stdlib/Result.affine | 94 - affinescript/stdlib/collections.affine | 243 -- affinescript/stdlib/effects.affine | 33 - affinescript/stdlib/io.affine | 272 -- affinescript/stdlib/math.affine | 368 --- affinescript/stdlib/option.affine | 328 --- affinescript/stdlib/prelude.affine | 197 -- affinescript/stdlib/result.affine | 244 -- affinescript/stdlib/string.affine | 239 -- affinescript/stdlib/testing.affine | 321 -- affinescript/stdlib/traits.affine | 178 -- affinescript/test/dune | 7 - .../test/e2e/fixtures/affine_basic.affine | 13 - .../test/e2e/fixtures/affine_let_valid.affine | 12 - .../fixtures/affine_let_valid_sugar.affine | 9 - .../test/e2e/fixtures/affine_violation.affine | 7 - .../test/e2e/fixtures/arithmetic.affine | 15 - .../bug_001_omega_let_smuggles_linear.affine | 18 - .../e2e/fixtures/bug_001_sugar_form.affine | 11 - .../test/e2e/fixtures/cmd_dropped.affine | 18 - .../test/e2e/fixtures/cmd_linear.affine | 23 - affinescript/test/e2e/fixtures/counter.affine | 77 - .../test/e2e/fixtures/dependent_types.affine | 12 - affinescript/test/e2e/fixtures/effects.affine | 19 - .../test/e2e/fixtures/erased_violation.affine | 7 - .../test/e2e/fixtures/full_pipeline.affine | 37 - .../test/e2e/fixtures/interp_simple.affine | 15 - .../test/e2e/fixtures/julia_simple.affine | 11 - affinescript/test/e2e/fixtures/lambda.affine | 19 - .../test/e2e/fixtures/linear_arrow.affine | 15 - .../fixtures/linear_arrow_violation.affine | 10 - .../test/e2e/fixtures/ownership.affine | 15 - .../e2e/fixtures/ownership_codegen.affine | 20 - .../test/e2e/fixtures/pattern_match.affine | 25 - .../test/e2e/fixtures/python_face_basic.pyaff | 20 - .../test/e2e/fixtures/refinement_types.affine | 7 - .../test/e2e/fixtures/row_polymorphism.affine | 20 - .../test/e2e/fixtures/titlescreen.affine | 93 - .../fixtures/trait_impl_missing_method.affine | 20 - .../test/e2e/fixtures/trait_impl_valid.affine | 18 - affinescript/test/e2e/fixtures/traits.affine | 34 - .../test/e2e/fixtures/try_body_only.affine | 15 - .../e2e/fixtures/try_catch_finally.affine | 19 - .../test/e2e/fixtures/try_catch_var.affine | 17 - .../e2e/fixtures/try_catch_wildcard.affine | 17 - .../test/e2e/fixtures/try_finally.affine | 17 - .../test/e2e/fixtures/type_decls.affine | 35 - .../fixtures/verify_ownership_clean.affine | 23 - .../test/e2e/fixtures/wasm_simple.affine | 15 - affinescript/test/golden/binary_ops.affine | 8 - affinescript/test/golden/binary_ops.expected | 131 - affinescript/test/golden/effects.affine | 10 - affinescript/test/golden/effects.expected | 132 - affinescript/test/golden/functions.affine | 16 - affinescript/test/golden/functions.expected | 211 -- affinescript/test/golden/hello.affine | 4 - affinescript/test/golden/hello.expected | 27 - affinescript/test/golden/imports.affine | 8 - affinescript/test/golden/imports.expected | 103 - affinescript/test/golden/lambda.affine | 10 - affinescript/test/golden/lambda.expected | 202 -- affinescript/test/golden/literals.affine | 9 - affinescript/test/golden/literals.expected | 134 - affinescript/test/golden/ownership.affine | 8 - affinescript/test/golden/ownership.expected | 119 - affinescript/test/golden/patterns.affine | 20 - affinescript/test/golden/patterns.expected | 276 -- affinescript/test/golden/rows.affine | 8 - affinescript/test/golden/rows.expected | 208 -- affinescript/test/golden/traits.affine | 14 - affinescript/test/golden/traits.expected | 260 -- affinescript/test/golden/types.affine | 12 - affinescript/test/golden/types.expected | 125 - affinescript/test/test_e2e.ml | 2574 ----------------- affinescript/test/test_golden.ml | 132 - affinescript/test/test_lexer.ml | 147 - affinescript/test/test_main.ml | 13 - affinescript/test_if.affine | 4 - affinescript/test_min.affine | 7 - affinescript/test_simple.affine | 7 - affinescript/test_spread.affine | 4 - affinescript/test_spread2.affine | 4 - affinescript/test_spread3.affine | 5 - affinescript/tests/borrow/minimal.affine | 1 - affinescript/tests/borrow/move_test.affine | 2 - affinescript/tests/borrow/simple.affine | 3 - .../tests/borrow/simple_move_test.affine | 3 - affinescript/tests/borrow/simple_own.affine | 6 - .../borrow/test_immutable_assign_error.affine | 7 - affinescript/tests/borrow/test_move.affine | 64 - .../tests/borrow/test_move_after_use.affine | 11 - .../tests/borrow/test_mutable_borrow.affine | 7 - .../tests/borrow/test_ref_borrow.affine | 8 - .../tests/borrow/use_after_move.affine | 15 - affinescript/tests/borrow/valid_move.affine | 14 - affinescript/tests/codegen/README.adoc | 20 - .../tests/codegen/simple_arithmetic.affine | 11 - .../tests/codegen/test_array_bounds.affine | 10 - .../tests/codegen/test_array_empty.affine | 7 - .../tests/codegen/test_array_simple.affine | 6 - .../tests/codegen/test_array_single.affine | 6 - .../tests/codegen/test_array_sum.affine | 10 - .../tests/codegen/test_assign_compound.affine | 10 - .../tests/codegen/test_assign_simple.affine | 7 - .../tests/codegen/test_closure_capture.affine | 9 - .../tests/codegen/test_closure_debug.affine | 9 - .../codegen/test_closure_multi_capture.affine | 13 - .../tests/codegen/test_enum_pattern.affine | 21 - .../tests/codegen/test_for_loop.affine | 10 - .../tests/codegen/test_function_call.affine | 12 - .../tests/codegen/test_function_call.mjs | 19 - .../tests/codegen/test_lambda_call.affine | 12 - .../codegen/test_lambda_multi_param.affine | 19 - .../tests/codegen/test_match_literal.affine | 13 - .../tests/codegen/test_match_mixed.affine | 17 - .../tests/codegen/test_match_simple.affine | 11 - .../tests/codegen/test_multiple_calls.affine | 23 - .../tests/codegen/test_multiple_calls.mjs | 19 - .../codegen/test_multiple_lambdas.affine | 25 - .../codegen/test_opt_constant_fold.affine | 8 - .../tests/codegen/test_opt_if_fold.affine | 7 - .../tests/codegen/test_pattern_as.affine | 12 - .../tests/codegen/test_pattern_or.affine | 13 - .../tests/codegen/test_pattern_record.affine | 11 - .../tests/codegen/test_pattern_tuple.affine | 11 - .../codegen/test_record_multi_field.affine | 6 - .../tests/codegen/test_record_multiple.affine | 8 - .../tests/codegen/test_record_simple.affine | 6 - .../tests/codegen/test_recursive_call.affine | 16 - .../tests/codegen/test_ref_deref_chain.affine | 10 - .../codegen/test_ref_deref_simple.affine | 8 - .../tests/codegen/test_simple_lambda.affine | 10 - .../tests/codegen/test_string_diff.affine | 11 - .../tests/codegen/test_string_eq.affine | 10 - .../tests/codegen/test_string_multi.affine | 8 - .../tests/codegen/test_string_ptr.affine | 6 - .../tests/codegen/test_string_simple.affine | 6 - .../tests/codegen/test_tuple_five.affine | 10 - .../tests/codegen/test_tuple_nested.affine | 8 - .../tests/codegen/test_tuple_pair.affine | 6 - .../codegen/test_tuple_record_array.affine | 10 - .../tests/codegen/test_tuple_record_array.mjs | 19 - .../tests/codegen/test_tuple_simple.affine | 6 - .../tests/codegen/test_variant_as_int.affine | 12 - .../codegen/test_variant_match_arg.affine | 17 - .../codegen/test_variant_match_none.affine | 17 - .../codegen/test_variant_multi_arg.affine | 11 - .../codegen/test_variant_with_arg.affine | 11 - .../tests/codegen/test_while_loop.affine | 11 - affinescript/tests/conformance/README.adoc | 174 -- .../tests/conformance/arithmetic/add.affine | 64 - .../conformance/arithmetic/divide.affine | 72 - .../conformance/arithmetic/modulo.affine | 64 - .../conformance/arithmetic/multiply.affine | 72 - .../conformance/arithmetic/subtract.affine | 72 - .../conformance/collection/contains.affine | 78 - .../conformance/collection/filter.affine | 69 - .../tests/conformance/collection/fold.affine | 78 - .../tests/conformance/collection/map.affine | 69 - .../tests/conformance/comparison/equal.affine | 59 - .../comparison/greater_equal.affine | 47 - .../comparison/greater_than.affine | 47 - .../conformance/comparison/less_equal.affine | 47 - .../conformance/comparison/less_than.affine | 47 - .../conformance/comparison/not_equal.affine | 47 - .../conditional/if_then_else.affine | 59 - .../tests/conformance/logical/and.affine | 41 - .../tests/conformance/logical/not.affine | 29 - .../tests/conformance/logical/or.affine | 41 - affinescript/tests/conformance/run_all.affine | 214 -- .../tests/conformance/string/concat.affine | 52 - .../tests/conformance/string/length.affine | 52 - .../tests/conformance/string/substring.affine | 55 - .../tests/effects/basic_effect.affine | 17 - affinescript/tests/modules/test_const.affine | 7 - affinescript/tests/modules/test_import.affine | 15 - .../tests/modules/test_import_only.affine | 10 - .../tests/modules/test_math_functions.affine | 13 - .../tests/modules/test_no_import.affine | 13 - .../tests/modules/test_simple_import.affine | 10 - .../tests/parser/baseline_block.affine | 4 - .../tests/parser/baseline_stmt.affine | 4 - affinescript/tests/parser/unsafe_all.affine | 46 - affinescript/tests/parser/unsafe_empty.affine | 4 - affinescript/tests/parser/unsafe_ops.affine | 45 - affinescript/tests/parser/unsafe_read.affine | 7 - .../tests/parser/unsafe_simple.affine | 10 - affinescript/tests/parser/unsafe_stmt.affine | 4 - .../tests/types/test_dependent_e2e.affine | 23 - .../tests/types/test_dependent_parsing.affine | 25 - .../tests/types/test_effect_inference.affine | 28 - .../tests/types/test_effect_lambda.affine | 23 - .../types/test_effect_polymorphism.affine | 28 - .../types/test_generic_programming.affine | 39 - .../tests/types/test_hkt_parsing.affine | 33 - .../tests/types/test_kind_checking.affine | 25 - .../tests/types/test_lambda_scope_fix.affine | 27 - .../types/test_lambda_scope_simple.affine | 17 - .../tests/types/test_parse_row_type.affine | 12 - .../tests/types/test_row_polymorphism.affine | 30 - .../tests/types/test_row_simple.affine | 13 - affinescript/tests/types/test_traits.affine | 27 - affinescript/tools/affine-doc/Cargo.toml | 55 - .../tools/affine-doc/assets/search.js | 204 -- .../tools/affine-doc/assets/style.css | 228 -- affinescript/tools/affine-doc/src/extract.rs | 293 -- affinescript/tools/affine-doc/src/html.rs | 269 -- affinescript/tools/affine-doc/src/index.rs | 219 -- affinescript/tools/affine-doc/src/main.rs | 137 - affinescript/tools/affine-doc/src/render.rs | 121 - affinescript/tools/affine-pkg/Cargo.toml | 65 - affinescript/tools/affine-pkg/src/build.rs | 234 -- affinescript/tools/affine-pkg/src/config.rs | 234 -- affinescript/tools/affine-pkg/src/lockfile.rs | 147 - affinescript/tools/affine-pkg/src/main.rs | 324 --- affinescript/tools/affine-pkg/src/manifest.rs | 223 -- affinescript/tools/affine-pkg/src/registry.rs | 260 -- affinescript/tools/affine-pkg/src/resolve.rs | 191 -- affinescript/tools/affine-pkg/src/storage.rs | 196 -- .../tools/affine-pkg/src/workspace.rs | 164 -- .../tools/affinescript-dap/Cargo.toml | 25 - .../tools/affinescript-dap/src/main.rs | 172 -- .../tools/affinescript-lsp/Cargo.lock | 1344 --------- .../tools/affinescript-lsp/Cargo.toml | 51 - .../affinescript-lsp/src/capabilities.rs | 148 - .../tools/affinescript-lsp/src/diagnostics.rs | 180 -- .../tools/affinescript-lsp/src/document.rs | 179 -- .../tools/affinescript-lsp/src/handlers.rs | 830 ------ .../tools/affinescript-lsp/src/main.rs | 517 ---- .../tools/affinescript-lsp/src/symbols.rs | 186 -- .../tools/affinescript-lsp/src/text_index.rs | 382 --- affinescript/tools/alib_conformance_gen.jl | 176 -- affinescript/tools/run_codegen_wasm_tests.sh | 35 - affinescript/wiki/README.md | 166 -- affinescript/wiki/compiler/architecture.md | 428 --- affinescript/wiki/compiler/borrow-checker.md | 379 --- affinescript/wiki/compiler/codegen.md | 435 --- affinescript/wiki/compiler/lexer.md | 364 --- affinescript/wiki/compiler/parser.md | 340 --- affinescript/wiki/compiler/type-checker.md | 410 --- .../language-reference/dependent-types.md | 459 --- .../wiki/language-reference/effects.md | 582 ---- .../wiki/language-reference/functions.md | 498 ---- .../wiki/language-reference/lexical.md | 488 ---- .../wiki/language-reference/modules.md | 472 --- .../wiki/language-reference/ownership.md | 503 ---- .../wiki/language-reference/patterns.md | 393 --- affinescript/wiki/language-reference/rows.md | 453 --- .../wiki/language-reference/traits.md | 488 ---- affinescript/wiki/language-reference/types.md | 607 ---- affinescript/wiki/stdlib/overview.md | 425 --- affinescript/wiki/testing/guide.md | 631 ---- affinescript/wiki/testing/property-based.md | 395 --- affinescript/wiki/tooling/cli.md | 306 -- affinescript/wiki/tooling/repl.md | 293 -- affinescript/wiki/tutorials/introduction.md | 177 -- affinescript/wiki/tutorials/quickstart.md | 403 --- 616 files changed, 95877 deletions(-) delete mode 100644 affinescript/.build/.ocamlformat delete mode 100644 affinescript/.build/Justfile delete mode 100644 affinescript/.build/Mustfile delete mode 100644 affinescript/.build/affinescript.opam delete mode 100644 affinescript/.build/dune-project delete mode 100644 affinescript/.claude/CLAUDE.md delete mode 100644 affinescript/.editorconfig delete mode 100644 affinescript/.gitattributes delete mode 100644 affinescript/.github/FUNDING.yml delete mode 100644 affinescript/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 affinescript/.github/ISSUE_TEMPLATE/documentation.md delete mode 100644 affinescript/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 affinescript/.github/ISSUE_TEMPLATE/question.md delete mode 100644 affinescript/.github/dependabot.yml delete mode 100644 affinescript/.github/workflows/casket-pages.yml delete mode 100644 affinescript/.github/workflows/ci.yml delete mode 100644 affinescript/.github/workflows/codeql.yml delete mode 100644 affinescript/.github/workflows/guix-nix-policy.yml delete mode 100644 affinescript/.github/workflows/hypatia-scan.yml delete mode 100644 affinescript/.github/workflows/instant-sync.yml delete mode 100644 affinescript/.github/workflows/mirror.yml delete mode 100644 affinescript/.github/workflows/npm-bun-blocker.yml delete mode 100644 affinescript/.github/workflows/quality.yml delete mode 100644 affinescript/.github/workflows/release.yml delete mode 100644 affinescript/.github/workflows/scorecard-enforcer.yml delete mode 100644 affinescript/.github/workflows/scorecard.yml delete mode 100644 affinescript/.github/workflows/secret-scanner.yml delete mode 100644 affinescript/.github/workflows/security-policy.yml delete mode 100644 affinescript/.github/workflows/semgrep.yml delete mode 100644 affinescript/.github/workflows/ts-blocker.yml delete mode 100644 affinescript/.github/workflows/wellknown-enforcement.yml delete mode 100644 affinescript/.github/workflows/workflow-linter.yml delete mode 100644 affinescript/.gitignore delete mode 100644 affinescript/.machine_readable/6a2/AGENTIC.a2ml delete mode 100644 affinescript/.machine_readable/6a2/ECOSYSTEM.a2ml delete mode 100644 affinescript/.machine_readable/6a2/META.a2ml delete mode 100644 affinescript/.machine_readable/6a2/NEUROSYM.a2ml delete mode 100644 affinescript/.machine_readable/6a2/PLAYBOOK.a2ml delete mode 100644 affinescript/.machine_readable/6a2/STATE.a2ml delete mode 100644 affinescript/.machine_readable/CLADE.a2ml delete mode 100644 affinescript/.machine_readable/anchors/ANCHOR.a2ml delete mode 100644 affinescript/.machine_readable/bot_directives/README.adoc delete mode 100644 affinescript/.machine_readable/bot_directives/coverage.a2ml delete mode 100644 affinescript/.machine_readable/bot_directives/debt.a2ml delete mode 100644 affinescript/.machine_readable/bot_directives/methodology.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/Adjustfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/Intentfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/Mustfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/README.adoc delete mode 100644 affinescript/.machine_readable/contractiles/Trustfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/adjust/Adjustfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/dust/Dustfile delete mode 100644 affinescript/.machine_readable/contractiles/intend/Intentfile.a2ml delete mode 100644 affinescript/.machine_readable/contractiles/must/Mustfile delete mode 100644 affinescript/.machine_readable/integrations/verisimdb.a2ml delete mode 100644 affinescript/.machine_readable/integrations/vexometer.a2ml delete mode 100644 affinescript/.well-known/dnt-policy.txt delete mode 100644 affinescript/.well-known/groove/manifest.json delete mode 100644 affinescript/.well-known/keybase.txt delete mode 100644 affinescript/.well-known/security.txt delete mode 100644 affinescript/.well-known/sellers.json delete mode 100644 affinescript/.well-known/trust.txt delete mode 100644 affinescript/ABI-FFI-README.adoc delete mode 100644 affinescript/AI.a2ml delete mode 100644 affinescript/AI.djot delete mode 100644 affinescript/ALPHA-1-RELEASE-NOTES.adoc delete mode 100644 affinescript/BACKEND-ANALYSIS.adoc delete mode 100644 affinescript/BACKEND-IMPLEMENTATION.adoc delete mode 100644 affinescript/CNAME delete mode 100644 affinescript/CODE_OF_CONDUCT.adoc delete mode 100644 affinescript/COMPILER-CAPABILITIES.adoc delete mode 100644 affinescript/CONTRIBUTING.adoc delete mode 100644 affinescript/CONTROLS-REFERENCE.adoc delete mode 100644 affinescript/DAMAGE-SYSTEM.adoc delete mode 100644 affinescript/EXPLAINME.adoc delete mode 100644 affinescript/FIXES_2026-01-23.adoc delete mode 100644 affinescript/GAME-BUNDLING-STRATEGY.adoc delete mode 100644 affinescript/Justfile delete mode 100644 affinescript/LICENSE delete mode 100644 affinescript/LICENSE-AGPL-3.0 delete mode 100644 affinescript/LICENSES/LICENSE delete mode 100644 affinescript/LICENSES/LICENSE-AGPL-3.0 delete mode 100644 affinescript/LICENSES/LICENSE-PMPL-1.0 delete mode 100644 affinescript/LICENSING-GUIDE.adoc delete mode 100644 affinescript/MAINTAINERS.adoc delete mode 100644 affinescript/MODULE-SYSTEM-PROGRESS.adoc delete mode 100644 affinescript/NAVIGATION.adoc delete mode 100644 affinescript/PHASE3-ASSESSMENT.adoc delete mode 100644 affinescript/PHASE3-COMPLETE.adoc delete mode 100644 affinescript/PHASE3-SESSION-SUMMARY.adoc delete mode 100644 affinescript/PROOF-NEEDS.adoc delete mode 100644 affinescript/README.adoc delete mode 100644 affinescript/REPLY-SUM-TYPES.adoc delete mode 100644 affinescript/ROADMAP.adoc delete mode 100644 affinescript/RSR_OUTLINE.adoc delete mode 100644 affinescript/SECURITY-SETUP.adoc delete mode 100644 affinescript/SECURITY.adoc delete mode 100644 affinescript/SESSION-2026-01-23.adoc delete mode 100644 affinescript/SESSION-COMPLETE.adoc delete mode 100644 affinescript/SPARK-experiment/README.adoc delete mode 100644 affinescript/TRAIT_SYSTEM_STATUS.adoc delete mode 100644 affinescript/ads.txt delete mode 100644 affinescript/affinescript.opam delete mode 100644 affinescript/affinescript.runbook.a2ml delete mode 100644 affinescript/app-ads.txt delete mode 100644 affinescript/bench/dune delete mode 100644 affinescript/conformance/README.adoc delete mode 100644 affinescript/conformance/invalid/001_unclosed_brace.affine delete mode 100644 affinescript/conformance/invalid/001_unclosed_brace.expected delete mode 100644 affinescript/conformance/invalid/002_unclosed_string.affine delete mode 100644 affinescript/conformance/invalid/002_unclosed_string.expected delete mode 100644 affinescript/conformance/invalid/003_bad_number.affine delete mode 100644 affinescript/conformance/invalid/003_bad_number.expected delete mode 100644 affinescript/conformance/invalid/004_unexpected_token.affine delete mode 100644 affinescript/conformance/invalid/004_unexpected_token.expected delete mode 100644 affinescript/conformance/invalid/005_missing_arrow.affine delete mode 100644 affinescript/conformance/invalid/005_missing_arrow.expected delete mode 100644 affinescript/conformance/invalid/006_bad_operator.affine delete mode 100644 affinescript/conformance/invalid/006_bad_operator.expected delete mode 100644 affinescript/conformance/invalid/007_incomplete_effect.affine delete mode 100644 affinescript/conformance/invalid/007_incomplete_effect.expected delete mode 100644 affinescript/conformance/invalid/008_mismatched_parens.affine delete mode 100644 affinescript/conformance/invalid/008_mismatched_parens.expected delete mode 100644 affinescript/conformance/invalid/009_reserved_keyword.affine delete mode 100644 affinescript/conformance/invalid/009_reserved_keyword.expected delete mode 100644 affinescript/conformance/invalid/010_empty_match.affine delete mode 100644 affinescript/conformance/invalid/010_empty_match.expected delete mode 100644 affinescript/conformance/invalid/011_bad_escape.affine delete mode 100644 affinescript/conformance/invalid/011_bad_escape.expected delete mode 100644 affinescript/conformance/invalid/012_trailing_comma.affine delete mode 100644 affinescript/conformance/invalid/012_trailing_comma.expected delete mode 100644 affinescript/conformance/valid/001_empty.affine delete mode 100644 affinescript/conformance/valid/001_empty.expected delete mode 100644 affinescript/conformance/valid/002_comments.affine delete mode 100644 affinescript/conformance/valid/002_comments.expected delete mode 100644 affinescript/conformance/valid/003_let_bindings.affine delete mode 100644 affinescript/conformance/valid/003_let_bindings.expected delete mode 100644 affinescript/conformance/valid/004_literals.affine delete mode 100644 affinescript/conformance/valid/004_literals.expected delete mode 100644 affinescript/conformance/valid/005_functions.affine delete mode 100644 affinescript/conformance/valid/005_functions.expected delete mode 100644 affinescript/conformance/valid/006_type_alias.affine delete mode 100644 affinescript/conformance/valid/006_type_alias.expected delete mode 100644 affinescript/conformance/valid/007_struct_def.affine delete mode 100644 affinescript/conformance/valid/007_struct_def.expected delete mode 100644 affinescript/conformance/valid/008_enum_def.affine delete mode 100644 affinescript/conformance/valid/008_enum_def.expected delete mode 100644 affinescript/conformance/valid/009_effect_def.affine delete mode 100644 affinescript/conformance/valid/009_effect_def.expected delete mode 100644 affinescript/conformance/valid/010_pattern_match.affine delete mode 100644 affinescript/conformance/valid/010_pattern_match.expected delete mode 100644 affinescript/conformance/valid/011_rows.affine delete mode 100644 affinescript/conformance/valid/011_rows.expected delete mode 100644 affinescript/conformance/valid/012_ownership.affine delete mode 100644 affinescript/conformance/valid/012_ownership.expected delete mode 100644 affinescript/distributions/rattlescript/Cargo.toml delete mode 100644 affinescript/distributions/rattlescript/Justfile delete mode 100644 affinescript/distributions/rattlescript/README.adoc delete mode 100644 affinescript/distributions/rattlescript/build.rs delete mode 100644 affinescript/distributions/rattlescript/examples/hello.rattle delete mode 100644 affinescript/distributions/rattlescript/examples/ownership.rattle delete mode 100644 affinescript/distributions/rattlescript/manifest.scm delete mode 100644 affinescript/distributions/rattlescript/src/main.rs delete mode 100644 affinescript/docs/.well-known/dnt-policy.txt delete mode 100644 affinescript/docs/.well-known/keybase.txt delete mode 100644 affinescript/docs/.well-known/security.txt delete mode 100644 affinescript/docs/.well-known/sellers.json delete mode 100644 affinescript/docs/.well-known/trust.txt delete mode 100644 affinescript/docs/ALIB-INTEGRATION.adoc delete mode 100644 affinescript/docs/BRIDGE-DECISIONS.adoc delete mode 100644 affinescript/docs/CITATIONS.adoc delete mode 100644 affinescript/docs/CNAME delete mode 100644 affinescript/docs/DESIGN-VISION.adoc delete mode 100644 affinescript/docs/EFFECTS-IMPLEMENTATION.adoc delete mode 100644 affinescript/docs/README.adoc delete mode 100644 affinescript/docs/WASM-EFFECT-HANDLER-WORKAROUND.adoc delete mode 100644 affinescript/docs/academic/README.adoc delete mode 100644 affinescript/docs/academic/formal-verification/axiomatic-semantics.adoc delete mode 100644 affinescript/docs/academic/formal-verification/denotational-semantics.adoc delete mode 100644 affinescript/docs/academic/formal-verification/operational-semantics.adoc delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/Context.idr delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/Quantity.idr delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/README.adoc delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/Soundness.idr delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/Syntax.idr delete mode 100644 affinescript/docs/academic/formal-verification/solo-core/Typing.idr delete mode 100644 affinescript/docs/academic/mathematical-foundations/categorical-semantics.adoc delete mode 100644 affinescript/docs/academic/mathematical-foundations/complexity-analysis.adoc delete mode 100644 affinescript/docs/academic/mathematical-foundations/logic-foundations.adoc delete mode 100644 affinescript/docs/academic/mechanized/agda/README.adoc delete mode 100644 affinescript/docs/academic/mechanized/coq/README.adoc delete mode 100644 affinescript/docs/academic/mechanized/lean/README.adoc delete mode 100644 affinescript/docs/academic/proofs/coherence-parametricity.adoc delete mode 100644 affinescript/docs/academic/proofs/dependent-types.adoc delete mode 100644 affinescript/docs/academic/proofs/effect-soundness.adoc delete mode 100644 affinescript/docs/academic/proofs/inference-algorithm.adoc delete mode 100644 affinescript/docs/academic/proofs/ownership-soundness.adoc delete mode 100644 affinescript/docs/academic/proofs/quantitative-types.adoc delete mode 100644 affinescript/docs/academic/proofs/row-polymorphism.adoc delete mode 100644 affinescript/docs/academic/proofs/type-soundness.adoc delete mode 100644 affinescript/docs/academic/tropical-session-types/TropicalSessionTypes.lean delete mode 100644 affinescript/docs/academic/tropical-session-types/tropical-unification-2026-04-04.adoc delete mode 100644 affinescript/docs/academic/white-papers/language-design.adoc delete mode 100644 affinescript/docs/ads.txt delete mode 100644 affinescript/docs/alib/README.adoc delete mode 100644 affinescript/docs/app-ads.txt delete mode 100644 affinescript/docs/governance/CODE_OF_CONDUCT.adoc delete mode 100644 affinescript/docs/governance/CONTRIBUTING.adoc delete mode 100644 affinescript/docs/governance/MAINTAINERS.adoc delete mode 100644 affinescript/docs/governance/SECURITY.adoc delete mode 100644 affinescript/docs/guides/TESTING-REPORT.adoc delete mode 100644 affinescript/docs/guides/TESTING-REPORT.scm delete mode 100644 affinescript/docs/guides/WHAT-MAKES-IT-BRILLIANT.adoc delete mode 100644 affinescript/docs/guides/frontier-guide.adoc delete mode 100644 affinescript/docs/guides/frontier-programming-practices/AI.a2ml delete mode 100644 affinescript/docs/guides/frontier-programming-practices/Human_Programming_Guide.adoc delete mode 100644 affinescript/docs/guides/lessons/01-hello-affinescript.adoc delete mode 100644 affinescript/docs/guides/lessons/README.adoc delete mode 100644 affinescript/docs/guides/warmup/01_basics.affine delete mode 100644 affinescript/docs/guides/warmup/02_ownership.affine delete mode 100644 affinescript/docs/guides/warmup/03_effects.affine delete mode 100644 affinescript/docs/guides/warmup/04_rows.affine delete mode 100644 affinescript/docs/guides/warmup/README.adoc delete mode 100644 affinescript/docs/history/TYPECHECKER-COMPLETION-2026-01-23.adoc delete mode 100644 affinescript/docs/humans.txt delete mode 100644 affinescript/docs/spec.adoc delete mode 100644 affinescript/docs/specs/SETTLED-DECISIONS.adoc delete mode 100644 affinescript/docs/specs/SPEC.adoc delete mode 100644 affinescript/docs/specs/faces.adoc delete mode 100644 affinescript/docs/standards/DECISIONS.adoc delete mode 100644 affinescript/docs/standards/PALIMPSEST.adoc delete mode 100644 affinescript/docs/standards/RELEASE.adoc delete mode 100644 affinescript/docs/standards/ROADMAP.adoc delete mode 100644 affinescript/docs/tutorial/lesson-02-functions.adoc delete mode 100644 affinescript/docs/tutorial/lesson-03-data.adoc delete mode 100644 affinescript/docs/tutorial/lesson-04-patterns.adoc delete mode 100644 affinescript/docs/tutorial/lesson-05-types.adoc delete mode 100644 affinescript/docs/tutorial/lesson-06-errors.adoc delete mode 100644 affinescript/docs/tutorial/lesson-07-effects.adoc delete mode 100644 affinescript/docs/tutorial/lesson-08-generics.adoc delete mode 100644 affinescript/docs/tutorial/lesson-09-modules.adoc delete mode 100644 affinescript/docs/tutorial/lesson-10-building.adoc delete mode 100644 affinescript/dune-project delete mode 100644 affinescript/editors/tree-sitter-affinescript/README.adoc delete mode 100644 affinescript/editors/tree-sitter-affinescript/grammar.js delete mode 100644 affinescript/editors/tree-sitter-affinescript/package.json delete mode 100644 affinescript/editors/tree-sitter-affinescript/queries/highlights.scm delete mode 100644 affinescript/editors/vscode/README.adoc delete mode 100644 affinescript/editors/vscode/language-configuration.json delete mode 100644 affinescript/editors/vscode/package.json delete mode 100644 affinescript/editors/vscode/src/extension.affine delete mode 100644 affinescript/editors/vscode/syntaxes/affinescript.tmLanguage.json delete mode 100644 affinescript/examples/comprehensive_test.affine delete mode 100644 affinescript/examples/counter.afs delete mode 100644 affinescript/examples/effects.affine delete mode 100644 affinescript/examples/exception_test.affine delete mode 100644 affinescript/examples/hello.affine delete mode 100644 affinescript/examples/lessons/01_hello.affine delete mode 100644 affinescript/examples/ownership.affine delete mode 100644 affinescript/examples/rows.affine delete mode 100644 affinescript/examples/simple_test.affine delete mode 100644 affinescript/examples/titlescreen.afs delete mode 100644 affinescript/examples/traits.affine delete mode 100644 affinescript/examples/typecheck_complete_test.affine delete mode 100644 affinescript/examples/typecheck_features_test.affine delete mode 100644 affinescript/examples/web-project-deno.json delete mode 100644 affinescript/ffi/zig/build.zig delete mode 100644 affinescript/ffi/zig/src/main.zig delete mode 100644 affinescript/ffi/zig/test/integration_test.zig delete mode 100755 affinescript/hooks/validate-codeql.sh delete mode 100755 affinescript/hooks/validate-permissions.sh delete mode 100755 affinescript/hooks/validate-sha-pins.sh delete mode 100755 affinescript/hooks/validate-spdx.sh delete mode 100644 affinescript/humans.txt delete mode 100644 affinescript/js/dune delete mode 100644 affinescript/js/js_api.ml delete mode 100644 affinescript/js/playground.ml delete mode 100644 affinescript/js/runtime.js delete mode 100644 affinescript/lib/ast.ml delete mode 100644 affinescript/lib/borrow.ml delete mode 100644 affinescript/lib/codegen.ml delete mode 100644 affinescript/lib/codegen_gc.ml delete mode 100644 affinescript/lib/desugar_traits.ml delete mode 100644 affinescript/lib/dune delete mode 100644 affinescript/lib/effect.ml delete mode 100644 affinescript/lib/effect.mli delete mode 100644 affinescript/lib/error.ml delete mode 100644 affinescript/lib/error_collector.ml delete mode 100644 affinescript/lib/error_formatter.ml delete mode 100644 affinescript/lib/error_formatter.mli delete mode 100644 affinescript/lib/face.ml delete mode 100644 affinescript/lib/face.mli delete mode 100644 affinescript/lib/formatter.ml delete mode 100644 affinescript/lib/formatter.mli.disabled delete mode 100644 affinescript/lib/interp.ml delete mode 100644 affinescript/lib/js_face.ml delete mode 100644 affinescript/lib/json_output.ml delete mode 100644 affinescript/lib/julia_codegen.ml delete mode 100644 affinescript/lib/lexer.ml delete mode 100644 affinescript/lib/linter.ml delete mode 100644 affinescript/lib/linter.mli.disabled delete mode 100644 affinescript/lib/lsp_server.ml delete mode 100644 affinescript/lib/module_loader.ml delete mode 100644 affinescript/lib/opt.ml delete mode 100644 affinescript/lib/parse.ml delete mode 100644 affinescript/lib/parse_driver.ml delete mode 100644 affinescript/lib/parser.mly delete mode 100644 affinescript/lib/parser_errors.ml delete mode 100644 affinescript/lib/python_face.ml delete mode 100644 affinescript/lib/quantity.ml delete mode 100644 affinescript/lib/repl.ml delete mode 100644 affinescript/lib/resolve.ml delete mode 100644 affinescript/lib/sexpr_dump.ml delete mode 100644 affinescript/lib/span.ml delete mode 100644 affinescript/lib/symbol.ml delete mode 100644 affinescript/lib/tea_bridge.ml delete mode 100644 affinescript/lib/tea_cs_bridge.ml delete mode 100644 affinescript/lib/tea_router.ml delete mode 100644 affinescript/lib/token.ml delete mode 100644 affinescript/lib/trait.ml delete mode 100644 affinescript/lib/tw_interface.ml delete mode 100644 affinescript/lib/tw_verify.ml delete mode 100644 affinescript/lib/typecheck.ml delete mode 100644 affinescript/lib/types.ml delete mode 100644 affinescript/lib/unify.ml delete mode 100644 affinescript/lib/value.ml delete mode 100644 affinescript/lib/wasi_runtime.ml delete mode 100644 affinescript/lib/wasm.ml delete mode 100644 affinescript/lib/wasm_encode.ml delete mode 100644 affinescript/lib/wasm_gc.ml delete mode 100644 affinescript/lib/wasm_gc_encode.ml delete mode 100644 affinescript/manifest.scm delete mode 100644 affinescript/opsm.toml delete mode 100644 affinescript/packages/affine-js/README.adoc delete mode 100644 affinescript/packages/affine-js/marshal.js delete mode 100644 affinescript/packages/affine-js/mod.js delete mode 100644 affinescript/packages/affine-js/runtime.js delete mode 100644 affinescript/packages/affine-js/types.d.affine delete mode 100644 affinescript/packages/affine-res/README.adoc delete mode 100644 affinescript/packages/affine-res/src/AffineScript.affine delete mode 100644 affinescript/packages/affine-res/src/AffineScript.resi delete mode 100644 affinescript/packages/affine-res/src/AffineScriptValue.affine delete mode 100644 affinescript/packages/affine-ts/README.adoc delete mode 100644 affinescript/packages/affine-ts/mod.js delete mode 100644 affinescript/packages/affine-ts/types.d.affine delete mode 100644 affinescript/panic-attack.toml delete mode 100644 affinescript/run_wasm.js delete mode 100644 affinescript/runtime/Cargo.toml delete mode 100644 affinescript/runtime/src/alloc.rs delete mode 100644 affinescript/runtime/src/effects.rs delete mode 100644 affinescript/runtime/src/ffi.rs delete mode 100644 affinescript/runtime/src/gc.rs delete mode 100644 affinescript/runtime/src/lib.rs delete mode 100644 affinescript/runtime/src/panic.rs delete mode 100644 affinescript/site/index.adoc delete mode 100644 affinescript/stdlib/Core.affine delete mode 100644 affinescript/stdlib/Math.affine delete mode 100644 affinescript/stdlib/Option.affine delete mode 100644 affinescript/stdlib/README.adoc delete mode 100644 affinescript/stdlib/Result.affine delete mode 100644 affinescript/stdlib/collections.affine delete mode 100644 affinescript/stdlib/effects.affine delete mode 100644 affinescript/stdlib/io.affine delete mode 100644 affinescript/stdlib/math.affine delete mode 100644 affinescript/stdlib/option.affine delete mode 100644 affinescript/stdlib/prelude.affine delete mode 100644 affinescript/stdlib/result.affine delete mode 100644 affinescript/stdlib/string.affine delete mode 100644 affinescript/stdlib/testing.affine delete mode 100644 affinescript/stdlib/traits.affine delete mode 100644 affinescript/test/dune delete mode 100644 affinescript/test/e2e/fixtures/affine_basic.affine delete mode 100644 affinescript/test/e2e/fixtures/affine_let_valid.affine delete mode 100644 affinescript/test/e2e/fixtures/affine_let_valid_sugar.affine delete mode 100644 affinescript/test/e2e/fixtures/affine_violation.affine delete mode 100644 affinescript/test/e2e/fixtures/arithmetic.affine delete mode 100644 affinescript/test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine delete mode 100644 affinescript/test/e2e/fixtures/bug_001_sugar_form.affine delete mode 100644 affinescript/test/e2e/fixtures/cmd_dropped.affine delete mode 100644 affinescript/test/e2e/fixtures/cmd_linear.affine delete mode 100644 affinescript/test/e2e/fixtures/counter.affine delete mode 100644 affinescript/test/e2e/fixtures/dependent_types.affine delete mode 100644 affinescript/test/e2e/fixtures/effects.affine delete mode 100644 affinescript/test/e2e/fixtures/erased_violation.affine delete mode 100644 affinescript/test/e2e/fixtures/full_pipeline.affine delete mode 100644 affinescript/test/e2e/fixtures/interp_simple.affine delete mode 100644 affinescript/test/e2e/fixtures/julia_simple.affine delete mode 100644 affinescript/test/e2e/fixtures/lambda.affine delete mode 100644 affinescript/test/e2e/fixtures/linear_arrow.affine delete mode 100644 affinescript/test/e2e/fixtures/linear_arrow_violation.affine delete mode 100644 affinescript/test/e2e/fixtures/ownership.affine delete mode 100644 affinescript/test/e2e/fixtures/ownership_codegen.affine delete mode 100644 affinescript/test/e2e/fixtures/pattern_match.affine delete mode 100644 affinescript/test/e2e/fixtures/python_face_basic.pyaff delete mode 100644 affinescript/test/e2e/fixtures/refinement_types.affine delete mode 100644 affinescript/test/e2e/fixtures/row_polymorphism.affine delete mode 100644 affinescript/test/e2e/fixtures/titlescreen.affine delete mode 100644 affinescript/test/e2e/fixtures/trait_impl_missing_method.affine delete mode 100644 affinescript/test/e2e/fixtures/trait_impl_valid.affine delete mode 100644 affinescript/test/e2e/fixtures/traits.affine delete mode 100644 affinescript/test/e2e/fixtures/try_body_only.affine delete mode 100644 affinescript/test/e2e/fixtures/try_catch_finally.affine delete mode 100644 affinescript/test/e2e/fixtures/try_catch_var.affine delete mode 100644 affinescript/test/e2e/fixtures/try_catch_wildcard.affine delete mode 100644 affinescript/test/e2e/fixtures/try_finally.affine delete mode 100644 affinescript/test/e2e/fixtures/type_decls.affine delete mode 100644 affinescript/test/e2e/fixtures/verify_ownership_clean.affine delete mode 100644 affinescript/test/e2e/fixtures/wasm_simple.affine delete mode 100644 affinescript/test/golden/binary_ops.affine delete mode 100644 affinescript/test/golden/binary_ops.expected delete mode 100644 affinescript/test/golden/effects.affine delete mode 100644 affinescript/test/golden/effects.expected delete mode 100644 affinescript/test/golden/functions.affine delete mode 100644 affinescript/test/golden/functions.expected delete mode 100644 affinescript/test/golden/hello.affine delete mode 100644 affinescript/test/golden/hello.expected delete mode 100644 affinescript/test/golden/imports.affine delete mode 100644 affinescript/test/golden/imports.expected delete mode 100644 affinescript/test/golden/lambda.affine delete mode 100644 affinescript/test/golden/lambda.expected delete mode 100644 affinescript/test/golden/literals.affine delete mode 100644 affinescript/test/golden/literals.expected delete mode 100644 affinescript/test/golden/ownership.affine delete mode 100644 affinescript/test/golden/ownership.expected delete mode 100644 affinescript/test/golden/patterns.affine delete mode 100644 affinescript/test/golden/patterns.expected delete mode 100644 affinescript/test/golden/rows.affine delete mode 100644 affinescript/test/golden/rows.expected delete mode 100644 affinescript/test/golden/traits.affine delete mode 100644 affinescript/test/golden/traits.expected delete mode 100644 affinescript/test/golden/types.affine delete mode 100644 affinescript/test/golden/types.expected delete mode 100644 affinescript/test/test_e2e.ml delete mode 100644 affinescript/test/test_golden.ml delete mode 100644 affinescript/test/test_lexer.ml delete mode 100644 affinescript/test/test_main.ml delete mode 100644 affinescript/test_if.affine delete mode 100644 affinescript/test_min.affine delete mode 100644 affinescript/test_simple.affine delete mode 100644 affinescript/test_spread.affine delete mode 100644 affinescript/test_spread2.affine delete mode 100644 affinescript/test_spread3.affine delete mode 100644 affinescript/tests/borrow/minimal.affine delete mode 100644 affinescript/tests/borrow/move_test.affine delete mode 100644 affinescript/tests/borrow/simple.affine delete mode 100644 affinescript/tests/borrow/simple_move_test.affine delete mode 100644 affinescript/tests/borrow/simple_own.affine delete mode 100644 affinescript/tests/borrow/test_immutable_assign_error.affine delete mode 100644 affinescript/tests/borrow/test_move.affine delete mode 100644 affinescript/tests/borrow/test_move_after_use.affine delete mode 100644 affinescript/tests/borrow/test_mutable_borrow.affine delete mode 100644 affinescript/tests/borrow/test_ref_borrow.affine delete mode 100644 affinescript/tests/borrow/use_after_move.affine delete mode 100644 affinescript/tests/borrow/valid_move.affine delete mode 100644 affinescript/tests/codegen/README.adoc delete mode 100644 affinescript/tests/codegen/simple_arithmetic.affine delete mode 100644 affinescript/tests/codegen/test_array_bounds.affine delete mode 100644 affinescript/tests/codegen/test_array_empty.affine delete mode 100644 affinescript/tests/codegen/test_array_simple.affine delete mode 100644 affinescript/tests/codegen/test_array_single.affine delete mode 100644 affinescript/tests/codegen/test_array_sum.affine delete mode 100644 affinescript/tests/codegen/test_assign_compound.affine delete mode 100644 affinescript/tests/codegen/test_assign_simple.affine delete mode 100644 affinescript/tests/codegen/test_closure_capture.affine delete mode 100644 affinescript/tests/codegen/test_closure_debug.affine delete mode 100644 affinescript/tests/codegen/test_closure_multi_capture.affine delete mode 100644 affinescript/tests/codegen/test_enum_pattern.affine delete mode 100644 affinescript/tests/codegen/test_for_loop.affine delete mode 100644 affinescript/tests/codegen/test_function_call.affine delete mode 100644 affinescript/tests/codegen/test_function_call.mjs delete mode 100644 affinescript/tests/codegen/test_lambda_call.affine delete mode 100644 affinescript/tests/codegen/test_lambda_multi_param.affine delete mode 100644 affinescript/tests/codegen/test_match_literal.affine delete mode 100644 affinescript/tests/codegen/test_match_mixed.affine delete mode 100644 affinescript/tests/codegen/test_match_simple.affine delete mode 100644 affinescript/tests/codegen/test_multiple_calls.affine delete mode 100644 affinescript/tests/codegen/test_multiple_calls.mjs delete mode 100644 affinescript/tests/codegen/test_multiple_lambdas.affine delete mode 100644 affinescript/tests/codegen/test_opt_constant_fold.affine delete mode 100644 affinescript/tests/codegen/test_opt_if_fold.affine delete mode 100644 affinescript/tests/codegen/test_pattern_as.affine delete mode 100644 affinescript/tests/codegen/test_pattern_or.affine delete mode 100644 affinescript/tests/codegen/test_pattern_record.affine delete mode 100644 affinescript/tests/codegen/test_pattern_tuple.affine delete mode 100644 affinescript/tests/codegen/test_record_multi_field.affine delete mode 100644 affinescript/tests/codegen/test_record_multiple.affine delete mode 100644 affinescript/tests/codegen/test_record_simple.affine delete mode 100644 affinescript/tests/codegen/test_recursive_call.affine delete mode 100644 affinescript/tests/codegen/test_ref_deref_chain.affine delete mode 100644 affinescript/tests/codegen/test_ref_deref_simple.affine delete mode 100644 affinescript/tests/codegen/test_simple_lambda.affine delete mode 100644 affinescript/tests/codegen/test_string_diff.affine delete mode 100644 affinescript/tests/codegen/test_string_eq.affine delete mode 100644 affinescript/tests/codegen/test_string_multi.affine delete mode 100644 affinescript/tests/codegen/test_string_ptr.affine delete mode 100644 affinescript/tests/codegen/test_string_simple.affine delete mode 100644 affinescript/tests/codegen/test_tuple_five.affine delete mode 100644 affinescript/tests/codegen/test_tuple_nested.affine delete mode 100644 affinescript/tests/codegen/test_tuple_pair.affine delete mode 100644 affinescript/tests/codegen/test_tuple_record_array.affine delete mode 100644 affinescript/tests/codegen/test_tuple_record_array.mjs delete mode 100644 affinescript/tests/codegen/test_tuple_simple.affine delete mode 100644 affinescript/tests/codegen/test_variant_as_int.affine delete mode 100644 affinescript/tests/codegen/test_variant_match_arg.affine delete mode 100644 affinescript/tests/codegen/test_variant_match_none.affine delete mode 100644 affinescript/tests/codegen/test_variant_multi_arg.affine delete mode 100644 affinescript/tests/codegen/test_variant_with_arg.affine delete mode 100644 affinescript/tests/codegen/test_while_loop.affine delete mode 100644 affinescript/tests/conformance/README.adoc delete mode 100644 affinescript/tests/conformance/arithmetic/add.affine delete mode 100644 affinescript/tests/conformance/arithmetic/divide.affine delete mode 100644 affinescript/tests/conformance/arithmetic/modulo.affine delete mode 100644 affinescript/tests/conformance/arithmetic/multiply.affine delete mode 100644 affinescript/tests/conformance/arithmetic/subtract.affine delete mode 100644 affinescript/tests/conformance/collection/contains.affine delete mode 100644 affinescript/tests/conformance/collection/filter.affine delete mode 100644 affinescript/tests/conformance/collection/fold.affine delete mode 100644 affinescript/tests/conformance/collection/map.affine delete mode 100644 affinescript/tests/conformance/comparison/equal.affine delete mode 100644 affinescript/tests/conformance/comparison/greater_equal.affine delete mode 100644 affinescript/tests/conformance/comparison/greater_than.affine delete mode 100644 affinescript/tests/conformance/comparison/less_equal.affine delete mode 100644 affinescript/tests/conformance/comparison/less_than.affine delete mode 100644 affinescript/tests/conformance/comparison/not_equal.affine delete mode 100644 affinescript/tests/conformance/conditional/if_then_else.affine delete mode 100644 affinescript/tests/conformance/logical/and.affine delete mode 100644 affinescript/tests/conformance/logical/not.affine delete mode 100644 affinescript/tests/conformance/logical/or.affine delete mode 100644 affinescript/tests/conformance/run_all.affine delete mode 100644 affinescript/tests/conformance/string/concat.affine delete mode 100644 affinescript/tests/conformance/string/length.affine delete mode 100644 affinescript/tests/conformance/string/substring.affine delete mode 100644 affinescript/tests/effects/basic_effect.affine delete mode 100644 affinescript/tests/modules/test_const.affine delete mode 100644 affinescript/tests/modules/test_import.affine delete mode 100644 affinescript/tests/modules/test_import_only.affine delete mode 100644 affinescript/tests/modules/test_math_functions.affine delete mode 100644 affinescript/tests/modules/test_no_import.affine delete mode 100644 affinescript/tests/modules/test_simple_import.affine delete mode 100644 affinescript/tests/parser/baseline_block.affine delete mode 100644 affinescript/tests/parser/baseline_stmt.affine delete mode 100644 affinescript/tests/parser/unsafe_all.affine delete mode 100644 affinescript/tests/parser/unsafe_empty.affine delete mode 100644 affinescript/tests/parser/unsafe_ops.affine delete mode 100644 affinescript/tests/parser/unsafe_read.affine delete mode 100644 affinescript/tests/parser/unsafe_simple.affine delete mode 100644 affinescript/tests/parser/unsafe_stmt.affine delete mode 100644 affinescript/tests/types/test_dependent_e2e.affine delete mode 100644 affinescript/tests/types/test_dependent_parsing.affine delete mode 100644 affinescript/tests/types/test_effect_inference.affine delete mode 100644 affinescript/tests/types/test_effect_lambda.affine delete mode 100644 affinescript/tests/types/test_effect_polymorphism.affine delete mode 100644 affinescript/tests/types/test_generic_programming.affine delete mode 100644 affinescript/tests/types/test_hkt_parsing.affine delete mode 100644 affinescript/tests/types/test_kind_checking.affine delete mode 100644 affinescript/tests/types/test_lambda_scope_fix.affine delete mode 100644 affinescript/tests/types/test_lambda_scope_simple.affine delete mode 100644 affinescript/tests/types/test_parse_row_type.affine delete mode 100644 affinescript/tests/types/test_row_polymorphism.affine delete mode 100644 affinescript/tests/types/test_row_simple.affine delete mode 100644 affinescript/tests/types/test_traits.affine delete mode 100644 affinescript/tools/affine-doc/Cargo.toml delete mode 100644 affinescript/tools/affine-doc/assets/search.js delete mode 100644 affinescript/tools/affine-doc/assets/style.css delete mode 100644 affinescript/tools/affine-doc/src/extract.rs delete mode 100644 affinescript/tools/affine-doc/src/html.rs delete mode 100644 affinescript/tools/affine-doc/src/index.rs delete mode 100644 affinescript/tools/affine-doc/src/main.rs delete mode 100644 affinescript/tools/affine-doc/src/render.rs delete mode 100644 affinescript/tools/affine-pkg/Cargo.toml delete mode 100644 affinescript/tools/affine-pkg/src/build.rs delete mode 100644 affinescript/tools/affine-pkg/src/config.rs delete mode 100644 affinescript/tools/affine-pkg/src/lockfile.rs delete mode 100644 affinescript/tools/affine-pkg/src/main.rs delete mode 100644 affinescript/tools/affine-pkg/src/manifest.rs delete mode 100644 affinescript/tools/affine-pkg/src/registry.rs delete mode 100644 affinescript/tools/affine-pkg/src/resolve.rs delete mode 100644 affinescript/tools/affine-pkg/src/storage.rs delete mode 100644 affinescript/tools/affine-pkg/src/workspace.rs delete mode 100644 affinescript/tools/affinescript-dap/Cargo.toml delete mode 100644 affinescript/tools/affinescript-dap/src/main.rs delete mode 100644 affinescript/tools/affinescript-lsp/Cargo.lock delete mode 100644 affinescript/tools/affinescript-lsp/Cargo.toml delete mode 100644 affinescript/tools/affinescript-lsp/src/capabilities.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/diagnostics.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/document.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/handlers.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/main.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/symbols.rs delete mode 100644 affinescript/tools/affinescript-lsp/src/text_index.rs delete mode 100644 affinescript/tools/alib_conformance_gen.jl delete mode 100755 affinescript/tools/run_codegen_wasm_tests.sh delete mode 100644 affinescript/wiki/README.md delete mode 100644 affinescript/wiki/compiler/architecture.md delete mode 100644 affinescript/wiki/compiler/borrow-checker.md delete mode 100644 affinescript/wiki/compiler/codegen.md delete mode 100644 affinescript/wiki/compiler/lexer.md delete mode 100644 affinescript/wiki/compiler/parser.md delete mode 100644 affinescript/wiki/compiler/type-checker.md delete mode 100644 affinescript/wiki/language-reference/dependent-types.md delete mode 100644 affinescript/wiki/language-reference/effects.md delete mode 100644 affinescript/wiki/language-reference/functions.md delete mode 100644 affinescript/wiki/language-reference/lexical.md delete mode 100644 affinescript/wiki/language-reference/modules.md delete mode 100644 affinescript/wiki/language-reference/ownership.md delete mode 100644 affinescript/wiki/language-reference/patterns.md delete mode 100644 affinescript/wiki/language-reference/rows.md delete mode 100644 affinescript/wiki/language-reference/traits.md delete mode 100644 affinescript/wiki/language-reference/types.md delete mode 100644 affinescript/wiki/stdlib/overview.md delete mode 100644 affinescript/wiki/testing/guide.md delete mode 100644 affinescript/wiki/testing/property-based.md delete mode 100644 affinescript/wiki/tooling/cli.md delete mode 100644 affinescript/wiki/tooling/repl.md delete mode 100644 affinescript/wiki/tutorials/introduction.md delete mode 100644 affinescript/wiki/tutorials/quickstart.md diff --git a/affinescript/.build/.ocamlformat b/affinescript/.build/.ocamlformat deleted file mode 100644 index a73f3a9..0000000 --- a/affinescript/.build/.ocamlformat +++ /dev/null @@ -1,5 +0,0 @@ -version = 0.26.2 -profile = default -margin = 100 -parse-docstrings = true -break-infix = fit-or-vertical diff --git a/affinescript/.build/Justfile b/affinescript/.build/Justfile deleted file mode 100644 index faf997d..0000000 --- a/affinescript/.build/Justfile +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-or-later -# SPDX-FileCopyrightText: 2024-2025 hyperpolymath -# Justfile - hyperpolymath standard task runner for AffineScript - -# Show available recipes -default: - @just --list - -# Build the compiler -build: - dune build - -# Run all tests -test: - dune runtest - -# Run format check (lint) -lint: - dune fmt --check - -# Clean build artifacts -clean: - dune clean - -# Format code in place -fmt: - dune fmt - -# Run all checks (lint + test) -check: lint test - -# Build documentation -doc: - dune build @doc - -# Run the lexer on a file -lex FILE: - dune exec affinescript -- lex {{FILE}} - -# Run the parser on a file -parse FILE: - dune exec affinescript -- parse {{FILE}} - -# Run conformance tests -conformance: - dune runtest conformance - -# Verify golden path (smoke test) -golden-path: - @echo "=== Golden Path Verification ===" - @echo "1. Building..." - dune build - @echo "2. Running tests..." - dune runtest - @echo "3. Testing lexer on hello.as..." - dune exec affinescript -- lex examples/hello.as - @echo "4. Testing parser on ownership.as..." - dune exec affinescript -- parse examples/ownership.as - @echo "=== Golden Path Complete ===" - -# Prepare a release -release VERSION: - @echo "Releasing 0.0.1..." - @echo "=== Pre-release Checks ===" - just check - @echo "=== Updating Version ===" - # Update version in dune-project - sed -i 's/(version [^)]*/(version 0.0.1/' dune-project - @echo "=== Building Release ===" - dune build --release - @echo "=== Creating Git Tag ===" - git add -A - git commit -m "Release v0.0.1" - git tag -a "v0.0.1" -m "Release v0.0.1" - @echo "=== Release Complete ===" - @echo "To push: git push && git push --tags" - -secret-scan-trufflehog: - @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true diff --git a/affinescript/.build/Mustfile b/affinescript/.build/Mustfile deleted file mode 100644 index 2516d22..0000000 --- a/affinescript/.build/Mustfile +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile - hyperpolymath mandatory checks -# See: https://github.com/hyperpolymath/mustfile - -version: 1 - -checks: - - name: security - run: just lint - - name: tests - run: just test - - name: format - run: just fmt - diff --git a/affinescript/.build/affinescript.opam b/affinescript/.build/affinescript.opam deleted file mode 100644 index 1bc717a..0000000 --- a/affinescript/.build/affinescript.opam +++ /dev/null @@ -1,51 +0,0 @@ -# This file is generated by dune, edit dune-project instead -opam-version: "2.0" -version: "0.1.0" -synopsis: - "A programming language with affine types, dependent types, row polymorphism, and extensible effects" -description: - "AffineScript combines Rust-style ownership (affine types), compile-time size verification (dependent types), extensible records (row polymorphism), and trackable side effects (extensible effects). It compiles to WebAssembly for efficient, portable execution." -maintainer: ["hyperpolymath"] -authors: ["hyperpolymath"] -license: "MIT" -tags: [ - "programming-language" - "compiler" - "webassembly" - "affine-types" - "dependent-types" - "row-polymorphism" - "effects" - "ocaml" -] -homepage: "https://github.com/hyperpolymath/affinescript" -doc: "https://github.com/hyperpolymath/affinescript" -bug-reports: "https://github.com/hyperpolymath/affinescript/issues" -depends: [ - "ocaml" {>= "5.1"} - "dune" {>= "3.14"} - "menhir" {>= "20231231"} - "sedlex" {>= "3.2"} - "ppx_deriving" {>= "5.2"} - "ppx_sexp_conv" {>= "0.16"} - "sexplib0" {>= "0.16"} - "fmt" {>= "0.9"} - "cmdliner" {>= "1.2"} - "alcotest" {>= "1.7" & with-test} - "odoc" {>= "2.4" & with-doc} -] -build: [ - ["dune" "subst"] {dev} - [ - "dune" - "build" - "-p" - name - "-j" - jobs - "@install" - "@runtest" {with-test} - "@doc" {with-doc} - ] -] -dev-repo: "git+https://github.com/hyperpolymath/affinescript.git" diff --git a/affinescript/.build/dune-project b/affinescript/.build/dune-project deleted file mode 100644 index 7a0a71d..0000000 --- a/affinescript/.build/dune-project +++ /dev/null @@ -1,42 +0,0 @@ -(lang dune 3.17) - -(using menhir 3.0) - -(name affinescript) - -(version 0.1.0) - -(generate_opam_files true) - -(source - (github hyperpolymath/affinescript)) - -(authors "hyperpolymath") - -(maintainers "hyperpolymath") - -(license "MIT OR AGPL-3.0-or-later") - -(documentation https://github.com/hyperpolymath/affinescript) - -(package - (name affinescript) - (allow_empty) - (synopsis "A programming language with affine types, dependent types, row polymorphism, and extensible effects") - (description - "AffineScript combines Rust-style ownership (affine types), compile-time size verification (dependent types), extensible records (row polymorphism), and trackable side effects (extensible effects). It compiles to WebAssembly for efficient, portable execution.") - (depends - (ocaml (>= 5.1)) - (dune (>= 3.14)) - (menhir (>= 20231231)) - (sedlex (>= 3.2)) - (ppx_deriving (>= 5.2)) - (ppx_sexp_conv (>= 0.16)) - (sexplib0 (>= 0.16)) - (fmt (>= 0.9)) - (cmdliner (>= 1.2)) - (yojson (>= 2.1)) - (alcotest (and (>= 1.7) :with-test)) - (odoc (and (>= 2.4) :with-doc))) - (tags - (programming-language compiler webassembly affine-types dependent-types row-polymorphism effects ocaml))) diff --git a/affinescript/.claude/CLAUDE.md b/affinescript/.claude/CLAUDE.md deleted file mode 100644 index c89b229..0000000 --- a/affinescript/.claude/CLAUDE.md +++ /dev/null @@ -1,89 +0,0 @@ - -## Machine-Readable Artefacts - -The following files in `.machine_readable/` contain structured project metadata: - -- `.machine_readable/6a2/STATE.a2ml` - Current project state and progress -- `.machine_readable/6a2/META.a2ml` - Architecture decisions and development practices -- `.machine_readable/6a2/ECOSYSTEM.a2ml` - Position in the ecosystem and related projects -- `.machine_readable/6a2/AGENTIC.a2ml` - AI agent interaction patterns -- `.machine_readable/6a2/NEUROSYM.a2ml` - Neurosymbolic integration config -- `.machine_readable/6a2/PLAYBOOK.a2ml` - Operational runbook - ---- - -# CLAUDE.md - AI Assistant Instructions - -## Language Policy (Hyperpolymath Standard) - -### ALLOWED Languages & Tools - -| Language/Tool | Use Case | Notes | -|---------------|----------|-------| -| **AffineScript** | Primary application code | Compiles to JS, type-safe | -| **Bun** | JS runtime & package management (tier 1) | Default for all new work. Runs compiled ESM/JS directly — no bundler step. Uses an npm-compatible `package.json` plus `bun.lock` — both are expected, not anti-patterns. | -| **Rust** | Performance-critical, systems, WASM | Preferred for CLI tools | -| **Tauri 2.0+** | Mobile apps (iOS/Android) | Rust backend + web UI | -| **Dioxus** | Mobile apps (native UI) | Pure Rust, React-like | -| **Gleam** | Backend services | Runs on BEAM or compiles to JS | -| **Bash/POSIX Shell** | Scripts, automation | Keep minimal | -| **JavaScript** | Only where AffineScript cannot | MCP protocol glue, Bun APIs | -| **Python** | SaltStack only | No other Python permitted | -| **Nickel** | Configuration language | For complex configs | -| **A2ML** | State/meta files | .machine_readable/6a2/STATE.a2ml, .machine_readable/6a2/META.a2ml, .machine_readable/6a2/ECOSYSTEM.a2ml | -| **Julia** | Batch scripts, data processing | Per RSR | -| **OCaml** | AffineScript compiler | Language-specific | -| **Ada** | Safety-critical systems | Where required | - -### BANNED - Do Not Use - -| Banned | Replacement | -|--------|-------------| -| TypeScript | AffineScript | -| ReScript | AffineScript | -| Deno | Bun | -| Node.js | Bun | -| npm | Bun | -| pnpm/yarn | Bun | -| Go | Rust | -| Python (general) | AffineScript/Rust | -| Java/Kotlin | Rust/Tauri/Dioxus | -| Swift | Tauri/Dioxus | -| React Native | Tauri/Dioxus | -| Flutter/Dart | Tauri/Dioxus | - -### Mobile Development - -**No exceptions for Kotlin/Swift** - use Rust-first approach: - -1. **Tauri 2.0+** - Web UI (AffineScript) + Rust backend, MIT/Apache-2.0 -2. **Dioxus** - Pure Rust native UI, MIT/Apache-2.0 - -Both are FOSS with independent governance (no Big Tech). - -### Enforcement Rules - -1. **No new TypeScript files** - Convert existing TS to AffineScript -2. **Use `package.json` + `bun.lock` for JS runtime deps** - Bun is npm-compatible; a manifest is REQUIRED -3. **`bun install --production --frozen-lockfile` for production deps** - resolved from `package.json` and pinned via `bun.lock`; `--frozen-lockfile` makes a lockfile mismatch a build failure rather than a silent re-resolve -4. **No Go code** - Use Rust instead -5. **Python only for SaltStack** - All other Python must be rewritten -6. **No Kotlin/Swift for mobile** - Use Tauri 2.0+ or Dioxus - -### Package Management - -- **Primary**: Guix (guix.scm) -- **Fallback**: Nix (flake.nix) -- **JS deps**: Bun (`package.json` + `bun.lock`). Declare tooling as a devDependency and run `bunx --no-install --bun ` — a bare `bunx ` can fetch an unpinned package and may start Node via its shebang. - -### Security Requirements - -- No MD5/SHA1 for security (use SHA256+) -- HTTPS only (no HTTP URLs) -- No hardcoded secrets -- SHA-pinned dependencies -- SPDX license headers on all files - diff --git a/affinescript/.editorconfig b/affinescript/.editorconfig deleted file mode 100644 index fc6650c..0000000 --- a/affinescript/.editorconfig +++ /dev/null @@ -1,68 +0,0 @@ -# RSR-template-repo - Editor Configuration -# https://editorconfig.org - -root = true - -[*] -charset = utf-8 -end_of_line = lf -indent_size = 2 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[*.md] -trim_trailing_whitespace = false - -[*.adoc] -trim_trailing_whitespace = false - -[*.rs] -indent_size = 4 - -[*.ex] -indent_size = 2 - -[*.exs] -indent_size = 2 - -[*.zig] -indent_size = 4 - -[*.ada] -indent_size = 3 - -[*.adb] -indent_size = 3 - -[*.ads] -indent_size = 3 - -[*.hs] -indent_size = 2 - -[*.res] -indent_size = 2 - -[*.resi] -indent_size = 2 - -[*.ncl] -indent_size = 2 - -[*.rkt] -indent_size = 2 - -[*.scm] -indent_size = 2 - -[*.nix] -indent_size = 2 - -[Justfile] -indent_style = space -indent_size = 4 - -[justfile] -indent_style = space -indent_size = 4 diff --git a/affinescript/.gitattributes b/affinescript/.gitattributes deleted file mode 100644 index e860a85..0000000 --- a/affinescript/.gitattributes +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# RSR-compliant .gitattributes - -* text=auto eol=lf - -# Source -*.rs text eol=lf diff=rust -*.ex text eol=lf diff=elixir -*.exs text eol=lf diff=elixir -*.jl text eol=lf -*.res text eol=lf -*.resi text eol=lf -*.ada text eol=lf diff=ada -*.adb text eol=lf diff=ada -*.ads text eol=lf diff=ada -*.hs text eol=lf -*.chpl text eol=lf -*.scm text eol=lf -*.ncl text eol=lf -*.nix text eol=lf - -# Docs -*.md text eol=lf diff=markdown -*.adoc text eol=lf -*.txt text eol=lf - -# Data -*.json text eol=lf -*.yaml text eol=lf -*.yml text eol=lf -*.toml text eol=lf - -# Config -.gitignore text eol=lf -.gitattributes text eol=lf -justfile text eol=lf -Makefile text eol=lf -Containerfile text eol=lf - -# Scripts -*.sh text eol=lf - -# Binary -*.png binary -*.jpg binary -*.gif binary -*.pdf binary -*.woff2 binary -*.zip binary -*.gz binary - -# Lock files -Cargo.lock text eol=lf -diff -flake.lock text eol=lf -diff diff --git a/affinescript/.github/FUNDING.yml b/affinescript/.github/FUNDING.yml deleted file mode 100644 index 688a442..0000000 --- a/affinescript/.github/FUNDING.yml +++ /dev/null @@ -1,7 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Funding platforms for hyperpolymath projects -# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository - -github: hyperpolymath -ko_fi: hyperpolymath -liberapay: hyperpolymath diff --git a/affinescript/.github/ISSUE_TEMPLATE/bug_report.md b/affinescript/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 917068f..0000000 --- a/affinescript/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,42 +0,0 @@ - ---- -name: Bug report -about: Create a report to help us improve -title: "[Bug]: " -labels: 'bug, priority: unset, triage' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/affinescript/.github/ISSUE_TEMPLATE/documentation.md b/affinescript/.github/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index b686e81..0000000 --- a/affinescript/.github/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,70 +0,0 @@ - ---- -name: Documentation -about: Report unclear, missing, or incorrect documentation -title: "[DOCS]: " -labels: 'documentation, priority: unset, triage' -assignees: '' - ---- - -name: Documentation -description: Report unclear, missing, or incorrect documentation -title: "[Docs]: " -labels: ["documentation", "triage"] -body: - - type: markdown - attributes: - value: | - Help us improve our documentation by reporting issues or gaps. - - - type: dropdown - id: type - attributes: - label: Documentation issue type - options: - - Missing (documentation doesn't exist) - - Incorrect (information is wrong) - - Unclear (confusing or hard to follow) - - Outdated (no longer accurate) - - Typo or grammar - validations: - required: true - - - type: input - id: location - attributes: - label: Location - description: Where is this documentation? (URL, file path, or section name) - placeholder: README.adoc, section "Installation" - validations: - required: true - - - type: textarea - id: description - attributes: - label: Description - description: What's the problem with the current documentation? - placeholder: Describe what's wrong or missing - validations: - required: true - - - type: textarea - id: suggestion - attributes: - label: Suggested improvement - description: How should it be fixed or improved? - placeholder: The documentation should say... - validations: - required: false - - - type: checkboxes - id: contribution - attributes: - label: Contribution - options: - - label: I would be willing to submit a PR to fix this - required: false diff --git a/affinescript/.github/ISSUE_TEMPLATE/feature_request.md b/affinescript/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index fa0243f..0000000 --- a/affinescript/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,24 +0,0 @@ - ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: 'enhancement, priority: unset, triage' -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/affinescript/.github/ISSUE_TEMPLATE/question.md b/affinescript/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index ac505d4..0000000 --- a/affinescript/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,59 +0,0 @@ - ---- -name: Question -about: Ask a question about usage or behaviour -title: "[QUESTION]: " -labels: question, triage -assignees: '' - ---- - -name: Question -description: Ask a question about usage or behaviour -title: "[Question]: " -labels: ["question", "triage"] -body: - - type: markdown - attributes: - value: | - Have a question? You can also ask in [Discussions](../discussions) for broader conversations. - - - type: textarea - id: question - attributes: - label: Your question - description: What would you like to know? - placeholder: How do I...? - validations: - required: true - - - type: textarea - id: context - attributes: - label: Context - description: Any relevant context that helps us answer your question - placeholder: I'm trying to achieve X and I've tried Y... - validations: - required: false - - - type: textarea - id: research - attributes: - label: What I've already tried - description: What have you already looked at or attempted? - placeholder: I've read the README and searched issues but... - validations: - required: false - - - type: checkboxes - id: checked - attributes: - label: Pre-submission checklist - options: - - label: I have searched existing issues and discussions - required: true - - label: I have read the documentation - required: true diff --git a/affinescript/.github/dependabot.yml b/affinescript/.github/dependabot.yml deleted file mode 100644 index 0089680..0000000 --- a/affinescript/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - commit-message: - prefix: "ci" - labels: - - "dependencies" - - "github-actions" diff --git a/affinescript/.github/workflows/casket-pages.yml b/affinescript/.github/workflows/casket-pages.yml deleted file mode 100644 index 4291ad4..0000000 --- a/affinescript/.github/workflows/casket-pages.yml +++ /dev/null @@ -1,88 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: GitHub Pages -on: - push: - branches: [main] - workflow_dispatch: -permissions: - contents: read - pages: write - id-token: write -concurrency: - group: "pages" - cancel-in-progress: false -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - name: Checkout casket-ssg - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - repository: hyperpolymath/casket-ssg - path: .casket-ssg - - name: Setup GHCup - uses: haskell-actions/setup@f9150cb1d140e9a9271700670baa38991e6fa25c # v2 - with: - ghc-version: '9.8.2' - cabal-version: '3.10' - - name: Cache Cabal - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v4 - with: - path: | - ~/.cabal/packages - ~/.cabal/store - .casket-ssg/dist-newstyle - key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} - - name: Build casket-ssg - working-directory: .casket-ssg - run: cabal build - - name: Build site - run: | - mkdir -p site _site - # Generate index.md from README if site/index.md doesn't exist - if [ ! -f site/index.md ]; then - if [ -f README.adoc ]; then - # Convert AsciiDoc to Markdown (basic conversion) - echo "---" > site/index.md - echo "title: $(basename $PWD)" >> site/index.md - echo "date: $(date +%Y-%m-%d)" >> site/index.md - echo "---" >> site/index.md - cat README.adoc >> site/index.md - elif [ -f README.md ]; then - echo "---" > site/index.md - echo "title: $(basename $PWD)" >> site/index.md - echo "date: $(date +%Y-%m-%d)" >> site/index.md - echo "---" >> site/index.md - cat README.md >> site/index.md - else - echo "---" > site/index.md - echo "title: $(basename $PWD)" >> site/index.md - echo "date: $(date +%Y-%m-%d)" >> site/index.md - echo "---" >> site/index.md - echo "" >> site/index.md - echo "# $(basename $PWD)" >> site/index.md - echo "" >> site/index.md - echo "Documentation coming soon." >> site/index.md - fi - fi - cd .casket-ssg && cabal run casket-ssg -- build ../site ../_site - - name: Setup Pages - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - - name: Upload artifact - uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 - with: - path: '_site' - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/affinescript/.github/workflows/ci.yml b/affinescript/.github/workflows/ci.yml deleted file mode 100644 index d31cce0..0000000 --- a/affinescript/.github/workflows/ci.yml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: CI -on: - push: - branches: [main] - pull_request: - branches: [main] -permissions: - contents: read -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up OCaml - uses: ocaml/setup-ocaml@dec6499fef64fc5d7ed43d43a87251b7b1c306f5 # v3 - with: - ocaml-compiler: "5.1" - - name: Set up Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4 - with: - node-version: "20" - - name: Install dependencies - run: opam install . --deps-only --with-test --with-doc - - name: Build - run: opam exec -- dune build - - name: Run tests - run: opam exec -- dune runtest - - name: Run codegen WASM tests - run: opam exec -- ./tools/run_codegen_wasm_tests.sh - - name: Check formatting - run: opam exec -- dune build @fmt - lint: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up OCaml - uses: ocaml/setup-ocaml@dec6499fef64fc5d7ed43d43a87251b7b1c306f5 # v3 - with: - ocaml-compiler: "5.1" - - name: Install dependencies - run: opam install . --deps-only - - name: Build - run: opam exec -- dune build - - name: Lint with odoc - run: opam exec -- dune build @doc - continue-on-error: true diff --git a/affinescript/.github/workflows/codeql.yml b/affinescript/.github/workflows/codeql.yml deleted file mode 100644 index 1526568..0000000 --- a/affinescript/.github/workflows/codeql.yml +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: CodeQL Security Analysis -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - schedule: - - cron: '0 6 * * 1' -permissions: read-all -jobs: - analyze: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - include: - # affinescript is OCaml-only - OCaml not supported by CodeQL - # Using 'actions' to scan GitHub Actions workflow files - - language: actions - build-mode: none - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v3 - with: - category: "/language:${{ matrix.language }}" diff --git a/affinescript/.github/workflows/guix-nix-policy.yml b/affinescript/.github/workflows/guix-nix-policy.yml deleted file mode 100644 index d94cfb8..0000000 --- a/affinescript/.github/workflows/guix-nix-policy.yml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Guix/Nix Package Policy -on: [push, pull_request] -permissions: read-all -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Enforce Guix primary / Nix fallback - run: | - # Check for package manager files - HAS_GUIX=$(find . -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" 2>/dev/null | head -1) - HAS_NIX=$(find . -name "*.nix" 2>/dev/null | head -1) - - # Block new package-lock.json, yarn.lock, Gemfile.lock, etc. - NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) - if [ -n "$NEW_LOCKS" ]; then - echo "⚠️ Lock files detected. Prefer Guix manifests for reproducibility." - fi - - # Prefer Guix, fallback to Nix - if [ -n "$HAS_GUIX" ]; then - echo "✅ Guix package management detected (primary)" - elif [ -n "$HAS_NIX" ]; then - echo "✅ Nix package management detected (fallback)" - else - echo "ℹ️ Consider adding guix.scm or flake.nix for reproducible builds" - fi - - echo "✅ Package policy check passed" diff --git a/affinescript/.github/workflows/hypatia-scan.yml b/affinescript/.github/workflows/hypatia-scan.yml deleted file mode 100644 index bdf21bc..0000000 --- a/affinescript/.github/workflows/hypatia-scan.yml +++ /dev/null @@ -1,125 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Hypatia Neurosymbolic CI/CD Security Scan -name: Hypatia Security Scan -on: - push: - branches: [main, master, develop] - pull_request: - branches: [main, master] - schedule: - - cron: '0 0 * * 0' # Weekly on Sunday - workflow_dispatch: -permissions: read-all -jobs: - scan: - name: Hypatia Neurosymbolic Analysis - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 # Full history for better pattern analysis - - name: Setup Elixir for Hypatia scanner - uses: erlef/setup-beam@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.18.2 - with: - elixir-version: '1.19.4' - otp-version: '28.3' - - name: Clone Hypatia - run: | - if [ ! -d "$HOME/hypatia" ]; then - git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" - fi - - name: Build Hypatia scanner (if needed) - working-directory: ${{ env.HOME }}/hypatia - run: | - if [ ! -f hypatia-v2 ]; then - echo "Building hypatia-v2 scanner..." - mix deps.get - mix escript.build - mv hypatia hypatia-v2 - fi - - name: Run Hypatia scan - id: scan - run: | - echo "Scanning repository: ${{ github.repository }}" - - # Run scanner - HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . > hypatia-findings.json - - # Count findings - FINDING_COUNT=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) - echo "findings_count=$FINDING_COUNT" >> $GITHUB_OUTPUT - - # Extract severity counts - CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json) - HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json) - MEDIUM=$(jq '[.[] | select(.severity == "medium")] | length' hypatia-findings.json) - - echo "critical=$CRITICAL" >> $GITHUB_OUTPUT - echo "high=$HIGH" >> $GITHUB_OUTPUT - echo "medium=$MEDIUM" >> $GITHUB_OUTPUT - - echo "## Hypatia Scan Results" >> $GITHUB_STEP_SUMMARY - echo "- Total findings: $FINDING_COUNT" >> $GITHUB_STEP_SUMMARY - echo "- Critical: $CRITICAL" >> $GITHUB_STEP_SUMMARY - echo "- High: $HIGH" >> $GITHUB_STEP_SUMMARY - echo "- Medium: $MEDIUM" >> $GITHUB_STEP_SUMMARY - - name: Upload findings artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 - with: - name: hypatia-findings - path: hypatia-findings.json - retention-days: 90 - - name: Submit findings to gitbot-fleet (Phase 2) - if: steps.scan.outputs.findings_count > 0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SHA: ${{ github.sha }} - run: "echo \"\U0001F4E4 Submitting ${{ steps.scan.outputs.findings_count }} findings to gitbot-fleet...\"\n\n# Clone gitbot-fleet to temp directory\nFLEET_DIR=\"/tmp/gitbot-fleet-$$\"\ngit clone https://github.com/hyperpolymath/gitbot-fleet.git \"$FLEET_DIR\"\n\n# Run submission script\nbash \"$FLEET_DIR/scripts/submit-finding.sh\" hypatia-findings.json\n\n# Cleanup\nrm -rf \"$FLEET_DIR\"\n\necho \"✅ Finding submission complete\"\n" - - name: Check for critical issues - if: steps.scan.outputs.critical > 0 - run: | - echo "⚠️ Critical security issues found!" - echo "Review hypatia-findings.json for details" - # Don't fail the build yet - just warn - # exit 1 - - name: Generate scan report - run: | - cat << EOF > hypatia-report.md - # Hypatia Security Scan Report - - **Repository:** ${{ github.repository }} - **Scan Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") - **Commit:** ${{ github.sha }} - - ## Summary - - | Severity | Count | - |----------|-------| - | Critical | ${{ steps.scan.outputs.critical }} | - | High | ${{ steps.scan.outputs.high }} | - | Medium | ${{ steps.scan.outputs.medium }} | - | **Total**| ${{ steps.scan.outputs.findings_count }} | - - ## Next Steps - - 1. Review findings in the artifact: hypatia-findings.json - 2. Auto-fixable issues will be addressed by robot-repo-automaton (Phase 3) - 3. Manual review required for complex issues - - ## Learning - - These findings feed Hypatia's learning engine to improve future rules. - - --- - *Powered by [Hypatia](https://github.com/hyperpolymath/hypatia) - Neurosymbolic CI/CD Intelligence* - EOF - - cat hypatia-report.md >> $GITHUB_STEP_SUMMARY - - name: Comment on PR with findings - if: github.event_name == 'pull_request' && steps.scan.outputs.findings_count > 0 - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v7 - with: - script: "const fs = require('fs');\nconst findings = JSON.parse(fs.readFileSync('hypatia-findings.json', 'utf8'));\n\nconst critical = findings.filter(f => f.severity === 'critical').length;\nconst high = findings.filter(f => f.severity === 'high').length;\n\nlet comment = `## \U0001F50D Hypatia Security Scan\\n\\n`;\ncomment += `**Findings:** ${findings.length} issues detected\\n\\n`;\ncomment += `| Severity | Count |\\n|----------|-------|\\n`;\ncomment += `| \U0001F534 Critical | ${critical} |\\n`;\ncomment += `| \U0001F7E0 High | ${high} |\\n`;\ncomment += `| \U0001F7E1 Medium | ${findings.length - critical - high} |\\n\\n`;\n\nif (critical > 0) {\n comment += `⚠️ **Action Required:** Critical security issues found!\\n\\n`;\n}\n\ncomment += `
View findings\\n\\n`;\ncomment += `\\`\\`\\`json\\n${JSON.stringify(findings.slice(0, 10), null, 2)}\\n\\`\\`\\`\\n`;\ncomment += `
\\n\\n`;\ncomment += `*Powered by Hypatia Neurosymbolic CI/CD Intelligence*`;\n\ngithub.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.issue.number,\n body: comment\n});\n" diff --git a/affinescript/.github/workflows/instant-sync.yml b/affinescript/.github/workflows/instant-sync.yml deleted file mode 100644 index e9df9c7..0000000 --- a/affinescript/.github/workflows/instant-sync.yml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Instant Forge Sync - Triggers propagation to all forges on push/release -name: Instant Sync -on: - push: - branches: [main, master] - release: - types: [published] -permissions: - contents: read -jobs: - dispatch: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Trigger Propagation - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 - with: - token: ${{ secrets.FARM_DISPATCH_TOKEN }} - repository: hyperpolymath/.git-private-farm - event-type: propagate - client-payload: |- - { - "repo": "${{ github.event.repository.name }}", - "ref": "${{ github.ref }}", - "sha": "${{ github.sha }}", - "forges": "" - } - - name: Confirm - run: echo "::notice::Propagation triggered for ${{ github.event.repository.name }}" diff --git a/affinescript/.github/workflows/mirror.yml b/affinescript/.github/workflows/mirror.yml deleted file mode 100644 index c1f6b6f..0000000 --- a/affinescript/.github/workflows/mirror.yml +++ /dev/null @@ -1,127 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2025 Jonathan D.A. Jewell -name: Mirror to Git Forges -on: - push: - branches: [main] - workflow_dispatch: -permissions: read-all -jobs: - mirror-gitlab: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.GITLAB_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.GITLAB_SSH_KEY }} - - name: Mirror to GitLab - run: | - ssh-keyscan -t ed25519 gitlab.com >> ~/.ssh/known_hosts - git remote add gitlab git@gitlab.com:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force gitlab main - mirror-bitbucket: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.BITBUCKET_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.BITBUCKET_SSH_KEY }} - - name: Mirror to Bitbucket - run: | - ssh-keyscan -t ed25519 bitbucket.org >> ~/.ssh/known_hosts - git remote add bitbucket git@bitbucket.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force bitbucket main - mirror-codeberg: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.CODEBERG_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.CODEBERG_SSH_KEY }} - - name: Mirror to Codeberg - run: | - ssh-keyscan -t ed25519 codeberg.org >> ~/.ssh/known_hosts - git remote add codeberg git@codeberg.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force codeberg main - mirror-sourcehut: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.SOURCEHUT_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.SOURCEHUT_SSH_KEY }} - - name: Mirror to SourceHut - run: | - ssh-keyscan -t ed25519 git.sr.ht >> ~/.ssh/known_hosts - git remote add sourcehut git@git.sr.ht:~hyperpolymath/${{ github.event.repository.name }} || true - git push --force sourcehut main - mirror-disroot: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.DISROOT_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.DISROOT_SSH_KEY }} - - name: Mirror to Disroot - run: | - ssh-keyscan -t ed25519 git.disroot.org >> ~/.ssh/known_hosts - git remote add disroot git@git.disroot.org:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force disroot main - mirror-gitea: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.GITEA_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - uses: webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555 # v0.10.0 - with: - ssh-private-key: ${{ secrets.GITEA_SSH_KEY }} - - name: Mirror to Gitea - run: | - ssh-keyscan -t ed25519 ${{ vars.GITEA_HOST }} >> ~/.ssh/known_hosts - git remote add gitea git@${{ vars.GITEA_HOST }}:hyperpolymath/${{ github.event.repository.name }}.git || true - git push --force gitea main - mirror-radicle: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: vars.RADICLE_MIRROR_ENABLED == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 - - name: Setup Rust - uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # stable - with: - toolchain: stable - - name: Install Radicle - run: | - # Install via cargo (safer than curl|sh) - cargo install radicle-cli --locked - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - name: Mirror to Radicle - run: | - echo "${{ secrets.RADICLE_KEY }}" > ~/.radicle/keys/radicle - chmod 600 ~/.radicle/keys/radicle - rad sync --announce || echo "Radicle sync attempted" diff --git a/affinescript/.github/workflows/npm-bun-blocker.yml b/affinescript/.github/workflows/npm-bun-blocker.yml deleted file mode 100644 index bfe5156..0000000 --- a/affinescript/.github/workflows/npm-bun-blocker.yml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: NPM/Bun Blocker -on: [push, pull_request] -permissions: read-all -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Block npm/pnpm/yarn lockfiles - run: | - if [ -f "package-lock.json" ] || [ -f "pnpm-lock.yaml" ] || [ -f "yarn.lock" ]; then - echo "❌ npm/pnpm/yarn artifacts detected. Use Bun (package.json + bun.lock) instead." - exit 1 - fi - echo "✅ No npm/pnpm/yarn violations" diff --git a/affinescript/.github/workflows/quality.yml b/affinescript/.github/workflows/quality.yml deleted file mode 100644 index 76651e2..0000000 --- a/affinescript/.github/workflows/quality.yml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Code Quality -on: [push, pull_request] -permissions: read-all -jobs: - lint: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check file permissions - run: | - find . -type f -perm /111 -name "*.sh" | head -10 || true - - name: Check for secrets - uses: trufflesecurity/trufflehog@6bd2d14f7a4bc1e569fa3550efa7ec632a4fa67b # v3.94.2 - with: - path: ./ - base: ${{ github.event.pull_request.base.sha || github.event.before }} - head: ${{ github.sha }} - continue-on-error: true - - name: Check TODO/FIXME - run: | - echo "=== TODOs ===" - grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.res" --include="*.py" --include="*.ex" . | head -20 || echo "None found" - - name: Check for large files - run: | - find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" - - name: EditorConfig check - uses: editorconfig-checker/action-editorconfig-checker@4b6cd6190d435e7e084fb35e36a096e98506f7b9 # v2.1.0 - continue-on-error: true - docs: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check documentation - run: | - MISSING="" - [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && MISSING="$MISSING README" - [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ] && MISSING="$MISSING LICENSE" - [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] && MISSING="$MISSING CONTRIBUTING" - - if [ -n "$MISSING" ]; then - echo "::warning::Missing docs:$MISSING" - else - echo "✅ Core documentation present" - fi diff --git a/affinescript/.github/workflows/release.yml b/affinescript/.github/workflows/release.yml deleted file mode 100644 index 1c0d524..0000000 --- a/affinescript/.github/workflows/release.yml +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Release -on: - push: - tags: - - 'v*' -permissions: - contents: write -jobs: - release: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Set up OCaml - uses: ocaml/setup-ocaml@dec6499fef64fc5d7ed43d43a87251b7b1c306f5 # v3 - with: - ocaml-compiler: "5.1" - - name: Install dependencies - run: opam install . --deps-only - - name: Build release - run: opam exec -- dune build --release - - name: Create release archive - run: | - mkdir -p dist - cp _build/default/bin/main.exe dist/affinescript-linux-x64 || true - tar -czvf affinescript-linux-x64.tar.gz -C dist . - - name: Create GitHub Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 - with: - files: affinescript-linux-x64.tar.gz - generate_release_notes: true diff --git a/affinescript/.github/workflows/scorecard-enforcer.yml b/affinescript/.github/workflows/scorecard-enforcer.yml deleted file mode 100644 index f6b6846..0000000 --- a/affinescript/.github/workflows/scorecard-enforcer.yml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - runs OpenSSF Scorecard and fails on low scores -name: OpenSSF Scorecard Enforcer -on: - push: - branches: [main] - schedule: - - cron: '0 6 * * 1' # Weekly on Monday - workflow_dispatch: -permissions: read-all -jobs: - scorecard: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - security-events: write - id-token: write # For OIDC - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 - with: - results_file: results.sarif - results_format: sarif - publish_results: true - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3 - with: - sarif_file: results.sarif - # Check specific high-priority items - check-critical: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - name: Check SECURITY.md exists - run: | - if [ ! -f "SECURITY.md" ]; then - echo "::error::SECURITY.md is required" - exit 1 - fi - - name: Check for pinned dependencies - run: | - # Check workflows for unpinned actions - unpinned=$(grep -r "uses:.*@v[0-9]" .github/workflows/*.yml 2>/dev/null | grep -v "#" | head -5 || true) - if [ -n "$unpinned" ]; then - echo "::warning::Found unpinned actions:" - echo "$unpinned" - fi diff --git a/affinescript/.github/workflows/scorecard.yml b/affinescript/.github/workflows/scorecard.yml deleted file mode 100644 index 6badcc1..0000000 --- a/affinescript/.github/workflows/scorecard.yml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: OSSF Scorecard -on: - push: - branches: [main, master] - schedule: - - cron: '0 4 * * *' - workflow_dispatch: -permissions: read-all -jobs: - analysis: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - security-events: write - id-token: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Run Scorecard - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.3.1 - with: - results_file: results.sarif - results_format: sarif - - name: Upload results - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.31.8 - with: - sarif_file: results.sarif diff --git a/affinescript/.github/workflows/secret-scanner.yml b/affinescript/.github/workflows/secret-scanner.yml deleted file mode 100644 index c93bad5..0000000 --- a/affinescript/.github/workflows/secret-scanner.yml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Prevention workflow - scans for hardcoded secrets before they reach main -name: Secret Scanner -on: - pull_request: - push: - branches: [main] -permissions: read-all -jobs: - trufflehog: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - with: - fetch-depth: 0 # Full history for scanning - - name: TruffleHog Secret Scan - uses: trufflesecurity/trufflehog@6bd2d14f7a4bc1e569fa3550efa7ec632a4fa67b # v3 - with: - extra_args: --only-verified --fail - # Rust-specific: Check for hardcoded crypto values - rust-secrets: - runs-on: ubuntu-latest - timeout-minutes: 15 - if: hashFiles('**/Cargo.toml') != '' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 - - name: Check for hardcoded secrets in Rust - run: | - # Patterns that suggest hardcoded secrets - PATTERNS=( - 'const.*SECRET.*=.*"' - 'const.*KEY.*=.*"[a-zA-Z0-9]{16,}"' - 'const.*TOKEN.*=.*"' - 'let.*api_key.*=.*"' - 'HMAC.*"[a-fA-F0-9]{32,}"' - 'password.*=.*"[^"]+"' - ) - - found=0 - for pattern in "${PATTERNS[@]}"; do - if grep -rn --include="*.rs" -E "$pattern" src/; then - echo "WARNING: Potential hardcoded secret found matching: $pattern" - found=1 - fi - done - - if [ $found -eq 1 ]; then - echo "::error::Potential hardcoded secrets detected. Use environment variables instead." - exit 1 - fi diff --git a/affinescript/.github/workflows/security-policy.yml b/affinescript/.github/workflows/security-policy.yml deleted file mode 100644 index 1c7f8a7..0000000 --- a/affinescript/.github/workflows/security-policy.yml +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Security Policy -on: [push, pull_request] -permissions: read-all -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Security checks - run: | - FAILED=false - - # Block MD5/SHA1 for security (allow for checksums/caching) - WEAK_CRYPTO=$(grep -rE 'md5\(|sha1\(' --include="*.py" --include="*.rb" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" . 2>/dev/null | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) - if [ -n "$WEAK_CRYPTO" ]; then - echo "⚠️ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" - echo "$WEAK_CRYPTO" - fi - - # Block HTTP URLs (except localhost) - HTTP_URLS=$(grep -rE 'http://[^l][^o][^c]' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) - if [ -n "$HTTP_URLS" ]; then - echo "⚠️ HTTP URLs found. Use HTTPS:" - echo "$HTTP_URLS" - fi - - # Block hardcoded secrets patterns - SECRETS=$(grep -rEi '(api_key|apikey|secret_key|password)\s*[=:]\s*["\x27][A-Za-z0-9+/=]{20,}' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.env" . 2>/dev/null | grep -v 'example\|sample\|test\|mock\|placeholder' | head -3 || true) - if [ -n "$SECRETS" ]; then - echo "❌ Potential hardcoded secrets detected!" - FAILED=true - fi - - if [ "$FAILED" = true ]; then - exit 1 - fi - - echo "✅ Security policy check passed" diff --git a/affinescript/.github/workflows/semgrep.yml b/affinescript/.github/workflows/semgrep.yml deleted file mode 100644 index df18b16..0000000 --- a/affinescript/.github/workflows/semgrep.yml +++ /dev/null @@ -1,31 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Semgrep SAST -on: - push: - branches: [main, master] - pull_request: - branches: [main, master] - schedule: - - cron: '0 5 * * 1' - workflow_dispatch: -permissions: read-all -jobs: - semgrep: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - security-events: write - contents: read - container: - image: semgrep/semgrep - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Run Semgrep - run: semgrep scan --sarif --output=semgrep.sarif --config=auto . - env: - SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} - - name: Upload SARIF - uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 - with: - sarif_file: semgrep.sarif - if: always() diff --git a/affinescript/.github/workflows/ts-blocker.yml b/affinescript/.github/workflows/ts-blocker.yml deleted file mode 100644 index 7ca3b07..0000000 --- a/affinescript/.github/workflows/ts-blocker.yml +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: TypeScript/JavaScript Blocker -on: [push, pull_request] -permissions: read-all -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Block new TypeScript/JavaScript - run: | - NEW_TS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.(ts|tsx)$' | grep -v '\.gen\.' || true) - NEW_JS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E '\.(js|jsx)$' | grep -v '\.res\.js$' | grep -v '\.gen\.' | grep -v 'node_modules' || true) - - if [ -n "$NEW_TS" ] || [ -n "$NEW_JS" ]; then - echo "❌ New TS/JS files detected. Use ReScript instead." - [ -n "$NEW_TS" ] && echo "$NEW_TS" - [ -n "$NEW_JS" ] && echo "$NEW_JS" - exit 1 - fi - echo "✅ ReScript policy enforced" diff --git a/affinescript/.github/workflows/wellknown-enforcement.yml b/affinescript/.github/workflows/wellknown-enforcement.yml deleted file mode 100644 index 5a6ac27..0000000 --- a/affinescript/.github/workflows/wellknown-enforcement.yml +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: Well-Known Standards (RFC 9116 + RSR) -on: - push: - branches: [main, master] - paths: - - '.well-known/**' - - 'security.txt' - pull_request: - paths: - - '.well-known/**' - schedule: - # Weekly expiry check - - cron: '0 9 * * *' - workflow_dispatch: -permissions: read-all -jobs: - validate: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: RFC 9116 security.txt validation - run: | - SECTXT="" - [ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt" - [ -f "security.txt" ] && SECTXT="security.txt" - - if [ -z "$SECTXT" ]; then - echo "::warning::No security.txt found. See https://github.com/hyperpolymath/well-known-ecosystem" - exit 0 - fi - - # Required: Contact - grep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; } - - # Required: Expires - if ! grep -q "^Expires:" "$SECTXT"; then - echo "::error::Missing Expires field" - exit 1 - fi - - # Check expiry - EXPIRES=$(grep "^Expires:" "$SECTXT" | cut -d: -f2- | tr -d ' ' | head -1) - if date -d "$EXPIRES" > /dev/null 2>&1; then - DAYS=$(( ($(date -d "$EXPIRES" +%s) - $(date +%s)) / 86400 )) - if [ $DAYS -lt 0 ]; then - echo "::error::security.txt EXPIRED" - exit 1 - elif [ $DAYS -lt 30 ]; then - echo "::warning::security.txt expires in $DAYS days" - else - echo "✅ security.txt valid ($DAYS days)" - fi - fi - - name: RSR well-known compliance - run: | - MISSING="" - [ ! -f ".well-known/security.txt" ] && [ ! -f "security.txt" ] && MISSING="$MISSING security.txt" - [ ! -f ".well-known/ai.txt" ] && MISSING="$MISSING ai.txt" - [ ! -f ".well-known/humans.txt" ] && MISSING="$MISSING humans.txt" - - if [ -n "$MISSING" ]; then - echo "::warning::Missing RSR recommended files:$MISSING" - echo "Reference: https://github.com/hyperpolymath/well-known-ecosystem/.well-known/" - else - echo "✅ RSR well-known compliant" - fi - - name: Mixed content check - run: | - MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com' | head -5 || true) - if [ -n "$MIXED" ]; then - echo "::error::Mixed content (HTTP in HTML)" - echo "$MIXED" - exit 1 - fi - echo "✅ No mixed content" - - name: DNS security records check - if: hashFiles('CNAME') != '' - run: | - DOMAIN=$(cat CNAME 2>/dev/null | tr -d '\n') - if [ -n "$DOMAIN" ]; then - echo "Checking DNS for $DOMAIN..." - # CAA record - dig +short CAA "$DOMAIN" | grep -q "issue" && echo "✅ CAA record" || echo "::warning::No CAA record" - # DNSSEC - dig +dnssec +short "$DOMAIN" | grep -q "RRSIG" && echo "✅ DNSSEC" || echo "::warning::No DNSSEC" - fi diff --git a/affinescript/.github/workflows/workflow-linter.yml b/affinescript/.github/workflows/workflow-linter.yml deleted file mode 100644 index 905ec0e..0000000 --- a/affinescript/.github/workflows/workflow-linter.yml +++ /dev/null @@ -1,145 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# workflow-linter.yml - Validates GitHub workflows against RSR security standards -# This workflow can be copied to other repos for consistent enforcement -name: Workflow Security Linter -on: - push: - paths: - - '.github/workflows/**' - pull_request: - paths: - - '.github/workflows/**' - workflow_dispatch: -permissions: read-all -jobs: - lint-workflows: - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check SPDX Headers - run: | - echo "=== Checking SPDX License Headers ===" - failed=0 - for file in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$file" ] || continue - if ! head -1 "$file" | grep -q "^# SPDX-License-Identifier:"; then - echo "ERROR: $file missing SPDX header" - failed=1 - fi - done - if [ $failed -eq 1 ]; then - echo "Add '# SPDX-License-Identifier: MPL-2.0' as first line" - exit 1 - fi - echo "All workflows have SPDX headers" - - name: Check Permissions Declaration - run: | - echo "=== Checking Permissions ===" - failed=0 - for file in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$file" ] || continue - if ! grep -q "^permissions:" "$file"; then - echo "ERROR: $file missing top-level 'permissions:' declaration" - failed=1 - fi - done - if [ $failed -eq 1 ]; then - echo "Add 'permissions: read-all' at workflow level" - exit 1 - fi - echo "All workflows have permissions declared" - - name: Check SHA-Pinned Actions - run: | - echo "=== Checking Action Pinning ===" - # Find any uses: lines that don't have @SHA format - # Pattern: uses: owner/repo@<40-char-hex> - unpinned=$(grep -rn "uses:" .github/workflows/ | \ - grep -v "@[a-f0-9]\{40\}" | \ - grep -v "uses: \./\|uses: docker://\|uses: actions/github-script" || true) - - if [ -n "$unpinned" ]; then - echo "ERROR: Found unpinned actions:" - echo "$unpinned" - echo "" - echo "Replace version tags with SHA pins, e.g.:" - echo " uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.1.1" - exit 1 - fi - echo "All actions are SHA-pinned" - - name: Check for Duplicate Workflows - run: | - echo "=== Checking for Duplicates ===" - # Known duplicate patterns - if [ -f .github/workflows/codeql.yml ] && [ -f .github/workflows/codeql-analysis.yml ]; then - echo "ERROR: Duplicate CodeQL workflows found" - echo "Delete codeql-analysis.yml (keep codeql.yml)" - exit 1 - fi - if [ -f .github/workflows/rust.yml ] && [ -f .github/workflows/rust-ci.yml ]; then - echo "WARNING: Potential duplicate Rust workflows" - echo "Consider consolidating rust.yml and rust-ci.yml" - fi - echo "No critical duplicates found" - - name: Check CodeQL Language Matrix - run: | - echo "=== Checking CodeQL Configuration ===" - if [ ! -f .github/workflows/codeql.yml ]; then - echo "No CodeQL workflow found (optional)" - exit 0 - fi - - # Detect repo languages - has_js=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) - has_py=$(find . -name "*.py" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) - has_go=$(find . -name "*.go" -path "*/src/*" -o -path "*/cmd/*" -o -path "*/pkg/*" 2>/dev/null | head -1) - has_rs=$(find . -name "*.rs" -path "*/src/*" 2>/dev/null | head -1) - has_java=$(find . -name "*.java" -path "*/src/*" 2>/dev/null | head -1) - has_rb=$(find . -name "*.rb" -path "*/lib/*" -o -path "*/app/*" 2>/dev/null | head -1) - - echo "Detected languages:" - [ -n "$has_js" ] && echo " - javascript-typescript" - [ -n "$has_py" ] && echo " - python" - [ -n "$has_go" ] && echo " - go" - [ -n "$has_rs" ] && echo " - rust (note: CodeQL rust is limited)" - [ -n "$has_java" ] && echo " - java-kotlin" - [ -n "$has_rb" ] && echo " - ruby" - - # Check for over-reach - if grep -q "language:.*'go'" .github/workflows/codeql.yml && [ -z "$has_go" ]; then - echo "WARNING: CodeQL configured for Go but no Go files found" - fi - if grep -q "language:.*'python'" .github/workflows/codeql.yml && [ -z "$has_py" ]; then - echo "WARNING: CodeQL configured for Python but no Python files found" - fi - if grep -q "language:.*'java'" .github/workflows/codeql.yml && [ -z "$has_java" ]; then - echo "WARNING: CodeQL configured for Java but no Java files found" - fi - if grep -q "language:.*'ruby'" .github/workflows/codeql.yml && [ -z "$has_rb" ]; then - echo "WARNING: CodeQL configured for Ruby but no Ruby files found" - fi - - echo "CodeQL check complete" - - name: Check Secrets Guards - run: | - echo "=== Checking Secrets Usage ===" - # Look for secrets without conditional guards in mirror workflows - if [ -f .github/workflows/mirror.yml ]; then - if grep -q "secrets\." .github/workflows/mirror.yml; then - if ! grep -q "if:.*vars\." .github/workflows/mirror.yml; then - echo "WARNING: mirror.yml uses secrets without vars guard" - echo "Add 'if: vars.FEATURE_ENABLED == true' to jobs" - fi - fi - fi - echo "Secrets check complete" - - name: Summary - run: | - echo "" - echo "=== Workflow Linter Summary ===" - echo "All critical checks passed." - echo "" - echo "For more info, see: robot-repo-bot/ERROR-CATALOG.scm" diff --git a/affinescript/.gitignore b/affinescript/.gitignore deleted file mode 100644 index e6e2016..0000000 --- a/affinescript/.gitignore +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# RSR-compliant .gitignore - -# OS & Editor -.DS_Store -Thumbs.db -*.swp -*.swo -*~ -.idea/ -.vscode/ - -# Build -/target/ -/_build/ -/build/ -/dist/ -/out/ -# Idris2 build artifacts (nested, e.g. under formal-verification dirs) -**/solo-core/build/ - -# Dependencies -/node_modules/ -/vendor/ -/deps/ -/.elixir_ls/ - -# Rust -# Cargo.lock # Keep for binaries - -# Elixir -/cover/ -/doc/ -*.ez -erl_crash.dump - -# Julia -*.jl.cov -*.jl.mem -/Manifest.toml - -# ReScript -/lib/bs/ -/.bsb.lock - -# Python (SaltStack only) -__pycache__/ -*.py[cod] -.venv/ - -# Ada/SPARK -*.ali -/obj/ -/bin/ -!bin/*.ml -!bin/dune - -# Haskell -/.stack-work/ -/dist-newstyle/ - -# Chapel -*.chpl.tmp.* - -# Secrets -.env -.env.* -*.pem -*.key -secrets/ - -# Test/Coverage -/coverage/ -htmlcov/ - -# Logs -*.log -/logs/ - -# Temp -/tmp/ -*.tmp -*.bak -*.wasm -/a.out diff --git a/affinescript/.machine_readable/6a2/AGENTIC.a2ml b/affinescript/.machine_readable/6a2/AGENTIC.a2ml deleted file mode 100644 index cd1fb1d..0000000 --- a/affinescript/.machine_readable/6a2/AGENTIC.a2ml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# AGENTIC.a2ml — AI agent constraints and capabilities -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[agent-permissions] -can-edit-source = true -can-edit-tests = true -can-edit-docs = true -can-edit-config = true -can-create-files = true - -[agent-constraints] -# What AI agents must NOT do: -# - Never use banned language patterns (believe_me, unsafeCoerce, etc.) -# - Never commit secrets or credentials -# - Never use banned languages (TypeScript, Python, Go, etc.) -# - Never place state files in repository root (must be in .machine_readable/) -# - Never use AGPL license (use MPL-2.0) - -[maintenance-integrity] -fail-closed = true -require-evidence-per-step = true -allow-silent-skip = false -require-rerun-after-fix = true -release-claim-requires-hard-pass = true - -[automation-hooks] -# on-enter: Read 0-AI-MANIFEST.a2ml, then STATE.a2ml -# on-exit: Update STATE.a2ml with session outcomes -# on-commit: Run just validate-rsr diff --git a/affinescript/.machine_readable/6a2/ECOSYSTEM.a2ml b/affinescript/.machine_readable/6a2/ECOSYSTEM.a2ml deleted file mode 100644 index 4a29adf..0000000 --- a/affinescript/.machine_readable/6a2/ECOSYSTEM.a2ml +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# ECOSYSTEM.a2ml — Ecosystem position -# Converted from ECOSYSTEM.scm on 2026-03-21 - -[metadata] -project = "affinescript" -ecosystem = "hyperpolymath" - -[position] -type = "programming-language" -purpose = "A systems programming language combining affine types, dependent types, row polymorphism, and extensible effects, compiling to WebAssembly" - -[pipeline] -position = "co-product" -chain = "katagoria → typell → typed-wasm → PanLL" -notes = "AffineScript compiles to typed WasmGC. TypeLL grounds its type system. typed-wasm provides the shared binary layout and ABI conventions." -coordination = "nextgen-typing" - -[related-projects] -projects = [ - { name = "typell", relationship = "type-theory-foundation", notes = "TypeLL's open-ended progressive framework grounds AffineScript's type system. typell-affinescript bridge crate." }, - { name = "typed-wasm", relationship = "aggregate-library", notes = "Shared binary layout conventions for WasmGC cross-language calls. AffineScript Option[T]/Result[T,E]/String have agreed layouts here." }, - { name = "nextgen-typing", relationship = "coordination-parent", notes = "Coordination monorepo for the type theory pipeline. Canonical architecture doc." }, - { name = "ephapax", relationship = "sibling-wasm-target", notes = "Both compile to typed WasmGC. Independent designs; converge at binary level via typed-wasm." }, - { name = "katagoria", relationship = "research-upstream", notes = "Type theory research origination. Future AffineScript type system extensions may originate there." }, - { name = "panll", relationship = "consumer", notes = "AffineScript is one of PanLL's development language targets." }, -] diff --git a/affinescript/.machine_readable/6a2/META.a2ml b/affinescript/.machine_readable/6a2/META.a2ml deleted file mode 100644 index efecf35..0000000 --- a/affinescript/.machine_readable/6a2/META.a2ml +++ /dev/null @@ -1,671 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# META.a2ml — Project meta-information -# Converted from META.scm on 2026-03-21 - -[metadata] -project = "affinescript" -author = "Jonathan D.A. Jewell " -license = "MPL-2.0" -standard = "RSR 2026" - -# ────────────────────────────────────────────────────────────────────────────── -# Architecture Decision Records -# ────────────────────────────────────────────────────────────────────────────── -# ADRs are numbered sequentially and never renumbered. Superseded ADRs remain -# in place with status = "superseded" and a pointer to the replacement. -# Added 2026-04-10 during Track F1 (Idris2 Solo-core formalisation) — the -# formal-verification track surfaced three decisions about the QTT type system -# that were previously implicit. - -[[adr]] -id = "ADR-001" -status = "accepted" -date = "2026-04-10" -title = "Split-Γ is the canonical presentation of the QTT type system" -context = """ -The Idris2 Solo-core formalisation (docs/academic/formal-verification/solo-core/) -revealed that docs/spec.md §3.6 presents typing rules against a shared Γ -(writing e.g. T-App with both premises typed under the same Γ), while -lib/quantity.ml runs a post-hoc usage walk that accumulates per-variable -usage summed across subterms. The literature-standard QTT presentation -(Atkey 2018, Granule, Idris2 core) uses explicit context splitting - Γ = Γ₁ + q·Γ₂ -at every eliminator position. - -Both forms are extensionally equivalent for the Solo core (same programs -accepted/rejected). The difference is purely presentational. -""" -decision = """ -Split-Γ is canonical. The spec, the formalisation, and all future -documentation present typing rules in split-Γ form. The post-hoc usage walk -in lib/quantity.ml is an equivalent implementation strategy, documented as -such in a comment, not the definition of the type system. -""" -consequences = """ -- docs/spec.md §3.6 is rewritten to show explicit context splitting (~30-60 LOC). -- lib/quantity.ml gets a header comment explaining that its flat walk is an - equivalent implementation of the split-Γ rules, with a pointer to this ADR. -- The Idris2 formalisation's Typing.idr (which uses split-Γ) becomes the - mechanised witness of the spec — no translation layer required. -- Progress + preservation proofs can proceed in textbook form. -- Dependability gain: one source of truth. The spec-vs-impl gap (the failure - mode flagged in Track B's audit of TYPECHECKER-COMPLETION.md) cannot - structurally hide in this part of the system. -- Interop gain: the spec becomes citable and legible to anyone with QTT - background (Atkey, Idris2, Granule, Quill readers). -- Reader-tax cost: split-Γ notation is heavier than shared-Γ for newcomers. - Mitigated by surface-language tutorials keeping informal shared-Γ intuition - while the spec remains the formal reference. -""" - -[[adr]] -id = "ADR-002" -status = "accepted" -date = "2026-04-10" -title = "Let rule scales value context by binder quantity (QTT-orthodox)" -context = """ -During the Decision-2 audit of docs/spec.md T-Let against lib/typecheck.ml -and lib/quantity.ml, the current implementation was found to handle Let -unsoundly: - -- lib/typecheck.ml:631-665 (ExprLet) does zero quantity handling — pure - HM-style inference with let-generalisation, quantities entirely absent. -- lib/quantity.ml:251-253 (ExprLet in infer_usage_expr) does a flat walk - that sums e1's and e2's usages without scaling e1 by the binder's quantity. - -This is equivalent to the rule - Γ₁ ⊢ e1 : A (Γ₂, x:A) ⊢ e2 : B - ───────────────────────────────── - Γ₁ + Γ₂ ⊢ let x = e1 in e2 : B -rather than the QTT-orthodox - Γ₁ ⊢ e1 : A (Γ₂, x:^q A) ⊢ e2 : B - ───────────────────────────────────── - q·Γ₁ + Γ₂ ⊢ let x :^q = e1 in e2 : B - -Two concrete soundness bugs follow: - -BUG-001 (high severity — linear-value smuggling): - let x :ω = linear_resource() in use_x_once(x) - Current impl accepts; QTT-correct form rejects (e1's Γ must be scaled by - ω, so any Γ₁ containing a linear variable becomes ill-formed). - -BUG-002 (medium severity — erasure failure): - let x :0 = expensive_proof_term() in body_not_using_x - Current impl evaluates e1 and consumes its resources; QTT-correct form - scales e1's Γ by 0, producing the zero context, which means e1 can be - erased at runtime. - -BUG-001 is a soundness hole that defeats the point of affine types in any -program where ω-binders and linear values coexist. -""" -decision = """ -The Let rule in both the spec and the implementation scales the value -context by the binder's quantity. T-Let becomes: - - Γ₁ ⊢ e1 : A (Γ₂, x:^q A) ⊢ e2 : B - ───────────────────────────────────── - q·Γ₁ + Γ₂ ⊢ let x :^q = e1 in e2 : B - -Unannotated Let defaults to q = ω (matching current behaviour for all -existing unannotated code — so the fix is non-breaking for the common case). -""" -consequences = """ -- lib/quantity.ml ExprLet case grows a scale-by-q step before summing - into the outer context (~10 LOC). -- lib/typecheck.ml ExprLet case threads quantities (folded into Track A3 - which is already rewriting quantity flow through typecheck.ml). -- docs/spec.md T-Let rule is rewritten to show the scaling explicitly. -- Two regression tests added as Track A2 fixtures covering BUG-001 and - BUG-002. -- The Idris2 formalisation (Typing.idr THLet) already assumes the scaled - form — no rework there. -- Users who had previously (accidentally) relied on the unsound behaviour - to duplicate linear values through ω-lets will now get a compile error. - Expected blast radius: near-zero because affine enforcement was never - wired in the first place (see ADR-001 rationale). -""" - -[[adr]] -id = "ADR-003" -status = "accepted" -date = "2026-04-10" -title = "Evaluation order is strict CBV, left-to-right, for all n-ary forms" -context = """ -During the Decision-3 audit of lib/interp.ml for the Track F1 Step relation, -the evaluator was found to be internally inconsistent about evaluation order -of n-ary expression forms. - -- ExprBinary (lib/interp.ml:135-138) is explicitly left-to-right: - let* left_val = eval env left in - let* right_val = eval env right in - eval_binop op left_val right_val -- ExprApp args (:130), ExprTuple (:144), ExprArray (:148) all delegate to - eval_list, which is implemented with List.fold_right: - and eval_list env exprs = - List.fold_right (fun expr acc -> - let* vals = acc in - let* v = eval env expr in - Ok (v :: vals) - ) exprs (Ok []) - OCaml is strict, so in f (fold_right f [a;b;c] init) the innermost - application runs first — eval env c is forced before eval env b before - eval env a. Net result: right-to-left. - -This was almost certainly unintentional; nothing currently depends on the -order because affine enforcement was never wired. However: - -- The WASM and Julia backends will naturally generate left-to-right code, - producing cross-backend semantic divergence for any program with effectful - or resource-consuming arguments. -- The effect system (70% complete per STATE.a2ml) would observe handler - ordering tied to evaluation order, making this a latent soundness hazard. -- A Track F1 Step relation defined in textbook CBV left-to-right form would - disagree with the interpreter, requiring a non-standard right-to-left - presentation or a proof of order-independence (which is false in general - for effectful languages). -""" -decision = """ -All n-ary expression forms evaluate their subterms strict left-to-right. -This applies to: application arguments, tuples, arrays, record fields, -function calls in all positions. - -lib/interp.ml eval_list is fixed to left-to-right: - and eval_list env exprs = - let rec loop acc = function - | [] -> Ok (List.rev acc) - | e :: rest -> - let* v = eval env e in - loop (v :: acc) rest - in - loop [] exprs - -Track F1's Step relation uses the standard CBV left-to-right presentation - e1 → e1' ⟹ e1 e2 → e1' e2 (reduce function first) - e2 → e2' ⟹ v1 e2 → v1 e2' (then argument, only once function is a value) -with the symmetric congruences for pairs, tuples, etc. -""" -consequences = """ -- lib/interp.ml eval_list is replaced (~6 LOC diff). -- docs/spec.md gains (or receives an edit to) an explicit operational - semantics section stating strict CBV, left-to-right. -- A regression test lands in Track A2 exercising a program whose output - depends on left-to-right evaluation (e.g. a tuple of two side-effecting - calls that mutate a shared counter). -- WASM, Julia, and the effect system all become consistent with the - interpreter by default — no backend fix required. -- Track F1 can proceed on its current footing to mechanise Step and the - progress/preservation proofs without author input. -- Existing programs are unaffected unless they rely on observable right- - to-left evaluation of tuple/arg components. The interpreter is the only - place that did this, and nobody has filed a bug, so blast radius is - expected to be zero. -""" - -[[adr]] -id = "ADR-007" -status = "accepted" -date = "2026-04-10" -accepted = "2026-04-10" -title = "Surface syntax for quantity annotations: @linear/@erased/@unrestricted (primary) + :1/:0/:ω (sugar)" -context = """ -The Track A audit on 2026-04-10 found that affine enforcement is wired -through the CLI (Quantity.check_program_quantities runs from -lib/typecheck.ml:1206 on every check/compile/eval invocation) but -**unreachable** from user programs because the surface syntax for declaring -quantity annotations on let-binders does not exist. Specifically: - -- lib/ast.ml ExprLet and StmtLet have no quantity field. They carry - el_mut/sl_mut, el_pat/sl_pat, el_ty/sl_ty, el_value/sl_value, el_body - — no el_quantity. Only function `param` and `type_param` carry - `p_quantity`/`tp_quantity`. -- lib/lexer.ml never emits ZERO/ONE tokens. Source `0` and `1` are lexed - as `INT 0`/`INT 1`. The only quantity literal that round-trips through - source today is `omega`/`ω` (lexer.ml:53 and lexer.ml:153 respectively). -- lib/parser.mly's `quantity` rule (lines 180-183) takes ZERO|ONE|OMEGA - but only OMEGA is reachable from source. Even on function parameters, - `:1` and `:0` annotations cannot be written. -- ADR-002 specifies the QTT-orthodox scaled Let rule (q·Γ₁ + Γ₂), which - requires the binder to carry a quantity. Without surface syntax to - attach one, the rule has no input to scale by, BUG-001 cannot be - closed, and the headline affine-types feature remains theatre. - -Surface syntax is a language-design decision, not an implementation -detail. The decision needs author input before any of items 1-6 in -[track-a-manhattan] proceed. -""" -options = """ -Three candidate surface syntaxes are listed. None has been chosen. - -# Option A — caret-quantity suffix on `let` keyword - - let^1 x = linear_resource() in use_x_once(x) - let^0 _proof = expensive_term() in body - let^ω y = pure_int() in use_y_many(y) - - Pros: - - Visually parallel to T-Let's `let x :^q A` notation in QTT papers. - - The `^` is currently unused in the lexer, so no disambiguation cost. - - The annotation lives on the keyword, not the binder, which mirrors - the type-theory framing where `q` parameterises the rule, not the - variable. - - Easy parser rule: LET (CARET quantity)? pat (COLON ty)? EQ value. - - Cons: - - `^` is unfamiliar surface syntax for users coming from Rust/Idris2. - - The `^` may collide with future bitwise-XOR or pow operators - (currently OpBitXor exists in ast.ml but the lexer surface form is - unclear). - - Requires users to type a non-letter character on every annotation. - -# Option B — colon-quantity prefix on the type ascription - - let x :1 :Int = linear_resource() - let x :0 :Int = expensive_term() - let x :ω :Int = pure_int() - let x :1 = linear_resource() # type inferred - - Pros: - - Reuses the existing COLON token; no new operators needed. - - Mirrors the function parameter syntax, which already accepts - `qty? own? name COLON ty` (parser.mly:186). - - Familiar to readers of Idris2 (`x : 1 A`) and Quantitative Type - Theory papers. - - Cons: - - Two consecutive colons looks awkward and may confuse readers - expecting `::` (the path separator COLONCOLON token). - - When the type is inferred (`let x :1 = e`), the parser needs - one-token lookahead to distinguish `:1` (quantity) from `:Int` - (type). This is a minor parser challenge but non-zero. - - The lexer must additionally route `INT 0`/`INT 1` to ZERO/ONE in - quantity position, which is context-sensitive lexing — best handled - by accepting `INT 0|INT 1` directly in the parser's quantity rule - instead, but that introduces a small grammar wart. - -# Option C — annotation-style attribute before `let` - - @linear let x = linear_resource() in use_x_once(x) - @erased let _proof = expensive_term() in body - @unrestricted let y = pure_int() in use_y_many(y) - let z = pure_int() in use_z(z) # default = unrestricted - - Pros: - - Reads naturally; `@linear` and `@erased` are self-documenting and - require no quantity-theory background. - - Trivially extensible to future modal annotations (e.g. `@borrowed`, - `@owned`) without grammar changes. - - Parser change is local: AT (ident) LET pat (COLON ty)? EQ value. - No COLON disambiguation, no new operator tokens. - - Aligns with the language-policy preference for descriptive names - over symbolic ones (CLAUDE.md: "Always use descriptive variable - names"; the same instinct applied to annotations). - - Cons: - - Verbose. `@linear let x = ...` is six tokens vs `let^1 x = ...`'s - four. - - Diverges from the QTT mathematical notation, making it harder for - paper-trained readers to map source to spec. Mitigation: spec.md - can show both forms in parallel. - - The `@` token is currently unused, so it's free, but introducing - it for one feature creates pressure to use it for others (which - may or may not be desirable depending on language direction). - - Requires a small mapping table from attribute name to quantity - value (`linear → QOne`, `erased → QZero`, `unrestricted → QOmega`). -""" -decision = """ -Hybrid: Option C accepted as primary, Option B accepted as sugar. - -Both surface syntaxes parse to the same internal representation. -Tutorials, error messages, IDE tooling, and the spec's prose all use -Option C. Option B remains legal for users porting from QTT papers or -who prefer the compact form. Option A is rejected outright. - -# Primary surface form (Option C — what tutorials and error messages use) - - @linear — used exactly once (QOne) - @erased — must not be used at runtime (QZero) - @unrestricted — any number of uses (QOmega), the default - -Examples: - @linear let x = linear_resource() in use_x_once(x) - @erased let _proof = expensive_term() in body_not_using_proof - @unrestricted let y = pure_int() in use_y_many(y) - let z = pure_int() in use_z(z) # default: @unrestricted - - fn consume(@linear x: Resource) -> Unit = release(x) - fn weigh(@erased _proof: Even(n), n: Nat) -> Nat = n / 2 - -# Sugar surface form (Option B — accepted but not promoted) - - let x :1 :Resource = linear_resource() in use_x_once(x) - let _proof :0 :Even(n) = expensive_term() in body - let y :ω :Int = pure_int() in use_y_many(y) - let z :Int = pure_int() in use_z(z) # no quantity = @unrestricted - - fn consume(x :1: Resource) -> Unit = release(x) - fn weigh(_proof :0: Even(n), n: Nat) -> Nat = n / 2 - -# Equivalence - - @linear ≡ :1 - @erased ≡ :0 - @unrestricted ≡ :ω (also: omitted entirely) - -Both forms parse to the same `el_quantity = Some QOne | QZero | QOmega` -in lib/ast.ml. The compiler chooses the canonical form (Option C) when -emitting diagnostics, formatter output, and pretty-printed source. The -formatter rewrites Option B to Option C on `affinescript fmt`, with an -opt-out flag `--keep-quantity-sugar` for users who deliberately prefer -the compact form. - -Rationale (decided per the standing priority order -dependability > security > interop > usability/accessibility/marketability -> performance > versatility > functional extension): - -- Usability/accessibility/marketability beats consistency-with-papers - in the priority order, so Option C must be the primary form a new - user encounters in tutorials and error messages. A new user reading - `@linear let x = e` can form a working hypothesis without consulting - the spec. The same user looking at `let x :1 = e` has nothing to - Google. -- However, the priority order does not require *exclusivity*. Accepting - Option B as sugar costs ~15 LOC of parser surface area and one - paragraph in the spec, and in return it: - * Preserves consistency with the existing function-parameter syntax - (which uses `qty? own? name COLON ty`), so the param-quantity gap - closes with the *same* grammar production rather than a parallel - one. Users learning the param syntax already see `:1` in the - grammar; the sugar makes that knowledge transfer to let-binders. - * Preserves citation isomorphism with QTT papers — anyone porting an - Idris2 example or a Granule example can paste the numeric form - directly without rewriting it. - * Gives advanced users a denser form for code where annotations are - frequent and English keywords would create visual noise. -- Compiler errors and source code share the C vocabulary regardless of - which form the user wrote: "`@linear` binding 'x' used 2 times" reads - cleanly without mental translation, even if the source was written - as `:1`. The diagnostic always speaks the canonical form. -- Tutorials can be example-driven from the first runnable program with - Option C; the QTT semiring and Option B sugar become optional - advanced reading rather than a prerequisite for "hello world with - affine types." -- The `@` namespace is now committed to AffineScript and may grow to - cover additional modal annotations (e.g. `@total`, `@pure`, - `@borrowed`, `@owned`) in future ADRs. This is an intentional - reservation, not a side effect. -- The Rust/Linear-Haskell precedent is real: Rust ships affine - semantics under approachable keywords and reaches a vastly larger - audience than Linear Haskell, which exposes the type-theory - machinery directly. AffineScript chooses Rust's marketing instinct - for the primary form, while keeping Linear Haskell's notation - available for users who already think in it. - -# Style guide commitment - -The spec commits explicitly to keeping both forms supported. Future -contributors proposing to drop Option B sugar must amend this ADR -rather than removing it silently. The recommended convention in -published AffineScript code is: - -- New code: Option C (`@linear let x = e`). -- Code accompanying a paper: either form, author's choice. -- Auto-formatted code: Option C (formatter rewrites unless - `--keep-quantity-sugar` is set). -- Generated code (compiler diagnostics, IDE quick-fixes, refactoring - tools): Option C exclusively. - -Rejected alternatives: - -- Option A (`let^q x = e`): rejected. Lowest implementation cost but - worst on the priority order — symbolic, opaque to non-TT readers, - forecloses future use of `^` for bitwise XOR or exponentiation. -- Option B exclusively (without C): rejected for the same - accessibility/adoption reasons that make C the primary form. -- Option C exclusively (without B sugar): considered and rejected. The - marginal cost of accepting B is low (~15 LOC parser, one paragraph - spec), and the benefit — closing the param-quantity gap with one - grammar production and preserving paper-citation isomorphism — is - worth the cost given the standing priority order's high weighting - of interop alongside accessibility. -""" -consequences = """ -- lib/ast.ml gains el_quantity / sl_quantity fields on ExprLet and - StmtLet. ~10 modules pattern-matching on Let must be updated - mechanically (typecheck, interp, codegen, codegen_gc, julia_codegen, - formatter, sexpr_dump, json_output, linter, opt, desugar_traits). -- lib/lexer.ml gains an AT token for `@`. ~2 LOC. -- lib/token.ml gains the AT token kind. ~2 LOC. -- lib/parser.mly gains: - * AT ident — a quantity_attr rule that maps `@linear`/`@erased`/ - `@unrestricted` to QOne/QZero/QOmega and rejects unknown attribute - names with a parse error pointing at the documented set. - * Optional quantity_attr prefix on let_decl, stmt_let, param, and - lambda_param productions. ~15 LOC. - * The existing numeric quantity rule (ZERO|ONE|OMEGA) gains INT 0 - and INT 1 as accepted lexemes (since the lexer emits INT not - ZERO/ONE for `0` and `1`). This makes Option B sugar reachable - from source on let-binders and params, in addition to type_params - where it already worked. ~5 LOC. - * Both productions resolve to the same el_quantity / sl_quantity / - p_quantity slot — they are alternative concrete syntaxes for the - identical abstract syntax. -- lib/quantity.ml ExprLet/StmtLet cases gain context scaling per - ADR-002. ~25 LOC including a `scale_usage_by` helper. -- Error messages in lib/quantity.ml format_quantity_error are updated - to use `@linear` / `@erased` / `@unrestricted` vocabulary instead of - the show_quantity numeric forms. -- Regression fixtures land covering BOTH surface forms: - * test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine — must - be rejected (Option C form: `@unrestricted let x = ...`). - * test/e2e/fixtures/bug_001_sugar_form.affine — must be rejected - (Option B form: `let x :ω = ...`). Proves both surface forms are - enforced through the same code path. - * test/e2e/fixtures/affine_let_valid.affine — must pass (Option C). - * test/e2e/fixtures/affine_let_valid_sugar.affine — must pass - (Option B). -- docs/spec.md T-Let rule is rewritten to show the formal QTT notation - alongside both surface forms (Option C primary, Option B sugar) so - paper-trained readers and pedagogy-first readers both see their - preferred presentation. -- docs/spec.md gains a "Surface syntax for quantity annotations" - section documenting the equivalence table and the style-guide - commitment from this ADR. -- BUG-002 (`:0` lets do not erase their RHS) closes in the same change, - per ADR-002's consequence list. -- The affine-types feature flag in STATE.a2ml [features] flips from - `wired-but-unreachable` to `wired-and-reachable` once the change - lands. -- Future modal annotations (`@total`, `@pure`, `@borrowed`, `@owned`, - etc.) inherit the same `@`-attribute parsing infrastructure for free. -- The formatter (lib/formatter.ml) gains a `--keep-quantity-sugar` flag - that defaults to OFF (so default `affinescript fmt` rewrites Option - B sugar to Option C primary). Users opting in retain their chosen - form across format passes. -""" -references = [ - ".machine_readable/descriptiles/STATE.a2ml [track-a-manhattan]", - ".machine_readable/descriptiles/STATE.a2ml [[open-bug]] BUG-001", - ".machine_readable/descriptiles/STATE.a2ml [[open-bug]] BUG-002", - ".machine_readable/descriptiles/META.a2ml [[adr]] ADR-002 (scaled Let rule)", - "lib/ast.ml ExprLet (lines 105-111), StmtLet (lines 170-176)", - "lib/lexer.ml (ZERO/ONE never emitted)", - "lib/parser.mly quantity rule (lines 180-183)", -] - -[[adr]] -id = "ADR-008" -status = "accepted" -date = "2026-04-11" -title = "Effect invocation uses direct call syntax — no 'perform' keyword" -context = """ -Algebraic effects require a syntax for invoking an effect operation at a -call site. Two candidates exist in the literature: - -1. Direct call: `Http.get(url)` — the effect operation looks like a - namespaced function call. The effect is declared in the return type - annotation (`-> T / Http, Async`) not at each call site. - -2. Explicit perform: `perform Http.get(url)` — the keyword makes the - effectful nature of the call visible at every use site. Used by Koka - (earlier versions) and some effect-system research languages. - -The AffineScript DESIGN-VISION.adoc already shows direct call style in its -effect examples. The decision needed to be made explicit so the conformance -suite, parser, and documentation are unambiguous. -""" -decision = """ -Effect operations are invoked with direct call syntax. No `perform` keyword. - - fn fetch_user(id: Int) -> User / Http, Async { - let resp = Http.get("/users/" ++ show(id)) - Async.await(resp.json()) - } - -The effect is declared once — in the return type annotation (`/ Http, Async`). -The call sites are plain calls. The type signature is the contract; the call -site is just a call. -""" -consequences = """ -- The conformance suite, parser, and spec must not admit or require `perform`. -- Effect operation calls are syntactically indistinguishable from regular - function calls; the distinction is entirely in the type system. -- Error messages for unhandled effects reference the return type annotation, - not a `perform` site. -- This is consistent with the ergonomics goal: effect-heavy code does not - accumulate keyword noise at every call site. -- Face parsers (Python-face, JS-face) can map `async/await` to the Async - effect without introducing a `perform` concept — `await` desugars to - `Async.await(...)`, which is a direct call. -""" -references = [ - "docs/DESIGN-VISION.adoc (effect examples)", - "docs/specs/effects.md", -] - -[[adr]] -id = "ADR-009" -status = "accepted" -date = "2026-04-11" -title = "The conformance suite is authoritative — parser must conform to spec" -context = """ -The conformance suite (test/conformance/) contains valid AffineScript programs -drawn from the spec. As of 2026-04-11, 8 of 12 valid conformance tests fail -to parse. The three categories of failure are: - -1. Uppercase type names — `Int`, `String`, `Bool`, `Option` are written in - the spec with PascalCase. The parser's `ident` rule accepts only lowercase, - so `Int` fails to parse as a type name. - -2. ML-style enum syntax — the spec's enum declaration syntax does not match - what the parser accepts. - -3. Effect op type parameters — the spec includes type parameters on effect - operations; the parser does not support them. - -Two possible authorities: the spec (conformance suite) or the parser -(current implementation). -""" -decision = """ -The spec is authoritative. The parser must conform to the spec. This is -consistent with the standing estate-wide rule: 'Language scope lives in a -written thesis. Thesis authoritative, code must conform. If disagreement, -code is wrong.' - -Specific required changes: -1. Parser must accept PascalCase type names (`Int`, `String`, `Bool`, user- - defined types). The type name namespace is PascalCase; value names remain - camelCase/snake_case. This also fixes the conformance suite failure where - effect and enum type names could not be written. -2. Parser must accept the spec's enum declaration syntax (to be confirmed - against spec and fixed accordingly). -3. Parser must support type parameters on effect operations. - -The target is 12/12 conformance suite passing. -""" -consequences = """ -- lib/lexer.ml and lib/parser.mly are updated to accept PascalCase type names. -- The `ident` vs `type_ident` distinction is formalised in the grammar: - `ident` = lowercase-leading (values, variables, functions) - `type_ident` = uppercase-leading (types, effects, enums, type aliases) -- lib/parser.mly enum and effect rules are audited against spec and fixed. -- 12/12 conformance tests pass. The conformance suite becomes a live - regression suite — any future parser change that breaks a conformance test - is a bug, not a spec disagreement. -- All fixture files under test/e2e/fixtures/ that use lowercase type names - (e.g. `type point = ...`) are reviewed; lowercase type aliases remain valid - (they are value-level names) but builtin types (`int` written lowercase) - are normalised to PascalCase. -- Error messages and diagnostics use PascalCase type names consistently. -""" -references = [ - "test/conformance/", - "lib/lexer.ml", - "lib/parser.mly", - "docs/spec.md", -] - -[[adr]] -id = "ADR-010" -status = "accepted" -date = "2026-04-11" -title = "Face-aware error formatting is a first-class toolchain concern" -context = """ -AffineScript supports syntactic face layers (Python-face, JS-face, -pseudocode-face, canonical AffineScript) that allow developers to write -AffineScript using familiar surface syntax from other languages. The compiler -pipeline forks only at the parser; everything downstream (type checker, -codegen) is shared and operates on the canonical AST. - -Without face-aware error formatting, a developer writing Python-face -AffineScript receives type errors expressed in canonical AffineScript -syntax — terms and constructs they have deliberately not learned yet. This -shatters the face illusion at the worst possible moment (when the developer -is stuck and needs help) and is a direct contradiction of the adoption goal. -""" -decision = """ -Face-aware error formatting is a first-class toolchain concern, not an -afterthought. It is implemented as a formatting layer between the compiler -and the terminal, not inside the compiler itself. - -Architecture: - compiler (emits errors in canonical AST terms) - ↓ - face-aware error formatter ← separate concern, swappable - ↓ - terminal / IDE / LSP - -The compiler's error representation is canonical and face-agnostic. The -formatter receives: (error, active-face) → formatted error string. - -Each face provides an error vocabulary mapping: - canonical term → face term - e.g. (python-face) "affine binding" → "single-use variable" - e.g. (js-face) "Option[T]" → "T | null" [with note: 'use .unwrap_or()'] - -Error source spans are always reported in the face's syntax, not canonical -AffineScript syntax, since the user's file is written in the face. -""" -consequences = """ -- The compiler's error types carry enough information for the formatter to - reconstruct face-appropriate messages. No compiler internals leak face - knowledge. -- Each face ships with an error vocabulary file (part of the face definition). -- The toolchain (CLI, LSP server, playground) passes the active face to the - formatter. The formatter is the single point of face-awareness for errors. -- A developer on Python-face who hits a linearity violation sees: - Error: single-use variable 'x' used twice - → line 7, in greet - not: - Error: affine binding 'x' used 2 times (quantity violation: QOne, found 2) -- The face formatter is versioned alongside the face. When a face is - deprecated, its error formatter is deprecated with it. -- The IDE/LSP integration carries face information in the project config so - hover types, completions, and inline errors all speak the face's vocabulary. -- This is a design commitment, not an immediate implementation task. The - canonical compiler error representation must be designed with this - formatability requirement in mind from the start. -""" -references = [ - "docs/DESIGN-VISION.adoc (faces section)", - "docs/specs/faces.md (to be written)", -] diff --git a/affinescript/.machine_readable/6a2/NEUROSYM.a2ml b/affinescript/.machine_readable/6a2/NEUROSYM.a2ml deleted file mode 100644 index e1d34c0..0000000 --- a/affinescript/.machine_readable/6a2/NEUROSYM.a2ml +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# NEUROSYM.a2ml — Neurosymbolic integration metadata -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[hypatia-config] -scan-enabled = true -scan-depth = "standard" # quick | standard | deep -report-format = "logtalk" - -[symbolic-rules] -# Custom symbolic rules for this project -# - { name = "no-unsafe-ffi", pattern = "believe_me|unsafeCoerce", severity = "critical" } - -[neural-config] -# Neural pattern detection settings -# confidence-threshold = 0.85 -# model = "hypatia-v2" diff --git a/affinescript/.machine_readable/6a2/PLAYBOOK.a2ml b/affinescript/.machine_readable/6a2/PLAYBOOK.a2ml deleted file mode 100644 index b9215be..0000000 --- a/affinescript/.machine_readable/6a2/PLAYBOOK.a2ml +++ /dev/null @@ -1,26 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# PLAYBOOK.a2ml — Operational playbook -[metadata] -version = "0.1.0" -last-updated = "2026-04-11" - -[deployment] -# method = "gitops" # gitops | manual | ci-triggered -# target = "container" # container | binary | library | wasm - -[incident-response] -# 1. Check .machine_readable/descriptiles/STATE.a2ml for current status -# 2. Review recent commits and CI results -# 3. Run `just validate` to check compliance -# 4. Run `just security` to audit for vulnerabilities - -[release-process] -# 1. Update version in STATE.a2ml, META.a2ml -# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass) -# 3. Tag and push - -[maintenance-operations] -# Baseline audit: just maint-audit -# Hard release gate: just maint-hard-pass diff --git a/affinescript/.machine_readable/6a2/STATE.a2ml b/affinescript/.machine_readable/6a2/STATE.a2ml deleted file mode 100644 index 18f11df..0000000 --- a/affinescript/.machine_readable/6a2/STATE.a2ml +++ /dev/null @@ -1,215 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# STATE.a2ml — Project state checkpoint - -[metadata] -project = "affinescript" -version = "0.1.0" -last-updated = "2026-04-12" -status = "active" -session-note-2026-04-12-c = "STAGE 12 (SECOND IDAPTIK SCREEN) IN PROGRESS — CharacterSelectScreen chosen as dogfood target (6 class cards: Assault/Recon/Engineer/Signals/Medic/Logistics, 1 confirm → JessicaCustomise). Plan: (1) lib/tea_cs_bridge.ml — new Wasm module, same API surface as tea_bridge.ml, 7 msgs (0-5=select class, 6=confirm), update: selected_tag=msg+1, selected_tag 1-6=class highlighted, 7=navigate. (2) lib/dune + bin/main.ml — add cs-bridge subcommand and update which_arg/interface/verify-bridge. (3) IDApTIK: AffineTEARouter.js+.res add screenJessicaCustomise=6. (4) CharacterSelectScreen.res: csTeaBridge module-level ref, card click handlers → teaDrive, applyView syncs Wasm→ReScript selectedClass, confirm → AffineTEA.update+navigate via showScreenWithTag. Seam check: push TitleScreen→CharacterSelect, select class, confirm→JessicaCustomise, back-stack correct. 173/173 tests currently passing." -session-note-2026-04-12-b = "STAGES 7-11 COMPLETE (173/173 tests). Stage 9: per-path min/max linearity (count_uses_range, LinearDroppedOnSomePath, fn_push else-branch fix). Stage 10: tw_interface.ml multi-module boundary verifier (LinearImportCalledMultiple, LinearImportDroppedOnSomePath, CLI interface+verify-bridge cmds). Stage 11: source-level Cmd linearity — Cmd[ClickMsg] bracket syntax, quantity_of_ty_annotation returns QOne for Cmd[_], infer_usage_block declares QOne locals before subsequent stmts so uses tracked, fires LinearVariableUnused when Cmd dropped; builtins cmd_none+cmd_perform in typecheck/resolve/interp. Commit b8a0f9c." -session-note-2026-04-12-a = "STAGE 6 (CADRE ROUTER) COMPLETE: lib/tea_router.ml generates Wasm 1.0 router module (915 bytes, WebAssembly.validate=true). RouterModel at offset 64: screen_w(+0), screen_h(+4), stack_len(+8), popup_tag(+12), stack_data[8](+16). 11 functions: init, push(Linear), pop, present_popup(Linear), dismiss_popup, resize(Linear×2), get_screen_w/h/stack_len/stack_top/popup_tag. affinescript.ownership: 3 functions with Linear params (push, present_popup, resize). affinescript.tea_layout: version=1, 5 fields. router-bridge subcommand added. 8 new E2E tests; 113 total, 0 regressions. IDApTIK: AffineTEARouter.js + AffineTEARouter.res; Navigation.res: routerBridge field + setRouterBridge + showScreenWithTag + presentPopupWithTag + dismissPopup router sync + resize router sync; TitleScreen.res: teaDrive uses showScreenWithTag/presentPopupWithTag, router.wasm loaded on first show alongside titlescreen.wasm, graceful fallback. router.wasm committed to idaptik/public/assets/wasm/. Both repos pushed. commit: affinescript 5969b7b, idaptik e3eafd1." -session-note-2026-04-11-h = "STAGE 5 (AFFINATEA DRIVES SCENE) COMPLETE: teaDrive(engine, msgTag) is the single entry point from all 4 button handlers — update → applyView → navigate. applyView reads selected_tag via getSelected() and alpha-pulses the chosen button (0.65 → animate 1.0 over 0.22s). teaBridge loaded asynchronously on first show; graceful fallback to direct ReScript navigation when bridge is None. resize handler calls AffineTEA.setScreen(tea, width, height) to keep screen_w/h in sync with canvas. Button handlers reduced to single teaDrive call each. Navigation decision comes FROM Wasm state (selected_tag), not from button identity. Credits button wired (msgCredits) with TODO screen. IDApTIK TitleScreen is fully AffineTEA-driven for Stages 4+5." -session-note-2026-04-11-g = "STAGE 4 (WASM/JS BRIDGE FOR IDAPTIK) COMPLETE: lib/tea_bridge.ml generates complete TitleScreen TEA bridge Wasm 1.0 module (512 bytes). TitleModel at offset 64: screen_w(+0), screen_h(+4), bgm_playing(+8), selected_tag(+12). 7 functions: init, update (branchless selected_tag=msg+1), get_screen_w/h/bgm_playing/selected, set_screen. affinescript.ownership custom section: update param kind=1 (Linear). affinescript.tea_layout custom section: version=1, 4 fields. affinescript tea-bridge subcommand added to bin/main.ml. 6 bridge E2E tests added; 101 total tests passing. AffineTEA.js + AffineTEA.res bindings in idaptik/src/app/tea/. titlescreen.wasm (512 bytes, WebAssembly.validate=true, end-to-end Node.js verified) in idaptik/public/assets/wasm/." -session-note-2026-04-11-f = "LSP PHASE C COMPLETE: completion candidates subcommand shipped. extract_prefix_at scans backward from cursor col to extract ident prefix + dot-context flag. collect_completions filters symbol table by prefix and appends affine_keywords (suppressed in dot context). emit_completions emits JSON array {name,kind,type,detail}. complete_file + complete_cmd added to bin/main.ml (FILE LINE COL args). 6 E2E tests. 101 total, 0 regressions. NEXT: LSP Phase D." -session-note-2026-04-11-e = "LSP PHASE B COMPLETE (commit 79c0829): hover/goto-def subcommands shipped. span_contains, find_symbol_at (refs-first then def-spans), hover_to_json/goto_def_to_json, emit_hover/emit_goto_def, run_pipeline_for_query. 89 tests. NEXT: LSP Phase C (completion candidates from symbol table)." -session-note-2026-04-11-d = "TRAITS WIRED (commit 1ca143e): trait_registry in context; TopTrait/TopImpl in forward pass; ExprField trait fallback; find_impl unification-based; TopImpl bodies typechecked; 80 tests. NEXT: LSP Phase B." -session-note-2026-04-11-c = "LINEAR ARROWS ENFORCED (commit d2f9b7b): (1) lambda synth — |@linear x: T| e now synthesises T -[1]-> U; (2) lambda check mode — explicit quantity annotation validated against expected TArrow; (3) quantity.ml ExprLambda — annotated params declared in env, usage verified, violations accumulated in env.errors and drained after check_function_quantities step 3; scope leakage prevented via quantities save/restore. Also fixed codegen.ml missing custom_sections field (pre-existing wasm.ml change). 75 tests, 0 regressions. NEXT: traits generic resolution → unification integration; then LSP Phase B." -session-note-2026-04-11-b = "BUG-005 CLOSED: WasmGC silent drop+null fallbacks replaced with explicit CodegenError (UnboundFunction for missing func_indices entries, UnsupportedFeature for indirect/higher-order calls). Effects runtime SHIPPED: TopEffect now registers each op as a PerformEffect-raising VBuiltin in interp.ml (was no-op). Multi-arg handler arg_vals bug fixed. ExprResume wired to __resume__ continuation in handler env. WasmGC path: TopEffect ops registered as unreachable stubs with correct func_indices offsets; ExprHandle and ExprResume reject with UnsupportedFeature (EH proposal or CPS transform required for full dispatch)." - -[project-context] -name = "affinescript" -completion-percentage = 94 -phase = "affinatea-stage6-complete" -tagline = "Rust-inspired language with affine types, dependent types, row polymorphism, and extensible effects" -note = "Session 2026-04-11 final checkpoint. DONE THIS SESSION: (1) JS-face (lib/js_face.ml) + Pseudocode-face (lib/pseudocode_face.ml) — both wired into face.ml and bin/main.ml. resolve_face() wired into ALL six file handlers in bin/main.ml (parse/check/eval/compile/fmt/lint) — .rattle/.pyaff/.jsaff/.pseudoaff auto-detected without --face flag. (2) P3 packages: packages/affine-js/ (Deno ESM WASM loader, marshal/unmarshal, types.d.ts), packages/affine-ts/ (typed call helpers), packages/affine-res/ (ReScript bindings). (3) P4 educational: docs/guides/frontier-guide.adoc (6-chapter tutorial), docs/guides/warmup/ (4 warmup scripts). (4) CoffeeScript + ActionScript roadmap faces added to docs/specs/faces.md. (5) RattleScript distribution: distributions/rattlescript/ in-tree (Rust wrapper, build.rs, examples/), standalone repo hyperpolymath/rattlescript (GitHub, starred, v0.1.0-alpha released, 8 topics set, affinescript submodule, just bootstrap workflow). (6) dune build + dune runtest: clean, 0 regressions throughout. NEXT SESSION (clean thread): BUG-005 first (WasmGC silent bad codegen — replace fallback arms with CodegenError, ~30min), then effects runtime (interpreter handler dispatch + WASM codegen — the headline missing feature). Previous session note: P3 (aggregate library ecosystem) and P4 (educational materials) complete. P2 faces extended. P2 faces extended: JS-face (lib/js_face.ml) and Pseudocode-face (lib/pseudocode_face.ml) added, face.ml and bin/main.ml updated with all four active faces (canonical/python/js/pseudocode). aggregate packages: packages/affine-js/ (Deno ESM, WASM loader, marshal/unmarshal, types.d.ts), packages/affine-ts/ (typed call helpers, narrowing predicates), packages/affine-res/ (ReScript bindings with typed shortcuts and exhaustive matching). educational materials: docs/guides/frontier-guide.adoc (6-chapter unveiling tutorial), docs/guides/warmup/ (4 warmup scripts: basics/ownership/effects/rows). faces.md updated with JS-face and Pseudocode-face entries. Previously: Faces architecture (ADR-010, Priority 2) complete 2026-04-11. (1) lib/python_face.ml: source-level text preprocessor mapping Python surface syntax to canonical AffineScript — indentation→braces, def/True/False/None/and/or/not/class/pass keyword subs, elif/else chains, tail-position semicolon suppression. (2) lib/face.ml: face-aware error formatter mapping canonical error terms to face-specific vocabulary — Python face uses 'Ownership error / single-use variable' for linear violations, 'Name not found' for unbound vars, 'if/else type mismatch' for branch mismatches, renders Unit as None and Bool as bool. (3) bin/main.ml: --face [canonical|python] flag on all subcommands; parse_with_face selects preprocessor path; all error sites route through Face.format_*_error. (4) Span fidelity deferred: error spans currently refer to transformed canonical text positions (planned follow-up). (5) docs/specs/faces.md: face architecture reference doc written. (6) panic-attack audit: 1 High (search.js innerHTML — false positive but fixed to char-replacement), 2 Medium (LSP unwraps/shell heredoc — both false positives), 2 Low (TODO markers — informational). 73/73 E2E tests: 0 regressions. Seam check (items 1+2) verified: --face python on @linear double-use produces correct Python-face ownership error end-to-end." - -[components] -lexer = "complete-minus-affine-tokens (ZERO/ONE tokens for quantity literals NOT emitted; blocks QTT surface syntax). float_exponent now correctly named sub-regexp — scientific notation crash fixed 2026-04-11." -parser = "conformance-complete (ADR-009 achieved 2026-04-11). 12/12 valid tests parse. Added: optional SEMICOLON on type aliases/enums, enum leading-pipe syntax, effect op type params, ROW_VAR type params, ty_record_body/expr_record_body recursive rules for row-polymorphic records, field_name contextual keyword rule, struct literal expressions." -type-checker-rules = "99% (bidirectional inference, Never/bottom, block divergence, variant/constructor/record-spread/mutable bindings all implemented in lib/typecheck.ml)" -type-checker-enforcement-wiring = "wired (Typecheck.check_program is invoked from bin/main.ml on every check/compile/eval path; Quantity.check_program_quantities runs after typecheck at lib/typecheck.ml:1206; the gating IS live for the rules and quantity annotations that exist today)" -borrow-checker = "live-gate (2026-04-11): Borrow.check_program wired into check/compile pipeline at all 4 Typecheck success sites. Emits E0501-E0506 diagnostics. PlaceVar carries variable name for readable errors (file:line:col at both use and move sites). ExprMatch arm state merging fixed. Lexical borrow lifetime clearing in check_block. BUG-004 (lambda capture tracking) still deferred — requires type info propagation. lib/borrow.ml 669 LOC." -interpreter = "95%" -wasm-codegen = "92% (real-world game compiles: airborne-submarine-squadron/src/main.affine → 8KB WASM)" -wasm-gc-codegen = "70% (WasmGC proposal target: struct.new/struct.get/array.new_fixed/array.get; --wasm-gc CLI flag)" -julia-codegen = "exists" -lsp-phase-a = "complete" -lsp-phase-b = "complete (2026-04-11, commit 79c0829): hover/goto-def subcommands shipped. json_output.ml: span_contains (1-based, single+multi-line), find_symbol_at (references-first then def-spans), hover_to_json/goto_def_to_json/not_found_json, emit_hover/emit_goto_def. bin/main.ml: hover + goto-def subcommands, run_pipeline_for_query tolerates typecheck errors. 4 E2E tests. 89 total." -lsp-phase-c = "complete (2026-04-11): completion candidates subcommand shipped. json_output.ml: extract_prefix_at (scans backward from cursor col to find ident prefix + dot-context flag), collect_completions (symbol table prefix filter + affine_keywords list suppressed in dot context), emit_completions (JSON array). bin/main.ml: complete_file handler + complete_cmd (FILE LINE COL args). 6 E2E tests added (prefix extraction, prefix match, empty prefix, no-match, keyword included, dot-ctx suppresses keywords). 101 total tests, 0 regressions." -lsp-phase-d = "complete (2026-04-11): JSON-RPC LSP server shipped as `affinescript server --stdio`. lib/lsp_server.ml: Content-Length framing, document store (URI→source), pipeline cache (URI→symbols+refs+diags), run_pipeline (temp-file + span path fixup), lsp_range (1→0-based conversion), diag_to_lsp, publish_diagnostics. Handlers: initialize (hover+definition+completion caps), didOpen/didChange (pipeline+cache+push), didClose (clear), hover (markdown+type+quantity), definition (URI+range), completion (LSP kind mapping). bin/main.ml: server_cmd in default group. 4 E2E tests. 105 total, 0 regressions." -stdlib = "95% (5 stubs remain as extern builtins)" - -[stats] -compiler-loc = 12750 -compiler-modules = 39 -lsp-files = 5 -test-files = 54 - -[features] -# Honest status of the headline language features advertised in README.adoc. -# "declared-but-unwired" = surface syntax and/or internal module exists, but -# the feature is not enforced on user programs through the CLI pipeline. -affine-types = "wired-and-reachable (Track A Manhattan plan complete 2026-04-10. Quantity semiring in lib/quantity.ml; invoked from typecheck.ml:1206 inside the standard CLI pipeline. Surface syntax per ADR-007 hybrid: @linear/@erased/@unrestricted attributes (Option C primary) on let/stmt-let/param/lambda-param, AND :1/:0/:ω numeric sugar (Option B) on let/stmt-let. Scaled Let rule per ADR-002 implemented in lib/quantity.ml ExprLet/StmtLet — closes BUG-001 (ω-let smuggles linear values) and BUG-002 (zero-let erasure). Four regression fixtures in test/e2e/fixtures/ exercise both surface forms. Behavioural enforcement verified via E2E Quantity test suite — 4 new passing tests, 0 regressions.)" -linear-arrows = "enforced (2026-04-11): Three-part fix landed. (1) typecheck.ml lambda synth: |@linear x: T| e now synthesises T -[1]-> U (was always QOmega). (2) typecheck.ml lambda check mode: explicit param quantity annotation validated against expected TArrow quantity; unannotated params inherit context quantity. (3) quantity.ml ExprLambda: added env.errors accumulator; annotated lambda params declared via env_declare so env_use tracks them; usage verified with check_quantity after body walk; violations pushed to env.errors and drained at end of check_function_quantities (step 4). Saved/restored env.quantities entries to prevent scope leakage. Two E2E fixtures + 2 passing tests. 75 tests total, 0 regressions. Commit d2f9b7b pushed." -borrow-checker = "phase-3-in-progress (see lib/borrow.ml [IMPL-DEP: Phase 3])" -row-polymorphism = "60% (records + effects rows implemented in typecheck/unify; not fully exercised end-to-end)" -effects = "interpreter-complete (handler dispatch, PerformEffect propagation, ExprResume, multi-arg ops all wired in interp.ml 2026-04-11). WasmGC: ops registered as unreachable stubs; ExprHandle/ExprResume reject with UnsupportedFeature — full WASM dispatch needs EH proposal or CPS transform." -dependent-types = "parse-only (TRefined AST node exists and refinement predicates parse, but predicates do not reduce; no SMT/decision procedure wired in)" -traits = "90% (2026-04-11, commit 1ca143e): trait_registry added to typecheck context; TopTrait/TopImpl processed in forward pass; ExprField falls back to Trait.find_method_for_type on record-field failure; find_impl/find_impls_for_type use unification-based matching via fresh_impl_self_ty + Unify.unify instead of name string comparison; TopImpl bodies type-checked via check_fn_decl with Self bound; check_impl_satisfies_trait verifies required methods present. Two E2E fixtures + 2 tests. 80 total, 0 regressions. Remaining: associated type substitution in method bodies, where-clause supertrait enforcement, coherence checking.)" - -[track-a-manhattan] -owner = "primary" -scope = """ -Close the surface-syntax gap that makes affine enforcement unreachable from -user programs. Revised 2026-04-10 after the original ETA was set against the -incorrect premise that `Quantity.check_program_quantities` was unwired from -the CLI — it is wired (typecheck.ml:1206) and has been since at least -the Manhattan-recovery refactor (#19). The actual remaining work: - -1. AST: add `el_quantity : quantity option` to ExprLet, `sl_quantity` to - StmtLet. (~4 LOC + cascading pattern updates across ~10 modules that - match on Let.) -2. Lexer/parser: make ZERO/ONE tokens reachable in quantity position - (cheapest path: accept INT 0/INT 1 in the parser's `quantity` rule, - leaving the lexer alone). ~3 LOC. -3. Parser: extend `let_decl` and `stmt_let` rules with an optional - binder-quantity annotation. Surface-syntax choice is a language-design - call — see META.a2ml ADR-007 candidate. -4. quantity.ml ExprLet/StmtLet: implement context scaling per ADR-002. - Snapshot before walking el_value, walk it, scale all newly-recorded - usages by `q`, then merge back. ~25 LOC including a `scale_usage_by` - helper. -5. Two regression fixtures covering BUG-001 and the dual valid case. -6. spec.md T-Let rewrite to show the scaling explicitly. - -Out of scope for Track A: -- BUG-003 (eval_list L-to-R): SHIPPED 2026-04-10. interp.ml:347 now uses - fold_left + List.rev. Behavioural regression test deferred — depends on - the broken `let mut` interpreter path (separately tracked in the failing - baseline interp tests). -- BUG-004 (lambda capture tracking): blocked on borrow checker Phase 3. -- BUG-002 (zero-let erasure semantics): folds in with BUG-001 once - surface syntax exists. -""" -spine-files = ["lib/ast.ml", "lib/parser.mly", "lib/quantity.ml", "lib/typecheck.ml", "lib/interp.ml", "lib/codegen.ml", "lib/codegen_gc.ml", "lib/julia_codegen.ml", "lib/formatter.ml", "lib/sexpr_dump.ml", "lib/json_output.ml", "lib/linter.ml", "lib/opt.ml", "lib/desugar_traits.ml", "test/e2e/fixtures/", "docs/spec.md"] -eta = "T+7d from 2026-04-10 (ADR-007 accepted, implementation chain unblocked)" -blocking-decision = "RESOLVED 2026-04-10: ADR-007 accepted as hybrid C-primary + B-sugar. @linear/@erased/@unrestricted are the canonical forms emitted by tutorials, error messages, IDE tooling, and the formatter. The :1/:0/:ω numeric forms are accepted as sugar (legal in source, rewritten to attribute form by `affinescript fmt` unless --keep-quantity-sugar is set). See META.a2ml ADR-007 for full decision text." - -[[closed-bug]] -id = "BUG-001" -severity = "high" -category = "soundness" -title = "ω-let smuggles linear values" -discovered = "2026-04-10" -fixed = "2026-04-10" -description = """ -let x :ω = linear_resource() in use_x_once(x) was previously accepted. -The QTT-orthodox scaled Let rule (ADR-002) rejects this because scaling -the value context by ω promotes the linear usage of `linear_resource()` -to UMany, which the quantity checker reports as a linear-binding violation. -""" -fix = "lib/quantity.ml ExprLet/StmtLet cases now snapshot the env, walk the value, scale the per-variable delta by the binder quantity (via the new scale_usage helper), and merge the scaled deltas back. ExprLet was extended with el_quantity, StmtLet with sl_quantity (lib/ast.ml). Surface syntax landed per ADR-007 hybrid: @linear/@erased/@unrestricted primary + :1/:0/:ω sugar." -verification = "test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine (Option C form) and test/e2e/fixtures/bug_001_sugar_form.affine (Option B form) — both produce the @linear-vocabulary error message and are pinned in test/test_e2e.ml as `BUG-001 attr/sugar form rejects ω-let smuggling @linear`. Behaviourally verified end-to-end through `dune runtest`. The dual valid cases (affine_let_valid.affine and affine_let_valid_sugar.affine) confirm the rule does not over-reject." - -[[closed-bug]] -id = "BUG-002" -severity = "medium" -category = "semantics" -title = ":0 lets do not erase their RHS" -discovered = "2026-04-10" -fixed = "2026-04-10" -description = """ -let x :0 = expensive_proof_term() in body_not_using_x previously evaluated -expensive_proof_term() and consumed its resources. The QTT-orthodox scaled -Let rule scales the value context by 0, producing the zero context, which -means the RHS carries no runtime obligations and may be erased. -""" -fix = "Closed via the same scaled-Let infrastructure as BUG-001. The quantity-checking pass now correctly drops usage contributions from the RHS of an @erased-bound let (scale_usage QZero _ = UZero), so any linear variables consumed in the RHS no longer count against their owner. The optional interpreter-side erasure (skipping eval of @erased-bound RHS) is deferred — it is an optimisation, not a correctness requirement." -verification = "Covered transitively by the BUG-001 fixtures: the same scaling logic that closes BUG-001 closes BUG-002. A focused @erased fixture is on the follow-up backlog but not required to declare the bug fixed at the rule level." - -[[closed-bug]] -id = "BUG-003" -severity = "medium" -category = "semantics" -title = "eval_list evaluates right-to-left via List.fold_right" -discovered = "2026-04-10" -fixed = "2026-04-10" -description = """ -lib/interp.ml:347 eval_list previously used List.fold_right with monadic -bind, which in strict OCaml evaluates arguments right-to-left. ExprBinary -was already left-to-right, creating internal evaluator inconsistency and a -latent divergence point for future effects/affine enforcement. See ADR-003. -""" -fix = "Replaced fold_right with an explicit L-to-R recursive loop that accumulates results in reverse and reverses at the end. See lib/interp.ml `eval_list` (~12 LOC including the doc comment referencing ADR-003)." -regression-test-status = "deferred — observable order verification requires either the mutable-binding interpreter path (currently broken in baseline; the failing 'lambda'/'simple eval' interp tests share the same root cause) or a side-effecting builtin reachable from a fixture. Both routes are out of scope for the BUG-003 fix and should be revisited once the interpreter mutation path is repaired." -verification = "Code review against ADR-003 + dune build clean + dune runtest baseline-equivalent (22 pre-existing failures, none new, none resolved)." - -[[closed-bug]] -id = "BUG-004" -severity = "medium" -category = "soundness" -title = "Lambda-body usage not tracked against outer captures" -discovered = "2026-04-10" -fixed = "2026-04-11" -description = """ -lib/quantity.ml ExprLambda was a no-op for outer variable usage: captures -were not scaled by QOmega, so a lambda capturing a linear variable did not -raise LinearVariableUsedMultiple. Borrow checker also ignored captures, -allowing move of a captured variable after lambda creation. -""" -fix = """ -Two coordinated fixes (commit 48422d1): -1. quantity.ml ExprLambda: snapshot env, walk body (shadowing lambda params), - compute per-variable delta, restore env, re-apply delta scaled by QOmega. - scale_usage QOmega UOne = UMany → LinearVariableUsedMultiple for linear captures. -2. borrow.ml ExprLambda: collect_free walker finds all free variables in body, - creates Shared borrows for each captured place. Move of captured variable - → MoveWhileBorrowed. Borrows expire at block exit via lexical-lifetime clearing. - borrow_kind_name replaces show_borrow_kind for readable error messages. -""" -verification = "Manual smoke tests: lambda capturing moved value → 'cannot move `v` while shared-borrowed'; valid lambda (no owned captures) → passes. 73/73 tests pass." - -[[closed-bug]] -id = "BUG-005" -severity = "medium" -category = "codegen" -title = "WasmGC backend silently drops unknown function calls" -discovered = "2026-04-10" -fixed = "2026-04-11" -description = """ -lib/codegen_gc.ml ExprApp handling silently emitted 'drop all args + push null' -when the callee was not in the func_indices table, and did the same for -indirect calls. This was placeholder behaviour that produced wrong code rather -than failing loudly. -""" -fix = "Both fallback arms replaced with explicit CodegenError. Direct-call miss → Error (UnboundFunction id.name). Indirect/higher-order callee → Error (UnsupportedFeature \"indirect / higher-order call in WasmGC backend (call_ref not yet implemented)\"). New UnboundFunction variant added to codegen_error and format_codegen_error. commit b58178a." -regression-test-status = "deferred — fixture needs a known-unknown function name in a WasmGC compile path" - -# ─── Deferred upgrade: bring .machine_readable/ to current standards ────────── -# AffineScript predates most of the current hyperpolymath .machine_readable/ -# suite. The 6a2 core is present; the surrounding machinery is not. This is -# logged as deferred work, separate from the Manhattan recovery, to avoid -# scope creep into that plan. - -[deferred-upgrade] -# DONE 2026-04-12 — suite upgrade completed in dedicated session (no .ml changes) -status = "done" -completed = "2026-04-12" - -added = [ - ".machine_readable/CLADE.a2ml", - ".machine_readable/contractiles/ (migrated from root via git mv — history preserved)", - ".machine_readable/contractiles/adjust/Adjustfile.a2ml", - ".machine_readable/contractiles/intend/Intentfile.a2ml", - ".machine_readable/bot_directives/README.adoc", - ".machine_readable/bot_directives/coverage.a2ml", - ".machine_readable/bot_directives/debt.a2ml", - ".machine_readable/bot_directives/methodology.a2ml", - ".machine_readable/integrations/verisimdb.a2ml", - ".machine_readable/integrations/vexometer.a2ml", - ".machine_readable/ADJUST.contractile", - ".machine_readable/INTENT.contractile", - ".machine_readable/MUST.contractile", - ".machine_readable/TRUST.contractile" -] diff --git a/affinescript/.machine_readable/CLADE.a2ml b/affinescript/.machine_readable/CLADE.a2ml deleted file mode 100644 index ce120cd..0000000 --- a/affinescript/.machine_readable/CLADE.a2ml +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# Clade declaration — part of the gv-clade-index registry -# See: https://github.com/hyperpolymath/gv-clade-index - -[identity] -uuid = "b3f82c1a-4e9d-4b7a-a831-6c2d5e9f0a14" -primary-forge = "github" -primary-owner = "hyperpolymath" -canonical-name = "affinescript" -prefixed-name = "gv-affinescript" - -[clade] -primary = "language" -secondary = ["compiler", "type-system"] -assigned = "2026-04-12" -rationale = "AffineScript is a programming language with affine types targeting Typed WASM. Primary clade is 'language'; secondary clades capture the compiler pipeline and type-system research aspects." - -[forges] -github = "hyperpolymath/nextgen-languages" -# affinescript lives in the nextgen-languages monorepo submodule -# it is NOT a standalone GitHub repo - -[lineage] -type = "submodule" -parent = "nextgen-languages — the hyperpolymath language monorepo" -born = "2025-09-12" -note = "Predates ANCHOR.a2ml convention. Retroactively assigned clade 2026-04-12 during suite upgrade." - -[language] -# AffineScript-specific fields -paradigm = ["functional", "affine", "typed"] -compile-targets = ["typed-wasm", "julia", "wasm-gc"] -inspiration = ["ReScript", "Rust", "Granule", "OCaml"] -headline-feature = "affine-types-with-javascript-ergonomics" - -[thesis] -one-sentence = "AffineScript is JavaScript-ergonomic correctness: the developer experience of the -script family raised to a higher correctness floor through affine types, compiling to Typed WASM." diff --git a/affinescript/.machine_readable/anchors/ANCHOR.a2ml b/affinescript/.machine_readable/anchors/ANCHOR.a2ml deleted file mode 100644 index 66ff739..0000000 --- a/affinescript/.machine_readable/anchors/ANCHOR.a2ml +++ /dev/null @@ -1,179 +0,0 @@ -# ⚓ ANCHOR: affinescript -# Canonical authority and identity statement for the AffineScript repository. -# -# First written 2026-04-10 during the Manhattan Recovery session that realigned -# the language scope from bot-drift back to user intent. This ANCHOR is -# retroactive: the project predates the ANCHOR.a2ml convention, and a full -# .machine_readable/ suite upgrade (anchors/, CLADE.a2ml, contractiles/, -# bot_directives/, integrations/, top-level .contractile files) is deferred -# as separate work — see STATE.a2ml [deferred-upgrade]. - -id: "org.hyperpolymath.affinescript" -version: "0.2.0" -clade: "language" -status: "active-recovery" - -# ─── Thesis (canonical scope statement) ─────────────────────────────────────── -# This thesis is authoritative. Code must conform to it, not the reverse. -# See [feedback_language_scope_in_thesis.md] in the estate memory store. - -thesis: - one-sentence: > - AffineScript is JavaScript-ergonomic correctness: the developer experience - of the -script family (TypeScript, ReScript, PureScript) raised to a higher - correctness floor through a small, deliberately chosen set of advanced - types, compiling to Typed WASM. - - ergonomic-target: "closer to JavaScript than ReScript in experience" - - design-principle: > - Ergonomics first. The type system defaults must be sensible so most code - reads like modern JavaScript while correctness is enforced at compile time. - A feature earns its place only if it makes correct code easier to write. - - in-scope: - - phantom-types # free via generics - - immutable-by-default # matches ReScript/Elm/Grain/JS `const` - - row-polymorphism # PureScript/Elm pattern - - full-type-inference # HM-style; quantities at signatures, inferred in function bodies - - sound-type-system # table stakes; three bugs currently open - - effect-tracking # TRACKING only; handlers deferred — see ADR-004 - - affine-types # the headline novelty; no -script has this - - refinement-types-decidable # soft commitment, decidable fragment only, no SMT - - out-of-scope: - - linear-strict-types # subsumed by affine; bot scope-creep, being removed - - full-dependent-types # deferred to sibling Typed WASM project - - tropical-types # deferred to sibling Typed WASM project - - algebraic-effect-handlers # documented extension point, not current feature - - unbounded-refinement-types # decidability boundary; SMT not required - - units-of-measure # Typed WASM project - - shape-indexed-vectors # Typed WASM project - - exception-model: - # Option A+: exceptions are the one degenerate algebraic-effect case that - # works cleanly with affine types because resume is never called. - - "try/catch/finally grammar already present in parser.mly:542-587" - - "wiring through interp + codegen is Phase 1.5 work" - - "richer effect handlers are a documented extension point, see ADR-004" - -# ─── Authoritative documents ────────────────────────────────────────────────── - -authorities: - scope-statement: - - ".machine_readable/descriptiles/META.a2ml (ADRs)" - - "~/Desktop/Frontier_Programming_Practices_AffineScript/AI.a2ml (v2.0)" - - human-guide: - - "~/Desktop/Frontier_Programming_Practices_AffineScript/Human_Programming_Guide.adoc (v2.0)" - - architecture-decisions: - - "ADR-001: Split-Γ canonical (META.a2ml)" - - "ADR-002: Scaled Let rule (META.a2ml)" - - "ADR-003: L-to-R evaluation order (META.a2ml)" - # ADR-004: algebraic effect handlers extension point — pending - # ADR-005: language scope statement (narrow thesis) — pending - # ADR-006: exceptions as degenerate effect handler — pending - - "ADR-007: Surface syntax for quantity annotations — @linear/@erased/@unrestricted primary + :1/:0/:ω sugar (META.a2ml — ACCEPTED 2026-04-10)" - -# ─── Relationships ──────────────────────────────────────────────────────────── - -parents: - - id: "org.hyperpolymath.nextgen-languages" - relationship: "submodule-of-monorepo" - -siblings: - - id: "org.hyperpolymath.typed-wasm" - relationship: "compile-target-primary" - note: > - Typed WASM is AffineScript's primary compile target. It is designed as a - standalone project that can serve other languages beyond AffineScript. - Dependent types, tropical types, units of measure, and shape-indexed - vectors all live there, not here. - - - id: "org.hyperpolymath.julia" - relationship: "compile-target-secondary" - note: "Julia backend for scientific computing use cases." - -# ─── Realignment history ────────────────────────────────────────────────────── - -realignment-history: - - date: "2026-04-12" - event: "suite-upgrade" - session-type: "machine-readable-reorganisation" - trigger: > - Deferred .machine_readable/ suite upgrade executed as a separate session - per STATE.a2ml [deferred-upgrade]. No .ml code changes. - - summary: > - Brought .machine_readable/ up to the canonical standards repo shape. - Migrated root contractiles/ → .machine_readable/contractiles/ (git mv, - history preserved). Added: CLADE.a2ml, contractiles/adjust/Adjustfile.a2ml, - contractiles/intend/Intentfile.a2ml, bot_directives/ (README + coverage - + debt + methodology), integrations/ (verisimdb + vexometer), - ADJUST.contractile, INTENT.contractile, MUST.contractile, TRUST.contractile. - STATE.a2ml [deferred-upgrade] marked done. - - changed: - - ".machine_readable/ now matches canonical standards shape" - - "root contractiles/ migrated to .machine_readable/contractiles/" - - "CLADE.a2ml added (clade=language)" - - "bot_directives/ + integrations/ added" - - "top-level .contractile instance files added" - - "ANCHOR notes section updated" - - references: - - "standards/.machine_readable/ (canonical reference shape)" - - "STATE.a2ml [deferred-upgrade] (now marked done)" - - - date: "2026-04-10" - event: "manhattan-recovery" - session-type: "scope-recovery" - trigger: > - User acknowledged that bot scope creep had silently added linear types, - dependent types, and algebraic effect handler ambitions that were never - requested. The cascade of fixes each caused new problems, and the codebase - had drifted from being a conceptual mirror of the author's intent. - - summary: > - Scope narrowed from bot-drifted 12+ features back to the original 8-item - thesis. Three ADRs adopted. Four soundness bugs identified and logged. - Two estate-wide feedback memories saved. Desktop Frontier guides rewritten - from defensive v1.0 drafts to positive v2.0 scope statements. Option A+ - chosen for effects (tracking + exceptions via existing try/catch grammar + - documented extension point for future handlers). Inference form (a) chosen - (quantities at signatures, inferred in bodies). Simplest module system - chosen (one-file-per-module, flat namespace, no functors). - - changed: - - "thesis (narrowed and made authoritative)" - - "scope (8 features, not 12+)" - - "soundness posture (BUG-001 through BUG-004 logged)" - - "effect-handler posture (Option A+, exceptions via existing grammar)" - - "authority structure (this ANCHOR created, thesis lives here and in META.a2ml)" - - "documentation (Desktop guides v2.0 replacing v1.0)" - - references: - - "META.a2ml [adr] ADR-001 split-gamma" - - "META.a2ml [adr] ADR-002 scaled-let" - - "META.a2ml [adr] ADR-003 lr-eval" - - "STATE.a2ml [open-bug] BUG-001 omega-let-smuggles-linear" - - "STATE.a2ml [open-bug] BUG-002 zero-let-erasure" - - "STATE.a2ml [open-bug] BUG-003 eval-list-right-to-left" - - "STATE.a2ml [open-bug] BUG-004 lambda-captures-untracked" - - "~/.claude/projects/-var-mnt-eclipse-repos/memory/feedback_bot_scope_creep_free_variant.md" - - "~/.claude/projects/-var-mnt-eclipse-repos/memory/feedback_language_scope_in_thesis.md" - - "~/.claude/projects/-var-mnt-eclipse-repos/memory/feedback_completion_vs_behavioural_coverage.md" - - "~/Desktop/Frontier_Programming_Practices_AffineScript/AI.a2ml (v2.0)" - - "~/Desktop/Frontier_Programming_Practices_AffineScript/Human_Programming_Guide.adoc (v2.0)" - -# ─── Notes on retroactive status ────────────────────────────────────────────── - -notes: > - This ANCHOR was first written 2026-04-10 (manhattan-recovery session). - The full .machine_readable/ suite upgrade was completed 2026-04-12 — see - realignment-history entry "suite-upgrade". The .machine_readable/ directory - now matches the canonical standards repo shape. The minimum captured in the - first ANCHOR (identity, thesis, authorities, relationships, manhattan-recovery) - is preserved; the suite-upgrade adds the contractile instance files, - CLADE.a2ml, bot_directives/, and integrations/. diff --git a/affinescript/.machine_readable/bot_directives/README.adoc b/affinescript/.machine_readable/bot_directives/README.adoc deleted file mode 100644 index 625908e..0000000 --- a/affinescript/.machine_readable/bot_directives/README.adoc +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript Agent Instructions -:toc: preamble - -Methodology-aware configuration for AI agents working in this repository. -Read at session start by any AI agent (Claude, Gemini, Copilot, etc.). - -== Critical: read before touching code - -1. **ANCHOR.a2ml is authoritative for scope.** The thesis is fixed. New features require a new ADR, not a code change. Bot scope creep has been a recurring problem (see manhattan-recovery 2026-04-10). -2. **No `.ml` edits without reading ANCHOR.a2ml + META.a2ml first.** The type system has interdependencies that are not obvious from reading individual files. -3. **dune build must pass** before and after every change. Run it. -4. **Thesis is the authority; code must conform.** If code contradicts thesis, code is wrong. - -== Files - -[cols="1,3"] -|=== -| File | Purpose - -| `methodology.a2ml` -| Default mode, ring ceiling, priority weights, wave cap - -| `coverage.a2ml` -| Session coverage tracking — what was visited, what was skipped - -| `debt.a2ml` -| Meander debt — things found but not fixed, carried between sessions -|=== - -== Open bugs (from ANCHOR.a2ml) - -BUG-001 through BUG-004 are tracked in STATE.a2ml. Check before starting any type-system work — one of these may already cover the issue you found. - -== Faces architecture - -AffineScript supports multiple surface-syntax dialects (canonical, python-face, js-face, pseudocode-face). New faces follow the pattern in `lib/face.ml`. Do NOT add face-specific logic directly to the parser or type checker. diff --git a/affinescript/.machine_readable/bot_directives/coverage.a2ml b/affinescript/.machine_readable/bot_directives/coverage.a2ml deleted file mode 100644 index 5bc5042..0000000 --- a/affinescript/.machine_readable/bot_directives/coverage.a2ml +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# coverage.a2ml — Session coverage tracking for AffineScript -# Updated at the end of each AI agent session. - -[metadata] -version = "1.0.0" -last-updated = "2026-04-12" - -[coverage] -total-components = 39 # compiler modules (from STATE.a2ml [stats]) -visited-components = 0 -coverage-percent = 0 - -# ============================================================================ -# VISITED COMPONENTS -# ============================================================================ -# Component → session date + what was done - -[coverage.visited] -# Suite upgrade session (2026-04-12) — .machine_readable/ only, no .ml changes - -# [coverage.visited.typecheck] -# date = "2026-04-11" -# ring = 1 -# fixes = 3 -# note = "Traits wired, linear arrows enforced, borrow checker gated" - -# [coverage.visited.lsp-server] -# date = "2026-04-11" -# ring = 1 -# fixes = 4 -# note = "Phases A-D complete: hover, goto-def, completion, JSON-RPC server" diff --git a/affinescript/.machine_readable/bot_directives/debt.a2ml b/affinescript/.machine_readable/bot_directives/debt.a2ml deleted file mode 100644 index 89dfb75..0000000 --- a/affinescript/.machine_readable/bot_directives/debt.a2ml +++ /dev/null @@ -1,73 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# debt.a2ml — Meander debt for AffineScript -# Things found but not fixed. Carried between sessions. - -[metadata] -version = "1.0.0" -last-updated = "2026-04-12" - -# ============================================================================ -# SHOULD — fix next wave -# ============================================================================ - -[[debt.should]] -component = "lib/quantity.ml" -issue = "BUG-001: ω-let smuggles linear values — Scaled Let rule not fully enforced in all let-binding positions. See STATE.a2ml open-bugs." -effort = "medium" -impact = "high" -discovered = "2026-04-10" - -[[debt.should]] -component = "lib/quantity.ml" -issue = "BUG-002: zero-let erasure — erased bindings not fully pruned in codegen. See STATE.a2ml open-bugs." -effort = "medium" -impact = "medium" -discovered = "2026-04-10" - -[[debt.should]] -component = "lib/interp.ml" -issue = "BUG-003: eval list right-to-left — should be left-to-right per ADR-003. See STATE.a2ml open-bugs." -effort = "easy" -impact = "medium" -discovered = "2026-04-10" - -[[debt.should]] -component = "lib/borrow.ml" -issue = "BUG-004: lambda capture tracking unimplemented — requires type info propagation. See STATE.a2ml open-bugs." -effort = "hard" -impact = "high" -discovered = "2026-04-10" - -[[debt.should]] -component = "lib/lexer.mll" -issue = "ZERO/ONE tokens for quantity literals not emitted — blocks :1/:0 surface syntax. Grammar has the nodes; lexer does not emit them." -effort = "easy" -impact = "medium" -discovered = "2026-04-11" - -# ============================================================================ -# COULD — fix eventually -# ============================================================================ - -[[debt.could]] -component = "lib/typecheck.ml" -issue = "Associated type substitution in method bodies and where-clause supertrait enforcement not yet implemented in traits. Coherence checking absent." -effort = "hard" -impact = "medium" -discovered = "2026-04-11" - -[[debt.could]] -component = "lib/codegen.ml (wasm-gc)" -issue = "WasmGC ExprHandle/ExprResume reject with UnsupportedFeature — full WASM dispatch needs EH proposal or CPS transform." -effort = "hard" -impact = "low" -discovered = "2026-04-11" - -[[debt.could]] -component = "error spans" -issue = "Face-aware error spans refer to transformed canonical positions, not original source positions. Follow-up from faces architecture (ADR-010)." -effort = "medium" -impact = "medium" -discovered = "2026-04-11" diff --git a/affinescript/.machine_readable/bot_directives/methodology.a2ml b/affinescript/.machine_readable/bot_directives/methodology.a2ml deleted file mode 100644 index a027de3..0000000 --- a/affinescript/.machine_readable/bot_directives/methodology.a2ml +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# methodology.a2ml — AI agent methodology for AffineScript -# Declares how agents should approach work here. - -[metadata] -version = "1.0.0" -last-updated = "2026-04-12" - -[methodology] -default-mode = "convergent" -ring-ceiling = 1 # Hard ceiling — do not expand beyond immediate compiler modules -wave-cap = 2 # Max waves before requiring "keep going" -spike-required = true # Sessions must ship code (or verified .machine_readable/ changes), not just designs - -[methodology.priority-weights] -must = 3 -should = 2 -could = 1 - -[methodology.invariants] -# These MUST be checked before and after any session -always-check = [ - "dune build passes", - "dune runtest passes (all E2E tests green)", - "no new assert_total / believe_me / Obj.magic introduced", - "ANCHOR.a2ml thesis not violated by any code change", - "BUG-001 through BUG-004 not made worse" -] - -# These are forbidden regardless of scope -never-do = [ - "rewrite type checker naively without reading ANCHOR.a2ml first", - "add a language feature not in the ANCHOR.a2ml thesis without a new ADR", - "add linear types as a separate concept (subsumed by affine — this is bot scope creep)", - "add dependent types (Typed WASM project — out of scope)", - "remove the faces architecture", - "break the IDApTIK integration (ASS + IDApTIK use affinescript Wasm output)" -] - -[methodology.on-enter] -# Sequence for beginning any session -sequence = [ - "read .machine_readable/anchors/ANCHOR.a2ml", - "check dune build passes", - "read debt.a2ml for outstanding items", - "read coverage.a2ml for what was visited last time", - "decide ring-0 targets (open bugs first, then debt.should items)" -] - -[methodology.on-exit] -# Sequence for ending any session -sequence = [ - "run dune build + dune runtest", - "update debt.a2ml (add newly found items, remove fixed ones)", - "update coverage.a2ml (mark visited components)", - "commit: chore(affinescript): session — " -] diff --git a/affinescript/.machine_readable/contractiles/Adjustfile.a2ml b/affinescript/.machine_readable/contractiles/Adjustfile.a2ml deleted file mode 100644 index e99ce6d..0000000 --- a/affinescript/.machine_readable/contractiles/Adjustfile.a2ml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Adjustfile (A2ML) - Converted from ADJUST.contractile - -@abstract: -Drift tolerances and corrective actions. Converted from Scheme-based -ADJUST.contractile format to A2ML format. -@end - -## Drift Tolerance - -### placeholder-drift -- description: Template placeholder values should be replaced -- tolerance: 0 placeholder markers -- corrective: Replace all {{PLACEHOLDER}} values -- severity: advisory diff --git a/affinescript/.machine_readable/contractiles/Intentfile.a2ml b/affinescript/.machine_readable/contractiles/Intentfile.a2ml deleted file mode 100644 index 375edf4..0000000 --- a/affinescript/.machine_readable/contractiles/Intentfile.a2ml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Intentfile (A2ML) - Converted from INTENT.contractile for affinescript - -@abstract: -Purpose and scope for affinescript. This file was converted from Scheme-based -INTENT.contractile format to A2ML format. -@end - -## Purpose - -AffineScript is a programming language with affine types and JavaScript - -## Anti-Purpose - - - -## If In Doubt - -Sensitive areas from original contractile: diff --git a/affinescript/.machine_readable/contractiles/Mustfile.a2ml b/affinescript/.machine_readable/contractiles/Mustfile.a2ml deleted file mode 100644 index 5de9e04..0000000 --- a/affinescript/.machine_readable/contractiles/Mustfile.a2ml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile (A2ML) - Converted from MUST.contractile - -@abstract: -Physical state contract for this repository. Converted from Scheme-based -MUST.contractile format to A2ML format. -@end - -## File Presence - -### license-present -- description: LICENSE file must exist -- run: test -f LICENSE -- severity: critical diff --git a/affinescript/.machine_readable/contractiles/README.adoc b/affinescript/.machine_readable/contractiles/README.adoc deleted file mode 100644 index 9f94acc..0000000 --- a/affinescript/.machine_readable/contractiles/README.adoc +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Contractiles Template Set -:toc: -:sectnums: - -This directory contains the generalized contractiles templates. Copy the `contractiles/` directory into a new repo to establish a consistent operational, validation, trust, recovery, and intent framework. - -== Fill-In Instructions - -1. Update the Mustfile to reflect your real invariants (paths, schema versions, ports). -2. Replace Trustfile.hs placeholders with your actual key paths and verification commands. -3. Adjust Dustfile handlers to match your rollback and recovery tooling. -4. Update Intentfile to mirror the roadmap you want the system to evolve toward. - -== Contents - -* `must/Mustfile` - required invariants and validations. -* `trust/Trustfile.hs` - cryptographic verification steps. -* `dust/Dustfile` - rollback and recovery semantics. -* `lust/Intentfile` - future intent and roadmap direction. diff --git a/affinescript/.machine_readable/contractiles/Trustfile.a2ml b/affinescript/.machine_readable/contractiles/Trustfile.a2ml deleted file mode 100644 index 0184839..0000000 --- a/affinescript/.machine_readable/contractiles/Trustfile.a2ml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Trustfile (A2ML) - Converted from TRUST.contractile - -@abstract: -Trust boundaries and integrity invariants. Converted from Scheme-based -TRUST.contractile format to A2ML format. -@end - -## Integrity Invariants - -### no-secrets-committed -- description: No credential files in repo -- run: test ! -f .env && test ! -f credentials.json -- severity: critical diff --git a/affinescript/.machine_readable/contractiles/adjust/Adjustfile.a2ml b/affinescript/.machine_readable/contractiles/adjust/Adjustfile.a2ml deleted file mode 100644 index 669cb6e..0000000 --- a/affinescript/.machine_readable/contractiles/adjust/Adjustfile.a2ml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Adjustfile.a2ml — Accessibility invariants for AffineScript -# -# AffineScript is a compiler + LSP server, not a web app. Its accessibility -# surface is: CLI flags/errors, LSP diagnostics, documentation. -# WCAG visual rules do not apply directly, but all user-facing output must -# be accessible to screen readers and assistive tools. - -[adjust] -version = "1.0.0" -standard = "CLI-accessible (subset of WCAG 2.2 AA)" -repo = "nextgen-languages/affinescript" - -[adjust.cli] -# Error messages must be plain text, not colour-only -colour-free-errors = true -# All errors must include: file, line, col, short description -error-format = "file:line:col: [E####] short description" -# --help must always be available on all subcommands -help-flag = true -# Exit codes must be documented and consistent -exit-codes-documented = true -# No spinner/animation that doesn't degrade gracefully in non-TTY -tty-safe-output = true - -[adjust.lsp] -# LSP diagnostics must include range (not just offset) -diagnostic-range = true -# Hover responses must be plain Markdown, no HTML injection -hover-format = "markdown-plain" -# Completion items must have a kind field for screen reader discrimination -completion-kind = true - -[adjust.errors] -# Errors must identify the construct in plain language (not jargon) -plain-language = true -# Linear/affine type errors must name the variable and say WHY it is consumed -affine-error-detail = true -# No error message whose only information is a code like E0501 without text -code-plus-text = true - -[adjust.documentation] -# All public docs must have alt text on diagrams -diagram-alt-text = true -# Code examples must have comments explaining non-obvious steps -commented-examples = true -# Internationalisation: all user-facing strings must be in English (single lang for now) -# i18n expansion is tracked in ROADMAP.adoc -i18n-target = "en" diff --git a/affinescript/.machine_readable/contractiles/dust/Dustfile b/affinescript/.machine_readable/contractiles/dust/Dustfile deleted file mode 100644 index 314903c..0000000 --- a/affinescript/.machine_readable/contractiles/dust/Dustfile +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Dustfile template - recovery and rollback semantics - -version: 1 - -recovery: - logs: - - name: decision-log - path: logs/decisions.json - reversible: true - handler: "log-replay --reverse logs/decisions.json" - - policy: - - name: policy-rollback - path: policy/policy.ncl - rollback: "git checkout HEAD~1 -- policy/policy.ncl" - notes: "Rollback policy to the previous known-good revision." - - gateway: - - name: bad-deployment - event: "deploy.failure" - undo: "kubectl rollout undo deployment/gateway" - notes: "Undo a failed deployment while preserving audit logs." - - dust-events: - - name: decision-log-to-dust - source: logs/decisions.json - transform: "dustify --input logs/decisions.json --output logs/dust-events.json" - notes: "Map gateway decision logs into reversible dust events." diff --git a/affinescript/.machine_readable/contractiles/intend/Intentfile.a2ml b/affinescript/.machine_readable/contractiles/intend/Intentfile.a2ml deleted file mode 100644 index 43bb4e2..0000000 --- a/affinescript/.machine_readable/contractiles/intend/Intentfile.a2ml +++ /dev/null @@ -1,54 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Intentfile.a2ml — Purpose and scope for AffineScript -# Authoritative anti-scope-creep document. -# See: ANCHOR.a2ml thesis + META.a2ml ADRs for full scope statement. - -[intent] -version = "1.0.0" -repo = "nextgen-languages/affinescript" - -[intent.purpose] -is = """ -AffineScript is a programming language with affine types and JavaScript ergonomics, -compiling to Typed WASM. It targets the -script developer community (TypeScript, -ReScript, PureScript users) and offers a higher correctness floor through a small, -deliberately chosen set of advanced types. The feature set is fixed by the thesis -in ANCHOR.a2ml — new features require a thesis change, not a code change. -""" - -is-not = """ -AffineScript is NOT: -- A language with full dependent types (Typed WASM project) -- A language with tropical types (Typed WASM project) -- A language with units of measure (Typed WASM project) -- A language with algebraic effect handlers (extension point only — ADR-004) -- A language with linear types (subsumed by affine; was bot scope creep) -- A general-purpose scripting language or replacement for JavaScript -- A language targeting native machine code -""" - -[intent.decisions] -# These ADRs are load-bearing. Do NOT reverse them without a new ADR. -# Full text in META.a2ml. -adr-001 = "Split-Γ typing context is canonical" -adr-002 = "Scaled Let rule governs affine/linear/erased binding" -adr-003 = "Left-to-right evaluation order" -adr-007 = "Surface syntax: @linear/@erased/@unrestricted primary, :1/:0/:ω sugar" -adr-010 = "Faces architecture for Python/JS/Pseudocode surface dialects" - -[intent.roadmap] -next = [ - "BUG-001: ω-let smuggles linear values (ANCHOR.a2ml)", - "BUG-002: zero-let erasure (ANCHOR.a2ml)", - "BUG-003: eval list right-to-left (ANCHOR.a2ml)", - "BUG-004: lambda capture tracking (ANCHOR.a2ml)", - "LSP Phase E: full JSON-RPC server testing", - "Typed WASM integration: surface syntax → typed-wasm IR", -] -deferred = [ - "algebraic effect handlers beyond try/catch (ADR-004 extension point)", - "dependent types (Typed WASM project)", - "ZERO/ONE tokens for quantity literals in lexer", -] diff --git a/affinescript/.machine_readable/contractiles/must/Mustfile b/affinescript/.machine_readable/contractiles/must/Mustfile deleted file mode 100644 index dc7b3be..0000000 --- a/affinescript/.machine_readable/contractiles/must/Mustfile +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile - declarative state contract (template) -# See: https://github.com/hyperpolymath/mustfile - -version: 1 - -metadata: - name: project-state-contract - spec: v0.0.1 - description: "Invariant checks for config, policy, gateway, logs, and schema." - -parameters: - gateway_port: "8080" - schema_version: "v0.0.1" - -checks: - - name: config-valid - description: "config/service.yaml must be valid." - run: "yq -e '.' config/service.yaml >/dev/null" - - - name: policy-compiles - description: "policy/policy.ncl must compile." - run: "nickel check policy/policy.ncl" - - - name: gateway-exposes-port - description: "Service must expose the configured port." - run: "bash -uc 'ss -lnt | rg \":${GATEWAY_PORT:-8080}\"'" - - - name: logs-are-json - description: "Logs must be JSON." - run: "bash -uc 'rg --files -g \"*.json\" logs | xargs -r jq -e .'" - - - name: schema-version-matches - description: "Schema must match version spec." - run: "bash -uc 'rg -n \"${SCHEMA_VERSION:-v0.0.1}\" schema'" diff --git a/affinescript/.machine_readable/integrations/verisimdb.a2ml b/affinescript/.machine_readable/integrations/verisimdb.a2ml deleted file mode 100644 index c854867..0000000 --- a/affinescript/.machine_readable/integrations/verisimdb.a2ml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# VeriSimDB integration for AffineScript - -[integration] -name = "verisimdb" -type = "data-feed" -repository = "https://github.com/hyperpolymath/nextgen-databases" -data-store = "verisimdb-data" - -[feed-config] -emit-scan-results = true -emit-build-metrics = true -format = "hexad" -destination = "verisimdb-data/feeds/affinescript/" - -[proof-obligations] -# AffineScript has no standalone I2 proofs yet (type soundness is in TypeLL + Ephapax). -# When AffineScript grows its own Idris2 proof, register it here. -# See: PROOF-NEEDS.md in repo root -standalone-proofs = [] -related-proofs = [ - "ephapax: linear type preservation (uses similar Split-Γ)", - "typell: session type duality (informs ADR-001 split-Γ design)" -] - -[notes] -status = "integration stub — emit when CI is instrumented" diff --git a/affinescript/.machine_readable/integrations/vexometer.a2ml b/affinescript/.machine_readable/integrations/vexometer.a2ml deleted file mode 100644 index 9454611..0000000 --- a/affinescript/.machine_readable/integrations/vexometer.a2ml +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Vexometer integration for AffineScript -# Measures irritation surface of the compiler, LSP, and CLI. - -[integration] -name = "vexometer" -type = "friction-measurement" -repository = "https://github.com/hyperpolymath/vexometer" - -[measurement-config] -dimensions = 10 -emit-isa-reports = true - -[hooks] -cli-tools = "measure-on-error" # affine error messages → friction score -lsp-server = "measure-on-interaction" # hover/completion/goto latency + quality -build-failures = "measure-on-failure" # dune build failures in consumer projects - -[friction-areas] -# Known high-friction areas to watch -linear-error-messages = "users frequently confused by affine violation messages — prime vexometer target" -quantity-annotation-syntax = "ADR-007 hybrid syntax (@linear AND :1) causes discoverability friction" -face-detection = "auto-detected faces (--face flag) sometimes surprising to users" diff --git a/affinescript/.well-known/dnt-policy.txt b/affinescript/.well-known/dnt-policy.txt deleted file mode 100644 index e58439d..0000000 --- a/affinescript/.well-known/dnt-policy.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Do Not Track Policy for affinescript.dev -# https://www.eff.org/dnt-policy - -This site respects Do Not Track (DNT) browser settings. - -When DNT is enabled: -- No analytics tracking (even with consent) -- No personalization cookies -- No third-party tracking -- Essential cookies only - -To enable DNT: -- Chrome: Settings > Privacy > Send "Do Not Track" -- Firefox: Preferences > Privacy > Tell sites not to track -- Safari: Preferences > Privacy > Ask websites not to track - -Contact: privacy@affinescript.dev -Last Updated: 2026-01-31 diff --git a/affinescript/.well-known/groove/manifest.json b/affinescript/.well-known/groove/manifest.json deleted file mode 100644 index bb1c074..0000000 --- a/affinescript/.well-known/groove/manifest.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "applicability": [ - "individual", - "team" - ], - "capabilities": { - "primary": { - "description": "TODO: describe this service's primary capability", - "panel_compatible": true, - "protocol": "cli", - "requires_auth": false, - "type": "custom" - } - }, - "consumes": [], - "groove_version": "1", - "health": null, - "invoke_patterns": { - "ci": "affinescript --ci --format json", - "local": "affinescript --interactive", - "mcp": "boj cartridge_invoke affinescript" - }, - "mode": "passive", - "service_id": "affinescript", - "service_version": "0.1.0" -} \ No newline at end of file diff --git a/affinescript/.well-known/keybase.txt b/affinescript/.well-known/keybase.txt deleted file mode 100644 index 5dac7c7..0000000 --- a/affinescript/.well-known/keybase.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Keybase verification for affinescript.dev -# https://keybase.io/hyperpolymath - -# This file will contain the Keybase verification proof once configured. -# To verify this domain with Keybase: -# 1. Run: keybase prove dns affinescript.dev -# 2. Replace this file with the generated proof - -# Contact: jonathan.jewell@open.ac.uk -# Keybase profile: https://keybase.io/hyperpolymath (coming soon) - -# Placeholder - awaiting Keybase setup diff --git a/affinescript/.well-known/security.txt b/affinescript/.well-known/security.txt deleted file mode 100644 index 21b3a7e..0000000 --- a/affinescript/.well-known/security.txt +++ /dev/null @@ -1,53 +0,0 @@ -# Security Contact Information for affinescript.dev -# RFC 9116 Compliant: https://www.rfc-editor.org/rfc/rfc9116.html -# Consent-Aware HTTP Compatible - -# === Primary Contact === -Contact: mailto:security@affinescript.dev -Contact: https://github.com/hyperpolymath/affinescript/security/advisories/new - -# === Security Policy === -Policy: https://github.com/hyperpolymath/affinescript/security/policy -Policy: https://affinescript.dev/.well-known/security-policy.html - -# === Encryption (PGP) === -Encryption: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk -Encryption: https://github.com/hyperpolymath.gpg -Encryption: https://affinescript.dev/.well-known/pgp-key.asc - -# === Acknowledgments === -Acknowledgments: https://github.com/hyperpolymath/affinescript#security-acknowledgments -Acknowledgments: https://affinescript.dev/security/acknowledgments - -# === Hiring (Security Team) === -Hiring: https://github.com/hyperpolymath/affinescript/blob/main/CONTRIBUTING.md - -# === Preferred Languages === -Preferred-Languages: en - -# === Consent-Aware HTTP Endpoints === -# User Consent Management (GDPR/Privacy Compliance) -# Consent-Endpoint: https://affinescript.dev/api/consent -# Consent-Policy: https://affinescript.dev/privacy#consent-management -# Consent-Categories: essential, functional, analytics, marketing, personalization - -# === Canonical URL === -Canonical: https://affinescript.dev/.well-known/security.txt - -# === CSAF (Common Security Advisory Framework) === -# CSAF: https://affinescript.dev/.well-known/csaf/provider-metadata.json - -# === Expiration (1 year from creation) === -Expires: 2027-01-31T16:43:19Z - -# === Additional Information === -# This site implements: -# - Consent-Aware HTTP (cookie-based consent verification) -# - HTTP Capability Gateway (fine-grained access control) -# - HSTS with preload (max-age=31536000) -# - TLS 1.3 minimum -# - CAA records (Let's Encrypt, DigiCert) -# - SPF, DMARC for email security -# -# Report security issues to: security@affinescript.dev -# For privacy/consent issues: privacy@affinescript.dev diff --git a/affinescript/.well-known/sellers.json b/affinescript/.well-known/sellers.json deleted file mode 100644 index 63b9331..0000000 --- a/affinescript/.well-known/sellers.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "contact_email": "info@affinescript.dev", - "contact_address": "United Kingdom", - "version": "1.0", - "identifiers": [], - "sellers": [] -} diff --git a/affinescript/.well-known/trust.txt b/affinescript/.well-known/trust.txt deleted file mode 100644 index 65194d7..0000000 --- a/affinescript/.well-known/trust.txt +++ /dev/null @@ -1,57 +0,0 @@ -# Trust and Transparency Information for affinescript.dev -# https://github.com/nightwatchcybersecurity/trustandtransparency.org - -# === Organization Information === -Organization: hyperpolymath -Legal Name: Jonathan D.A. Jewell -Country: United Kingdom -Contact: info@affinescript.dev -Website: https://affinescript.dev - -# === Responsible Disclosure === -Security Contact: security@affinescript.dev -Security Policy: https://github.com/hyperpolymath/affinescript/security/policy -Bug Bounty: Not currently available -Encryption: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk - -# === Privacy === -Privacy Policy: https://affinescript.dev/privacy -Data Controller: Jonathan D.A. Jewell -Data Protection Officer: privacy@affinescript.dev -GDPR Compliance: Yes -Consent Management: Consent-Aware HTTP (cookie-based) - -# === Transparency === -Open Source: Yes -License: MPL-2.0 (https://palimpsest.spdx.dev) -Source Code: https://github.com/hyperpolymath/affinescript -Issue Tracker: https://github.com/hyperpolymath/affinescript/issues -Changelog: https://github.com/hyperpolymath/affinescript/releases - -# === Infrastructure === -Hosting: GitHub Pages -CDN: Cloudflare -TLS Provider: Let's Encrypt, DigiCert -DNS: Cloudflare -Analytics: Self-hosted (consent required) - -# === Third-Party Services === -# None currently (all self-hosted or GitHub-hosted) - -# === Compliance & Standards === -WCAG Level: AA (target) -RFC 9116: Compliant (security.txt) -HSTS Preload: Yes -CAA Records: Yes (flag 128) -SPF/DMARC: Yes - -# === Contact Methods === -Email: info@affinescript.dev -GitHub: https://github.com/hyperpolymath/affinescript -Security Email: security@affinescript.dev -PGP Key: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk - -# === Verification === -Last Updated: 2026-01-31 -Verified By: Jonathan D.A. Jewell -Canonical URL: https://affinescript.dev/.well-known/trust.txt diff --git a/affinescript/ABI-FFI-README.adoc b/affinescript/ABI-FFI-README.adoc deleted file mode 100644 index 9338812..0000000 --- a/affinescript/ABI-FFI-README.adoc +++ /dev/null @@ -1,409 +0,0 @@ -\{\{~ Aditionally delete this line and fill out the template below ~}} - -== Proof Burrower ABI/FFI Documentation - -=== Overview - -This library follows the *Hyperpolymath RSR Standard* for ABI and FFI -design: - -* *ABI (Application Binary Interface)* defined in *Idris2* with formal -proofs -* *FFI (Foreign Function Interface)* implemented in *Zig* for C -compatibility -* *Generated C headers* bridge Idris2 ABI to Zig FFI -* *Any language* can call through standard C ABI - -=== Architecture - -.... -┌─────────────────────────────────────────────┐ -│ ABI Definitions (Idris2) │ -│ src/abi/ │ -│ - Types.idr (Type definitions) │ -│ - Layout.idr (Memory layout proofs) │ -│ - Foreign.idr (FFI declarations) │ -└─────────────────┬───────────────────────────┘ - │ - │ generates (at compile time) - ▼ -┌─────────────────────────────────────────────┐ -│ C Headers (auto-generated) │ -│ generated/abi/{{project}}.h │ -└─────────────────┬───────────────────────────┘ - │ - │ imported by - ▼ -┌─────────────────────────────────────────────┐ -│ FFI Implementation (Zig) │ -│ ffi/zig/src/main.zig │ -│ - Implements C-compatible functions │ -│ - Zero-cost abstractions │ -│ - Memory-safe by default │ -└─────────────────┬───────────────────────────┘ - │ - │ compiled to lib{{project}}.so/.a - ▼ -┌─────────────────────────────────────────────┐ -│ Any Language via C ABI │ -│ - Rust, ReScript, Julia, Python, etc. │ -└─────────────────────────────────────────────┘ -.... - -=== Directory Structure - -.... -{{project}}/ -├── src/ -│ ├── abi/ # ABI definitions (Idris2) -│ │ ├── Types.idr # Core type definitions with proofs -│ │ ├── Layout.idr # Memory layout verification -│ │ └── Foreign.idr # FFI function declarations -│ └── lib/ # Core library (any language) -│ -├── ffi/ -│ └── zig/ # FFI implementation (Zig) -│ ├── build.zig # Build configuration -│ ├── build.zig.zon # Dependencies -│ ├── src/ -│ │ └── main.zig # C-compatible FFI implementation -│ ├── test/ -│ │ └── integration_test.zig -│ └── include/ -│ └── {{project}}.h # C header (optional, can be generated) -│ -├── generated/ # Auto-generated files -│ └── abi/ -│ └── {{project}}.h # Generated from Idris2 ABI -│ -└── bindings/ # Language-specific wrappers (optional) - ├── rust/ - ├── rescript/ - └── julia/ -.... - -=== Why Idris2 for ABI? - -==== 1. *Formal Verification* - -Idris2’s dependent types allow proving properties about the ABI at -compile-time: - -[source,idris] ----- --- Prove struct size is correct -public export -exampleStructSize : HasSize ExampleStruct 16 - --- Prove field alignment is correct -public export -fieldAligned : Divides 8 (offsetOf ExampleStruct.field) - --- Prove ABI is platform-compatible -public export -abiCompatible : Compatible (ABI 1) (ABI 2) ----- - -==== 2. *Type Safety* - -Encode invariants that C/Zig cannot express: - -[source,idris] ----- --- Non-null pointer guaranteed at type level -data Handle : Type where - MkHandle : (ptr : Bits64) -> {auto 0 nonNull : So (ptr /= 0)} -> Handle - --- Array with length proof -data Buffer : (n : Nat) -> Type where - MkBuffer : Vect n Byte -> Buffer n ----- - -==== 3. *Platform Abstraction* - -Platform-specific types with compile-time selection: - -[source,idris] ----- -CInt : Platform -> Type -CInt Linux = Bits32 -CInt Windows = Bits32 - -CSize : Platform -> Type -CSize Linux = Bits64 -CSize Windows = Bits64 ----- - -==== 4. *Safe Evolution* - -Prove that new ABI versions are backward-compatible: - -[source,idris] ----- --- Compiler enforces compatibility -abiUpgrade : ABI 1 -> ABI 2 -abiUpgrade old = MkABI2 { - -- Must preserve all v1 fields - v1_compat = old, - -- Can add new fields - new_features = defaults -} ----- - -=== Why Zig for FFI? - -==== 1. *C ABI Compatibility* - -Zig exports C-compatible functions naturally: - -[source,zig] ----- -export fn library_function(param: i32) i32 { - return param * 2; -} ----- - -==== 2. *Memory Safety* - -Compile-time safety without runtime overhead: - -[source,zig] ----- -// Null check enforced at compile time -const handle = init() orelse return error.InitFailed; -defer free(handle); ----- - -==== 3. *Cross-Compilation* - -Built-in cross-compilation to any platform: - -[source,bash] ----- -zig build -Dtarget=x86_64-linux -zig build -Dtarget=aarch64-macos -zig build -Dtarget=x86_64-windows ----- - -==== 4. *Zero Dependencies* - -No runtime, no libc required (unless explicitly needed): - -[source,zig] ----- -// Minimal binary size -pub const lib = @import("std"); -// Only includes what you use ----- - -=== Building - -==== Build FFI Library - -[source,bash] ----- -cd ffi/zig -zig build # Build debug -zig build -Doptimize=ReleaseFast # Build optimized -zig build test # Run tests ----- - -==== Generate C Header from Idris2 ABI - -[source,bash] ----- -cd src/abi -idris2 --cg c-header Types.idr -o ../../generated/abi/{{project}}.h ----- - -==== Cross-Compile - -[source,bash] ----- -cd ffi/zig - -# Linux x86_64 -zig build -Dtarget=x86_64-linux - -# macOS ARM64 -zig build -Dtarget=aarch64-macos - -# Windows x86_64 -zig build -Dtarget=x86_64-windows ----- - -=== Usage - -==== From C - -[source,c] ----- -#include "{{project}}.h" - -int main() { - void* handle = {{project}}_init(); - if (!handle) return 1; - - int result = {{project}}_process(handle, 42); - if (result != 0) { - const char* err = {{project}}_last_error(); - fprintf(stderr, "Error: %s\n", err); - } - - {{project}}_free(handle); - return 0; -} ----- - -Compile with: - -[source,bash] ----- -gcc -o example example.c -l{{project}} -L./zig-out/lib ----- - -==== From Idris2 - -[source,idris] ----- -import Proof Burrower.ABI.Foreign - -main : IO () -main = do - Just handle <- init - | Nothing => putStrLn "Failed to initialize" - - Right result <- process handle 42 - | Left err => putStrLn $ "Error: " ++ errorDescription err - - free handle - putStrLn "Success" ----- - -==== From Rust - -[source,rust] ----- -#[link(name = "{{project}}")] -extern "C" { - fn {{project}}_init() -> *mut std::ffi::c_void; - fn {{project}}_free(handle: *mut std::ffi::c_void); - fn {{project}}_process(handle: *mut std::ffi::c_void, input: u32) -> i32; -} - -fn main() { - unsafe { - let handle = {{project}}_init(); - assert!(!handle.is_null()); - - let result = {{project}}_process(handle, 42); - assert_eq!(result, 0); - - {{project}}_free(handle); - } -} ----- - -==== From Julia - -[source,julia] ----- -const lib{{project}} = "lib{{project}}" - -function init() - handle = ccall((:{{project}}_init, lib{{project}}), Ptr{Cvoid}, ()) - handle == C_NULL && error("Failed to initialize") - handle -end - -function process(handle, input) - result = ccall((:{{project}}_process, lib{{project}}), Cint, (Ptr{Cvoid}, UInt32), handle, input) - result -end - -function cleanup(handle) - ccall((:{{project}}_free, lib{{project}}), Cvoid, (Ptr{Cvoid},), handle) -end - -# Usage -handle = init() -try - result = process(handle, 42) - println("Result: $result") -finally - cleanup(handle) -end ----- - -=== Testing - -==== Unit Tests (Zig) - -[source,bash] ----- -cd ffi/zig -zig build test ----- - -==== Integration Tests - -[source,bash] ----- -cd ffi/zig -zig build test-integration ----- - -==== ABI Verification (Idris2) - -[source,idris] ----- --- Compile-time verification -%runElab verifyABI - --- Runtime checks -main : IO () -main = do - verifyLayoutsCorrect - verifyAlignmentsCorrect - putStrLn "ABI verification passed" ----- - -=== Contributing - -When modifying the ABI/FFI: - -[arabic] -. *Update ABI first* (`+src/abi/*.idr+`) -* Modify type definitions -* Update proofs -* Ensure backward compatibility -. *Generate C header* -+ -[source,bash] ----- -idris2 --cg c-header src/abi/Types.idr -o generated/abi/{{project}}.h ----- -. *Update FFI implementation* (`+ffi/zig/src/main.zig+`) -* Implement new functions -* Match ABI types exactly -. *Add tests* -* Unit tests in Zig -* Integration tests -* ABI verification tests -. *Update documentation* -* Function signatures -* Usage examples -* Migration guide (if breaking changes) - -=== License - -MPL-2.0 - -=== See Also - -* https://idris2.readthedocs.io[Idris2 Documentation] -* https://ziglang.org/documentation/master/[Zig Documentation] -* https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium -Standard Repositories] -* link:../ffi-migration-guide.md[FFI Migration Guide] -* link:../abi-migration-guide.md[ABI Migration Guide] diff --git a/affinescript/AI.a2ml b/affinescript/AI.a2ml deleted file mode 100644 index 11aa803..0000000 --- a/affinescript/AI.a2ml +++ /dev/null @@ -1,22 +0,0 @@ -# AI Assistant Instructions - -# Machine-Readable Metadata - -Project metadata and AI agent configuration files are located in `.machine_readable/`: - -- **STATE.scm** - Current project state, progress, blockers, next actions -- **ECOSYSTEM.scm** - Position in ecosystem, relationships to other projects -- **META.scm** - Architecture decisions, governance, development practices -- **PLAYBOOK.scm** - Operational runbook, procedures, alerts -- **AGENTIC.scm** - AI agent interaction patterns and constraints -- **NEUROSYM.scm** - Neurosymbolic integration configuration - -**These files are the canonical source of truth for project metadata.** - -## Workflow - -1. Read `.machine_readable/*.scm` files at session start -2. Check STATE.scm for current phase, blockers, next actions -3. Follow patterns and constraints from AGENTIC.scm -4. Update STATE.scm after completing work -5. Sync to database: `~/sync-language-tracker.sh` diff --git a/affinescript/AI.djot b/affinescript/AI.djot deleted file mode 100644 index e1efbe2..0000000 --- a/affinescript/AI.djot +++ /dev/null @@ -1,100 +0,0 @@ -# AI Assistant Specification - -## Machine-Readable Artefacts - -The `.machine_readable/` directory contains structured metadata required for automated reasoning and project introspection. - -- `STATE.scm`: Current project state, milestones, and progress. -- `META.scm`: Architecture decisions, development methodology, and rationale. -- `ECOSYSTEM.scm`: Ecosystem placement, upstream/downstream relationships, and interoperability. -- `AGENTIC.scm`: AI agent interaction patterns, affordances, and constraints. -- `NEUROSYM.scm`: Neurosymbolic integration configuration and hybrid reasoning settings. -- `PLAYBOOK.scm`: Operational runbook, workflows, and incident procedures. - -Additional artefact: - -- `AI.djot`: AI Assistant behavioural and operational instructions. - -## Repository Roots - -- Local repository root: `/var$REPOS_DIR` -- Remote mapping: `https://github.com/hyperpolymath/` -- Repository names are lowercase; dots (`.`) are replaced with hyphens (`-`). -- If a repository is missing locally, check the remote and synchronize before proceeding. - -## AI CLI Standards - -- Include `ai-cli-crash-capture/` and `contractiles/` from this template in all new repositories. -- Mirror all 6SCM files into `.machine_readable/6scm/`. -- Before generating code, inspect `/var$REPOS_DIR/proven` for the current Idris “unbreakable” library status. -- If the project depends on the unbreakable Idris library, add an *Idris Inside* badge to the repository. -- When new bindings are required, create them under `/var$REPOS_DIR/proven` using the naming pattern `idris2-zig-`. -- If new code implies additional unbreakable Idris modules, add them to `/var$REPOS_DIR/proven` and update binders across all supported languages. - -## Language Policy - -### Allowed Languages and Tools - -| Language/Tool | Use Case | Notes | -|---------------|----------|-------| -| ReScript | Application code | Type-safe, compiles to JS | -| Deno | Runtime and package management | Replaces Node/npm/bun | -| Rust | Systems, performance-critical, WASM | Preferred for CLI tools | -| Tauri 2.0+ | Mobile apps (iOS/Android) | Rust backend + web UI | -| Dioxus | Mobile apps (native UI) | Pure Rust, React-like | -| Gleam | Backend services | BEAM or JS targets | -| Bash/POSIX Shell | Automation scripts | Keep minimal | -| JavaScript | Only where ReScript cannot | MCP glue, Deno APIs | -| Nickel | Configuration | Complex config logic | -| Guile Scheme | Metadata/state files | For all `.scm` artefacts | -| Julia | Batch/data processing | Per RSR | -| OCaml | AffineScript compiler | Language-specific | -| Ada | Safety-critical systems | Where required | - -### Banned Languages and Tools - -| Banned | Replacement | -|--------|-------------| -| TypeScript | ReScript | -| Node.js | Deno | -| npm | Deno | -| Bun | Deno | -| pnpm/yarn | Deno | -| Go | Rust | -| Python | Julia / Rust / ReScript | -| Java/Kotlin | Rust / Tauri / Dioxus | -| Swift | Tauri / Dioxus | -| React Native | Tauri / Dioxus | -| Flutter/Dart | Tauri / Dioxus | - -## Mobile Development Policy - -A strict Rust-first approach applies. - -- Use **Tauri 2.0+** for web UI (ReScript) + Rust backend. -- Use **Dioxus** for pure Rust native UI. - -Both are FOSS with independent governance. - -## Enforcement Rules - -- Do not create new TypeScript files; convert existing TS to ReScript. -- Do not use `package.json` for runtime dependencies; use `deno.json` imports. -- Do not include `node_modules` in production; Deno handles dependency caching. -- Do not use Go; use Rust. -- Do not use Python; use Julia (data/batch), Rust (systems), or ReScript (apps). -- Do not use Kotlin/Swift; use Tauri 2.0+ or Dioxus for mobile. - -## Package Management - -- Primary: Guix (`guix.scm`) -- Fallback: Nix (`flake.nix`) -- JavaScript dependencies: Deno (`deno.json` imports) - -## Security Requirements - -- Do not use MD5 or SHA-1; use SHA-256 or stronger. -- Use HTTPS only. -- Do not hardcode secrets. -- Pin all dependencies by SHA. -- Include SPDX license headers in all files. diff --git a/affinescript/ALPHA-1-RELEASE-NOTES.adoc b/affinescript/ALPHA-1-RELEASE-NOTES.adoc deleted file mode 100644 index 3c32841..0000000 --- a/affinescript/ALPHA-1-RELEASE-NOTES.adoc +++ /dev/null @@ -1,248 +0,0 @@ -== AffineScript Alpha-1 Release Notes - -=== 🎮 Game Developer’s Edition - -*Release Date:* March 31, 2026 + -*Version:* 0.1.0-alpha.1 + -*Codename:* "`Bug-Free by Design`" - -''''' - -=== 🚀 What’s New in Alpha-1 - -==== Core Language Features - -* ✅ *Affine Types*: Compiler-proven resource management (no leaks!) -* ✅ *Effect System*: Explicit I/O and side effects tracking -* ✅ *Row Polymorphism*: Flexible data structures without boilerplate -* ✅ *Algebraic Effects*: Composable computation effects -* ✅ *WebAssembly Backend*: Compile to WASM for browser games -* ✅ *VSCode Integration*: Full syntax highlighting and LSP support - -==== Game Development Superpowers - -* ✅ *Type-Safe Game State*: Compiler enforces valid state transitions -* ✅ *Resource Leak Prevention*: Textures, sounds, connections -auto-managed -* ✅ *Protocol Correctness*: Network code that can’t have protocol bugs -* ✅ *Zero-Cost Abstractions*: Type safety erased at compile time - -==== Ecosystem Integration - -* ✅ *Gossamer*: Resource-safe desktop apps (MPL-2.0) -* ✅ *Burble*: Low-latency voice comms (MPL-2.0) -* ✅ *Tree-sitter Grammar*: Advanced syntax highlighting -* ✅ *Language Server*: IDE integration - -''''' - -=== 🎯 Perfect For - -*Indie Game Developers* who want: - Fewer bugs in their game logic - -Compiler-enforced resource management - Type-safe game state machines - -WebAssembly deployment - -*Game Jam Participants* who need: - Rapid prototyping with type safety - -No runtime crashes from invalid state - Easy WASM deployment - -*Educational Use* for teaching: - Affine types and linear logic - -Type-driven game development - Functional programming concepts - -''''' - -=== 📦 What’s Included - -==== Core Technology (MPL-2.0) - -* AffineScript compiler (OCaml backend) -* WebAssembly code generator -* Tree-sitter grammar -* VSCode extension -* Language Server Protocol implementation -* Standard library modules - -==== Game Examples (AGPL-3.0-or-later) - -* Hello World with effects -* Resource management patterns -* Game state machine examples -* Type-safe entity systems -* Network protocol examples - -==== Documentation - -* Complete language specification -* Game development tutorials -* Compiler architecture guide -* Type system reference - -''''' - -=== 🔧 Installation - -==== Prerequisites - -* OCaml 5.1+ -* Dune 3.14+ -* opam packages: `+sedlex+`, `+menhir+`, `+ppx_deriving+` - -==== Build from Source - -[source,bash] ----- -git clone https://github.com/hyperpolymath/affinescript -git checkout v0.1.0-alpha.1 -cd affinescript -dune build ----- - -==== Try the Examples - -[source,bash] ----- -# Type check a game example -dune exec affinescript -- check examples/hello.affine - -# Run with interpreter -dune exec affinescript -- eval examples/hello.affine - -# Compile to WebAssembly -dune exec affinescript -- compile examples/hello.affine -o hello.wasm ----- - -''''' - -=== 📝 Licensing - -==== Core Technology - -*MPL-2.0* - Palimpsest Mutual Public License - Covers: Compiler, -tooling, standard library - Permissive with ethical use requirements - -Quantum-safe provenance tracking - -==== Game Content & Examples - -*AGPL-3.0-or-later* - GNU Affero General Public License - Covers: Game -examples, assets, tutorials - Ensures game content remains open - -Network use provisions for online games - -''''' - -=== ⚠️ Known Limitations - -==== Not Yet Implemented - -* *Effect Handlers*: Declarations parsed, runtime not implemented -* *Trait System*: 70% complete (basic traits work) -* *WASM Backend*: Basic types only (records coming soon) -* *Non-lexical Lifetimes*: Planned for beta - -==== Performance Notes - -* Compiler: Fast enough for development -* WASM output: Not yet optimized -* Runtime: Minimal overhead from type erasure - -''''' - -=== 🎮 Game Development Highlights - -==== Why This Changes Everything - -*Before AffineScript:* - -[source,rust] ----- -// Rust - manual resource management -let texture = load_texture("player.png"); -// ... hundreds of lines later ... -unload_texture(texture); // Easy to forget! ----- - -*After AffineScript:* - -[source,affinescript] ----- -// AffineScript - compiler-enforced resource management -fn game_loop() -> () / IO { - let texture = load_texture("player.png"); // own GameTexture - render(ref texture); - unload(texture); // MUST happen - compiler proves it! - // texture is GONE here - using it would be a compile error -} ----- - -==== Real-World Impact - -* ✅ *No more memory leaks* in game resources -* ✅ *No more invalid state* bugs in game logic -* ✅ *No more protocol errors* in network code -* ✅ *No more hidden I/O* in pure game functions -* ✅ *Flexible data* without boilerplate - -''''' - -=== 🚀 Roadmap to 1.0 - -==== Alpha Phase (Current) - -* Core language working -* Basic WASM backend -* Game examples included -* Documentation complete - -==== Beta Phase (Q2 2026) - -* Complete trait system -* Effect handlers runtime -* Advanced WASM features -* Performance optimization - -==== Release Candidate (Q3 2026) - -* Full standard library -* Game engine integration -* Production-ready compiler -* Complete toolchain - -==== 1.0 Release (Q4 2026) - -* Full feature set -* Production-ready -* Ecosystem packages -* Game jam templates - -''''' - -=== 🤝 Community & Support - -*GitHub*: https://github.com/hyperpolymath/affinescript *Issues*: -https://github.com/hyperpolymath/affinescript/issues *Discussions*: -https://github.com/hyperpolymath/affinescript/discussions - -*Related Projects:* - Gossamer: -https://github.com/hyperpolymath/gossamer (MPL-2.0) - Burble: -https://github.com/hyperpolymath/burble (MPL-2.0) - -''''' - -=== 🎁 Try It Today - -[source,bash] ----- -# Clone the alpha release -git clone --branch v0.1.0-alpha.1 https://github.com/hyperpolymath/affinescript - -# Build and run -dune build -dune exec affinescript -- eval examples/hello.affine - -# Start building your bug-free game! ----- - -''''' - -*AffineScript: Where your compiler becomes your QA team.* - -SPDX-License-Identifier: CC-BY-SA-4.0 SPDX-FileCopyrightText: 2026 -Jonathan D.A. Jewell and contributors diff --git a/affinescript/BACKEND-ANALYSIS.adoc b/affinescript/BACKEND-ANALYSIS.adoc deleted file mode 100644 index 0e5e688..0000000 --- a/affinescript/BACKEND-ANALYSIS.adoc +++ /dev/null @@ -1,190 +0,0 @@ -== AffineScript Backend Analysis: Coprocessor Support - -=== Executive Summary - -*Current State:* AffineScript has basic WASM backend support but *NO -dedicated coprocessor backends* for audio, I/O, NPU, TPU, GPU, math, -physics, FPGA, crypto, or vector operations. - -*What Exists:* - ✅ Basic WASM code generation (lib/codegen.ml) - ✅ -WASI runtime support (lib/wasi_runtime.ml) - ✅ FFI layer -(runtime/src/ffi.rs) - ✅ Memory management (runtime/src/alloc.rs) - ✅ -Basic I/O operations (print, println) - -*What’s Missing:* - ❌ Audio coprocessor backend - ❌ GPU compute -backend - ❌ NPU/TPU acceleration - ❌ Physics engine integration - ❌ -FPGA acceleration - ❌ Cryptographic acceleration - ❌ Vector/SIMD -optimization - ❌ Math coprocessor utilization - -''''' - -=== Detailed Analysis - -==== 1. Current Backend Architecture - -*WASM Backend (lib/codegen.ml):* - Targets WebAssembly 1.0 (MVP) - Basic -arithmetic operations - Memory management via linear memory - Function -calls and control flow - No SIMD or vector instructions - No -multithreading support - -*WASI Runtime (lib/wasi_runtime.ml):* - fd_write for stdout/stderr - -Basic string and integer printing - No file I/O, networking, or system -calls - No hardware acceleration - -*FFI Layer (runtime/src/ffi.rs):* - Host function callbacks - String -interop - JavaScript bindings (stubbed) - WASI bindings (stubbed) - No -coprocessor-specific FFI - -==== 2. Coprocessor Support Status - -===== Audio Processing - -* *Status:* ❌ Not implemented -* *Current:* Basic print/println only -* *Missing:* Audio buffers, DSP operations, real-time processing -* *Files:* No audio-specific modules - -===== GPU Compute - -* *Status:* ❌ Not implemented -* *Current:* CPU-only WASM execution -* *Missing:* WebGPU bindings, compute shaders, GPU memory management -* *Files:* No GPU-related code - -===== NPU/TPU Acceleration - -* *Status:* ❌ Not implemented -* *Current:* No neural network support -* *Missing:* Tensor operations, NPU instruction sets, model loading -* *Files:* No AI/ML modules - -===== Physics Engine - -* *Status:* ❌ Not implemented -* *Current:* No physics calculations -* *Missing:* Collision detection, rigid body dynamics, physics -simulation -* *Files:* No physics-related code - -===== FPGA Acceleration - -* *Status:* ❌ Not implemented -* *Current:* No hardware acceleration -* *Missing:* FPGA bitstream generation, hardware description -* *Files:* No FPGA-related modules - -===== Cryptographic Acceleration - -* *Status:* ❌ Not implemented -* *Current:* No crypto operations -* *Missing:* Hash functions, encryption, digital signatures -* *Files:* No crypto modules - -===== Vector/SIMD Operations - -* *Status:* ❌ Not implemented -* *Current:* Scalar operations only -* *Missing:* SIMD instructions, vectorized math, parallel operations -* *Files:* No vector optimization - -===== Math Coprocessor - -* *Status:* ❌ Not implemented -* *Current:* Basic arithmetic in WASM -* *Missing:* High-precision math, transcendental functions, numerical -optimization -* *Files:* No math library - -==== 3. Backend vs Kernel Distinction - -*Current Backends:* - These are *compilation targets* (WASM) - They -generate code for execution - No runtime optimization or -hardware-specific code - -*Missing Kernels:* - No specialized computation kernels - No -hardware-optimized routines - No domain-specific libraries - No -acceleration frameworks - -==== 4. What Would Be Needed - -===== For Audio Backend: - -[source,rust] ----- -// Example: Audio coprocessor backend (missing) -pub mod audio { - pub fn init_device(sample_rate: u32, channels: u8) -> AudioDevice; - pub fn create_buffer(samples: &[f32]) -> AudioBuffer; - pub fn play_buffer(device: &AudioDevice, buffer: &AudioBuffer); - pub fn apply_effect(buffer: &mut AudioBuffer, effect: AudioEffect); -} ----- - -===== For GPU Backend: - -[source,rust] ----- -// Example: GPU compute backend (missing) -pub mod gpu { - pub fn init_context() -> GPUContext; - pub fn create_shader(source: &str) -> GPUShader; - pub fn dispatch_compute(context: &GPUContext, shader: &GPUShader, workgroups: (u32, u32, u32)); - pub fn read_buffer(buffer: &GPUBuffer) -> Vec; -} ----- - -===== For Vector Backend: - -[source,ocaml] ----- -(* Example: Vector optimization backend (missing) *) -module Vector = struct - let simd_add : float array -> float array -> float array - let simd_mul : float array -> float array -> float array - let simd_dot : float array -> float array -> float - let simd_normalize : float array -> float array -end ----- - -==== 5. Recommendations - -===== Short-Term (Alpha-1): - -* ✅ Document current limitations -* ✅ Focus on core language stability -* ✅ Complete basic WASM backend -* ✅ Implement WASI file I/O - -===== Medium-Term (Beta): - -* 🔄 Add WASM SIMD support -* 🔄 Implement basic audio via Web Audio API -* 🔄 Add WebGPU bindings for browser GPU -* 🔄 Implement vector math library - -===== Long-Term (1.0+): - -* 🚀 Dedicated audio DSP backend -* 🚀 GPU compute backend -* 🚀 NPU/TPU acceleration -* 🚀 Physics engine integration -* 🚀 Cryptographic acceleration -* 🚀 FPGA backend - -''''' - -=== Conclusion - -AffineScript currently has *basic backend support only* - enough to -compile to WASM and run simple programs, but *no coprocessor-specific -backends or kernels*. All the mentioned coprocessor support (audio, GPU, -NPU, physics, FPGA, crypto, vector) would need to be implemented from -scratch. - -The current architecture is designed to be extensible, with the FFI -layer providing a foundation for future coprocessor integration, but -none of these advanced backends exist yet. - -*Priority for Alpha-1:* Focus on core language features and basic WASM -backend. Coprocessor support should be planned for future releases (Beta -and beyond). diff --git a/affinescript/BACKEND-IMPLEMENTATION.adoc b/affinescript/BACKEND-IMPLEMENTATION.adoc deleted file mode 100644 index 0b1a9b4..0000000 --- a/affinescript/BACKEND-IMPLEMENTATION.adoc +++ /dev/null @@ -1,399 +0,0 @@ -== AffineScript Backend Implementation - -=== 🎯 Overview - -*Complete processor backends with kernel stubs* have been implemented -for AffineScript. - -==== What’s Been Added - -===== 1. *Processor Backends (Complete)* - -* ✅ *WASM Backend* - Full WebAssembly support -* ✅ *Native Backend* - x86-64, ARM64, RISC-V assembly -* ✅ *GPU Backend* - WebGPU, Vulkan, Metal, CUDA, OpenCL -* ✅ *Audio DSP Backend* - WebAudio, ALSA, CoreAudio, WASAPI, JACK -* ✅ *NPU/TPU Backend* - TensorFlow Lite, ONNX, TVM -* ✅ *FPGA Backend* - Xilinx, Intel, AMD, Lattice - -===== 2. *Kernel Stubs (Placeholders)* - -* ✅ *Audio Kernel* - DSP effects, mixing, real-time processing -* ✅ *GPU Kernel* - Compute shaders, parallel operations -* ✅ *NPU Kernel* - Neural network acceleration -* ✅ *Math Kernel* - High-precision calculations -* ✅ *Physics Kernel* - Collision detection, rigid body dynamics -* ✅ *FPGA Kernel* - Hardware acceleration -* ✅ *Crypto Kernel* - Encryption, hashing -* ✅ *Vector Kernel* - SIMD operations - -===== 3. *Architecture Framework* - -* ✅ *Backend Registry* - Dynamic backend loading -* ✅ *Kernel Registry* - Hardware acceleration management -* ✅ *Configuration System* - Target-specific settings -* ✅ *Optimization Pipeline* - Code transformation framework - -''''' - -=== 📁 File Structure - -.... -lib/backends/ -├── architecture.ml # Core architecture and interfaces -├── wasm_backend.ml # Complete WASM backend -├── native_backend.ml # Native code generation (stub) -├── gpu_backend.ml # GPU compute backend (stub) -├── audio_backend.ml # Audio DSP backend (stub) -├── npu_backend.ml # NPU/TPU backend (stub) -├── fpga_backend.ml # FPGA backend (stub) -├── audio_kernel.ml # Audio processing kernel (stub) -├── gpu_kernel.ml # GPU acceleration kernel (stub) -├── npu_kernel.ml # NPU acceleration kernel (stub) -├── math_kernel.ml # Math operations kernel (stub) -├── physics_kernel.ml # Physics simulation kernel (stub) -├── fpga_kernel.ml # FPGA acceleration kernel (stub) -├── crypto_kernel.ml # Cryptographic kernel (stub) -├── vector_kernel.ml # Vector/SIMD kernel (stub) -└── backends.ml # Main entry point -.... - -''''' - -=== 🔧 Backend Capabilities - -==== WASM Backend - -[source,ocaml] ----- -capabilities = [ - BasicArithmetic; - MemoryManagement; - ControlFlow; - FunctionCalls; -] ----- - -==== Native Backend - -[source,ocaml] ----- -capabilities = [ - BasicArithmetic; - MemoryManagement; - ControlFlow; - FunctionCalls; - SIMDOperations; -] ----- - -==== GPU Backend - -[source,ocaml] ----- -capabilities = [ - BasicArithmetic; - MemoryManagement; - ControlFlow; - FunctionCalls; - SIMDOperations; - HardwareAcceleration; -] ----- - -''''' - -=== 🚀 Usage Examples - -==== Compile to WASM - -[source,ocaml] ----- -let target = Architecture.WASM { - target_version = "1.0"; - enable_simd = true; - memory_pages = 16; - optimize_for = `Speed; -} - -let wasm_code = Backends.compile target program ----- - -==== Compile to Native - -[source,ocaml] ----- -let target = Architecture.Native { - target_arch = `X86_64; - target_os = `Linux; - optimization_level = 3; - enable_lto = true; -} - -let asm_code = Backends.compile target program ----- - -==== Compile to GPU - -[source,ocaml] ----- -let target = Architecture.GPU { - api = `WebGPU; - device_type = `Discrete; - enable_compute = true; - max_workgroups = (1024, 1024, 64); -} - -let shader_code = Backends.compile target program ----- - -==== Execute Audio Kernel - -[source,ocaml] ----- -let result = Backends.execute_kernel "audio_kernel" "apply_reverb" [audio_buffer; settings] ----- - -''''' - -=== 🔍 Backend vs Kernel - -==== Processor Backends - -*What they do:* - Generate target-specific code - Handle memory -management - Implement control flow - Provide basic arithmetic - -*Examples:* - WASM backend → WebAssembly text format - Native backend → -x86-64 assembly - GPU backend → WGSL/Vulkan shaders - -==== Kernels - -*What they do:* - Hardware-accelerated routines - Domain-specific -optimizations - Low-level hardware access - Performance-critical -operations - -*Examples:* - Audio kernel → DSP effects, mixing - GPU kernel → Compute -shaders, parallel operations - NPU kernel → Neural network inference - -''''' - -=== 📊 Feature Support Matrix - -[cols=",,,,,,",options="header",] -|=== -|Feature |WASM |Native |GPU |Audio |NPU |FPGA -|Basic Arithmetic |✅ |✅ |✅ |✅ |✅ |✅ -|Memory Management |✅ |✅ |✅ |✅ |✅ |✅ -|Control Flow |✅ |✅ |✅ |✅ |✅ |✅ -|Function Calls |✅ |✅ |✅ |✅ |✅ |✅ -|SIMD Operations |❌ |✅ |✅ |✅ |✅ |✅ -|Multithreading |❌ |✅ |✅ |✅ |✅ |✅ -|Hardware Acceleration |❌ |❌ |✅ |✅ |✅ |✅ -|Real-Time Processing |❌ |❌ |❌ |✅ |❌ |❌ -|=== - -''''' - -=== 🛠️ Implementation Status - -==== Complete Backends - -* ✅ *WASM Backend* - Fully functional -* ✅ *Backend Architecture* - Complete framework -* ✅ *Registry System* - Working backend/kernel management - -==== Stub Backends (Need Implementation) - -* ⚠️ *Native Backend* - Needs assembly generation -* ⚠️ *GPU Backend* - Needs shader compilation -* ⚠️ *Audio Backend* - Needs DSP code generation -* ⚠️ *NPU Backend* - Needs neural network compilation -* ⚠️ *FPGA Backend* - Needs hardware description generation - -==== Kernel Stubs (Need Implementation) - -* ⚠️ *All Kernels* - Need actual hardware acceleration code - -''''' - -=== 🎯 Roadmap - -==== Alpha-1 (Current) - -* ✅ Complete WASM backend -* ✅ Backend architecture framework -* ✅ All backend stubs in place -* ✅ All kernel stubs in place -* ✅ Registry and management system - -==== Beta (Next) - -* 🔄 Implement Native backend (x86-64 first) -* 🔄 Add WASM SIMD support -* 🔄 Implement basic Audio backend -* 🔄 Add WebGPU support to GPU backend - -==== 1.0 Release - -* 🚀 Complete all processor backends -* 🚀 Implement key kernels (Audio, GPU, Math) -* 🚀 Hardware acceleration testing -* 🚀 Performance optimization - -''''' - -=== 📝 API Documentation - -==== Backend Selection - -[source,ocaml] ----- -val select_backend : Architecture.backend_target -> (module PROCESSOR_BACKEND) ----- - -==== Code Generation - -[source,ocaml] ----- -val generate_code : (module PROCESSOR_BACKEND) -> Ast.program -> string ----- - -==== Kernel Execution - -[source,ocaml] ----- -val execute_kernel : string -> string -> Ast.expr list -> Ast.expr ----- - -==== Backend Management - -[source,ocaml] ----- -val available_backends : unit -> string list -val backend_supports : string -> string -> bool -val backend_capabilities : string -> Architecture.capability list ----- - -''''' - -=== 🔧 Configuration Options - -==== WASM Configuration - -[source,ocaml] ----- -type wasm_config = { - target_version : string; - enable_simd : bool; - enable_threads : bool; - enable_reference_types : bool; - enable_tail_calls : bool; - memory_pages : int; - optimize_for : [ `Size | `Speed | `Balanced ]; -} ----- - -==== Native Configuration - -[source,ocaml] ----- -type native_config = { - target_arch : [ `X86_64 | `ARM64 | `RISCV64 | `WASM32 ]; - target_os : [ `Linux | `Windows | `MacOS | `WASI ]; - optimization_level : int; - enable_lto : bool; - enable_debug : bool; -} ----- - -==== GPU Configuration - -[source,ocaml] ----- -type gpu_config = { - api : [ `WebGPU | `Vulkan | `Metal | `CUDA | `OpenCL ]; - device_type : [ `Integrated | `Discrete | `Virtual ]; - enable_compute : bool; - enable_graphics : bool; - max_workgroups : int * int * int; - shader_model : string; -} ----- - -''''' - -=== 🎓 Example: Compiling to Multiple Targets - -[source,ocaml] ----- -(* Initialize backends *) -Backends.initialize (); - -(* Compile to WASM *) -let wasm_target = Architecture.WASM { - target_version = "1.0"; - enable_simd = true; - memory_pages = 16; - optimize_for = `Size; -}; -let wasm_code = Backends.compile wasm_target program; - -(* Compile to Native x86-64 *) -let native_target = Architecture.Native { - target_arch = `X86_64; - target_os = `Linux; - optimization_level = 3; - enable_lto = true; -}; -let asm_code = Backends.compile native_target program; - -(* Compile to WebGPU *) -let gpu_target = Architecture.GPU { - api = `WebGPU; - device_type = `Discrete; - enable_compute = true; - max_workgroups = (1024, 1024, 64); -}; -let shader_code = Backends.compile gpu_target program; - -(* Check available backends *) -let backends = Backends.available_backends (); -Printf.printf "Available backends: %s\n" (String.concat ", " backends); - -(* Check backend capabilities *) -let wasm_caps = Backends.backend_capabilities "wasm"; -let has_simd = Backends.backend_supports "wasm" "simd"; ----- - -''''' - -=== 🏗️ Build Integration - -Add to your build system: - -[source,ocaml] ----- -(* In your main compiler module *) -module Backends = Backends - -let () = - Backends.initialize (); - (* Now you can use Backends.compile, etc. *) ----- - -''''' - -=== 📚 Related Documentation - -* BACKEND-ANALYSIS.md - Detailed analysis of backend requirements -* ALPHA-1-RELEASE-NOTES.md - Release notes with backend status -* ROADMAP.adoc - Future backend development plans - -''''' - -=== 🔒 License - -All backend code is licensed under *MPL-2.0*. - -SPDX-License-Identifier: CC-BY-SA-4.0 SPDX-FileCopyrightText: 2026 -Jonathan D.A. Jewell and contributors diff --git a/affinescript/CNAME b/affinescript/CNAME deleted file mode 100644 index 71fb6c2..0000000 --- a/affinescript/CNAME +++ /dev/null @@ -1 +0,0 @@ -affinescript.dev diff --git a/affinescript/CODE_OF_CONDUCT.adoc b/affinescript/CODE_OF_CONDUCT.adoc deleted file mode 100644 index 3d22c4c..0000000 --- a/affinescript/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,339 +0,0 @@ -== Code of Conduct - -=== Our Pledge - -We as members, contributors, and leaders pledge to make participation in -Proof Burrower a harassment-free experience for everyone, regardless of -age, body size, visible or invisible disability, ethnicity, sex -characteristics, gender identity and expression, level of experience, -education, socio-economic status, nationality, personal appearance, -race, caste, colour, religion, or sexual identity and orientation. - -We pledge to act and interact in ways that contribute to an open, -welcoming, diverse, inclusive, and healthy community. - -We recognise that a thriving open source community requires -*psychological safety* — an environment where people can contribute, ask -questions, make mistakes, and learn without fear of ridicule or -retaliation. - -''''' - -=== Our Standards - -==== Expected Behaviour - -The following behaviours contribute to a positive environment: - -*Communication* - Using welcoming and inclusive language - Being -respectful of differing viewpoints and experiences - Giving and -gracefully accepting constructive feedback - Assuming good intent while -addressing impact - Communicating clearly and patiently, especially with -newcomers - -*Collaboration* - Focusing on what is best for the community - Showing -empathy and kindness toward other community members - Being -collaborative rather than competitive - Mentoring and supporting less -experienced contributors - Celebrating others’ contributions and -successes - -*Professionalism* - Accepting responsibility and apologising to those -affected by our mistakes - Learning from the experience and avoiding -repetition - Respecting others’ time and attention - Staying on topic in -project spaces - Following project guidelines and conventions - -*Accessibility* - Using plain language and avoiding unnecessary jargon - -Providing alt text for images and transcripts for audio/video - Being -patient with those using assistive technologies - Accommodating -different communication styles and needs - Recognising that not everyone -communicates the same way - -==== Unacceptable Behaviour - -The following behaviours are considered harassment and are unacceptable: - -*Harassment* - The use of sexualised language or imagery, and sexual -attention or advances of any kind - Trolling, insulting or derogatory -comments, and personal or political attacks - Public or private -harassment - Deliberate intimidation, stalking, or following (online or -in-person) - Unwelcome physical contact or simulated physical contact -(e.g., emoji) - Sustained disruption of talks, events, or online -discussions - -*Discrimination* - Discriminatory jokes and language - Posting or -threatening to post others’ personally identifying information -("`doxing`") - Advocating for, or encouraging, any of the above -behaviour - Microaggressions — subtle, often unintentional, -discriminatory comments or actions - -*Professional Misconduct* - Publishing others’ private information -without explicit permission - Misrepresenting affiliation or -contributions - Plagiarism or claiming credit for others’ work - -Retaliating against anyone who reports a Code of Conduct violation - -Other conduct which could reasonably be considered inappropriate in a -professional setting - -==== Grey Areas - -Some situations require judgement. When uncertain: - -* *Intent vs Impact*: Good intentions do not excuse harmful impact. -Focus on making things right. -* *Power Dynamics*: Those with more power (maintainers, employers, -experienced contributors) must be especially mindful of their impact. -* *Cultural Differences*: What’s acceptable varies by culture. When in -doubt, err on the side of caution and ask. -* *Humour*: Jokes at others’ expense are rarely funny to everyone. Punch -up, not down. - -''''' - -=== Scope - -This Code of Conduct applies within all community spaces, including: - -*Online Spaces* - Repository discussions, issues, and pull/merge -requests - Project chat channels (Matrix, Discord, Slack, IRC) - Mailing -lists and forums - Social media when representing the project - Video -calls and virtual meetings - -*In-Person Spaces* - Conferences, meetups, and events - Workshops and -training sessions - Any gathering where you represent the project - -*Representation* This Code of Conduct also applies when an individual is -officially representing the community in public spaces. Examples -include: - -* Using an official project email address -* Posting via an official social media account -* Acting as an appointed representative at an event -* Speaking on behalf of the project - -''''' - -=== Enforcement - -==== Reporting - -If you experience or witness unacceptable behaviour, or have any other -concerns, please report it as soon as possible. - -*How to Report* - -[width="99%",cols="30%,33%,37%",options="header",] -|=== -|Method |Details |Best For -|*Email* |j.d.a.jewell@open.ac.uk |Detailed reports, sensitive matters - -|*Private Message* |Contact any maintainer directly |Quick questions, -minor issues - -|*Anonymous Form* |[Link to form if available] |When you need anonymity -|=== - -*What to Include* - -* Your contact information (unless anonymous) -* Names/usernames of those involved -* Description of what happened -* When and where it occurred -* Any witnesses -* Any supporting evidence (screenshots, links) -* How you would like us to respond (if you have a preference) - -*What Happens Next* - -[arabic] -. You will receive acknowledgment within *\{\{RESPONSE_TIME}}* -. The hyperpolymath will review the report -. We may ask for additional information -. We will determine appropriate action -. We will inform you of the outcome (respecting others’ privacy) - -==== Confidentiality - -All reports will be handled with discretion: - -* Reporter identity is protected by default -* Details are shared only with those who need to know -* We will ask before naming you in any communication -* Anonymous reports are accepted and investigated - -==== Conflicts of Interest - -If a hyperpolymath member is involved in an incident: - -* They will recuse themselves from the process -* Another maintainer or external party will handle the report -* We will disclose any potential conflicts - -''''' - -=== Enforcement Guidelines - -The hyperpolymath will follow these guidelines in determining -consequences: - -==== 1. Correction - -*Community Impact*: Use of inappropriate language or other behaviour -deemed unprofessional or unwelcome. - -*Consequence*: A private, written warning providing clarity around the -nature of the violation and an explanation of why the behaviour was -inappropriate. A public apology may be requested. - -*Duration*: Immediate - -==== 2. Warning - -*Community Impact*: A violation through a single incident or series of -actions. - -*Consequence*: A warning with consequences for continued behaviour. No -interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, for a specified period. This -includes avoiding interactions in community spaces as well as external -channels like social media. Violating these terms may lead to a -temporary or permanent ban. - -*Duration*: 1-4 weeks - -==== 3. Temporary Ban - -*Community Impact*: A serious violation of community standards, -including sustained inappropriate behaviour. - -*Consequence*: A temporary ban from any sort of interaction or public -communication with the community for a specified period. No public or -private interaction with the people involved, including unsolicited -interaction with those enforcing the Code of Conduct, is allowed during -this period. Violating these terms may lead to a permanent ban. - -*Duration*: 1-6 months - -==== 4. Permanent Ban - -*Community Impact*: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behaviour, harassment of an -individual, or aggression toward or disparagement of classes of -individuals. - -*Consequence*: A permanent ban from any sort of public interaction -within the community. - -*Duration*: Permanent (with appeal rights after 12 months) - -==== Enforcement Across Perimeters - -For contributors with elevated access (Perimeter 2 or 1): - -[cols=",",options="header",] -|=== -|Level |Additional Consequence -|Correction |Noted in contributor record -|Warning |Access privileges may be temporarily reduced -|Temporary Ban |Access reduced to Perimeter 3 for ban duration -|Permanent Ban |All access revoked -|=== - -''''' - -=== Appeals - -If you believe an enforcement decision was made in error: - -[arabic] -. *Wait 7 days* after the decision (cooling-off period) -. *Email* j.d.a.jewell@open.ac.uk with subject line "`Appeal: [Original -Report ID]`" -. *Explain* why you believe the decision should be reconsidered -. *Provide* any new information not previously available - -*Appeals Process* - -* Appeals are reviewed by a different hyperpolymath member than the -original -* You will receive a response within 14 days -* The appeals decision is final -* You may only appeal once per incident - -*Grounds for Appeal* - -* Procedural errors in the original investigation -* New evidence not previously available -* Disproportionate response to the violation -* Misunderstanding of facts - -''''' - -=== Supporting Those Who Report - -We are committed to supporting those who report violations: - -*We Will* - Believe and take all reports seriously - Respect your -privacy and confidentiality preferences - Keep you informed of progress -(if you wish) - Take steps to protect you from retaliation - Provide -resources if you need support - -*We Will Not* - Require you to confront the person directly - Dismiss -reports without investigation - Reveal your identity without consent - -Tolerate retaliation against reporters - Rush you to make decisions - -''''' - -=== Prevention - -Beyond enforcement, we actively work to prevent issues: - -*Onboarding* - All contributors are expected to read this Code of -Conduct - Perimeter 2 applicants must confirm they’ve read and -understood it - Maintainers receive additional training on enforcement - -*Culture* - We model the behaviour we expect - We intervene early when -we see potential issues - We thank people for positive contributions - -We create opportunities for diverse voices - -*Review* - This Code of Conduct is reviewed annually - Community -feedback is welcomed - Changes are communicated clearly - -''''' - -=== Acknowledgments - -This Code of Conduct is adapted from: - -* https://www.contributor-covenant.org/[Contributor Covenant], version -2.1 -* https://www.djangoproject.com/conduct/[Django Code of Conduct] -* https://www.rust-lang.org/policies/code-of-conduct[Rust Code of -Conduct] -* https://www.python.org/psf/conduct/[Python Community Code of Conduct] - -We thank these communities for their leadership in creating welcoming -spaces. - -''''' - -=== Questions? - -If you have questions about this Code of Conduct: - -* Open a -https://github.com/hyperpolymath/proof-burrower/discussions[Discussion] -(for general questions) -* Email j.d.a.jewell@open.ac.uk (for private questions) -* Contact any maintainer directly - -''''' - -=== Summary - -*Be kind. Be respectful. Be collaborative.* - -We’re all here because we care about this project. Let’s make it a place -where everyone can do their best work. - -''''' - -Last updated: 2026 · Based on Contributor Covenant 2.1 diff --git a/affinescript/COMPILER-CAPABILITIES.adoc b/affinescript/COMPILER-CAPABILITIES.adoc deleted file mode 100644 index 93742fb..0000000 --- a/affinescript/COMPILER-CAPABILITIES.adoc +++ /dev/null @@ -1,431 +0,0 @@ -== AffineScript Compiler Capabilities Analysis - -=== 🎯 Current Compiler Targets - -==== Primary Target: WebAssembly - -*Current Status:* ✅ *Fully Implemented* - -The AffineScript compiler currently *compiles to WebAssembly 1.0* as its -primary target. - -[source,ocaml] ----- -(* lib/codegen.ml - WASM Backend *) -module Wasm : sig - val generate_code : context -> Ast.program -> string - (* Generates WebAssembly text format *) -end ----- - -==== Secondary Target: Julia (Experimental) - -*Current Status:* ⚠️ *Phase 1 - Basic Types Only* - -[source,ocaml] ----- -(* lib/julia_codegen.ml - Julia Backend *) -module Julia_codegen : sig - val generate_code : Ast.program -> string - (* Generates Julia source code - Phase 1 MVP *) -end ----- - -''''' - -=== 📊 Deployment Platform Support - -==== iOS - -* *Status:* ❌ *Not Directly Supported* -* *Workaround:* ✅ *Via WebAssembly in Safari* -* *Limitations:* No native compilation, WASM only -* *Performance:* Good (Safari has excellent WASM support) - -==== Android - -* *Status:* ✅ *Supported via WebAssembly* -* *Methods:* Chrome/Firefox WASM, or native via Termux -* *Performance:* Excellent (Chrome V8 WASM engine) -* *Limitations:* No direct APK compilation - -==== MINIX - -* *Status:* ❌ *Not Supported* -* *Reason:* No WASM runtime in MINIX by default -* *Workaround:* Would need custom WASM runtime port - -==== RISC-V - -* *Status:* ⚠️ *Partial Support* -* *Current:* WASM can run on RISC-V via Wasmtime/WASI -* *Native:* ❌ No RISC-V assembly backend yet -* *Performance:* Good (Wasmtime is optimized for RISC-V) - -==== ARM (32/64-bit) - -* *Status:* ✅ *Supported via WASM* -* *Native:* ❌ No ARM assembly backend yet -* *Performance:* Excellent (ARM has fast WASM interpreters) -* *Platforms:* iOS, Android, Linux ARM, Windows ARM - -==== AMD (x86-64) - -* *Status:* ✅ *Fully Supported* -* *Methods:* WASM in browsers, native via WASI -* *Performance:* Excellent (native-speed WASM) -* *Platforms:* Windows, Linux, macOS - -==== NVIDIA (CUDA/GPU) - -* *Status:* ❌ *Not Supported* -* *Current:* No GPU code generation -* *Workaround:* WASM + WebGPU in browsers -* *Future:* GPU backend planned (see backends/gpu_backend.ml) - -''''' - -=== 🧠 Memory Architecture Support - -==== GDDR5/6/7 (GPU Memory) - -* *Status:* ❌ *Not Supported* -* *Reason:* No GPU backend implementation -* *Current:* WASM uses linear memory only -* *Future:* GPU backend will add GDDR support - -==== HBM (High Bandwidth Memory) - -* *Status:* ❌ *Not Supported* -* *Reason:* No GPU/accelerator backends -* *Current:* All memory operations are CPU-bound -* *Future:* NPU/GPU backends will add HBM support - -==== Current Memory Model - -[source,wasm] ----- -(module - (memory $memory 16) ; 16 pages = 1MB (64KB per page) - (export "memory" (memory $memory)) -) ----- - -*Limitations:* - Maximum 1MB by default (configurable) - No GPU memory -access - No shared memory between CPU/GPU - No HBM or GDDR support - -''''' - -=== 🔧 Current Compiler Pipeline - -.... -AffineScript Source - ↓ (Parser) -Abstract Syntax Tree (AST) - ↓ (Type Checker) -Typed AST - ↓ (Borrow Checker) -Validated AST - ↓ (Code Generator) -WebAssembly Text Format - ↓ (WASM Toolchain) -WebAssembly Binary (.wasm) -.... - -==== Supported Features - -* ✅ *Lexer & Parser:* Complete (quantity sugar `+:0/:1/:ω+` and -attribute form `+@erased/@linear/@unrestricted+` accepted) -* ✅ *Type Checker:* Wired into `+check+`/`+compile+`/`+eval+` CLI paths -* ✅ *Affine Types / QTT:* Live gate -(`+Quantity.check_program_quantities+` runs in standard CLI pipeline) -* ⚠️ *Borrow Checker:* Live gate with ongoing Phase 3 work -* ⚠️ *Dependent / Refinement Types:* parse-only — `+TRefined+` AST node -exists but predicates do not reduce -* ✅ *WASM Codegen:* Primary backend (feature gaps remain for advanced -effect-handler lowering). *Bridge layer workaround available in -`+docs/WASM-EFFECT-HANDLER-WORKAROUND.md+`.* -* ✅ *Julia Codegen:* Phase 1 (basic types) -* ❌ *Native Codegen:* Not implemented -* ❌ *GPU Codegen:* Not implemented -* ⚠️ *Optimizer:* Basic constant folding in `+lib/opt.ml+`; no full -optimization pipeline yet - -==== Missing Features - -* ❌ *SIMD Operations:* No WASM SIMD support -* ❌ *Multithreading:* No WASM threads -* ❌ *Tail Calls:* Not implemented -* ❌ *Reference Types:* Not implemented -* ⚠️ *Exception Handling in WASM Backends:* `+try/finally+` lowering -exists; `+try/catch+` still requires EH proposal/CPS path -* ⚠️ *Garbage Collection:* WASM GC backend exists (`+--wasm-gc+`) but is -not full feature parity - -''''' - -=== 📊 Platform Compatibility Matrix - -[width="99%",cols="19%,21%,26%,21%,13%",options="header",] -|=== -|Platform |WASM Support |Native Support |GPU Support |Status -|*iOS* |✅ Excellent |❌ None |❌ None |Works via Safari -|*Android* |✅ Excellent |❌ None |❌ None |Works via Chrome -|*MINIX* |❌ None |❌ None |❌ None |Not supported -|*RISC-V* |✅ Good |❌ None |❌ None |Via Wasmtime -|*ARM64* |✅ Excellent |❌ None |❌ None |All modern browsers -|*ARM32* |✅ Good |❌ None |❌ None |Most browsers -|*x86-64 (AMD/Intel)* |✅ Excellent |❌ None |❌ None |All browsers -|*NVIDIA GPU* |❌ None |❌ None |❌ None |Not supported -|*Apple Silicon* |✅ Excellent |❌ None |❌ None |Safari WASM -|*Windows* |✅ Excellent |❌ None |❌ None |Edge/Chrome/Firefox -|*Linux* |✅ Excellent |❌ None |❌ None |All browsers -|*macOS* |✅ Excellent |❌ None |❌ None |Safari/Chrome -|=== - -''''' - -=== 🚀 Deployment Options - -==== Web Deployment (Recommended) - -[source,html] ----- - - ----- - -*Pros:* - ✅ Works on all platforms with modern browsers - ✅ No -installation required - ✅ Automatic updates - ✅ Sandboxed execution - -*Cons:* - ❌ No GPU acceleration - ❌ Limited to browser capabilities - -❌ No direct hardware access - -==== Node.js/WASI Deployment - -[source,javascript] ----- -// Run WASM in Node.js -const fs = require('fs'); -const wasmBuffer = fs.readFileSync('program.wasm'); -WebAssembly.instantiate(wasmBuffer).then(wasmModule => { - wasmModule.instance.exports.main(); -}); ----- - -*Pros:* - ✅ Server-side execution - ✅ File system access via WASI - ✅ -Can use Node.js APIs - -*Cons:* - ❌ No GPU access - ❌ Single-threaded - ❌ No SIMD - -==== Native Deployment (Future) - -[source,bash] ----- -# Future: Compile to native binary -affinescript compile --target native --arch arm64 program.affine -./program ----- - -*Status:* ❌ Not yet implemented *Planned:* Native backend in -development *Target:* Alpha-2 release - -''''' - -=== 💾 Memory Usage Analysis - -==== Current WASM Memory Model - -.... -Memory Layout: -- Linear memory: 64KB pages (default 16 pages = 1MB) -- Heap: Managed manually (no GC) -- Stack: Grows downward from high address -- Globals: Fixed addresses - -Allocation Strategy: -- Bump pointer allocation -- No fragmentation handling -- Manual free required -- No generational GC -.... - -==== Memory Limitations - -* *Maximum Memory:* 1MB by default (configurable) -* *No GPU Memory:* Cannot access GDDR5/6/7 or HBM -* *No Shared Memory:* CPU and GPU memory separate -* *No Unified Memory:* No automatic CPU/GPU sync -* *Manual Management:* Developer must free memory - -==== Future Memory Improvements - -* 🔄 *Automatic GC:* Planned for Beta -* 🔄 *SIMD Support:* WASM SIMD extension -* 🔄 *Multithreading:* WASM threads -* 🔄 *GPU Memory:* GPU backend will add GDDR/HBM support -* 🔄 *Unified Memory:* Future CPU/GPU integration - -''''' - -=== 🎯 Performance Characteristics - -==== Current Performance - -* *Arithmetic:* Native speed (WASM is fast) -* *Memory Access:* Linear memory (no caching) -* *Function Calls:* Direct calls (no virtual dispatch) -* *Control Flow:* Optimized branches -* *No SIMD:* Scalar operations only -* *No Parallelism:* Single-threaded - -==== Benchmark Results (Estimated) - -.... -Operation | WASM (Current) | Native (Future) | GPU (Future) ---------------------|----------------|-----------------|-------------- -Integer Arithmetic | 100% | 100% | N/A -Float Arithmetic | 95% | 100% | 1000x (GPU) -Memory Access | 80% | 100% | 500x (GPU) -Function Calls | 90% | 100% | N/A -Control Flow | 95% | 100% | N/A -SIMD Operations | N/A | 4x-8x | 16x-32x -Parallel Operations| N/A | 2x-4x (cores) | 1000x (cores) -.... - -''''' - -=== 🔮 Future Platform Support Roadmap - -==== Short-Term (Alpha-1 to Alpha-2) - -* 🔄 *Native Backend:* x86-64 assembly generation -* 🔄 *ARM64 Support:* Native ARM assembly -* 🔄 *RISC-V Support:* Native RISC-V assembly -* 🔄 *WASM SIMD:* Enable SIMD extensions -* 🔄 *WASM Threads:* Add multithreading support - -==== Medium-Term (Beta Release) - -* 🚀 *GPU Backend:* WebGPU/Vulkan compute -* 🚀 *Audio Backend:* WebAudio/DSP support -* 🚀 *iOS/Android:* Native app integration -* 🚀 *Memory Management:* Automatic GC -* 🚀 *Optimizer:* Code optimization passes - -==== Long-Term (1.0 Release) - -* 🌟 *NVIDIA CUDA:* GPU acceleration -* 🌟 *Apple Metal:* GPU acceleration -* 🌟 *Vulkan Compute:* Cross-platform GPU -* 🌟 *GDDR5/6/7:* GPU memory support -* 🌟 *HBM Support:* High bandwidth memory -* 🌟 *Unified Memory:* CPU/GPU shared memory -* 🌟 *MINIX Support:* Custom runtime port - -''''' - -=== 📋 Deployment Checklist - -==== Currently Supported ✅ - -* [x] WebAssembly compilation -* [x] Browser deployment (iOS/Android/Desktop) -* [x] Node.js/WASI deployment -* [x] Basic arithmetic operations -* [x] Memory management (manual) -* [x] Function calls and control flow -* [x] Linear memory model - -==== Not Yet Supported ❌ - -* [ ] Native binary compilation -* [ ] GPU acceleration (CUDA/WebGPU/Vulkan) -* [ ] SIMD vector operations -* [ ] Multithreading -* [ ] Automatic garbage collection -* [ ] GDDR5/6/7 memory access -* [ ] HBM memory access -* [ ] Unified CPU/GPU memory -* [ ] MINIX deployment -* [ ] Direct hardware access - -==== Planned for Future Releases 🔄 - -* [ ] Native backend (Alpha-2) -* [ ] GPU backend (Beta) -* [ ] Audio backend (Beta) -* [ ] ARM64 native (Alpha-2) -* [ ] RISC-V native (Alpha-2) -* [ ] WASM SIMD (Alpha-2) -* [ ] WASM threads (Alpha-2) -* [ ] Memory optimizer (Beta) -* [ ] GPU memory support (1.0) - -''''' - -=== 🎓 Recommendations - -==== For Game Developers (Current) - -.... -✅ Use WASM for browser-based games -✅ Target iOS/Android via Web browsers -✅ Use Web Audio API for sound -✅ Use WebGL/WebGPU for graphics -❌ Avoid GPU compute (not available) -❌ Avoid native compilation (not available) -.... - -==== For Systems Programmers (Current) - -.... -✅ Use WASM for portable code -✅ Use WASI for system integration -✅ Manual memory management required -❌ No low-level hardware access -❌ No GPU acceleration -❌ No SIMD optimization -.... - -==== For Future-Proof Development - -.... -🔄 Plan for native backend (Alpha-2) -🔄 Design for GPU acceleration (Beta) -🔄 Consider SIMD for performance (Alpha-2) -🔄 Prepare for multithreading (Alpha-2) -🔄 Design memory-efficient data structures -.... - -''''' - -=== 🔒 Conclusion - -*Current State:* AffineScript compiles to *WebAssembly 1.0* as its -primary target, with experimental Julia code generation. The compiler is -*deployable on iOS/Android via browsers*, *RISC-V via Wasmtime*, and -*ARM/AMD via WASM*, but has *no native compilation*, *no GPU support*, -and *no GDDR/HBM memory access*. - -*Strengths:* - ✅ Excellent browser support - ✅ Portable across -platforms - ✅ Memory-safe by design - ✅ Type-safe compilation - -*Limitations:* - ❌ No GPU acceleration - ❌ No native binary output - -❌ Limited to 1MB memory by default - ❌ No SIMD or parallelism - ❌ No -GDDR5/6/7 or HBM support - -*Future:* The backend architecture has been designed to support all -these features, with processor backends and kernel stubs in place. -Implementation will proceed through Alpha-2, Beta, and 1.0 releases. - -*Recommendation:* For current development, use WASM deployment targeting -browsers. For future-proof design, plan for native and GPU backends -coming in later releases. - -SPDX-License-Identifier: CC-BY-SA-4.0 SPDX-FileCopyrightText: 2026 -Jonathan D.A. Jewell diff --git a/affinescript/CONTRIBUTING.adoc b/affinescript/CONTRIBUTING.adoc deleted file mode 100644 index 6784343..0000000 --- a/affinescript/CONTRIBUTING.adoc +++ /dev/null @@ -1,109 +0,0 @@ -== Clone the repository - -git clone https://github.com/hyperpolymath/proof-burrower.git cd -proof-burrower - -== Using Nix (recommended for reproducibility) - -nix develop - -== Or using toolbox/distrobox - -toolbox create proof-burrower-dev toolbox enter proof-burrower-dev # -Install dependencies manually - -== Verify setup - -just check # or: cargo check / mix compile / etc. just test # Run test -suite - -.... - -### Repository Structure -.... - -proof-burrower/ ├── src/ # Source code (Perimeter 1-2) ├── lib/ # -Library code (Perimeter 1-2) ├── extensions/ # Extensions (Perimeter 2) -├── plugins/ # Plugins (Perimeter 2) ├── tools/ # Tooling (Perimeter 2) -├── docs/ # Documentation (Perimeter 3) │ ├── architecture/ # ADRs, -specs (Perimeter 2) │ └── proposals/ # RFCs (Perimeter 3) ├── examples/ -# Examples (Perimeter 3) ├── spec/ # Spec tests (Perimeter 3) ├── tests/ -# Test suite (Perimeter 2-3) ├── .well-known/ # Protocol files -(Perimeter 1-3) ├── .github/ # GitHub config (Perimeter 1) │ ├── -ISSUE_TEMPLATE/ │ └── workflows/ ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md -├── CONTRIBUTING.md # This file ├── GOVERNANCE.md ├── LICENSE ├── -MAINTAINERS.md ├── README.adoc ├── SECURITY.md ├── flake.nix # Nix flake -(Perimeter 1) └── Justfile # Task runner (Perimeter 1) - -.... - ---- - -## How to Contribute - -### Reporting Bugs - -**Before reporting**: -1. Search existing issues -2. Check if it's already fixed in `main` -3. Determine which perimeter the bug affects - -**When reporting**: - -Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: - -- Clear, descriptive title -- Environment details (OS, versions, toolchain) -- Steps to reproduce -- Expected vs actual behaviour -- Logs, screenshots, or minimal reproduction - -### Suggesting Features - -**Before suggesting**: -1. Check the [roadmap](ROADMAP.md) if available -2. Search existing issues and discussions -3. Consider which perimeter the feature belongs to - -**When suggesting**: - -Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) and include: - -- Problem statement (what pain point does this solve?) -- Proposed solution -- Alternatives considered -- Which perimeter this affects - -### Your First Contribution - -Look for issues labelled: - -- [`good first issue`](https://github.com/hyperpolymath/proof-burrower/labels/good%20first%20issue) — Simple Perimeter 3 tasks -- [`help wanted`](https://github.com/hyperpolymath/proof-burrower/labels/help%20wanted) — Community help needed -- [`documentation`](https://github.com/hyperpolymath/proof-burrower/labels/documentation) — Docs improvements -- [`perimeter-3`](https://github.com/hyperpolymath/proof-burrower/labels/perimeter-3) — Community sandbox scope - ---- - -## Development Workflow - -### Branch Naming -.... - -docs/short-description # Documentation (P3) test/what-added # Test -additions (P3) feat/short-description # New features (P2) -fix/issue-number-description # Bug fixes (P2) refactor/what-changed # -Code improvements (P2) security/what-fixed # Security fixes (P1-2) - -.... - -### Commit Messages - -We follow [Conventional Commits](https://www.conventionalcommits.org/): -.... - -(): - -{empty}[optional body] - -{empty}[optional footer] diff --git a/affinescript/CONTROLS-REFERENCE.adoc b/affinescript/CONTROLS-REFERENCE.adoc deleted file mode 100644 index 7efdf72..0000000 --- a/affinescript/CONTROLS-REFERENCE.adoc +++ /dev/null @@ -1,587 +0,0 @@ -== AffineScript Game Controls Reference - -=== 🎮 Basic Movement Controls - -==== Standard Navigation - -.... -┌─────────────────────────────────────┐ -│ AFFINESCRIPT CONTROLS │ -├─────────────────┬───────────────────┤ -│ MOVEMENT │ ACTIONS │ -├─────────────────┼───────────────────┤ -│ ← → │ Turn Left/Right │ -│ ↑ ↓ │ Accelerate │ -│ │ Forward/Backward │ -├─────────────────┼───────────────────┤ -│ P │ Periscope Mode │ -│ E │ Embark/Land │ -│ M │ Disembark (Water)│ -│ TAB │ Eject (Air) │ -│ SPACE │ Fire Torpedo │ -│ ENTER │ Fire Missile │ -│ SHIFT │ Depth Charge │ -├─────────────────┼───────────────────┤ -│ J L │ Strafe Left/Right│ -│ (on land) │ │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🌊 Submarine Controls - -==== Underwater Navigation - -* *← → Arrow Keys*: Turn submarine left/right -* *↑ ↓ Arrow Keys*: Accelerate forward/backward -* *P*: Toggle periscope mode (invisible to surface detection) -** Works only when sitting in water -** Toggle on/off with same key -* *M*: Disembark as deep sea diver -** Must be underwater -** Equips diving gear automatically - -==== Combat - -* *SPACE*: Fire torpedo -** Limited ammunition -** Straight-line trajectory -** Effective against ships and subs -* *SHIFT*: Drop depth charge -** Anti-submarine weapon -** Sinks to maximum depth -** Area effect damage - -''''' - -=== 🚀 Spaceflight Controls - -==== Atmospheric Flight - -* Maintain *at least 88 mph* (142 km/h) -* Make *sharp turn upward* at top of screen -* Transition to space mode automatically - -==== Space Navigation - -* *← → ↑ ↓*: Standard movement -* *1-9*: Autopilot to planets -** *1*: Mercury -** *2*: Venus -** *3*: Earth -** *4*: Mars -** *5*: Jupiter -** *6*: Saturn -** *7*: Uranus -** *8*: Neptune -** *9*: Pluto (if included) -* *ALT GR (Right Alt)*: Warp speed -** Opens galaxy map -** Enables interstellar travel -** Consumes fuel rapidly - -==== Planetary Approach - -* Navigate to planet vicinity -* Press *E* for interaction menu: -** *Enter*: Land on planet -** *Orbit*: Establish stable orbit -** *Depart*: Leave planetary system - -==== Space Hazards - -* *⚠️ Sun*: Instant death on contact -* *⚠️ Asteroids*: Collision damage -* *⚠️ Black Holes*: Irreversible pull -* *⚠️ Solar Flares*: Temporary system damage - -''''' - -=== 🌍 Land Vehicle Controls - -==== Ground Movement - -* *← → ↑ ↓*: Standard movement -* *E*: Embark/Dembark -** Board nearby vehicles -** Exit current vehicle -** Enter buildings -* *J*: Strafe left -* *L*: Strafe right -* *SPACE*: Primary weapon (when implemented) -* *ENTER*: Secondary weapon (when implemented) - -''''' - -=== 🛡️ Combat Controls (Planned) - -==== Current Implementation - -.... -┌─────────────────────────────────────┐ -│ CURRENT COMBAT │ -├─────────────────┬───────────────────┤ -│ WEAPON │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Torpedo │ SPACE │ -│ Missile │ ENTER │ -│ Depth Charge │ SHIFT │ -├─────────────────┼───────────────────┤ -│ Targeting │ Mouse │ -│ Fire │ Left Click │ -│ Special │ Right Click │ -└─────────────────┴───────────────────┘ -.... - -==== Planned Features - -* *T*: Toggle targeting system -* *R*: Reload weapons -* *Q*: Cycle weapons -* *F*: Use special ability -* *C*: Change camera view -* *V*: Toggle HUD display - -''''' - -=== 🎮 Vehicle-Specific Controls - -==== Submarine - -.... -┌─────────────────────────────────────┐ -│ SUBMARINE CONTROLS │ -├─────────────────┬───────────────────┤ -│ ACTION │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Dive │ ↓ (hold) │ -│ Surface │ ↑ (hold) │ -│ Periscope │ P │ -│ Silent Running │ S │ -│ Sonar │ O │ -│ Torpedo │ SPACE │ -│ Mine │ N │ -│ Eject │ TAB │ -└─────────────────┴───────────────────┘ -.... - -==== Aircraft - -.... -┌─────────────────────────────────────┐ -│ AIRCRAFT CONTROLS │ -├─────────────────┬───────────────────┤ -│ ACTION │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Ascend │ ↑ │ -│ Descend │ ↓ │ -│ Roll Left │ Q │ -│ Roll Right │ E │ -│ Afterburner │ SHIFT │ -│ Missile │ ENTER │ -│ Eject │ TAB │ -│ Landing Gear │ G │ -└─────────────────┴───────────────────┘ -.... - -==== Spacecraft - -.... -┌─────────────────────────────────────┐ -│ SPACECRAFT CONTROLS │ -├─────────────────┬───────────────────┤ -│ ACTION │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Thrust │ ↑ │ -│ Reverse │ ↓ │ -│ Yaw Left │ ← │ -│ Yaw Right │ → │ -│ Warp Drive │ ALT GR │ -│ Shields │ H │ -│ Target Lock │ T │ -│ Dock │ D │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🗺️ Navigation Reference - -==== Movement Modes - -.... -┌─────────────────────────────────────┐ -│ NAVIGATION MODES │ -├─────────────────┬───────────────────┤ -│ MODE │ CONTROLS │ -├─────────────────┼───────────────────┤ -│ Submarine │ Arrow Keys │ -│ Land Vehicle │ Arrow Keys + J/L│ -│ Aircraft │ Arrow Keys + Q/E│ -│ Spacecraft │ Arrow Keys │ -│ On Foot │ Arrow Keys │ -│ Diving │ WASD (planned) │ -└─────────────────┴───────────────────┘ -.... - -==== Transition Commands - -.... -┌─────────────────────────────────────┐ -│ TRANSITION COMMANDS │ -├─────────────────┬───────────────────┤ -│ FROM → TO │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Sub → Diver │ M (underwater) │ -│ Diver → Sub │ E (near sub) │ -│ Sub → Land │ E (near shore) │ -│ Land → Sub │ E (in water) │ -│ Any → Eject │ TAB (in air) │ -│ Atmosphere → │ 88 mph + ↑ │ -│ Space │ │ -│ Space → Planet │ 1-9 + E │ -│ Planet → Orbit │ E → Orbit │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🎯 Targeting & Combat - -==== Current Weapon Systems - -.... -┌─────────────────────────────────────┐ -│ WEAPON SYSTEMS │ -├─────────────────┬───────────────────┤ -│ WEAPON │ DETAILS │ -├─────────────────┼───────────────────┤ -│ Torpedo │ SPACE │ -│ │ - Straight line │ -│ │ - Underwater only │ -│ │ - Limited range │ -├─────────────────┼───────────────────┤ -│ Missile │ ENTER │ -│ │ - Air/space only │ -│ │ - Heat-seeking │ -│ │ - Limited ammo │ -├─────────────────┼───────────────────┘ -│ Depth Charge │ SHIFT │ -│ │ - Anti-submarine │ -│ │ - Sinks to bottom │ -│ │ - Area effect │ -└─────────────────┴───────────────────┘ -.... - -==== Planned Combat Features - -* *Laser*: Continuous beam weapon -* *Railgun*: High-velocity projectile -* *EM Pulse*: Disables electronics -* *Mine*: Deployable explosive -* *Decoy*: Distraction flare -* *ECM*: Electronic countermeasures - -''''' - -=== 🌌 Space Travel Guide - -==== Solar System Navigation - -.... -┌─────────────────────────────────────┐ -│ SPACE NAVIGATION │ -├─────────────────┬───────────────────┤ -│ DESTINATION │ CONTROL │ -├─────────────────┼───────────────────┤ -│ Mercury │ 1 │ -│ Venus │ 2 │ -│ Earth │ 3 │ -│ Mars │ 4 │ -│ Jupiter │ 5 │ -│ Saturn │ 6 │ -│ Uranus │ 7 │ -│ Neptune │ 8 │ -│ Pluto │ 9 │ -├─────────────────┼───────────────────┤ -│ Warp Speed │ ALT GR │ -│ Galaxy Map │ ALT GR (hold) │ -│ Solar Orbit │ O │ -│ Planet Orbit │ P │ -│ Dock │ D │ -└─────────────────┴───────────────────┘ -.... - -==== Interstellar Travel - -[arabic] -. *Reach orbit* of current planet -. *Press ALT GR* to engage warp drive -. *Hold ALT GR* to open galaxy map -. *Select destination* star system -. *Confirm travel* (fuel check) -. *Warp transition* animation -. *Arrive* in new system - -''''' - -=== ⚠️ Hazards & Warnings - -==== Environmental Hazards - -.... -┌─────────────────────────────────────┐ -│ ENVIRONMENTAL RISKS │ -├─────────────────┬───────────────────┤ -│ HAZARD │ EFFECT │ -├─────────────────┼───────────────────┤ -│ Sun Contact │ Instant death │ -│ Deep Space │ Oxygen depletion │ -│ Black Hole │ Irreversible pull │ -│ Asteroid Field │ Collision damage │ -│ Solar Flare │ Systems failure │ -│ Radiation Belt │ Health damage │ -│ Extreme Depth │ Pressure damage │ -│ Surface │ Detection risk │ -└─────────────────┴───────────────────┘ -.... - -==== Combat Hazards - -.... -┌─────────────────────────────────────┐ -│ COMBAT RISKS │ -├─────────────────┬───────────────────┤ -│ THREAT │ COUNTERMEASURE │ -├─────────────────┼───────────────────┤ -│ Torpedo │ Evasive maneuvers│ -│ Missile │ Chaff flares │ -│ Depth Charge │ Deep dive │ -│ Sonar Ping │ Silent running │ -│ Radar Lock │ ECM │ -│ Homing Weapon │ Decoy │ -│ Boarding Party │ Security lock │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🎮 Control Schemes Comparison - -==== Default Scheme - -.... -┌─────────────────────────────────────┐ -│ DEFAULT CONTROLS │ -├─────────────────┬───────────────────┤ -│ ACTION │ KEY │ -├─────────────────┼───────────────────┤ -│ Move │ Arrow Keys │ -│ Turn │ Arrow Keys │ -│ Accelerate │ Arrow Keys │ -│ Periscope │ P │ -│ Embark │ E │ -│ Disembark │ M (water) │ -│ Eject │ TAB (air) │ -│ Torpedo │ SPACE │ -│ Missile │ ENTER │ -│ Depth Charge │ SHIFT │ -│ Strafe │ J/L (land) │ -│ Warp │ ALT GR │ -│ Autopilot │ 1-9 │ -└─────────────────┴───────────────────┘ -.... - -==== Alternative Scheme (Planned) - -.... -┌─────────────────────────────────────┐ -│ ALTERNATIVE CONTROLS │ -├─────────────────┬───────────────────┤ -│ ACTION │ KEY │ -├─────────────────┼───────────────────┤ -│ Move │ WASD │ -│ Turn │ Mouse │ -│ Accelerate │ W/S │ -│ Periscope │ Middle Mouse │ -│ Embark │ F │ -│ Disembark │ G (water) │ -│ Eject │ C (air) │ -│ Torpedo │ Left Mouse │ -│ Missile │ Right Mouse │ -│ Depth Charge │ X │ -│ Strafe │ A/D (land) │ -│ Warp │ Shift+W │ -│ Autopilot │ F1-F9 │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🎓 Tips & Tricks - -==== Efficient Movement - -* *Combine turns* with acceleration for tighter maneuvers -* *Use periscope mode* when near surface threats -* *Maintain speed* for quick space transition -* *Plan orbits* before planetary approach - -==== Combat Strategies - -* *Lead targets* when firing torpedoes -* *Use terrain* for cover against sonar -* *Depth charges* work best in shallow water -* *Missiles* are heat-seeking - use flares - -==== Space Travel - -* *88 mph minimum* for space transition -* *Sharp upward turn* required -* *Autopilot numbers* correspond to planetary order -* *Warp drive* consumes fuel quickly - -==== Emergency Procedures - -* *TAB ejects* in air emergencies -* *M disembarks* underwater -* *E embarks* on land -* *P toggles* stealth modes - -''''' - -=== 🔧 Control Customization (Future) - -==== Planned Features - -* *Key binding* configuration -* *Controller support* (gamepad, joystick) -* *Sensitivity adjustment* for analog controls -* *Inverted controls* option -* *Vibration feedback* settings -* *Haptic feedback* integration - -==== Configuration File - -[source,json] ----- -{ - "controls": { - "movement": { - "forward": "ArrowUp", - "backward": "ArrowDown", - "left": "ArrowLeft", - "right": "ArrowRight" - }, - "actions": { - "periscope": "P", - "embark": "E", - "disembark": "M", - "eject": "Tab", - "torpedo": "Space", - "missile": "Enter", - "depth_charge": "Shift" - }, - "sensitivity": 0.75, - "invert_y": false, - "vibration": true - } -} ----- - -''''' - -=== 📊 Control Reference Cheat Sheet - -.... -┌─────────────────────────────────────────────────────┐ -│ AFFINESCRIPT QUICK REFERENCE │ -├─────────────────────────────────────────────────────┤ -│ │ -│ MOVEMENT: Arrow Keys (← ↑ → ↓) │ -│ PERISCOPE: P (toggle stealth) │ -│ EMBARK: E (land/vehicle) │ -│ DISEMBARK: M (water) / TAB (air) │ -│ TORPEDO: SPACE │ -│ MISSILE: ENTER │ -│ DEPTH CHARGE: SHIFT │ -│ STRAFE: J (left) L (right) - land only │ -│ SPACE: 88 mph + ↑ at screen top │ -│ AUTOPILOT: 1-9 (planets from sun) │ -│ WARP: ALT GR (right alt) │ -│ │ -├─────────────────────────────────────────────────────┤ -│ SPACE NAVIGATION: │ -│ 1=Mercury, 2=Venus, 3=Earth, 4=Mars, 5=Jupiter │ -│ 6=Saturn, 7=Uranus, 8=Neptune, 9=Pluto │ -│ E=Interact (Enter/Orbit/Depart) │ -│ ALT GR=Warp/Galaxy Map │ -│ │ -└─────────────────────────────────────────────────────┘ -.... - -''''' - -=== 🎯 Future Control Enhancements - -==== Planned Additions - -* *Gamepad Support*: Full controller mapping -* *Joystick Support*: Flight stick compatibility -* *Touch Controls*: Mobile device interface -* *Voice Commands*: Experimental voice control -* *Gesture Controls*: Motion sensing -* *Eye Tracking*: Gaze-based interaction -* *Haptic Feedback*: Enhanced tactile response -* *Adaptive Controls*: Context-sensitive bindings - -==== Roadmap - -.... -┌─────────────────────────────────────┐ -│ CONTROL FEATURE ROADMAP │ -├─────────────────┬───────────────────┤ -│ VERSION │ FEATURES │ -├─────────────────┼───────────────────┤ -│ Alpha-1 │ Basic controls │ -│ │ Keyboard only │ -│ │ Fixed bindings │ -├─────────────────┼───────────────────┤ -│ Alpha-2 │ Key rebinding │ -│ │ Gamepad support │ -│ │ Sensitivity adj. │ -├─────────────────┼───────────────────┤ -│ Beta │ Controller presets│ -│ │ Touch controls │ -│ │ Haptic feedback │ -├─────────────────┼───────────────────┤ -│ 1.0 │ Voice commands │ -│ │ Gesture controls │ -│ │ Eye tracking │ -│ │ Adaptive controls│ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== 🔒 Summary - -*Current Controls:* - Arrow keys for movement - P for periscope/stealth -- E/M for embark/disembark - SPACE/ENTER/SHIFT for weapons - 88 mph + ↑ -for space transition - 1-9 for planetary autopilot - ALT GR for warp -speed - -*Key Principle:* Simple, intuitive controls with clear separation -between movement and action keys. - -*Future:* Expand to full controller support, customization, and -alternative input methods. - -''''' - -*Last Updated:* March 31, 2026 *Version:* Alpha-1 *Status:* Basic -controls implemented, combat system planned - -SPDX-License-Identifier: CC-BY-SA-4.0 SPDX-FileCopyrightText: 2026 -Jonathan D.A. Jewell diff --git a/affinescript/DAMAGE-SYSTEM.adoc b/affinescript/DAMAGE-SYSTEM.adoc deleted file mode 100644 index 5703541..0000000 --- a/affinescript/DAMAGE-SYSTEM.adoc +++ /dev/null @@ -1,1109 +0,0 @@ -== AffineScript Submarine Damage System - -=== 🚨 Critical Damage Information - -*For Players and AI Systems:* This document explains how submarine -damage affects gameplay and what happens when different parts of your -submarine are damaged. - -''''' - -=== 🛡️ Submarine Component Damage Effects - -==== Visual Damage Indicators - -.... -┌─────────────────────────────────────────────────────┐ -│ SUBMARINE DAMAGE INDICATORS │ -├─────────────────────────────────────────────────────┤ -│ │ -│ FRONT SHIELD: [■■■■■■■■■■] 100% │ -│ DAMAGE METER: [□□□□□□□□□□] 0% │ -│ │ -│ █████████████████████████████████████████████████ │ -│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ -│ │ -│ [FRONT] [WINGS] [MIDDLE] [BACK] [PERISCOPE] │ -│ │ -└─────────────────────────────────────────────────────┘ -.... - -''''' - -=== 🔧 Damage System Breakdown - -==== 1. *Front Shield Damage* - -*Indicator:* Shield integrity meter at top of HUD - -*Effects:* - -.... -┌─────────────────────────────────────┐ -│ FRONT SHIELD DAMAGE │ -├─────────────────┬───────────────────┤ -│ DAMAGE LEVEL │ EFFECT │ -├─────────────────┼───────────────────┤ -│ 0-20% │ Minor handling │ -│ │ issues │ -├─────────────────┼───────────────────┤ -│ 21-50% │ Moderate control │ -│ │ erraticism │ -├─────────────────┼───────────────────┤ -│ 51-80% │ Severe handling │ -│ │ difficulties │ -├─────────────────┼───────────────────┤ -│ 81-100% │ Critical failure │ -│ │ - Front end │ -│ │ takes direct │ -│ │ damage │ -│ │ - Controls │ -│ │ become highly │ -│ │ erratic │ -│ │ - Turning │ -│ │ radius │ -│ │ increases │ -│ │ - Response time │ -│ │ degraded │ -└─────────────────┴───────────────────┘ -.... - -*Gameplay Impact:* - Submarine becomes harder to control - Turning -requires more anticipation - Precision maneuvers become difficult - -*Front end takes direct damage after shield failure* - -''''' - -==== 2. *Front End Damage* (After Shield Failure) - -*Indicator:* Red flashing on front section of submarine - -*Effects:* - -.... -┌─────────────────────────────────────┐ -│ FRONT END DAMAGE │ -├─────────────────┬───────────────────┤ -│ DAMAGE LEVEL │ EFFECT │ -├─────────────────┼───────────────────┤ -│ Any damage │ Controls become │ -│ │ erratic │ -├─────────────────┼───────────────────┤ -│ 30%+ │ - Turning circle │ -│ │ increases 50% │ -│ │ - Response lag │ -│ │ +200ms │ -│ │ - Maximum speed │ -│ │ reduced 15% │ -├─────────────────┼───────────────────┤ -│ 60%+ │ - Turning circle │ -│ │ increases 100%│ -│ │ - Response lag │ -│ │ +500ms │ -│ │ - Maximum speed │ -│ │ reduced 30% │ -│ │ - Random yaws │ -│ │ during turns │ -├─────────────────┼───────────────────┤ -│ 90%+ │ - Controls │ -│ │ barely │ -│ │ responsive │ -│ │ - Submarine │ -│ │ drifts │ -│ │ unpredictably │ -│ │ - Emergency │ -│ │ surfacing │ -│ │ recommended │ -└─────────────────┴───────────────────┘ -.... - -*AI Considerations:* - Increase prediction algorithms for turning - -Reduce maximum speed calculations - Add randomness factor to movement - -Implement emergency surfacing protocol - -''''' - -==== 3. *Wing Damage* - -*Indicator:* Damage indicators on port/starboard sides - -*Effects:* - -.... -┌─────────────────────────────────────┐ -│ WING DAMAGE │ -├─────────────────┬───────────────────┤ -│ DAMAGE LEVEL │ EFFECT │ -├─────────────────┼───────────────────┤ -│ 0-30% │ Reduced climb │ -│ │ rate │ -├─────────────────┼───────────────────┤ -│ 31-60% │ - Climb rate │ -│ │ -40% │ -│ │ - Turn radius │ -│ │ +30% │ -│ │ - Banking │ -│ │ less precise │ -├─────────────────┼───────────────────┤ -│ 61-90% │ - Climb rate │ -│ │ -70% │ -│ │ - Turn radius │ -│ │ +60% │ -│ │ - Severe roll │ -│ │ instability │ -│ │ - Risk of │ -│ │ uncontrolled │ -│ │ spin │ -├─────────────────┼───────────────────┤ -│ 91-100% │ - WINGS │ -│ │ DESTROYED │ -│ │ - NO FLIGHT │ -│ │ CAPABILITY │ -│ │ - Submarine │ -│ │ sinks │ -│ │ - Emergency │ -│ │ ballast │ -│ │ required │ -└─────────────────┴───────────────────┘ -.... - -*Gameplay Impact:* - Reduced maneuverability in air - Difficulty -maintaining altitude - Increased stall risk - *Complete wing destruction -= no flight capability* - -*AI Considerations:* - Reduce maximum bank angle - Increase stall speed -calculations - Add roll damping compensation - Implement emergency -ballast procedures - -''''' - -==== 4. *Back End Damage* - -*Indicator:* Damage indicators on rear section - -*Effects:* - -.... -┌─────────────────────────────────────┐ -│ BACK END DAMAGE │ -├─────────────────┬───────────────────┤ -│ DAMAGE LEVEL │ EFFECT │ -├─────────────────┼───────────────────┤ -│ 0-25% │ Reduced speed │ -├─────────────────┼───────────────────┤ -│ 26-50% │ - Maximum speed │ -│ │ -20% │ -│ │ - Acceleration │ -│ │ -15% │ -│ │ - Fuel │ -│ │ efficiency │ -│ │ -10% │ -├─────────────────┼───────────────────┤ -│ 51-75% │ - Maximum speed │ -│ │ -40% │ -│ │ - Acceleration │ -│ │ -30% │ -│ │ - Fuel │ -│ │ efficiency │ -│ │ -25% │ -│ │ - Maneuvering │ -│ │ thrusters │ -│ │ -50% │ -├─────────────────┼───────────────────┤ -│ 76-100% │ - Maximum speed │ -│ │ -60% │ -│ │ - Acceleration │ -│ │ -50% │ -│ │ - Fuel │ -│ │ efficiency │ -│ │ -40% │ -│ │ - Maneuvering │ -│ │ thrusters │ -│ │ -80% │ -│ │ - Reverse │ -│ │ capability │ -│ │ lost │ -└─────────────────┴───────────────────┘ -.... - -*Gameplay Impact:* - Slower movement in water and air - Reduced -acceleration - Poor fuel economy - Difficulty in precise maneuvering - -*Severe damage prevents reverse movement* - -*AI Considerations:* - Reduce maximum speed parameters - Increase fuel -consumption calculations - Add thrust vector randomization - Implement -emergency propulsion protocols - -''''' - -==== 5. *Middle Section Damage* - -*Indicator:* Central damage warning - -*Effects:* - -.... -┌─────────────────────────────────────┐ -│ MIDDLE SECTION DAMAGE │ -├─────────────────┬───────────────────┤ -│ DAMAGE LEVEL │ EFFECT │ -├─────────────────┼───────────────────┤ -│ 0-20% │ Reduced │ -│ │ buoyancy │ -├─────────────────┼───────────────────┤ -│ 21-40% │ - Buoyancy │ -│ │ -15% │ -│ │ - Sink rate │ -│ │ +10% │ -│ │ - Stability │ -│ │ -5% │ -├─────────────────┼───────────────────┤ -│ 41-60% │ - Buoyancy │ -│ │ -30% │ -│ │ - Sink rate │ -│ │ +25% │ -│ │ - Stability │ -│ │ -15% │ -│ │ - PERISCOPE │ -│ │ MODE │ -│ │ DISABLED │ -├─────────────────┼───────────────────┤ -│ 61-80% │ - Buoyancy │ -│ │ -50% │ -│ │ - Sink rate │ -│ │ +50% │ -│ │ - Stability │ -│ │ -30% │ -│ │ - PERISCOPE │ -│ │ MODE │ -│ │ DISABLED │ -│ │ - Constant │ -│ │ downward │ -│ │ drift │ -├─────────────────┼───────────────────┤ -│ 81-100% │ - Buoyancy │ -│ │ -75% │ -│ │ - Sink rate │ -│ │ +100% │ -│ │ - Stability │ -│ │ -50% │ -│ │ - PERISCOPE │ -│ │ MODE │ -│ │ DISABLED │ -│ │ - Severe │ -│ │ downward │ -│ │ pull │ -│ │ - SPACE FLIGHT │ -│ │ IMPOSSIBLE │ -│ │ - Emergency │ -│ │ ballast │ -│ │ dump │ -│ │ required │ -└─────────────────┴───────────────────┘ -.... - -*Gameplay Impact:* - Submarine sinks faster - Difficulty maintaining -depth - Periscope mode unavailable - *Space flight impossible* until -repaired - Constant downward drift - -*AI Considerations:* - Increase ballast calculations - Add constant -downward velocity - Disable periscope functionality - Block space flight -attempts - Implement emergency ballast dump - -''''' - -=== 🔧 Damage Progression System - -==== Damage Accumulation - -.... -FRONT SHIELD (100%) → FRONT END → WINGS → BACK → MIDDLE -.... - -==== Repair Priorities - -.... -1. MIDDLE SECTION (restores space flight, buoyancy) -2. FRONT SHIELD (prevents front end damage) -3. WINGS (restores flight capability) -4. BACK END (improves speed) -5. FRONT END (improves control) -.... - -''''' - -=== 🎮 Player Strategies for Damaged Submarines - -==== Front Shield Damaged - -[source,markdown] ----- -✅ Use gentle, anticipatory turns -✅ Reduce speed to maintain control -✅ Avoid rapid direction changes -✅ Plan wider turning radius -✅ Consider emergency repairs ----- - -==== Front End Damaged - -[source,markdown] ----- -✅ Reduce speed by 30-50% -✅ Use small, gradual corrections -✅ Avoid tight spaces -✅ Plan turns well in advance -✅ Consider surfacing if severe ----- - -==== Wings Damaged - -[source,markdown] ----- -✅ Avoid sharp bank turns -✅ Maintain level flight -✅ Reduce altitude changes -✅ Use gentle climbs/descents -✅ Prepare for emergency landing ----- - -==== Back End Damaged - -[source,markdown] ----- -✅ Accept reduced speed -✅ Plan longer travel times -✅ Avoid situations requiring reverse -✅ Monitor fuel consumption -✅ Consider conservative routes ----- - -==== Middle Section Damaged - -[source,markdown] ----- -✅ Activate emergency ballast -✅ Seek shallow waters -✅ Avoid deep dives -✅ Prioritize repairs -✅ Space flight impossible ----- - -''''' - -=== 🤖 AI System Considerations - -==== Damage Detection - -[source,python] ----- -# Pseudocode for AI damage detection -def detect_damage(submarine): - if submarine.front_shield < 100: - # Front shield damaged - increase_turn_prediction() - add_control_randomness() - - if submarine.front_damage > 0: - # Front end damaged - reduce_max_speed(0.3) - increase_turn_radius(0.5) - add_response_lag(200) - - if submarine.wing_damage > 30: - # Wings damaged - reduce_climb_rate(0.4) - increase_turn_radius(0.3) - add_roll_instability() - - if submarine.back_damage > 25: - # Back end damaged - reduce_max_speed(0.2) - reduce_acceleration(0.15) - reduce_fuel_efficiency(0.1) - - if submarine.middle_damage > 0: - # Middle damaged - reduce_buoyancy(0.15) - increase_sink_rate(0.1) - disable_periscope() - disable_space_flight() - - if submarine.middle_damage > 40: - add_constant_downward_drift() ----- - -==== AI Behavior Adjustments - -[source,python] ----- -def adjust_ai_behavior(submarine): - # Front damage adjustments - if submarine.front_damage > 50: - submarine.ai.turn_prediction += 0.5 - submarine.ai.response_lag = 0.3 - submarine.ai.max_bank_angle = 20 - - # Wing damage adjustments - if submarine.wing_damage > 60: - submarine.ai.max_climb_angle = 15 - submarine.ai.stall_speed += 10 - submarine.ai.roll_damping = 0.7 - - # Back damage adjustments - if submarine.back_damage > 50: - submarine.ai.max_speed = 0.6 - submarine.ai.acceleration = 0.7 - submarine.ai.fuel_efficiency = 0.6 - - # Middle damage adjustments - if submarine.middle_damage > 20: - submarine.ai.buoyancy = 0.85 - submarine.ai.sink_rate = 1.1 - submarine.ai.can_use_periscope = False - submarine.ai.can_space_flight = False - - if submarine.middle_damage > 60: - submarine.ai.constant_sink = 0.5 # units per second ----- - -''''' - -=== 🛠️ Repair Systems - -==== Emergency Repairs - -.... -┌─────────────────────────────────────┐ -│ EMERGENCY REPAIRS │ -├─────────────────┬───────────────────┤ -│ COMPONENT │ METHOD │ -├─────────────────┼───────────────────┤ -│ Front Shield │ Shield Generator │ -│ │ (50% capacity) │ -├─────────────────┼───────────────────┤ -│ Front End │ Hull Patching │ -│ │ (Temporary) │ -├─────────────────┼───────────────────┤ -│ Wings │ Structural Brace │ -│ │ (Reduces flight │ -│ │ capability) │ -├─────────────────┼───────────────────┤ -│ Back End │ Engine Bypass │ -│ │ (50% thrust) │ -├─────────────────┼───────────────────┤ -│ Middle │ Ballast Adjust │ -│ │ (Temporary) │ -│ │ Emergency Pump │ -└─────────────────┴───────────────────┘ -.... - -==== Full Repairs - -.... -┌─────────────────────────────────────┐ -│ FULL REPAIRS │ -├─────────────────┬───────────────────┤ -│ COMPONENT │ REQUIREMENTS │ -├─────────────────┼───────────────────┤ -│ Front Shield │ Dry Dock │ -│ │ 200 Steel │ -│ │ 100 Energy │ -├─────────────────┼───────────────────┤ -│ Front End │ Dry Dock │ -│ │ 300 Steel │ -│ │ 150 Energy │ -├─────────────────┼───────────────────┤ -│ Wings │ Dry Dock │ -│ │ 400 Steel │ -│ │ 200 Energy │ -├─────────────────┼───────────────────┤ -│ Back End │ Dry Dock │ -│ │ 350 Steel │ -│ │ 175 Energy │ -├─────────────────┼───────────────────┤ -│ Middle │ Dry Dock │ -│ │ 500 Steel │ -│ │ 250 Energy │ -│ │ Periscope Module │ -│ │ Space Flight │ -│ │ Certification │ -└─────────────────┴───────────────────┘ -.... - -''''' - -=== ⚠️ Critical Damage States - -==== Imminent Failure Warnings - -.... -┌─────────────────────────────────────┐ -│ CRITICAL DAMAGE STATES │ -├─────────────────┬───────────────────┤ -│ COMPONENT │ WARNING │ -├─────────────────┼───────────────────┤ -│ Front Shield │ "SHIELD CRITICAL"│ -│ <20% │ "FRONT EXPOSED" │ -├─────────────────┼───────────────────┤ -│ Front End │ "CONTROLS FAILING"│ -│ >80% │ "EMERGENCY SURFACE"│ -├─────────────────┼───────────────────┤ -│ Wings │ "FLIGHT CRITICAL"│ -│ >90% │ "PREPARE FOR │ -│ │ IMPACT" │ -├─────────────────┼───────────────────┤ -│ Back End │ "PROPULSION │ -│ >75% │ FAILURE" │ -│ │ "STRANDED RISK" │ -├─────────────────┼───────────────────┤ -│ Middle │ "SINKING IMMINENT"│ -│ >80% │ "ABANDON SHIP" │ -│ │ "SPACE FLIGHT │ -│ │ IMPOSSIBLE" │ -└─────────────────┴───────────────────┘ -.... - -==== AI Emergency Protocols - -[source,python] ----- -def critical_damage_response(submarine): - if submarine.front_shield < 20: - # Front shield critical - submarine.ai.avoid_combat() - submarine.ai.seek_repair() - submarine.ai.reduce_speed(0.5) - - if submarine.front_damage > 80: - # Front end critical - submarine.ai.emergency_surface() - submarine.ai.avoid_all_combat() - submarine.ai.signal_distress() - - if submarine.wing_damage > 90: - # Wings critical - submarine.ai.prepare_for_crash() - submarine.ai.deploy_parachute() - submarine.ai.eject_crew() - - if submarine.back_damage > 75: - # Back end critical - submarine.ai.anchor_submarine() - submarine.ai.request_tow() - submarine.ai.activate_beacon() - - if submarine.middle_damage > 80: - # Middle critical - submarine.ai.abandon_ship() - submarine.ai.deploy_lifeboats() - submarine.ai.transmit_distress() ----- - -''''' - -=== 📊 Damage Effect Summary Table - -.... -┌─────────────────────────────────────────────────────────────┐ -│ SUBMARINE DAMAGE EFFECTS SUMMARY │ -├─────────────────┬───────────────────┬───────────────────────┤ -│ COMPONENT │ DAMAGE LEVEL │ PRIMARY EFFECTS │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front Shield │ 0-100% │ Controls become │ -│ │ │ erratic when failed │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front End │ Any damage │ - Erratic controls │ -│ │ │ - Increased turn │ -│ │ │ radius │ -│ │ │ - Reduced max speed │ -├─────────────────┬───────────────────┼───────────────────────┤ -│ Wings │ 30%+ │ - Reduced climb rate │ -│ │ │ - Increased turn │ -│ │ │ radius │ -│ │ │ - Roll instability │ -│ │ │ - 90%+ = NO FLIGHT │ -├─────────────────┬───────────────────┼───────────────────────┤ -│ Back End │ 25%+ │ - Reduced max speed │ -│ │ │ - Reduced │ -│ │ │ acceleration │ -│ │ │ - Poor fuel │ -│ │ │ efficiency │ -│ │ │ - 75%+ = NO REVERSE │ -├─────────────────┬───────────────────┼───────────────────────┤ -│ Middle │ Any damage │ - Reduced buoyancy │ -│ │ │ - Increased sink │ -│ │ │ rate │ -│ │ │ - Periscope │ -│ │ │ DISABLED │ -│ │ │ - 20%+ = Constant │ -│ │ │ downward drift │ -│ │ │ - SPACE FLIGHT │ -│ │ │ IMPOSSIBLE │ -└─────────────────┴───────────────────┴───────────────────────┘ -.... - -''''' - -=== 🎯 Player Decision Flowchart - -.... -IS FRONT SHIELD DAMAGED? - │ - ├── YES → Avoid combat, seek repair - │ → Reduce speed, gentle turns - │ - └── NO → Proceed normally - │ - ├── IS FRONT END DAMAGED? - │ │ - │ ├── YES → Reduce speed 50% - │ │ → Avoid tight turns - │ │ → Consider surfacing - │ │ - │ └── NO → Continue - │ │ - │ ├── ARE WINGS DAMAGED? - │ │ │ - │ │ ├── YES → Avoid sharp turns - │ │ │ → Maintain level flight - │ │ │ → Prepare for landing - │ │ │ - │ │ └── NO → Continue - │ │ │ - │ │ ├── IS BACK END DAMAGED? - │ │ │ │ - │ │ │ ├── YES → Accept reduced speed - │ │ │ │ → Monitor fuel - │ │ │ │ → Avoid reverse - │ │ │ │ - │ │ │ └── NO → Continue - │ │ │ │ - │ │ │ ├── IS MIDDLE DAMAGED? - │ │ │ │ │ - │ │ │ │ ├── YES → Activate emergency ballast - │ │ │ │ │ → Seek shallow water - │ │ │ │ │ → Prioritize repairs - │ │ │ │ │ → SPACE FLIGHT IMPOSSIBLE - │ │ │ │ │ - │ │ │ │ └── NO → Full capability - │ │ │ │ - │ │ │ └── All systems operational - │ │ │ - │ │ └── Optimal performance - │ │ - │ └── Normal operations - │ - └── Standard procedures -.... - -''''' - -=== 🤖 AI Damage Response Matrix - -.... -┌─────────────────────────────────────────────────────────────┐ -│ AI DAMAGE RESPONSE PROTOCOLS │ -├─────────────────┬───────────────────┬───────────────────────┤ -│ COMPONENT │ DAMAGE LEVEL │ AI RESPONSE │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front Shield │ <50% │ Increase turn │ -│ │ │ prediction by 25% │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front Shield │ 50-80% │ Increase turn │ -│ │ │ prediction by 50% │ -│ │ │ Add 10% control │ -│ │ │ randomness │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front Shield │ >80% │ Increase turn │ -│ │ │ prediction by 75% │ -│ │ │ Add 25% control │ -│ │ │ randomness │ -│ │ │ Reduce max speed │ -│ │ │ by 30% │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front End │ 20-50% │ Reduce max speed │ -│ │ │ by 20% │ -│ │ │ Increase turn │ -│ │ │ radius by 30% │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front End │ 51-80% │ Reduce max speed │ -│ │ │ by 40% │ -│ │ │ Increase turn │ -│ │ │ radius by 60% │ -│ │ │ Add 15% yaw │ -│ │ │ instability │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Front End │ >80% │ Reduce max speed │ -│ │ │ by 60% │ -│ │ │ Increase turn │ -│ │ │ radius by 100% │ -│ │ │ Add 30% yaw │ -│ │ │ instability │ -│ │ │ Add 500ms │ -│ │ │ response lag │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Wings │ 30-60% │ Reduce climb rate │ -│ │ │ by 40% │ -│ │ │ Increase turn │ -│ │ │ radius by 30% │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Wings │ 61-90% │ Reduce climb rate │ -│ │ │ by 70% │ -│ │ │ Increase turn │ -│ │ │ radius by 60% │ -│ │ │ Add roll │ -│ │ │ instability │ -├─────────────────┼───────────────────┼───────────────────────┤ -│ Wings │ >90% │ DISABLE FLIGHT │ -│ │ │ Prepare for │ -│ │ │ impact │ -│ │ │ Deploy │ -│ │ │ parachute │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Back End │ 25-50% │ Reduce max speed │ -│ │ │ by 20% │ -│ │ │ Reduce │ -│ │ │ acceleration by │ -│ │ │ 15% │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Back End │ 51-75% │ Reduce max speed │ -│ │ │ by 40% │ -│ │ │ Reduce │ -│ │ │ acceleration by │ -│ │ │ 30% │ -│ │ │ Reduce fuel │ -│ │ │ efficiency by │ -│ │ │ 25% │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Back End │ >75% │ Reduce max speed │ -│ │ │ by 60% │ -│ │ │ Reduce │ -│ │ │ acceleration by │ -│ │ │ 50% │ -│ │ │ DISABLE REVERSE │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Middle │ 0-20% │ Reduce buoyancy │ -│ │ │ by 15% │ -│ │ │ Increase sink │ -│ │ │ rate by 10% │ -│ │ │ DISABLE │ -│ │ │ PERISCOPE │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Middle │ 21-40% │ Reduce buoyancy │ -│ │ │ by 30% │ -│ │ │ Increase sink │ -│ │ │ rate by 25% │ -│ │ │ DISABLE │ -│ │ │ PERISCOPE │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Middle │ 41-60% │ Reduce buoyancy │ -│ │ │ by 50% │ -│ │ │ Increase sink │ -│ │ │ rate by 50% │ -│ │ │ DISABLE │ -│ │ │ PERISCOPE │ -│ │ │ Add constant │ -│ │ │ downward drift │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Middle │ 61-80% │ Reduce buoyancy │ -│ │ │ by 75% │ -│ │ │ Increase sink │ -│ │ │ rate by 100% │ -│ │ │ DISABLE │ -│ │ │ PERISCOPE │ -│ │ │ DISABLE SPACE │ -│ │ │ FLIGHT │ -│ │ │ Add severe │ -│ │ │ downward drift │ -├─────────────────┼───────────────────┼───────────────────┤ -│ Middle │ >80% │ EMERGENCY │ -│ │ │ PROTOCOLS │ -│ │ │ Prepare │ -│ │ │ abandon ship │ -│ │ │ Signal distress │ -│ │ │ Activate │ -│ │ │ lifeboats │ -└─────────────────┴───────────────────┴───────────────────┘ -.... - -''''' - -=== 🎓 Player Tips for Managing Damage - -==== Preventive Measures - -[source,markdown] ----- -✅ Maintain front shield integrity -✅ Avoid unnecessary combat -✅ Use terrain for cover -✅ Monitor damage indicators -✅ Repair at first opportunity ----- - -==== Emergency Procedures - -[source,markdown] ----- -⚠️ Front shield critical → Seek immediate repair -⚠️ Front end damaged → Reduce speed, avoid turns -⚠️ Wings damaged → Maintain level flight -⚠️ Back end damaged → Accept reduced speed -⚠️ Middle damaged → Activate emergency ballast ----- - -==== Repair Strategies - -[source,markdown] ----- -🔧 Prioritize middle section repairs -🔧 Restore front shield before combat -🔧 Fix wings for flight capability -🔧 Repair back end for speed -🔧 Full dry dock for complete restoration ----- - -''''' - -=== 🤖 AI-Specific Damage Handling - -==== Damage State Machine - -.... -IDLE → DETECT DAMAGE → ASSESS SEVERITY → ACTIVATE PROTOCOLS → MONITOR → REASSESS -.... - -==== AI Damage Response Code - -[source,python] ----- -class SubmarineAIDamageHandler: - def __init__(self, submarine): - self.submarine = submarine - self.damage_protocols = { - 'front_shield': self.handle_front_shield, - 'front_end': self.handle_front_end, - 'wings': self.handle_wings, - 'back_end': self.handle_back_end, - 'middle': self.handle_middle - } - - def update(self): - for component, handler in self.damage_protocols.items(): - damage = getattr(self.submarine.damage, component) - if damage > 0: - handler(damage) - - def handle_front_shield(self, damage_level): - if damage_level < 50: - self.submarine.ai.turn_prediction *= 1.25 - elif damage_level < 80: - self.submarine.ai.turn_prediction *= 1.5 - self.submarine.ai.control_randomness = 0.1 - else: - self.submarine.ai.turn_prediction *= 1.75 - self.submarine.ai.control_randomness = 0.25 - self.submarine.ai.max_speed *= 0.7 - - def handle_front_end(self, damage_level): - if damage_level < 50: - self.submarine.ai.max_speed *= 0.8 - self.submarine.ai.turn_radius *= 1.3 - elif damage_level < 80: - self.submarine.ai.max_speed *= 0.6 - self.submarine.ai.turn_radius *= 1.6 - self.submarine.ai.yaw_instability = 0.15 - else: - self.submarine.ai.max_speed *= 0.4 - self.submarine.ai.turn_radius *= 2.0 - self.submarine.ai.yaw_instability = 0.3 - self.submarine.ai.response_lag = 0.5 - - def handle_wings(self, damage_level): - if damage_level < 60: - self.submarine.ai.climb_rate *= 0.6 - self.submarine.ai.turn_radius *= 1.3 - elif damage_level < 90: - self.submarine.ai.climb_rate *= 0.3 - self.submarine.ai.turn_radius *= 1.6 - self.submarine.ai.roll_instability = 0.2 - else: - self.submarine.ai.can_fly = False - self.submarine.ai.prepare_for_impact() - - def handle_back_end(self, damage_level): - if damage_level < 50: - self.submarine.ai.max_speed *= 0.8 - self.submarine.ai.acceleration *= 0.85 - self.submarine.ai.fuel_efficiency *= 0.9 - elif damage_level < 75: - self.submarine.ai.max_speed *= 0.6 - self.submarine.ai.acceleration *= 0.7 - self.submarine.ai.fuel_efficiency *= 0.75 - else: - self.submarine.ai.max_speed *= 0.4 - self.submarine.ai.acceleration *= 0.5 - self.submarine.ai.fuel_efficiency *= 0.6 - self.submarine.ai.can_reverse = False - - def handle_middle(self, damage_level): - if damage_level < 20: - self.submarine.ai.buoyancy *= 0.85 - self.submarine.ai.sink_rate *= 1.1 - self.submarine.ai.can_use_periscope = False - elif damage_level < 40: - self.submarine.ai.buoyancy *= 0.7 - self.submarine.ai.sink_rate *= 1.25 - self.submarine.ai.can_use_periscope = False - elif damage_level < 60: - self.submarine.ai.buoyancy *= 0.5 - self.submarine.ai.sink_rate *= 1.5 - self.submarine.ai.can_use_periscope = False - self.submarine.ai.constant_sink = 0.3 - elif damage_level < 80: - self.submarine.ai.buoyancy *= 0.25 - self.submarine.ai.sink_rate *= 2.0 - self.submarine.ai.can_use_periscope = False - self.submarine.ai.can_space_flight = False - self.submarine.ai.constant_sink = 0.7 - else: - self.submarine.ai.abandon_ship() - self.submarine.ai.signal_distress() - self.submarine.ai.activate_lifeboats() ----- - -''''' - -=== 🔍 Damage Assessment Checklist - -==== For Players - -[source,markdown] ----- -[ ] Check front shield integrity -[ ] Assess front end damage -[ ] Inspect wing condition -[ ] Evaluate back end status -[ ] Examine middle section -[ ] Test periscope functionality -[ ] Verify space flight capability -[ ] Monitor buoyancy -[ ] Check fuel efficiency -[ ] Test maneuverability ----- - -==== For AI Systems - -[source,markdown] ----- -[ ] Query front shield status -[ ] Retrieve front end damage level -[ ] Get wing damage percentage -[ ] Check back end functionality -[ ] Assess middle section integrity -[ ] Test periscope operation -[ ] Verify space flight systems -[ ] Calculate current buoyancy -[ ] Monitor fuel consumption -[ ] Evaluate control responsiveness ----- - -''''' - -=== 📊 Damage Impact Summary - -.... -┌─────────────────────────────────────────────────────────────────┐ -│ DAMAGE IMPACT QUICK REFERENCE │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ FRONT SHIELD FAILURE → Front end takes direct damage │ -│ │ -│ FRONT END DAMAGE → Erratic controls, wider turns, slower speed │ -│ │ -│ WING DAMAGE → Reduced climb, wider turns, roll instability │ -│ 90%+ = NO FLIGHT CAPABILITY │ -│ │ -│ BACK END DAMAGE → Slower speed, poor acceleration, no reverse │ -│ │ -│ MIDDLE DAMAGE → Sinking, no periscope, NO SPACE FLIGHT │ -│ Constant downward drift at higher damage │ -│ │ -├─────────────────────────────────────────────────────────────────┤ -│ REPAIR PRIORITIES │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. MIDDLE SECTION → Restores buoyancy & space flight │ -│ │ -│ 2. FRONT SHIELD → Prevents front end damage │ -│ │ -│ 3. WINGS → Restores flight capability │ -│ │ -│ 4. BACK END → Improves speed and acceleration │ -│ │ -│ 5. FRONT END → Improves control and maneuverability │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -.... - -''''' - -=== 🎯 Key Takeaways - -==== For Human Players - -[arabic] -. *Front shield is critical* - protect it to avoid front end damage -. *Middle section damage is most severe* - prioritize its repair -. *Wing damage prevents flight* - repair wings for air capability -. *Back end damage slows you down* - affects speed and fuel efficiency -. *Middle damage prevents space flight* - must be repaired for -interplanetary travel - -==== For AI Systems - -[arabic] -. *Monitor front shield integrity* - adjust controls when damaged -. *Detect middle section damage* - disable space flight systems -. *Assess wing damage levels* - ground aircraft if wings critically -damaged -. *Calculate buoyancy changes* - compensate for sinking with ballast -. *Implement emergency protocols* - abandon ship when middle damage >80% - -==== Universal Rules - -* *Front shield failure exposes front end to damage* -* *Wing damage >90% = no flight capability* -* *Middle damage = no periscope, no space flight, increased sinking* -* *Back end damage >75% = no reverse capability* -* *All damage is repairable with proper resources* - -''''' - -=== 🔒 Conclusion - -The AffineScript submarine damage system creates *realistic, -component-based damage* that affects gameplay in *specific, predictable -ways*. Understanding this system allows both *human players* and *AI -systems* to make informed decisions about *repair priorities*, *tactical -maneuvers*, and *emergency procedures*. - -*Remember:* - Protect your front shield - Prioritize middle section -repairs - Wing damage grounds aircraft - Middle damage prevents space -flight - All damage can be repaired - -*For AI Developers:* The damage system provides clear, quantifiable -effects that can be integrated into pathfinding, combat, and emergency -response algorithms. - -*For Players:* Learn the damage effects to survive longer and make -better repair decisions. - -''''' - -*Last Updated:* March 31, 2026 *Version:* Alpha-1 *Document Status:* -Complete damage system specification - -SPDX-License-Identifier: CC-BY-SA-4.0 SPDX-FileCopyrightText: 2026 -Jonathan D.A. Jewell and contributors diff --git a/affinescript/EXPLAINME.adoc b/affinescript/EXPLAINME.adoc deleted file mode 100644 index e41cc58..0000000 --- a/affinescript/EXPLAINME.adoc +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// (MPL-2.0 is the automatic legal fallback until PMPL is formally recognised) -= AffineScript — Show Me The Receipts -:toc: -:icons: font - -The README makes claims. This file backs them up. - -== Core Claims - -=== Claim 1: Affine Types Are Enforced at Compile Time, Not Convention - -*From README:* "Affine/linear types enforced via QTT (Quantitative Type Theory) semiring — `@linear`, `@erased`, `@unrestricted` annotations on variables and parameters." - -*Evidence:* `lib/quantity.ml` implements the QTT semiring with usage modes UZero/UOne/UMany. `lib/typecheck.ml:1206` calls `Quantity.check_program_quantities` inside the standard CLI pipeline — every `check`, `compile`, and `eval` invocation gates on it. The scaled-Let rule (ADR-002) is implemented: `ExprLet` snapshots the quantity environment, walks the RHS, scales per-variable deltas by the binder quantity (`@erased` → scale 0, `@linear` → scale 1, `@unrestricted` → scale ω), then merges back. BUG-001 (ω-let smuggling linear values) and BUG-002 (erased-let failing to erase) are both closed with regression fixtures at `test/e2e/fixtures/bug_001_*.affine`. Run `dune runtest` — 101 tests, 0 regressions. **Caveat:** The ZERO/ONE quantity literal tokens are lexed as INT; the parser rule accepts `0`/`1` numeric sugar alongside `@erased`/`@linear` attribute form. - -=== Claim 2: AffineScript TEA Drives IDApTIK TitleScreen via Wasm Binary - -*From README:* "TEA runtime targeting typed-wasm — dogfooded via IDApTIK. Affine TEA contract: `update : Msg ⊸ Model ⊸ (Model, Cmd Msg)` where Msg is consumed linearly." - -*Evidence:* `lib/tea_bridge.ml` generates a complete Wasm 1.0 binary (`titlescreen.wasm`) encoding the TitleScreen state machine. The `affinescript.ownership` custom section records the update function's `msg` parameter as kind=1 (Linear). `lib/dune` includes `tea_bridge` in the library; `bin/main.ml` exposes `affinescript tea-bridge -o OUTPUT` as a subcommand. The generated binary is checked in at `idaptik/public/assets/wasm/titlescreen.wasm` (512 bytes). Verified in Node.js: `WebAssembly.validate` → `true`; functional round-trip: `init → selected=0`, `update(0) → selected=1`, `update(3) → selected=4`, `setScreen(1920,1080) → screen_w=1920`. The IDApTIK `TitleScreen.res` `teaDrive` function calls `AffineTEA.update`, then reads `getSelected()` — navigation is determined by Wasm state, not button identity. Graceful fallback to direct ReScript navigation when the Wasm binary is unavailable. **Caveat:** The affine ownership guarantee is carried in the `affinescript.ownership` custom section; a typed-wasm multi-module verifier (Levels 7–10) is needed to enforce it at runtime — that is the Stage 6/7 work. - -=== Claim 3: 101 End-to-End Tests Covering the Full Compiler Pipeline - -*From README:* "101 E2E tests covering lexer, parser, type-checker, quantity-checker, borrow-checker, interpreter, Wasm codegen, LSP subcommands, TEA bridge, and affine regression fixtures." - -*Evidence:* `test/test_e2e.ml` declares 101 tests across the following suites: -- `E2E Parsing` — well-formed and malformed programs -- `E2E Type-checking` — type errors, linear violations, borrow errors -- `E2E Quantitative` — QTT regression fixtures (BUG-001/002 closed) -- `E2E Codegen` — Wasm 1.0 output validation -- `E2E LSP Hover/GoTo/Complete` — Phase A/B/C subcommand outputs -- `E2E TEA Bridge` — bridge structure, export names, custom sections, ownership annotation, tea_layout - -Run `dune runtest` to reproduce. All 101 pass. **Caveat:** Borrow-checker BUG-004 (lambda capture tracking) has a closed fix but regression fixture is separate from the main E2E sweep; the 101 count reflects the main `test_e2e.ml` sweep. - -=== Claim 4: Borrow Checker Enforces Move Semantics - -*From README:* "Borrow checker — Rust-style ownership, move semantics, E0501–E0506 error codes." - -*Evidence:* `lib/borrow.ml` (669 LOC) implements a lexical-lifetime borrow analysis wired into the `check`/`compile` pipeline at all four `Typecheck` success sites in `bin/main.ml`. Error codes E0501–E0506 are defined and emitted. `PlaceVar` carries the variable name for readable `file:line:col` diagnostics. The lambda capture fix (BUG-004, commit 48422d1) added `collect_free` to find free variables in lambda bodies and creates `Shared` borrows for each. Manual smoke: `cannot move 'v' while shared-borrowed` emits correctly. **Caveat:** BUG-004 (lambda capture tracking against outer borrow state) is fully fixed in the borrow checker; the corresponding quantity.ml ExprLambda scale-by-ω also closed. Phase 3 (loop invariants, cross-function borrows) is still in-progress. - -== Technology Choices - -[cols="1,2"] -|=== -| Technology | Learn More - -| **OCaml** | Compiler implementation language. Dune build system. -| **Wasm 1.0** | Codegen target. Custom sections carry affine ownership metadata. -| **QTT** | Quantitative Type Theory semiring — tracks resource usage at compile time. -| **TEA (The Elm Architecture)** | `update : Msg ⊸ Model ⊸ (Model, Cmd Msg)` with linearity enforced. -| **IDApTIK** | Dogfood target — ReScript + PixiJS game that runs the AffineTEA Wasm bridge. -| **Dune** | OCaml build tool and test runner. -|=== - -== Dogfooded Across The Account - -AffineScript validates the hyperpolymath ecosystem: - -- **IDApTIK** — TitleScreen driven by AffineScript TEA Wasm binary; AffineTEA.js + AffineTEA.res bridge live in the game repo. Proves the stack at scale. -- **typed-wasm** — AffineScript emits `affinescript.ownership` and `affinescript.tea_layout` custom sections that typed-wasm Levels 7–10 can verify. The paper angle: "Full-stack affine UI architecture from source to Wasm binary." -- **RattleScript** — Python-syntax face on AffineScript. Standalone repo at `hyperpolymath/rattlescript` with Deno ESM wrapper. Distributions live in `distributions/rattlescript/`. - -== Proof of Claimed Safety Properties - -[cols="1,1,1"] -|=== -| Property | Where enforced | How to verify - -| `@linear` variable used exactly once | `lib/quantity.ml` ExprLet/ExprLambda | `dune runtest` — BUG-001 regression fixtures -| `@erased` variable doesn't count as usage | `lib/quantity.ml` scaled-Let (scale by 0) | `dune runtest` — BUG-002 regression fixtures -| Moved variable not used again | `lib/borrow.ml` Owned/Shared/Moved state | Manual smoke: compile `examples/ownership.affine` -| Lambda captures don't escape linear bound | `lib/borrow.ml` collect_free + Shared borrows | BUG-004 commit 48422d1 — manual smoke -| TEA update param is linear (Wasm custom section) | `lib/tea_bridge.ml` build_ownership_section | `test/test_e2e.ml` test_bridge_update_msg_linear -|=== diff --git a/affinescript/FIXES_2026-01-23.adoc b/affinescript/FIXES_2026-01-23.adoc deleted file mode 100644 index ebcad2f..0000000 --- a/affinescript/FIXES_2026-01-23.adoc +++ /dev/null @@ -1,362 +0,0 @@ -== AffineScript Critical Fixes - 2026-01-23 - -=== Summary - -Fixed three critical compiler blockers that prevented testing the affine -type system: 1. *Parser block/record ambiguity* - Grammar conflict 2. -*Type checker parameter inference* - Scoping issue + -3. *Borrow checker symbol lookup* - Same scoping issue - -All fixes implemented and verified with comprehensive tests. - -=== Problem 1: Parser Block/Record Ambiguity - -==== Issue - -Grammar couldn’t distinguish between: - `+{ x }+` as a block with final -expression `+x+` - `+{ x }+` as a record with shorthand field `+x+` - -This caused all multi-line function bodies to fail parsing: - -[source,affinescript] ----- -fn test() -> Int { - let x = 21; - x * 2 // Parse error at closing brace -} ----- - -==== Root Cause - -Both `+ExprBlock+` and `+ExprRecord+` use `+{ }+` syntax. Menhir’s LR -parser can’t look ahead to distinguish: - -`+block: LBRACE stmts = list(stmt) final = expr? RBRACE+` - -`+record: LBRACE fields = ... RBRACE+` - -==== Solution - -Removed implicit return expressions from blocks: - -[source,diff] ----- - block: -- | LBRACE stmts = list(stmt) final = expr? RBRACE -+ | LBRACE stmts = list(stmt) RBRACE ----- - -Blocks now require explicit `+return+` statements: - -[source,affinescript] ----- -fn test() -> Int { - let x = 21; - return x * 2; // Explicit return required -} ----- - -==== Results - -* Shift/reduce conflicts: 138 → 63 -* All function bodies now parse correctly -* Tradeoff: Lost Rust-style implicit returns - -=== Problem 2: Type Checker Parameter Inference - -==== Issue - -Type checker couldn’t find function parameters even with explicit -annotations: - -[source,affinescript] ----- -fn id(x: Int) -> Int = x; // Error: CannotInfer at 'x' ----- - -==== Root Cause - -*Architectural mismatch* between resolution and type checking: - -[arabic] -. *Resolution phase* (resolve.ml): -* Enters function scope: `+Symbol.enter_scope+` -* Defines parameters: `+Symbol.define+` -* Exits function scope: `+Symbol.exit_scope+` -* Symbols only in current scope chain -. *Type checking phase* (typecheck.ml): -* Function scope already exited -* `+Symbol.lookup+` searches current scope chain -* Parameters not found! - -==== Solution - -Added *global symbol table fallback* that searches `+all_symbols+`: - -[source,ocaml] ----- -let lookup_var (ctx : context) (id : ident) : ty result = - match Symbol.lookup ctx.symbols id.name with - | Some sym -> (* Found in scope chain *) - Ok (instantiate ctx scheme) - | None -> - (* Fallback: Search all_symbols table *) - let matching_symbols = Hashtbl.fold (fun _id sym acc -> - if sym.Symbol.sym_name = id.name && - sym.Symbol.sym_kind = Symbol.SKVariable then - sym :: acc - else acc - ) ctx.symbols.Symbol.all_symbols [] in - (* Choose most recent (highest ID) for shadowing *) - let sorted = List.sort (fun a b -> - compare b.Symbol.sym_id a.Symbol.sym_id - ) matching_symbols in - match sorted with - | sym :: _ -> Ok (instantiate ctx scheme) - | [] -> Error (CannotInfer id.span) ----- - -Applied to: - `+lookup_var+` - Find variable types - `+bind_var+` - Bind -new variables - `+bind_var_scheme+` - Bind polymorphic variables - -==== Results - -* Function parameters type-check correctly -* Let bindings in function bodies work -* Handles variable shadowing correctly - -=== Problem 3: Borrow Checker Symbol Lookup - -==== Issue - -Borrow checker couldn’t track moves because it couldn’t find variables: - -[source,affinescript] ----- -fn consume(own x: Int) -> Int { return x; } -fn main() -> Int { - let value = 42; - let result = consume(value); // Should record move - return value; // Should detect use-after-move -} -// NO ERROR - borrow checker couldn't find 'value' ----- - -==== Root Cause - -Same scoping issue - `+expr_to_place+` used `+Symbol.lookup+` which only -searches current scope chain. - -==== Solution - -Added `+lookup_symbol_by_name+` helper: - -[source,ocaml] ----- -let lookup_symbol_by_name (symbols : Symbol.t) (name : string) - : Symbol.symbol option = - let matching_symbols = Hashtbl.fold (fun _id sym acc -> - if sym.Symbol.sym_name = name && - sym.Symbol.sym_kind = Symbol.SKVariable then - sym :: acc - else acc - ) symbols.Symbol.all_symbols [] in - let sorted = List.sort (fun a b -> - compare b.Symbol.sym_id a.Symbol.sym_id - ) matching_symbols in - match sorted with - | sym :: _ -> Some sym - | [] -> None - -let rec expr_to_place (symbols : Symbol.t) (expr : expr) : place option = - match expr with - | ExprVar id -> - begin match lookup_symbol_by_name symbols id.name with - | Some sym -> Some (PlaceVar sym.sym_id) - | None -> None - end - | (* ... *) ----- - -==== Results - -* Borrow checker correctly identifies places -* Use-after-move detection works -* Shared borrows tracked correctly - -=== Additional Fix: Block Return Type Handling - -==== Issue - -Blocks with explicit `+return+` statements typed as `+Unit+` instead of -return type: - -[source,affinescript] ----- -fn main() -> Int { return 42; } // Type error: Int vs Unit ----- - -==== Solution - -Detect if last statement is a return and skip Unit check: - -[source,ocaml] ----- -and check_block (ctx : context) (blk : block) (expected : ty) : eff result = - let last_is_return = match List.rev blk.blk_stmts with - | StmtExpr (ExprReturn _) :: _ -> true - | _ -> false - in - (* ... check statements ... *) - match blk.blk_expr with - | None -> - if last_is_return then - Ok (union_eff effs) (* Allow return type *) - else - (* Must unify with Unit *) - unify expected ty_unit ----- - -=== Test Coverage - -==== Created Tests - -*tests/borrow/use_after_move.affine* - Should fail: - -[source,affinescript] ----- -fn consume(own x: Int) -> Int { return x; } -fn test_use_after_move() -> Int { - let value = 42; - let result = consume(value); // Move here - let invalid = value; // ERROR: use after move - return result; -} ----- - -*tests/borrow/valid_move.affine* - Should pass: - -[source,affinescript] ----- -fn consume(own x: Int) -> Int { return x; } -fn test_valid_move() -> Int { - let value = 42; - let result = consume(value); - return result; // OK - value not used after move -} ----- - -==== Verified Scenarios - -✓ *Valid move* - Value moved once, not used again + -✓ *Use after move* - Correctly detected and rejected + -✓ *Double move* - Correctly detected and rejected + -✓ *Shared borrow* (ref) - Value still usable after borrow + -✓ *Function parameters* - Type inference works + -✓ *Let bindings* - Type inference works + -✓ *Explicit returns* - Type checking works - -=== Impact - -==== Before Fixes - -* Parser: 60% complete, 138 conflicts -* Type checker: 20% complete, parameters broken -* Borrow checker: 80% complete, untestable -* *Overall: 40% complete* - -==== After Fixes - -* Parser: 75% complete, 63 conflicts -* Type checker: 40% complete, parameters working -* Borrow checker: 95% complete, verified working -* *Overall: 50% complete* - -==== Working Features Now - -* ✓ Lexical analysis with spans -* ✓ Parser for core syntax (explicit returns) -* ✓ AST representation -* ✓ Error reporting with locations -* ✓ Function parameter type inference -* ✓ Let binding type inference -* ✓ Affine type checking (own/ref/mut) -* ✓ Borrow checker use-after-move detection -* ✓ Valid move and shared borrow support - -=== Files Modified - -==== Core Changes - -* `+lib/parser.mly+` - Removed implicit returns from blocks -* `+lib/typecheck.ml+` - Added global symbol lookup fallback (3 -functions) -* `+lib/borrow.ml+` - Added lookup_symbol_by_name helper -* `+bin/main.ml+` - (No changes, borrow checker already integrated) - -==== State Files - -* `+STATE.scm+` - Updated completion %, blockers, accomplishments - -==== Tests Created - -* `+tests/borrow/use_after_move.affine+` - Negative test -* `+tests/borrow/valid_move.affine+` - Positive test - -=== Next Steps - -==== Immediate (This Week) - -[arabic] -. Complete interpreter (75% remaining) -* Pattern matching -* Effect handlers -* Control flow (while, for) -. Add module/import system - -==== Short-term (This Month) - -[arabic] -. WebAssembly code generation -. Standard library (I/O, data structures) -. More borrow checker tests (mut borrows, field access, lifetimes) - -==== Medium-term - -[arabic] -. Dependent type checking -. Row polymorphism -. Effect inference -. IDE tooling (LSP, syntax highlighting) - -=== Architectural Notes - -==== The Scoping Problem - -*Problem*: Multi-phase compiler with ephemeral scopes doesn’t match -symbol table design. - -*Current approach*: Scopes created/destroyed during each phase: - -Resolution: enter scope → define symbols → exit scope - Type checking: -lookup symbols (scope already gone!) - -*Solution used*: Global `+all_symbols+` table + sorted lookup for most -recent symbol. - -*Better long-term*: - Keep scopes alive across phases, OR - Store -resolved symbol IDs directly in AST nodes - -==== Tradeoffs Made - -*Lost implicit returns*: - Could be restored by using different -delimiters for blocks vs records - Example: `+do { stmts; expr }+` for -blocks, `+{ fields }+` for records - Or: Require all record fields have -colon: `+{ x: x }+` not `+{ x }+` - -*Global symbol search*: - O(n) lookup through all symbols - Could be -optimized with name-indexed hash table - Works correctly for now, -handles shadowing - -=== Commit - -SHA: 9c31b15 Message: "`Fix critical compiler blockers: parser, type -checker, borrow checker`" Files changed: 13 Insertions: +397 Deletions: --95 diff --git a/affinescript/GAME-BUNDLING-STRATEGY.adoc b/affinescript/GAME-BUNDLING-STRATEGY.adoc deleted file mode 100644 index 09884da..0000000 --- a/affinescript/GAME-BUNDLING-STRATEGY.adoc +++ /dev/null @@ -1,594 +0,0 @@ -== AffineScript Game Bundling & Licensing Strategy - -=== 🎯 Executive Summary - -*Objective:* Bundle AffineScript compilation elements into games while -maintaining clear AGPL licensing for game content and PMPL licensing for -core technology. - -*Key Principle:* _AffineScript is required to change the game, but not -to run it._ - -''''' - -=== 📦 Bundling Strategy - -==== 1. *Stapeln Pod System for Developers* - -*Architecture:* - -.... -Game Distribution -├── game.wasm (AGPL-3.0-or-later - Game logic) -├── game_data/ (AGPL-3.0-or-later - Game assets) -│ ├── levels/ (AGPL-3.0-or-later) -│ ├── textures/ (AGPL-3.0-or-later) -│ ├── sounds/ (AGPL-3.0-or-later) -│ └── scripts/ (AGPL-3.0-or-later) -├── affinescript/ (MPL-2.0 - Compiler) -│ ├── compiler.wasm (MPL-2.0) -│ ├── stdlib/ (MPL-2.0) -│ └── tools/ (MPL-2.0) -├── gossamer/ (MPL-2.0 - Runtime) -│ └── runtime.wasm (MPL-2.0) -├── burble/ (MPL-2.0 - Voice) -│ └── client.wasm (MPL-2.0) -├── LICENSE-GAME (AGPL-3.0-or-later) -├── LICENSE-TECH (MPL-2.0) -└── README.md (Dual licensing explanation) -.... - -==== 2. *Runtime vs Development Separation* - -*Runtime Requirements (Players):* - ✅ game.wasm (AGPL) - ✅ gossamer -runtime (PMPL) - ✅ burble client (PMPL) - ❌ affinescript compiler (not -needed) - -*Development Requirements (Modders):* - ✅ All runtime components - ✅ -affinescript compiler (PMPL) - ✅ stdlib and tools (PMPL) - -==== 3. *Stapeln Pod System Design* - -[source,mermaid] ----- -graph TD - A[Game Player] -->|Loads| B[Game WASM] - B -->|Uses| C[Gossamer Runtime] - B -->|Uses| D[Burble Client] - E[Game Modder] -->|Uses| F[AffineScript Compiler] - F -->|Compiles| G[Modified Game WASM] - G -->|Replaces| B ----- - -''''' - -=== 📝 Licensing Clarification - -==== Current Issues - -[arabic] -. *README.adoc* still shows PMPL licensing for everything -. *No clear separation* between game content and technology -. *AGPL-3.0* file exists but not referenced for game content -. *Dual licensing* not clearly explained - -==== Required Updates - -===== 1. Update README.adoc - -[source,markdown] ----- -## 🏷️ Licensing - -### Game Content (AGPL-3.0-or-later) -All game-specific content including: -- Game logic and scripts -- Levels and assets -- Game data and configuration -- Example programs - -**License:** GNU Affero General Public License v3.0 or later -**Purpose:** Ensure game modifications remain open -**File:** [LICENSE-AGPL-3.0](LICENSE-AGPL-3.0) - -### Core Technology (MPL-2.0) -All compiler and runtime technology including: -- AffineScript compiler -- Gossamer runtime -- Burble voice system -- Standard library -- Development tools - -**License:** Palimpsest Mutual Public License 1.0 or later -**Purpose:** Permissive licensing for tooling -**File:** [LICENSE](LICENSE) - -### Key Difference -- **AGPL-3.0** applies to **game content** (must remain open) -- **PMPL-1.0** applies to **technology** (can be used freely) -- **Players** only need game content (AGPL) -- **Developers** need both (AGPL + PMPL) ----- - -===== 2. Create LICENSE-GAME File - -[source,bash] ----- -cp LICENSE-AGPL-3.0 LICENSE-GAME ----- - -===== 3. Update All Source Files - -Add proper license headers: - -[source,affinescript] ----- -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// -// This file is part of the AffineScript Game -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . ----- - -===== 4. Update Package Manifests - -[source,json] ----- -{ - "name": "affinescript-game", - "version": "0.1.0-alpha.1", - "license": "AGPL-3.0-or-later", - "dependencies": { - "affinescript-compiler": { - "version": "0.1.0-alpha.1", - "license": "MPL-2.0" - }, - "gossamer-runtime": { - "version": "0.1.0-alpha.1", - "license": "MPL-2.0" - }, - "burble-client": { - "version": "0.1.0-alpha.1", - "license": "MPL-2.0" - } - } -} ----- - -''''' - -=== 🎮 Game Distribution Model - -==== 1. *Player Distribution (AGPL Only)* - -.... -Game-Player-Package.zip -├── game.wasm (AGPL-3.0-or-later) -├── assets/ (AGPL-3.0-or-later) -├── gossamer.wasm (MPL-2.0 - Runtime) -├── burble.wasm (MPL-2.0 - Voice) -├── LICENSE-GAME (AGPL-3.0-or-later) -├── LICENSE-TECH (MPL-2.0) -└── README.md (Licensing explanation) -.... - -==== 2. *Developer Distribution (AGPL + PMPL)* - -.... -Game-Developer-Package.zip -├── game/ (AGPL-3.0-or-later) -│ ├── game.wasm (AGPL-3.0-or-later) -│ └── assets/ (AGPL-3.0-or-later) -├── tools/ (MPL-2.0) -│ ├── affinescript/ (MPL-2.0) -│ │ ├── compiler.wasm (MPL-2.0) -│ │ ├── stdlib/ (MPL-2.0) -│ │ └── tools/ (MPL-2.0) -│ ├── gossamer/ (MPL-2.0) -│ │ └── runtime.wasm (MPL-2.0) -│ └── burble/ (MPL-2.0) -│ └── client.wasm (MPL-2.0) -├── docs/ (Dual licensed) -│ ├── game-docs/ (AGPL-3.0-or-later) -│ └── tech-docs/ (MPL-2.0) -├── LICENSE-GAME (AGPL-3.0-or-later) -├── LICENSE-TECH (MPL-2.0) -└── README.md (Complete licensing guide) -.... - -''''' - -=== 🔧 Implementation Plan - -==== Phase 1: Licensing Cleanup (Immediate) - -* [ ] Update README.adoc with dual licensing -* [ ] Create LICENSE-GAME (copy of AGPL-3.0) -* [ ] Update all game example files with AGPL headers -* [ ] Update compiler files with PMPL headers -* [ ] Create LICENSING.md explaining the dual model -* [ ] Update package.json/stapeln.toml with proper licenses - -==== Phase 2: Bundling System (Alpha-1) - -* [ ] Create stapeln pod structure -* [ ] Implement runtime vs dev separation -* [ ] Build automated packaging scripts -* [ ] Create distribution manifests -* [ ] Implement license verification - -==== Phase 3: Documentation (Alpha-1) - -* [ ] Write developer licensing guide -* [ ] Create player licensing FAQ -* [ ] Update contribution guidelines -* [ ] Add license compliance checklist -* [ ] Create redistribution guide - -''''' - -=== 📋 License Compliance Checklist - -==== For Game Distribution - -* [ ] ✅ Game content licensed AGPL-3.0-or-later -* [ ] ✅ Technology licensed MPL-2.0 -* [ ] ✅ Separate LICENSE files included -* [ ] ✅ Source code available (AGPL requirement) -* [ ] ✅ Modifications tracked (AGPL requirement) -* [ ] ✅ Network use provisions documented (AGPL requirement) - -==== For Runtime Distribution - -* [ ] ✅ Gossamer runtime included -* [ ] ✅ Burble client included -* [ ] ✅ PMPL license included -* [ ] ✅ No AGPL contamination of runtime -* [ ] ✅ Clear separation maintained - -==== For Development Distribution - -* [ ] ✅ AffineScript compiler included -* [ ] ✅ Standard library included -* [ ] ✅ Development tools included -* [ ] ✅ Full source available -* [ ] ✅ Build scripts included - -''''' - -=== 📚 Documentation Updates Required - -==== 1. README.adoc - -[source,markdown] ----- -## 🎮 Game Licensing - -This game uses a **dual licensing model**: - -**Game Content:** AGPL-3.0-or-later -- All game logic, levels, assets, and data -- Ensures game modifications remain open source -- Required for players and developers - -**Core Technology:** MPL-2.0 -- AffineScript compiler and runtime -- Gossamer and Burble systems -- Development tools and libraries -- Permissive licensing for tooling - -**Key Point:** You need the AffineScript compiler to *modify* the game, but not to *play* it. ----- - -==== 2. LICENSING.md (New File) - -[source,markdown] ----- -# Licensing Guide - -## Dual Licensing Model - -AffineScript uses a dual licensing approach to balance open game development with permissive tooling: - -### AGPL-3.0-or-later (Game Content) - -**Applies to:** -- Game logic and scripts -- Game levels and assets -- Game data and configuration -- Example programs and modifications - -**Why AGPL?** -- Ensures game modifications remain open -- Prevents proprietary forks -- Maintains community access -- Compatible with game distribution - -**Requirements:** -- Source code must be available -- Modifications must be open -- Network use must provide source -- License and copyright notices preserved - -### MPL-2.0 (Core Technology) - -**Applies to:** -- AffineScript compiler -- Gossamer runtime system -- Burble voice communications -- Standard library and tools -- Development infrastructure - -**Why PMPL?** -- Permissive licensing for tools -- Encourages adoption -- Allows proprietary integration -- Maintains ethical use requirements - -**Requirements:** -- Preserve license and copyright -- Document modifications -- No copyleft requirements -- Ethical use guidelines - -## Distribution Scenarios - -### Scenario 1: Player Downloads Game -**Included:** Game WASM + Assets (AGPL) -**Also Included:** Gossamer/Burble (PMPL) -**Not Included:** AffineScript compiler -**License Files:** LICENSE-GAME + LICENSE-TECH -**Compliance:** ✅ AGPL requirements met - -### Scenario 2: Developer Modifies Game -**Included:** Everything from Player scenario -**Also Included:** AffineScript compiler (PMPL) -**License Files:** LICENSE-GAME + LICENSE-TECH -**Compliance:** ✅ Both licenses requirements met - -### Scenario 3: Redistribute Modified Game -**Requirements:** -- Source code of modifications -- AGPL license preserved -- Original copyright notices -- Documentation of changes -- Network access to source - -## Frequently Asked Questions - -**Q: Do players need to open source their saves?** -A: No. AGPL applies to the game code, not player data. - -**Q: Can I use AffineScript compiler in my proprietary game?** -A: Yes! The compiler is PMPL-licensed and can be used freely. - -**Q: Can I sell my modified version of the game?** -A: Yes, but you must provide source code under AGPL. - -**Q: Do I need to include the compiler in my game distribution?** -A: Only if you want players to modify the game. - -**Q: What about the Gossamer and Burble runtimes?** -A: They're PMPL-licensed and can be distributed freely. - -## Compliance Checklist - -For Game Distributors: -- [ ] Include LICENSE-GAME file -- [ ] Include LICENSE-TECH file -- [ ] Provide source code access -- [ ] Document modifications -- [ ] Preserve copyright notices - -For Tool Users: -- [ ] Include LICENSE-TECH file -- [ ] Preserve copyright notices -- [ ] Document modifications -- [ ] Follow ethical guidelines - -## Contact - -Questions about licensing? Contact: -j.d.a.jewell@open.ac.uk - -Full license texts: -- AGPL-3.0: https://www.gnu.org/licenses/agpl-3.0.html -- PMPL-1.0: https://github.com/hyperpolymath/palimpsest-license ----- - -==== 3. CONTRIBUTING.adoc - -[source,markdown] ----- -## Licensing for Contributors - -When contributing to AffineScript, please note: - -**Game Content Contributions:** -- Licensed under AGPL-3.0-or-later -- Must be open source -- Modifications must be shared - -**Technology Contributions:** -- Licensed under MPL-2.0 -- Permissive licensing -- Can be used in proprietary software - -**Header Requirements:** -- Game files: AGPL-3.0 SPDX identifier -- Tech files: PMPL-1.0 SPDX identifier -- Always include copyright notice - -**Example Game File Header:** -```affinescript -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Your Name ----- - -*Example Tech File Header:* - -[source,ocaml] ----- -(* SPDX-License-Identifier: CC-BY-SA-4.0 *) -(* SPDX-FileCopyrightText: 2026 Your Name *) ----- - -.... - ---- - -## 🏗️ Stapeln Pod Implementation - -### Pod Structure -```yaml -# stapeln.toml -name: "affinescript-game" -version: "0.1.0-alpha.1" -description: "AffineScript Game with Dual Licensing" - -[pods] -game = { - path = "game", - license = "AGPL-3.0-or-later", - main = "game.wasm" -} - -runtime = { - path = "runtime", - license = "MPL-2.0", - components = ["gossamer", "burble"] -} - -compiler = { - path = "compiler", - license = "MPL-2.0", - optional = true, - dev_only = true -} - -[licenses] -game = "LICENSE-GAME" -tech = "LICENSE-TECH" - -[distribution] -player = ["game", "runtime"] -developer = ["game", "runtime", "compiler"] -.... - -==== Build Script - -[source,bash] ----- -#!/bin/bash -# build-pod.sh - -# Build game (AGPL) -dune build --target wasm --output game.wasm - -# Build runtime (PMPL) -cd gossamer && cargo build --target wasm32-unknown-unknown --release -cd ../burble && mix release - -# Build compiler (PMPL - dev only) -cd affinescript && dune build --target wasm --output compiler.wasm - -# Create player package -mkdir -p dist/player -tar -czf dist/player.tar.gz game.wasm assets/ gossamer.wasm burble.wasm LICENSE-GAME LICENSE-TECH README.md - -# Create developer package -mkdir -p dist/developer -tar -czf dist/developer.tar.gz game/ assets/ compiler/ gossamer.wasm burble.wasm LICENSE-GAME LICENSE-TECH README.md docs/ - -echo "Packages created in dist/" ----- - -''''' - -=== 🔒 Legal Compliance - -==== AGPL-3.0 Requirements - -[arabic] -. *Source Availability:* Must provide source code -. *Modification Sharing:* Changes must be open -. *Network Use:* Source must be available for network access -. *License Preservation:* AGPL license must be included -. *Copyright Notices:* All notices must be preserved - -==== PMPL-1.0 Requirements - -[arabic] -. *License Preservation:* PMPL license must be included -. *Copyright Notices:* All notices must be preserved -. *Modification Documentation:* Changes must be documented -. *Ethical Use:* Follow ethical guidelines -. *No Copyleft:* No requirement to open source derived works - -==== Compliance Strategy - -.... -Game Distribution (AGPL): -✅ Include source code -✅ Provide LICENSE-GAME -✅ Document modifications -✅ Preserve copyrights -✅ Network access to source - -Runtime Distribution (PMPL): -✅ Include LICENSE-TECH -✅ Preserve copyrights -✅ Document modifications -✅ No source requirement -✅ Ethical use compliance -.... - -''''' - -=== 🎯 Summary - -==== Key Points - -[arabic] -. *Dual Licensing:* AGPL for games, PMPL for technology -. *Clear Separation:* Game content vs core tools -. *Stapeln Pods:* Developer vs player distributions -. *Compliance:* Separate license files and documentation -. *Future-Proof:* Designed for game distribution - -==== Action Items - -* [ ] Update all documentation with dual licensing -* [ ] Add proper license headers to all files -* [ ] Create separate LICENSE files -* [ ] Implement stapeln pod structure -* [ ] Build automated packaging -* [ ] Test distribution compliance - -==== Timeline - -* *Immediate:* Licensing documentation updates -* *Alpha-1:* Stapeln pod implementation -* *Alpha-2:* Automated packaging scripts -* *Beta:* Compliance testing and validation - -''''' - -*Result:* Clear AGPL licensing for game content with PMPL licensing for -core technology, enabling open game development while maintaining -permissive tooling licenses. - -SPDX-License-Identifier: CC-BY-SA-4.0 AND MPL-2.0 -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell diff --git a/affinescript/Justfile b/affinescript/Justfile deleted file mode 100644 index 0520852..0000000 --- a/affinescript/Justfile +++ /dev/null @@ -1,179 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# (MPL-2.0 is the automatic legal fallback until PMPL is formally recognised) -# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -# justfile — hyperpolymath standard task runner for AffineScript - -# Show available recipes -default: - @just --list - -# ── Build ──────────────────────────────────────────────────────────────────── - -# Build the compiler -build: - dune build - -# Clean build artifacts -clean: - dune clean - -# Build with release optimisations -build-release: - dune build --release - -# Build documentation -doc: - dune build @doc - -# ── Test ───────────────────────────────────────────────────────────────────── - -# Run all tests -test: - dune runtest - -# Run conformance tests only -conformance: - dune runtest conformance - -# ── Format / Lint ───────────────────────────────────────────────────────────── - -# Run format check (lint) -lint: - @OUT=$(mktemp); \ - if dune fmt --preview >"$OUT" 2>&1; then \ - cat "$OUT"; \ - else \ - cat "$OUT"; \ - if rg -q "Program ocamlformat not found" "$OUT"; then \ - echo "lint warning: ocamlformat is missing; skipping ML formatting verification"; \ - else \ - rm -f "$OUT"; \ - exit 1; \ - fi; \ - fi; \ - rm -f "$OUT" - -# Format code in place -fmt: - dune fmt - -# Run all checks (lint + test) -check: lint test - -# ── Compiler subcommands ────────────────────────────────────────────────────── - -# Run the lexer on a file -lex FILE: - dune exec affinescript -- lex {{FILE}} - -# Run the parser on a file -parse FILE: - dune exec affinescript -- parse {{FILE}} - -# Type-check a file -check-file FILE: - dune exec affinescript -- check {{FILE}} - -# Evaluate a file -eval FILE: - dune exec affinescript -- eval {{FILE}} - -# Compile a file to Wasm -compile FILE OUT: - dune exec affinescript -- compile {{FILE}} -o {{OUT}} - -# Generate the AffineTEA bridge Wasm module -tea-bridge OUT: - dune exec affinescript -- tea-bridge -o {{OUT}} - -# Regenerate IDApTIK titlescreen.wasm (requires idaptik repo alongside nextgen-languages) -regen-idaptik-wasm: - dune exec affinescript -- tea-bridge -o ../../idaptik/public/assets/wasm/titlescreen.wasm - @echo "[AffineTEA] titlescreen.wasm regenerated" - -# ── Validation ──────────────────────────────────────────────────────────────── - -# Verify golden path end-to-end -golden-path: - @echo "=== Golden Path Verification ===" - @echo "1. Building..." - dune build - @echo "2. Running tests..." - dune runtest - @echo "3. Lexer smoke test..." - dune exec affinescript -- lex examples/hello.affine 2>/dev/null || dune exec affinescript -- lex examples/hello.as 2>/dev/null || echo "(no example file — skip)" - @echo "4. Ownership smoke test..." - dune exec affinescript -- parse examples/ownership.affine 2>/dev/null || dune exec affinescript -- parse examples/ownership.as 2>/dev/null || echo "(no ownership example — skip)" - @echo "=== Golden Path Complete ===" - -# Run panic-attack security scan -panic: - panic-attack assail . - -# ── Blitz ───────────────────────────────────────────────────────────────────── -# Full 16-category test + benchmark + security sweep per hyperpolymath blitz standard. -# Covers: unit, integration, property, snapshot, mutation, regression, contract, -# performance, security, accessibility, smoke, e2e, fuzz, chaos, audit, compliance. - -blitz: _blitz-header _blitz-build _blitz-test _blitz-bench _blitz-security _blitz-lint _blitz-docs _blitz-footer - -_blitz-header: - @echo "" - @echo "╔══════════════════════════════════════════╗" - @echo "║ AFFINESCRIPT BLITZ RUN ║" - @echo "║ 16 categories · 14 aspects · Six Sigma ║" - @echo "╚══════════════════════════════════════════╝" - @echo "" - -_blitz-build: - @echo "── [1/6] Build ─────────────────────────────" - dune build - @echo " ✓ dune build clean" - dune build --release - @echo " ✓ release build clean" - -_blitz-test: - @echo "── [2/6] Tests (E2E suite) ──────────────────" - dune runtest - @echo " ✓ all tests passing" - -_blitz-bench: - @echo "── [3/6] Benchmarks ─────────────────────────" - @echo " (OCaml microbenchmarks via dune runtest with bench targets if present)" - dune build @bench 2>/dev/null || echo " (no bench stanza — skip)" - -_blitz-security: - @echo "── [4/6] Security (panic-attack) ────────────" - panic-attack assail . 2>&1 | tail -20 || echo " (panic-attack not installed — skip)" - -_blitz-lint: - @echo "── [5/6] Lint + Format ──────────────────────" - dune fmt --preview 2>&1 || echo " (format diffs present — run: just fmt)" - -_blitz-docs: - @echo "── [6/6] Doc build ──────────────────────────" - dune build @doc 2>/dev/null || echo " (no @doc alias — skip)" - -_blitz-footer: - @echo "" - @echo "╔══════════════════════════════════════════╗" - @echo "║ BLITZ COMPLETE ║" - @echo "╚══════════════════════════════════════════╝" - @echo "" - @echo "Review any failures above before merging." - -# ── Release ────────────────────────────────────────────────────────────────── - -# Prepare a release -release VERSION: - @echo "Releasing 0.0.1..." - just check - sed -i 's/(version [^)]*/(version 0.0.1/' dune-project - dune build --release - git add -A - git commit -m "Release v0.0.1" - git tag -a "v0.0.1" -m "Release v0.0.1" - @echo "To push: git push && git push --tags" - -secret-scan-trufflehog: - @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true diff --git a/affinescript/LICENSE b/affinescript/LICENSE deleted file mode 100644 index ec540b3..0000000 --- a/affinescript/LICENSE +++ /dev/null @@ -1,153 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2024-2025 Palimpsest Stewardship Council - -================================================================================ -PALIMPSEST-MPL LICENSE VERSION 1.0 -================================================================================ - -File-level copyleft with ethical use and quantum-safe provenance - -Based on Mozilla Public License 2.0 - --------------------------------------------------------------------------------- -PREAMBLE --------------------------------------------------------------------------------- - -This License extends the Mozilla Public License 2.0 (MPL-2.0) with provisions -for ethical use, post-quantum cryptographic provenance, and emotional lineage -protection. The base MPL-2.0 terms apply except where explicitly modified by -the Exhibits below. - -Like a palimpsest manuscript where each layer builds upon what came before, -this license recognizes that creative works carry history, context, and meaning -that transcend mere code or text. - --------------------------------------------------------------------------------- -SECTION 1: BASE LICENSE --------------------------------------------------------------------------------- - -This License incorporates the full text of Mozilla Public License 2.0 by -reference. The complete MPL-2.0 text is available at: -https://www.mozilla.org/en-US/MPL/2.0/ - -All terms, conditions, and definitions from MPL-2.0 apply except where -explicitly modified by the Exhibits in this License. - --------------------------------------------------------------------------------- -SECTION 2: ADDITIONAL DEFINITIONS --------------------------------------------------------------------------------- - -2.1. "Emotional Lineage" - means the narrative, cultural, symbolic, and contextual meaning embedded - in Covered Software, including but not limited to: protest traditions, - cultural heritage, trauma narratives, and community stories. - -2.2. "Provenance Metadata" - means cryptographically signed attribution information attached to or - associated with Covered Software, including author identities, timestamps, - modification history, and lineage references. - -2.3. "Non-Interpretive System" - means any automated system that processes Covered Software without - preserving or considering its Emotional Lineage, including but not - limited to: AI training pipelines, content aggregators, and automated - summarization tools. - -2.4. "Quantum-Safe Signature" - means a cryptographic signature using algorithms resistant to attacks - by quantum computers, as specified in Exhibit B. - --------------------------------------------------------------------------------- -SECTION 3: ETHICAL USE REQUIREMENTS --------------------------------------------------------------------------------- - -In addition to the rights and obligations under MPL-2.0: - -3.1. Emotional Lineage Preservation - You must make reasonable efforts to preserve and communicate the - Emotional Lineage of Covered Software when distributing or creating - derivative works. This includes maintaining narrative context, cultural - attributions, and symbolic meaning where documented. - -3.2. Non-Interpretive System Notice - If You use Covered Software as input to a Non-Interpretive System, You - must: - (a) document such use in a publicly accessible manner; and - (b) not claim that outputs of such systems carry the Emotional Lineage - of the original work without explicit permission from Contributors. - -3.3. Ethical Use Declaration - Commercial use of Covered Software requires acknowledgment that You have - read and understood Exhibit A (Ethical Use Guidelines) and agree to act - in good faith accordance with its principles. - -See Exhibit A for complete Ethical Use Guidelines. - --------------------------------------------------------------------------------- -SECTION 4: PROVENANCE REQUIREMENTS --------------------------------------------------------------------------------- - -4.1. Metadata Preservation - You must not strip, alter, or obscure Provenance Metadata from Covered - Software except where technically necessary and with clear documentation - of any changes. - -4.2. Quantum-Safe Provenance (Optional) - Contributors may sign their Contributions using Quantum-Safe Signatures. - If Quantum-Safe Signatures are present, You must preserve them in all - distributions. - -4.3. Lineage Chain - When creating derivative works, You should extend the provenance chain - to include Your own contributions, maintaining cryptographic linkage to - prior Contributors where feasible. - -See Exhibit B for Quantum-Safe Provenance specifications. - --------------------------------------------------------------------------------- -SECTION 5: GOVERNANCE --------------------------------------------------------------------------------- - -5.1. Stewardship Council - This License is maintained by the Palimpsest Stewardship Council, which - may issue clarifications, interpretive guidance, and future versions. - -5.2. Version Selection - You may use Covered Software under this version of the License or any - later version published by the Palimpsest Stewardship Council. - -5.3. Dispute Resolution - Disputes regarding interpretation of Ethical Use Requirements (Section 3) - should first be submitted to the Palimpsest Stewardship Council for - non-binding guidance before pursuing legal remedies. - --------------------------------------------------------------------------------- -SECTION 6: COMPATIBILITY --------------------------------------------------------------------------------- - -6.1. MPL-2.0 Compatibility - Covered Software under this License may be combined with software under - MPL-2.0. The combined work must comply with both licenses. - -6.2. Secondary Licenses - The Secondary License provisions of MPL-2.0 Section 3.3 apply to this - License. - --------------------------------------------------------------------------------- -EXHIBITS --------------------------------------------------------------------------------- - -Exhibit A - Ethical Use Guidelines -Exhibit B - Quantum-Safe Provenance Specification - -See separate files: -- EXHIBIT-A-ETHICAL-USE.txt -- EXHIBIT-B-QUANTUM-SAFE.txt - --------------------------------------------------------------------------------- -END OF PALIMPSEST-MPL LICENSE VERSION 1.0 --------------------------------------------------------------------------------- - -For questions about this License: -- Repository: https://github.com/hyperpolymath/palimpsest-license -- Council: contact via repository Issues diff --git a/affinescript/LICENSE-AGPL-3.0 b/affinescript/LICENSE-AGPL-3.0 deleted file mode 100644 index 883bcb6..0000000 --- a/affinescript/LICENSE-AGPL-3.0 +++ /dev/null @@ -1,647 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell and contributors - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License giving you legal permission to copy, distribute and/or -modify it. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the work -under this License, and how to view a copy of this License. If the -interface presents a list of user commands or options, such as a menu, -a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other parts -of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical medium - customarily used for software interchange, for a price no more - than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the corresponding source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally terminates -your license, and (b) permanently, if the copyright holder fails to -notify you of the violation by some reasonable means prior to 60 days -after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or run a -copy of the Program. Ancillary propagation of a covered work occurring -solely as a consequence of using peer-to-peer transmission to receive a -copy likewise does not require acceptance. However, nothing other than -this License grants you permission to propagate or modify any covered -work. These actions infringe copyright if you do not accept this License -by doing them. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that any -patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims owned -or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted by -this License, of making, using, or selling its contributor version, but -do not include claims that would be infringed only as a consequence of -further modification of the contributor version. For purposes of this -definition, "control" includes the right to grant patent sublicenses in a -manner consistent with the requirements of this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to make, -use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express license to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone to -copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, then -you must either (1) cause the Corresponding Source to be so available, -or (2) arrange to deprive yourself of the benefit of the patent license -for this particular work, or (3) arrange, in a manner consistent with -the requirements of this License, to extend the patent license to -downstream recipients. "Knowingly relying" means you have actual -knowledge that, but for the patent license, your conveying the covered -work in a country, or your recipient's use of the covered work in a -country, would infringe one or more identifiable patents in that country -that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically -granted under this License. You may not convey a covered work if you -are a party to an arrangement with a third party that is in the -business of distributing software, under which you make payment to the -third party based on the extent of your activity of conveying the work, -and under which the third party grants, to any of the parties who would -receive the covered work from you, a discriminatory patent license (a) -in connection with copies of the covered work conveyed by you (or copies -made from those copies), or (b) primarily for and in connection with -specific products or compilations that contain the covered work, unless -you entered into that arrangement, or that patent license was granted, -prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate -you to collect a royalty for further conveying from those to whom you -convey the Program, the only way you could satisfy both those terms and -this License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed under -version 3 of the GNU General Public License into a single combined work, -and to convey the resulting work. The terms of this License will -continue to apply to the part which is the covered work, but the special -requirements of the GNU General Public License, section 13, concerning -interaction through a network will apply to the combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU Affero General Public -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that numbered version or of -any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU Affero General Public -License, you may choose any version ever published by the Free Software -Foundation. - - If the Program specifies that a proxy can decide which future versions of -the GNU Affero General Public License can be used, that proxy's public -statement of acceptance of a version permanently authorizes you to choose -that version for the Program. - - Later license versions may give you additional or different permissions. -However, no additional obligations are imposed on any author or copyright -holder as a result of your choosing to follow a later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface should display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - See for more information. \ No newline at end of file diff --git a/affinescript/LICENSES/LICENSE b/affinescript/LICENSES/LICENSE deleted file mode 100644 index 5859f7b..0000000 --- a/affinescript/LICENSES/LICENSE +++ /dev/null @@ -1,154 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2024-2025 Palimpsest Stewardship Council - -================================================================================ -PALIMPSEST-MPL LICENSE VERSION 1.0 -================================================================================ - -File-level copyleft with ethical use and quantum-safe provenance - -Based on Mozilla Public License 2.0 - --------------------------------------------------------------------------------- -PREAMBLE --------------------------------------------------------------------------------- - -This License extends the Mozilla Public License 2.0 (MPL-2.0) with provisions -for ethical use, post-quantum cryptographic provenance, and emotional lineage -protection. The base MPL-2.0 terms apply except where explicitly modified by -the Exhibits below. - -Like a palimpsest manuscript where each layer builds upon what came before, -this license recognizes that creative works carry history, context, and meaning -that transcend mere code or text. - --------------------------------------------------------------------------------- -SECTION 1: BASE LICENSE --------------------------------------------------------------------------------- - -This License incorporates the full text of Mozilla Public License 2.0 by -reference. The complete MPL-2.0 text is available at: -https://www.mozilla.org/en-US/MPL/2.0/ - -All terms, conditions, and definitions from MPL-2.0 apply except where -explicitly modified by the Exhibits in this License. - --------------------------------------------------------------------------------- -SECTION 2: ADDITIONAL DEFINITIONS --------------------------------------------------------------------------------- - -2.1. "Emotional Lineage" - means the narrative, cultural, symbolic, and contextual meaning embedded - in Covered Software, including but not limited to: protest traditions, - cultural heritage, trauma narratives, and community stories. - -2.2. "Provenance Metadata" - means cryptographically signed attribution information attached to or - associated with Covered Software, including author identities, timestamps, - modification history, and lineage references. - -2.3. "Non-Interpretive System" - means any automated system that processes Covered Software without - preserving or considering its Emotional Lineage, including but not - limited to: AI training pipelines, content aggregators, and automated - summarization tools. - -2.4. "Quantum-Safe Signature" - means a cryptographic signature using algorithms resistant to attacks - by quantum computers, as specified in Exhibit B. - --------------------------------------------------------------------------------- -SECTION 3: ETHICAL USE REQUIREMENTS --------------------------------------------------------------------------------- - -In addition to the rights and obligations under MPL-2.0: - -3.1. Emotional Lineage Preservation - You must make reasonable efforts to preserve and communicate the - Emotional Lineage of Covered Software when distributing or creating - derivative works. This includes maintaining narrative context, cultural - attributions, and symbolic meaning where documented. - -3.2. Non-Interpretive System Notice - If You use Covered Software as input to a Non-Interpretive System, You - must: - (a) document such use in a publicly accessible manner; and - (b) not claim that outputs of such systems carry the Emotional Lineage - of the original work without explicit permission from Contributors. - -3.3. Ethical Use Declaration - Commercial use of Covered Software requires acknowledgment that You have - read and understood Exhibit A (Ethical Use Guidelines) and agree to act - in good faith accordance with its principles. - -See Exhibit A for complete Ethical Use Guidelines. - --------------------------------------------------------------------------------- -SECTION 4: PROVENANCE REQUIREMENTS --------------------------------------------------------------------------------- - -4.1. Metadata Preservation - You must not strip, alter, or obscure Provenance Metadata from Covered - Software except where technically necessary and with clear documentation - of any changes. - -4.2. Quantum-Safe Provenance (Optional) - Contributors may sign their Contributions using Quantum-Safe Signatures. - If Quantum-Safe Signatures are present, You must preserve them in all - distributions. - -4.3. Lineage Chain - When creating derivative works, You should extend the provenance chain - to include Your own contributions, maintaining cryptographic linkage to - prior Contributors where feasible. - -See Exhibit B for Quantum-Safe Provenance specifications. - --------------------------------------------------------------------------------- -SECTION 5: GOVERNANCE --------------------------------------------------------------------------------- - -5.1. Stewardship Council - This License is maintained by the Palimpsest Stewardship Council, which - may issue clarifications, interpretive guidance, and future versions. - -5.2. Version Selection - You may use Covered Software under this version of the License or any - later version published by the Palimpsest Stewardship Council. - -5.3. Dispute Resolution - Disputes regarding interpretation of Ethical Use Requirements (Section 3) - should first be submitted to the Palimpsest Stewardship Council for - non-binding guidance before pursuing legal remedies. - --------------------------------------------------------------------------------- -SECTION 6: COMPATIBILITY --------------------------------------------------------------------------------- - -6.1. MPL-2.0 Compatibility - Covered Software under this License may be combined with software under - MPL-2.0. The combined work must comply with both licenses. - -6.2. Secondary Licenses - The Secondary License provisions of MPL-2.0 Section 3.3 apply to this - License. - --------------------------------------------------------------------------------- -EXHIBITS --------------------------------------------------------------------------------- - -Exhibit A - Ethical Use Guidelines -Exhibit B - Quantum-Safe Provenance Specification - -See separate files: -- EXHIBIT-A-ETHICAL-USE.txt -- EXHIBIT-B-QUANTUM-SAFE.txt - --------------------------------------------------------------------------------- -END OF PALIMPSEST-MPL LICENSE VERSION 1.0 --------------------------------------------------------------------------------- - -For questions about this License: -- Repository: https://github.com/hyperpolymath/palimpsest-license -- Council: contact via repository Issues diff --git a/affinescript/LICENSES/LICENSE-AGPL-3.0 b/affinescript/LICENSES/LICENSE-AGPL-3.0 deleted file mode 100644 index 883bcb6..0000000 --- a/affinescript/LICENSES/LICENSE-AGPL-3.0 +++ /dev/null @@ -1,647 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell and contributors - - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License giving you legal permission to copy, distribute and/or -modify it. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the work -under this License, and how to view a copy of this License. If the -interface presents a list of user commands or options, such as a menu, -a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other parts -of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical medium - customarily used for software interchange, for a price no more - than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the corresponding source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; the -above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your license -from a particular copyright holder is reinstated (a) provisionally, -unless and until the copyright holder explicitly and finally terminates -your license, and (b) permanently, if the copyright holder fails to -notify you of the violation by some reasonable means prior to 60 days -after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or run a -copy of the Program. Ancillary propagation of a covered work occurring -solely as a consequence of using peer-to-peer transmission to receive a -copy likewise does not require acceptance. However, nothing other than -this License grants you permission to propagate or modify any covered -work. These actions infringe copyright if you do not accept this License -by doing them. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that any -patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims owned -or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted by -this License, of making, using, or selling its contributor version, but -do not include claims that would be infringed only as a consequence of -further modification of the contributor version. For purposes of this -definition, "control" includes the right to grant patent sublicenses in a -manner consistent with the requirements of this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to make, -use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express license to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone to -copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, then -you must either (1) cause the Corresponding Source to be so available, -or (2) arrange to deprive yourself of the benefit of the patent license -for this particular work, or (3) arrange, in a manner consistent with -the requirements of this License, to extend the patent license to -downstream recipients. "Knowingly relying" means you have actual -knowledge that, but for the patent license, your conveying the covered -work in a country, or your recipient's use of the covered work in a -country, would infringe one or more identifiable patents in that country -that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on -the non-exercise of one or more of the rights that are specifically -granted under this License. You may not convey a covered work if you -are a party to an arrangement with a third party that is in the -business of distributing software, under which you make payment to the -third party based on the extent of your activity of conveying the work, -and under which the third party grants, to any of the parties who would -receive the covered work from you, a discriminatory patent license (a) -in connection with copies of the covered work conveyed by you (or copies -made from those copies), or (b) primarily for and in connection with -specific products or compilations that contain the covered work, unless -you entered into that arrangement, or that patent license was granted, -prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate -you to collect a royalty for further conveying from those to whom you -convey the Program, the only way you could satisfy both those terms and -this License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed under -version 3 of the GNU General Public License into a single combined work, -and to convey the resulting work. The terms of this License will -continue to apply to the part which is the covered work, but the special -requirements of the GNU General Public License, section 13, concerning -interaction through a network will apply to the combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the Program -specifies that a certain numbered version of the GNU Affero General Public -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that numbered version or of -any later version published by the Free Software Foundation. If the -Program does not specify a version number of the GNU Affero General Public -License, you may choose any version ever published by the Free Software -Foundation. - - If the Program specifies that a proxy can decide which future versions of -the GNU Affero General Public License can be used, that proxy's public -statement of acceptance of a version permanently authorizes you to choose -that version for the Program. - - Later license versions may give you additional or different permissions. -However, no additional obligations are imposed on any author or copyright -holder as a result of your choosing to follow a later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface should display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - See for more information. \ No newline at end of file diff --git a/affinescript/LICENSES/LICENSE-PMPL-1.0 b/affinescript/LICENSES/LICENSE-PMPL-1.0 deleted file mode 100644 index ec540b3..0000000 --- a/affinescript/LICENSES/LICENSE-PMPL-1.0 +++ /dev/null @@ -1,153 +0,0 @@ -SPDX-License-Identifier: MPL-2.0 -SPDX-FileCopyrightText: 2024-2025 Palimpsest Stewardship Council - -================================================================================ -PALIMPSEST-MPL LICENSE VERSION 1.0 -================================================================================ - -File-level copyleft with ethical use and quantum-safe provenance - -Based on Mozilla Public License 2.0 - --------------------------------------------------------------------------------- -PREAMBLE --------------------------------------------------------------------------------- - -This License extends the Mozilla Public License 2.0 (MPL-2.0) with provisions -for ethical use, post-quantum cryptographic provenance, and emotional lineage -protection. The base MPL-2.0 terms apply except where explicitly modified by -the Exhibits below. - -Like a palimpsest manuscript where each layer builds upon what came before, -this license recognizes that creative works carry history, context, and meaning -that transcend mere code or text. - --------------------------------------------------------------------------------- -SECTION 1: BASE LICENSE --------------------------------------------------------------------------------- - -This License incorporates the full text of Mozilla Public License 2.0 by -reference. The complete MPL-2.0 text is available at: -https://www.mozilla.org/en-US/MPL/2.0/ - -All terms, conditions, and definitions from MPL-2.0 apply except where -explicitly modified by the Exhibits in this License. - --------------------------------------------------------------------------------- -SECTION 2: ADDITIONAL DEFINITIONS --------------------------------------------------------------------------------- - -2.1. "Emotional Lineage" - means the narrative, cultural, symbolic, and contextual meaning embedded - in Covered Software, including but not limited to: protest traditions, - cultural heritage, trauma narratives, and community stories. - -2.2. "Provenance Metadata" - means cryptographically signed attribution information attached to or - associated with Covered Software, including author identities, timestamps, - modification history, and lineage references. - -2.3. "Non-Interpretive System" - means any automated system that processes Covered Software without - preserving or considering its Emotional Lineage, including but not - limited to: AI training pipelines, content aggregators, and automated - summarization tools. - -2.4. "Quantum-Safe Signature" - means a cryptographic signature using algorithms resistant to attacks - by quantum computers, as specified in Exhibit B. - --------------------------------------------------------------------------------- -SECTION 3: ETHICAL USE REQUIREMENTS --------------------------------------------------------------------------------- - -In addition to the rights and obligations under MPL-2.0: - -3.1. Emotional Lineage Preservation - You must make reasonable efforts to preserve and communicate the - Emotional Lineage of Covered Software when distributing or creating - derivative works. This includes maintaining narrative context, cultural - attributions, and symbolic meaning where documented. - -3.2. Non-Interpretive System Notice - If You use Covered Software as input to a Non-Interpretive System, You - must: - (a) document such use in a publicly accessible manner; and - (b) not claim that outputs of such systems carry the Emotional Lineage - of the original work without explicit permission from Contributors. - -3.3. Ethical Use Declaration - Commercial use of Covered Software requires acknowledgment that You have - read and understood Exhibit A (Ethical Use Guidelines) and agree to act - in good faith accordance with its principles. - -See Exhibit A for complete Ethical Use Guidelines. - --------------------------------------------------------------------------------- -SECTION 4: PROVENANCE REQUIREMENTS --------------------------------------------------------------------------------- - -4.1. Metadata Preservation - You must not strip, alter, or obscure Provenance Metadata from Covered - Software except where technically necessary and with clear documentation - of any changes. - -4.2. Quantum-Safe Provenance (Optional) - Contributors may sign their Contributions using Quantum-Safe Signatures. - If Quantum-Safe Signatures are present, You must preserve them in all - distributions. - -4.3. Lineage Chain - When creating derivative works, You should extend the provenance chain - to include Your own contributions, maintaining cryptographic linkage to - prior Contributors where feasible. - -See Exhibit B for Quantum-Safe Provenance specifications. - --------------------------------------------------------------------------------- -SECTION 5: GOVERNANCE --------------------------------------------------------------------------------- - -5.1. Stewardship Council - This License is maintained by the Palimpsest Stewardship Council, which - may issue clarifications, interpretive guidance, and future versions. - -5.2. Version Selection - You may use Covered Software under this version of the License or any - later version published by the Palimpsest Stewardship Council. - -5.3. Dispute Resolution - Disputes regarding interpretation of Ethical Use Requirements (Section 3) - should first be submitted to the Palimpsest Stewardship Council for - non-binding guidance before pursuing legal remedies. - --------------------------------------------------------------------------------- -SECTION 6: COMPATIBILITY --------------------------------------------------------------------------------- - -6.1. MPL-2.0 Compatibility - Covered Software under this License may be combined with software under - MPL-2.0. The combined work must comply with both licenses. - -6.2. Secondary Licenses - The Secondary License provisions of MPL-2.0 Section 3.3 apply to this - License. - --------------------------------------------------------------------------------- -EXHIBITS --------------------------------------------------------------------------------- - -Exhibit A - Ethical Use Guidelines -Exhibit B - Quantum-Safe Provenance Specification - -See separate files: -- EXHIBIT-A-ETHICAL-USE.txt -- EXHIBIT-B-QUANTUM-SAFE.txt - --------------------------------------------------------------------------------- -END OF PALIMPSEST-MPL LICENSE VERSION 1.0 --------------------------------------------------------------------------------- - -For questions about this License: -- Repository: https://github.com/hyperpolymath/palimpsest-license -- Council: contact via repository Issues diff --git a/affinescript/LICENSING-GUIDE.adoc b/affinescript/LICENSING-GUIDE.adoc deleted file mode 100644 index 24b1c73..0000000 --- a/affinescript/LICENSING-GUIDE.adoc +++ /dev/null @@ -1,482 +0,0 @@ -== AffineScript Licensing Guide - -=== 📜 Comprehensive Licensing Information - -This document clarifies the licensing structure for the AffineScript -ecosystem, including game content, core technology, and related -projects. - -''''' - -=== 🏷️ Three-Tier Licensing Structure - -==== 1. *Game Content (AGPL-3.0-or-later)* - -*Applies to:* All game-specific assets, levels, scripts, and -modifications - -*Purpose:* Ensure game modifications remain open source and accessible -to the community - -*Key Requirements:* - Source code must be made available - Modifications -must be shared under same license - Network use must provide source -access - License and copyright notices preserved - -*Files Covered:* - Game logic and scripts (`+game.wasm+`) - Game levels -and assets (`+assets/+`) - Game data and configuration - Example -programs and modifications - -''''' - -==== 2. *Core Technology (MPL-2.0)* - -*Applies to:* AffineScript compiler, runtime, and development tools - -*Purpose:* Provide permissive licensing for language technology while -maintaining ethical use requirements - -*Key Requirements:* - Preserve license and copyright notices - Document -modifications - Follow ethical use guidelines - No copyleft requirements -for derived works - -*Files Covered:* - AffineScript compiler (`+compiler.wasm+`) - Standard -library (`+stdlib/+`) - Development tools (`+tools/+`) - Language server -and IDE integration - -''''' - -==== 3. *Foundational Technologies (MPL-2.0-derived)* - -*Applies to:* Gossamer, Burble, and other supporting technologies - -*Purpose:* Provide Mozilla Public License 2.0 base with additional -ethical use provisions - -*Key Requirements:* - Preserve MPL-2.0 requirements - Follow Palimpsest -ethical use guidelines - Document emotional lineage - Maintain -provenance metadata - -*Projects Covered:* - *Gossamer*: Linearly-typed webview shell - -*Burble*: High-assurance multiplayer communications - Supporting -libraries and frameworks - -''''' - -=== 📚 License Relationships - -[source,mermaid] ----- -graph TD - A[Game Content] -->|AGPL-3.0-or-later| B[Open Source Game] - C[Core Technology] -->|MPL-2.0| D[AffineScript Compiler] - E[Foundational Tech] -->|PMPL-1.0/MPL-2.0| F[Gossamer/Burble] - - B -->|Uses| D - B -->|Uses| F - D -->|Depends on| F ----- - -''''' - -=== 📋 Detailed License Breakdown - -==== AGPL-3.0-or-later (Game Content) - -*Full Name:* GNU Affero General Public License version 3.0 or later - -*Key Provisions:* - *Copyleft:* Strong copyleft - modifications must be -open source - *Network Use:* Source must be available for -network-accessible versions - *Patent Grant:* Automatic patent license -for contributors - *Compatibility:* Compatible with GPL-3.0 - -*When to Use:* - Game content and assets - Game modifications and -extensions - Player-created content - Game-specific examples - -*File Header:* - -[source,affinescript] ----- -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// -// This file is part of the AffineScript Game -// Licensed under AGPL-3.0-or-later ----- - -''''' - -==== MPL-2.0 (Core Technology) - -*Full Name:* Palimpsest Mutual Public License 1.0 or later - -*Base License:* Mozilla Public License 2.0 - -*Additional Provisions:* - *Emotional Lineage:* Preserve narrative and -cultural context - *Provenance Metadata:* Maintain cryptographic -attribution - *Ethical Use:* Follow community guidelines - -*Quantum-Safe:* Optional post-quantum signatures - -*Key Provisions:* - *File-Level Copyleft:* Strong copyleft at file level -- *Patent Grant:* Automatic patent license - *Compatibility:* Compatible -with MPL-2.0 - *Governance:* Palimpsest Stewardship Council oversight - -*When to Use:* - AffineScript compiler and tools - Standard library -modules - Development infrastructure - Language server and IDE plugins - -*File Header:* - -[source,ocaml] ----- -(* SPDX-License-Identifier: CC-BY-SA-4.0 *) -(* SPDX-FileCopyrightText: 2026 Palimpsest Stewardship Council *) -(* - * This file is part of AffineScript Core Technology - * Licensed under MPL-2.0 (based on MPL-2.0) - *) ----- - -''''' - -==== PMPL-1.0 / MPL-2.0-derived (Foundational Technologies) - -*Full Name:* Palimpsest Mutual Public License 1.0 (based on MPL-2.0) - -*Relationship to MPL-2.0:* - *Base:* Full MPL-2.0 text incorporated by -reference - *Extensions:* Additional sections for ethical use - -*Compatibility:* Fully compatible with MPL-2.0 projects - *Governance:* -Additional stewardship council provisions - -*Key Provisions:* - *File-Level Copyleft:* Strong copyleft at file level -- *Patent Grant:* Automatic patent license for contributors - *Secondary -Licensing:* Allows specified secondary licenses - *Modification -Requirements:* Clear modification documentation - -*When to Use:* - Gossamer (linearly-typed webview shell) - Burble -(high-assurance communications) - Supporting libraries and frameworks - -Infrastructure components - -*File Header:* - -[source,rust] ----- -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-License-Identifier: CC-BY-SA-4.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// -// This file is part of Gossamer/Burble Foundational Technologies -// Licensed under PMPL-1.0 (Palimpsest-MPL) based on MPL-2.0 -// Complete license: https://github.com/hyperpolymath/palimpsest-license ----- - -''''' - -=== 🎯 Repository Description and Tags - -==== Recommended Repository Description - -*Short Version (GitHub):* - -.... -AffineScript: The game developer's secret weapon. AGPL-3.0 game content with PMPL-1.0 core technology. Compiles to WASM with compiler-proven correctness. Built on Gossamer (PMPL) and Burble (PMPL) foundations. -.... - -*Long Version (README):* - -[source,markdown] ----- -# AffineScript: The Game Developer's Secret Weapon - -**AffineScript** is a revolutionary game development platform featuring: - -🎮 **Game Content** (AGPL-3.0-or-later) -- Open source game assets and modifications -- Community-driven development -- Ensured accessibility for all players - -💻 **Core Technology** (MPL-2.0) -- Affine-type programming language -- Compiler-proven correctness -- WebAssembly compilation -- Permissive tooling license - -🛡️ **Foundational Technologies** (PMPL-1.0/MPL-2.0) -- **Gossamer**: Linearly-typed webview shell -- **Burble**: High-assurance multiplayer communications -- Ethical use requirements -- Quantum-safe provenance - -**Built for:** Game developers who want bug-free code, type-safe game logic, and compiler-enforced resource management. - -**Licensing:** Dual licensing model ensures open game content while providing permissive tooling licenses. ----- - -==== Recommended GitHub Topics - -.... -retro-game, pmpl, palimpsest-mpl, mpl-2-0-derived, agpl-3-0, game-development, -wasm, affine-types, type-safety, game-engine, open-source, ethical-licensing, -quantum-safe, provenance, linear-types, resource-safety -.... - -==== Repository Tags - -*Version Tags:* - `+v0.1.0-alpha.1+` (Current) - `+v0.1.0-alpha+` (Alpha -base) - `+game-agpl+` (Game content license) - `+tech-pmpl+` (Technology -license) - -*Content Tags:* - `+game-content+` (AGPL-3.0 content) - `+compiler+` -(PMPL-1.0 technology) - `+gossamer+` (PMPL-1.0 foundation) - `+burble+` -(PMPL-1.0 foundation) - -''''' - -=== 📁 License Directory Structure - -.... -LICENSES/ -├── LICENSE # PMPL-1.0 (Primary) -├── LICENSE-AGPL-3.0 # AGPL-3.0 (Game Content) -├── LICENSE-PMPL-1.0 # PMPL-1.0 (Core Tech) -├── LICENSE-MPL-2.0 # MPL-2.0 (Reference) -├── EXHIBIT-A-ETHICAL-USE.txt # Ethical guidelines -├── EXHIBIT-B-QUANTUM-SAFE.txt # Quantum-safe specs -└── README.md # License guide -.... - -''''' - -=== 🔧 Addressing Dependabot Alerts - -==== atty Potential Unaligned Read (Rust) - -*Alert Summary:* - *Package:* atty (Rust) - *Version:* <= 0.2.14 - -*Issue:* Potential unaligned pointer dereference on Windows - -*Severity:* Medium (theoretical risk) - *Status:* Unmaintained package - -*Analysis:* - -[source,markdown] ----- -✅ **Actual Risk:** Low -- System allocator on Windows uses HeapAlloc -- HeapAlloc guarantees sufficient alignment -- Issue only manifests with custom global allocators - -⚠️ **Theoretical Risk:** -- Custom allocators could cause alignment issues -- Unaligned pointer dereference possible -- Potential for crashes or undefined behavior - -❌ **Mitigation Challenges:** -- Package unmaintained (last release: ~3 years ago) -- Maintainer unreachable -- No official patches available ----- - -*Recommended Actions:* - -===== 1. *Immediate (Low Effort)* - -[source,markdown] ----- -✅ Add to dependency documentation: ----- - -== Known Issues - -=== atty (Rust) - -* Version: 0.2.14 (via transitive dependency) -* Issue: Potential unaligned read on Windows -* Risk: Low (mitigated by System allocator) -* Status: Unmaintained -* Workaround: None needed (System allocator provides safety) - -.... -.... - -==== 2. *Short-Term (Medium Effort)* - -[source,markdown] ----- -🔄 Update Cargo.toml to document: -```toml -[package.metadata.dependency-issues] -atty = "Potential unaligned read (Windows only). Mitigated by System allocator. No action required." ----- - -🔄 Add to security policy: - -[source,markdown] ----- -### Known Vulnerabilities - -#### atty (Transitive Dependency) -- **CVE:** None assigned -- **Affected:** Windows systems with custom allocators -- **Mitigation:** System allocator provides safety -- **Status:** Monitoring for updates -- **Action:** None required for standard configurations ----- - -.... - -#### 3. **Long-Term (Future Consideration)** -```markdown -🚀 Evaluate alternatives when feasible: - -**Option 1: std::io::IsTerminal (Rust 1.70+)** -```rust -use std::io::IsTerminal as _; -let is_terminal = std::io::stdin().is_terminal(); -.... - -* ✅ Stable since Rust 1.70.0 -* ✅ No external dependencies -* ❌ Requires Rust 1.70+ - -*Option 2: is-terminal (Standalone Crate)* - -[source,toml] ----- -[dependencies] -is-terminal = "0.4" ----- - -* ✅ Actively maintained -* ✅ Supports older Rust versions -* ✅ Cross-platform -* ❌ Additional dependency - -*Option 3: Custom Implementation* - -[source,rust] ----- -#[cfg(windows)] -fn is_terminal() -> bool { - // Windows-specific implementation - unsafe { - let handle = winapi::um::processenv::GetStdHandle( - winapi::um::winbase::STD_INPUT_HANDLE - ); - let mut mode: winapi::um::wincon::DWORD = 0; - winapi::um::wincon::GetConsoleMode(handle, &mut mode) != 0 - } -} ----- - -* ✅ No dependencies -* ✅ Full control -* ❌ Platform-specific code -* ❌ Maintenance burden - -.... - -**Decision:** -```markdown -📋 **Current Status:** No action required - -✅ **Rationale:** -- System allocator mitigates risk -- No known exploits in wild -- Low severity issue -- Package used transitively (via clap) - -🔍 **Monitoring:** -- Watch for maintainer activity -- Track Rust ecosystem developments -- Re-evaluate at next major version - -🚀 **Future:** Consider migration when: -- Alternative provides clear benefits -- Migration cost justified -- Breaking changes acceptable -.... - -''''' - -=== 📝 License Compliance Checklist - -==== For Game Distributors - -* [ ] Include LICENSE-AGPL-3.0 file -* [ ] Include LICENSE-PMPL-1.0 file -* [ ] Provide source code access (AGPL requirement) -* [ ] Document modifications (AGPL requirement) -* [ ] Preserve copyright notices -* [ ] Include license guide - -==== For Technology Users - -* [ ] Include LICENSE-PMPL-1.0 file -* [ ] Preserve copyright notices -* [ ] Document modifications -* [ ] Follow ethical use guidelines -* [ ] Include license guide - -==== For Repository Maintainers - -* [ ] Maintain separate license files -* [ ] Update license headers -* [ ] Document licensing clearly -* [ ] Monitor dependency alerts -* [ ] Update security policy - -''''' - -=== 🔗 License Resources - -==== Full License Texts - -* *AGPL-3.0:* https://www.gnu.org/licenses/agpl-3.0.html -* *PMPL-1.0:* https://github.com/hyperpolymath/palimpsest-license -* *MPL-2.0:* https://www.mozilla.org/en-US/MPL/2.0/ - -==== License Identification - -* *SPDX AGPL-3.0:* `+AGPL-3.0-or-later+` -* *SPDX PMPL-1.0:* `+MPL-2.0+` -* *SPDX MPL-2.0:* `+MPL-2.0+` - -==== Compliance Tools - -* *REUSE:* https://reuse.software/ -* *FOSSA:* https://fossa.com/ -* *Licensee:* https://github.com/licensee/licensee - -''''' - -=== 🎯 Summary - -==== Licensing Structure - -.... -Game Content (AGPL-3.0) → Open game development -Core Technology (PMPL-1.0) → Permissive tooling -Foundational Tech (PMPL/MPL-2.0) → Ethical infrastructure -.... - -==== Key Points - -[arabic] -. *Dual licensing* ensures open games with permissive tools -. *PMPL-1.0* extends MPL-2.0 with ethical use requirements -. *AGPL-3.0* ensures game modifications remain open -. *All licenses* are OSI-approved and compatible -. *Clear separation* between game content and technology - -==== Action Items - -* [ ] Update repository description and tags -* [ ] Ensure license directory has all versions -* [ ] Document atty dependency status -* [ ] Add license compliance checklist -* [ ] Update contributing guidelines - -''''' - -*Last Updated:* March 31, 2026 *Version:* Alpha-1 *Status:* Complete -licensing documentation - -SPDX-License-Identifier: CC-BY-SA-4.0 AND MPL-2.0 AND MPL-2.0 -SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell and contributors diff --git a/affinescript/MAINTAINERS.adoc b/affinescript/MAINTAINERS.adoc deleted file mode 100644 index aa23a55..0000000 --- a/affinescript/MAINTAINERS.adoc +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Maintainers -:toc: preamble - -This document lists the maintainers of this project and their responsibilities. - -== Current Maintainers - -[cols="2,3,2",options="header"] -|=== -| Name | Role | Contact - -| Jonathan D.A. Jewell -| Lead Maintainer -| https://github.com/hyperpolymath[@hyperpolymath] -|=== - -== Responsibilities - -Maintainers are responsible for: - -* Reviewing and merging pull requests -* Triaging issues and feature requests -* Ensuring code quality and security standards -* Managing releases and versioning -* Upholding the project's code of conduct - -== Becoming a Maintainer - -Contributors who demonstrate: - -* Consistent, high-quality contributions -* Understanding of the project's goals and standards -* Constructive participation in discussions -* Commitment to the project's long-term health - -May be invited to become maintainers at the discretion of existing maintainers. - -== Decision Making - -* Routine decisions (bug fixes, minor improvements) can be made by any maintainer -* Significant changes require discussion and consensus among maintainers -* Breaking changes or major features should be discussed in issues before implementation - -== Contact - -For questions about project governance, open an issue or contact the maintainers listed above. diff --git a/affinescript/MODULE-SYSTEM-PROGRESS.adoc b/affinescript/MODULE-SYSTEM-PROGRESS.adoc deleted file mode 100644 index 52132df..0000000 --- a/affinescript/MODULE-SYSTEM-PROGRESS.adoc +++ /dev/null @@ -1,369 +0,0 @@ -== AffineScript Compiler Implementation Progress - -*Session Date:* 2026-01-23 *Status:* Phases 1 & 2 Complete - Moving to -Phase 3 - -=== 3-Phase Implementation Plan - -==== Phase 1: Module System ✅ COMPLETE - -*Goal:* Finish the remaining 10% of module system implementation - -*Blocking Issue:* Type information wasn’t being transferred during -imports - Symbols were registered but `+var_types+` hashtable entries -weren’t copied - Result: `+CannotInfer+` errors when using imported -functions - -*Solution Implemented:* - Created `+resolve_and_typecheck_module+` to -type-check modules before importing - Modified -`+import_resolved_symbols+` to copy type information: -`+ocaml match Hashtbl.find_opt source_types sym.Symbol.sym_id with | Some scheme -> Hashtbl.replace dest_types sym.Symbol.sym_id scheme | None -> ()+` -- Updated `+resolve_program_with_loader+` to return both resolution and -type contexts - Fixed signature issues (result type arity, Types.scheme -vs Typecheck.scheme) - -*Files Modified:* - lib/resolve.ml (+150 lines) - bin/main.ml -(integrated module loader) - -*Tests Passing:* - ✅ test_simple_import.affine - Single function import -- ✅ test_import.affine - Multiple imports (Core + Math) - ✅ -test_math_functions.affine - Complex math operations - -*Commit:* `+a1b2c3d+` "`fix: Transfer type information during module -imports`" - -==== Phase 2: Function Calls in WASM ✅ COMPLETE - -*Goal:* Implement function call compilation to WebAssembly - -*Problem:* WASM codegen couldn’t compile function calls - ExprApp -returned "`not yet supported`" - -*Solution Implemented:* - Added `+func_indices: (string * int) list+` to -codegen context - Implemented ExprApp case to: * Evaluate arguments -left-to-right * Look up function index from func_indices map * Generate -Call instruction with correct index - Modified TopFn to register -function name-to-index mappings before generation - -*Files Modified:* - lib/codegen.ml (~30 lines added) - -*Tests Passing:* - ✅ test_function_call.affine - Simple helper function -(returns 42) - ✅ test_recursive_call.affine - Factorial recursion -(returns 120) - ✅ test_multiple_calls.affine - Multiple functions + -composition (returns 135) - All tests verified with Node.js WASM -execution - -*Commit:* `+31a60c5+` "`feat: Implement function calls in WASM codegen -(Phase 2 complete)`" - -==== Phase 3: Advanced Type System Features 🔨 IN PROGRESS - -*Goal:* Expand type system capabilities for AffineScript’s unique -features - -*Remaining Type System Features:* - -===== 3.1 Dependent Types - -* Type-level computation -* Refined types (e.g., `+Vec n Int+` where n is a value) -* Proof-carrying code support - -===== 3.2 Row Polymorphism - -* Extensible records: `+{ x: Int | r }+` -* Polymorphic variants with row types -* Effect row types for effect system - -===== 3.3 Effect System Inference - -* Effect annotations: `+fn foo() -> Int [IO, State]+` -* Effect polymorphism -* Effect handler type checking -* Integration with borrow checker - -===== 3.4 Linear Types Refinement - -* Full affine type tracking -* Uniqueness types -* Integration with existing borrow checker - -===== 3.5 Higher-Kinded Types - -* Type constructors as parameters -* Functor, Applicative, Monad instances -* Generic programming abstractions - -*Implementation Strategy:* 1. Start with row polymorphism (foundation -for effects) 2. Add effect inference (builds on rows) 3. Implement -dependent types (most complex) 4. Refine linear types (integrate with -borrow checker) 5. Add higher-kinded types (advanced generics) - -*Current Status:* Planning phase - -''''' - -=== Module System Infrastructure (Phase 1 - Complete) - -==== 1. Module Loader Created ✅ - -*File:* `+lib/module_loader.ml+` (272 lines) - -*Features:* - Module path to file path resolution (`+Math.Geometry+` → -`+stdlib/Math/Geometry.affine+`) - Configurable search paths (stdlib, -current dir, additional paths) - Module file parsing and caching - -Circular dependency detection - Dependency loading (imports within -modules) - -*Configuration:* - `+AFFINESCRIPT_STDLIB+` environment variable support -- Default stdlib path: `+./stdlib+` - Search order: current dir → stdlib -→ additional paths - -==== 2. Resolution System Enhanced ✅ - -*File:* `+lib/resolve.ml+` (+150 lines) - -*Key Functions:* - `+resolve_and_typecheck_module+`: Resolve AND -type-check before importing - `+import_resolved_symbols+`: Import public -symbols with type info - `+import_specific_items+`: Import selected -symbols with type info - `+resolve_program_with_loader+`: Full program -resolution with modules - -*Features:* - Selective imports: `+use Core::{min, max}+` ✅ - Glob -imports: `+use Core::*+` ✅ - Visibility checking (Public, PubCrate) ✅ -- Type information transfer ✅ - -==== 3. Standard Library Fixed ✅ - -*Core.affine:* - Removed underscore-prefixed parameters - Removed -lambdas (parser limitation) - Added explicit `+return+` statements - -Status: ✅ Working - -*Math.affine:* - Converted `+const+` to functions - Removed float -operations (type checker limitation) - Added explicit `+return+` -statements - Status: ✅ Working - -=== Function Call Implementation (Phase 2 - Complete) - -==== Code Generation Enhancement - -*Context Enhancement:* - -[source,ocaml] ----- -type context = { - (* ... existing fields ... *) - func_indices : (string * int) list; (* name -> index map *) -} ----- - -*ExprApp Implementation:* - -[source,ocaml] ----- -| ExprApp (func_expr, args) -> - (* 1. Evaluate arguments left-to-right *) - let* (ctx_final, all_arg_code) = - List.fold_left (fun acc arg -> ...) (Ok (ctx, [])) args in - - (* 2. Look up function index *) - match func_expr with - | ExprVar id -> - match List.assoc_opt id.name ctx_final.func_indices with - | Some func_idx -> Ok (ctx_final, all_arg_code @ [Call func_idx]) - | None -> Error (UnboundVariable ...) - | _ -> Error (UnsupportedFeature "Indirect calls") ----- - -*Function Registration:* - -[source,ocaml] ----- -| TopFn fd -> - (* Register function name before generation *) - let func_idx = List.length ctx.funcs in - let ctx' = { ctx with - func_indices = ctx.func_indices @ [(fd.fd_name.name, func_idx)] - } in - (* Now gen_function can look up other functions *) ----- - -==== Test Coverage - -[cols=",,,",options="header",] -|=== -|Test |Feature |Expected |Result -|test_function_call.affine |Simple call |42 |✅ PASS -|test_recursive_call.affine |Recursion |120 |✅ PASS -|test_multiple_calls.affine |Composition |135 |✅ PASS -|=== - -=== Module System Features Status - -[cols=",,",options="header",] -|=== -|Feature |Status |Notes -|Module loading |✅ |File system search, parsing -|Dependency resolution |✅ |Recursive loading -|Circular dep detection |✅ |Prevents infinite loops -|Selective imports |✅ |`+use A::{x, y}+` -|Glob imports |✅ |`+use A::*+` -|Visibility checking |✅ |Public/PubCrate filtering -|Symbol registration |✅ |Symbols added to table -|Type information transfer |✅ |*FIXED* -|Re-exports |❌ |Not implemented -|Nested modules |❌ |Not implemented -|=== - -=== Known Limitations - -==== Parser Limitations - -[arabic] -. *No const declarations* - Had to convert to functions -. *No lambda expressions* - Removed from stdlib -. *No implicit returns* - Must use `+return+` everywhere -. *No underscore parameters* - `+_x+` not allowed - -==== Type Checker Limitations - -[arabic] -. *No Float comparisons* - Float operations removed -. *No function types as parameters* - Higher-order functions don’t work -yet -. *Limited polymorphism* - Working on row polymorphism -. *No dependent types* - Phase 3 feature -. *No effect inference* - Phase 3 feature - -==== WASM Codegen Limitations - -[arabic] -. *No indirect calls* - Function pointers not supported yet -. *No closures* - Would require heap allocation -. *No exceptions* - Effect system will handle this -. *Limited types* - Only I32/F64, no structs yet - -==== Module System Limitations - -[arabic] -. *No re-exports* - Can’t `+pub use+` to re-export -. *No nested modules* - Only flat hierarchy -. *No module-qualified calls* - Can’t call `+Math.pow()+` after -`+use Math+` - -=== Architecture Decisions - -==== 1. Module Loader is Parse-Only - -*Decision:* Module_loader only handles file loading and parsing - -*Rationale:* Avoids circular dependency between Module_loader and -Resolve modules - -*Benefits:* - Clean separation of concerns - No circular dependencies - -Resolve module controls all symbol resolution logic - -==== 2. Per-Module Symbol Tables - -*Decision:* Each loaded module gets its own symbol table during -resolution - -*Rationale:* Modules should have isolated namespaces - -*Benefits:* - Clean module boundaries - No symbol pollution between -modules - Easy to track what’s public vs private - -==== 3. Type-Check Before Import - -*Decision:* Modules are fully type-checked before their symbols are -imported - -*Rationale:* Ensures imported functions have valid types - -*Benefits:* - Type errors caught at module boundary - Type schemes -available for import - Cleaner error messages - -=== What We Have Now (After Phases 1 & 2) - -==== ✅ Complete - -* Lexer (tokens, spans, error reporting) -* Parser (full syntax, imports, patterns, effects) -* AST (comprehensive node types) -* Symbol resolution (scoping, modules, imports) -* Type checking (basic inference, annotations) -* Borrow checker (affine types, use-after-move) -* Interpreter (evaluation, standard library) -* REPL (interactive development) -* Module system (loading, importing, type transfer) -* WASM codegen (expressions, function calls) - -==== 🔨 Partial - -* Type system (basic inference works, advanced features pending) -* WASM codegen (basic features work, missing closures/structs) -* Standard library (Core + Math work, Option/Result need fixes) - -==== ❌ Not Started - -* Dependent types -* Row polymorphism -* Effect inference -* Higher-kinded types -* Advanced WASM features (closures, exceptions, structs) - -=== Next Steps (Phase 3) - -==== Immediate - -[arabic] -. Implement row polymorphism for records -. Add effect system type checking -. Integrate effects with borrow checker - -==== Short-term - -[arabic] -. Fix Option.affine and Result.affine (explicit returns) -. Add more stdlib modules -. Improve error messages - -==== Medium-term - -[arabic] -. Implement dependent types -. Add higher-kinded types -. Complete WASM features (closures, structs) - -==== Long-term - -[arabic] -. Self-hosting (compiler written in AffineScript) -. Proof-carrying code -. Formal verification integration - -=== Session Summary - -*Date:* 2026-01-23 *Tasks Completed:* Priority #1 and #2 from "`1 2 3`" -directive - -==== Phase 1: Module System (✅ Complete) - -* Fixed type information transfer during imports -* All module import tests passing -* Standard library usable - -==== Phase 2: Function Calls (✅ Complete) - -* Implemented WASM function call codegen -* All call tests passing (simple, recursive, composition) -* WASM output verified with Node.js - -*Total Changes:* - ~650 lines added (Phase 1) - ~30 lines added (Phase -2) - 12 files modified - 8 test files created - 2 major features -completed - -*Commits:* 1. Phase 1: Type information transfer fix 2. Phase 2: -Function call implementation - -*Current State:* Ready to begin Phase 3 (Advanced Type System) diff --git a/affinescript/NAVIGATION.adoc b/affinescript/NAVIGATION.adoc deleted file mode 100644 index c4d625c..0000000 --- a/affinescript/NAVIGATION.adoc +++ /dev/null @@ -1,199 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Hyperpolymath Contributors - -= AffineScript Repository Navigation Guide - -This guide helps you navigate the AffineScript repository structure. - -== Quick Start Files (Root Level) - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|**README.adoc** |Project overview and introduction -|**LICENSE** |MPL-2.0 license text -|**STATE.scm** |Current project state and progress -|=== - -== Source Code - -[cols="1,3"] -|=== -|Directory |Contents - -|**lib/** |OCaml library source code (core compiler) -|**bin/** |Executable entry points -|**runtime/** |Runtime system implementation -|**test/** |Test suites -|**examples/** |Example AffineScript programs -|**conformance/** |Conformance test suite -|**tools/** |Development and build tools -|=== - -== Documentation - -[cols="1,3"] -|=== -|Directory |Contents - -|**docs/specs/** |Language specifications -|**docs/governance/** |Community and contribution docs -|**docs/standards/** |Design decisions, roadmap, releases -|**docs/guides/** |Testing reports and guides -|**wiki/** |Extended documentation and wiki content -|=== - -**Key specification documents:** - -* link:docs/specs/affinescript-spec.md[AffineScript Specification] - Complete language spec -* link:docs/specs/SPEC.md[Core Specification] - Core language features - -**Governance documents:** - -* link:docs/governance/CONTRIBUTING.adoc[Contributing Guide] - How to contribute -* link:docs/governance/CODE_OF_CONDUCT.md[Code of Conduct] - Community standards -* link:docs/governance/SECURITY.md[Security Policy] - Reporting vulnerabilities -* link:docs/governance/MAINTAINERS.adoc[Maintainers] - Project maintainers - -**Standards and planning:** - -* link:docs/standards/ROADMAP.md[Roadmap] - Development roadmap -* link:docs/standards/DECISIONS.md[Decisions] - Architecture decisions -* link:docs/standards/RELEASE.md[Release Process] - Release procedures -* link:docs/standards/PALIMPSEST.adoc[Palimpsest Integration] - License integration - -**Guides:** - -* link:docs/guides/TESTING-REPORT.adoc[Testing Report (AsciiDoc)] - Test coverage report -* link:docs/guides/TESTING-REPORT.scm[Testing Report (Scheme)] - Machine-readable test report - -== Build System - -[cols="1,3"] -|=== -|Directory |Contents - -|**.build/** |Build configuration and tooling -|**.build/justfile** |Task automation (build, test, deploy) -|**.build/dune-project** |Dune build configuration (OCaml) -|**.build/affinescript.opam** |OPAM package definition -|**.build/Mustfile** |Must-use file enforcement -|**.build/.ocamlformat** |OCaml formatting rules -|=== - -== Project Metadata - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|**STATE.scm** |Current project state (symlink to .machine_readable/) -|**.machine_readable/** |Structured metadata for tooling -|**.claude/** |AI assistant context and instructions -|**hooks/** |Git hooks for development workflow -|=== - -== Hidden Directories - -[cols="1,3"] -|=== -|Directory |Purpose - -|**.build/** |Build configuration files -|**.claude/** |AI assistant instructions -|**.machine_readable/** |Machine-readable SCM metadata -|**.github/** |GitHub Actions workflows and templates -|**_build/** |OCaml/Dune build artifacts (gitignored) -|=== - -== Common Tasks - -=== Building the Compiler - -[source,bash] ----- -cd .build/ -just build # Build compiler -just test # Run tests -just install # Install locally ----- - -=== Running Tests - -[source,bash] ----- -cd .build/ -just test # Run all tests -just test-conformance # Run conformance tests ----- - -=== Development - -[source,bash] ----- -cd .build/ -just dev # Development mode -just watch # Watch mode for auto-rebuild -just fmt # Format code ----- - -=== Contributing - -1. Read link:docs/governance/CONTRIBUTING.adoc[Contributing Guide] -2. Check link:docs/governance/CODE_OF_CONDUCT.md[Code of Conduct] -3. Review link:docs/standards/DECISIONS.md[Architecture Decisions] -4. Follow link:docs/standards/ROADMAP.md[Development Roadmap] - -== Directory Tree Overview - -[source] ----- -affinescript/ -├── README.adoc # Start here -├── NAVIGATION.adoc # This file -├── LICENSE # Legal terms -├── STATE.scm # Project state -│ -├── lib/ # OCaml compiler source -├── bin/ # Executables -├── runtime/ # Runtime system -├── test/ # Test suites -├── examples/ # Example programs -├── conformance/ # Conformance tests -├── tools/ # Development tools -│ -├── docs/ # Documentation -│ ├── specs/ # Language specs -│ ├── governance/ # Community docs -│ ├── standards/ # Decisions & roadmap -│ └── guides/ # Testing & guides -│ -├── .build/ # Build system -│ ├── Justfile # Task runner -│ ├── dune-project # Dune config -│ ├── affinescript.opam # OPAM package -│ └── .ocamlformat # Code style -│ -├── wiki/ # Extended documentation -├── hooks/ # Git hooks -└── .machine_readable/ # Machine metadata ----- - -== Getting Help - -* **Issues**: https://github.com/hyperpolymath/affinescript/issues -* **Discussions**: https://github.com/hyperpolymath/affinescript/discussions -* **Security**: See link:docs/governance/SECURITY.md[Security Policy] - -== Document Conventions - -* **`.adoc`** - AsciiDoc format (preferred for complex docs) -* **`.md`** - Markdown format (for GitHub compatibility) -* **`.scm`** - Guile Scheme (machine-readable metadata) -* **`.ml`** - OCaml source code - ---- - -**Last updated:** 2026-01-23 diff --git a/affinescript/PHASE3-ASSESSMENT.adoc b/affinescript/PHASE3-ASSESSMENT.adoc deleted file mode 100644 index f2c1693..0000000 --- a/affinescript/PHASE3-ASSESSMENT.adoc +++ /dev/null @@ -1,568 +0,0 @@ -== Phase 3: Advanced Type System - Implementation Assessment - -*Date:* 2026-01-23 *Last Updated:* 2026-01-23 21:30 UTC *Status:* Row -Polymorphism COMPLETE ✅ - -=== Executive Summary - -Phase 3 infrastructure is *surprisingly complete*! The type system -(lib/types.ml) and unification (lib/unify.ml) already implement: - -* ✅ *Row types and row polymorphism* - WORKING END-TO-END! -* ✅ *Effect types and effect inference* - WORKING END-TO-END! -* ✅ *Effect polymorphism* - WORKING END-TO-END! -* ✅ Type-level naturals (dependent types foundation) -* ✅ Refinement types with predicates -* ✅ Higher-kinded types (KArrow of kind * kind) -* ✅ Quantification (TForall, TExists) - -*Major Progress (2026-01-23):* 1. *Row polymorphism* is now fully -functional! Functions can accept extensible records and work correctly -with records of different shapes. 2. *Effect inference* is working! -Function effects are inferred from their bodies, effect variables are -properly generalized and instantiated. 3. *Effect polymorphism* works! -Functions can be polymorphic over effects. - -=== Feature-by-Feature Assessment - -==== 3.1 Row Polymorphism ✅ COMPLETE - -*Implementation Status:* - ✅ Type representation (types.ml) - ✅ -Unification with row rewriting (unify.ml) - ✅ Parser support for -`+{x: T, ..rest}+` syntax - ✅ Type checker generalization of row -variables - ✅ Type checker instantiation with fresh row variables - ✅ -End-to-end testing passing - -*What Was Fixed (2026-01-23):* - -[arabic] -. *Parser Grammar (lib/parser.mly:264-295)*: Fixed shift/reduce -conflicts when parsing `+{x: Int, ..rest}+` syntax by creating custom -recursive grammar rules for record types. -. *Type Scheme Instantiation (lib/typecheck.ml:122-172)*: Added row -variable substitution so each function call gets fresh row variables: -+ -[source,ocaml] ----- -let row_subst = List.map (fun v -> - (v, fresh_rowvar ctx.level) -) scheme.sc_rowvars in ----- -. *Type Scheme Generalization (lib/typecheck.ml:85-162)*: Added -`+collect_rowvars+` function to collect unbound row variables during -generalization: -+ -[source,ocaml] ----- -let rec collect_rowvars (ty : ty) (acc : rowvar list) : rowvar list = - (* Recursively collect RVar nodes at appropriate levels *) ----- -. *Function Definition Generalization (lib/typecheck.ml:1246-1266)*: -* Made `+context.level+` mutable -* Enter level+1 when processing function signatures -* Generalize at outer level to capture type/row variables -* Use `+bind_var_scheme+` instead of `+bind_var+` -+ -This ensures type variables and row variables in function signatures are -properly generalized as polymorphic. - -*Test Cases Passing:* - -[source,bash] ----- -✓ tests/types/test_row_simple.affine # Basic row polymorphism -✓ tests/types/test_parse_row_type.affine # Parser validation -✓ tests/types/test_row_polymorphism.affine # Complex multi-call test ----- - -*Example Working Code:* - -[source,affinescript] ----- -fn get_x(r: {x: Int, ..rest}) -> Int { - return r.x; -} - -fn main() -> Int { - let r1 = {x: 10}; // Only x field - let r2 = {x: 20, y: 30}; // Extra y field - let r3 = {x: 5, y: 10, z: 15}; // Extra y, z fields - - return get_x(r1) + get_x(r2) + get_x(r3); // All work! -} ----- - -*Technical Details:* - -The fix addressed a subtle bug in let-polymorphism: When a function is -defined at level 0 and its type annotation creates row variables at -level 0, generalization would fail because it only collected variables -where `+lvl > ctx.level+` (0 > 0 = false). - -Solution: Enter level+1 before processing function signatures, then -generalize at level 0, ensuring all signature variables are at level 1 -and get captured. - -==== 3.2 Dependent Types - -*Type System Support:* - -[source,ocaml] ----- -(* types.ml *) -type nat_expr = - | NLit of int - | NVar of string - | NAdd of nat_expr * nat_expr - | NSub of nat_expr * nat_expr - | NMul of nat_expr * nat_expr - | NLen of string - -type ty = - | TDepArrow of string * ty * ty * eff (* Dependent function *) - | TNat of nat_expr (* Type-level natural *) - | TRefined of ty * predicate (* Refinement types *) ----- - -*Unification Support:* - -[source,ocaml] ----- -(* unify.ml:217-221 *) -| (TNat n1, TNat n2) -> - if nat_eq (normalize_nat n1) (normalize_nat n2) then Ok () - else Error (TypeMismatch (t1, t2)) ----- - -*Status:* - ✅ Type representation exists - ✅ Unification implemented - -✅ Parser support for dependent arrow `+(x: T) -> U+` *NEW!* - ✅ Parser -support for refined types `+T where (P)+` *NEW!* - ✅ Parser support for -nat expressions and predicates - ✅ Type checker integration for -dependent types (already exists!) - ❌ End-to-end testing needed - -*What Was Added (2026-01-23):* 1. ✅ Parser grammar for dependent arrow -types: `+(x: T) -> U+` and `+(x: T) -{E}-> U+` 2. ✅ Parser grammar for -refined types: `+T where (P)+` 3. ✅ Nat expression parsing: literals, -variables, +, -, * 4. ✅ Predicate parsing: <, <=, >, >=, ==, !=, !, &&, -|| - -*What’s Needed:* 1. ❌ End-to-end testing with actual dependent -functions 2. ❌ SMT solver integration for refinement checking (future) - -==== 3.3 Effect System - -*Type System Support:* - -[source,ocaml] ----- -(* types.ml *) -type eff = - | EPure (* No effects *) - | EVar of effvar_state ref (* Effect variable *) - | ESingleton of string (* Single effect: IO, State, etc *) - | EUnion of eff list (* Effect union: IO + State *) - -type ty = - | TArrow of ty * ty * eff - | TDepArrow of string * ty * ty * eff ----- - -*Unification Support:* - -[source,ocaml] ----- -(* unify.ml:292-357 - COMPLETE IMPLEMENTATION *) -and unify_eff (e1 : eff) (e2 : eff) : unit result = - (* Handles: - - Pure effects - - Effect variables with occurs check - - Singleton effects - - Effect unions (set-based unification) - *) ----- - -*Status:* - ✅ Type representation exists - ✅ Unification fully -implemented - ❓ Parser support for effect annotations - ❌ Effect -inference not implemented - -*What’s Needed:* 1. Type checker effect inference 2. Effect polymorphism -(effect variables in schemes) 3. Integration with borrow checker - -==== 3.4 Linear Types (Affine) - -*Status:* - ✅ Borrow checker already implements affine types! - ✅ -Use-after-move checking works - ✅ Ownership tracking implemented - -*What’s Needed:* - Integration with effect system - Quantity types (QTT) -fully wired up - -==== 3.5 Higher-Kinded Types - -*Type System Support:* - -[source,ocaml] ----- -(* types.ml *) -type kind = - | KType - | KNat - | KRow - | KEffect - | KArrow of kind * kind (* Higher-order kind! *) - -type ty = - | TForall of tyvar * kind * ty (* Universal quantification *) - | TApp of ty * ty list (* Type application *) ----- - -*Status:* - ✅ Kind system with higher-order kinds exists - ✅ Type -application exists - ✅ Parser support for kind annotations -`+[F: Type -> Type]+` *NEW!* - ✅ Kind checking functions implemented -*NEW!* - ❌ Kind checking not integrated into type definitions yet - ❌ -Generic programming abstractions not implemented yet - -*What Was Added (2026-01-23):* - -*Parser Support* (lib/parser.mly:164-179): - Kind annotations on type -parameters: `+[F: Type -> Type, A, B]+` - Arrow kinds: `+Type -> Type+`, -`+Type -> Type -> Type+` - Base kinds: `+Type+`, `+Nat+`, `+Row+`, -`+Effect+` - -*Kind Checking Functions* (lib/typecheck.ml:442-533): - -[source,ocaml] ----- -(** Infer the kind of a type *) -let rec infer_kind (ctx : context) (ty : ty) : kind result - -(** Check a type has an expected kind *) -and check_kind (ctx : context) (ty : ty) (expected : kind) : unit result - -(** Check type application kinds *) -and check_kind_app (ctx : context) (con_kind : kind) (args : ty list) : kind result ----- - -*Built-in Type Constructor Kinds:* - `+Vec : Nat -> Type -> Type+` - -`+Array : Type -> Type+` - `+List : Type -> Type+` - -`+Option : Type -> Type+` - `+Result : Type -> Type -> Type+` - -*Example Working Code:* - -[source,affinescript] ----- -// Higher-kinded type parameter -fn map[F: Type -> Type, A, B](fa: F[A], f: A -> B) -> F[B] { - return fa; -} - -// Multiple higher-kinded parameters -fn apply[F: Type -> Type, G: Type -> Type, A](f: F[A], g: G[A]) -> F[A] { - return f; -} ----- - -*Test File:* tests/types/test_hkt_parsing.affine ✅ PASSES - -*What’s Needed:* 1. ❌ Integrate kind checking into type definitions 2. -❌ Integrate kind checking into function type checking 3. ❌ Generic -programming abstractions (Functor, Monad traits) - -=== Implementation Progress - -==== Row Polymorphism: Complete Timeline (2026-01-23) - -*Session Start*: ~18:00 UTC - Started with parser shift/reduce conflicts -- Parser couldn’t handle `+{x: Int, ..rest}+` syntax - -*18:30 - Parser Fix* - Rewrote grammar rules to eliminate ambiguity - -Custom recursive rules for `+record_type_body+` and -`+record_fields_with_row+` - All parser conflicts eliminated - -*19:00 - Type Checker Investigation* - Discovered `+instantiate+` wasn’t -creating fresh row variables - Discovered `+generalize+` wasn’t -collecting row variables - Fixed both functions to handle row variables - -*20:00 - First Success* - Simple test (single function call) passing - -Complex test (multiple calls) still failing - Error: -`+LabelNotFound("y")+` on second call - -*20:30 - Root Cause Analysis* - Isolated issue: First call with -`+{x: Int}+` works - Second call with `+{x: Int, y: Int}+` fails - Row -variable being bound to `+REmpty+` on first call, affecting second call - -*21:00 - Generalization Bug Found* - Function types not being -generalized before binding - `+bind_var+` creates scheme with empty -variable lists - Row variables created at level 0, generalization at -level 0 - Condition `+lvl > ctx.level+` fails (0 > 0 = false) - -*21:15 - Final Fix* - Made `+context.level+` mutable - Enter level+1 -when processing function signatures - Generalize at outer level to -capture variables - Use `+bind_var_scheme+` with proper scheme - -*21:30 - All Tests Passing ✅* - Simple test: ✅ - Parse test: ✅ - -Complex test: ✅ - Basic functions: ✅ - Generic functions: ✅ - -*Total Implementation Time:* ~3.5 hours (parser + type checker + -debugging) - -=== Infrastructure vs Integration Gap - -==== What We Have (Infrastructure) - -[cols=",,",options="header",] -|=== -|Component |Status |Lines -|Type representation |✅ Complete |types.ml (423 lines) -|Unification algorithm |✅ Complete |unify.ml (366 lines) -|Row unification |✅ Complete |unify.ml (55 lines) -|Effect unification |✅ Complete |unify.ml (66 lines) -|Occurs checks |✅ Complete |unify.ml (various) -|Kind system |✅ Complete |types.ml (6 kinds) -|Borrow checker |✅ Complete |borrow.ml (580 lines) -|*Row polymorphism* |✅ *Complete* |*End-to-end* -|=== - -==== What We Need (Integration) - -[cols=",,",options="header",] -|=== -|Component |Status |Estimated Work -|[line-through]#Row polymorphism# |✅ DONE |[line-through]#2-3 hours# -3.5 hours actual - -|Effect inference |❌ |4-6 hours - -|Dependent type checking |❌ |8-12 hours - -|Parser for effect syntax |❌ |1-2 hours - -|Higher-kinded type checking |❌ |6-10 hours - -|SMT integration (refinements) |❌ |Future work -|=== - -=== Immediate Next Steps - -==== ✅ Step 1: Enable Row Polymorphism (COMPLETE) - -*Goal:* Make row polymorphism work end-to-end ✅ - -*Completed Tasks:* 1. ✅ Fix parser for `+{x: T, ..rest}+` syntax 2. ✅ -Update type checker to introduce row variables 3. ✅ Fix generalization -to capture row variables 4. ✅ Test with multiple scenarios 5. ✅ Verify -extensible records work correctly - -==== ✅ Step 2: Effect Inference (MOSTLY COMPLETE) - -*Goal:* Infer and check effects automatically ✅ - -*Completed Tasks:* 1. ✅ Added effect variable collection to -generalization (lib/typecheck.ml:149-191) 2. ✅ Added effect variable -substitution to instantiation (lib/typecheck.ml:195-277) 3. ✅ Changed -function definitions to use fresh effect variables -(lib/typecheck.ml:1321-1338) 4. ✅ Unify function body effects with -declared effects 5. ✅ Effect propagation already implemented -(union_eff, synth_app) - -*What Was Fixed (2026-01-23):* - -[arabic] -. *Generalization - Effect Variable Collection*: Added -`+collect_effvars+` function that recursively collects unbound effect -variables from: -* TArrow and TDepArrow (function effects) -* EUnion (effect unions) -* EVar (effect variables at appropriate levels) -. *Instantiation - Effect Variable Substitution*: Added -`+apply_subst_eff+` function that substitutes effect variables with -fresh ones during type scheme instantiation. -. *Function Definition Effect Inference*: Changed function definitions -from using hardcoded `+EPure+` to: -* Create fresh effect variable for each function -* Check body and unify inferred effect with function effect -* Properly generalize effect variables - -*Test Cases Passing:* - -[source,bash] ----- -✓ tests/types/test_effect_inference.affine # Pure function composition -✓ tests/types/test_row_polymorphism.affine # Still works with effect changes -✓ tests/types/test_row_simple.affine # Still works ----- - -*Known Limitation:* - Lambda parameter scope bug (pre-existing, not -caused by effect inference) - Multiple lambda calls fail due to -parameter binding leaking into outer scope - This is a separate issue -that needs fixing independently - -*Example Working Code:* - -[source,affinescript] ----- -// Pure function - effect inferred as EPure -fn pure_add(x: Int, y: Int) -> Int { - return x + y; -} - -// Function calling pure functions - also inferred as pure -fn compound_pure(x: Int) -> Int { - let a = pure_add(x, 10); - let b = pure_add(a, 20); - return b; -} - -fn main() -> Int { - return compound_pure(5); // All effects properly inferred! -} ----- - -==== ✅ Step 3: Dependent Type Parsing (COMPLETE) - -*Goal:* Support parsing dependent functions and refinement types ✅ - -*Completed Tasks:* 1. ✅ Parser support for `+(x: T) -> U+` dependent -arrow syntax 2. ✅ Parser support for `+T where (P)+` refined type -syntax 3. ✅ Parser support for nat expressions (literals, vars, +, -, -*) 4. ✅ Parser support for predicates (<, <=, >, >=, ==, !=, !, &&, ||) -5. ✅ Type checker integration already exists (instantiate_dep_arrow in -constraint.ml) - -*What Was Added (2026-01-23):* - -*Parser Grammar* (lib/parser.mly:244-273): - -[source,ocaml] ----- -type_expr_arrow: - | LPAREN param = ident COLON param_ty = type_expr RPAREN ARROW ret = type_expr_arrow - { TyDepArrow { da_param = param; da_param_ty = param_ty; - da_ret_ty = ret; da_eff = None } } - | LPAREN param = ident COLON param_ty = type_expr RPAREN - MINUS LBRACE eff = effect_expr RBRACE ARROW ret = type_expr_arrow - { TyDepArrow { da_param = param; da_param_ty = param_ty; - da_ret_ty = ret; da_eff = Some eff } } - -type_expr_refined: - | ty = type_expr_primary WHERE LPAREN pred = predicate RPAREN - { TyRefined (ty, pred) } ----- - -*Test Case:* - -[source,affinescript] ----- -// Dependent arrow type -fn dep_func(f: (x: Int) -> Int) -> Int { return 0; } - -// Refined type with predicate -fn take_positive(x: Int where (x > 0)) -> Int { return x; } - -// Dependent arrow with effect -fn dep_with_eff(f: (x: Int) -{IO}-> Int) -> Int { return 0; } ----- - -*Test File:* tests/types/test_dependent_parsing.affine ✅ PASSES - -=== Testing Strategy - -==== ✅ Row Polymorphism Tests (COMPLETE) - -* ✅ tests/types/test_row_simple.affine - Basic usage -* ✅ tests/types/test_parse_row_type.affine - Parser validation -* ✅ tests/types/test_row_polymorphism.affine - Complex scenarios - -==== ✅ Effect System Tests (COMPLETE) - -* ✅ tests/types/test_effect_inference.affine - Pure function -composition - -==== ✅ Dependent Types Tests (PARSING COMPLETE) - -* ✅ tests/types/test_dependent_parsing.affine - Parser validation for -dependent arrows and refinements - -==== ✅ Higher-Kinded Types Tests (COMPLETE) - -* ✅ tests/types/test_hkt_parsing.affine - Parser validation for kind -annotations and type applications -* ✅ tests/types/test_kind_checking.affine - Kind checking integration - -==== ✅ Generic Programming Tests (COMPLETE) - -* ✅ tests/types/test_traits.affine - Trait definitions with -higher-kinded types -* ✅ tests/types/test_generic_programming.affine - Functor, Applicative, -Monad traits - -==== ✅ End-to-End Tests (COMPLETE) - -* ✅ tests/types/test_dependent_e2e.affine - Dependent types with -refinements in practice - -=== Conclusion - -*Phase 3 Status:* 95% Complete ✨ - -*Breakdown:* - Infrastructure (types, unification): 95% ✅ - Row -Polymorphism: 100% ✅ - Effect Inference: 85% ✅ - Effect Polymorphism: -100% ✅ - Dependent Types: 95% ✅ (parsing + e2e tests complete) - -Higher-Kinded Types: 90% ✅ *UPDATED!* (kind checking integrated) - -Generic Programming: 90% ✅ *NEW!* (traits + HKT working) - Testing: 80% -✅ *Improved!* - -*What Changed Today (2026-01-23):* - Row polymorphism: 40% → 100% -complete ✅ - Effect inference: 30% → 85% complete ✅ - Effect -polymorphism: 0% → 100% complete ✅ - Dependent types: 40% → 95% -complete ✅ *UPDATED!* - Higher-kinded types: 20% → 90% complete ✅ -*UPDATED!* - Generic programming: 0% → 90% complete ✅ *NEW!* - Added -parser support for dependent arrows `+(x: T) -> U+` ✅ - Added parser -support for refined types `+T where (P)+` ✅ - Added parser support for -kind annotations `+[F: Type -> Type]+` ✅ - Implemented kind checking -functions (infer_kind, check_kind) ✅ - Integrated kind checking into -type and function definitions ✅ *NEW!* - Created comprehensive test -suite for traits and generic programming ✅ *NEW!* - Added 12 passing -test files (all advanced type system features) - Fixed critical bugs in -parser, generalization, and instantiation - -*Critical Path:* 1. [line-through]#Row polymorphism# ✅ *COMPLETE* 2. -[line-through]#Effect inference# ✅ *MOSTLY COMPLETE* (lambda scope bug -separate issue) 3. [line-through]#Dependent type parsing# ✅ *COMPLETE* -4. [line-through]#Higher-kinded type parsing# ✅ *COMPLETE* 5. -[line-through]#Kind checking implementation# ✅ *COMPLETE* 6. -Integration work (kind checking into type definitions) - NEXT - -*Good News:* - Row polymorphism is production-ready! ✅ - Effect -inference working for regular functions! ✅ - Effect variables properly -generalized and instantiated ✅ - Effect polymorphism allows functions -to work with any effect ✅ - Dependent type parsing complete! ✅ - -Refined type parsing complete! ✅ - Type checking infrastructure for -dependent types already exists! ✅ - Higher-kinded type parsing -complete! ✅ - Kind checking functions implemented! ✅ - Kind checking -integrated into type and function definitions! ✅ *NEW!* - End-to-end -dependent type tests working! ✅ *NEW!* - Generic programming with -traits and HKTs complete! ✅ *NEW!* - *12 comprehensive tests passing!* -✅ *NEW!* - -*Known Issues:* - Lambda parameter scope bug (pre-existing, separate -from Phase 3) - Multiple lambda uses fail due to parameter binding issue -- Not related to effect inference implementation - -*Estimated Time to Complete Remaining Phase 3 Features:* - -[line-through]#Effect inference#: ✅ *DONE* (2 hours actual) - -[line-through]#Dependent type parsing#: ✅ *DONE* (0.5 hours actual) - -[line-through]#Higher-kinded type parsing + kind checking#: ✅ *DONE* -(0.5 hours actual) - [line-through]#Kind checking integration#: ✅ -*DONE* (1 hour actual) - [line-through]#End-to-end dependent type -tests#: ✅ *DONE* (0.5 hours actual) - [line-through]#Generic -programming abstractions#: ✅ *DONE* (1 hour actual) - Lambda scope fix: -1-2 hours (not Phase 3, separate bug) - SMT integration for refinement -checking: Future work - *Total Remaining: 1-2 hours (lambda scope bug -only)* - -*Original Estimate:* 22-34 hours *Time Spent:* - Row polymorphism: 3.5 -hours - Effect inference: 2 hours - Dependent type parsing: 0.5 hours - -Higher-kinded types: 0.5 hours - Kind checking integration: 1 hour - -End-to-end dependent tests: 0.5 hours - Generic programming: 1 hour - -*Total: 9 hours* *Remaining:* 1-2 hours (lambda scope bug, not Phase 3 -work) *Efficiency:* 95% complete in 26% of estimated time! 🚀 diff --git a/affinescript/PHASE3-COMPLETE.adoc b/affinescript/PHASE3-COMPLETE.adoc deleted file mode 100644 index 6ec8661..0000000 --- a/affinescript/PHASE3-COMPLETE.adoc +++ /dev/null @@ -1,406 +0,0 @@ -== Phase 3: Advanced Type System - COMPLETE! 🎉 - -*Date:* 2026-01-23 *Total Time:* ~10 hours *Status:* 100% Complete! - -=== 🏆 Achievement Summary - -Phase 3 is *essentially complete*! AffineScript now has a -*research-grade advanced type system* with features found only in -cutting-edge languages. - -=== ✅ Completed Features - -==== 0. Lambda Scope Bug Fix (100%) ⭐ - -*Implementation:* Save/restore pattern for lambda parameter bindings -*Time:* 1 hour *Date:* 2026-01-24 - -*Problem:* Lambda parameters were bound to the type checking context but -never removed, causing them to leak into outer scope and interfere with -subsequent lambda definitions using the same parameter names. - -*Solution:* Implemented save-restore pattern for variable bindings: 1. -Save existing bindings for parameter names before binding lambda -parameters 2. Bind lambda parameters temporarily for body type checking -3. Remove lambda parameter bindings after type checking 4. Restore -original bindings - -*Implementation:* - Added `+save_bindings+` helper function -(lib/typecheck.ml:68-77) - Added `+restore_bindings+` helper function -(lib/typecheck.ml:79-83) - Added `+remove_bindings+` helper function -(lib/typecheck.ml:85-90) - Modified ExprLambda handling in `+synth+` -function (lib/typecheck.ml:626-646) - Modified ExprLambda handling in -`+check+` function (lib/typecheck.ml:982-997) - -*Test:* tests/types/test_lambda_scope_simple.affine ✓ Passes - -==== 1. Row Polymorphism (100%) - -*Implementation:* Extensible record types with row variables *Time:* 3.5 -hours - -*Features:* - Functions can accept records with extra fields - Row -variables properly generalized and instantiated - Works with nested and -complex record types - -*Example:* - -[source,affinescript] ----- -fn get_x(r: {x: Int, ..rest}) -> Int { - return r.x; -} - -fn main() -> Int { - let r1 = {x: 10}; // Only x - let r2 = {x: 20, y: 30}; // Extra y field - let r3 = {x: 5, y: 10, z: 15}; // Extra y, z fields - return get_x(r1) + get_x(r2) + get_x(r3); // All work! -} ----- - -==== 2. Effect Inference (85%) - -*Implementation:* Automatic effect inference from function bodies -*Time:* 2 hours - -*Features:* - Effects inferred from function bodies - Effect variables -generalized and instantiated - Effect unions for combining multiple -effects - Functions polymorphic over effects - -*Example:* - -[source,affinescript] ----- -fn pure_add(x: Int, y: Int) -> Int { - return x + y; -} - -fn compound_pure(x: Int) -> Int { - let a = pure_add(x, 10); - let b = pure_add(a, 20); - return b; // Effect automatically inferred as pure! -} ----- - -*Known Limitation:* Lambda parameter scope bug (pre-existing, separate -issue) - -==== 3. Effect Polymorphism (100%) - -*Implementation:* Functions work with any effect *Time:* Included in -effect inference - -*Features:* - Higher-order functions that work with any effect - Effect -variables in type schemes - Automatic effect unification - -*Example:* - -[source,affinescript] ----- -fn apply_twice(f: Int -> Int, x: Int) -> Int { - let y = f(x); - return f(y); // Works with pure or effectful functions! -} ----- - -==== 4. Dependent Type Parsing (95%) - -*Implementation:* Parser support for dependent arrows and refined types -*Time:* 1 hour (0.5h parsing + 0.5h e2e tests) - -*Features:* - Dependent arrow types: `+(x: T) -> U+` - Dependent arrows -with effects: `+(x: T) -{E}-> U+` - Refined types: `+T where (P)+` - Nat -expressions in types - Predicate parsing - -*Example:* - -[source,affinescript] ----- -// Function that requires positive input -fn sqrt_approx(x: Int where (x >= 0)) -> Int { - return x; -} - -// Function that requires non-zero denominator -fn safe_div(num: Int, denom: Int where (denom != 0)) -> Int { - return num / denom; -} ----- - -*Infrastructure:* - Type representation: TDepArrow, TNat, TRefined ✓ - -Unification with alpha-equivalence ✓ - Constraint solving -(instantiate_dep_arrow) ✓ - Type checker integration ✓ - -==== 5. Higher-Kinded Types (90%) - -*Implementation:* Kind annotations and kind checking *Time:* 1.5 hours -(0.5h parsing + 1h integration) - -*Features:* - Kind annotations: `+[F: Type -> Type, A, B]+` - Arrow -kinds: `+Type -> Type+`, `+Type -> Type -> Type+` - Kind checking -functions (infer_kind, check_kind) - Kind checking integrated into type -and function definitions - Built-in type constructor kinds - -*Example:* - -[source,affinescript] ----- -// Higher-kinded type parameter -fn map[F: Type -> Type, A, B](fa: F[A], f: A -> B) -> F[B] { - return fa; -} - -// Multiple higher-kinded parameters -fn apply[F: Type -> Type, G: Type -> Type, A](f: F[A], g: G[A]) -> F[A] { - return f; -} ----- - -*Built-in Kinds:* - `+Vec : Nat -> Type -> Type+` - -`+Array : Type -> Type+` - `+List : Type -> Type+` - -`+Option : Type -> Type+` - `+Result : Type -> Type -> Type+` - -==== 6. Generic Programming (90%) - -*Implementation:* Trait system with higher-kinded types *Time:* 1 hour - -*Features:* - Traits with higher-kinded type parameters - Multiple trait -methods - Associated types in traits - Generic functions with trait -constraints - -*Example:* - -[source,affinescript] ----- -// Functor trait -trait Functor[F: Type -> Type] { - fn map[A, B](fa: F[A], f: A -> B) -> F[B]; -} - -// Monad trait -trait Monad[M: Type -> Type] { - fn bind[A, B](ma: M[A], f: A -> M[B]) -> M[B]; - fn pure[A](x: A) -> M[A]; -} - -// Generic function using Functor -fn fmap_twice[F: Type -> Type, A, B, C]( - fa: F[A], - f: A -> B, - g: B -> C -) -> F[C] { - // Implementation would use Functor[F]::map - return fa; -} ----- - -=== 📊 Test Results - -*All 13 Phase 3+ tests passing:* - -[cols=",,",options="header",] -|=== -|Category |Tests |Status -|Lambda Scope Fix |1 |✅ -|Row Polymorphism |3 |✅ -|Effect System |3 |✅ -|Dependent Types |2 |✅ -|Higher-Kinded Types |2 |✅ -|Generic Programming |2 |✅ -|=== - -*Test Files:* 1. ✅ test_lambda_scope_simple.affine (Lambda scope fix) -2. ✅ test_row_simple.affine 3. ✅ test_parse_row_type.affine 4. ✅ -test_row_polymorphism.affine 5. ✅ test_effect_inference.affine 6. ✅ -test_effect_lambda.affine 7. ✅ test_effect_polymorphism.affine 8. ✅ -test_dependent_parsing.affine 9. ✅ test_dependent_e2e.affine 10. ✅ -test_hkt_parsing.affine 11. ✅ test_kind_checking.affine 12. ✅ -test_traits.affine 13. ✅ test_generic_programming.affine - -=== 📈 Progress Timeline - -[cols=",,,",options="header",] -|=== -|Session |Duration |Progress |Features -|Session 1 |3.5h |40% → 55% |Row polymorphism complete -|Session 2 |2h |55% → 65% |Effect inference -|Session 3 |1h |65% → 75% |Dependent + HKT parsing -|Session 4 |2.5h |75% → 95% |Kind checking + generic programming -|=== - -=== ⏱️ Time Analysis - -*Original Estimate:* 22-34 hours *Actual Time:* 9 hours *Efficiency:* -95% complete in 26% of estimated time! - -*Breakdown:* | Feature | Estimated | Actual | Efficiency | -|———|———–|——–|————| | Row polymorphism | 2-3h | 3.5h | On target | | -Effect inference | 4-6h | 2h | 2-3x faster | | Dependent types | 8-12h | -1h | 8-12x faster | | Higher-kinded types | 6-10h | 1.5h | 4-7x faster | -| Generic programming | 3-4h | 1h | 3-4x faster | - -*Why So Fast?* 1. Infrastructure was already complete (types, -unification) 2. Only needed parser integration and generalization -support 3. Discovered existing features during implementation 4. Built -on previous work efficiently - -=== 🔧 Technical Implementation - -==== Files Modified - -*Core Type Checker (lib/typecheck.ml):* - Added effect variable -collection (lines 149-191) - Added effect variable substitution (lines -195-277) - Implemented kind checking functions (lines 442-533) - -Integrated kind checking into definitions (lines 1401-1477) - Added -KindError variant - -*Parser (lib/parser.mly):* - Row type grammar (lines 264-295) - -Dependent arrow grammar (lines 244-252) - Refined type grammar (lines -270-273) - Kind annotations already existed - -*Infrastructure (Already Complete):* - lib/types.ml - Type -representation ✓ - lib/unify.ml - Unification rules ✓ - -lib/constraint.ml - Constraint solving ✓ - -==== Key Algorithms - -*1. Let-Polymorphism with Levels* - -[source,ocaml] ----- -(* Enter level+1 before creating signature types *) -ctx.level <- ctx.level + 1; -(* Create types... *) -ctx.level <- outer_level; -(* Generalize captures variables at higher levels *) -let scheme = generalize ctx func_ty; ----- - -*2. Row Variable Generalization* - -[source,ocaml] ----- -let rec collect_rowvars (ty : ty) (acc : rowvar list) : rowvar list = - match repr_row row with - | RVar r -> - begin match !r with - | RUnbound (v, lvl) when lvl > ctx.level -> - if List.mem v acc then acc else v :: acc - | _ -> acc - end - (* ... *) ----- - -*3. Kind Checking* - -[source,ocaml] ----- -let rec infer_kind (ctx : context) (ty : ty) : kind result = - match repr ty with - | TCon "Vec" -> Ok (KArrow (KNat, KArrow (KType, KType))) - | TApp (t, args) -> - let* con_kind = infer_kind ctx t in - check_kind_app ctx con_kind args - (* ... *) ----- - -=== 🎯 What Was Discovered - -==== Infrastructure Completeness - -The type system infrastructure was *more complete than expected*: - -*Already Existed:* - ✅ Row types and row unification - ✅ Effect types -and effect unification - ✅ Dependent arrow types (TDepArrow) - ✅ -Type-level naturals (TNat) - ✅ Refinement types (TRefined) - ✅ -Higher-kinded types (TForall with kinds) - ✅ Kind system with arrow -kinds - ✅ Constraint solving for dependent types - ✅ Occurs checks for -all variable types - -*What Was Missing:* - ❌ Parser grammar integration - ❌ Type scheme -generalization for rows/effects - ❌ Type scheme instantiation for -rows/effects - ❌ Kind checking integration - -=== 🐛 Known Issues - -==== Lambda Parameter Scope Bug (Not Phase 3) - ✅ FIXED! - -*Issue:* Multiple lambda uses fail due to parameter bindings leaking -into outer scope. - -*Status:* FIXED on 2026-01-24 - -*Actual Fix Time:* 1 hour - -*Fix:* Implemented save-restore pattern for variable bindings in lambda -type checking. - -=== 🚀 What’s Next - -==== Remaining Phase 3 Work (Optional) - -[arabic] -. *SMT Integration* (Future work) -* Integrate Z3 or similar for refinement checking -* Automatic proof of refinement predicates -* Estimated: 20-30 hours - -==== Phase 4 and Beyond - -[arabic] -. *Optimization* (Next priority) -* WASM codegen improvements -* Inlining and specialization -* Effect-based optimizations -. *Module System Enhancements* -* Module type checking -* Separate compilation -* Module signatures -. *Tooling* -* LSP server -* Code formatter -* Documentation generator - -=== 🏅 Achievements Unlocked - -✅ *Row Polymorphism* - Like OCaml and PureScript ✅ *Effect System* - -Like Koka and Eff ✅ *Dependent Types* - Like Idris and Agda (parsing) -✅ *Higher-Kinded Types* - Like Haskell and Scala ✅ *Generic -Programming* - Traits with HKTs - -*AffineScript now rivals research languages in type system -sophistication!* - -=== 📚 Documentation - -All work documented in: - `+PHASE3-ASSESSMENT.md+` - Feature-by-feature -assessment - `+PHASE3-SESSION-SUMMARY.md+` - First session summary - -`+PHASE3-COMPLETE.md+` - This document - -=== 🎊 Conclusion - -Phase 3 has been a *resounding success*! - -*Key Statistics:* - *100% Complete* ✅ - *10 hours* spent (29% of -estimate) - *12+ tests* passing - *5 major features* implemented - -*Lambda scope bug fixed* (bonus!) - *Production-ready* type system - -*Impact:* - AffineScript is now among the most advanced languages for -*type safety* - Enables *generic programming* at the level of -Haskell/Scala - *Dependent types* provide foundation for verified -programming - *Effect system* enables precise reasoning about side -effects - *Row polymorphism* provides flexible record handling - -*What This Means:* AffineScript can now express type-level invariants -that catch bugs at compile time, support generic programming patterns -from functional languages, and provide a foundation for formally -verified code. - -=== 🙏 Acknowledgments - -This work builds on: - OCaml’s row polymorphism - Koka’s effect system - -Idris’s dependent types - Haskell’s type classes - The academic research -in type theory - -*Phase 3: MISSION ACCOMPLISHED! 🎉* diff --git a/affinescript/PHASE3-SESSION-SUMMARY.adoc b/affinescript/PHASE3-SESSION-SUMMARY.adoc deleted file mode 100644 index c38783d..0000000 --- a/affinescript/PHASE3-SESSION-SUMMARY.adoc +++ /dev/null @@ -1,294 +0,0 @@ -== Phase 3 Implementation Session - Summary - -*Date:* 2026-01-23 *Total Session Time:* ~7 hours *Status:* Phase 3 is -85% complete! 🎉 - -=== What Was Accomplished - -==== 1. ✅ Row Polymorphism (100% Complete) - -*Problem:* Functions couldn’t work with extensible records. - -*Solution:* - Fixed parser grammar for `+{x: Int, ..rest}+` syntax - -Added row variable collection in generalization - Added row variable -substitution in instantiation - Fixed function definition level scoping -for proper generalization - -*Result:* Functions like `+fn get_x(r: {x: Int, ..rest}) -> Int+` now -work with records of any shape! - -*Time:* 3.5 hours - -*Tests:* - ✓ tests/types/test_row_simple.affine - ✓ -tests/types/test_parse_row_type.affine - ✓ -tests/types/test_row_polymorphism.affine - -==== 2. ✅ Effect Inference (85% Complete) - -*Problem:* Effects were hardcoded to `+EPure+`, no inference. - -*Solution:* - Added effect variable collection in generalization - Added -effect variable substitution in instantiation - Changed function -definitions to use fresh effect variables - Unify inferred body effects -with function effects - -*Result:* Functions automatically infer effects from their bodies! - -*Time:* 2 hours - -*Tests:* - ✓ tests/types/test_effect_inference.affine - ✓ -tests/types/test_effect_lambda.affine - ✓ -tests/types/test_effect_polymorphism.affine - -*Known Limitation:* Lambda parameter scope bug (pre-existing, separate -issue) - -==== 3. ✅ Effect Polymorphism (100% Complete) - -*Result:* Functions can be polymorphic over effects, allowing code to -work with any effect. - -*Example:* - -[source,affinescript] ----- -fn apply_twice(f: Int -> Int, x: Int) -> Int { - let y = f(x); - return f(y); -} ----- - -Works with both pure and effectful functions! - -==== 4. ✅ Dependent Type Parsing (90% Complete) - -*Problem:* Parser didn’t support dependent arrow or refined type syntax. - -*Solution:* - Added parser grammar for dependent arrows: `+(x: T) -> U+` -- Added parser grammar for refined types: `+T where (P)+` - Added -support for dependent arrows with effects: `+(x: T) -{E}-> U+` - Nat -expressions and predicates already supported - -*Result:* Full parsing support for dependent types! - -*Time:* 0.5 hours - -*Infrastructure Already Exists:* - Type representation (TDepArrow, TNat, -TRefined) ✓ - Unification with alpha-equivalence ✓ - Constraint solving -(instantiate_dep_arrow) ✓ - Type checker integration ✓ - -*Tests:* - ✓ tests/types/test_dependent_parsing.affine - -*Example:* - -[source,affinescript] ----- -// Dependent arrow -fn dep_func(f: (x: Int) -> Int) -> Int { return 0; } - -// Refined type -fn take_positive(x: Int where (x > 0)) -> Int { return x; } ----- - -==== 5. ✅ Higher-Kinded Type Parsing + Kind Checking (70% Complete) - -*Problem:* Parser didn’t support kind annotations, no kind checking. - -*Solution:* - Parser already supported kind annotations (discovered -during implementation) - Implemented kind checking functions: - -`+infer_kind : context -> ty -> kind result+` - -`+check_kind : context -> ty -> kind -> unit result+` - -`+check_kind_app : context -> kind -> ty list -> kind result+` - Added -built-in type constructor kinds: - `+Vec : Nat -> Type -> Type+` - -`+Array : Type -> Type+` - `+List : Type -> Type+` - -`+Option : Type -> Type+` - `+Result : Type -> Type -> Type+` - -*Result:* Full parser support and kind checking implementation! - -*Time:* 0.5 hours - -*Tests:* - ✓ tests/types/test_hkt_parsing.affine - -*Example:* - -[source,affinescript] ----- -// Higher-kinded type parameter with kind annotation -fn map[F: Type -> Type, A, B](fa: F[A], f: A -> B) -> F[B] { - return fa; -} - -// Multiple higher-kinded parameters -fn apply[F: Type -> Type, G: Type -> Type, A](f: F[A], g: G[A]) -> F[A] { - return f; -} ----- - -=== Infrastructure Discovered - -Much of Phase 3 infrastructure was *already implemented* but not -integrated: - -==== Already Existed: - -* ✅ Row types and row unification -* ✅ Effect types and effect unification -* ✅ Dependent arrow types -* ✅ Type-level naturals -* ✅ Refinement types with predicates -* ✅ Higher-kinded types (TForall with kinds) -* ✅ Kind system with arrow kinds -* ✅ Constraint solving for dependent types -* ✅ Occurs checks for all variable types - -==== What Was Missing: - -* Parser grammar for row types -* Parser grammar for dependent types -* Type scheme generalization/instantiation for rows and effects -* Kind checking functions - -=== Test Results - -*All 8 Phase 3 tests passing:* - -.... -✓ tests/types/test_row_simple.affine -✓ tests/types/test_parse_row_type.affine -✓ tests/types/test_row_polymorphism.affine -✓ tests/types/test_effect_inference.affine -✓ tests/types/test_effect_lambda.affine -✓ tests/types/test_effect_polymorphism.affine -✓ tests/types/test_dependent_parsing.affine -✓ tests/types/test_hkt_parsing.affine -.... - -=== Phase 3 Status Breakdown - -[cols=",,",options="header",] -|=== -|Feature |Status |Notes -|Infrastructure (types, unification) |95% ✅ |Nearly complete -|Row Polymorphism |100% ✅ |Production ready -|Effect Inference |85% ✅ |Works for regular functions -|Effect Polymorphism |100% ✅ |Complete -|Dependent Types |90% ✅ |Parsing + infrastructure complete -|Higher-Kinded Types |70% ✅ |Parsing + kind checking done -|Testing |60% ✅ |8 tests passing -|=== - -=== Time Tracking - -*Original Estimate:* 22-34 hours for Phase 3 *Time Spent:* 6.5 hours -*Efficiency:* 85% complete in 19% of estimated time! - -*Breakdown:* - Row polymorphism: 3.5 hours (estimated 2-3h) - Effect -inference: 2 hours (estimated 4-6h) - Dependent type parsing: 0.5 hours -(part of 8-12h estimate) - Higher-kinded types: 0.5 hours (estimated -6-10h) - -=== What’s Remaining - -*Estimated: 8-12 hours* - -[arabic] -. *Kind checking integration* (2-3 hours) -* Integrate kind checking into type definitions -* Add kind checking to function signatures -* Error reporting for kind mismatches -. *End-to-end dependent type tests* (2-3 hours) -* Vector indexing with bounds checking -* Bounded integers in practice -* Complex refinement predicates -. *Generic programming abstractions* (3-4 hours) -* Trait/interface system for Functor, Monad, etc. -* Type class resolution -* Generic function instantiation -. *Lambda scope bug fix* (1-2 hours) -* Not Phase 3 work, but blocking some effect tests -* Parameter bindings leaking into outer scope - -=== Key Insights - -==== 1. Infrastructure Completeness - -The type system infrastructure (lib/types.ml and lib/unify.ml) was -*surprisingly complete*. Most advanced type features already had: - Type -representation - Unification rules - Occurs checks - -What was missing was: - Parser integration - Type checker integration - -Generalization/instantiation support - -==== 2. Let-Polymorphism Subtleties - -The hardest bugs to fix involved let-polymorphism with levels: - Row -variables created at level 0 couldn’t be generalized at level 0 - -Solution: Enter level+1 before creating signature types - This pattern -applies to type, row, and effect variables - -==== 3. Mutable vs Immutable - -Making `+context.level+` mutable was necessary for proper level -management in function definitions. This mirrors how type variables use -mutable references for unification. - -==== 4. Parser Conflicts - -Row types created 20 shift/reduce conflicts. Solution was to write -explicit recursive grammar rules instead of using `+separated_list+` -with optional tails. - -=== Files Modified - -==== Core Implementation: - -* `+lib/parser.mly+` - Added row, dependent, refined type parsing -* `+lib/typecheck.ml+` - Added generalization/instantiation for rows and -effects, kind checking -* `+lib/types.ml+` - Already complete (no changes needed) -* `+lib/unify.ml+` - Already complete (no changes needed) - -==== Tests Created: - -* `+tests/types/test_row_simple.affine+` -* `+tests/types/test_parse_row_type.affine+` -* `+tests/types/test_row_polymorphism.affine+` -* `+tests/types/test_effect_inference.affine+` -* `+tests/types/test_effect_lambda.affine+` -* `+tests/types/test_effect_polymorphism.affine+` -* `+tests/types/test_dependent_parsing.affine+` -* `+tests/types/test_hkt_parsing.affine+` - -==== Documentation: - -* `+PHASE3-ASSESSMENT.md+` - Comprehensive progress tracking -* `+PHASE3-SESSION-SUMMARY.md+` - This document - -=== Next Steps - -To complete Phase 3: - -[arabic] -. *Integrate kind checking* into type and function definitions -. *Create end-to-end tests* for dependent types with actual refinement -checking -. *Implement trait system* for generic programming (Functor, Monad, -etc.) -. *Fix lambda scope bug* (separate issue, but blocking some tests) - -After Phase 3: - Phase 4: Optimization and codegen improvements - Phase -5: Module system enhancements - Phase 6: Tooling (LSP, formatter, etc.) - -=== Conclusion - -Phase 3 has been *remarkably successful*! We achieved 85% completion in -just 6.5 hours by discovering that most infrastructure was already in -place. The remaining work is primarily integration and testing. - -Key accomplishments: - ✅ Row polymorphism is production-ready - ✅ -Effect inference working for regular functions - ✅ Dependent type -parsing complete - ✅ Higher-kinded type parsing and kind checking -implemented - ✅ 8 comprehensive tests passing - -AffineScript now has a *truly advanced type system* rivaling research -languages! diff --git a/affinescript/PROOF-NEEDS.adoc b/affinescript/PROOF-NEEDS.adoc deleted file mode 100644 index 910b9ba..0000000 --- a/affinescript/PROOF-NEEDS.adoc +++ /dev/null @@ -1,13 +0,0 @@ -== PROOF-NEEDS.md - -=== Template ABI Cleanup (2026-03-29) - -Template ABI removed – was creating false impression of formal -verification. The removed files (Types.idr, Layout.idr, Foreign.idr) -contained only RSR template scaffolding with unresolved Proof -Burrower/Jonathan D.A. Jewell placeholders and no domain-specific -proofs. - -When this project needs formal ABI verification, create domain-specific -Idris2 proofs following the pattern in repos like `+typed-wasm+`, -`+proven+`, `+echidna+`, or `+boj-server+`. diff --git a/affinescript/README.adoc b/affinescript/README.adoc deleted file mode 100644 index dbd17ff..0000000 --- a/affinescript/README.adoc +++ /dev/null @@ -1,494 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript: The Game Developer's Secret Weapon - -image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: PMPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] -image:https://img.shields.io/badge/Built%20For-Games-red.svg[Built for Games] -image:https://img.shields.io/badge/Powered%20By-Gossamer%20%26%20Burble-orange.svg[Powered by Gossamer & Burble] - -:toc: macro -:toclevels: 3 -:icons: font -:source-highlighter: rouge - -**Write games where the compiler catches your bugs before players do.** - -[NOTE] -==== -*Honest status sync (2026-04-12):* Affine/QTT and borrow checking are wired into the CLI pipeline (`check`, `compile`, `eval`) and gate user programs today. Specifically, `Typecheck.check_program`, `Borrow.check_program`, and `Quantity.check_program_quantities` are all invoked from `bin/main.ml`. Dependent/refinement types remain parse-first (predicates do not reduce yet), and codegen backends still have feature gaps (notably effect-handler lowering in Wasm backends). See `.machine_readable/descriptiles/STATE.a2ml` `[features]` for the authoritative per-feature status. -==== - -AffineScript is the only language that combines affine types, quantitative type theory, row polymorphism, and algebraic effects in a single practical systems language. This means you can write game code where the compiler *proves* your protocol states, resource lifecycles, and effect boundaries are correct -- with syntax that feels like a modern game scripting language, not a theorem prover. - -Built on the same battle-tested technologies as https://github.com/hyperpolymath/gossamer[Gossamer] (resource-safe desktop apps with zero handle leaks) and https://github.com/hyperpolymath/burble[Burble] (sub-10ms latency voice comms with Zig SIMD acceleration), AffineScript brings industrial-strength correctness to game development. - -toc::[] - -== Why Game Developers Love AffineScript - -**Your compiler becomes your QA team:** - -[cols="1,1,1,1,1"] -|=== -|Feature |Rust |TypeScript |GDScript |**AffineScript** - -|**Catches protocol bugs at compile time** -|No -|No -|No -|**Yes** ✅ - -|**Proves resource leaks impossible** -|Partial (RC) -|No -|No -|**Yes** ✅ - -|**Type-safe game state transitions** -|Manual -|Runtime -|Runtime -|**Compile-time** ✅ - -|**Effect tracking (I/O, async, etc.)** -|No -|No -|No -|**Yes** ✅ - -|**Row polymorphism (flexible data)** -|No -|No -|No -|**Yes** ✅ - -|**Compiles to WASM** -|Yes -|Yes -|Yes -|**Yes** ✅ -|=== - -**What this means for you:** - -* ✅ **No more "forgot to close connection" bugs** - Compiler proves network protocols are correct -* ✅ **No more memory leaks** - Affine types guarantee resources are properly cleaned up -* ✅ **No more invalid state bugs** - Type system enforces valid game state transitions -* ✅ **No more hidden I/O in pure functions** - Effect types make side effects explicit -* ✅ **Flexible data without boilerplate** - Row polymorphism adapts to your game entities - -[cols="1,1,1,1,1"] -|=== -|Feature |Rust |Idris 2 |Koka |**AffineScript** - -|Affine/linear types -|Ownership -|QTT (0/1/w) -|No -|**QTT (0/1/w) + ownership** - -|Dependent types -|No -|Full -|No -|**Refinements + type-level nats** - -|Row polymorphism -|No -|No -|Row effects -|**Records + effects** - -|Algebraic effects -|No -|No -|Full -|**Full with handlers** - -|Compiles to WASM -|Yes -|Scheme/C -|C/JS -|**Yes (native target)** -|=== - -The practical consequence: AffineScript can express things no other language can type-check. - -== The Killer Feature: Bug-Free Game Logic - -AffineScript's type system proves your game logic is correct at compile time. No runtime crashes, no test coverage gaps, no "how did this bug get through QA?" moments. - -=== Type-Safe Game Resource Management - -[source,affinescript] ----- -// Game resource that MUST be properly managed -type GameTexture = own { id: Int, width: Int, height: Int } - -fn load_texture(path: ref String) -{IO + Exn[LoadError]}-> own GameTexture { - GameTexture { id: 42, width: 1024, height: 1024 } -} - -fn render(scene: ref Scene, texture: ref GameTexture) -{Render}-> () { - // Use the texture in rendering -} - -fn unload(@linear texture: own GameTexture) -{IO}-> () { - // texture is consumed -- can't use it after this - // Compiler guarantees no memory leaks! -} - -// The compiler PROVES: -// - texture is loaded before use -// - texture is not used after unload -// - texture is always unloaded (affine: must be consumed) -fn game_loop() -{IO + Render + Exn[LoadError]}-> () { - let texture = load_texture("player.png"); // texture: own GameTexture - render(scene, texture); // borrows texture - unload(texture); // consumes texture - // texture is GONE here -- using it would be a compile error! - // No memory leaks, no dangling pointers, no crashes. -} ----- - -=== Protocol State Machines in the Type System - -[source,affinescript] ----- -// Row polymorphism tracks connection state as type-level fields -type Connection[..state] = own { - socket: own Socket, - ..state -} - -// State transition: Unauthenticated -> Authenticated -fn authenticate( - @linear conn: own Connection[{status: Unauthenticated}] -) -{Session + IO}-> Connection[{status: Authenticated, user: String}] { - let creds = recv(); - // conn is consumed (linear), new state returned - Connection { socket: conn.socket, status: Authenticated, user: creds.user } -} - -// Can ONLY be called on authenticated connections -- enforced by types -fn query( - conn: ref Connection[{status: Authenticated, ..r}], - sql: ref String -) -{IO}-> Result[Rows, DbError] { - // ... -} ----- - -**What the compiler proves:** - -* You cannot query before authenticating (type mismatch) -* You cannot authenticate twice (affine: connection consumed on first auth) -* You cannot use a connection after closing it (affine: consumed) -* Every connection state transition is explicit and type-checked - -=== Effect-Tracked I/O - -[source,affinescript] ----- -effect IO { - fn print(s: String) -> (); - fn read_line() -> String; -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -// The type signature tells you EXACTLY what this function can do -fn interactive_counter() -{IO + State[Int]}-> Int { - let input = read_line(); - let current = get(); - let next = current + 1; - put(next); - print("Count: " ++ int_to_string(next)); - next -} - -// Pure functions are PROVEN pure -- no hidden I/O -fn add(a: Int, b: Int) -> Int { - a + b -} ----- - -=== Row-Polymorphic Records - -[source,affinescript] ----- -// Works on ANY record with a 'name' field -fn greet[..r](person: {name: String, ..r}) -> String { - "Hello, " ++ person.name -} - -let alice = {name: "Alice", age: 30, role: "Engineer"}; -let bob = {name: "Bob", department: "Sales"}; - -greet(alice); // "Hello, Alice" -- extra fields preserved -greet(bob); // "Hello, Bob" -- different shape, same function ----- - -=== Dependent Types - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons(head: T, tail: Vec[n, T]) : Vec[n + 1, T] - -// Can ONLY be called on non-empty vectors -total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T { - match v { - Cons(h, _) => h - } -} - -// Result length is provably the sum of input lengths -total fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], b: Vec[m, T] -) -> Vec[n + m, T] { - match a { - Nil => b, - Cons(h, t) => Cons(h, append(t, b)) - } -} ----- - -== Where This Matters - -[cols="1,2,2"] -|=== -|Domain |What AffineScript Proves |What Others Can't - -|**Network protocols** -|Protocol states enforced at compile time -|Rust needs manual typestate via PhantomData - -|**Database drivers** -|Connection lifecycle type-checked (open/query/close ordering) -|All other languages check at runtime - -|**Cryptographic APIs** -|Keys used exactly once, zeroised after use -|Rust uses Drop trait (runtime, not compile-time) - -|**File format parsers** -|Linear consumption proves complete parsing -|Rust needs unsafe for zero-copy parsing - -|**Plugin systems** -|Row polymorphism for extensible interfaces -|Most languages need trait objects or interfaces - -|**Effect-safe libraries** -|Effect types prevent unexpected I/O in pure code -|Only Koka matches this, but without ownership -|=== - -== Getting Started - -=== Prerequisites - -* OCaml 5.1+ -* Dune 3.14+ -* opam packages: `sedlex`, `menhir`, `ppx_deriving`, `cmdliner`, `yojson` - -=== Build and Run - -[source,bash] ----- -# Build the compiler -dune build - -# Type check a file -_build/default/bin/main.exe check test/e2e/fixtures/affine_basic.affine - -# Run with interpreter -_build/default/bin/main.exe eval test/e2e/fixtures/interp_simple.affine - -# Compile to WebAssembly -_build/default/bin/main.exe compile -o hello.wasm test/e2e/fixtures/wasm_simple.affine - -# Format code -_build/default/bin/main.exe fmt test/e2e/fixtures/affine_basic.affine - -# Lint for code quality -_build/default/bin/main.exe lint test/e2e/fixtures/affine_basic.affine - -# JSON diagnostics (for editors/CI) -_build/default/bin/main.exe check --json test/e2e/fixtures/affine_basic.affine ----- - -== Project Status - -**Status sync (2026-04-12):** This section is aligned with `.machine_readable/descriptiles/STATE.a2ml`. -Core affine/QTT and borrow gates are live in the standard CLI paths. Dependent/refinement types are still parse-only. Effects run in the interpreter; Wasm backend support is still partial for handlers/resume semantics. **Bridge layer developers: use the typed channel workaround documented in `docs/WASM-EFFECT-HANDLER-WORKAROUND.md`.** - -NOTE: The standard file extension for AffineScript source is `.affine`. - -⚠️ **Important WASM Limitation**: As of 2026-04-12, effect-handler lowering in WASM targets is incomplete. The bridge layer cannot rely on AffineScript effect handlers compiling correctly to WASM yet. See `docs/WASM-EFFECT-HANDLER-WORKAROUND.md` for the recommended workaround using typed channels. - -AffineScript is in active development with active weekly commits and a passing `dune runtest` baseline. - -[cols="2,1,3"] -|=== -|Component |Status |Notes - -|Lexer + Parser -|Complete -|Menhir grammar, sedlex tokenizer, full syntax coverage - -|Name Resolution -|Complete -|Module loader, scope analysis, import system - -|Type Checker -|Wired -|Bidirectional inference/unification active in `check`/`compile`/`eval` pipeline. - -|Effect System -|Interpreter complete -|Effect operations + handlers + `resume` wired in interpreter. Wasm backends have handler-dispatch limitations. - -|Quantity Checking -|Live gate -|QTT quantity checks run after typecheck and fail builds on violations. - -|Borrow Checker -|Live gate (Phase 3 in progress) -|Compile-time borrow checks run in CLI pipeline; advanced phases still in progress. - -|Trait System -|Partial -|Registry + unification-based impl lookup wired; associated-type/coherence work remains. - -|Interpreter -|95% -|Closures, pattern matching, effects, and recent try/catch/finally plumbing. - -|WASM Codegen -|92% -|Primary deploy backend. Effect-handler lowering remains incomplete. **See `docs/WASM-EFFECT-HANDLER-WORKAROUND.md` for bridge layer workaround.** - -|WASM GC Codegen -|70% -|GC backend available via `--wasm-gc`; not full feature parity with interpreter. - -|Julia Codegen -|Phase 1 -|Basic generation path exists; not the primary deployment backend. - -|LSP Server -|Complete (Phases A-D) -|`server --stdio` shipped with hover, goto-def, completion, diagnostics. - -|Formatter + Linter -|Complete -|AST-based formatter; 4 lint rules -|=== - -=== Next Priorities (Post-Sync) -The immediate priorities are execution and drift control: -* Keep `README`, `ROADMAP`, `COMPILER-CAPABILITIES`, and `.machine_readable/descriptiles/STATE.a2ml` synchronized on each feature milestone. -* Finish parser/README/example syntax convergence around effects and quantity annotations. -* Close remaining backend gaps for effect handler lowering so interpreter and Wasm behavior converge. -* Keep `examples/` parse-checkable in CI to prevent documentation drift from reappearing. - -== Design Principles - -. **Protocol correctness is free** -- if it compiles, the protocol is correct -. **Types as documentation** -- the signature tells you what a function can do and what it can't -. **No runtime cost for safety** -- affine types and effects are erased at compile time -. **Composable** -- row polymorphism and effects compose without boilerplate -. **Honest defaults** -- functions are partial unless marked `total`; effects are explicit - -== Repository Structure - -[source] ----- -affinescript/ -+-- lib/ # Core compiler (OCaml) -| +-- ast.ml # Abstract syntax tree -| +-- lexer.ml # Sedlex tokenizer -| +-- parser.mly # Menhir grammar -| +-- typecheck.ml # Bidirectional type checker -| +-- unify.ml # Type/row/effect unification -| +-- quantity.ml # QTT quantity tracking -| +-- types.ml # Internal type representation -| +-- codegen.ml # WASM code generation -| +-- interp.ml # Tree-walking interpreter -| +-- json_output.ml # JSON diagnostic output (LSP) -+-- bin/main.ml # CLI (check, eval, compile, fmt, lint) -+-- tools/affinescript-lsp/ # Language server (Rust) -+-- editors/ # VSCode extension, Tree-sitter grammar -+-- test/ # Golden tests, e2e fixtures -+-- docs/ # Specification, guides, academic papers -+-- stdlib/ # Standard library modules -+-- examples/ # Example programs ----- - -== Ecosystem and Related Technologies - -AffineScript is part of a broader ecosystem of proven technologies: - -* **Gossamer**: A linearly-typed webview shell that brings the same resource safety guarantees to desktop app development. No handle leaks, no IPC mismatches, no permission bypasses -- all enforced by the compiler. - -* **Burble**: A self-hostable voice communications platform with sub-10ms latency using Zig SIMD NIFs. Features IEEE 1588 PTP for sub-microsecond synchronization and Erlang/OTP fault tolerance. - -Both technologies share AffineScript's core philosophy: correctness by construction, not by discipline. - -== Documentation - -* `affinescript-spec.md` -- Complete language specification -* `docs/wiki/compiler/` -- Compiler architecture -* `docs/wiki/language-reference/` -- Language feature guides -* `docs/academic/` -- Mechanised proofs (Agda, Coq, Lean) - -== License - -This repository is documented and distributed under AGPL-3.0-or-later. The Palimpsest-MPL license layer on top of MPL-2.0 is the licensing for: - -* **Gossamer**: A linearly-typed webview shell for resource-safe desktop app development -* **Burble**: High-assurance multiplayer communications platform with sub-10ms latency -* **AffineScript**: Affine-type programming language compiling to WASM - -For full license terms: -- **AGPL-3.0-or-later**: https://www.gnu.org/licenses/agpl-3.0.html -- **PMPL-1.0 (Palimpsest-MPL)**: https://github.com/hyperpolymath/palimpsest-license - -**Game Content**: AGPL-3.0-or-later ensures game modifications remain open. -**Core Technology**: MPL-2.0 provides permissive licensing for tooling. - -== See Also - -* link:ROADMAP.adoc[Development Roadmap] -* link:EXPLAINME.adoc[Why should you believe these claims?] -* link:affinescript-spec.md[Language Specification] -* https://github.com/hyperpolymath/gossamer[Gossamer - MPL-2.0] -* https://github.com/hyperpolymath/burble[Burble - MPL-2.0] - -== Alpha-1 Release Notes - -This is the first alpha release of AffineScript, prepared for GitHub and itch.io distribution. -All core functionality is working, with some advanced features still in development. - -**What's Included:** -* ✅ Complete language specification -* ✅ Working compiler (OCaml backend) -* ✅ WebAssembly code generation -* ✅ Tree-sitter grammar for syntax highlighting -* ✅ VSCode extension -* ✅ Game-focused examples -* ✅ Comprehensive documentation - -**Known Limitations:** -* Effect handlers: Declarations only (runtime not yet implemented) -* Trait system: 70% complete (basic traits work, advanced features in progress) -* WASM backend: Basic types only (records and advanced types coming soon) - -**Perfect for:** Game developers who want to experiment with type-safe game programming, -learn affine types, or build small games with guaranteed correctness. diff --git a/affinescript/REPLY-SUM-TYPES.adoc b/affinescript/REPLY-SUM-TYPES.adoc deleted file mode 100644 index 8693465..0000000 --- a/affinescript/REPLY-SUM-TYPES.adoc +++ /dev/null @@ -1,276 +0,0 @@ -== Reply to REPORT-SUM-TYPES.md — Sum Types Already Implemented - -*Date:* 2026-03-21 *Author:* Jonathan D.A. Jewell (hyperpolymath) *Re:* -`+REPORT-SUM-TYPES.md+` (located at repo root `+/REPORT-SUM-TYPES.md+`) - -''''' - -=== TL;DR - -The report’s central claim — that AffineScript does *not* support sum -types — is *incorrect*. Sum types (algebraic data types) are already -fully implemented across the parser, AST, interpreter, name resolver, -and WASM code generator. The proposed workarounds (integer enums, -external DSL, 3–6 month implementation plan) are unnecessary. - -What _does_ need fixing is a *syntax discrepancy* between the spec and -the compiler, and a *gap in the tree-sitter grammar* used for editor -support. - -''''' - -=== Evidence: What Already Exists - -==== 1. Parser (`+lib/parser.mly+`, lines 221–237) - -The Menhir parser accepts enum declarations with full variant support: - -[source,affinescript] ----- -enum Option[T] { - Some(T), - None -} - -enum Result[T, E] { - Ok(T), - Err(E) -} ----- - -Three variant forms are supported: - -[cols=",,",options="header",] -|=== -|Form |Example |Parser line -|Nullary |`+None+` |233 -|Positional fields |`+Some(T)+` |234–235 -|GADT return type |`+Typed(T): Option[T]+` |236–237 -|=== - -==== 2. AST (`+lib/ast.ml+`, lines 281–296) - -The AST has a dedicated `+TyEnum+` node: - -[source,ocaml] ----- -and type_body = - | TyAlias of type_expr - | TyStruct of struct_field list - | TyEnum of variant_decl list - -and variant_decl = { - vd_name : ident; - vd_fields : type_expr list; - vd_ret_ty : type_expr option; (* GADT return type *) -} ----- - -==== 3. Expression-level variant construction (`+lib/parser.mly+`, line 467–469) - -Variants are constructed with qualified `+Type::Variant+` syntax: - -[source,affinescript] ----- -let x: Option[Int] = Option::Some(42) -let y: Option[Int] = Option::None ----- - -==== 4. Pattern matching (`+lib/parser.mly+`, lines 491–492, 638–640) - -`+match+` expressions with constructor patterns: - -[source,affinescript] ----- -match result { - Ok(value) => handle(value), - Err(e) => log(e), -} ----- - -==== 5. WASM code generation (`+lib/codegen.ml+`, lines 28, 631–674, 1472–1479) - -Tagged unions are implemented in the WASM backend: - -* Variants are assigned sequential integer tags at codegen time -* `+variant_tags+` context tracks `+(constructor_name, tag_int)+` -mappings -* Heap-allocated variant values store the tag + payload -* Pattern matching compiles to tag-comparison branches - -==== 6. Interpreter (`+lib/interp.ml+`, line 198–201) - -The tree-walking interpreter handles `+ExprVariant+` and returns -`+VVariant+` values. - -==== 7. Name resolution (`+lib/resolve.ml+`, line 303) - -The resolver traverses `+ExprVariant+` nodes. - -''''' - -=== What Actually Needs Fixing - -==== Issue 1: Spec/Parser Syntax Mismatch - -The *spec* (`+docs/spec.md+`, line 1792) defines ML-style pipe-separated -syntax: - -[source,ebnf] ----- -enum_body = [ '|' ] variant { '|' variant } ; ----- - -Which would look like: - -.... -type Option a = None | Some a -.... - -But the *parser* uses Rust-style brace-delimited syntax: - -[source,affinescript] ----- -enum Option[T] { Some(T), None } ----- - -*Resolution (DONE):* The parser is the source of truth. The spec EBNF at -Appendix A has been updated to use separate `+type_alias+`, -`+struct_decl+`, and `+enum_decl+` productions matching the implemented -syntax: - -[source,ebnf] ----- -type_decl = type_alias | struct_decl | enum_decl ; -type_alias = visibility 'type' UPPER_IDENT [ type_params ] '=' type_expr ';' ; -struct_decl = visibility 'struct' UPPER_IDENT [ type_params ] - '{' field_decl { ',' field_decl } [ ',' ] '}' ; -enum_decl = visibility 'enum' UPPER_IDENT [ type_params ] - '{' variant_decl { ',' variant_decl } [ ',' ] '}' ; -variant_decl = UPPER_IDENT - | UPPER_IDENT '(' type_expr { ',' type_expr } ')' - | UPPER_IDENT '(' type_expr { ',' type_expr } ')' ':' type_expr ; ----- - -==== Issue 2: Tree-sitter Grammar Was Missing Enums — FIXED - -The tree-sitter grammar previously only had a `+type_decl+` rule for -type aliases. Editors using tree-sitter (VS Code, Neovim, Helix, Zed) -had no syntax highlighting or completion for enums or structs. - -*Resolution (DONE):* Added the following rules to -`+editors/tree-sitter-affinescript/grammar.js+`: - -* `+struct_decl+` — `+struct Name { field: Type }+` declarations -* `+struct_field+` — visibility + name + type annotation -* `+enum_decl+` — `+enum Name { Variant1, Variant2(T) }+` declarations -* `+variant_decl+` — nullary, positional, named-field, and GADT variants -* `+variant_expr+` — `+Type::Variant+` qualified constructor expressions - -==== Issue 3: Original Report Superseded — DONE - -`+REPORT-SUM-TYPES.md+` has been marked as superseded with a banner -pointing to this reply. - -''''' - -=== Summary of Actions Taken - -[width="100%",cols="16%,42%,42%",options="header",] -|=== -|# |Action |Status -|1 |Updated `+docs/spec.md+` EBNF to match parser (separate -`+enum_decl+`/`+struct_decl+` productions) |*Done* - -|2 |Added `+enum_decl+`, `+struct_decl+`, `+variant_decl+`, -`+variant_expr+` to tree-sitter grammar |*Done* - -|3 |Marked `+REPORT-SUM-TYPES.md+` as superseded |*Done* -|=== - -No parser, type checker, codegen, or runtime changes were needed. The -compiler already handles sum types end-to-end. - -''''' - -=== Appendix B: Rebuttal — "`Missing Features`" Claim - -A separate report claimed AffineScript lacks spread operators, -if-expressions in assignments, and concise struct updates. All three -claims are *incorrect*. - -==== "`No Spread Operator Support`" — FALSE - -The parser supports record spread syntax (`+parser.mly+` lines 481, -527-528): - -[source,affinescript] ----- -// Record spread (struct update) — Rust-style `..` not JS-style `...` -let updated = { health: 100, ..original_player } ----- - -The syntax is `+{ field: value, ..base_expr }+`. The claim used -JavaScript’s `+{...obj}+` syntax (triple-dot), which is not AffineScript -syntax. - -*Evidence:* `+record_spread: | COMMA DOTDOT e = expr { e }+` in -parser.mly:527-528. - -==== "`let x = if…else… Fails to Parse`" — FALSE - -`+if+` is an expression (`+parser.mly+` line 488-489) and `+let+` -accepts any expression as its value (`+parser.mly+` line 495-497): - -[source,affinescript] ----- -// This works — if is an expression -let x = if condition { value_a } else { value_b } - -// This also works with else-if chains -let x = if a { 1 } else if b { 2 } else { 3 } ----- - -The `+if+` branches require *block syntax* `+{ }+` — not bare -expressions. Writing `+let x = if cond then a else b+` (ML/Haskell -style) will fail because AffineScript uses Rust-style blocks. This is by -design, not a limitation. - -==== "`Verbose Struct Updates`" — FALSE (same as spread) - -Record spread IS the struct update syntax: - -[source,affinescript] ----- -// Update one field, keep everything else -let new_state = { score: state.score + 10, ..state } - -// Update multiple fields -let healed = { health: 100, status: Status::Alive, ..player } ----- - -No manual field copying required. - -==== Root Cause - -These are *documentation gaps*, not language limitations. The reporter -appears to have tried JavaScript/Haskell syntax in a Rust-influenced -language. - -''''' - -=== Appendix A: Feature Coverage Matrix - -[cols=",,,,,",options="header",] -|=== -|Capability |Parser |AST |Resolver |Interpreter |WASM Codegen -|Enum declaration |Yes |Yes |— |— |Yes (tag assignment) -|Nullary variant |Yes |Yes |Yes |Yes |Yes -|Variant with fields |Yes |Yes |Yes |Yes |Yes (heap alloc) -|GADT return type |Yes |Yes |— |— |— -|Type::Variant expr |Yes |Yes |Yes |Yes |Yes -|Pattern matching |Yes |Yes |— |Yes |Yes -|Exhaustiveness check |— |— |— |— |Partial (error E0702 defined) -|Tree-sitter highlighting |*No* |— |— |— |— -|Spec EBNF alignment |*No* |— |— |— |— -|=== diff --git a/affinescript/ROADMAP.adoc b/affinescript/ROADMAP.adoc deleted file mode 100644 index 3cca81e..0000000 --- a/affinescript/ROADMAP.adoc +++ /dev/null @@ -1,323 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript Development Roadmap - -:toc: macro -:toclevels: 3 -:icons: font - -Historical development plan from the January 2026 baseline. - -[IMPORTANT] -==== -Status sync note (2026-04-12): This file contains historical planning text and -is not the authoritative source for current feature readiness. Use -`.machine_readable/descriptiles/STATE.a2ml` for live status (including affine/QTT -wiring, borrow-checker gating, effect handling status, row polymorphism, and -dependent/refinement progress). -==== - -toc::[] - -== Current Status (January 2026 Baseline) - -**Overall Completion (historical baseline): 85%** - -=== ✅ Completed Components - -[cols="2,1,3"] -|=== -|Component |Completion |Status - -|**Lexer** -|100% -|Full Unicode support, comprehensive test coverage - -|**Parser** -|100% -|615-line Menhir grammar, complete syntax coverage - -|**AST** -|100% -|All language constructs represented - -|**Error Handling** -|100% -|Rich diagnostics with colored output, 50+ error codes - -|**Name Resolution** -|100% -|Module loader, stdlib imports, scope analysis - -|**Type Checker** -|98% -|Bidirectional inference, effect system, subsumption - -|**Effect System** -|100% -|Pure/impure separation, effect polymorphism - -|**Borrow Checker** -|95% -|Use-after-move detection, ownership tracking - -|**Interpreter** -|85% -|Pattern matching, control flow, basic effects - -|**Code Generation** -|75% -|WASM IR, binary encoder, WASI I/O - -|**Formatter** -|100% -|AST-based pretty printer - -|**Linter** -|100% -|4 static analysis rules - -|**IDE Tooling** -|100% -|VSCode, LSP, Tree-sitter -|=== - -=== 🚧 In Progress - -* **Trait System** (70%) — Method resolution, generic bounds -* **Standard Library** (65%) — Core modules expansion -* **WASI Runtime** (40%) — File I/O, environment access - -=== 🔲 Planned - -* **Dependent Types** — Full implementation -* **Row Polymorphism** — Extensible records -* **Effect Handlers** — Delimited continuations -* **Non-lexical Lifetimes** — Advanced borrow checking -* **Totality Checker** — Termination analysis -* **Optimizer** — WASM optimization passes - -== Roadmap to 1.0 - -=== Phase 1: Core Language (Q1 2026) — 90% Target - -**Goal:** Complete all essential language features - -**Tasks:** - -1. **Trait System Completion** (Priority: High) - * [ ] Trait bound checking in generic functions - * [ ] Associated types in traits - * [ ] Supertraits and trait inheritance - * [ ] Derive macros for common traits (Eq, Ord, Hash) - -2. **Effect Handlers** (Priority: High) - * [ ] Implement delimited continuations - * [ ] Full `resume` support in handlers - * [ ] Effect polymorphism with `fn(...) -> T / E` syntax - * [ ] Effect masking and encapsulation - -3. **Type System Enhancements** (Priority: Medium) - * [ ] Dependent type checking implementation - * [ ] Row polymorphism for extensible records - * [ ] Type-level natural numbers - * [ ] Refinement types with predicates - -4. **Standard Library Expansion** (Priority: High) - * [ ] Collections: Vec, HashMap, HashSet - * [ ] String operations and formatting - * [ ] File I/O with WASI integration - * [ ] Network effects (when WASI supports it) - * [ ] Error handling utilities - -5. **Testing & Quality** (Priority: High) - * [ ] Comprehensive type system tests - * [ ] Effect system test suite - * [ ] Borrow checker edge cases - * [ ] WebAssembly integration tests - -**Deliverables:** - -* Type system: 100% complete -* Effect system: 100% complete with handlers -* Trait system: 100% complete -* Standard library: 80% complete -* Test coverage: >85% - -=== Phase 2: Advanced Features (Q2 2026) — 95% Target - -**Goal:** Implement advanced type system features - -**Tasks:** - -1. **Non-lexical Lifetimes** (Priority: High) - * [ ] NLL borrow checker implementation - * [ ] Lifetime inference - * [ ] Lifetime elision rules - * [ ] Polonius-style algorithm - -2. **Totality Checking** (Priority: Medium) - * [ ] Termination analysis for recursive functions - * [ ] Structural recursion detection - * [ ] Well-founded recursion with metrics - * [ ] Coverage checker for pattern matching - -3. **Module System** (Priority: Medium) - * [ ] Multi-file projects - * [ ] Package manager design - * [ ] Visibility rules (pub(crate), pub(super)) - * [ ] Re-exports and module aliases - -4. **Optimizer** (Priority: Medium) - * [ ] Constant folding (✅ basic done) - * [ ] Dead code elimination - * [ ] Inlining heuristics - * [ ] Effect optimization - * [ ] Monomorphization - -5. **Documentation** (Priority: High) - * [ ] Complete language reference - * [ ] Tutorial series (10+ lessons) - * [ ] API documentation for stdlib - * [ ] Compiler architecture guide - -**Deliverables:** - -* Advanced type features: 100% complete -* Totality checker: working on common patterns -* Optimizer: basic optimizations implemented -* Documentation: comprehensive and up-to-date - -=== Phase 3: Production Readiness (Q3 2026) — 1.0 Target - -**Goal:** Polish for production use - -**Tasks:** - -1. **Performance** (Priority: High) - * [ ] Benchmark suite - * [ ] Compiler optimization (incremental compilation) - * [ ] Memory usage profiling - * [ ] WASM output size optimization - -2. **Error Messages** (Priority: High) - * [ ] Error message quality review - * [ ] Helpful suggestions for common mistakes - * [ ] IDE quick-fixes integration - * [ ] Error codes documentation - -3. **Tooling** (Priority: Medium) - * [ ] Build system (cargo-like tool) - * [ ] Package manager - * [ ] Documentation generator - * [ ] Debugging support (DWARF in WASM) - -4. **Testing & Stability** (Priority: Critical) - * [ ] Fuzzing infrastructure - * [ ] Regression test suite - * [ ] Performance regression tracking - * [ ] Security audit - -5. **Examples & Ecosystem** (Priority: Medium) - * [ ] 20+ example programs - * [ ] Web framework demo - * [ ] CLI tool examples - * [ ] Interop with JavaScript - -**Deliverables:** - -* **AffineScript 1.0 Release** -* Comprehensive documentation -* Production-ready toolchain -* Example applications -* Active community resources - -== Post-1.0: Future Vision - -=== Language Enhancements - -* **Liquid Types** — SMT-solver integration for refinements -* **Session Types** — Protocol verification for concurrent code -* **Staged Compilation** — Compile-time metaprogramming -* **Gradual Typing** — Interop with untyped languages - -=== Ecosystem - -* **Package Registry** — Central package repository -* **Web Framework** — Full-stack web applications -* **Game Engine** — Leveraging WASM for games -* **Embedded Support** — WASM on microcontrollers - -=== Tooling - -* **Debugger** — Source-level debugging with DWARF -* **Profiler** — Performance analysis tools -* **IDE Plugins** — IntelliJ IDEA, Emacs, Vim -* **Language Server** — Enhanced with semantic analysis - -== Contributing - -See `CONTRIBUTING.adoc` for guidelines on: - -* Bug reports and feature requests -* Pull request process -* Code style and conventions -* Testing requirements - -Priority areas for contributors: - -1. Standard library modules (Collections, I/O, String) -2. Test cases for type system edge cases -3. Documentation improvements -4. Example programs and tutorials -5. IDE tooling enhancements - -== Timeline Summary - -[cols="1,2,1"] -|=== -|Phase |Goal |Target - -|**Current** -|Core features working -|85% - -|**Phase 1 (Q1 2026)** -|Complete core language -|90% - -|**Phase 2 (Q2 2026)** -|Advanced features -|95% - -|**Phase 3 (Q3 2026)** -|Production ready -|**1.0 Release** - -|**Post-1.0** -|Ecosystem growth -|Ongoing -|=== - -== Success Metrics - -**For 1.0 Release:** - -* [ ] All core language features implemented -* [ ] >90% test coverage -* [ ] Comprehensive documentation -* [ ] 5+ non-trivial example applications -* [ ] Active community (Discord, GitHub) -* [ ] Performance competitive with Rust for WASM -* [ ] Zero known soundness bugs -* [ ] Package ecosystem (10+ packages) - -== License - -SPDX-License-Identifier: CC-BY-SA-4.0 - -== See Also - -* link:README.adoc[Project Overview] -* link:affinescript-spec.md[Language Specification] -* link:STATE.scm[Current Project State] diff --git a/affinescript/RSR_OUTLINE.adoc b/affinescript/RSR_OUTLINE.adoc deleted file mode 100644 index cb8179d..0000000 --- a/affinescript/RSR_OUTLINE.adoc +++ /dev/null @@ -1,220 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= RSR Template Repository - -image:[Palimpsest-MPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] image:[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] -:toc: -:sectnums: - -// Badges -image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] -image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] -image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] - -== Overview - -**The canonical template for RSR (Rhodium Standard Repository) projects.** - -This repository provides the standardized structure, configuration, and tooling for all 139 repos in the hyperpolymath ecosystem. Use it to: - -* Bootstrap new projects with RSR compliance -* Reference the standard directory structure -* Copy configuration templates (Justfile, STATE.scm, etc.) - -== Quick Start - -[source,bash] ----- -# Clone the template -git clone https://github.com/hyperpolymath/RSR-template-repo my-project -cd my-project - -# Remove template git history -rm -rf .git -git init - -# Customize -sed -i 's/RSR-template-repo/my-project/g' Justfile guix.scm README.adoc - -# Enter development environment -guix shell -D -f build/guix.scm - -# Validate compliance -just validate-rsr ----- - -== What's Included - -[cols="1,3"] -|=== -|File/Directory |Purpose - -|`.editorconfig` -|Editor configuration (indent, charset) - -|`.gitignore` -|Standard ignore patterns - -|`.guix-channel` -|Guix channel definition - -|`.well-known/` -|RFC-compliant metadata (security.txt, ai.txt, humans.txt) - -|`docs/` -|Documentation directory - -|`guix.scm` -|Guix package definition - -|`justfile` -|Task runner with 50+ recipes - -|`LICENSE.txt` -|AGPL + Palimpsest dual license - -|`README.adoc` -|This file - -|`RSR_COMPLIANCE.adoc` -|Compliance tracking - -|`STATE.scm` -|Project state checkpoint -|=== - -== Justfile Features - -The template Justfile provides: - -* **~10 billion recipe combinations** via matrix recipes -* **Cookbook generation**: `just cookbook` → `docs/just-cookbook.adoc` -* **Man page generation**: `just man` → `docs/man/project.1` -* **RSR validation**: `just validate-rsr` -* **STATE.scm management**: `just state-touch`, `just state-phase` -* **Container support**: `just container-build`, `just container-push` -* **CI matrix**: `just ci-matrix [stage] [depth]` - -=== Key Recipes - -[source,bash] ----- -just # Show all recipes -just help # Detailed help -just info # Project info -just combinations # Show matrix options - -just build # Build (debug) -just test # Run tests -just quality # Format + lint + test -just ci # Full CI pipeline - -just validate # RSR + STATE validation -just docs # Generate all docs -just cookbook # Generate Justfile docs - -just guix-shell # Guix dev environment -just container-build # Build container ----- - -== Directory Structure - -[source] ----- -project/ -├── .editorconfig # Editor settings -├── .gitignore # Git ignore -├── .guix-channel # Guix channel -├── .well-known/ # RFC metadata -│ ├── ai.txt -│ ├── humans.txt -│ └── security.txt -├── config/ # Nickel configs (optional) -├── docs/ # Documentation -│ ├── generated/ -│ ├── man/ -│ └── just-cookbook.adoc -├── guix.scm # Guix package -├── Justfile # Task runner -├── LICENSE.txt # Dual license -├── README.adoc # Overview -├── RSR_COMPLIANCE.adoc # Compliance -├── src/ # Source code -├── STATE.scm # State checkpoint -└── tests/ # Tests ----- - -== RSR Compliance - -=== Language Tiers - -* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, ReScript -* **Tier 2** (Silver): Nickel, Racket, Guile Scheme, Nix -* **Infrastructure**: Guix channels, derivations - -=== Required Files - -* `.editorconfig` -* `.gitignore` -* `justfile` -* `README.adoc` -* `RSR_COMPLIANCE.adoc` -* `LICENSE.txt` (AGPL + Palimpsest) -* `.well-known/security.txt` -* `.well-known/ai.txt` -* `.well-known/humans.txt` -* `guix.scm` OR `flake.nix` - -=== Prohibited - -* Python outside `salt/` directory -* TypeScript/JavaScript (use ReScript) -* CUE (use Guile/Nickel) -* `Dockerfile` (use `Containerfile`) - -== STATE.scm - -The STATE.scm file tracks project state: - -[source,scheme] ----- -(define state - `((metadata - (project . "my-project") - (updated . "2025-12-10")) - (position - (phase . implementation) ; design|implementation|testing|maintenance|archived - (maturity . beta)) ; experimental|alpha|beta|production|lts - (ecosystem - (part-of . ("RSR Framework")) - (depends-on . ())))) ----- - -== Badge Schema - -Generate badges from STATE.scm: - -[source,bash] ----- -just badges standard ----- - -See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. - -== Ecosystem Integration - -This template is part of: - -* **STATE.scm Ecosystem**: Conversation checkpoints -* **RSR Framework**: Repository standards -* **Consent-Aware-HTTP**: .well-known compliance - -== License - -SPDX-License-Identifier: CC-BY-SA-4.0 - -== Links - -* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE.scm tooling -* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement -* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/affinescript/SECURITY-SETUP.adoc b/affinescript/SECURITY-SETUP.adoc deleted file mode 100644 index 60b38f7..0000000 --- a/affinescript/SECURITY-SETUP.adoc +++ /dev/null @@ -1,244 +0,0 @@ -== Security Configuration for affinescript.dev - -=== Overview - -This site is configured with maximum security settings on Cloudflare’s -free tier, including: - -* *TLS 1.3* minimum (no TLS 1.2 or older) -* *HSTS* with preload (max-age=31536000, includeSubDomains) -* *Strict SSL/TLS* mode -* *HTTP/2 and HTTP/3 (QUIC)* enabled -* *Brotli compression* -* *Consent-Aware HTTP* (GDPR/privacy compliance) -* *RFC 9116 compliant* security.txt - -=== DNS Configuration - -==== A Records (GitHub Pages) - -Both root (@) and www use A records for consistency: - -.... -@ A 185.199.108.153 -@ A 185.199.109.153 -@ A 185.199.110.153 -@ A 185.199.111.153 - -www A 185.199.108.153 -www A 185.199.109.153 -www A 185.199.110.153 -www A 185.199.111.153 -.... - -*Why A records for www instead of CNAME?* - Consistent behavior with -root domain - No CNAME chain resolution needed - Direct control over IP -addresses - Better for SEO (some crawlers prefer consistency) - -==== AAAA Records (IPv6) - -.... -@ AAAA 2606:50c0:8000::153 -@ AAAA 2606:50c0:8001::153 -@ AAAA 2606:50c0:8002::153 -@ AAAA 2606:50c0:8003::153 - -www AAAA 2606:50c0:8000::153 -www AAAA 2606:50c0:8001::153 -www AAAA 2606:50c0:8002::153 -www AAAA 2606:50c0:8003::153 -.... - -==== CAA Records (Certificate Authority Authorization) - -.... -@ CAA 128 issue "letsencrypt.org" -@ CAA 128 issuewild "letsencrypt.org" -@ CAA 128 issue "digicert.com" -@ CAA 128 iodef "mailto:security@affinescript.dev" -.... - -*Flag 128 = Critical*: If a CA doesn’t understand the CAA record, it -MUST refuse to issue the certificate. - -==== Email Security - -.... -@ TXT "v=spf1 include:_spf.github.com ~all" -_dmarc TXT "v=DMARC1; p=reject; rua=mailto:security@affinescript.dev" -.... - -=== Cloudflare Security Settings - -==== TLS/SSL - -* *Minimum TLS Version*: 1.3 -* *SSL Mode*: Strict (Full with certificate verification) -* *Always Use HTTPS*: On -* *Automatic HTTPS Rewrites*: On -* *Opportunistic Encryption*: On -* *TLS 1.3 0-RTT*: On - -==== HSTS (HTTP Strict Transport Security) - -.... -Strict-Transport-Security: max-age=31536000; includeSubDomains; preload -.... - -* *Max Age*: 31536000 seconds (1 year) -* *Include Subdomains*: Yes -* *Preload*: Yes (eligible for browser preload lists) - -To add to Chrome’s preload list: https://hstspreload.org/ - -==== Security Headers - -* *X-Content-Type-Options*: nosniff -* *X-Frame-Options*: SAMEORIGIN -* *X-XSS-Protection*: 1; mode=block -* *Referrer-Policy*: strict-origin-when-cross-origin -* *Permissions-Policy*: Configured via meta tags or headers - -==== Performance - -* *HTTP/2*: Enabled -* *HTTP/3 (QUIC)*: Enabled -* *Brotli Compression*: Enabled -* *0-RTT Connection Resumption*: Enabled - -==== Bot Management - -* *Security Level*: Medium (allows search bots) -* *Browser Integrity Check*: On -* *Challenge Passage*: 30 minutes -* *Email Obfuscation*: On - -=== Consent-Aware HTTP - -This site implements consent-aware-http for GDPR/privacy compliance. - -==== Consent Categories - -[arabic] -. *Essential* (always on) -* Core site functionality -* Security features -* Session management -. *Functional* -* Enhanced features -* User preferences -* Language settings -. *Analytics* -* Anonymous usage statistics -* Performance monitoring -* Error tracking -. *Marketing* -* Advertising -* Campaign tracking -* Social media integration -. *Personalization* -* Customized content -* Recommendations -* User profiling - -==== Implementation - -*Cloudflare Worker* (consent-aware-http.js) intercepts all requests and: -1. Checks for `+user-consent+` cookie 2. Validates consent level matches -resource requirements 3. Returns 403 if consent not granted 4. Passes -request to origin if consent valid - -*Frontend* includes consent banner allowing users to: - View current -consent settings - Grant/revoke consent per category - Export consent -preferences - Delete all tracking data - -==== Testing Consent - -[source,bash] ----- -# Without consent cookie (essential only) -curl https://affinescript.dev/api/analytics -# → 403 Forbidden (requires analytics consent) - -# With analytics consent -curl -b "user-consent=%7B%22analytics%22%3Atrue%7D" \ - https://affinescript.dev/api/analytics -# → 200 OK - -# Essential resources always work -curl https://affinescript.dev/ -# → 200 OK (no consent needed) ----- - -=== .well-known/security.txt - -RFC 9116 compliant security contact information: - -.... -https://affinescript.dev/.well-known/security.txt -.... - -Contains: - Security contact emails - GitHub security advisory links - -PGP encryption keys - Security policy links - Acknowledgments page - -Consent-aware-http endpoints - Expiration date (1 year) - -=== Verification - -==== Check DNS Records - -[source,bash] ----- -dig affinescript.dev A +short -dig affinescript.dev AAAA +short -dig affinescript.dev CAA +short -dig _dmarc.affinescript.dev TXT +short ----- - -==== Check TLS Configuration - -[source,bash] ----- -curl -I https://affinescript.dev | grep -i strict-transport ----- - -==== Check security.txt - -[source,bash] ----- -curl https://affinescript.dev/.well-known/security.txt ----- - -==== SSL Labs Test - -https://www.ssllabs.com/ssltest/analyze.html?d=affinescript.dev - -*Expected Grade*: A+ (with HSTS preload) - -==== SecurityHeaders.com - -https://securityheaders.com/?q=affinescript.dev - -*Expected Grade*: A+ (with all headers configured) - -=== Reporting Security Issues - -*DO NOT* open public GitHub issues for security vulnerabilities. - -Instead: 1. Email: security@affinescript.dev 2. GitHub Security -Advisory: -https://github.com/hyperpolymath/affinescript/security/advisories/new 3. -PGP encrypted: -https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk - -We aim to respond within 48 hours. - -=== Privacy & Consent Issues - -For privacy concerns or consent management: - Email: -privacy@affinescript.dev - Consent settings: -https://affinescript.dev/privacy#consent - Data export/deletion: -https://affinescript.dev/privacy#your-rights - -=== License - -Security configuration: MPL-2.0 Documentation: CC-BY-SA-4.0 diff --git a/affinescript/SECURITY.adoc b/affinescript/SECURITY.adoc deleted file mode 100644 index d93ef9d..0000000 --- a/affinescript/SECURITY.adoc +++ /dev/null @@ -1,452 +0,0 @@ -== Security Policy - -We take security seriously. We appreciate your efforts to responsibly -disclose vulnerabilities and will make every effort to acknowledge your -contributions. - -=== Table of Contents - -* link:#reporting-a-vulnerability[Reporting a Vulnerability] -* link:#what-to-include[What to Include] -* link:#response-timeline[Response Timeline] -* link:#disclosure-policy[Disclosure Policy] -* link:#scope[Scope] -* link:#safe-harbour[Safe Harbour] -* link:#recognition[Recognition] -* link:#security-updates[Security Updates] -* link:#security-best-practices[Security Best Practices] - -''''' - -=== Reporting a Vulnerability - -==== Preferred Method: GitHub Security Advisories - -The preferred method for reporting security vulnerabilities is through -GitHub’s Security Advisory feature: - -[arabic] -. Navigate to -https://github.com/hyperpolymath/proof-burrower/security/advisories/new[Report -a Vulnerability] -. Click *"`Report a vulnerability`"* -. Complete the form with as much detail as possible -. Submit — we’ll receive a private notification - -This method ensures: - -* End-to-end encryption of your report -* Private discussion space for collaboration -* Coordinated disclosure tooling -* Automatic credit when the advisory is published - -==== Alternative: Encrypted Email - -If you cannot use GitHub Security Advisories, you may email us directly: - -[cols=",",] -|=== -|*Email* |j.d.a.jewell@open.ac.uk -|*PGP Key* |link:%7B%7BPGP_KEY_URL%7D%7D[Download Public Key] -|*Fingerprint* |`+{{PGP_FINGERPRINT}}+` -|=== - -[source,bash] ----- -# Import our PGP key -curl -sSL {{PGP_KEY_URL}} | gpg --import - -# Verify fingerprint -gpg --fingerprint j.d.a.jewell@open.ac.uk - -# Encrypt your report -gpg --armor --encrypt --recipient j.d.a.jewell@open.ac.uk report.txt ----- - -____ -*⚠️ Important:* Do not report security vulnerabilities through public -GitHub issues, pull requests, discussions, or social media. -____ - -''''' - -=== What to Include - -A good vulnerability report helps us understand and reproduce the issue -quickly. - -==== Required Information - -* *Description*: Clear explanation of the vulnerability -* *Impact*: What an attacker could achieve (confidentiality, integrity, -availability) -* *Affected versions*: Which versions/commits are affected -* *Reproduction steps*: Detailed steps to reproduce the issue - -==== Helpful Additional Information - -* *Proof of concept*: Code, scripts, or screenshots demonstrating the -vulnerability -* *Attack scenario*: Realistic attack scenario showing exploitability -* *CVSS score*: Your assessment of severity (use -https://www.first.org/cvss/calculator/3.1[CVSS 3.1 Calculator]) -* *CWE ID*: Common Weakness Enumeration identifier if known -* *Suggested fix*: If you have ideas for remediation -* *References*: Links to related vulnerabilities, research, or -advisories - -==== Example Report Structure - -[source,markdown] ----- -## Summary -[One-sentence description of the vulnerability] - -## Vulnerability Type -[e.g., SQL Injection, XSS, SSRF, Path Traversal, etc.] - -## Affected Component -[File path, function name, API endpoint, etc.] - -## Affected Versions -[Version range or specific commits] - -## Severity Assessment -- CVSS 3.1 Score: [X.X] -- CVSS Vector: [CVSS:3.1/AV:X/AC:X/PR:X/UI:X/S:X/C:X/I:X/A:X] - -## Description -[Detailed technical description] - -## Steps to Reproduce -1. [First step] -2. [Second step] -3. [...] - -## Proof of Concept -[Code, curl commands, screenshots, etc.] - -## Impact -[What can an attacker achieve?] - -## Suggested Remediation -[Optional: your ideas for fixing] - -## References -[Links to related issues, CVEs, research] ----- - -''''' - -=== Response Timeline - -We commit to the following response times: - -[width="100%",cols="24%,35%,41%",options="header",] -|=== -|Stage |Timeframe |Description -|*Initial Response* |48 hours |We acknowledge receipt and confirm we’re -investigating - -|*Triage* |7 days |We assess severity, confirm the vulnerability, and -estimate timeline - -|*Status Update* |Every 7 days |Regular updates on remediation progress - -|*Resolution* |90 days |Target for fix development and release (complex -issues may take longer) - -|*Disclosure* |90 days |Public disclosure after fix is available -(coordinated with you) -|=== - -____ -*Note:* These are targets, not guarantees. Complex vulnerabilities may -require more time. We’ll communicate openly about any delays. -____ - -''''' - -=== Disclosure Policy - -We follow *coordinated disclosure* (also known as responsible -disclosure): - -[arabic] -. *You report* the vulnerability privately -. *We acknowledge* and begin investigation -. *We develop* a fix and prepare a release -. *We coordinate* disclosure timing with you -. *We publish* security advisory and fix simultaneously -. *You may publish* your research after disclosure - -==== Our Commitments - -* We will not take legal action against researchers who follow this -policy -* We will work with you to understand and resolve the issue -* We will credit you in the security advisory (unless you prefer -anonymity) -* We will notify you before public disclosure -* We will publish advisories with sufficient detail for users to assess -risk - -==== Your Commitments - -* Report vulnerabilities promptly after discovery -* Give us reasonable time to address the issue before disclosure -* Do not access, modify, or delete data beyond what’s necessary to -demonstrate the vulnerability -* Do not degrade service availability (no DoS testing on production) -* Do not share vulnerability details with others until coordinated -disclosure - -==== Disclosure Timeline - -.... -Day 0 You report vulnerability -Day 1-2 We acknowledge receipt -Day 7 We confirm vulnerability and share initial assessment -Day 7-90 We develop and test fix -Day 90 Coordinated public disclosure - (earlier if fix is ready; later by mutual agreement) -.... - -If we cannot reach agreement on disclosure timing, we default to 90 days -from your initial report. - -''''' - -=== Scope - -==== In Scope ✅ - -The following are within scope for security research: - -* This repository (`+hyperpolymath/proof-burrower+`) and all its code -* Official releases and packages published from this repository -* Documentation that could lead to security issues -* Build and deployment configurations in this repository -* Dependencies (report here, we’ll coordinate with upstream) - -==== Out of Scope ❌ - -The following are *not* in scope: - -* Third-party services we integrate with (report directly to them) -* Social engineering attacks against maintainers -* Physical security -* Denial of service attacks against production infrastructure -* Spam, phishing, or other non-technical attacks -* Issues already reported or publicly known -* Theoretical vulnerabilities without proof of concept - -==== Qualifying Vulnerabilities - -We’re particularly interested in: - -* Remote code execution -* SQL injection, command injection, code injection -* Authentication/authorisation bypass -* Cross-site scripting (XSS) and cross-site request forgery (CSRF) -* Server-side request forgery (SSRF) -* Path traversal / local file inclusion -* Information disclosure (credentials, PII, secrets) -* Cryptographic weaknesses -* Deserialisation vulnerabilities -* Memory safety issues (buffer overflows, use-after-free, etc.) -* Supply chain vulnerabilities (dependency confusion, etc.) -* Significant logic flaws - -==== Non-Qualifying Issues - -The following generally do not qualify as security vulnerabilities: - -* Missing security headers on non-sensitive pages -* Clickjacking on pages without sensitive actions -* Self-XSS (requires victim to paste code) -* Missing rate limiting (unless it enables a specific attack) -* Username/email enumeration (unless high-risk context) -* Missing cookie flags on non-sensitive cookies -* Software version disclosure -* Verbose error messages (unless exposing secrets) -* Best practice deviations without demonstrable impact - -''''' - -=== Safe Harbour - -We support security research conducted in good faith. - -==== Our Promise - -If you conduct security research in accordance with this policy: - -* ✅ We will not initiate legal action against you -* ✅ We will not report your activity to law enforcement -* ✅ We will work with you in good faith to resolve issues -* ✅ We consider your research authorised under the Computer Fraud and -Abuse Act (CFAA), UK Computer Misuse Act, and similar laws -* ✅ We waive any potential claim against you for circumvention of -security controls - -==== Good Faith Requirements - -To qualify for safe harbour, you must: - -* Comply with this security policy -* Report vulnerabilities promptly -* Avoid privacy violations (do not access others’ data) -* Avoid service degradation (no destructive testing) -* Not exploit vulnerabilities beyond proof-of-concept -* Not use vulnerabilities for profit (beyond bug bounties where offered) - -____ -*⚠️ Important:* This safe harbour does not extend to third-party -systems. Always check their policies before testing. -____ - -''''' - -=== Recognition - -We believe in recognising security researchers who help us improve. - -==== Hall of Fame - -Researchers who report valid vulnerabilities will be acknowledged in our -link:SECURITY-ACKNOWLEDGMENTS.md[Security Acknowledgments] (unless they -prefer anonymity). - -Recognition includes: - -* Your name (or chosen alias) -* Link to your website/profile (optional) -* Brief description of the vulnerability class -* Date of report - -==== What We Offer - -* ✅ Public credit in security advisories -* ✅ Acknowledgment in release notes -* ✅ Entry in our Hall of Fame -* ✅ Reference/recommendation letter upon request (for significant -findings) - -==== What We Don’t Currently Offer - -* ❌ Monetary bug bounties -* ❌ Hardware or swag -* ❌ Paid security research contracts - -____ -*Note:* We’re a community project with limited resources. Your -contributions help everyone who uses this software. -____ - -''''' - -=== Security Updates - -==== Receiving Updates - -To stay informed about security updates: - -* *Watch this repository*: Click "`Watch`" → "`Custom`" → Select -"`Security alerts`" -* *GitHub Security Advisories*: Published at -https://github.com/hyperpolymath/proof-burrower/security/advisories[Security -Advisories] -* *Release notes*: Security fixes noted in link:CHANGELOG.md[CHANGELOG] - -==== Update Policy - -[cols=",",options="header",] -|=== -|Severity |Response -|*Critical/High* |Patch release as soon as fix is ready -|*Medium* |Included in next scheduled release (or earlier) -|*Low* |Included in next scheduled release -|=== - -==== Supported Versions - -[cols=",,",options="header",] -|=== -|Version |Supported |Notes -|`+main+` branch |✅ Yes |Latest development -|Latest release |✅ Yes |Current stable -|Previous minor release |✅ Yes |Security fixes backported -|Older versions |❌ No |Please upgrade -|=== - -''''' - -=== Security Best Practices - -When using Proof Burrower, we recommend: - -==== General - -* Keep dependencies up to date -* Use the latest stable release -* Subscribe to security notifications -* Review configuration against security documentation -* Follow principle of least privilege - -==== For Contributors - -* Never commit secrets, credentials, or API keys -* Use signed commits (`+git config commit.gpgsign true+`) -* Review dependencies before adding them -* Run security linters locally before pushing -* Report any concerns about existing code - -''''' - -=== Additional Resources - -* link:%7B%7BPGP_KEY_URL%7D%7D[Our PGP Public Key] -* https://github.com/hyperpolymath/proof-burrower/security/advisories[Security -Advisories] -* link:CHANGELOG.md[Changelog] -* link:CONTRIBUTING.md[Contributing Guidelines] -* https://cve.mitre.org/[CVE Database] -* https://www.first.org/cvss/calculator/3.1[CVSS Calculator] - -''''' - -=== Contact - -[width="100%",cols="50%,50%",options="header",] -|=== -|Purpose |Contact -|*Security issues* -|https://github.com/hyperpolymath/proof-burrower/security/advisories/new[Report -via GitHub] or j.d.a.jewell@open.ac.uk - -|*General questions* -|https://github.com/hyperpolymath/proof-burrower/discussions[GitHub -Discussions] - -|*Other enquiries* |See link:README.md[README] for contact information -|=== - -''''' - -=== Policy Changes - -This security policy may be updated from time to time. Significant -changes will be: - -* Committed to this repository with a clear commit message -* Noted in the changelog -* Announced via GitHub Discussions (for major changes) - -''''' - -_Thank you for helping keep Proof Burrower and its users safe._ 🛡️ - -''''' - -Last updated: 2026 · Policy version: 1.0.0 diff --git a/affinescript/SESSION-2026-01-23.adoc b/affinescript/SESSION-2026-01-23.adoc deleted file mode 100644 index 7dd7d2b..0000000 --- a/affinescript/SESSION-2026-01-23.adoc +++ /dev/null @@ -1,301 +0,0 @@ -== AffineScript Development Session - 2026-01-23 - -=== Session Overview - -This session focused on completing the four priorities requested: 1. ✅ -Complete interpreter implementation 2. 🔄 WebAssembly code generation 3. -⏳ Standard library (not started) 4. ⏳ Module system (not started) - -=== Major Accomplishments - -==== 1. Tutorial System Complete ✅ - -Created comprehensive tutorial series (lessons 2-10): - -*Lesson 2: Functions and Ownership* - Function declarations with -ownership parameters - `+own+`, `+ref+`, and `+mut+` parameter modes - -Move semantics and borrowing - -*Lesson 3: Data Structures* - Tuples, records, arrays - Type annotations -- Field access and destructuring - -*Lesson 4: Pattern Matching* - Match expressions - Pattern types: -wildcards, literals, tuples, records, variants - Guards and -exhaustiveness - -*Lesson 5: Types and Inference* - Type annotations - Type inference - -Generic type parameters - -*Lesson 6: Error Handling* - Result types - Error propagation with `+?+` -operator - Explicit error handling - -*Lesson 7: Algebraic Effects* - Effect declarations - Effect handlers - -Resume mechanism - -*Lesson 8: Generic Types* - Type parameters - Generic data structures - -Parametric polymorphism - -*Lesson 9: Modules and Imports* - Module declarations - Import -statements - Selective imports - -*Lesson 10: Building Real Programs* - Project structure - Testing - -Compilation commands - -*Location:* `+docs/tutorial/lesson-{02-10}-*.md+` - -==== 2. Effect Handlers Implementation ✅ - -Implemented algebraic effects and handlers in the interpreter: - -*Features Implemented:* - Effect declarations create builtin operations -- Effect operations raise `+PerformEffect+` errors when called - -`+handle+` expressions catch and dispatch to handlers - -`+HandlerReturn+` can intercept final return values - `+HandlerOp+` -matches specific effect operations - Error propagation for unhandled -effects - -*Implementation Details:* - Added `+PerformEffect+` error type to -`+Value.eval_error+` - Implemented `+eval_handle+` function for handler -matching - Implemented `+eval_effect_handler+` for operation dispatch - -Effect operations registered as builtins in `+eval_decl+` - -*Example:* - -[source,affinescript] ----- -effect Ask { - fn get_value() -> Int; -} - -fn main() -> Int { - return handle get_value() { - get_value() => { - return 42; - } - }; -} ----- - -*Limitations:* - No delimited continuations (yet) - Effects only work at -top level of handle expression - Resume doesn’t continue suspended -computations - Multiple sequential effects don’t work correctly - -*Documentation:* `+docs/EFFECTS-IMPLEMENTATION.md+` *Test:* -`+tests/effects/basic_effect.affine+` - -==== 3. WebAssembly Code Generation Infrastructure 🔄 - -Created complete WASM code generation infrastructure: - -*Wasm Module (`+lib/wasm.ml+`):* - Complete WASM IR definitions - Value -types: I32, I64, F32, F64 - All WASM instructions: - Control flow: -block, loop, if, br, brif, call, return - Memory: load, store, -memory.size, memory.grow - Arithmetic: add, sub, mul, div, rem - -Comparison: eq, ne, lt, le, gt, ge - Bitwise: and, or, xor, shl, shr - -Conversions: wrap, extend, trunc, convert, promote, demote - Module -structure: types, functions, exports, imports, memory, globals - Based -on WebAssembly 1.0 specification - -*Codegen Module (`+lib/codegen.ml+`):* - Code generation context with -locals tracking - Expression code generation: - ✅ Literals: int, bool, -float, char, unit - ✅ Variables: local get/set - ✅ Binary operations: -arithmetic, comparison, logical, bitwise - ✅ Unary operations: -negation, logical not - ✅ If expressions with then/else branches - ✅ -Let bindings (simple variable patterns) - ✅ Blocks with multiple -statements - ✅ Return statements - Statement code generation: - ✅ Let -statements - ✅ Expression statements (with drop) - ✅ While loops -(using WASM block/loop/br) - ❌ For loops (not yet) - ❌ Assignment (not -yet) - Function code generation: - ✅ Basic structure - ✅ Parameter -handling - ❌ Function calls (not yet) - ❌ Closures (not yet) - -Top-level declarations: - ✅ Functions (with export for `+main+`) - ❌ -Constants (not yet) - ❌ Types (ignored for now) - -*What’s Missing:* - WASM binary encoder (currently just IR) - Function -calls and indirect calls - Heap allocation and memory management - -Complex data structures (tuples, records, arrays) - Pattern matching -translation - Effect handler codegen - Runtime support functions - -*Status:* Infrastructure complete, 30% done overall - -=== Commits Made - -==== Commit 1: Implement basic effect handler support - -* *Hash:* `+5115ede+` -* *Files changed:* 13 -* *Insertions:* 562 -* *Deletions:* 5 -* *Files added:* -** `+docs/EFFECTS-IMPLEMENTATION.md+` -** `+docs/tutorial/lesson-{02-10}-*.md+` (9 files) -** `+tests/effects/basic_effect.affine+` -* *Files modified:* -** `+lib/value.ml+`: Add PerformEffect error type -** `+lib/interp.ml+`: Implement effect handlers - -==== Commit 2: Add WebAssembly code generation infrastructure - -* *Hash:* `+dae5c09+` -* *Files changed:* 4 -* *Insertions:* 608 -* *Deletions:* 9 -* *Files added:* -** `+lib/wasm.ml+`: WASM intermediate representation -** `+lib/codegen.ml+`: Code generator -* *Files modified:* -** `+lib/dune+`: Add wasm and codegen modules -** `+STATE.scm+`: Update progress - -==== Commit 3: Update STATE.scm with session progress - -* *Hash:* `+06f4bb7+` -* *Files changed:* 1 -* *Insertions:* 13 -* *Deletions:* 3 - -=== Project Status Update - -==== Completion Metrics - -* *Overall:* 55% → 60% -* *Lexer:* 90% -* *Parser:* 75% -* *AST:* 100% -* *Borrow Checker:* 95% -* *Type Checker:* 40% -* *Interpreter:* 80% → 85% -* *WASM Codegen:* 0% → 30% -* *Standard Library:* 10% -* *Tooling:* 30% - -==== Working Features - -* ✅ Lexical analysis with token spans -* ✅ Parser with explicit return statements -* ✅ AST representation -* ✅ Error reporting with source locations -* ✅ Basic type definitions -* ✅ Affine type checking with ownership tracking -* ✅ Borrow checker detects use-after-move errors -* ✅ Function parameter type inference -* ✅ Tree-walking interpreter with pattern matching -* ✅ Control flow: while loops, for loops -* ✅ Basic algebraic effect handlers -* ✅ WebAssembly IR and basic code generation - -==== Known Issues - -* *Parser:* 63 shift/reduce conflicts, 5 reduce/reduce conflicts -* *Lambda Syntax:* Test files use `+fn(x) => expr+` but parser supports -`+|x| expr+` -* *Implicit Returns:* Not supported (intentional design decision) -* *Type Checker:* Limited coverage (missing dependent types, row -polymorphism, effect inference) -* *Effect Handlers:* Only work at top level, need delimited -continuations -* *WASM Codegen:* No binary encoder yet - -=== Task Status - -==== Completed - -[arabic] -. ✅ Create "`What Makes AffineScript Brilliant`" landing page -. ✅ Create Lesson 1: Hello AffineScript -. ✅ Create Lessons 2-10 structure -. ✅ Integrate borrow checker into compiler pipeline -. ✅ Fix parser block/record ambiguity -. ✅ Fix type inference for function parameters -. ✅ Test borrow checker with use-after-move -. ✅ Complete interpreter implementation - -==== In Progress - -[arabic, start=9] -. 🔄 Implement WebAssembly code generation -* ✅ WASM IR definitions -* ✅ Basic code generator -* ⏳ Binary encoder (next priority) -* ⏳ Function calls -* ⏳ Memory management -* ⏳ Complex data structures - -=== Next Steps - -==== Immediate (This Week) - -[arabic] -. *WASM Binary Encoder* - Implement binary encoder to output .wasm files -. *Function Calls* - Add support for direct and indirect function calls -. *Memory Management* - Implement linear memory allocation for heap data -. *Test End-to-End* - AffineScript → WASM → run in browser/wasmtime - -==== Medium Term (This Month) - -[arabic] -. *Standard Library* - Basic I/O, data structures, string operations -. *Module System* - Simple module/import system for code organization -. *Delimited Continuations* - Full effect handler resume support -. *More Tests* - Expand borrow checker tests (mut borrows, field access, -lifetimes) - -==== Long Term - -[arabic] -. *Dependent Types* - Dependent type checking implementation -. *Row Polymorphism* - Extensible records -. *Effect Inference* - Automatic effect inference and checking -. *IDE Tooling* - LSP server, syntax highlighting - -=== Files Modified This Session - -==== Created - -* `+docs/tutorial/lesson-02-functions.md+` -* `+docs/tutorial/lesson-03-data.md+` -* `+docs/tutorial/lesson-04-patterns.md+` -* `+docs/tutorial/lesson-05-types.md+` -* `+docs/tutorial/lesson-06-errors.md+` -* `+docs/tutorial/lesson-07-effects.md+` -* `+docs/tutorial/lesson-08-generics.md+` -* `+docs/tutorial/lesson-09-modules.md+` -* `+docs/tutorial/lesson-10-building.md+` -* `+docs/EFFECTS-IMPLEMENTATION.md+` -* `+tests/effects/basic_effect.affine+` -* `+lib/wasm.ml+` -* `+lib/codegen.ml+` -* `+SESSION-2026-01-23.md+` (this file) - -==== Modified - -* `+lib/value.ml+` - Add PerformEffect error -* `+lib/interp.ml+` - Implement effect handlers -* `+lib/dune+` - Add wasm and codegen modules -* `+STATE.scm+` - Update progress and session history - -=== Lines of Code - -* *Tutorial lessons:* ~350 lines (9 files) -* *Effect handlers:* ~100 lines -* *WASM IR:* ~350 lines -* *Code generator:* ~360 lines -* *Documentation:* ~200 lines -* *Total session output:* ~1,360 lines - -=== Summary - -This was a highly productive session that completed most of the -interpreter implementation and established the foundation for -WebAssembly code generation. The effect handler implementation is -functional for basic use cases and demonstrates the core concepts, even -though it lacks full continuation support. The WASM codegen -infrastructure is complete and ready for the binary encoder -implementation. - -*Priorities Status:* - ✅ #1: Interpreter - COMPLETE - 🔄 #2: -WebAssembly - Infrastructure complete, need binary encoder - ⏳ #3: -Standard library - Not started - ⏳ #4: Module system - Not started - -*Key Achievement:* AffineScript now has a working interpreter that can -execute programs with affine types, pattern matching, control flow, and -basic algebraic effects! diff --git a/affinescript/SESSION-COMPLETE.adoc b/affinescript/SESSION-COMPLETE.adoc deleted file mode 100644 index ae92bf6..0000000 --- a/affinescript/SESSION-COMPLETE.adoc +++ /dev/null @@ -1,583 +0,0 @@ -== AffineScript Session Complete - 2026-01-23/24 - -=== 🎉 All Priorities Accomplished! - -This extended session completed all four priorities requested by the -user: - -[arabic] -. ✅ *Complete interpreter implementation* - DONE (85%) -. ✅ *WebAssembly code generation* - WORKING (70%) -. ✅ *Standard library implementation* - DONE (60%) -. ⚠️ *Module system* - Parser ready, needs resolution phase - -=== Summary Statistics - -*Overall Project Completion:* 55% → 70% (+15%) - -[cols=",,,",options="header",] -|=== -|Component |Before |After |Delta -|Interpreter |80% |85% |+5% -|WASM Codegen |30% |70% |+40% -|Standard Library |10% |60% |+50% -|=== - -*Session Output:* - *8 commits* made - *~2,200 lines* of code written - -*15 files* created/modified - *4 stdlib modules* implemented - *1 -working compiler* (AffineScript → WebAssembly) - -=== Priority 1: Interpreter ✅ COMPLETE - -*Status:* 85% complete, fully functional - -*Completed earlier in session:* - ✅ Pattern matching (all pattern -types) - ✅ Control flow (while, for loops) - ✅ Effect handlers (basic -algebraic effects) - ✅ Tutorial lessons 2-10 created - ✅ Comprehensive -documentation - -*What works:* - Execute AffineScript programs - Pattern matching on all -types - Effect operations and handlers - Control flow constructs - -Affine type checking at runtime - -*Example:* - -[source,bash] ----- -affinescript eval program.affine ----- - -=== Priority 2: WebAssembly Codegen ✅ WORKING - -*Status:* 70% complete, end-to-end working - -==== What Was Implemented - -*WASM IR Module (lib/wasm.ml)* - 350 lines - Complete WASM 1.0 -specification - All value types (I32, I64, F32, F64) - All instructions -(control flow, memory, arithmetic, conversions) - Module structure -(types, functions, exports, imports, memory) - -*Code Generator (lib/codegen.ml)* - 360+ lines - Expression code -generation with context threading - Statement code generation - Function -code generation - Proper local variable allocation - -*Binary Encoder (lib/wasm_encode.ml)* - 430 lines - LEB128 encoding -(unsigned and signed) - IEEE 754 float encoding - Complete instruction -encoding - Section-based module encoding - File output with -`+write_module_to_file+` - -==== What Works - -*Supported Features:* - ✅ Literals (int, bool, float, char, unit) - ✅ -Variables (local get/set) - ✅ Binary operations (arithmetic, -comparison, logical, bitwise) - ✅ Unary operations (negation, logical -not) - ✅ If expressions (with then/else) - ✅ Let bindings (simple -patterns) - ✅ Blocks and return statements - ✅ While loops - ✅ -Function definitions - ✅ Function exports (main) - ✅ Memory allocation -(1 page default) - -*End-to-End Pipeline:* - -.... -AffineScript source (.affine) - ↓ parse -AST - ↓ resolve names -Symbol table - ↓ generate code -WASM IR - ↓ encode binary -WebAssembly module (.wasm) - ↓ execute -Result -.... - -==== Verified Working - -*Test Program:* `+simple_arithmetic.affine+` - -[source,affinescript] ----- -fn main() -> Int { - let a = 10; - let b = 32; - let c = a + b; - return c; -} ----- - -*Compilation:* - -[source,bash] ----- -affinescript compile simple_arithmetic.affine -o test.wasm ----- - -*Execution with Node.js:* - -[source,javascript] ----- -const { main } = wasmModule.exports; -console.log(main()); // Output: 42 ✓ ----- - -*File verification:* - -[source,bash] ----- -$ file test.wasm -test.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) ----- - -==== What’s Missing (30%) - -Still TODO for complete WASM support: - Function calls and indirect -calls - Closures and function pointers - Heap memory management - -Complex data structures (tuples, records, arrays) - Pattern matching -translation - Effect handler codegen - Runtime support functions - -Standard library FFI - -=== Priority 3: Standard Library ✅ COMPLETE - -*Status:* 60% complete, 4 modules implemented - -==== Modules Created - -*1. Core.affine* - Basic utilities (~100 lines) - -[source,affinescript] ----- -pub fn min(a: Int, b: Int) -> Int -pub fn max(a: Int, b: Int) -> Int -pub fn abs(x: Int) -> Int -pub fn clamp(x: Int, low: Int, high: Int) -> Int -pub fn compose[A, B, C](f, g) // Function composition -pub fn flip[A, B, C](f) // Flip arguments ----- - -*2. Result.affine* - Error handling (~120 lines) - -[source,affinescript] ----- -pub fn unwrap[T, E](own r: Result[T, E]) -> T -pub fn unwrap_or[T, E](own r: Result[T, E], default: T) -> T -pub fn map[T, U, E](own r: Result[T, E], f: fn(T) -> U) -> Result[U, E] -pub fn and_then[T, U, E](own r: Result[T, E], f: fn(T) -> Result[U, E]) -> Result[U, E] ----- - -*3. Option.affine* - Optional values (~150 lines) - -[source,affinescript] ----- -pub fn unwrap[T](own opt: Option[T]) -> T -pub fn unwrap_or[T](own opt: Option[T], default: T) -> T -pub fn map[T, U](own opt: Option[T], f: fn(T) -> U) -> Option[U] -pub fn and_then[T, U](own opt: Option[T], f: fn(T) -> Option[U]) -> Option[U] -pub fn filter[T](own opt: Option[T], pred: fn(ref T) -> Bool) -> Option[T] ----- - -*4. Math.affine* - Mathematical functions (~150 lines) - -[source,affinescript] ----- -pub const PI: Float = 3.141592653589793; -pub const E: Float = 2.718281828459045; - -pub fn pow(base: Int, exp: Int) -> Int -pub fn gcd(a: Int, b: Int) -> Int -pub fn lcm(a: Int, b: Int) -> Int -pub fn factorial(n: Int) -> Int -pub fn fib(n: Int) -> Int ----- - -==== Features - -*All modules use:* - Generic type parameters (`+[T]+`, `+[T, E]+`) - -Affine ownership annotations (`+own+`, `+ref+`) - Pattern matching for -type discrimination - Higher-order functions - Comprehensive error -handling - -*Documentation:* - Complete README.md with usage examples - Import -syntax documentation - Examples for each function - Status of -implemented vs TODO features - -==== Usage Example - -[source,affinescript] ----- -use Core::{min, max}; -use Result::{map, unwrap_or}; -use Option::filter; -use Math::{PI, pow}; - -fn calculate() -> Int { - let smaller = min(10, 20); - let area = PI * pow(radius, 2); - return area; -} ----- - -==== What’s Missing (40%) - -Still TODO: - String manipulation functions - Array/List operations -(map, filter, fold, etc.) - I/O functions (requires FFI) - -Transcendental math (sin, cos, sqrt - requires FFI) - Date/Time -utilities - File system operations - Async/concurrency primitives - -=== Priority 4: Module System ⚠️ PARTIAL - -*Status:* Parser ready, needs resolution/evaluation - -==== What Exists - -*Parser support ✅:* - `+module+` declarations parsed - `+use+` imports -parsed - Module paths in AST - Import specifiers (simple, selective, -aliased) - -*Example parsed correctly:* - -[source,affinescript] ----- -module Math.Geometry; - -use Core::{min, max}; -use String as S; - -pub fn area(radius: Float) -> Float { - return PI * radius * radius; -} ----- - -==== What’s Missing - -*Resolution phase:* - Module path resolution - Import binding - Symbol -table organization by module - Cross-module name lookup - -*Evaluation phase:* - Module-scoped evaluation - Import resolution at -runtime - Standard library path configuration - -==== Implementation Plan - -[arabic] -. *Resolution:* -* Organize symbol table by module paths -* Resolve imports to module symbols -* Handle selective imports (`+use Mod::{a, b}+`) -* Handle aliases (`+use Mod as M+`) -. *File System:* -* Module path to file path mapping -* Standard library location configuration -* Module search paths -. *Evaluation:* -* Load and evaluate imported modules -* Cache module evaluation results -* Handle circular dependencies - -=== Commits Made - -==== Session Commits - -[arabic] -. *`+5115ede+`* - Implement basic effect handler support (562 lines) -. *`+dae5c09+`* - Add WebAssembly code generation infrastructure (608 -lines) -. *`+06f4bb7+`* - Update STATE.scm with session progress -. *`+34a239c+`* - Add comprehensive session summary -. *`+f90fb1b+`* - Implement WebAssembly binary encoder (544 lines) -. *`+adc4b3d+`* - Add standard library modules (576 lines) -. *`+f6564a3+`* - Update STATE.scm with complete progress -. *`+(this)+`* - Final session completion document - -*Total changes:* - *2,890 insertions* - *79 deletions* - *15 files -created* - *8 files modified* - -=== Files Created This Session - -==== Documentation - -* `+docs/EFFECTS-IMPLEMENTATION.md+` - Effect handler documentation -* `+docs/tutorial/lesson-{02-10}-*.md+` - 9 tutorial lessons -* `+stdlib/README.md+` - Standard library documentation -* `+SESSION-2026-01-23.md+` - Initial session summary -* `+SESSION-COMPLETE.md+` - This file - -==== Source Code - -* `+lib/wasm.ml+` - WASM IR definitions -* `+lib/codegen.ml+` - Code generator -* `+lib/wasm_encode.ml+` - Binary encoder -* `+stdlib/Core.affine+` - Core utilities -* `+stdlib/Result.affine+` - Result error handling -* `+stdlib/Option.affine+` - Option optional values -* `+stdlib/Math.affine+` - Mathematical functions - -==== Tests - -* `+tests/effects/basic_effect.affine+` - Effect handler test -* `+tests/codegen/simple_arithmetic.affine+` - WASM codegen test - -=== Working Examples - -==== 1. Effect Handlers - -*Source:* - -[source,affinescript] ----- -effect Ask { - fn get_value() -> Int; -} - -fn main() -> Int { - return handle get_value() { - get_value() => { - return 42; - } - }; -} ----- - -*Run:* - -[source,bash] ----- -affinescript eval effect_test.affine -# Output: Program executed successfully ----- - -==== 2. WebAssembly Compilation - -*Source:* - -[source,affinescript] ----- -fn main() -> Int { - let a = 10; - let b = 32; - let c = a + b; - return c; -} ----- - -*Compile:* - -[source,bash] ----- -affinescript compile arithmetic.affine -o output.wasm -# Output: Compiled arithmetic.affine -> output.wasm ----- - -*Execute:* - -[source,javascript] ----- -// Node.js -WebAssembly.instantiate(fs.readFileSync('output.wasm')).then(result => { - console.log(result.instance.exports.main()); // 42 -}); ----- - -==== 3. Standard Library Usage - -*Source:* - -[source,affinescript] ----- -use Core::{min, max, abs}; -use Math::{pow, gcd}; - -fn main() -> Int { - let a = min(10, 20); - let b = max(10, 20); - let c = abs(-15); - let d = pow(2, 8); - let e = gcd(48, 18); - return a + b + c + d + e; // 10 + 20 + 15 + 256 + 6 = 307 -} ----- - -=== Architecture Improvements - -==== Context Threading in Codegen - -*Problem:* Local variables allocated in statements weren’t visible to -subsequent code. - -*Solution:* Changed gen_expr and gen_stmt signatures to return -`+(context * instr list)+` instead of just `+instr list+`, properly -threading context through all expression and statement evaluation. - -*Impact:* Enables proper local variable scoping in generated WASM. - -==== Function Local Variables - -*Problem:* WASM parameters are implicitly locals 0..n-1, but codegen -started local allocation at 0. - -*Solution:* - -[source,ocaml] ----- -let fn_ctx = { ctx with locals = []; next_local = 0 } in -let (ctx_with_params, _) = (* allocate params at 0..n-1 *) -let param_count = List.length params in -(* additional locals start at param_count *) -let local_count = ctx_final.next_local - param_count in ----- - -*Impact:* Generated WASM now has correct local indices. - -=== Testing - -==== Interpreter Tests - -[source,bash] ----- -affinescript eval tests/effects/basic_effect.affine -affinescript eval tests/borrow/valid_move.affine -affinescript eval tests/borrow/use_after_move.affine # Should fail ----- - -==== Codegen Tests - -[source,bash] ----- -affinescript compile tests/codegen/simple_arithmetic.affine -o test.wasm -file test.wasm # Should show: WebAssembly (wasm) binary module -node run_wasm.js # Should output: 42 ----- - -==== Standard Library Tests - -[source,affinescript] ----- -use Core::{min, max}; -use Math::pow; - -fn test_stdlib() -> Int { - return min(10, max(5, pow(2, 3))); // min(10, max(5, 8)) = min(10, 8) = 8 -} ----- - -=== Known Limitations - -==== Effect Handlers - -* Only work at top level of handle expression -* No delimited continuations -* Resume doesn’t continue suspended computations -* Multiple sequential effects don’t work correctly - -==== WASM Codegen - -* No function calls yet -* No closures or function pointers -* No heap memory management -* No complex data structures -* No pattern matching translation -* No effect handler codegen - -==== Standard Library - -* String operations missing -* Array/List operations missing -* I/O requires FFI (not implemented) -* Transcendental math requires FFI -* Module imports not fully working - -==== Module System - -* Parser ready but resolution incomplete -* Imports don’t work yet -* No standard library path resolution -* No module caching - -=== Future Work - -==== Immediate Next Steps - -[arabic] -. *Complete Module Resolution:* -* Implement cross-module name resolution -* Add module path to file path mapping -* Configure standard library location -. *Function Calls in WASM:* -* Direct function calls -* Function types in type section -* Call instruction generation -. *Heap Memory Management:* -* Linear memory allocator -* Garbage collection or manual management -* Complex data structure layouts - -==== Medium Term - -[arabic] -. *Delimited Continuations:* -* Full effect handler resume support -* CPS transformation or stack-based approach -. *Pattern Matching in WASM:* -* Translate match expressions -* Efficient dispatch for variants -. *Standard Library Expansion:* -* String module -* Array/List module -* I/O module with FFI - -==== Long Term - -[arabic] -. *Dependent Types:* -* Dependent type checking -* Refinement types -* Proof obligations -. *Row Polymorphism:* -* Extensible records -* Row type inference -. *Effect Inference:* -* Automatic effect tracking -* Effect polymorphism -. *IDE Tooling:* -* LSP server -* Syntax highlighting -* Auto-completion - -=== Performance Metrics - -==== Compilation Speed - -* Simple programs (<100 lines): < 1 second -* Parser generates 63 shift/reduce conflicts (acceptable) -* WASM encoding is fast (< 100ms for small programs) - -==== Generated Code Size - -* Minimal WASM overhead -* Simple arithmetic: 54 bytes total -* No runtime yet, so very compact - -==== Interpreter Performance - -* Tree-walking interpreter (not optimized) -* Suitable for development and testing -* Production code should compile to WASM - -=== Conclusion - -This was an extraordinarily productive session that accomplished all -four requested priorities: - -✅ *Priority #1: Interpreter* - Fully functional with effects ✅ -*Priority #2: WASM Codegen* - End-to-end working, produces valid .wasm -files ✅ *Priority #3: Standard Library* - 4 modules with comprehensive -documentation ⚠️ *Priority #4: Module System* - Foundation in place, -needs resolution phase - -*Key Achievement:* AffineScript now has a complete compilation pipeline -from source code to executable WebAssembly, verified to work correctly! - -*Project Status:* 70% complete, Alpha phase, all core features working - -*Next Session:* Complete module system, add function calls to WASM, -expand standard library diff --git a/affinescript/SPARK-experiment/README.adoc b/affinescript/SPARK-experiment/README.adoc deleted file mode 100644 index d92fe03..0000000 --- a/affinescript/SPARK-experiment/README.adoc +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -= AffineScript SPARK Experiment -:toc: - -== Purpose - -This directory is a sandbox for testing how a *small, explicit subset* of -AffineScript can be translated into Ada/SPARK for proof-heavy kernels. - -Goal: learn what can be verified in SPARK without destabilizing AffineScript's -mainline compiler and language roadmap. - -== Scope - -In scope: - -* Minimal transliteration experiments from AffineScript typed IR to SPARK-safe - Ada packages. -* Explicit state-machine extraction for protocol/lifecycle logic. -* GNATprove-oriented contracts (`Pre`, `Post`, invariants) on translated code. - -Out of scope: - -* Rewriting AffineScript itself into Ada/SPARK. -* Full-feature parity (effects, row polymorphism, dependent/refinement surface). -* Claims of production readiness. - -== First Practical Kernel - -The first candidate kernel is memory-pressure classification and notification -state transitions (warning/critical/recovered), because it is: - -* small, -* safety-relevant, -* easy to compare against Rust implementation behavior. - -== Experiment Plan - -1. Define a tiny AffineScript subset grammar for deterministic control flow. -2. Specify typed IR -> SPARK mapping rules (types, ownership states, effects as - explicit state transitions). -3. Generate one SPARK package (`Memory_Guardian_Kernel`) and prove: - no runtime errors, total state transition coverage, bounded arithmetic. -4. Cross-check output behavior against reference traces from Rust. - -== Exit Criteria - -* A documented subset mapping that compiles with GNAT. -* `gnatprove` passing for the selected kernel. -* A short "keep/drop/park" decision on whether broader SPARK bridging is worth - continuing for AffineScript. diff --git a/affinescript/TRAIT_SYSTEM_STATUS.adoc b/affinescript/TRAIT_SYSTEM_STATUS.adoc deleted file mode 100644 index 8becae5..0000000 --- a/affinescript/TRAIT_SYSTEM_STATUS.adoc +++ /dev/null @@ -1,425 +0,0 @@ -== AffineScript Trait System - Implementation Status - -=== ✅ Completed Features - -==== 1. Trait Registry & Validation - -* Trait definitions stored in registry with methods and associated types -* Implementation validation ensures all required methods are present -* Parameter count checking for method signatures -* ✅ *NEW:* Struct type definitions stored in type_defs table - -==== 2. Standard Library Traits - -* *Eq*: Equality comparison with `+eq(ref self, ref other) -> Bool+` -* *Ord*: Ordering with `+cmp(ref self, ref other)+` (requires Eq) -* *Hash*: Hashing with `+hash(ref self) -> Int+` -* *Display*: Display conversion with `+to_string(ref self)+` - -All stdlib traits have correct parameter signatures. - -==== 3. Method Resolution ✨ *ENHANCED* - -* `+find_method_for_type+`: Locates trait implementations for a given -type -* `+find_method+`: Retrieves specific method from an implementation -* ✅ *NEW:* Works with both nominal types AND struct literals! -* ✅ *NEW:* Proper self parameter handling (excluded from method call -type) - -==== 4. Type Checker Integration ✨ *GREATLY ENHANCED* - -* Method call pattern detection: `+receiver.method(args)+` -* Automatic trait method resolution as fallback for field access -* Function type construction from method signatures (excluding self) -* Type annotation support in let bindings -* ✅ *NEW:* Nominal-to-structural type expansion during unification -* ✅ *NEW:* Field access on struct literals with type annotations -* ✅ *NEW:* Trait method calls on struct literals! - -==== 5. Working Examples - -[source,affinescript] ----- -struct Point { x: Int, y: Int } - -trait Display { - fn show(ref self: Self) -> Int; -} - -impl Display for Point { - fn show(ref self: Point) -> Int { - return self.x; - } -} - -fn main() -> Int { - // ✅ Struct literal with type annotation - let p: Point = { x: 10, y: 20 }; - - // ✅ Field access works! - let x: Int = p.x; - - // ✅ Trait method call works! - return p.show(); -} ----- - -=== 🎉 MAJOR MILESTONE: Nominal/Structural Type Bridge Complete! - -The nominal/structural type mismatch has been *SOLVED*! - -*What was fixed:* 1. Added `+type_defs+` table to context storing struct -→ record type mappings 2. Struct definitions now register their expanded -record types 3. `+check_subsumption+` expands nominal types before -unification + -4. `+ExprField+` handler expands nominal types for field access 5. -Method type construction excludes self parameter (first param) - -*Result:* Trait methods now work on: - ✅ Function parameters with -nominal types - ✅ *Struct literals with type annotations* - ✅ *Any -expression that evaluates to a struct type* - -=== 📋 Remaining Work - -==== High Priority - -[arabic] -. [line-through]#*Nominal/Structural Type Bridge*# ✅ *COMPLETE!* -. *Trait Bounds Checking*: Validate `+where T: Trait+` constraints ⚙️ -*IN PROGRESS* -. [line-through]#*Codegen for Trait Methods*# ✅ *COMPLETE!* -* ✅ Monomorphization: Generates specialized functions per type -* ✅ Function mangling: `+TypeName_TraitName_methodName+` format -* ✅ Automatic dispatch: `+p.show()+` correctly calls -`+Point_Display_show(p)+` -* ✅ Call site tracking: Type checker records trait method calls for -codegen -* ✅ Full end-to-end: Trait methods compile to WASM and execute -correctly! -. [line-through]#*Better Error Messages*# ✅ *COMPLETE!* -* ✅ *Task 3A*: Type error messages with diff display and suggestions -* ✅ *Task 3C*: Trait-specific error messages, if-statement divergence -fix -* ✅ *Task 3B*: Comprehensive error reporting (color output, error -codes) -. *Effect System Implementation* ⚙️ *IN PROGRESS* -* ✅ Effect operations module (union, subtyping, normalization) -* ⚠️ Effect tracking in type checker (TODO - integration) -* ⚠️ Effect inference and propagation (TODO) -* ⚠️ Polymorphic effects (TODO) - -==== Medium Priority - -[arabic, start=6] -. *Supertrait Validation*: Ensure implementations satisfy supertrait -requirements -. *Associated Types*: Full support for associated type substitution - -==== Low Priority - -[arabic, start=7] -. *Orphan Rules*: Prevent conflicting implementations (coherence) -. *Trait Objects*: Dynamic dispatch support -. *Default Methods*: Support for methods with default implementations - -=== 🐛 Known Issues - -[line-through]#*If Statement Return Detection:*# ✅ *FIXED!* - -The type checker now correctly recognizes when an if-expression with -returns in both branches never produces a value (diverges). This fix -includes: - -* *`+expr_diverges+` function*: Detects when an expression always -returns -* *Improved if-expression synthesis*: Handles diverging branches -correctly -* *Enhanced block checking*: Recognizes diverging statements - -[source,affinescript] ----- -// ✅ Now works! -fn main() -> Int { - if condition { - return 1; - } else { - return 0; - }; // Type checker recognizes both branches return -} ----- - -=== 🎯 Next Steps - -The critical path for full trait support: - -[arabic] -. [line-through]#Fix nominal/structural mismatch# ✅ *DONE!* -. *Implement trait bounds checking* (enables generic constraints) + -. *Add codegen for trait methods* (enables WASM compilation) - -Once these remaining two are complete, traits will be fully functional -end-to-end! - -=== 📊 Test Coverage - -Passing tests: - ✅ Basic trait implementation - ✅ Multiple traits on -same type - ✅ Stdlib trait (Eq) implementation - ✅ Method calls on -function parameters - ✅ *Method calls on struct literals* ⭐ - ✅ -*Field access on struct literals* ⭐ - ✅ Multi-parameter methods - ✅ -Multiple implementations of same trait for different types - ✅ *WASM -compilation with trait methods* ⭐ - ✅ *Trait method dispatch in -codegen* ⭐ - ✅ *Improved error messages* ⭐ *NEW!* - Type mismatch -with suggestions - Expected function error - Field not found error - -Trait method not found error - -Known failures/limitations: - ⚠️ Generic functions with trait bounds -(partial - validation started but not complete) - [line-through]#❌ -If-statements as last expression# ✅ *FIXED!* - -=== 📝 Implementation Details - -==== Error Message System ✅ *COMPLETE!* - -===== Session 4A - Type Error Messages (Task 3A) - -*Implemented:* 1. *Human-Readable Type Pretty-Printer*: `+string_of_ty+` -function converts internal type representation to user-friendly strings -- `+Int+`, `+Bool+`, `+String+` instead of `+TCon "Int"+` - -`+fn(Int) -> Bool+` instead of `+TArrow(...)+` - `+{ x: Int, y: Int }+` -instead of internal row representation - -[arabic, start=2] -. *Type Diff Display*: `+show_type_diff+` function shows expected vs -actual types with context-specific suggestions -* Detects common mistakes (Int vs Bool, function vs non-function, etc.) -* Provides helpful hints based on error pattern -. *Enhanced Unification Errors*: `+format_unify_error+` provides -detailed messages for each unification failure -* `+TypeMismatch+`: Shows type diff with suggestions -* `+OccursCheck+`: Explains infinite type loops in recursive functions -* `+RowMismatch+`: Shows record field differences -* `+LabelNotFound+`: Highlights missing fields with suggestions -* `+EffectMismatch+`: Explains purity constraints -. *Complete Error Coverage*: `+format_type_error+` handles all error -types with helpful context -* `+ExpectedFunction+`: Suggests checking if expression is actually a -function -* `+ExpectedRecord+`: Shows record literal syntax -* `+ArityMismatch+`: Counts missing/extra arguments -* `+CannotInfer+`: Suggests adding type annotations -* `+TraitNotImplemented+`: Shows impl block template -* `+TraitMethodNotFound+`: Explains field vs method distinction - -*Example Output:* - -.... -/tmp/test_type_mismatch.affine:9:26: Type mismatch - Expected: Bool - Actual: Int - Cannot mix Int and Bool types. - Help: Use comparison operators (==, <, >) which return Bool, not arithmetic on Bool -.... - -===== Session 4B - Comprehensive Error Reporting (Task 3B) - -*Implemented:* 1. *ANSI Color Support* (`+lib/error_formatter.ml+`): - -`+colorize+` function for terminal color output - Auto-detection of -terminal color support (checks `+TERM+` environment variable) - Color -codes: Red (errors), Yellow (warnings), Green (help), Cyan (locations), -Magenta (error codes) - `+format_error+` with severity levels and error -codes - `+format_error_with_context+` for showing source lines with -carets - -[arabic, start=2] -. *Error Collection System* (`+lib/error_collector.ml+`): -* `+collected_error+` type with severity, code, location, message, and -help -* `+Error_collector.t+` for accumulating multiple errors -* `+add_error+` and `+add_warning+` functions -* `+format_all+` to display all collected errors -* `+format_summary+` for error/warning counts with color -. *Error Codes*: -* E0001: Type mismatch -* E0002: Infinite type (occurs check) -* L0001: Lexer errors -* P0001: Parser errors -* (More codes can be added as needed) - -*Architecture:* - `+Error_formatter+`: Low-level formatting with ANSI -colors - `+Error_collector+`: Accumulates errors during compilation -phases - Designed for future enhancement: multiple errors per phase, -error recovery in parser - -*Files Created:* - `+lib/error_formatter.ml+` + `+.mli+`: Color -formatting utilities - `+lib/error_collector.ml+`: Error accumulation -and batch reporting - -*Files Modified:* - `+lib/dune+`: Added error_formatter and -error_collector modules - `+lib/typecheck.ml+`: Uses error codes and -colored output (future integration) - -==== Effect System (Session 5 & 6 - Task 2) ⚙️ *IN PROGRESS* - -*✅ COMPLETED:* 1. *Effect Operations Module* (`+lib/effect.ml+`): - -`+union_eff+`: Combine two effects (flattens, deduplicates) - -`+union_effs+`: Union a list of effects - `+is_pure+`: Check if effect -is EPure - `+eff_subsumes+`: Effect subtyping (Pure <: any effect) - -`+eff_subset+`: Check if e1 ⊆ e2 - `+normalize_eff+`: Flatten and -deduplicate effect unions - `+string_of_eff+`: Human-readable effect -display - -[arabic, start=2] -. *Effect Tracking in Type Checker*: -* ✅ `+synth+` returns `+(ty * eff) result+` -* ✅ `+check+` returns `+eff result+` -* ✅ Function applications union func_eff + arg_eff + call_eff (line -1393) -* ✅ Binary/unary operations propagate operand effects (line 1678, 1702) -* ✅ Let bindings union RHS and body effects (line 1068) -* ✅ If/match expressions union all branch effects (lines 1088-1100, -1119) -* ✅ Blocks union all statement effects (line 1596) -* ✅ Effect unification at lambda checking (line 1442) -. *Effect Inference & Generalization*: -* ✅ Lambda bodies infer effects (line 1044: -`+TArrow (param_ty, acc, body_eff)+`) -* ✅ Effect variables collected during generalization (line 547) -* ✅ Effect variables included in schemes (line 548) -* ✅ Effect variables instantiated with fresh vars (line 561-562) -* ✅ Effect substitution in instantiate (line 578) - -*✅ NEWLY COMPLETED (Session 6):* 4. *Trait Method Effect Annotations*: -- ✅ Extract effects from method declarations (typecheck.ml:1012, 1239) -- ✅ Use `+method_decl.fd_eff+` instead of hardcoded `+EPure+` - ✅ -Trait methods now properly typed with their declared effects - -*✅ NEWLY COMPLETED (Session 6 - Effect Safety):* 5. *Effect Subsumption -Checking*: - ✅ Added subsumption check in `+synth_app+` -(typecheck.ml:1408-1414, 1419-1425) - ✅ Checks -`+call_eff ⊑ current_effect+` before allowing function calls - ✅ Made -`+current_effect+` field mutable in context (line 341) - ✅ Set effect -context when checking lambda bodies (line 1467) - ✅ Set effect context -when checking function bodies (line 2020) - ✅ Extract declared effects -from `+fd.fd_eff+` (line 1985-1989) - ✅ Error messages include effect -names and context - -*⚠️ REMAINING WORK:* 1. *Effect Syntax & Parser*: - Effect annotation -syntax (`+fn(...) -> T / E+`) not in parser yet - Effect handlers parsed -but not in codegen - Need to add effect syntax to function signatures - -[arabic, start=2] -. *Standard Library Effects*: -* Declare builtin effects: IO, State, Exn -* Mark builtin operations with appropriate effects -* Add effect annotations to standard library functions -* Example: `+print: fn(String) -> Unit / IO+` -. *User-Defined Effects* (future): -* `+effect+` declarations in syntax -* Effect handlers and resumption -* Algebraic effects support - -*Files Created:* - `+lib/effect.ml+` + `+.mli+`: Effect system -operations - -*Files Modified (Session 6):* - `+lib/typecheck.ml+`: - Added effect -module imports with warning suppression (lines 14-18) - Uses local -`+union_eff+` implementation (line 1848) - consider replacing with -Effect.union_effs later - Effect tracking fully integrated throughout -synth/check - ✅ Fixed trait method effect extraction: * ExprApp trait -method calls (lines 1010-1017): Extract from `+method_decl.fd_eff+` * -ExprField trait method access (lines 1235-1243): Extract from -`+method_decl.fd_eff+` - Verified: `+ast_to_eff+` function already -exists (line 789) for converting AST effects - -*Current Status:* The effect system core is *substantially complete* -(95%). Effects: - ✅ Flow correctly through the type checker - ✅ Are -unified at lambda checking and subsumption - ✅ Are generalized in -let-bindings - ✅ Are instantiated with fresh variables - ✅ Track -through all expression forms (app, binop, if, match, blocks) - ✅ Are -properly extracted from trait method declarations - -*Remaining Work (5%):* 1. Effect subsumption validation at call sites -(prevent pure → impure calls) 2. Standard library effect declarations -(IO, State, Exn) 3. Builtin operation effect annotations - -*Next Immediate Steps:* 1. Implement effect subsumption checking in -function application 2. Add test cases for effect violation detection 3. -Declare standard library effects (IO, State, Exn) 4. Mark builtin -operations with appropriate effects - -*Testing:* - ✅ Basic trait method calls compile correctly -(/tmp/test_effects.affine) - ⚠️ Need tests for effect violations (pure -calling impure) - ⚠️ Need tests with explicit effect annotations - -==== Type Expansion System - -* `+ctx.type_defs+`: Hashtable mapping type names to their expanded -types -* Populated during `+TopType+` checking for structs -* Used in `+check_subsumption+` and `+ExprField+` to expand nominal -types -* Enables seamless interop between nominal and structural typing - -==== Method Type Construction - -* Trait methods include `+self+` as first parameter in definition -* Type checker *excludes* self when building function type for method -calls -* `+receiver.method(args)+` automatically binds self to receiver -* Remaining parameters become the function’s parameter types - -==== Codegen Monomorphization (Session 3) - -* *Function Mangling*: Trait methods compiled to -`+TypeName_TraitName_methodName+` -* *TopImpl Handler*: Generates monomorphized functions for each impl -block -* *Trait Registry in Codegen*: Passed from type checker to codegen for -method resolution -* *Example*: `+impl Display for Point { fn show(...) }+` → -`+Point_Display_show(...)+` function - -*Implementation Flow:* 1. Type checker creates trait registry and -type_defs 2. `+check_program+` now returns `+context result+` (not -`+unit result+`) 3. Compiler pipeline passes `+trait_registry+` to -codegen 4. `+TopImpl+` extracts methods from `+ib_items+` and generates -mangled functions 5. Each trait method becomes a regular WASM function - -*What Works:* - ✅ Monomorphized functions are generated correctly - ✅ -Programs with trait impls compile to WASM - ✅ Type checking correctly -resolves trait methods - -*What’s Missing:* - ⚠️ Automatic method call dispatch (needs desugaring -pass or AST transformation) - ⚠️ For now, trait methods compile but -`+p.show()+` syntax doesn’t call them yet - -==== Files Modified (Session 3 - Codegen) ✅ *COMPLETE!* - -* `+lib/typecheck.ml+`: -** Modified `+check_program+` to return `+context result+` -** Added `+trait_method_calls+` hashtable to track call sites -** Records `+(type_name, trait_name, method_name)+` for each trait -method call -** Uses `+method_name.span+` as key for unique identification -* `+lib/codegen.ml+`: -** Added `+trait_registry+` and `+trait_method_calls+` to context -** Implemented `+TopImpl+` monomorphization (generates mangled -functions) -** Enhanced `+ExprApp+` handler to detect and dispatch trait method -calls -** Pattern matches on `+ExprField(receiver, method)+` and checks -hashtable -** Direct call generation to monomorphized functions -* `+bin/main.ml+`: -** Updated compilation pipeline to pass trait registry and call sites to -codegen -** Threads type checking context through to codegen phase - -*End-to-End Flow:* 1. Type checker identifies trait method calls during -synthesis 2. Records call site info: -`+(type_name, trait_name, method_name)+` keyed by span 3. Codegen -TopImpl generates monomorphized functions with mangled names 4. Codegen -ExprApp checks if call site is a trait method 5. If yes, generates -direct Call instruction to mangled function 6. Result: `+p.show()+` → -`+Call Point_Display_show+` with `+p+` as first argument diff --git a/affinescript/ads.txt b/affinescript/ads.txt deleted file mode 100644 index 6895efa..0000000 --- a/affinescript/ads.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Authorized Digital Sellers for affinescript.dev -# https://iabtechlab.com/ads-txt/ - -# Currently, this site does NOT run ads. -# Any ads claiming to be from this domain are UNAUTHORIZED. - -# To report unauthorized ad placement: -# Email: abuse@affinescript.dev - -# If we add advertising in the future, authorized sellers will be listed here. - -# Last Updated: 2026-01-31 diff --git a/affinescript/affinescript.opam b/affinescript/affinescript.opam deleted file mode 100644 index 96db116..0000000 --- a/affinescript/affinescript.opam +++ /dev/null @@ -1,52 +0,0 @@ -# This file is generated by dune, edit dune-project instead -opam-version: "2.0" -version: "0.1.0" -synopsis: - "A programming language with affine types, dependent types, row polymorphism, and extensible effects" -description: - "AffineScript combines Rust-style ownership (affine types), compile-time size verification (dependent types), extensible records (row polymorphism), and trackable side effects (extensible effects). It compiles to WebAssembly for efficient, portable execution." -maintainer: ["hyperpolymath"] -authors: ["hyperpolymath"] -license: "MIT OR AGPL-3.0-or-later" -tags: [ - "programming-language" - "compiler" - "webassembly" - "affine-types" - "dependent-types" - "row-polymorphism" - "effects" - "ocaml" -] -homepage: "https://github.com/hyperpolymath/affinescript" -doc: "https://github.com/hyperpolymath/affinescript" -bug-reports: "https://github.com/hyperpolymath/affinescript/issues" -depends: [ - "ocaml" {>= "5.1"} - "dune" {>= "3.17" & >= "3.14"} - "menhir" {>= "20231231"} - "sedlex" {>= "3.2"} - "ppx_deriving" {>= "5.2"} - "ppx_sexp_conv" {>= "0.16"} - "sexplib0" {>= "0.16"} - "fmt" {>= "0.9"} - "cmdliner" {>= "1.2"} - "yojson" {>= "2.1"} - "alcotest" {>= "1.7" & with-test} - "odoc" {>= "2.4" & with-doc} -] -build: [ - ["dune" "subst"] {dev} - [ - "dune" - "build" - "-p" - name - "-j" - jobs - "@install" - "@runtest" {with-test} - "@doc" {with-doc} - ] -] -dev-repo: "git+https://github.com/hyperpolymath/affinescript.git" diff --git a/affinescript/affinescript.runbook.a2ml b/affinescript/affinescript.runbook.a2ml deleted file mode 100644 index 8d440d4..0000000 --- a/affinescript/affinescript.runbook.a2ml +++ /dev/null @@ -1,36 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# affinescript.launcher.a2ml — Live launcher config for AffineScript compiler - -[metadata] -project = "affinescript" -version = "0.1.0" -last-updated = "2026-04-11" - -[launcher] -kind = "cli-tool" -entry = "dune exec affinescript" -build-command = "dune build" -test-command = "dune runtest" - -[subcommands] -lex = "affinescript lex " -parse = "affinescript parse " -check = "affinescript check " -eval = "affinescript eval " -compile = "affinescript compile -o " -fmt = "affinescript fmt " -lint = "affinescript lint " -hover = "affinescript hover " -goto-def = "affinescript goto-def " -complete = "affinescript complete " -tea-bridge = "affinescript tea-bridge -o " - -[paths] -source = "lib/" -tests = "test/" -examples = "examples/" -wasm-output = "../../idaptik/public/assets/wasm/" - -[notes] -tea-bridge-target = "idaptik/public/assets/wasm/titlescreen.wasm" -tea-bridge-note = "Run `just regen-idaptik-wasm` to regenerate the IDApTIK bridge binary" diff --git a/affinescript/app-ads.txt b/affinescript/app-ads.txt deleted file mode 100644 index 7fb1249..0000000 --- a/affinescript/app-ads.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Authorized Digital Sellers for Mobile Apps from affinescript.dev -# https://iabtechlab.com/app-ads-txt/ - -# Currently, no mobile apps are monetized with ads. -# Any ads in apps claiming to be from this domain are UNAUTHORIZED. - -# Last Updated: 2026-01-31 diff --git a/affinescript/bench/dune b/affinescript/bench/dune deleted file mode 100644 index 63a759e..0000000 --- a/affinescript/bench/dune +++ /dev/null @@ -1,11 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 - -; Minimal benchmark alias so blitz has an executable @bench target. -; This is intentionally lightweight and deterministic. - -(rule - (alias bench) - (action - (progn - (echo "bench-smoke: parsing examples/hello.affine") - (run %{bin:affinescript} parse %{project_root}/examples/hello.affine)))) diff --git a/affinescript/conformance/README.adoc b/affinescript/conformance/README.adoc deleted file mode 100644 index c7c7738..0000000 --- a/affinescript/conformance/README.adoc +++ /dev/null @@ -1,65 +0,0 @@ -== AffineScript Conformance Test Suite - -This directory contains the conformance test corpus for AffineScript. -The tests here are *binding* - changes to expected outputs require -explicit justification. - -=== Structure - -.... -conformance/ -├── valid/ # Programs that must parse successfully (exit 0) -│ ├── *.affine # Source files -│ └── *.expected # Expected parser output -├── invalid/ # Programs that must fail with diagnostics (exit non-zero) -│ ├── *.affine # Source files -│ └── *.expected # Expected error diagnostics -└── README.md # This file -.... - -=== Test Methodology - -==== Valid Programs - -* Must parse without error -* CLI command: `+affinescript parse +` -* Expected exit code: 0 -* Output must match `+.expected+` file exactly - -==== Invalid Programs - -* Must produce a parse/lex error -* CLI command: `+affinescript parse +` or -`+affinescript lex +` -* Expected exit code: non-zero (1 for parse errors) -* Error diagnostic must match `+.expected+` file pattern - -=== Running Tests - -[source,bash] ----- -# Run all conformance tests -just conformance - -# Or directly with dune -dune runtest conformance ----- - -=== Adding New Tests - -[arabic] -. Add `+.affine+` source file to `+valid/+` or `+invalid/+` -. Run the compiler to generate expected output -. Review and save as `+.expected+` file -. Commit both files together - -=== Versioning - -* Format: `+conformance-vN.M+` -* Breaking changes (modified .expected): increment major version -* New tests only: increment minor version - -=== F0 Requirements - -Per the scope arrest directive: - Minimum 10 valid programs - Minimum 10 -invalid programs - Stable exit-code contract - Deterministic diagnostics diff --git a/affinescript/conformance/invalid/001_unclosed_brace.affine b/affinescript/conformance/invalid/001_unclosed_brace.affine deleted file mode 100644 index e6a6fde..0000000 --- a/affinescript/conformance/invalid/001_unclosed_brace.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: unclosed brace (parse error) - -fn broken() -> () { - let x = 42; - // Missing closing brace diff --git a/affinescript/conformance/invalid/001_unclosed_brace.expected b/affinescript/conformance/invalid/001_unclosed_brace.expected deleted file mode 100644 index 464aa21..0000000 --- a/affinescript/conformance/invalid/001_unclosed_brace.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for unclosed brace -# Exit code: 1 -# Error pattern: unexpected end of file or missing '}' -error[E0002]: Parse error: unexpected end of input - --> conformance/invalid/001_unclosed_brace.as:5:1 - | - 5 | // Missing closing brace - | ^ expected '}' diff --git a/affinescript/conformance/invalid/002_unclosed_string.affine b/affinescript/conformance/invalid/002_unclosed_string.affine deleted file mode 100644 index 5d56ff0..0000000 --- a/affinescript/conformance/invalid/002_unclosed_string.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: unclosed string literal (lex error) - -fn test() -> () { - let s = "hello world -} diff --git a/affinescript/conformance/invalid/002_unclosed_string.expected b/affinescript/conformance/invalid/002_unclosed_string.expected deleted file mode 100644 index 2744779..0000000 --- a/affinescript/conformance/invalid/002_unclosed_string.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: lex error for unclosed string -# Exit code: 1 -# Error pattern: unterminated string literal -error[E0001]: Lexical error: unterminated string literal - --> conformance/invalid/002_unclosed_string.as:4:11 - | - 4 | let s = "hello world - | ^ string literal not closed diff --git a/affinescript/conformance/invalid/003_bad_number.affine b/affinescript/conformance/invalid/003_bad_number.affine deleted file mode 100644 index 1f978ba..0000000 --- a/affinescript/conformance/invalid/003_bad_number.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: invalid number literal (lex error) - -fn test() -> () { - let x = 0xGHI; -} diff --git a/affinescript/conformance/invalid/003_bad_number.expected b/affinescript/conformance/invalid/003_bad_number.expected deleted file mode 100644 index edd60c1..0000000 --- a/affinescript/conformance/invalid/003_bad_number.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: lex error for invalid hex literal -# Exit code: 1 -# Error pattern: invalid hexadecimal literal -error[E0001]: Lexical error: invalid hexadecimal literal - --> conformance/invalid/003_bad_number.as:4:11 - | - 4 | let x = 0xGHI; - | ^^^^^ invalid hex digit 'G' diff --git a/affinescript/conformance/invalid/004_unexpected_token.affine b/affinescript/conformance/invalid/004_unexpected_token.affine deleted file mode 100644 index 6bfb257..0000000 --- a/affinescript/conformance/invalid/004_unexpected_token.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: unexpected token (parse error) - -fn test() -> () { - let = 42; -} diff --git a/affinescript/conformance/invalid/004_unexpected_token.expected b/affinescript/conformance/invalid/004_unexpected_token.expected deleted file mode 100644 index 4ad0d7a..0000000 --- a/affinescript/conformance/invalid/004_unexpected_token.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for missing identifier after let -# Exit code: 1 -# Error pattern: expected identifier -error[E0002]: Parse error: expected identifier - --> conformance/invalid/004_unexpected_token.as:4:7 - | - 4 | let = 42; - | ^ expected identifier, found '=' diff --git a/affinescript/conformance/invalid/005_missing_arrow.affine b/affinescript/conformance/invalid/005_missing_arrow.affine deleted file mode 100644 index 1acb8ec..0000000 --- a/affinescript/conformance/invalid/005_missing_arrow.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: missing arrow in function type (parse error) - -fn test() Int { - 42 -} diff --git a/affinescript/conformance/invalid/005_missing_arrow.expected b/affinescript/conformance/invalid/005_missing_arrow.expected deleted file mode 100644 index a2b4ad2..0000000 --- a/affinescript/conformance/invalid/005_missing_arrow.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for missing '->' in function signature -# Exit code: 1 -# Error pattern: expected '->' -error[E0002]: Parse error: expected '->' - --> conformance/invalid/005_missing_arrow.as:3:10 - | - 3 | fn test() Int { - | ^ expected '->', found identifier diff --git a/affinescript/conformance/invalid/006_bad_operator.affine b/affinescript/conformance/invalid/006_bad_operator.affine deleted file mode 100644 index 23d0dfb..0000000 --- a/affinescript/conformance/invalid/006_bad_operator.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: invalid operator (lex error) - -fn test() -> () { - let x = 1 @@ 2; -} diff --git a/affinescript/conformance/invalid/006_bad_operator.expected b/affinescript/conformance/invalid/006_bad_operator.expected deleted file mode 100644 index c220605..0000000 --- a/affinescript/conformance/invalid/006_bad_operator.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: lex error for invalid operator -# Exit code: 1 -# Error pattern: unknown operator or unexpected character -error[E0001]: Lexical error: unexpected character - --> conformance/invalid/006_bad_operator.as:4:15 - | - 4 | let x = 1 @@ 2; - | ^ unexpected '@' diff --git a/affinescript/conformance/invalid/007_incomplete_effect.affine b/affinescript/conformance/invalid/007_incomplete_effect.affine deleted file mode 100644 index a489039..0000000 --- a/affinescript/conformance/invalid/007_incomplete_effect.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Conformance test: incomplete effect definition (parse error) - -effect Broken { - fn missing_return_type(); diff --git a/affinescript/conformance/invalid/007_incomplete_effect.expected b/affinescript/conformance/invalid/007_incomplete_effect.expected deleted file mode 100644 index 4f7a8ed..0000000 --- a/affinescript/conformance/invalid/007_incomplete_effect.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for incomplete effect -# Exit code: 1 -# Error pattern: unexpected end of file or missing '}' -error[E0002]: Parse error: unexpected end of input - --> conformance/invalid/007_incomplete_effect.as:4:28 - | - 4 | fn missing_return_type(); - | ^ expected '}', found end of file diff --git a/affinescript/conformance/invalid/008_mismatched_parens.affine b/affinescript/conformance/invalid/008_mismatched_parens.affine deleted file mode 100644 index c7e8354..0000000 --- a/affinescript/conformance/invalid/008_mismatched_parens.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: mismatched parentheses (parse error) - -fn test() -> () { - let x = (1 + 2; -} diff --git a/affinescript/conformance/invalid/008_mismatched_parens.expected b/affinescript/conformance/invalid/008_mismatched_parens.expected deleted file mode 100644 index cff905a..0000000 --- a/affinescript/conformance/invalid/008_mismatched_parens.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for mismatched parentheses -# Exit code: 1 -# Error pattern: expected ')' -error[E0002]: Parse error: expected ')' - --> conformance/invalid/008_mismatched_parens.as:4:16 - | - 4 | let x = (1 + 2; - | ^ expected ')', found ';' diff --git a/affinescript/conformance/invalid/009_reserved_keyword.affine b/affinescript/conformance/invalid/009_reserved_keyword.affine deleted file mode 100644 index 865ea14..0000000 --- a/affinescript/conformance/invalid/009_reserved_keyword.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: reserved keyword as identifier (parse error) - -fn test() -> () { - let fn = 42; -} diff --git a/affinescript/conformance/invalid/009_reserved_keyword.expected b/affinescript/conformance/invalid/009_reserved_keyword.expected deleted file mode 100644 index b60c4a9..0000000 --- a/affinescript/conformance/invalid/009_reserved_keyword.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for using keyword as identifier -# Exit code: 1 -# Error pattern: expected identifier, found keyword -error[E0002]: Parse error: expected identifier - --> conformance/invalid/009_reserved_keyword.as:4:7 - | - 4 | let fn = 42; - | ^^ expected identifier, found keyword 'fn' diff --git a/affinescript/conformance/invalid/010_empty_match.affine b/affinescript/conformance/invalid/010_empty_match.affine deleted file mode 100644 index a5e6fe0..0000000 --- a/affinescript/conformance/invalid/010_empty_match.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Conformance test: empty match expression (parse error) - -fn test(x: Int) -> Int { - match x { - } -} diff --git a/affinescript/conformance/invalid/010_empty_match.expected b/affinescript/conformance/invalid/010_empty_match.expected deleted file mode 100644 index 5b2fb5e..0000000 --- a/affinescript/conformance/invalid/010_empty_match.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: parse error for empty match -# Exit code: 1 -# Error pattern: expected at least one match arm -error[E0002]: Parse error: expected pattern - --> conformance/invalid/010_empty_match.as:5:3 - | - 5 | } - | ^ match expression requires at least one arm diff --git a/affinescript/conformance/invalid/011_bad_escape.affine b/affinescript/conformance/invalid/011_bad_escape.affine deleted file mode 100644 index 8daece4..0000000 --- a/affinescript/conformance/invalid/011_bad_escape.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: invalid escape sequence (lex error) - -fn test() -> () { - let s = "bad \q escape"; -} diff --git a/affinescript/conformance/invalid/011_bad_escape.expected b/affinescript/conformance/invalid/011_bad_escape.expected deleted file mode 100644 index 4850bed..0000000 --- a/affinescript/conformance/invalid/011_bad_escape.expected +++ /dev/null @@ -1,8 +0,0 @@ -# Expected: lex error for invalid escape sequence -# Exit code: 1 -# Error pattern: invalid escape sequence -error[E0001]: Lexical error: invalid escape sequence '\q' - --> conformance/invalid/011_bad_escape.as:4:16 - | - 4 | let s = "bad \q escape"; - | ^^ unknown escape sequence diff --git a/affinescript/conformance/invalid/012_trailing_comma.affine b/affinescript/conformance/invalid/012_trailing_comma.affine deleted file mode 100644 index e7dd0c5..0000000 --- a/affinescript/conformance/invalid/012_trailing_comma.affine +++ /dev/null @@ -1,5 +0,0 @@ -// Conformance test: missing expression after comma (parse error) - -fn test(x: Int,) -> Int { - x -} diff --git a/affinescript/conformance/invalid/012_trailing_comma.expected b/affinescript/conformance/invalid/012_trailing_comma.expected deleted file mode 100644 index 0bc43cd..0000000 --- a/affinescript/conformance/invalid/012_trailing_comma.expected +++ /dev/null @@ -1,5 +0,0 @@ -# Expected: parse error or valid (depends on grammar rules) -# Exit code: 0 or 1 (trailing commas may be allowed) -# Note: This tests whether trailing commas are permitted in parameter lists -# If allowed, this should be moved to valid/ -# TODO: Verify actual compiler behavior diff --git a/affinescript/conformance/valid/001_empty.affine b/affinescript/conformance/valid/001_empty.affine deleted file mode 100644 index 7ecb5c9..0000000 --- a/affinescript/conformance/valid/001_empty.affine +++ /dev/null @@ -1,2 +0,0 @@ -// Conformance test: empty program -// Expected: parses successfully with empty AST diff --git a/affinescript/conformance/valid/001_empty.expected b/affinescript/conformance/valid/001_empty.expected deleted file mode 100644 index 379e42f..0000000 --- a/affinescript/conformance/valid/001_empty.expected +++ /dev/null @@ -1 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; prog_decls = [] } diff --git a/affinescript/conformance/valid/002_comments.affine b/affinescript/conformance/valid/002_comments.affine deleted file mode 100644 index 5b3dfe3..0000000 --- a/affinescript/conformance/valid/002_comments.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Conformance test: comments only -// Line comment - -/* Block comment */ - -/* - Multi-line - block comment -*/ - -// Nested: /* this is still a line comment */ diff --git a/affinescript/conformance/valid/002_comments.expected b/affinescript/conformance/valid/002_comments.expected deleted file mode 100644 index 379e42f..0000000 --- a/affinescript/conformance/valid/002_comments.expected +++ /dev/null @@ -1 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; prog_decls = [] } diff --git a/affinescript/conformance/valid/003_let_bindings.affine b/affinescript/conformance/valid/003_let_bindings.affine deleted file mode 100644 index 966294d..0000000 --- a/affinescript/conformance/valid/003_let_bindings.affine +++ /dev/null @@ -1,9 +0,0 @@ -// Conformance test: let bindings - -fn test() -> () { - let x = 42; - let y: Int = 10; - let z = x + y; - let mut counter = 0; - counter = counter + 1; -} diff --git a/affinescript/conformance/valid/003_let_bindings.expected b/affinescript/conformance/valid/003_let_bindings.expected deleted file mode 100644 index 21b048e..0000000 --- a/affinescript/conformance/valid/003_let_bindings.expected +++ /dev/null @@ -1,155 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 35 }; - end_pos = { Span.line = 3; col = 4; offset = 38 }; - file = "conformance/valid/003_let_bindings.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = - [Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 4; col = 3; offset = 55 }; - end_pos = { Span.line = 4; col = 7; offset = 59 }; - file = "conformance/valid/003_let_bindings.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (42, - { Span.start_pos = - { Span.line = 4; col = 9; offset = 61 }; - end_pos = { Span.line = 4; col = 11; offset = 63 }; - file = "conformance/valid/003_let_bindings.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 5; col = 3; offset = 69 }; - end_pos = { Span.line = 5; col = 7; offset = 73 }; - file = "conformance/valid/003_let_bindings.affine" } - }); - sl_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 5; col = 8; offset = 74 }; - end_pos = - { Span.line = 5; col = 10; offset = 76 }; - file = - "conformance/valid/003_let_bindings.affine" } - })); - sl_value = - (Ast.ExprLit - (Ast.LitInt (10, - { Span.start_pos = - { Span.line = 5; col = 14; offset = 80 }; - end_pos = { Span.line = 5; col = 16; offset = 82 }; - file = "conformance/valid/003_let_bindings.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "z"; - span = - { Span.start_pos = - { Span.line = 6; col = 3; offset = 88 }; - end_pos = { Span.line = 6; col = 7; offset = 92 }; - file = "conformance/valid/003_let_bindings.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 6; col = 9; offset = 94 }; - end_pos = { Span.line = 6; col = 11; offset = 96 }; - file = "conformance/valid/003_let_bindings.affine" - } - }), - Ast.OpAdd, - (Ast.ExprVar - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 6; col = 13; offset = 98 }; - end_pos = - { Span.line = 6; col = 15; offset = 100 }; - file = "conformance/valid/003_let_bindings.affine" - } - }) - ))}; - Ast.StmtLet {sl_mut = true; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "counter"; - span = - { Span.start_pos = - { Span.line = 7; col = 7; offset = 109 }; - end_pos = { Span.line = 7; col = 11; offset = 113 }; - file = "conformance/valid/003_let_bindings.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (0, - { Span.start_pos = - { Span.line = 7; col = 19; offset = 121 }; - end_pos = { Span.line = 7; col = 21; offset = 123 }; - file = "conformance/valid/003_let_bindings.affine" } - )))}; - (Ast.StmtAssign ( - (Ast.ExprVar - { Ast.name = "counter"; - span = - { Span.start_pos = - { Span.line = 7; col = 22; offset = 124 }; - end_pos = { Span.line = 8; col = 3; offset = 128 }; - file = "conformance/valid/003_let_bindings.affine" } - }), - Ast.AssignEq, - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "counter"; - span = - { Span.start_pos = - { Span.line = 8; col = 11; offset = 136 }; - end_pos = - { Span.line = 8; col = 13; offset = 138 }; - file = - "conformance/valid/003_let_bindings.affine" } - }), - Ast.OpAdd, - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 8; col = 21; offset = 146 }; - end_pos = - { Span.line = 8; col = 23; offset = 148 }; - file = - "conformance/valid/003_let_bindings.affine" } - ))) - )) - )) - ]; - blk_expr = None }) - }) - ] - } diff --git a/affinescript/conformance/valid/004_literals.affine b/affinescript/conformance/valid/004_literals.affine deleted file mode 100644 index 2f0df4a..0000000 --- a/affinescript/conformance/valid/004_literals.affine +++ /dev/null @@ -1,26 +0,0 @@ -// Conformance test: literal values - -fn test_literals() -> () { - // Integers - let dec = 42; - let neg = -10; - let hex = 0xFF; - let bin = 0b1010; - let oct = 0o777; - - // Floats - let pi = 3.14159; - let neg_float = -2.5; - let sci = 1.0e10; - - // Booleans - let t = true; - let f = false; - - // Strings - let s = "hello world"; - let escaped = "line\nbreak\ttab"; - - // Unit - let u = (); -} diff --git a/affinescript/conformance/valid/004_literals.expected b/affinescript/conformance/valid/004_literals.expected deleted file mode 100644 index 922ffa7..0000000 --- a/affinescript/conformance/valid/004_literals.expected +++ /dev/null @@ -1,267 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_literals"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 37 }; - end_pos = { Span.line = 3; col = 4; offset = 40 }; - file = "conformance/valid/004_literals.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = - [Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "dec"; - span = - { Span.start_pos = - { Span.line = 5; col = 3; offset = 80 }; - end_pos = { Span.line = 5; col = 7; offset = 84 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (42, - { Span.start_pos = - { Span.line = 5; col = 11; offset = 88 }; - end_pos = { Span.line = 5; col = 13; offset = 90 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "neg"; - span = - { Span.start_pos = - { Span.line = 6; col = 3; offset = 96 }; - end_pos = { Span.line = 6; col = 7; offset = 100 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (-10, - { Span.start_pos = - { Span.line = 6; col = 11; offset = 104 }; - end_pos = { Span.line = 6; col = 13; offset = 106 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "hex"; - span = - { Span.start_pos = - { Span.line = 7; col = 3; offset = 113 }; - end_pos = { Span.line = 7; col = 7; offset = 117 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (255, - { Span.start_pos = - { Span.line = 7; col = 11; offset = 121 }; - end_pos = { Span.line = 7; col = 13; offset = 123 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "bin"; - span = - { Span.start_pos = - { Span.line = 8; col = 3; offset = 131 }; - end_pos = { Span.line = 8; col = 7; offset = 135 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (10, - { Span.start_pos = - { Span.line = 8; col = 11; offset = 139 }; - end_pos = { Span.line = 8; col = 13; offset = 141 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "oct"; - span = - { Span.start_pos = - { Span.line = 9; col = 3; offset = 151 }; - end_pos = { Span.line = 9; col = 7; offset = 155 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (511, - { Span.start_pos = - { Span.line = 9; col = 11; offset = 159 }; - end_pos = { Span.line = 9; col = 13; offset = 161 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "pi"; - span = - { Span.start_pos = - { Span.line = 12; col = 3; offset = 183 }; - end_pos = { Span.line = 12; col = 7; offset = 187 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitFloat (3.14159, - { Span.start_pos = - { Span.line = 12; col = 10; offset = 190 }; - end_pos = { Span.line = 12; col = 12; offset = 192 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "neg_float"; - span = - { Span.start_pos = - { Span.line = 13; col = 3; offset = 203 }; - end_pos = { Span.line = 13; col = 7; offset = 207 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitFloat (-2.5, - { Span.start_pos = - { Span.line = 13; col = 17; offset = 217 }; - end_pos = { Span.line = 13; col = 19; offset = 219 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "sci"; - span = - { Span.start_pos = - { Span.line = 14; col = 3; offset = 227 }; - end_pos = { Span.line = 14; col = 7; offset = 231 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitFloat (10000000000., - { Span.start_pos = - { Span.line = 14; col = 11; offset = 235 }; - end_pos = { Span.line = 14; col = 13; offset = 237 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "t"; - span = - { Span.start_pos = - { Span.line = 17; col = 3; offset = 262 }; - end_pos = { Span.line = 17; col = 7; offset = 266 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitBool (true, - { Span.start_pos = - { Span.line = 17; col = 9; offset = 268 }; - end_pos = { Span.line = 17; col = 11; offset = 270 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 18; col = 3; offset = 278 }; - end_pos = { Span.line = 18; col = 7; offset = 282 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitBool (false, - { Span.start_pos = - { Span.line = 18; col = 9; offset = 284 }; - end_pos = { Span.line = 18; col = 11; offset = 286 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "s"; - span = - { Span.start_pos = - { Span.line = 21; col = 3; offset = 309 }; - end_pos = { Span.line = 21; col = 7; offset = 313 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitString ("hello world", - { Span.start_pos = - { Span.line = 21; col = 9; offset = 315 }; - end_pos = { Span.line = 21; col = 23; offset = 329 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "escaped"; - span = - { Span.start_pos = - { Span.line = 22; col = 3; offset = 334 }; - end_pos = { Span.line = 22; col = 7; offset = 338 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitString ("line\nbreak\ttab", - { Span.start_pos = - { Span.line = 22; col = 15; offset = 346 }; - end_pos = { Span.line = 22; col = 34; offset = 365 }; - file = "conformance/valid/004_literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "u"; - span = - { Span.start_pos = - { Span.line = 25; col = 3; offset = 381 }; - end_pos = { Span.line = 25; col = 7; offset = 385 }; - file = "conformance/valid/004_literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 25; col = 9; offset = 387 }; - end_pos = { Span.line = 25; col = 12; offset = 390 }; - file = "conformance/valid/004_literals.affine" }))} - ]; - blk_expr = None }) - }) - ] - } diff --git a/affinescript/conformance/valid/005_functions.affine b/affinescript/conformance/valid/005_functions.affine deleted file mode 100644 index c15ff69..0000000 --- a/affinescript/conformance/valid/005_functions.affine +++ /dev/null @@ -1,21 +0,0 @@ -// Conformance test: function declarations - -fn simple() -> () { - () -} - -fn with_param(x: Int) -> Int { - x -} - -fn two_params(x: Int, y: Int) -> Int { - x + y -} - -fn generic[T](x: T) -> T { - x -} - -fn total_func(n: Nat) -> Nat { - n -} diff --git a/affinescript/conformance/valid/005_functions.expected b/affinescript/conformance/valid/005_functions.expected deleted file mode 100644 index 436d87d..0000000 --- a/affinescript/conformance/valid/005_functions.expected +++ /dev/null @@ -1,287 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "simple"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 44 }; - end_pos = { Span.line = 3; col = 4; offset = 47 }; - file = "conformance/valid/005_functions.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 3; col = 19; offset = 62 }; - end_pos = { Span.line = 4; col = 4; offset = 67 }; - file = "conformance/valid/005_functions.affine" }))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "with_param"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 72 }; - end_pos = { Span.line = 7; col = 4; offset = 75 }; - file = "conformance/valid/005_functions.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 7; col = 14; offset = 85 }; - end_pos = { Span.line = 7; col = 15; offset = 86 }; - file = "conformance/valid/005_functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = { Span.line = 7; col = 16; offset = 87 }; - end_pos = { Span.line = 7; col = 18; offset = 89 }; - file = "conformance/valid/005_functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 7; col = 23; offset = 94 }; - end_pos = { Span.line = 7; col = 26; offset = 97 }; - file = "conformance/valid/005_functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 7; col = 30; offset = 101 }; - end_pos = { Span.line = 8; col = 3; offset = 105 }; - file = "conformance/valid/005_functions.affine" } - })) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "two_params"; - span = - { Span.start_pos = { Span.line = 11; col = 1; offset = 110 }; - end_pos = { Span.line = 11; col = 4; offset = 113 }; - file = "conformance/valid/005_functions.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 11; col = 14; offset = 123 }; - end_pos = { Span.line = 11; col = 15; offset = 124 }; - file = "conformance/valid/005_functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 16; offset = 125 }; - end_pos = { Span.line = 11; col = 18; offset = 127 }; - file = "conformance/valid/005_functions.affine" } - }) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "y"; - span = - { Span.start_pos = { Span.line = 11; col = 21; offset = 130 }; - end_pos = { Span.line = 11; col = 23; offset = 132 }; - file = "conformance/valid/005_functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 24; offset = 133 }; - end_pos = { Span.line = 11; col = 26; offset = 135 }; - file = "conformance/valid/005_functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 31; offset = 140 }; - end_pos = { Span.line = 11; col = 34; offset = 143 }; - file = "conformance/valid/005_functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 11; col = 38; offset = 147 }; - end_pos = - { Span.line = 12; col = 3; offset = 151 }; - file = "conformance/valid/005_functions.affine" - } - }), - Ast.OpAdd, - (Ast.ExprVar - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 12; col = 5; offset = 153 }; - end_pos = - { Span.line = 12; col = 7; offset = 155 }; - file = "conformance/valid/005_functions.affine" - } - }) - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "generic"; - span = - { Span.start_pos = { Span.line = 15; col = 1; offset = 160 }; - end_pos = { Span.line = 15; col = 4; offset = 163 }; - file = "conformance/valid/005_functions.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 15; col = 11; offset = 170 }; - end_pos = { Span.line = 15; col = 12; offset = 171 }; - file = "conformance/valid/005_functions.affine" } - }; - tp_kind = None } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 15; col = 14; offset = 173 }; - end_pos = { Span.line = 15; col = 15; offset = 174 }; - file = "conformance/valid/005_functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 15; col = 16; offset = 175 }; - end_pos = { Span.line = 15; col = 18; offset = 177 }; - file = "conformance/valid/005_functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 15; col = 21; offset = 180 }; - end_pos = { Span.line = 15; col = 24; offset = 183 }; - file = "conformance/valid/005_functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 15; col = 26; offset = 185 }; - end_pos = - { Span.line = 16; col = 3; offset = 189 }; - file = "conformance/valid/005_functions.affine" } - })) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "total_func"; - span = - { Span.start_pos = { Span.line = 19; col = 1; offset = 194 }; - end_pos = { Span.line = 19; col = 4; offset = 197 }; - file = "conformance/valid/005_functions.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "n"; - span = - { Span.start_pos = { Span.line = 19; col = 14; offset = 207 }; - end_pos = { Span.line = 19; col = 15; offset = 208 }; - file = "conformance/valid/005_functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Nat"; - span = - { Span.start_pos = - { Span.line = 19; col = 16; offset = 209 }; - end_pos = { Span.line = 19; col = 18; offset = 211 }; - file = "conformance/valid/005_functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Nat"; - span = - { Span.start_pos = - { Span.line = 19; col = 23; offset = 216 }; - end_pos = { Span.line = 19; col = 26; offset = 219 }; - file = "conformance/valid/005_functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprVar - { Ast.name = "n"; - span = - { Span.start_pos = - { Span.line = 19; col = 30; offset = 223 }; - end_pos = - { Span.line = 20; col = 3; offset = 227 }; - file = "conformance/valid/005_functions.affine" } - })) - }) - }) - ] - } diff --git a/affinescript/conformance/valid/006_type_alias.affine b/affinescript/conformance/valid/006_type_alias.affine deleted file mode 100644 index 778864c..0000000 --- a/affinescript/conformance/valid/006_type_alias.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Conformance test: type aliases - -type Meters = Int -type Kilograms = Float -type Pair[A, B] = (A, B) -type IntPair = Pair[Int, Int] -type StringList = List[String] diff --git a/affinescript/conformance/valid/006_type_alias.expected b/affinescript/conformance/valid/006_type_alias.expected deleted file mode 100644 index f3e429e..0000000 --- a/affinescript/conformance/valid/006_type_alias.expected +++ /dev/null @@ -1,166 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Meters"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 35 }; - end_pos = { Span.line = 3; col = 6; offset = 40 }; - file = "conformance/valid/006_type_alias.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = { Span.line = 3; col = 13; offset = 47 }; - end_pos = { Span.line = 3; col = 15; offset = 49 }; - file = "conformance/valid/006_type_alias.affine" } - })) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Kilograms"; - span = - { Span.start_pos = { Span.line = 4; col = 1; offset = 53 }; - end_pos = { Span.line = 4; col = 6; offset = 58 }; - file = "conformance/valid/006_type_alias.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyCon - { Ast.name = "Float"; - span = - { Span.start_pos = { Span.line = 4; col = 16; offset = 68 }; - end_pos = { Span.line = 4; col = 18; offset = 70 }; - file = "conformance/valid/006_type_alias.affine" } - })) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Pair"; - span = - { Span.start_pos = { Span.line = 5; col = 1; offset = 76 }; - end_pos = { Span.line = 5; col = 6; offset = 81 }; - file = "conformance/valid/006_type_alias.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "A"; - span = - { Span.start_pos = { Span.line = 5; col = 10; offset = 85 }; - end_pos = { Span.line = 5; col = 11; offset = 86 }; - file = "conformance/valid/006_type_alias.affine" } - }; - tp_kind = None }; - { Ast.tp_quantity = None; - tp_name = - { Ast.name = "B"; - span = - { Span.start_pos = { Span.line = 5; col = 12; offset = 87 }; - end_pos = { Span.line = 5; col = 14; offset = 89 }; - file = "conformance/valid/006_type_alias.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyAlias - (Ast.TyTuple - [(Ast.TyCon - { Ast.name = "A"; - span = - { Span.start_pos = - { Span.line = 5; col = 19; offset = 94 }; - end_pos = { Span.line = 5; col = 20; offset = 95 }; - file = "conformance/valid/006_type_alias.affine" } - }); - (Ast.TyCon - { Ast.name = "B"; - span = - { Span.start_pos = - { Span.line = 5; col = 21; offset = 96 }; - end_pos = { Span.line = 5; col = 23; offset = 98 }; - file = "conformance/valid/006_type_alias.affine" } - }) - ])) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "IntPair"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 101 }; - end_pos = { Span.line = 6; col = 6; offset = 106 }; - file = "conformance/valid/006_type_alias.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyApp ( - { Ast.name = "Pair"; - span = - { Span.start_pos = { Span.line = 6; col = 14; offset = 114 }; - end_pos = { Span.line = 6; col = 16; offset = 116 }; - file = "conformance/valid/006_type_alias.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 6; col = 20; offset = 120 }; - end_pos = { Span.line = 6; col = 21; offset = 121 }; - file = "conformance/valid/006_type_alias.affine" } - })); - (Ast.TyArg - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 6; col = 24; offset = 124 }; - end_pos = - { Span.line = 6; col = 26; offset = 126 }; - file = "conformance/valid/006_type_alias.affine" } - })) - ] - ))) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "StringList"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 131 }; - end_pos = { Span.line = 7; col = 6; offset = 136 }; - file = "conformance/valid/006_type_alias.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyApp ( - { Ast.name = "List"; - span = - { Span.start_pos = { Span.line = 7; col = 17; offset = 147 }; - end_pos = { Span.line = 7; col = 19; offset = 149 }; - file = "conformance/valid/006_type_alias.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 7; col = 23; offset = 153 }; - end_pos = { Span.line = 7; col = 24; offset = 154 }; - file = "conformance/valid/006_type_alias.affine" } - })) - ] - ))) - }) - ] - } diff --git a/affinescript/conformance/valid/007_struct_def.affine b/affinescript/conformance/valid/007_struct_def.affine deleted file mode 100644 index c331290..0000000 --- a/affinescript/conformance/valid/007_struct_def.affine +++ /dev/null @@ -1,21 +0,0 @@ -// Conformance test: struct definitions - -type Point = { - x: Int, - y: Int -} - -type Person = { - name: String, - age: Int, - email: String -} - -type Generic[T] = { - value: T, - count: Int -} - -fn make_point(x: Int, y: Int) -> Point { - Point { x: x, y: y } -} diff --git a/affinescript/conformance/valid/007_struct_def.expected b/affinescript/conformance/valid/007_struct_def.expected deleted file mode 100644 index 652047f..0000000 --- a/affinescript/conformance/valid/007_struct_def.expected +++ /dev/null @@ -1,302 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Point"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 41 }; - end_pos = { Span.line = 3; col = 6; offset = 46 }; - file = "conformance/valid/007_struct_def.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 14; offset = 54 }; - end_pos = { Span.line = 4; col = 3; offset = 58 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 4; col = 4; offset = 59 }; - end_pos = { Span.line = 4; col = 6; offset = 61 }; - file = "conformance/valid/007_struct_def.affine" } - }) - }; - { Ast.rf_name = - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 4; col = 9; offset = 64 }; - end_pos = { Span.line = 5; col = 3; offset = 68 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 5; col = 4; offset = 69 }; - end_pos = { Span.line = 5; col = 6; offset = 71 }; - file = "conformance/valid/007_struct_def.affine" } - }) - } - ], - None))) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Person"; - span = - { Span.start_pos = { Span.line = 8; col = 1; offset = 78 }; - end_pos = { Span.line = 8; col = 6; offset = 83 }; - file = "conformance/valid/007_struct_def.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "name"; - span = - { Span.start_pos = - { Span.line = 8; col = 15; offset = 92 }; - end_pos = { Span.line = 9; col = 3; offset = 96 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 9; col = 7; offset = 100 }; - end_pos = { Span.line = 9; col = 9; offset = 102 }; - file = "conformance/valid/007_struct_def.affine" } - }) - }; - { Ast.rf_name = - { Ast.name = "age"; - span = - { Span.start_pos = - { Span.line = 9; col = 15; offset = 108 }; - end_pos = { Span.line = 10; col = 3; offset = 112 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 10; col = 6; offset = 115 }; - end_pos = { Span.line = 10; col = 8; offset = 117 }; - file = "conformance/valid/007_struct_def.affine" } - }) - }; - { Ast.rf_name = - { Ast.name = "email"; - span = - { Span.start_pos = - { Span.line = 10; col = 11; offset = 120 }; - end_pos = { Span.line = 11; col = 3; offset = 124 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 11; col = 8; offset = 129 }; - end_pos = - { Span.line = 11; col = 10; offset = 131 }; - file = "conformance/valid/007_struct_def.affine" } - }) - } - ], - None))) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Generic"; - span = - { Span.start_pos = { Span.line = 14; col = 1; offset = 141 }; - end_pos = { Span.line = 14; col = 6; offset = 146 }; - file = "conformance/valid/007_struct_def.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 14; col = 13; offset = 153 }; - end_pos = { Span.line = 14; col = 14; offset = 154 }; - file = "conformance/valid/007_struct_def.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyAlias - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "value"; - span = - { Span.start_pos = - { Span.line = 14; col = 19; offset = 159 }; - end_pos = { Span.line = 15; col = 3; offset = 163 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 15; col = 8; offset = 168 }; - end_pos = { Span.line = 15; col = 10; offset = 170 }; - file = "conformance/valid/007_struct_def.affine" } - }) - }; - { Ast.rf_name = - { Ast.name = "count"; - span = - { Span.start_pos = - { Span.line = 15; col = 11; offset = 171 }; - end_pos = { Span.line = 16; col = 3; offset = 175 }; - file = "conformance/valid/007_struct_def.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 16; col = 8; offset = 180 }; - end_pos = - { Span.line = 16; col = 10; offset = 182 }; - file = "conformance/valid/007_struct_def.affine" } - }) - } - ], - None))) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "make_point"; - span = - { Span.start_pos = { Span.line = 19; col = 1; offset = 189 }; - end_pos = { Span.line = 19; col = 4; offset = 192 }; - file = "conformance/valid/007_struct_def.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 19; col = 14; offset = 202 }; - end_pos = { Span.line = 19; col = 15; offset = 203 }; - file = "conformance/valid/007_struct_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 19; col = 16; offset = 204 }; - end_pos = { Span.line = 19; col = 18; offset = 206 }; - file = "conformance/valid/007_struct_def.affine" } - }) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "y"; - span = - { Span.start_pos = { Span.line = 19; col = 21; offset = 209 }; - end_pos = { Span.line = 19; col = 23; offset = 211 }; - file = "conformance/valid/007_struct_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 19; col = 24; offset = 212 }; - end_pos = { Span.line = 19; col = 26; offset = 214 }; - file = "conformance/valid/007_struct_def.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Point"; - span = - { Span.start_pos = - { Span.line = 19; col = 31; offset = 219 }; - end_pos = { Span.line = 19; col = 34; offset = 222 }; - file = "conformance/valid/007_struct_def.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprRecord { - er_fields = - [({ Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 20; col = 9; offset = 238 }; - end_pos = - { Span.line = 20; col = 11; offset = 240 }; - file = "conformance/valid/007_struct_def.affine" - } - }, - (Some (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 20; col = 12; offset = 241 - }; - end_pos = - { Span.line = 20; col = 14; offset = 243 - }; - file = - "conformance/valid/007_struct_def.affine" - } - }))); - ({ Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 20; col = 15; offset = 244 }; - end_pos = - { Span.line = 20; col = 17; offset = 246 }; - file = "conformance/valid/007_struct_def.affine" - } - }, - (Some (Ast.ExprVar - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 20; col = 18; - offset = 247 }; - end_pos = - { Span.line = 20; col = 20; - offset = 249 }; - file = - "conformance/valid/007_struct_def.affine" - } - }))) - ]; - er_spread = None}) - }) - }) - ] - } diff --git a/affinescript/conformance/valid/008_enum_def.affine b/affinescript/conformance/valid/008_enum_def.affine deleted file mode 100644 index a81ac53..0000000 --- a/affinescript/conformance/valid/008_enum_def.affine +++ /dev/null @@ -1,19 +0,0 @@ -// Conformance test: enum definitions - -type Option[T] = - | None - | Some(T) - -type Result[T, E] = - | Ok(T) - | Err(E) - -type List[T] = - | Nil - | Cons(T, List[T]) - -type Color = - | Red - | Green - | Blue - | RGB(Int, Int, Int) diff --git a/affinescript/conformance/valid/008_enum_def.expected b/affinescript/conformance/valid/008_enum_def.expected deleted file mode 100644 index 5bec046..0000000 --- a/affinescript/conformance/valid/008_enum_def.expected +++ /dev/null @@ -1,270 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Option"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 39 }; - end_pos = { Span.line = 3; col = 6; offset = 44 }; - file = "conformance/valid/008_enum_def.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 3; col = 12; offset = 50 }; - end_pos = { Span.line = 3; col = 13; offset = 51 }; - file = "conformance/valid/008_enum_def.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "None"; - span = - { Span.start_pos = { Span.line = 4; col = 3; offset = 58 }; - end_pos = { Span.line = 4; col = 5; offset = 60 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Some"; - span = - { Span.start_pos = { Span.line = 5; col = 3; offset = 67 }; - end_pos = { Span.line = 5; col = 5; offset = 69 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 5; col = 9; offset = 73 }; - end_pos = { Span.line = 5; col = 10; offset = 74 }; - file = "conformance/valid/008_enum_def.affine" } - }) - ]; - vd_ret_ty = None } - ]) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Result"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 78 }; - end_pos = { Span.line = 7; col = 6; offset = 83 }; - file = "conformance/valid/008_enum_def.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 7; col = 12; offset = 89 }; - end_pos = { Span.line = 7; col = 13; offset = 90 }; - file = "conformance/valid/008_enum_def.affine" } - }; - tp_kind = None }; - { Ast.tp_quantity = None; - tp_name = - { Ast.name = "E"; - span = - { Span.start_pos = { Span.line = 7; col = 14; offset = 91 }; - end_pos = { Span.line = 7; col = 16; offset = 93 }; - file = "conformance/valid/008_enum_def.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "Ok"; - span = - { Span.start_pos = { Span.line = 8; col = 3; offset = 100 }; - end_pos = { Span.line = 8; col = 5; offset = 102 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 8; col = 7; offset = 104 }; - end_pos = { Span.line = 8; col = 8; offset = 105 }; - file = "conformance/valid/008_enum_def.affine" } - }) - ]; - vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Err"; - span = - { Span.start_pos = { Span.line = 9; col = 3; offset = 110 }; - end_pos = { Span.line = 9; col = 5; offset = 112 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "E"; - span = - { Span.start_pos = - { Span.line = 9; col = 8; offset = 115 }; - end_pos = { Span.line = 9; col = 9; offset = 116 }; - file = "conformance/valid/008_enum_def.affine" } - }) - ]; - vd_ret_ty = None } - ]) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "List"; - span = - { Span.start_pos = { Span.line = 11; col = 1; offset = 120 }; - end_pos = { Span.line = 11; col = 6; offset = 125 }; - file = "conformance/valid/008_enum_def.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 11; col = 10; offset = 129 }; - end_pos = { Span.line = 11; col = 11; offset = 130 }; - file = "conformance/valid/008_enum_def.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "Nil"; - span = - { Span.start_pos = { Span.line = 12; col = 3; offset = 137 }; - end_pos = { Span.line = 12; col = 5; offset = 139 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Cons"; - span = - { Span.start_pos = - { Span.line = 13; col = 3; offset = 145 }; - end_pos = { Span.line = 13; col = 5; offset = 147 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 13; col = 9; offset = 151 }; - end_pos = { Span.line = 13; col = 10; offset = 152 }; - file = "conformance/valid/008_enum_def.affine" } - }); - (Ast.TyApp ( - { Ast.name = "List"; - span = - { Span.start_pos = - { Span.line = 13; col = 11; offset = 153 }; - end_pos = { Span.line = 13; col = 13; offset = 155 }; - file = "conformance/valid/008_enum_def.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 13; col = 17; offset = 159 }; - end_pos = - { Span.line = 13; col = 18; offset = 160 }; - file = - "conformance/valid/008_enum_def.affine" } - })) - ] - )) - ]; - vd_ret_ty = None } - ]) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Color"; - span = - { Span.start_pos = { Span.line = 15; col = 1; offset = 165 }; - end_pos = { Span.line = 15; col = 6; offset = 170 }; - file = "conformance/valid/008_enum_def.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "Red"; - span = - { Span.start_pos = { Span.line = 16; col = 3; offset = 180 }; - end_pos = { Span.line = 16; col = 5; offset = 182 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Green"; - span = - { Span.start_pos = - { Span.line = 17; col = 3; offset = 188 }; - end_pos = { Span.line = 17; col = 5; offset = 190 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Blue"; - span = - { Span.start_pos = - { Span.line = 18; col = 3; offset = 198 }; - end_pos = { Span.line = 18; col = 5; offset = 200 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "RGB"; - span = - { Span.start_pos = - { Span.line = 19; col = 3; offset = 207 }; - end_pos = { Span.line = 19; col = 5; offset = 209 }; - file = "conformance/valid/008_enum_def.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 19; col = 8; offset = 212 }; - end_pos = { Span.line = 19; col = 9; offset = 213 }; - file = "conformance/valid/008_enum_def.affine" } - }); - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 19; col = 12; offset = 216 }; - end_pos = { Span.line = 19; col = 14; offset = 218 }; - file = "conformance/valid/008_enum_def.affine" } - }); - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 19; col = 17; offset = 221 }; - end_pos = { Span.line = 19; col = 19; offset = 223 }; - file = "conformance/valid/008_enum_def.affine" } - }) - ]; - vd_ret_ty = None } - ]) - }) - ] - } diff --git a/affinescript/conformance/valid/009_effect_def.affine b/affinescript/conformance/valid/009_effect_def.affine deleted file mode 100644 index 93bc470..0000000 --- a/affinescript/conformance/valid/009_effect_def.affine +++ /dev/null @@ -1,21 +0,0 @@ -// Conformance test: effect definitions - -effect IO { - fn print(s: String); - fn println(s: String); - fn read_line() -> String; -} - -effect State[S] { - fn get() -> S; - fn put(s: S); -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -effect Async { - fn await[T](promise: Promise[T]) -> T; - fn spawn[T](f: () -> T) -> Promise[T]; -} diff --git a/affinescript/conformance/valid/009_effect_def.expected b/affinescript/conformance/valid/009_effect_def.expected deleted file mode 100644 index fe69625..0000000 --- a/affinescript/conformance/valid/009_effect_def.expected +++ /dev/null @@ -1,341 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "IO"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 41 }; - end_pos = { Span.line = 3; col = 8; offset = 48 }; - file = "conformance/valid/009_effect_def.affine" } - }; - ed_type_params = []; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "print"; - span = - { Span.start_pos = { Span.line = 4; col = 3; offset = 55 }; - end_pos = { Span.line = 4; col = 6; offset = 58 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "s"; - span = - { Span.start_pos = { Span.line = 4; col = 11; offset = 63 }; - end_pos = { Span.line = 4; col = 12; offset = 64 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 4; col = 13; offset = 65 }; - end_pos = { Span.line = 4; col = 15; offset = 67 }; - file = "conformance/valid/009_effect_def.affine" } - }) - } - ]; - eod_ret_ty = None }; - { Ast.eod_name = - { Ast.name = "println"; - span = - { Span.start_pos = { Span.line = 5; col = 3; offset = 78 }; - end_pos = { Span.line = 5; col = 6; offset = 81 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "s"; - span = - { Span.start_pos = { Span.line = 5; col = 13; offset = 88 }; - end_pos = { Span.line = 5; col = 14; offset = 89 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 5; col = 15; offset = 90 }; - end_pos = { Span.line = 5; col = 17; offset = 92 }; - file = "conformance/valid/009_effect_def.affine" } - }) - } - ]; - eod_ret_ty = None }; - { Ast.eod_name = - { Ast.name = "read_line"; - span = - { Span.start_pos = { Span.line = 6; col = 3; offset = 103 }; - end_pos = { Span.line = 6; col = 6; offset = 106 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = []; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 6; col = 18; offset = 118 }; - end_pos = { Span.line = 6; col = 21; offset = 121 }; - file = "conformance/valid/009_effect_def.affine" } - })) - } - ] - }); - (Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "State"; - span = - { Span.start_pos = { Span.line = 9; col = 1; offset = 132 }; - end_pos = { Span.line = 9; col = 8; offset = 139 }; - file = "conformance/valid/009_effect_def.affine" } - }; - ed_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "S"; - span = - { Span.start_pos = { Span.line = 9; col = 13; offset = 144 }; - end_pos = { Span.line = 9; col = 14; offset = 145 }; - file = "conformance/valid/009_effect_def.affine" } - }; - tp_kind = None } - ]; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "get"; - span = - { Span.start_pos = { Span.line = 10; col = 3; offset = 152 }; - end_pos = { Span.line = 10; col = 6; offset = 155 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = []; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "S"; - span = - { Span.start_pos = - { Span.line = 10; col = 12; offset = 161 }; - end_pos = { Span.line = 10; col = 15; offset = 164 }; - file = "conformance/valid/009_effect_def.affine" } - })) - }; - { Ast.eod_name = - { Ast.name = "put"; - span = - { Span.start_pos = { Span.line = 11; col = 3; offset = 169 }; - end_pos = { Span.line = 11; col = 6; offset = 172 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "s"; - span = - { Span.start_pos = - { Span.line = 11; col = 9; offset = 175 }; - end_pos = { Span.line = 11; col = 10; offset = 176 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "S"; - span = - { Span.start_pos = - { Span.line = 11; col = 11; offset = 177 }; - end_pos = { Span.line = 11; col = 13; offset = 179 }; - file = "conformance/valid/009_effect_def.affine" } - }) - } - ]; - eod_ret_ty = None } - ] - }); - (Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "Exn"; - span = - { Span.start_pos = { Span.line = 14; col = 1; offset = 186 }; - end_pos = { Span.line = 14; col = 8; offset = 193 }; - file = "conformance/valid/009_effect_def.affine" } - }; - ed_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "E"; - span = - { Span.start_pos = { Span.line = 14; col = 11; offset = 196 }; - end_pos = { Span.line = 14; col = 12; offset = 197 }; - file = "conformance/valid/009_effect_def.affine" } - }; - tp_kind = None } - ]; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "throw"; - span = - { Span.start_pos = { Span.line = 15; col = 3; offset = 204 }; - end_pos = { Span.line = 15; col = 6; offset = 207 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "err"; - span = - { Span.start_pos = - { Span.line = 15; col = 11; offset = 212 }; - end_pos = { Span.line = 15; col = 12; offset = 213 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "E"; - span = - { Span.start_pos = - { Span.line = 15; col = 15; offset = 216 }; - end_pos = { Span.line = 15; col = 17; offset = 218 }; - file = "conformance/valid/009_effect_def.affine" } - }) - } - ]; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Never"; - span = - { Span.start_pos = - { Span.line = 15; col = 20; offset = 221 }; - end_pos = { Span.line = 15; col = 23; offset = 224 }; - file = "conformance/valid/009_effect_def.affine" } - })) - } - ] - }); - (Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "Async"; - span = - { Span.start_pos = { Span.line = 18; col = 1; offset = 234 }; - end_pos = { Span.line = 18; col = 8; offset = 241 }; - file = "conformance/valid/009_effect_def.affine" } - }; - ed_type_params = []; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "await"; - span = - { Span.start_pos = { Span.line = 19; col = 3; offset = 251 }; - end_pos = { Span.line = 19; col = 6; offset = 254 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "promise"; - span = - { Span.start_pos = - { Span.line = 19; col = 14; offset = 262 }; - end_pos = { Span.line = 19; col = 15; offset = 263 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyApp ( - { Ast.name = "Promise"; - span = - { Span.start_pos = - { Span.line = 19; col = 22; offset = 270 }; - end_pos = { Span.line = 19; col = 24; offset = 272 }; - file = "conformance/valid/009_effect_def.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 19; col = 31; offset = 279 }; - end_pos = - { Span.line = 19; col = 32; offset = 280 }; - file = "conformance/valid/009_effect_def.affine" - } - })) - ] - )) - } - ]; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 19; col = 36; offset = 284 }; - end_pos = { Span.line = 19; col = 39; offset = 287 }; - file = "conformance/valid/009_effect_def.affine" } - })) - }; - { Ast.eod_name = - { Ast.name = "spawn"; - span = - { Span.start_pos = { Span.line = 20; col = 3; offset = 292 }; - end_pos = { Span.line = 20; col = 6; offset = 295 }; - file = "conformance/valid/009_effect_def.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 20; col = 14; offset = 303 }; - end_pos = { Span.line = 20; col = 15; offset = 304 }; - file = "conformance/valid/009_effect_def.affine" } - }; - p_ty = - (Ast.TyArrow ((Ast.TyTuple []), None, - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 20; col = 21; offset = 310 }; - end_pos = - { Span.line = 20; col = 24; offset = 313 }; - file = "conformance/valid/009_effect_def.affine" } - }), - None)) - } - ]; - eod_ret_ty = - (Some (Ast.TyApp ( - { Ast.name = "Promise"; - span = - { Span.start_pos = - { Span.line = 20; col = 27; offset = 316 }; - end_pos = - { Span.line = 20; col = 30; offset = 319 }; - file = "conformance/valid/009_effect_def.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 20; col = 37; offset = 326 }; - end_pos = - { Span.line = 20; col = 38; offset = 327 }; - file = - "conformance/valid/009_effect_def.affine" } - })) - ] - ))) - } - ] - }) - ] - } diff --git a/affinescript/conformance/valid/010_pattern_match.affine b/affinescript/conformance/valid/010_pattern_match.affine deleted file mode 100644 index afaec00..0000000 --- a/affinescript/conformance/valid/010_pattern_match.affine +++ /dev/null @@ -1,27 +0,0 @@ -// Conformance test: pattern matching - -type Option[T] = - | None - | Some(T) - -fn unwrap_or[T](opt: Option[T], default: T) -> T { - match opt { - None => default, - Some(x) => x - } -} - -fn is_some[T](opt: Option[T]) -> Bool { - match opt { - None => false, - Some(_) => true - } -} - -fn describe_number(n: Int) -> String { - match n { - 0 => "zero", - 1 => "one", - _ => "many" - } -} diff --git a/affinescript/conformance/valid/010_pattern_match.expected b/affinescript/conformance/valid/010_pattern_match.expected deleted file mode 100644 index 9d3811b..0000000 --- a/affinescript/conformance/valid/010_pattern_match.expected +++ /dev/null @@ -1,494 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Option"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 39 }; - end_pos = { Span.line = 3; col = 6; offset = 44 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 3; col = 12; offset = 50 }; - end_pos = { Span.line = 3; col = 13; offset = 51 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "None"; - span = - { Span.start_pos = { Span.line = 4; col = 3; offset = 58 }; - end_pos = { Span.line = 4; col = 5; offset = 60 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Some"; - span = - { Span.start_pos = { Span.line = 5; col = 3; offset = 67 }; - end_pos = { Span.line = 5; col = 5; offset = 69 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 5; col = 9; offset = 73 }; - end_pos = { Span.line = 5; col = 10; offset = 74 }; - file = "conformance/valid/010_pattern_match.affine" } - }) - ]; - vd_ret_ty = None } - ]) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "unwrap_or"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 78 }; - end_pos = { Span.line = 7; col = 4; offset = 81 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 7; col = 13; offset = 90 }; - end_pos = { Span.line = 7; col = 14; offset = 91 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - tp_kind = None } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "opt"; - span = - { Span.start_pos = { Span.line = 7; col = 16; offset = 93 }; - end_pos = { Span.line = 7; col = 17; offset = 94 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - p_ty = - (Ast.TyApp ( - { Ast.name = "Option"; - span = - { Span.start_pos = { Span.line = 7; col = 20; offset = 97 }; - end_pos = { Span.line = 7; col = 22; offset = 99 }; - file = "conformance/valid/010_pattern_match.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 7; col = 28; offset = 105 }; - end_pos = { Span.line = 7; col = 29; offset = 106 }; - file = "conformance/valid/010_pattern_match.affine" - } - })) - ] - )) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "default"; - span = - { Span.start_pos = { Span.line = 7; col = 31; offset = 108 }; - end_pos = { Span.line = 7; col = 33; offset = 110 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 7; col = 40; offset = 117 }; - end_pos = { Span.line = 7; col = 42; offset = 119 }; - file = "conformance/valid/010_pattern_match.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 7; col = 45; offset = 122 }; - end_pos = { Span.line = 7; col = 48; offset = 125 }; - file = "conformance/valid/010_pattern_match.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "opt"; - span = - { Span.start_pos = - { Span.line = 8; col = 3; offset = 131 }; - end_pos = - { Span.line = 8; col = 9; offset = 137 }; - file = - "conformance/valid/010_pattern_match.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "None"; - span = - { Span.start_pos = - { Span.line = 8; col = 13; offset = 141 }; - end_pos = - { Span.line = 9; col = 5; offset = 147 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }, - [])); - ma_guard = None; - ma_body = - (Ast.ExprVar - { Ast.name = "default"; - span = - { Span.start_pos = - { Span.line = 9; col = 10; offset = 152 }; - end_pos = - { Span.line = 9; col = 13; offset = 155 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }) - }; - { Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "Some"; - span = - { Span.start_pos = - { Span.line = 9; col = 20; offset = 162 }; - end_pos = - { Span.line = 10; col = 5; offset = 168 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }, - [(Ast.PatVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 10; col = 9; offset = 172 - }; - end_pos = - { Span.line = 10; col = 10; offset = 173 - }; - file = - "conformance/valid/010_pattern_match.affine" - } - }) - ] - )); - ma_guard = None; - ma_body = - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 10; col = 13; offset = 176 }; - end_pos = - { Span.line = 10; col = 16; offset = 179 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }) - } - ]}) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "is_some"; - span = - { Span.start_pos = { Span.line = 14; col = 1; offset = 188 }; - end_pos = { Span.line = 14; col = 4; offset = 191 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 14; col = 11; offset = 198 }; - end_pos = { Span.line = 14; col = 12; offset = 199 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - tp_kind = None } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "opt"; - span = - { Span.start_pos = { Span.line = 14; col = 14; offset = 201 }; - end_pos = { Span.line = 14; col = 15; offset = 202 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - p_ty = - (Ast.TyApp ( - { Ast.name = "Option"; - span = - { Span.start_pos = - { Span.line = 14; col = 18; offset = 205 }; - end_pos = { Span.line = 14; col = 20; offset = 207 }; - file = "conformance/valid/010_pattern_match.affine" } - }, - [(Ast.TyArg - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 14; col = 26; offset = 213 }; - end_pos = - { Span.line = 14; col = 27; offset = 214 }; - file = "conformance/valid/010_pattern_match.affine" - } - })) - ] - )) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Bool"; - span = - { Span.start_pos = - { Span.line = 14; col = 31; offset = 218 }; - end_pos = { Span.line = 14; col = 34; offset = 221 }; - file = "conformance/valid/010_pattern_match.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "opt"; - span = - { Span.start_pos = - { Span.line = 15; col = 3; offset = 230 }; - end_pos = - { Span.line = 15; col = 9; offset = 236 }; - file = - "conformance/valid/010_pattern_match.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "None"; - span = - { Span.start_pos = - { Span.line = 15; col = 13; offset = 240 }; - end_pos = - { Span.line = 16; col = 5; offset = 246 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }, - [])); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitBool (false, - { Span.start_pos = - { Span.line = 16; col = 10; offset = 251 }; - end_pos = - { Span.line = 16; col = 13; offset = 254 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))) - }; - { Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "Some"; - span = - { Span.start_pos = - { Span.line = 16; col = 18; offset = 259 }; - end_pos = - { Span.line = 17; col = 5; offset = 265 }; - file = - "conformance/valid/010_pattern_match.affine" - } - }, - [(Ast.PatWildcard - { Span.start_pos = - { Span.line = 17; col = 9; offset = 269 }; - end_pos = - { Span.line = 17; col = 10; offset = 270 }; - file = - "conformance/valid/010_pattern_match.affine" - }) - ] - )); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitBool (true, - { Span.start_pos = - { Span.line = 17; col = 13; offset = 273 }; - end_pos = - { Span.line = 17; col = 16; offset = 276 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))) - } - ]}) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "describe_number"; - span = - { Span.start_pos = { Span.line = 21; col = 1; offset = 288 }; - end_pos = { Span.line = 21; col = 4; offset = 291 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "n"; - span = - { Span.start_pos = { Span.line = 21; col = 19; offset = 306 }; - end_pos = { Span.line = 21; col = 20; offset = 307 }; - file = "conformance/valid/010_pattern_match.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 21; col = 21; offset = 308 }; - end_pos = { Span.line = 21; col = 23; offset = 310 }; - file = "conformance/valid/010_pattern_match.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 21; col = 28; offset = 315 }; - end_pos = { Span.line = 21; col = 31; offset = 318 }; - file = "conformance/valid/010_pattern_match.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "n"; - span = - { Span.start_pos = - { Span.line = 22; col = 3; offset = 329 }; - end_pos = - { Span.line = 22; col = 9; offset = 335 }; - file = - "conformance/valid/010_pattern_match.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatLit - (Ast.LitInt (0, - { Span.start_pos = - { Span.line = 22; col = 11; offset = 337 }; - end_pos = - { Span.line = 23; col = 5; offset = 343 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitString ("zero", - { Span.start_pos = - { Span.line = 23; col = 7; offset = 345 }; - end_pos = - { Span.line = 23; col = 15; offset = 353 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))) - }; - { Ast.ma_pat = - (Ast.PatLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 23; col = 16; offset = 354 }; - end_pos = - { Span.line = 24; col = 5; offset = 360 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitString ("one", - { Span.start_pos = - { Span.line = 24; col = 7; offset = 362 }; - end_pos = - { Span.line = 24; col = 14; offset = 369 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))) - }; - { Ast.ma_pat = - (Ast.PatWildcard - { Span.start_pos = - { Span.line = 24; col = 15; offset = 370 }; - end_pos = - { Span.line = 25; col = 5; offset = 376 }; - file = - "conformance/valid/010_pattern_match.affine" }); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitString ("many", - { Span.start_pos = - { Span.line = 25; col = 7; offset = 378 }; - end_pos = - { Span.line = 25; col = 15; offset = 386 }; - file = - "conformance/valid/010_pattern_match.affine" - } - ))) - } - ]}) - }) - }) - ] - } diff --git a/affinescript/conformance/valid/011_rows.affine b/affinescript/conformance/valid/011_rows.affine deleted file mode 100644 index e4904db..0000000 --- a/affinescript/conformance/valid/011_rows.affine +++ /dev/null @@ -1,16 +0,0 @@ -// Conformance test: row polymorphism - -fn get_x[..r](point: {x: Int, ..r}) -> Int { - point.x -} - -fn get_name[..r](entity: {name: String, ..r}) -> String { - entity.name -} - -fn with_id[..r](record: {..r}, id: Int) -> {id: Int, ..r} { - { id: id, ..record } -} - -type HasPosition[..r] = {x: Int, y: Int, ..r} -type HasName[..r] = {name: String, ..r} diff --git a/affinescript/conformance/valid/011_rows.expected b/affinescript/conformance/valid/011_rows.expected deleted file mode 100644 index 7c80001..0000000 --- a/affinescript/conformance/valid/011_rows.expected +++ /dev/null @@ -1,445 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "get_x"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 39 }; - end_pos = { Span.line = 3; col = 4; offset = 42 }; - file = "conformance/valid/011_rows.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 3; col = 9; offset = 47 }; - end_pos = { Span.line = 3; col = 10; offset = 48 }; - file = "conformance/valid/011_rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "point"; - span = - { Span.start_pos = { Span.line = 3; col = 14; offset = 52 }; - end_pos = { Span.line = 3; col = 15; offset = 53 }; - file = "conformance/valid/011_rows.affine" } - }; - p_ty = - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 22; offset = 60 }; - end_pos = { Span.line = 3; col = 23; offset = 61 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 3; col = 24; offset = 62 }; - end_pos = { Span.line = 3; col = 26; offset = 64 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 3; col = 29; offset = 67 }; - end_pos = { Span.line = 3; col = 31; offset = 69 }; - file = "conformance/valid/011_rows.affine" } - }) - )) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 3; col = 37; offset = 75 }; - end_pos = { Span.line = 3; col = 40; offset = 78 }; - file = "conformance/valid/011_rows.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprField ( - (Ast.ExprVar - { Ast.name = "point"; - span = - { Span.start_pos = - { Span.line = 3; col = 44; offset = 82 }; - end_pos = - { Span.line = 4; col = 3; offset = 86 }; - file = "conformance/valid/011_rows.affine" } - }), - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 4; col = 8; offset = 91 }; - end_pos = { Span.line = 4; col = 9; offset = 92 }; - file = "conformance/valid/011_rows.affine" } - } - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "get_name"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 97 }; - end_pos = { Span.line = 7; col = 4; offset = 100 }; - file = "conformance/valid/011_rows.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 7; col = 12; offset = 108 }; - end_pos = { Span.line = 7; col = 13; offset = 109 }; - file = "conformance/valid/011_rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "entity"; - span = - { Span.start_pos = { Span.line = 7; col = 17; offset = 113 }; - end_pos = { Span.line = 7; col = 18; offset = 114 }; - file = "conformance/valid/011_rows.affine" } - }; - p_ty = - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "name"; - span = - { Span.start_pos = - { Span.line = 7; col = 26; offset = 122 }; - end_pos = { Span.line = 7; col = 27; offset = 123 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 7; col = 31; offset = 127 }; - end_pos = { Span.line = 7; col = 33; offset = 129 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 7; col = 39; offset = 135 }; - end_pos = { Span.line = 7; col = 41; offset = 137 }; - file = "conformance/valid/011_rows.affine" } - }) - )) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 7; col = 47; offset = 143 }; - end_pos = { Span.line = 7; col = 50; offset = 146 }; - file = "conformance/valid/011_rows.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprField ( - (Ast.ExprVar - { Ast.name = "entity"; - span = - { Span.start_pos = - { Span.line = 7; col = 57; offset = 153 }; - end_pos = - { Span.line = 8; col = 3; offset = 157 }; - file = "conformance/valid/011_rows.affine" } - }), - { Ast.name = "name"; - span = - { Span.start_pos = - { Span.line = 8; col = 9; offset = 163 }; - end_pos = - { Span.line = 8; col = 10; offset = 164 }; - file = "conformance/valid/011_rows.affine" } - } - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "with_id"; - span = - { Span.start_pos = { Span.line = 11; col = 1; offset = 172 }; - end_pos = { Span.line = 11; col = 4; offset = 175 }; - file = "conformance/valid/011_rows.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 11; col = 11; offset = 182 }; - end_pos = { Span.line = 11; col = 12; offset = 183 }; - file = "conformance/valid/011_rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "record"; - span = - { Span.start_pos = { Span.line = 11; col = 16; offset = 187 }; - end_pos = { Span.line = 11; col = 17; offset = 188 }; - file = "conformance/valid/011_rows.affine" } - }; - p_ty = - (Ast.TyRecord ([], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 11; col = 25; offset = 196 }; - end_pos = { Span.line = 11; col = 26; offset = 197 }; - file = "conformance/valid/011_rows.affine" } - }) - )) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "id"; - span = - { Span.start_pos = { Span.line = 11; col = 30; offset = 201 }; - end_pos = { Span.line = 11; col = 32; offset = 203 }; - file = "conformance/valid/011_rows.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 34; offset = 205 }; - end_pos = { Span.line = 11; col = 36; offset = 207 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "id"; - span = - { Span.start_pos = - { Span.line = 11; col = 44; offset = 215 }; - end_pos = { Span.line = 11; col = 45; offset = 216 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 47; offset = 218 }; - end_pos = - { Span.line = 11; col = 49; offset = 220 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 11; col = 52; offset = 223 }; - end_pos = - { Span.line = 11; col = 54; offset = 225 }; - file = "conformance/valid/011_rows.affine" } - }) - ))); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprRecord { - er_fields = - [({ Ast.name = "id"; - span = - { Span.start_pos = - { Span.line = 12; col = 3; offset = 234 }; - end_pos = - { Span.line = 12; col = 5; offset = 236 }; - file = "conformance/valid/011_rows.affine" } - }, - (Some (Ast.ExprVar - { Ast.name = "id"; - span = - { Span.start_pos = - { Span.line = 12; col = 7; offset = 238 - }; - end_pos = - { Span.line = 12; col = 9; offset = 240 - }; - file = - "conformance/valid/011_rows.affine" } - }))) - ]; - er_spread = - (Some (Ast.ExprVar - { Ast.name = "record"; - span = - { Span.start_pos = - { Span.line = 12; col = 11; offset = 242 }; - end_pos = - { Span.line = 12; col = 13; offset = 244 }; - file = "conformance/valid/011_rows.affine" - } - }))}) - }) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "HasPosition"; - span = - { Span.start_pos = { Span.line = 15; col = 1; offset = 258 }; - end_pos = { Span.line = 15; col = 6; offset = 263 }; - file = "conformance/valid/011_rows.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 15; col = 17; offset = 274 }; - end_pos = { Span.line = 15; col = 18; offset = 275 }; - file = "conformance/valid/011_rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - td_body = - (Ast.TyAlias - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 15; col = 25; offset = 282 }; - end_pos = { Span.line = 15; col = 26; offset = 283 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 15; col = 27; offset = 284 }; - end_pos = { Span.line = 15; col = 29; offset = 286 }; - file = "conformance/valid/011_rows.affine" } - }) - }; - { Ast.rf_name = - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 15; col = 32; offset = 289 }; - end_pos = { Span.line = 15; col = 34; offset = 291 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 15; col = 35; offset = 292 }; - end_pos = - { Span.line = 15; col = 37; offset = 294 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 15; col = 40; offset = 297 }; - end_pos = { Span.line = 15; col = 42; offset = 299 }; - file = "conformance/valid/011_rows.affine" } - }) - ))) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "HasName"; - span = - { Span.start_pos = { Span.line = 16; col = 1; offset = 304 }; - end_pos = { Span.line = 16; col = 6; offset = 309 }; - file = "conformance/valid/011_rows.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 16; col = 13; offset = 316 }; - end_pos = { Span.line = 16; col = 14; offset = 317 }; - file = "conformance/valid/011_rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - td_body = - (Ast.TyAlias - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "name"; - span = - { Span.start_pos = - { Span.line = 16; col = 21; offset = 324 }; - end_pos = { Span.line = 16; col = 22; offset = 325 }; - file = "conformance/valid/011_rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 16; col = 26; offset = 329 }; - end_pos = { Span.line = 16; col = 28; offset = 331 }; - file = "conformance/valid/011_rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 16; col = 34; offset = 337 }; - end_pos = { Span.line = 16; col = 36; offset = 339 }; - file = "conformance/valid/011_rows.affine" } - }) - ))) - }) - ] - } diff --git a/affinescript/conformance/valid/012_ownership.affine b/affinescript/conformance/valid/012_ownership.affine deleted file mode 100644 index 636c658..0000000 --- a/affinescript/conformance/valid/012_ownership.affine +++ /dev/null @@ -1,20 +0,0 @@ -// Conformance test: ownership annotations - -type Resource = own { handle: Int } - -fn consume(r: own Resource) -> () { - // Takes ownership, resource is consumed -} - -fn borrow(r: ref Resource) -> Int { - // Immutable borrow - r.handle -} - -fn mutate(r: mut Resource) -> () { - // Mutable borrow -} - -fn create() -> own Resource { - Resource { handle: 42 } -} diff --git a/affinescript/conformance/valid/012_ownership.expected b/affinescript/conformance/valid/012_ownership.expected deleted file mode 100644 index 87a3ce6..0000000 --- a/affinescript/conformance/valid/012_ownership.expected +++ /dev/null @@ -1,223 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Resource"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 44 }; - end_pos = { Span.line = 3; col = 6; offset = 49 }; - file = "conformance/valid/012_ownership.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyOwn - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "handle"; - span = - { Span.start_pos = - { Span.line = 3; col = 21; offset = 64 }; - end_pos = { Span.line = 3; col = 23; offset = 66 }; - file = "conformance/valid/012_ownership.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 3; col = 29; offset = 72 }; - end_pos = { Span.line = 3; col = 31; offset = 74 }; - file = "conformance/valid/012_ownership.affine" } - }) - } - ], - None)))) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "consume"; - span = - { Span.start_pos = { Span.line = 5; col = 1; offset = 81 }; - end_pos = { Span.line = 5; col = 4; offset = 84 }; - file = "conformance/valid/012_ownership.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 5; col = 11; offset = 91 }; - end_pos = { Span.line = 5; col = 12; offset = 92 }; - file = "conformance/valid/012_ownership.affine" } - }; - p_ty = - (Ast.TyOwn - (Ast.TyCon - { Ast.name = "Resource"; - span = - { Span.start_pos = - { Span.line = 5; col = 15; offset = 95 }; - end_pos = { Span.line = 5; col = 19; offset = 99 }; - file = "conformance/valid/012_ownership.affine" } - })) - } - ]; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = (Ast.FnBlock { Ast.blk_stmts = []; blk_expr = None }) }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "borrow"; - span = - { Span.start_pos = { Span.line = 9; col = 1; offset = 163 }; - end_pos = { Span.line = 9; col = 4; offset = 166 }; - file = "conformance/valid/012_ownership.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 9; col = 10; offset = 172 }; - end_pos = { Span.line = 9; col = 11; offset = 173 }; - file = "conformance/valid/012_ownership.affine" } - }; - p_ty = - (Ast.TyRef - (Ast.TyCon - { Ast.name = "Resource"; - span = - { Span.start_pos = - { Span.line = 9; col = 14; offset = 176 }; - end_pos = { Span.line = 9; col = 18; offset = 180 }; - file = "conformance/valid/012_ownership.affine" } - })) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 9; col = 28; offset = 190 }; - end_pos = { Span.line = 9; col = 31; offset = 193 }; - file = "conformance/valid/012_ownership.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprField ( - (Ast.ExprVar - { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 9; col = 35; offset = 197 }; - end_pos = - { Span.line = 11; col = 3; offset = 223 }; - file = "conformance/valid/012_ownership.affine" - } - }), - { Ast.name = "handle"; - span = - { Span.start_pos = - { Span.line = 11; col = 4; offset = 224 }; - end_pos = - { Span.line = 11; col = 5; offset = 225 }; - file = "conformance/valid/012_ownership.affine" } - } - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "mutate"; - span = - { Span.start_pos = { Span.line = 14; col = 1; offset = 235 }; - end_pos = { Span.line = 14; col = 4; offset = 238 }; - file = "conformance/valid/012_ownership.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 14; col = 10; offset = 244 }; - end_pos = { Span.line = 14; col = 11; offset = 245 }; - file = "conformance/valid/012_ownership.affine" } - }; - p_ty = - (Ast.TyMut - (Ast.TyCon - { Ast.name = "Resource"; - span = - { Span.start_pos = - { Span.line = 14; col = 14; offset = 248 }; - end_pos = { Span.line = 14; col = 18; offset = 252 }; - file = "conformance/valid/012_ownership.affine" } - })) - } - ]; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = (Ast.FnBlock { Ast.blk_stmts = []; blk_expr = None }) }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "create"; - span = - { Span.start_pos = { Span.line = 18; col = 1; offset = 293 }; - end_pos = { Span.line = 18; col = 4; offset = 296 }; - file = "conformance/valid/012_ownership.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyOwn - (Ast.TyCon - { Ast.name = "Resource"; - span = - { Span.start_pos = - { Span.line = 18; col = 16; offset = 308 }; - end_pos = { Span.line = 18; col = 20; offset = 312 }; - file = "conformance/valid/012_ownership.affine" } - }))); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprRecord { - er_fields = - [({ Ast.name = "handle"; - span = - { Span.start_pos = - { Span.line = 19; col = 12; offset = 334 }; - end_pos = - { Span.line = 19; col = 14; offset = 336 }; - file = "conformance/valid/012_ownership.affine" } - }, - (Some (Ast.ExprLit - (Ast.LitInt (42, - { Span.start_pos = - { Span.line = 19; col = 20; - offset = 342 }; - end_pos = - { Span.line = 19; col = 22; - offset = 344 }; - file = - "conformance/valid/012_ownership.affine" - } - ))))) - ]; - er_spread = None}) - }) - }) - ] - } diff --git a/affinescript/distributions/rattlescript/Cargo.toml b/affinescript/distributions/rattlescript/Cargo.toml deleted file mode 100644 index f70c39d..0000000 --- a/affinescript/distributions/rattlescript/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -[package] -name = "rattle" -version = "0.1.0" -edition = "2021" -description = "Python-syntax AffineScript — write Python-style code, get affine resource guarantees" -license = "MPL-2.0" -authors = ["Jonathan D.A. Jewell "] -repository = "https://github.com/hyperpolymath/rattlescript" -keywords = ["affinescript", "python-face", "wasm", "affine-types", "linear-types"] -categories = ["development-tools", "compilers"] - -[dependencies] -# Pure Rust thin wrapper — no external dependencies. - -[build-dependencies] -# build.rs detects or requires AFFINESCRIPT_BIN at compile time - -[[bin]] -name = "rattle" -path = "src/main.rs" - -[profile.release] -opt-level = "z" -lto = true -strip = true diff --git a/affinescript/distributions/rattlescript/Justfile b/affinescript/distributions/rattlescript/Justfile deleted file mode 100644 index 491ac01..0000000 --- a/affinescript/distributions/rattlescript/Justfile +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -// Owner: Jonathan D.A. Jewell -# SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -# RattleScript distribution justfile. -# Most work lives in the parent affinescript justfile. -# This file handles the thin Rust wrapper binary only. - -default: - just --list - -# Build the rattle binary (dev mode, uses sibling _build/) -build: - cargo build - -# Build optimised release binary -release: - cargo build --release - -# Run the example -run-hello: - cargo run -- eval examples/hello.rattle - -# Check example typechecks -check-hello: - cargo run -- check examples/hello.rattle - -# Show the Python-face transform for the hello example -preview-hello: - cargo run -- preview-python-transform examples/hello.rattle - -# Run all examples -check-examples: - cargo run -- check examples/hello.rattle - cargo run -- check examples/ownership.rattle - -# Install rattle binary to ~/.cargo/bin -install: - cargo install --path . - -# Bump AffineScript submodule pointer (called by CI after affinescript releases) -update-affinescript: - #!/usr/bin/env bash - set -euo pipefail - git -C ../.. submodule update --remote - git add ../.. - git commit -m "chore(affinescript): advance pointer to latest release" - -secret-scan-trufflehog: - @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true diff --git a/affinescript/distributions/rattlescript/README.adoc b/affinescript/distributions/rattlescript/README.adoc deleted file mode 100644 index 7898bf0..0000000 --- a/affinescript/distributions/rattlescript/README.adoc +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) -= RattleScript -:toc: -:toc-placement: preamble - -Python-syntax AffineScript. -Write code that looks like Python. -Get affine resource guarantees, algebraic effects, and typed WASM out. - ---- - -== What it is - -RattleScript is AffineScript with its Python face pre-selected. -If you write Python, you already know most of the syntax. -The compiler checks that your resources (files, sockets, tokens) are used -*exactly as many times as you say* — and proves it at compile time. - -[source,python] ----- -def greet(name: String) -> String: - "Hello, " + name + "!" - -def main() -> (): - let msg = greet("world") - IO.println(msg) ----- - -That's a valid `.rattle` file. Run it: - -[source,bash] ----- -rattle eval hello.rattle ----- - -== Why Python syntax? - -Because Python programmers deserve a sound type system. -RattleScript is Python's more dangerous cousin — same comfortable whitespace- -delimited blocks, but with teeth: no null pointer exceptions, no -use-after-free, no data races, and no runtime surprises that the type -checker already knows about. - -The mascot is a rattlesnake. Python's more dangerous cousin. - -== Surface mappings - -[cols="1,1"] -|=== -|Python surface |AffineScript equivalent - -|`def f(x: T) -> R:` |`fn f(x: T) -> R { ... }` -|`True` / `False` |`true` / `false` -|`None` |`()` -|`and` / `or` / `not` |`&&` / `\|\|` / `!` -|`class Name:` |`type Name` -|`pass` |`()` -|`import a.b` |`use a::b;` -|`from a import b` |`use a::b;` -|`if cond:` / `else:` |`if cond { ... } else { ... }` -|`for x in e:` |`for x in e { ... }` -|`match e:` |`match e { ... }` -|`# comment` |`// comment` -|=== - -See `rattle preview-python-transform ` to inspect the full transform -for any file. - -== Installation - -=== From source (monorepo dev) - -[source,bash] ----- -# From nextgen-languages/affinescript root — build affinescript first: -dune build - -# Then build the rattle wrapper: -cd distributions/rattlescript -just build -# Binary at: target/debug/rattle ----- - -=== Installed release - -[source,bash] ----- -just install -# Installs rattle to ~/.cargo/bin ----- - -=== Standalone GitHub repo - -The standalone https://github.com/hyperpolymath/rattlescript[hyperpolymath/rattlescript] -repo contains a pre-configured distribution with affinescript as a git -submodule. Use that for a self-contained checkout. - -== Usage - -[source,bash] ----- -# Type-check a file -rattle check hello.rattle - -# Evaluate -rattle eval hello.rattle - -# Compile to WASM -rattle compile hello.rattle - -# See the canonical AffineScript generated by the preprocessor -rattle preview-python-transform hello.rattle - -# All affinescript subcommands work — --face python is pre-injected -rattle lint hello.rattle -rattle fmt hello.rattle ----- - -== Relationship to AffineScript - -RattleScript *is* AffineScript — same compiler, same type system, same WASM -output. The only difference is `--face python` is the default. - -The Python face lives in -link:../../lib/python_face.ml[`lib/python_face.ml`] and the error vocabulary -in link:../../lib/face.ml[`lib/face.ml`]. No compiler internals change when -the face changes. - -== Sync strategy - -This directory (`distributions/rattlescript/`) lives inside the affinescript -monorepo. The standalone https://github.com/hyperpolymath/rattlescript[rattlescript] -GitHub repo uses affinescript as a git submodule and contains only: - -* This `distributions/rattlescript/` content (copied at release time) -* Branding assets -* CI workflows - -`just update-affinescript` advances the submodule pointer after each -affinescript release. All language changes, face changes, and bug fixes -come through that single submodule bump — no duplication. diff --git a/affinescript/distributions/rattlescript/build.rs b/affinescript/distributions/rattlescript/build.rs deleted file mode 100644 index 8aa9387..0000000 --- a/affinescript/distributions/rattlescript/build.rs +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -//! build.rs — detect or require the `affinescript` binary path. -//! -//! Checks, in order: -//! 1. `AFFINESCRIPT` env var (explicit override) -//! 2. `affinescript` on PATH (standard install) -//! 3. Sibling build output `../../_build/default/bin/main.exe` (monorepo dev) -//! -//! The resolved path is baked in as the `AFFINESCRIPT_BIN` compile-time env. - -use std::env; -use std::path::Path; - -fn main() { - println!("cargo:rerun-if-env-changed=AFFINESCRIPT"); - - let bin = if let Ok(p) = env::var("AFFINESCRIPT") { - p - } else if which_affinescript() { - "affinescript".to_string() - } else { - // Monorepo dev path: distributions/rattlescript/ → ../../_build/... - let manifest = env::var("CARGO_MANIFEST_DIR").unwrap(); - let dev_path = Path::new(&manifest) - .join("../../_build/default/bin/main.exe") - .canonicalize() - .ok() - .map(|p| p.to_string_lossy().into_owned()); - - match dev_path { - Some(p) => p, - None => { - // Fall back to the name — will fail at runtime with a clear message. - "affinescript".to_string() - } - } - }; - - println!("cargo:rustc-env=AFFINESCRIPT_BIN={bin}"); -} - -fn which_affinescript() -> bool { - std::process::Command::new("which") - .arg("affinescript") - .output() - .map(|o| o.status.success()) - .unwrap_or(false) -} diff --git a/affinescript/distributions/rattlescript/examples/hello.rattle b/affinescript/distributions/rattlescript/examples/hello.rattle deleted file mode 100644 index 93fbbe4..0000000 --- a/affinescript/distributions/rattlescript/examples/hello.rattle +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -# RattleScript — Hello World -# This is a .rattle file (Python-face AffineScript). -# You can also use .pyaff if you prefer. - -def greet(name: String) -> String: - "Hello, " + name + "!" - -def main() -> (): - let msg = greet("world") - IO.println(msg) diff --git a/affinescript/distributions/rattlescript/examples/ownership.rattle b/affinescript/distributions/rattlescript/examples/ownership.rattle deleted file mode 100644 index 4014af2..0000000 --- a/affinescript/distributions/rattlescript/examples/ownership.rattle +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -# RattleScript — Ownership demo -# Python programmers: this looks familiar, but the compiler tracks -# *who owns what* so you never get null-pointer surprises. - -def consume_token(@linear token: Token) -> Receipt: - # token is used exactly once here — the compiler proves it - process(token) - -def safe_divide(a: Int, b: Int) -> Option[Int]: - if b == 0: - None - else: - Some(a / b) - -def show_result(result: Option[Int]) -> (): - match result: - Some(n) => IO.println("Result: " + Int.to_string(n)) - None => IO.println("Division by zero — handled, not crashed") diff --git a/affinescript/distributions/rattlescript/manifest.scm b/affinescript/distributions/rattlescript/manifest.scm deleted file mode 100644 index dd942c7..0000000 --- a/affinescript/distributions/rattlescript/manifest.scm +++ /dev/null @@ -1,24 +0,0 @@ -;;; SPDX-License-Identifier: MPL-2.0 -;;; manifest.scm — Generic Guix manifest for RSR-compliant projects -;;; -;;; Usage: -;;; guix shell -m manifest.scm -;;; - -(specifications->manifest - '(;; Core development tools - "git" - "just" - "nickel" - "curl" - "bash" - "coreutils" - - ;; Documentation - "asciidoctor" - "pandoc" - - ;; Common build dependencies - "openssl" - "zlib" - "pkg-config")) diff --git a/affinescript/distributions/rattlescript/src/main.rs b/affinescript/distributions/rattlescript/src/main.rs deleted file mode 100644 index 1c8fa92..0000000 --- a/affinescript/distributions/rattlescript/src/main.rs +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -//! RattleScript CLI — thin distribution wrapper around AffineScript with -//! Python-face pre-selected. -//! -//! All real work is delegated to the `affinescript` binary. RattleScript -//! adds: -//! -//! - `.rattle` file extension auto-detection (maps to `--face python`) -//! - Branded banner and help text -//! - `rattle` as the CLI entry-point name - -use std::env; -use std::process::{self, Command}; - -const AFFINESCRIPT_BIN: &str = env!("AFFINESCRIPT_BIN"); -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -fn main() { - let args: Vec = env::args().skip(1).collect(); - - if args.is_empty() || args[0] == "--help" || args[0] == "-h" { - print_help(); - return; - } - - if args[0] == "--version" || args[0] == "-V" { - println!("rattle {VERSION} (powered by AffineScript)"); - return; - } - - // Build affinescript command, injecting --face python before any path arg. - // If the user already passes --face, honour their choice. - let has_face = args.iter().any(|a| a == "--face"); - let mut cmd_args: Vec = Vec::with_capacity(args.len() + 2); - - // Subcommand goes first. - let mut rest = args.iter().peekable(); - if let Some(subcommand) = rest.next() { - cmd_args.push(subcommand.clone()); - } - - // Inject --face python after the subcommand (unless user overrides). - if !has_face { - cmd_args.push("--face".to_string()); - cmd_args.push("python".to_string()); - } - - for a in rest { - cmd_args.push(a.clone()); - } - - let status = Command::new(AFFINESCRIPT_BIN) - .args(&cmd_args) - .status() - .unwrap_or_else(|e| { - eprintln!("rattle: could not launch affinescript ({AFFINESCRIPT_BIN}): {e}"); - eprintln!("Check that affinescript is installed and on PATH."); - process::exit(127); - }); - - process::exit(status.code().unwrap_or(1)); -} - -fn print_help() { - println!( - r#"rattle {VERSION} — Python-syntax AffineScript - -USAGE: - rattle [OPTIONS] - -SUBCOMMANDS: - check Type-check a .rattle file - eval Evaluate a .rattle file - compile Compile a .rattle file to WASM - fmt Format a .rattle file - lint Lint a .rattle file - preview-python-transform Show the canonical AffineScript produced by the - Python-face preprocessor (useful for learning) - -FILE EXTENSIONS: - .rattle RattleScript (Python-face AffineScript) — preferred - .pyaff Python-face AffineScript — also accepted - .affine Canonical AffineScript — accepted (bypasses Python-face) - -The Python face maps: - def f(x: T) -> R: → fn f(x: T) -> R {{ ... }} - True / False → true / false - None → () - and / or / not → && / || / ! - import a.b → use a::b; - ...and more. Run `rattle preview-python-transform ` to see the - full transform for any file. - -EXAMPLES: - rattle check hello.rattle - rattle eval hello.rattle - rattle compile --target wasm-gc hello.rattle - rattle preview-python-transform hello.rattle - -Powered by AffineScript -"# - ); -} diff --git a/affinescript/docs/.well-known/dnt-policy.txt b/affinescript/docs/.well-known/dnt-policy.txt deleted file mode 100644 index e58439d..0000000 --- a/affinescript/docs/.well-known/dnt-policy.txt +++ /dev/null @@ -1,18 +0,0 @@ -# Do Not Track Policy for affinescript.dev -# https://www.eff.org/dnt-policy - -This site respects Do Not Track (DNT) browser settings. - -When DNT is enabled: -- No analytics tracking (even with consent) -- No personalization cookies -- No third-party tracking -- Essential cookies only - -To enable DNT: -- Chrome: Settings > Privacy > Send "Do Not Track" -- Firefox: Preferences > Privacy > Tell sites not to track -- Safari: Preferences > Privacy > Ask websites not to track - -Contact: privacy@affinescript.dev -Last Updated: 2026-01-31 diff --git a/affinescript/docs/.well-known/keybase.txt b/affinescript/docs/.well-known/keybase.txt deleted file mode 100644 index 5dac7c7..0000000 --- a/affinescript/docs/.well-known/keybase.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Keybase verification for affinescript.dev -# https://keybase.io/hyperpolymath - -# This file will contain the Keybase verification proof once configured. -# To verify this domain with Keybase: -# 1. Run: keybase prove dns affinescript.dev -# 2. Replace this file with the generated proof - -# Contact: jonathan.jewell@open.ac.uk -# Keybase profile: https://keybase.io/hyperpolymath (coming soon) - -# Placeholder - awaiting Keybase setup diff --git a/affinescript/docs/.well-known/security.txt b/affinescript/docs/.well-known/security.txt deleted file mode 100644 index 21b3a7e..0000000 --- a/affinescript/docs/.well-known/security.txt +++ /dev/null @@ -1,53 +0,0 @@ -# Security Contact Information for affinescript.dev -# RFC 9116 Compliant: https://www.rfc-editor.org/rfc/rfc9116.html -# Consent-Aware HTTP Compatible - -# === Primary Contact === -Contact: mailto:security@affinescript.dev -Contact: https://github.com/hyperpolymath/affinescript/security/advisories/new - -# === Security Policy === -Policy: https://github.com/hyperpolymath/affinescript/security/policy -Policy: https://affinescript.dev/.well-known/security-policy.html - -# === Encryption (PGP) === -Encryption: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk -Encryption: https://github.com/hyperpolymath.gpg -Encryption: https://affinescript.dev/.well-known/pgp-key.asc - -# === Acknowledgments === -Acknowledgments: https://github.com/hyperpolymath/affinescript#security-acknowledgments -Acknowledgments: https://affinescript.dev/security/acknowledgments - -# === Hiring (Security Team) === -Hiring: https://github.com/hyperpolymath/affinescript/blob/main/CONTRIBUTING.md - -# === Preferred Languages === -Preferred-Languages: en - -# === Consent-Aware HTTP Endpoints === -# User Consent Management (GDPR/Privacy Compliance) -# Consent-Endpoint: https://affinescript.dev/api/consent -# Consent-Policy: https://affinescript.dev/privacy#consent-management -# Consent-Categories: essential, functional, analytics, marketing, personalization - -# === Canonical URL === -Canonical: https://affinescript.dev/.well-known/security.txt - -# === CSAF (Common Security Advisory Framework) === -# CSAF: https://affinescript.dev/.well-known/csaf/provider-metadata.json - -# === Expiration (1 year from creation) === -Expires: 2027-01-31T16:43:19Z - -# === Additional Information === -# This site implements: -# - Consent-Aware HTTP (cookie-based consent verification) -# - HTTP Capability Gateway (fine-grained access control) -# - HSTS with preload (max-age=31536000) -# - TLS 1.3 minimum -# - CAA records (Let's Encrypt, DigiCert) -# - SPF, DMARC for email security -# -# Report security issues to: security@affinescript.dev -# For privacy/consent issues: privacy@affinescript.dev diff --git a/affinescript/docs/.well-known/sellers.json b/affinescript/docs/.well-known/sellers.json deleted file mode 100644 index 63b9331..0000000 --- a/affinescript/docs/.well-known/sellers.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "contact_email": "info@affinescript.dev", - "contact_address": "United Kingdom", - "version": "1.0", - "identifiers": [], - "sellers": [] -} diff --git a/affinescript/docs/.well-known/trust.txt b/affinescript/docs/.well-known/trust.txt deleted file mode 100644 index 65194d7..0000000 --- a/affinescript/docs/.well-known/trust.txt +++ /dev/null @@ -1,57 +0,0 @@ -# Trust and Transparency Information for affinescript.dev -# https://github.com/nightwatchcybersecurity/trustandtransparency.org - -# === Organization Information === -Organization: hyperpolymath -Legal Name: Jonathan D.A. Jewell -Country: United Kingdom -Contact: info@affinescript.dev -Website: https://affinescript.dev - -# === Responsible Disclosure === -Security Contact: security@affinescript.dev -Security Policy: https://github.com/hyperpolymath/affinescript/security/policy -Bug Bounty: Not currently available -Encryption: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk - -# === Privacy === -Privacy Policy: https://affinescript.dev/privacy -Data Controller: Jonathan D.A. Jewell -Data Protection Officer: privacy@affinescript.dev -GDPR Compliance: Yes -Consent Management: Consent-Aware HTTP (cookie-based) - -# === Transparency === -Open Source: Yes -License: MPL-2.0 (https://palimpsest.spdx.dev) -Source Code: https://github.com/hyperpolymath/affinescript -Issue Tracker: https://github.com/hyperpolymath/affinescript/issues -Changelog: https://github.com/hyperpolymath/affinescript/releases - -# === Infrastructure === -Hosting: GitHub Pages -CDN: Cloudflare -TLS Provider: Let's Encrypt, DigiCert -DNS: Cloudflare -Analytics: Self-hosted (consent required) - -# === Third-Party Services === -# None currently (all self-hosted or GitHub-hosted) - -# === Compliance & Standards === -WCAG Level: AA (target) -RFC 9116: Compliant (security.txt) -HSTS Preload: Yes -CAA Records: Yes (flag 128) -SPF/DMARC: Yes - -# === Contact Methods === -Email: info@affinescript.dev -GitHub: https://github.com/hyperpolymath/affinescript -Security Email: security@affinescript.dev -PGP Key: https://keys.openpgp.org/search?q=jonathan.jewell@open.ac.uk - -# === Verification === -Last Updated: 2026-01-31 -Verified By: Jonathan D.A. Jewell -Canonical URL: https://affinescript.dev/.well-known/trust.txt diff --git a/affinescript/docs/ALIB-INTEGRATION.adoc b/affinescript/docs/ALIB-INTEGRATION.adoc deleted file mode 100644 index a951d27..0000000 --- a/affinescript/docs/ALIB-INTEGRATION.adoc +++ /dev/null @@ -1,505 +0,0 @@ -== AffineScript ↔ aggregate-library Integration Strategy - -*Date:* 2026-01-23 *Status:* Proposal / Roadmap *Goal:* Leverage aLib -methodology to strengthen AffineScript’s ecosystem position - -=== Executive Summary - -*aggregate-library (aLib)* is a methods repository demonstrating how to -specify, test, and stress-test library overlap across wildly different -systems. AffineScript, with its *affine types, dependent types, and -effect tracking*, represents an extreme case that can: - -[arabic] -. *Stress-test aLib specs* under unique constraints -. *Contribute novel semantics* for operations under affine ownership -. *Demonstrate cross-language compatibility* without sacrificing type -safety -. *Build ecosystem credibility* through conformance - -=== What aLib Provides - -From `+aggregate-library/+`: - -==== Spec Categories - -* *arithmetic*: Basic numeric operations -* *collection*: map, filter, fold, contains -* *comparison*: Ordering and equality -* *conditional*: Boolean logic -* *logical*: AND, OR, NOT operations -* *string*: String manipulation - -==== Each Spec Includes - -[arabic] -. *Interface signature* (language-agnostic) -. *Behavioral semantics* (properties, edge cases) -. *Executable test cases* (YAML conformance vectors) - -==== Philosophy - -* *NOT* a standard library replacement -* *IS* a methodology for cross-ecosystem design -* Enables implementations to share semantics without sharing code -* Emphasizes reversibility and conformance testing - -=== How AffineScript Benefits - -==== 1. Conformance Validation - -*Action:* Implement aLib conformance test runner for AffineScript - -[source,affinescript] ----- -// stdlib/alib_conformance.affine -fn run_collection_map_tests() -> TestResult { - // Load test vectors from aggregate-library/specs/collection/map.md - // Execute against AffineScript stdlib implementation - // Report conformance score -} ----- - -*Benefits:* - Validates stdlib correctness against language-neutral -specs - Provides regression safety during development - Demonstrates -interoperability guarantees - -==== 2. Affine Semantics Contribution - -*Action:* Contribute AffineScript-specific semantics notes to aLib - -*Example - `+map+` under affine constraints:* - -[source,markdown] ----- -# AffineScript Implementation Notes - -## Ownership Semantics -```affinescript -fn map(list: [T], f: T -> U) -> [U] ----- - -*Affine Constraint*: Each element `+T+` consumed exactly once during -map. - -*Properties under Affine Types:* - Source collection `+list+` is moved -(cannot be used after map) - Function `+f+` must consume its argument - -If `+f+` panics, remaining elements are dropped safely - No iterator -invalidation (collection moved, not borrowed) - -*Safety Guarantees:* - Memory leak freedom: All elements processed or -dropped - Use-after-free prevention: Source collection inaccessible - -Double-free prevention: Elements consumed exactly once - -.... - -**Impact:** Shows how affine types provide stronger guarantees than aLib's base specs - -### 3. Stress-Test Case for aLib - -**Action:** Position AffineScript as an "extreme constraint" test case - -**Unique Challenges AffineScript Presents:** - -| aLib Operation | Affine Challenge | Solution | -|---------------|------------------|----------| -| `map(list, f)` | Source consumed | Move semantics, explicit lifetime | -| `filter(list, pred)` | Predicate can't consume | Predicate must be `&T -> Bool` | -| `fold(list, acc, f)` | Accumulator ownership | Explicit `own` or `&mut` types | -| `contains(list, x)` | Search requires equality | Requires `Eq` trait, borrow semantics | - -**Value:** Demonstrates how aLib specs work under memory-safe, move-only semantics - -### 4. Cross-Language Benchmarking - -**Action:** Create `alib-benchmarks/affinescript/` with performance data - -```yaml -# alib-benchmarks/affinescript/collection_map.yml -language: affinescript -implementation: stdlib-0.1.0 -spec: collection/map - -benchmarks: - - name: map_int_array_1000 - input_size: 1000 - iterations: 10000 - time_ns: 42350 - memory_bytes: 4000 - - - name: map_string_array_100 - input_size: 100 - iterations: 5000 - time_ns: 15200 - memory_bytes: 2400 -.... - -*Compare against:* ReScript, OCaml, Rust, JavaScript implementations - -*Insight:* Shows cost/benefit of affine type safety - -==== 5. Ecosystem Implementation: `+alib-for-affinescript+` - -*Action:* Create separate repo `+alib-for-affinescript+` - -*Structure:* - -.... -alib-for-affinescript/ -├── src/ -│ ├── arithmetic.affine # Conformant arithmetic operations -│ ├── collection.affine # Conformant collection operations -│ ├── comparison.affine # Conformant comparison operations -│ ├── string.affine # Conformant string operations -│ └── ... -├── tests/ -│ └── conformance/ # aLib test vectors -│ ├── runner.affine -│ └── vectors/ # Imported from aggregate-library -├── docs/ -│ └── affine-semantics.md # AffineScript-specific notes -└── README.md -.... - -*Goal:* Demonstrate aLib method applied to affine-typed language - -=== Implementation Roadmap - -==== Phase 1: Conformance (Week 1-2) - -* [ ] Import aLib test vectors into AffineScript test suite -* [ ] Implement conformance test runner (YAML → AffineScript tests) -* [ ] Run conformance tests against current stdlib -* [ ] Document conformance gaps - -==== Phase 2: Semantics Contribution (Week 3-4) - -* [ ] Write affine semantics notes for each aLib spec -* [ ] Contribute to `+aggregate-library/notes/affine-types.md+` -* [ ] Submit PRs with AffineScript edge cases -* [ ] Add affine-specific test vectors - -==== Phase 3: Ecosystem Repo (Month 2) - -* [ ] Create `+alib-for-affinescript+` repository -* [ ] Implement all aLib specs with affine constraints -* [ ] Comprehensive documentation of ownership semantics -* [ ] Example projects using aLib-conformant API - -==== Phase 4: Cross-Language Integration (Month 3) - -* [ ] Benchmark AffineScript vs other implementations -* [ ] Create interop examples (AffineScript ↔ ReScript/OCaml) -* [ ] Publish comparison study -* [ ] Present findings to aLib community - -=== Specific Integration Points - -==== 1. AffineScript Stdlib Alignment - -*Current stdlib modules that align with aLib:* - -[width="100%",cols="42%,21%,37%",options="header",] -|=== -|AffineScript Module |aLib Spec |Conformance Status -|`+stdlib/prelude.affine+` |collection/\{map,filter,fold} |Partial -(needs testing) - -|`+stdlib/math.affine+` |arithmetic/* |Good (basic ops) - -|`+stdlib/string.affine+` |string/* |Partial (missing ops) - -|`+stdlib/collections.affine+` |collection/* |Good (comprehensive) -|=== - -*Actions:* 1. Add conformance attributes to stdlib functions: - -[source,affinescript] ----- -/// Conforms to aLib collection/map spec v1.0 -/// Test vectors: aggregate-library/specs/collection/map.md -fn map(arr: [T], f: T -> U) -> [U] { ... } ----- - -[arabic, start=2] -. Generate conformance report: - -[source,bash] ----- -$ affinescript test --alib-conformance -✓ collection/map: 12/12 test vectors pass -✓ collection/filter: 10/10 test vectors pass -✗ collection/fold: 8/12 test vectors pass (4 failures) - - Accumulator ownership semantics differ - - See: docs/affine-semantics.md#fold ----- - -==== 2. Novel Affine Operations - -*Contribute AffineScript-specific operations to aLib:* - -===== `+take_ownership+` - -[source,markdown] ----- -# Operation: take_ownership (Affine-specific) - -## Interface Signature ----- - -take_ownership: Collection[A: affine] -> (A, Collection[A]) - -.... - -## Behavioral Semantics -Removes first element from collection, transferring ownership. -Source collection is modified (mutable borrow). - -**Affine Guarantee**: Element removed exactly once, caller owns result. -.... - -===== `+partition_consume+` - -[source,markdown] ----- -# Operation: partition_consume (Affine-specific) - -## Interface Signature ----- - -partition_consume: Collection[A: affine], Function[&A -> Bool] -> -(Collection[A], Collection[A]) - -.... - -## Behavioral Semantics -Partitions collection into two based on predicate. -All elements consumed exactly once. - -**Affine Guarantee**: No element duplication, all elements accounted for. -.... - -==== 3. Conformance Test Integration - -*Add to AffineScript test suite:* - -[source,affinescript] ----- -// tests/alib_conformance_test.affine - -// Auto-generated from aggregate-library/specs/collection/map.md -fn test_map_conformance() -> TestResult { - // Test case 1: Double each number - let input = [1, 2, 3]; - let output = map(input, fn(x) => x * 2); - assert_eq(output, [2, 4, 6], "map: double numbers"); - - // Test case 2: Empty collection - let empty = []; - let result = map(empty, fn(x) => x * 2); - assert_eq(result, [], "map: empty collection"); - - // ... more test cases from YAML - - Pass -} ----- - -*Automation:* - -[source,bash] ----- -# Generate conformance tests from aLib specs -$ deno task gen-conformance-tests \ - --alib-repo ../aggregate-library \ - --output tests/conformance/ - -Generated 47 conformance tests from aLib specs ----- - -=== Strategic Value - -==== For AffineScript: - -[arabic] -. *Validation* - Proves stdlib correctness against language-neutral -specs -. *Credibility* - Shows serious approach to language design -. *Interoperability* - Eases integration with other aLib-conformant -systems -. *Documentation* - aLib specs serve as reference documentation - -==== For aLib: - -[arabic] -. *Stress Testing* - Affine types push specs to extremes -. *Semantics Enrichment* - Adds ownership/borrowing considerations -. *Safety Model* - Demonstrates specs under memory-safe constraints -. *Diversity* - Adds functional + affine-typed language to portfolio - -==== For Ecosystem: - -[arabic] -. *Pattern Library* - Shows how to handle affine constraints -. *Interop Guide* - AffineScript ↔ other languages via aLib surface -. *Research Value* - Novel combination of affine types + standard -operations - -=== Example: Complete Integration Flow - -==== 1. Start with aLib Spec - -From `+aggregate-library/specs/collection/filter.md+`: - -[source,markdown] ----- -filter: Collection[A], Function[A -> Bool] -> Collection[A] -Preserves order, only includes elements where predicate returns true ----- - -==== 2. Implement in AffineScript (Affine Semantics) - -[source,affinescript] ----- -// stdlib/collections.affine - -/// Conforms to aLib collection/filter v1.0 -/// Affine semantics: Predicate borrows, source moved -fn filter(arr: [T], pred: &T -> Bool) -> [T] { - let result = []; - for x in arr { // arr moved, each x consumed - if pred(&x) { // pred borrows (doesn't consume) - result = result ++ [x]; // x moved into result - } - // else: x dropped here (affine allows drop) - } - result // caller owns result, arr fully consumed -} ----- - -==== 3. Run Conformance Tests - -[source,affinescript] ----- -// tests/conformance/collection_filter.affine - -fn test_alib_filter_conformance() -> TestResult { - // From aLib test vectors - let input = [1, 2, 3, 4, 5]; - let evens = filter(input, fn(x) => x % 2 == 0); - assert_eq(evens, [2, 4], "filter: keep evens"); - - // Affine-specific: input no longer accessible - // Uncommenting next line would be compile error: - // let _ = len(input); // ERROR: value moved - - Pass -} ----- - -==== 4. Document Affine Semantics - -Contribute to `+aggregate-library/notes/affine-types.md+`: - -[source,markdown] ----- -## AffineScript Implementation: filter - -### Ownership Model -- **Source collection**: Moved (consumed) -- **Predicate function**: Borrows element (`&T -> Bool`) -- **Result collection**: Owned by caller -- **Filtered-out elements**: Automatically dropped - -### Why Predicate Borrows -```affinescript -// If predicate consumed element: -fn filter_bad(arr: [T], pred: T -> Bool) -> [T] - // Problem: pred(x) consumes x even if we want to keep it! - // Can't move x into both pred() and result - -// Solution: Predicate borrows -fn filter_good(arr: [T], pred: &T -> Bool) -> [T] - // pred checks without consuming - // We decide whether to move into result or drop ----- - -==== Safety Guarantees - -* No use-after-filter of source -* No double-free of filtered elements -* All elements either moved to result or properly dropped - -.... - -### 5. Benchmark and Compare - -```yaml -# Submitted to aLib benchmarks -spec: collection/filter -implementations: - - lang: affinescript - time_ns: 3500 - memory: 0 # No allocation (in-place filtering) - notes: "Move semantics enable zero-copy filtering" - - - lang: javascript - time_ns: 4200 - memory: 8192 # Allocates new array - notes: "GC handles cleanup" - - - lang: rust - time_ns: 3400 - memory: 0 # Iterator, no allocation - notes: "Similar to AffineScript (ownership semantics)" -.... - -=== Deliverables - -==== Short Term (Month 1) - -* [ ] Conformance test suite integrated -* [ ] Stdlib alignment assessment document -* [ ] Initial affine semantics notes - -==== Medium Term (Month 2-3) - -* [ ] `+alib-for-affinescript+` repository created -* [ ] All aLib specs implemented with affine semantics -* [ ] Contribution to aggregate-library (semantics notes) -* [ ] Benchmark comparison published - -==== Long Term (Month 4-6) - -* [ ] AffineScript cited as aLib stress-test case -* [ ] Cross-language interop examples -* [ ] Research paper: "`Affine Types Meet Common Library Interfaces`" -* [ ] Integration patterns documented - -=== Success Metrics - -[arabic] -. *Conformance*: ≥95% of aLib test vectors pass -. *Contribution*: ≥10 semantics notes contributed to aLib -. *Performance*: Within 20% of Rust implementations (affine-to-affine) -. *Documentation*: Complete affine semantics guide -. *Ecosystem*: ≥3 example projects using aLib-conformant API - -=== Next Steps - -*Immediate (This Week):* 1. Import aLib test vectors into AffineScript -test suite 2. Audit current stdlib for aLib spec alignment 3. Create -conformance test runner prototype - -*Follow-up (Next Week):* 1. Run full conformance test suite 2. Begin -affine semantics documentation 3. Identify gaps in stdlib coverage - -*Strategic (Next Month):* 1. Create `+alib-for-affinescript+` repository -2. Submit first contributions to aggregate-library 3. Begin -cross-language benchmarking - -''''' - -*Author:* Claude Sonnet 4.5 *Review Status:* Awaiting user feedback -*Implementation Priority:* High - positions AffineScript in wider -ecosystem diff --git a/affinescript/docs/BRIDGE-DECISIONS.adoc b/affinescript/docs/BRIDGE-DECISIONS.adoc deleted file mode 100644 index b945201..0000000 --- a/affinescript/docs/BRIDGE-DECISIONS.adoc +++ /dev/null @@ -1,159 +0,0 @@ -== Bridge Layer Decisions - -*Status as of 2026-04-12* - -This document collates all bridge layer architectural decisions for -paint.type, including gossamer_emit clarification, channel workaround, -and Burble LLM role constraints. - -=== Decision Tracking - -==== 1. Gossamer Streaming IPC for Dirty-Rects - -*Status*: CONFIRMED - -*Decision*: Tile dirty-rect notifications use `+gossamer_emit+` -(streaming IPC system) rather than the 256-slot capability registry. - -*Rationale*: - Command traffic and rendering updates must not interfere -- Real-time rendering performance requires dedicated streaming path - -Capability registry handles command traffic only - -*Implementation*: - -.... -native core → DirtyRingBuffer → channel → gossamer_emit → frontend -.... - -*References*: - CONTEXT.adoc § Architecture → Shell layer - CONTEXT.adoc -§ Architecture → Bridge layer - -==== 2. Channel-Based Workaround for Effect Handlers - -*Status*: CONFIRMED (Authoritative Implementation) - -*Decision*: Implement three traffic classes as separate typed channels -due to incomplete effect-handler lowering in WASM targets. - -*Rationale*: - Ephapax effect-handler lowering in WASM targets -incomplete as of 2026-04-12 - Bridge layer cannot block on incomplete -compiler features - Channel-based approach works with current WASM -backend capabilities - Preserves AffineScript’s strong typing guarantees - -*Traffic Classes*: 1. `+channel+` - Latency-critical -(<16ms target) 2. `+channel+` - Throughput-oriented -(coordinates + version only) 3. `+channel+` - Background -operations - -*Implementation Details*: - No pixel data crosses the bridge - -Dirty-rects contain coordinates and version numbers only - Frontend -accesses pixel data via typed region reference (L12 freshness) - Full -implementation: docs/WASM-EFFECT-HANDLER-WORKAROUND.md - -*Migration Path*: - Monitor Ephapax `+.machine_readable/descriptiles/STATE.a2ml+` -- Replace channels with effect handlers when lowering is complete - -Traffic class separation preserved regardless of transport mechanism - -*References*: - CONTEXT.adoc § Critical compiler constraint - -AGENTS.adoc § Common failure modes - -docs/WASM-EFFECT-HANDLER-WORKAROUND.md (comprehensive implementation) - -==== 3. Burble LLM Role Constraints - -*Status*: CONFIRMED - -*Decision*: LLM session participant has tier 1 role with `+chat_send+` -capability only - no canvas access. - -*Rationale*: - Security: Prevent unauthorized canvas modifications - -Architecture: LLM provides decision support, not direct execution - -Collaboration: LLM assists with merge conflict resolution strategies - -*Capabilities*: - `+speak+`, `+listen+`, `+see_presence+` (tier 0 -baseline) - `+chat_send+` (tier 1 addition) - *No*: `+hand_raise+`, -`+mute_self+`, `+CanvasSurface+` capability token - -*Primary Actions*: - Merge conflict proposal (strategy suggestion only) -- Session context maintenance (decisions, intent, unresolved items) - -Direct question answering in session channel - Session summary -generation - -*Implementation*: - BoJ NeSy+Agent cartridge ordered at session open - -Order ticket format: Scheme (written to BoJ at session open) - Boundary -types: `+NeuroSymbolicHandle+`, `+AgentSession+`, -`+CollaborationContext+` - `+CanvasBridge+` proven-server type not used - -*References*: - CONTEXT.adoc § Architecture → Collaboration layer - -SPEC-PROTOCOL.adoc § llm-channel.bop - AGENTS.adoc § Common failure -modes → "`LLM participant has canvas capability`" - -=== Decision Matrix - -[width="100%",cols="21%,15%,31%,33%",options="header",] -|=== -|Decision |Status |Implementation |Migration Path -|Gossamer streaming IPC |CONFIRMED |`+gossamer_emit+` for dirty-rects -|None (permanent) - -|Channel workaround |CONFIRMED |Three typed channels |Effect handlers -when ready - -|Burble LLM constraints |CONFIRMED |Tier 1, no canvas access |None -(permanent) -|=== - -=== Change Log - -==== 2026-04-12 - Initial Decisions - -* All three decisions confirmed as authoritative -* Channel workaround documentation completed -* Integration with paint.type architecture verified - -==== Future Updates - -* Channel → Effect handler migration: When Ephapax STATE.a2ml confirms -completion -* Performance optimizations: As metrics are gathered from real-world -usage -* Additional traffic classes: Only if new requirements emerge (unlikely) - -=== Verification Checklist - -Use this checklist when implementing or modifying bridge layer code: - -* [ ] Dirty-rects use `+gossamer_emit+`, not capability registry -* [ ] Three traffic classes remain on separate typed channels -* [ ] No pixel data crosses the bridge -* [ ] Dirty-rect path: native core → DirtyRingBuffer → channel → -gossamer_emit → frontend -* [ ] Brush command latency <16ms -* [ ] LLM participant has no canvas capability token -* [ ] BoJ order ticket written correctly at session open -* [ ] All SPDX headers present -* [ ] No banned languages or patterns used - -=== Status Definitions - -*CONFIRMED*: Decision is authoritative and implemented. Do not change -without project-wide discussion. - -*PENDING*: Decision is proposed but not yet implemented or ratified. - -*DEPRECATED*: Decision has been superseded by a newer approach. - -=== Related Documents - -* `+CONTEXT.adoc+` - Overall project context and architecture -* `+SPEC-ABI.adoc+` - Authoritative typed-wasm region schemas -* `+SPEC-PROTOCOL.adoc+` - Network-facing message schemas -* `+AGENTS.adoc+` - Coding AI constraints and common failure modes -* `+docs/WASM-EFFECT-HANDLER-WORKAROUND.md+` - Comprehensive channel -implementation - -=== Maintenance - -This document is maintained as part of the bridge layer. Updates -require: 1. Discussion in project channels 2. Reference to authoritative -sources (CONTEXT.adoc, SPEC files) 3. Status change rationale documented -4. Verification checklist updated if needed diff --git a/affinescript/docs/CITATIONS.adoc b/affinescript/docs/CITATIONS.adoc deleted file mode 100644 index 0bef7b2..0000000 --- a/affinescript/docs/CITATIONS.adoc +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= RSR-template-repo - Citation Guide -:toc: - -== BibTeX - -[source,bibtex] ----- -@software{rsr-template-repo_2025, - author = {Polymath, Hyper}, - title = {RSR-template-repo}, - year = {2025}, - url = {https://github.com/hyperpolymath/RSR-template-repo}, - license = {AGPL-3.0-or-later} -} ----- - -== Harvard Style - -Polymath, H. (2025) _RSR-template-repo_ [Computer software]. Available at: https://github.com/hyperpolymath/RSR-template-repo - -== OSCOLA - -Hyper Polymath, 'RSR-template-repo' (2025) - -== MLA - -Polymath, Hyper. "RSR-template-repo." 2025, github.com/hyperpolymath/RSR-template-repo. - -== APA 7 - -Polymath, H. (2025). _RSR-template-repo_ [Computer software]. GitHub. https://github.com/hyperpolymath/RSR-template-repo - -== See Also - -* link:../CITATION.cff[CITATION.cff] -* link:../codemeta.json[codemeta.json] diff --git a/affinescript/docs/CNAME b/affinescript/docs/CNAME deleted file mode 100644 index 71fb6c2..0000000 --- a/affinescript/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -affinescript.dev diff --git a/affinescript/docs/DESIGN-VISION.adoc b/affinescript/docs/DESIGN-VISION.adoc deleted file mode 100644 index ee351ee..0000000 --- a/affinescript/docs/DESIGN-VISION.adoc +++ /dev/null @@ -1,257 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript: Design Vision -Jonathan D.A. Jewell -:toc: -:toclevels: 3 -:icons: font - -== What AffineScript Is - -AffineScript is a *web programming language*. Its purpose is to replace the -fragmented x-script ecosystem — JavaScript, TypeScript, ReScript, CoffeeScript, -ActionScript, and their successors — with a single language that solves the -problems they collectively fail to solve, without inheriting their compromises. - -It compiles to *typed WebAssembly*. Not JavaScript. Not JavaScript-with-types. -WebAssembly, where the type guarantees survive into the runtime. - -== The Core Identity - -AffineScript is *affine-first*. This is its permanent resting state, not a -stepping stone toward something stricter. A resource can be used at most once -— that is the natural condition of the language. Unrestricted use is the -explicit opt-in. Linear (exactly once, must be used) is a stricter annotation -available when needed. - -This is different from Ephapax, where affine is the enabler of linear. -In AffineScript, affine IS the goal. The language rests here. - -== What Is Wrong With X-Scripts - -The entire x-script world has six unsolved problems: - -[cols="1,3", options="header"] -|=== -| Problem | What it looks like - -| *Null is a lie* -| `string \| null \| undefined` — two different nothings. `?.` and `??` are - band-aids that confirm the wound. `strictNullChecks` is opt-in. - -| *Mutation is invisible* -| Everything is mutable by default. You pass an object into a function and it - may come back changed, or something may hold a reference and change it later. - No type system has a model for this. - -| *Errors throw sideways* -| `throw` accepts anything. `catch (e: unknown)` means you know nothing. - Every function that can fail either lies in its type signature or forces - untyped exception handling. - -| *Async is bolted on in layers* -| Callbacks → Promises → async/await → hooks → signals. Each layer was added - because the previous one was insufficient. None composes. `async` infects - everything it touches. - -| *Complex types become unreadable* -| TypeScript generics with `infer`, conditional types, and mapped types produce - code nobody can read six months later. - -| *The runtime is still JavaScript* -| Types are erased. Runtime errors that should be compile errors still happen. - The type system floats above the execution, not in it. -|=== - -== How AffineScript Answers Each - -[cols="1,2,2", options="header"] -|=== -| Problem | X-Script answer | AffineScript answer - -| Null -| `T \| null \| undefined`, `?.`, `??` -| `Option[T]`. No null. No undefined. No two different nothings. - -| Mutation -| Nothing. `const` does not help. `readonly` is partial. -| `own`/`ref`/`mut` as surface syntax for quantity annotations. Ownership - is in the type. You know from the signature what a function does. - -| Errors -| Untyped `throw`/`catch` -| `Result[T, E]`. Errors are in the return type. Nothing is hidden. - -| Async/effects -| async/await + hooks + observables (incompatible) -| Algebraic effects. One model for all of: DOM, fetch, localStorage, timers, - websockets, state. Composable by design. - -| Complex types -| `infer`, conditional types, mapped types -| Row polymorphism. `{ name: String, ..r }` accepts any record with a name - field. Structural, readable, compositional. - -| Runtime -| Types erased at runtime -| Typed WASM. Types survive into the runtime. The guarantees are real. -|=== - -== The Type System - -=== Affine-First - -Resources are affine by default: used at most once. This prevents the entire -class of bugs where a reference is aliased and mutated through multiple paths. - -[source] ----- -own T — caller transfers ownership (affine: must not use after) -ref T — borrowed view, read-only (unrestricted: can use many times) -mut T — exclusive mutable access (affine: one writer, enforced) ----- - -These are surface syntax for the underlying quantity annotation system (QTT). -There is no separate borrow checker. The affine type system handles it. - -=== Structural / Row-Polymorphic - -Records are structural, not nominal. A function that operates on -`{ x: Int, ..r }` accepts any record with an `x` field. Type aliases give -names to shapes — they do not create new nominal types. - -[source] ----- -// Accepts any record that has a name field — nothing else required -fn get_name[..r](obj: { name: String, ..r }) -> String = obj.name - -// Type alias — a name for a shape, not a new nominal type -type point = { x: Int, y: Int } ----- - -This fits the web naturally. JSON is structural. APIs return open shapes. -Row polymorphism models web data correctly without the complexity of -TypeScript's mapped and conditional types. - -=== Algebraic Effects - -The web is effects. Every interaction with the browser — reading the DOM, -making a network call, writing to storage, receiving a timer event — is an -effect. AffineScript gives all of these a single, composable model. - -[source] ----- -effect Http { - fn get(url: String) -> Response; - fn post(url: String, body: String) -> Response; -} - -effect Async { - fn await[T](p: Promise[T]) -> T; -} - -// Effects are in the type signature. Compose freely. -fn fetch_user(id: Int) -{Http + Async}-> User { - let resp = Http.get("/users/" ++ show(id)); - Async.await(resp.json()) -} ----- - -The handler — installed at the boundary of your application — is the affine -resource. Individual invocations are capability uses. No `async` keyword -contamination. No incompatible effect libraries. One model. - -=== No Null, Typed Errors - -[source] ----- -// Nothing is hidden -fn divide(a: Int, b: Int) -> Result[Int, DivisionByZero] { - if b == 0 { Err(DivisionByZero) } else { Ok(a / b) } -} - -// No null, no undefined -fn find_user(id: Int) -> Option[User] ----- - -== Ergonomics: The Real Ambition - -AffineScript's type system is not its primary pitch. The pitch is: -*this should be easier to write than JavaScript, Python, or pseudocode.* - -That is the hard problem. The goal is that a JavaScript developer, a Python -developer, or someone who has never programmed before can engage with this -language without first needing to understand type theory. - -The rules of the type system should be invisible when you are writing -straightforward code. You should feel them only at the edges — when you try to -use a resource twice, when you try to pass `null` where a `String` is expected, -when you try to call a function that can fail without handling the failure. - -The type system enforces what good programmers already do instinctively. -AffineScript just makes it impossible to forget. - -=== What Familiar Looks Like - -[cols="1,1,1", options="header"] -|=== -| Pseudocode / Python | JavaScript | AffineScript - -| `x = 5` -| `const x = 5` -| `let x = 5` - -| `def greet(name):` -| `function greet(name) {` -| `fn greet(name: String) -> String {` - -| `if x > 0: return "yes"` -| `if (x > 0) return "yes";` -| `if x > 0 { "yes" } else { "no" }` - -| `result = maybe_thing or default` -| `result ?? default` -| `result.unwrap_or(default)` - -| `try: ... except: ...` -| `try { ... } catch(e) { ... }` -| `match divide(a, b) { Ok(v) -> v, Err(_) -> 0 }` -|=== - -The goal: the AffineScript column should feel like the obvious way to write -the thing, not like a translation from a foreign language. - -== Compilation Target - -Typed WebAssembly (WasmGC). Not JavaScript. The compiler produces WASM -modules where the type information survives into the runtime. This is the -right target for the next generation of web applications, browser games, -and edge compute. - -A thin JavaScript interop layer exists for calling Web APIs and for incremental -adoption from TypeScript codebases, but it is interop — not the target. - -== What AffineScript Is NOT - -* Not a JavaScript variant. Not "TypeScript with better types." A clean break. -* Not Rust for the web. Rust's borrow checker is a solution to a problem - AffineScript does not have (because it has affine types instead). -* Not a functional-programming language that happens to have a WASM backend. - It is a web language that happens to have a sound type system. -* Not Ephapax. Ephapax is affine-as-enabler-of-linear. AffineScript is - affine-as-home. Different purpose, different audience, different character. - -== Relationship to Ephapax - -Independent. Neither language constrains the other's design. - -They converge at typed WebAssembly. An AffineScript module and an Ephapax -module compiled to typed WASM can interoperate at the binary level — because -they speak the same bytecode, with agreed type layout conventions. - -The aggregate library lives at that convergence point: shared WASM-level -infrastructure (ABI conventions, stdlib types in typed WASM, linking -conventions) that neither language owns but both can consume. - -See `docs/specs/AGGREGATE-LIBRARY.adoc` and the `typed-wasm` repository for -the convergence infrastructure. diff --git a/affinescript/docs/EFFECTS-IMPLEMENTATION.adoc b/affinescript/docs/EFFECTS-IMPLEMENTATION.adoc deleted file mode 100644 index f2ea9e5..0000000 --- a/affinescript/docs/EFFECTS-IMPLEMENTATION.adoc +++ /dev/null @@ -1,100 +0,0 @@ -== Effect Handler Implementation - -=== Status Sync (2026-04-12) - -This document tracks the implementation status of algebraic effects in -AffineScript and is aligned with `+.machine_readable/descriptiles/STATE.a2ml+`. - -=== Syntax (Current Parser Form) - -==== Effect Declaration - -[source,affinescript] ----- -effect Console { - fn print(s: String) -> (); - fn read() -> String; -} ----- - -==== Effect Annotation in Function Types - -[source,affinescript] ----- -fn greet() -{Console}-> String { - Console.print("Hello!"); - Console.read() -} ----- - -==== Effect Handlers - -[source,affinescript] ----- -handle greet() { - print(s) => { - resume("ok") - }, - read() => { - resume("Alice") - }, - return(x) => x -} ----- - -=== Current Implementation - -==== Implemented - -[arabic] -. Effect declarations and operation signatures. -. Effect operations as builtins that raise `+PerformEffect+`. -. Handler operation matching (`+HandlerOp+`). -. Return-arm handling (`+HandlerReturn+`). -. `+resume+` plumbing in the interpreter path. - -==== Backend Caveats - -[arabic] -. *Interpreter path*: effect handlers are implemented and usable. -. *WASM 1.0 backend*: handler lowering is partial; advanced handler -semantics do not fully map to backend continuations. -. *WASM GC backend*: `+handle+` / `+resume+` are currently rejected with -`+UnsupportedFeature+` where continuation semantics are required. - -=== Technical Notes - -The interpreter represents effect operations via: - -[source,ocaml] ----- -type eval_error = - | ... - | PerformEffect of string * value list ----- - -Handler evaluation catches `+PerformEffect+`, selects a matching arm, -and evaluates the arm body in the handler context. - -=== Not Yet Complete - -[arabic] -. Full continuation semantics in Wasm backends without relying on -interpreter behavior. -. A backend strategy for multi-resume semantics when needed. -. End-to-end parity between interpreter and all codegen targets for -advanced handler/control-flow combinations. - -=== Testing - -See: - -* `+test/e2e/fixtures/effects.affine+` -* `+test/test_e2e.ml+` - -Run: - -[source,bash] ----- -dune runtest ----- diff --git a/affinescript/docs/README.adoc b/affinescript/docs/README.adoc deleted file mode 100644 index 5676ac9..0000000 --- a/affinescript/docs/README.adoc +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2026 Hyperpolymath Contributors - -= AffineScript Documentation Directory - -This directory contains all project documentation organized by purpose. - -== Structure - -[source] ----- -docs/ -├── README.adoc # This file -├── specs/ # Language specifications -│ ├── affinescript-spec.md # Complete language specification -│ └── SPEC.md # Core specification -├── governance/ # Community and process docs -│ ├── CODE_OF_CONDUCT.md # Community standards -│ ├── CONTRIBUTING.adoc # How to contribute -│ ├── MAINTAINERS.adoc # Project maintainers -│ └── SECURITY.md # Security policy -├── standards/ # Design & planning docs -│ ├── ROADMAP.md # Development roadmap -│ ├── DECISIONS.md # Architecture decisions -│ ├── RELEASE.md # Release procedures -│ └── PALIMPSEST.adoc # License integration -└── guides/ # Testing and guides - ├── TESTING-REPORT.adoc # Test coverage report - └── TESTING-REPORT.scm # Machine-readable test data ----- - -== Documentation Categories - -=== Language Specifications (`specs/`) - -**For language designers and implementers.** - -* link:specs/affinescript-spec.md[AffineScript Specification] - Complete language spec -* link:specs/SPEC.md[Core Specification] - Core language features - -=== Governance (`governance/`) - -**For contributors and community members.** - -* link:governance/CODE_OF_CONDUCT.md[Code of Conduct] - Community behavior standards -* link:governance/CONTRIBUTING.adoc[Contributing Guide] - How to contribute code/docs -* link:governance/MAINTAINERS.adoc[Maintainers] - Who maintains what -* link:governance/SECURITY.md[Security Policy] - How to report vulnerabilities - -=== Standards & Planning (`standards/`) - -**For understanding project direction and decisions.** - -* link:standards/ROADMAP.md[Roadmap] - Development roadmap and milestones -* link:standards/DECISIONS.md[Architecture Decisions] - Key design choices -* link:standards/RELEASE.md[Release Process] - How releases are made -* link:standards/PALIMPSEST.adoc[Palimpsest Integration] - License integration - -=== Guides (`guides/`) - -**For testing and quality assurance.** - -* link:guides/TESTING-REPORT.adoc[Testing Report] - Human-readable test coverage -* link:guides/TESTING-REPORT.scm[Testing Report (SCM)] - Machine-readable test data - -== Quick Links by Role - -=== I want to... - -**...understand AffineScript** -→ Start with link:specs/affinescript-spec.md[Language Specification] - -**...contribute code** -→ Read link:governance/CONTRIBUTING.adoc[Contributing Guide] - -**...report a security issue** -→ Follow link:governance/SECURITY.md[Security Policy] - -**...understand design decisions** -→ See link:standards/DECISIONS.md[Architecture Decisions] - -**...check the roadmap** -→ Review link:standards/ROADMAP.md[Roadmap] - -**...see test coverage** -→ Browse link:guides/TESTING-REPORT.adoc[Testing Report] - -== Contributing to Documentation - -Documentation improvements are always welcome! See link:governance/CONTRIBUTING.adoc[Contributing Guide] for: - -* Style guidelines -* Review process -* Documentation testing - -## External Resources - -* **Project Repository**: https://github.com/hyperpolymath/affinescript -* **Issue Tracker**: https://github.com/hyperpolymath/affinescript/issues -* **Discussions**: https://github.com/hyperpolymath/affinescript/discussions - ---- - -**Last updated:** 2026-01-23 diff --git a/affinescript/docs/WASM-EFFECT-HANDLER-WORKAROUND.adoc b/affinescript/docs/WASM-EFFECT-HANDLER-WORKAROUND.adoc deleted file mode 100644 index 0aac788..0000000 --- a/affinescript/docs/WASM-EFFECT-HANDLER-WORKAROUND.adoc +++ /dev/null @@ -1,398 +0,0 @@ -== WASM Effect Handler Lowering Workaround - -*Status as of 2026-04-12*: Effect-handler lowering in WASM targets is -incomplete. This document describes the workaround implementation for -the bridge layer. - -*paint.type Integration*: This workaround is the authoritative -implementation for paint.type’s bridge layer as documented in the -project’s CONTEXT.adoc and AGENTS.adoc files. - -=== Problem Statement - -The AffineScript compiler’s effect-handler lowering to WASM is not yet -complete. This means: - -[arabic] -. Effect handlers declared in AffineScript source code are parsed and -type-checked -. Effect operations work in the interpreter -. WASM codegen cannot yet properly lower effect handlers with `+resume+` -semantics -. The bridge layer cannot rely on effect handlers compiling correctly to -WASM - -=== Workaround Solution - -Instead of using effect handlers directly, implement the three traffic -classes as separate typed channels: - -==== Traffic Classes (paint.type Canonical) - -[arabic] -. *Brush Commands*: Latency-critical stroke and tool events (<16ms -target) -. *Tile Dirty-Rects*: Throughput-oriented dirty-rect notifications -(coordinates + version only) -. *Async Operations*: Background file IO, undo state, plugin events - -*Authoritative Reference*: paint.type CONTEXT.adoc § Architecture → -Bridge layer - -==== Implementation Approach - -[source,affinescript] ----- -// Instead of effect handlers: -effect BrushCommands { - fn draw_line(x1: Int, y1: Int, x2: Int, y2: Int) -> (); - fn fill_rect(x: Int, y: Int, w: Int, h: Int) -> (); -} - -handle brush_effects() { - draw_line => { /* handler impl */ } - fill_rect => { /* handler impl */ } -} - -// Use separate channels: -type BrushCommand - = DrawLine(Int, Int, Int, Int) - | FillRect(Int, Int, Int, Int) - | ClearScreen(Color); - -type TileDirtyRect = {x: Int, y: Int, width: Int, height: Int}; -type AsyncOperation = {id: String, payload: Json}; - -// Create typed channels -let brush_channel = channel_create(); -let tile_channel = channel_create(); -let async_channel = channel_create(); - -// Send operations via channels instead of effect operations -channel_send(brush_channel, DrawLine(0, 0, 100, 100)); -channel_send(tile_channel, {x: 10, y: 20, width: 50, height: 50}); -channel_send(async_channel, {id: "load_texture", payload: {"path": "assets/texture.png"}}); ----- - -=== Bridge Layer Architecture (paint.type) - -.... -┌───────────────────────────────────────────────────────┐ -│ Frontend Layer (Plain JS) │ -└───────────────────────────────────────────────────────┘ - │ - ▼ -┌───────────────────────────────────────────────────────┐ -│ Bridge Layer (AffineScript) │ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────┐ │ -│ │ BrushCommand │ │ TileDirtyRect │ │ Async│ │ -│ │ Channel │ │ Channel │ │ Op │ │ -│ │ (<16ms latency) │ │ (throughput) │ │ Chan │ │ -│ └─────────────────┘ └─────────────────┘ └─────┘ │ -│ │ -│ gossamer_emit → frontend (dirty-rects only) │ -│ Capability registry → frontend (commands only) │ -└───────────────────────────────────────────────────────┘ - │ - ▼ -┌───────────────────────────────────────────────────────┐ -│ Native Core (Ephapax) │ -│ │ -│ DirtyRingBuffer → channel → gossamer_│ -│ Tile handles: let! (linear) — compiler enforced │ -│ No &mut — consume-and-reproduce only │ -└───────────────────────────────────────────────────────┘ -.... - -*Authoritative Reference*: paint.type CONTEXT.adoc § Architecture - -=== Channel Implementation Details - -==== Brush Command Channel - -[source,affinescript] ----- -// Create channel -type BrushChannel = channel; - -// Sender side (application) -fn send_brush_command(ch: BrushChannel, cmd: BrushCommand) -> () { - channel_send(ch, cmd); -} - -// Receiver side (bridge layer) -fn process_brush_commands(ch: BrushChannel) -> () { - while let Some(cmd) = channel_recv(ch) { - match cmd { - DrawLine(x1, y1, x2, y2) => { - // Call WASM-exported function - wasm_export_draw_line(x1, y1, x2, y2); - } - FillRect(x, y, w, h) => { - wasm_export_fill_rect(x, y, w, h); - } - ClearScreen(color) => { - wasm_export_clear_screen(color); - } - } - } -} ----- - -==== Tile Dirty-Rect Channel - -[source,affinescript] ----- -type TileChannel = channel; - -fn send_dirty_rect(ch: TileChannel, rect: TileDirtyRect) -> () { - channel_send(ch, rect); -} - -fn process_dirty_rects(ch: TileChannel) -> () { - while let Some(rect) = channel_recv(ch) { - wasm_export_mark_dirty(rect.x, rect.y, rect.width, rect.height); - } -} ----- - -==== Async Operation Channel - -[source,affinescript] ----- -type AsyncChannel = channel; - -fn send_async_op(ch: AsyncChannel, op: AsyncOperation) -> () { - channel_send(ch, op); -} - -fn process_async_ops(ch: AsyncChannel) -> () { - while let Some(op) = channel_recv(ch) { - match op.id { - "load_texture" => { - let path = op.payload["path"].as_string(); - wasm_export_load_texture(path); - } - "save_game" => { - let data = op.payload["data"]; - wasm_export_save_game(data); - } - // ... other async operations - } - } -} ----- - -=== Integration with Existing Code - -==== Before (Using Effect Handlers - Not Working in WASM) - -[source,affinescript] ----- -effect GameIO { - fn draw_line(x1: Int, y1: Int, x2: Int, y2: Int) -> (); - fn load_texture(path: String) -> Texture; - fn mark_dirty(x: Int, y: Int, w: Int, h: Int) -> (); -} - -fn game_loop() -{GameIO}-> () { - draw_line(0, 0, 100, 100); - let texture = load_texture("player.png"); - mark_dirty(10, 20, 50, 50); -} - -// This would require complete effect handler lowering to WASM ----- - -==== After (Using Channels - Working Workaround) - -[source,affinescript] ----- -fn game_loop(brush_ch: BrushChannel, tile_ch: TileChannel, async_ch: AsyncChannel) -> () { - // Send brush command - channel_send(brush_ch, DrawLine(0, 0, 100, 100)); - - // Send async operation - channel_send(async_ch, { - id: "load_texture", - payload: {"path": "player.png"} - }); - - // Send dirty rect - channel_send(tile_ch, {x: 10, y: 20, width: 50, height: 50}); -} - -// Spawn processing tasks -fn main() -> () { - let brush_ch = channel_create(); - let tile_ch = channel_create(); - let async_ch = channel_create(); - - // Spawn processing tasks - task_spawn(process_brush_commands, brush_ch); - task_spawn(process_dirty_rects, tile_ch); - task_spawn(process_async_ops, async_ch); - - // Run game loop - game_loop(brush_ch, tile_ch, async_ch); -} ----- - -=== Performance Considerations - -==== Channel vs Effect Handler Overhead - -[cols=",,",options="header",] -|=== -|Approach |Overhead |Status -|Effect Handlers |Low (direct calls) |Not working in WASM -|Channels |Medium (queue + dispatch) |Working workaround -|Direct WASM exports |Lowest (FFI calls) |Requires manual binding -|=== - -==== paint.type Specific Requirements - -*Brush Command Latency*: <16ms round-trip target *Tile Dimensions*: -256×256 pixels (fixed) *Dirty-Rect Path*: -`+native core → DirtyRingBuffer → channel → gossamer_emit → frontend+` -*Freshness Protocol*: L12 typed-wasm freshness semantics with -`+is_fresh()+` checks - -==== Optimization Strategies - -[arabic] -. *Batch Processing*: Group multiple commands into single messages -. *Priority Channels*: Use separate channels for high/low priority -operations -. *Zero-Copy*: Use shared memory where possible instead of message -passing -. *Bulk Operations*: Implement batch versions of operations - -[source,affinescript] ----- -type BatchBrushCommand = {commands: Array}; - -fn send_batch(ch: channel, commands: Array) -> () { - channel_send(ch, {commands: commands}); -} ----- - -=== Future Migration Path - -When effect handler lowering is complete: - -[arabic] -. *Gradual Migration*: Replace channel-based code with effect handlers -incrementally -. *Compatibility Layer*: Maintain channel interfaces as fallback -. *Feature Detection*: Runtime detection of effect handler support -. *Benchmarking*: Compare performance before full migration - -[source,affinescript] ----- -#[cfg(effect_handlers_available)] -fn game_loop() -{GameIO}-> () { - // Use effect handlers when available - draw_line(0, 0, 100, 100); -} - -#[cfg(not(effect_handlers_available))] -fn game_loop(channels: GameChannels) -> () { - // Fall back to channels - channel_send(channels.brush, DrawLine(0, 0, 100, 100)); -} ----- - -=== Documentation Requirements - -==== For Coding AIs and Developers - -[arabic] -. *Clear Warning*: Document that effect handlers don’t work in WASM yet -. *Recommended Pattern*: Show the channel-based workaround -. *Examples*: Provide working examples of both approaches -. *Status Tracking*: Link to issue tracking effect handler -implementation - -==== Example Documentation Snippet - -[source,markdown] ----- -⚠️ **Important**: As of 2026-04-12, effect handlers are not fully supported in WASM targets. - -**Workaround**: Use typed channels for the three traffic classes: -- Brush commands: `channel` -- Tile dirty-rects: `channel` -- Async operations: `channel` - -See `docs/WASM-EFFECT-HANDLER-WORKAROUND.md` for complete implementation details. ----- - -=== Testing and Validation - -==== Test Coverage Requirements - -[arabic] -. *Channel Reliability*: Test message passing under load -. *Error Handling*: Test channel full/closed scenarios -. *Performance*: Benchmark channel vs future effect handler -implementation -. *Interoperability*: Test with different WASM runtimes - -==== Validation Checklist - -* [ ] Brush command channel handles all rendering operations -* [ ] Tile dirty-rect channel processes updates correctly -* [ ] Async operation channel handles error cases -* [ ] Performance meets paint.type requirements (<16ms latency for brush -commands) -* [ ] Dirty-rect notifications follow full path: -`+native core → DirtyRingBuffer → channel → gossamer_emit → frontend+` -* [ ] No pixel data crosses the bridge (coordinates and version numbers -only) -* [ ] Freshness protocol implemented with `+is_fresh()+` checks -* [ ] Memory usage stays within bounds -* [ ] Works across all target WASM runtimes - -=== Authoritative Status - -This document represents the *authoritative implementation* for -paint.type’s bridge layer as specified in: - -* `+CONTEXT.adoc+` § Critical compiler constraint -* `+CONTEXT.adoc+` § Architecture → Bridge layer -* `+AGENTS.adoc+` § Common failure modes → "`The bridge layer blocks on -effect-handler lowering`" - -*Do not work around the workaround*: The channel-based architecture is -intentional and correct, not technical debt. - -=== Conclusion - -This workaround provides a robust solution that: - -[arabic] -. *Works Today*: Uses only features that are currently working in WASM -. *Maintains Type Safety*: Preserves AffineScript’s type guarantees -. *Enables Progress*: Allows bridge layer development to continue -. *Clear Migration Path*: Can be replaced with effect handlers when -ready -. *Authoritative*: Aligns perfectly with paint.type’s architectural -requirements - -The channel-based approach is a proven pattern that will serve well -until effect handler lowering is complete. - -=== Migration Trigger - -*When to migrate*: Only when Ephapax -`+.machine_readable/descriptiles/STATE.a2ml+` confirms effect-handler lowering in -WASM targets is complete. - -*What changes*: Transport mechanism only (channels → effect handlers). -Traffic class separation and type signatures remain identical. - -*What stays the same*: - Three traffic classes - No pixel data crossing -the bridge - Dirty-rect path through gossamer_emit - <16ms latency -target for brush commands - L12 freshness protocol diff --git a/affinescript/docs/academic/README.adoc b/affinescript/docs/academic/README.adoc deleted file mode 100644 index 344e59d..0000000 --- a/affinescript/docs/academic/README.adoc +++ /dev/null @@ -1,129 +0,0 @@ -== AffineScript Academic Documentation - -This directory contains formal academic documentation for the -AffineScript programming language, including proofs, specifications, -white papers, and mechanized verification. - -=== Document Index - -==== Proofs and Metatheory - -[width="100%",cols="34%,41%,25%",options="header",] -|=== -|Document |Description |Status -|link:proofs/type-soundness.md[Type System Soundness] |Progress and -preservation theorems |Complete - -|link:proofs/quantitative-types.md[Quantitative Type Theory] |Linearity -and quantity proofs |Complete - -|link:proofs/effect-soundness.md[Effect Soundness] |Algebraic effects -metatheory |Complete - -|link:proofs/ownership-soundness.md[Ownership Soundness] |Affine/linear -type safety |Complete - -|link:proofs/row-polymorphism.md[Row Polymorphism] |Extensible records -metatheory |Complete - -|link:proofs/dependent-types.md[Dependent Types] |Indexed types and -refinements |Complete -|=== - -==== White Papers - -[width="100%",cols="44%,56%",options="header",] -|=== -|Document |Description -|link:white-papers/language-design.md[Language Design] |Design rationale -and related work - -|link:white-papers/type-system.md[Type System Design] |Bidirectional -typing with quantities - -|link:white-papers/effect-system.md[Effect System Design] |Algebraic -effects and handlers -|=== - -==== Formal Verification - -[width="100%",cols="34%,41%,25%",options="header",] -|=== -|Document |Description |Status -|link:formal-verification/operational-semantics.md[Operational -Semantics] |Small-step semantics |Complete - -|link:formal-verification/denotational-semantics.md[Denotational -Semantics] |Domain-theoretic model |Complete - -|link:formal-verification/axiomatic-semantics.md[Axiomatic Semantics] -|Hoare logic for AffineScript |Complete -|=== - -==== Mathematical Foundations - -[width="100%",cols="44%,56%",options="header",] -|=== -|Document |Description -|link:mathematical-foundations/categorical-semantics.md[Categorical -Semantics] |Category theory models - -|link:mathematical-foundations/logic-foundations.md[Logic Foundations] -|Curry-Howard and proof theory - -|link:mathematical-foundations/complexity-analysis.md[Complexity -Analysis] |Decidability and complexity bounds -|=== - -==== Mechanized Proofs - -[width="100%",cols="34%,41%,25%",options="header",] -|=== -|Document |Description |Status -|link:mechanized/coq/README.md[Coq Formalization] |Coq proof development -|Stub - -|link:mechanized/lean/README.md[Lean Formalization] |Lean 4 proof -development |Stub - -|link:mechanized/agda/README.md[Agda Formalization] |Agda proof -development |Stub -|=== - -=== Citation - -[source,bibtex] ----- -@misc{affinescript2024, - title = {AffineScript: A Quantitative Dependently-Typed Language with Algebraic Effects}, - author = {AffineScript Contributors}, - year = {2024}, - howpublished = {\url{https://github.com/hyperpolymath/affinescript}} -} ----- - -=== Status Legend - -* *Complete*: Theoretical content complete, pending implementation -verification -* *Stub*: Placeholder awaiting implementation of corresponding compiler -component -* *TODO*: Section identified but not yet written - -=== Dependencies on Implementation - -Many proofs in this documentation depend on compiler components that are -not yet implemented. These are marked with `+[IMPL-DEP]+` tags -throughout the documents, indicating which compiler phase must be -completed before the proof can be fully verified against the -implementation. - -[cols=",",options="header",] -|=== -|Proof Area |Required Implementation -|Type soundness |Type checker -|Effect soundness |Effect inference -|Ownership soundness |Borrow checker -|Termination |Termination checker -|Refinement verification |SMT integration -|=== diff --git a/affinescript/docs/academic/formal-verification/axiomatic-semantics.adoc b/affinescript/docs/academic/formal-verification/axiomatic-semantics.adoc deleted file mode 100644 index 517a42c..0000000 --- a/affinescript/docs/academic/formal-verification/axiomatic-semantics.adoc +++ /dev/null @@ -1,584 +0,0 @@ -== Axiomatic Semantics: Program Logic for AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -specification - -=== Abstract - -This document presents an axiomatic semantics for AffineScript based on -Hoare logic and separation logic. We define program logics for reasoning -about: 1. Partial and total correctness 2. Heap-manipulating programs -(separation logic) 3. Effectful computations 4. Ownership and borrowing -5. Refinement type verification - -=== 1. Introduction - -Axiomatic semantics provides: - Proof rules for program properties - -Compositional verification - Foundation for automated verification tools -- Connection to refinement type checking - -=== 2. Hoare Logic - -==== 2.1 Hoare Triples - -*Partial Correctness*: - -.... -{P} e {Q} -.... - -If precondition P holds and e terminates, then postcondition Q holds. - -*Total Correctness*: - -.... -[P] e [Q] -.... - -If P holds, then e terminates and Q holds. - -==== 2.2 Basic Rules - -*Skip* - -.... - ───────────────── - {P} () {P} -.... - -*Sequence* (via let) - -.... - {P} e₁ {Q} {Q} e₂ {R} - ────────────────────────────── - {P} let _ = e₁ in e₂ {R} -.... - -*Assignment* (for mutable variables) - -.... - ───────────────────────────────── - {P[e/x]} x := e {P} -.... - -*Conditional* - -.... - {P ∧ e₁} e₂ {Q} {P ∧ ¬e₁} e₃ {Q} - ───────────────────────────────────── - {P} if e₁ then e₂ else e₃ {Q} -.... - -*Consequence* - -.... - P' ⟹ P {P} e {Q} Q ⟹ Q' - ──────────────────────────────── - {P'} e {Q'} -.... - -==== 2.3 Loop Rule (for while) - -.... - {I ∧ b} e {I} - ───────────────────────── - {I} while b do e {I ∧ ¬b} -.... - -where I is the loop invariant. - -==== 2.4 Function Call - -.... - {P} f {Q} (specification of f) - ──────────────────────────────────── - {P[a/x]} f(a) {Q[a/x]} -.... - -=== 3. Separation Logic - -==== 3.1 Spatial Assertions - -For heap-manipulating programs, extend assertions with spatial -operators: - -.... -P, Q ::= - | emp -- Empty heap - | e₁ ↦ e₂ -- Singleton heap - | P * Q -- Separating conjunction - | P -* Q -- Magic wand (separating implication) - | P ∧ Q -- Conjunction - | P ∨ Q -- Disjunction - | ∃x. P -- Existential - | ∀x. P -- Universal -.... - -==== 3.2 Semantics of Spatial Operators - -*Empty Heap*: - -.... -h ⊨ emp iff dom(h) = ∅ -.... - -*Points-To*: - -.... -h ⊨ e₁ ↦ e₂ iff h = {⟦e₁⟧ ↦ ⟦e₂⟧} -.... - -*Separating Conjunction*: - -.... -h ⊨ P * Q iff ∃h₁, h₂. h = h₁ ⊎ h₂ ∧ h₁ ⊨ P ∧ h₂ ⊨ Q -.... - -*Magic Wand*: - -.... -h ⊨ P -* Q iff ∀h'. h' ⊨ P ∧ h # h' ⟹ h ⊎ h' ⊨ Q -.... - -==== 3.3 Frame Rule - -The key rule enabling local reasoning: - -.... - {P} e {Q} - ───────────────────────── - {P * R} e {Q * R} -.... - -where FV(R) ∩ mod(e) = ∅. - -==== 3.4 Heap Rules - -*Allocation* - -.... - ───────────────────────────────── - {emp} ref(e) {∃ℓ. ret ↦ ℓ * ℓ ↦ e} -.... - -*Read* - -.... - ──────────────────────────────── - {ℓ ↦ v} !ℓ {ret = v * ℓ ↦ v} -.... - -*Write* - -.... - ──────────────────────────────── - {ℓ ↦ _} ℓ := e {ℓ ↦ e} -.... - -*Deallocation* - -.... - ──────────────────────────────── - {ℓ ↦ _} free(ℓ) {emp} -.... - -=== 4. Ownership Logic - -==== 4.1 Ownership Assertions - -Extend assertions for ownership: - -.... -P ::= ... - | own(e, τ) -- Ownership of e at type τ - | borrow(e, τ, 'a) -- Borrow of e with lifetime 'a - | mut_borrow(e, τ, 'a) -- Mutable borrow - | 'a ⊑ 'b -- Lifetime ordering -.... - -==== 4.2 Ownership Rules - -*Move* - -.... - ───────────────────────────────────────── - {own(x, τ)} let y = move x {own(y, τ)} -.... - -*Borrow* - -.... - 'a ⊑ lifetime(x) - ──────────────────────────────────────────────────────── - {own(x, τ)} let y = &x {own(x, τ) * borrow(y, τ, 'a)} -.... - -*Mutable Borrow* (exclusive) - -.... - 'a ⊑ lifetime(x) - ─────────────────────────────────────────────────────────── - {own(x, τ)} let y = &mut x {suspended(x) * mut_borrow(y, τ, 'a)} -.... - -*Borrow End* - -.... - ─────────────────────────────────────────────── - {suspended(x) * mut_borrow(y, τ, 'a) * end('a)} - e - {own(x, τ')} -.... - -==== 4.3 Affine Rule - -.... - {P * own(x, τ)} e {Q} - ──────────────────────────── - {P * own(x, τ)} e; drop(x) {Q} -.... - -Owned resources may be dropped (affine, not linear). - -=== 5. Effect Logic - -==== 5.1 Effect Assertions - -For reasoning about effects: - -.... -P ::= ... - | performs(E) -- May perform effect E - | pure -- Performs no effects - | handled(E) -- Effect E is handled -.... - -==== 5.2 Effect Rules - -*Pure* - -.... - e is pure (no perform) - ─────────────────────────── - {P * pure} e {Q * pure} -.... - -*Perform* - -.... - op : τ → σ ∈ E - ───────────────────────────────────────────── - {P} perform op(e) {Q * performs(E)} -.... - -*Handle* - -.... - {P * performs(E)} e {Q} - {Q[v/ret]} e_ret {R} - ∀op ∈ E. {Q' * k : σ → R} e_op {R} - ─────────────────────────────────────────────────────── - {P} handle e with h {R * handled(E)} -.... - -==== 5.3 Effect Frame Rule - -.... - {P} e {Q * performs(ε₁)} ε₁ ⊆ ε₂ - ─────────────────────────────────────── - {P} e {Q * performs(ε₂)} -.... - -=== 6. Refinement Logic - -==== 6.1 Connection to Refinement Types - -Refinement types `+{x: τ | φ}+` correspond to Hoare preconditions: - -.... -If Γ ⊢ e : {x: τ | φ} then {φ[e/x]} use(e) {...} -.... - -==== 6.2 Verification Conditions - -Generate verification conditions from refined function signatures: - -[source,affinescript] ----- -fn divide(x: Int, y: {v: Int | v ≠ 0}) -> Int ----- - -generates VC: - -.... -∀x, y. y ≠ 0 ⟹ divide(x, y) is defined -.... - -==== 6.3 Subtyping as Implication - -.... -{x: τ | φ} <: {x: τ | ψ} -.... - -iff - -.... -∀x: τ. φ ⟹ ψ -.... - -=== 7. Total Correctness - -==== 7.1 Termination - -For total correctness, add a variant (decreasing measure): - -*While-Total* - -.... - {I ∧ b ∧ V = n} e {I ∧ V < n} V ≥ 0 - ─────────────────────────────────────────── - [I] while b do e [I ∧ ¬b] -.... - -==== 7.2 Well-Founded Recursion - -For recursive functions: - -.... - ∀x. [P(x) ∧ ∀y. (y, x) ∈ R ⟹ Q(y)] f(x) [Q(x)] - R is well-founded - ──────────────────────────────────────────────── - [P(x)] f(x) [Q(x)] -.... - -=== 8. Concurrent Separation Logic - -==== 8.1 Concurrent Rules - -For concurrent AffineScript (async effects): - -*Parallel* - -.... - {P₁} e₁ {Q₁} {P₂} e₂ {Q₂} - ───────────────────────────────────── - {P₁ * P₂} e₁ || e₂ {Q₁ * Q₂} -.... - -==== 8.2 Lock Invariants - -.... - {P * I} e {Q * I} - ───────────────────────────────────────────────── - {P * locked(l, I)} with_lock(l) { e } {Q * locked(l, I)} -.... - -==== 8.3 Rely-Guarantee - -For interference: - -.... -{P} e {Q} -R (rely): invariant maintained by environment -G (guarantee): invariant we maintain -.... - -=== 9. Derived Proof Rules - -==== 9.1 Array Access - -.... - {a ↦ [v₀, ..., vₙ₋₁] * 0 ≤ i < n} - a[i] - {ret = vᵢ * a ↦ [v₀, ..., vₙ₋₁]} -.... - -==== 9.2 List Predicates - -.... -list(x, []) ≡ x = null -list(x, v::vs) ≡ ∃y. x ↦ (v, y) * list(y, vs) -.... - -==== 9.3 Tree Predicates - -.... -tree(x, Leaf) ≡ x = null -tree(x, Node(v, l, r)) ≡ ∃y, z. x ↦ (v, y, z) * tree(y, l) * tree(z, r) -.... - -=== 10. Soundness - -==== 10.1 Interpretation - -Define satisfaction: s, h ⊨ P (state s and heap h satisfy P) - -==== 10.2 Soundness Theorem - -*Theorem 10.1 (Soundness)*: If `+{P} e {Q}+` is derivable, then: - -.... -∀s, h. s, h ⊨ P ⟹ ∀s', h'. (e, s, h) ⟶* (v, s', h') ⟹ s', h' ⊨ Q[v/ret] -.... - -*Proof*: By induction on the derivation, using the operational -semantics. ∎ - -==== 10.3 Relative Completeness - -*Theorem 10.2 (Relative Completeness)*: If `+⊨ {P} e {Q}+` holds -semantically, then `+{P} e {Q}+` is derivable (relative to the assertion -logic). - -=== 11. Examples - -==== 11.1 Swap - -[source,affinescript] ----- -fn swap(x: mut Int, y: mut Int) { - let t = *x - *x = *y - *y = t -} ----- - -Specification: - -.... -{x ↦ a * y ↦ b} -swap(x, y) -{x ↦ b * y ↦ a} -.... - -Proof: - -.... -{x ↦ a * y ↦ b} -let t = *x -{x ↦ a * y ↦ b * t = a} -*x = *y -{x ↦ b * y ↦ b * t = a} -*y = t -{x ↦ b * y ↦ a} -.... - -==== 11.2 List Append - -[source,affinescript] ----- -fn append(xs: own List[T], ys: own List[T]) -> own List[T] { - case xs { - Nil → ys, - Cons(x, xs') → Cons(x, append(xs', ys)) - } -} ----- - -Specification: - -.... -{list(xs, as) * list(ys, bs)} -append(xs, ys) -{∃r. list(r, as ++ bs) * ret = r} -.... - -==== 11.3 Binary Search - -[source,affinescript] ----- -fn binary_search(arr: ref [Int], target: Int) -> Option[Nat] { - let mut lo = 0 - let mut hi = arr.len() - while lo < hi { - let mid = lo + (hi - lo) / 2 - if arr[mid] == target { - return Some(mid) - } else if arr[mid] < target { - lo = mid + 1 - } else { - hi = mid - } - } - None -} ----- - -Specification: - -.... -{sorted(arr) * len(arr) = n} -binary_search(arr, target) -{ret = Some(i) ⟹ arr[i] = target ∧ 0 ≤ i < n} -{ret = None ⟹ ∀i. 0 ≤ i < n ⟹ arr[i] ≠ target} -.... - -Loop invariant: - -.... -I ≡ 0 ≤ lo ≤ hi ≤ n ∧ - (∀j. 0 ≤ j < lo ⟹ arr[j] < target) ∧ - (∀j. hi ≤ j < n ⟹ arr[j] > target) -.... - -=== 12. Automation - -==== 12.1 Verification Condition Generation - -Weakest precondition: - -.... -wp(skip, Q) = Q -wp(x := e, Q) = Q[e/x] -wp(e₁; e₂, Q) = wp(e₁, wp(e₂, Q)) -wp(if b then e₁ else e₂, Q) = (b ⟹ wp(e₁, Q)) ∧ (¬b ⟹ wp(e₂, Q)) -.... - -==== 12.2 SMT Integration - -Verification conditions are discharged using SMT solvers: - -[source,ocaml] ----- -let verify (spec : spec) (e : expr) : bool = - let vc = wp e spec.post in - let query = implies spec.pre vc in - Smt.check_valid query ----- - -==== 12.3 Symbolic Execution - -For path-sensitive reasoning: - -.... -symbolic_exec : expr → path_condition → (path_condition × symbolic_state) list -.... - -=== 13. Related Work - -[arabic] -. *Hoare Logic*: Hoare (1969) -. *Separation Logic*: Reynolds (2002), O’Hearn (2019) -. *Iris*: Jung et al. (2015) - Higher-order concurrent separation logic -. *RustBelt*: Jung et al. (2017) - Semantic foundations for Rust -. *Viper*: Müller et al. (2016) - Verification infrastructure -. *Dafny*: Leino (2010) - Verification-aware programming - -=== 14. References - -[arabic] -. Hoare, C. A. R. (1969). An Axiomatic Basis for Computer Programming. -_CACM_. -. Reynolds, J. C. (2002). Separation Logic: A Logic for Shared Mutable -Data Structures. _LICS_. -. O’Hearn, P. W. (2019). Separation Logic. _CACM_. -. Jung, R., et al. (2017). RustBelt: Securing the Foundations of the -Rust Programming Language. _POPL_. -. Leino, K. R. M. (2010). Dafny: An Automatic Program Verifier. _LPAR_. - -''''' - -*Document Metadata*: - Depends on: Type system, operational semantics, -SMT integration - Implementation verification: Pending -`+[IMPL-DEP: verifier]+` - Mechanized proof: See -`+mechanized/coq/Hoare.v+` (stub) diff --git a/affinescript/docs/academic/formal-verification/denotational-semantics.adoc b/affinescript/docs/academic/formal-verification/denotational-semantics.adoc deleted file mode 100644 index 80001d0..0000000 --- a/affinescript/docs/academic/formal-verification/denotational-semantics.adoc +++ /dev/null @@ -1,501 +0,0 @@ -== Denotational Semantics of AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -specification - -=== Abstract - -This document provides a denotational semantics for AffineScript, -interpreting programs as mathematical objects in suitable semantic -domains. We use domain theory for handling recursion, monads for -effects, and logical relations for establishing semantic properties. - -=== 1. Introduction - -Denotational semantics provides: - Compositional interpretation of -programs - Mathematical foundation for reasoning - Basis for program -equivalence - Connection to logic and category theory - -=== 2. Semantic Domains - -==== 2.1 Basic Domains - -We use complete partial orders (CPOs) with least elements (pointed -CPOs): - -*Definition 2.1 (Pointed CPO)*: A pointed CPO (D, ⊑, ⊥) consists of: - A -set D - A partial order ⊑ on D - A least element ⊥ ∈ D - Every ω-chain -has a least upper bound - -==== 2.2 Domain Constructors - -*Lifted Domain*: D_⊥ = D ∪ \{⊥} - -*Product Domain*: D₁ × D₂ with pointwise ordering - -*Sum Domain*: D₁ + D₂ = \{inl(d) | d ∈ D₁} ∪ \{inr(d) | d ∈ D₂} - -*Function Domain*: [D₁ → D₂] = \{f : D₁ → D₂ | f is continuous} - -*Recursive Domains*: Solutions to domain equations using inverse limits - -==== 2.3 Type Denotations - -.... -⟦Unit⟧ = {*} -⟦Bool⟧ = {true, false}_⊥ -⟦Int⟧ = Z_⊥ -⟦Float⟧ = R_⊥ -⟦String⟧ = List(Char)_⊥ - -⟦τ → σ⟧ = [⟦τ⟧ → ⟦σ⟧] -⟦τ × σ⟧ = ⟦τ⟧ × ⟦σ⟧ -⟦τ + σ⟧ = ⟦τ⟧ + ⟦σ⟧ -.... - -=== 3. Environment and Interpretation - -==== 3.1 Environments - -An environment maps variables to values: - -.... -Env = Var → Val - -⟦Γ⟧ = {ρ : Var → Val | ∀(x:τ) ∈ Γ. ρ(x) ∈ ⟦τ⟧} -.... - -==== 3.2 Expression Interpretation - -.... -⟦_⟧ : Expr → Env → Val -.... - -Compositionally defined for each expression form. - -=== 4. Core Language Interpretation - -==== 4.1 Literals - -.... -⟦()⟧ρ = * -⟦true⟧ρ = true -⟦false⟧ρ = false -⟦n⟧ρ = n -⟦f⟧ρ = f -⟦"s"⟧ρ = s -.... - -==== 4.2 Variables - -.... -⟦x⟧ρ = ρ(x) -.... - -==== 4.3 Functions - -.... -⟦λ(x:τ). e⟧ρ = λd. ⟦e⟧(ρ[x ↦ d]) -⟦e₁ e₂⟧ρ = ⟦e₁⟧ρ (⟦e₂⟧ρ) -.... - -==== 4.4 Let Binding - -.... -⟦let x = e₁ in e₂⟧ρ = ⟦e₂⟧(ρ[x ↦ ⟦e₁⟧ρ]) -.... - -==== 4.5 Recursion - -Using the least fixed point operator: - -.... -⟦fix f. e⟧ρ = fix(λd. ⟦e⟧(ρ[f ↦ d])) - = ⊔ₙ fⁿ(⊥) -.... - -where f = λd. ⟦e⟧(ρ[f ↦ d]) - -==== 4.6 Conditionals - -.... -⟦if e₁ then e₂ else e₃⟧ρ = - case ⟦e₁⟧ρ of - true → ⟦e₂⟧ρ - false → ⟦e₃⟧ρ - ⊥ → ⊥ -.... - -==== 4.7 Tuples - -.... -⟦(e₁, ..., eₙ)⟧ρ = (⟦e₁⟧ρ, ..., ⟦eₙ⟧ρ) -⟦e.i⟧ρ = πᵢ(⟦e⟧ρ) -.... - -==== 4.8 Records - -.... -⟦{l₁ = e₁, ..., lₙ = eₙ}⟧ρ = {l₁ ↦ ⟦e₁⟧ρ, ..., lₙ ↦ ⟦eₙ⟧ρ} -⟦e.l⟧ρ = ⟦e⟧ρ(l) -⟦e₁ with {l = e₂}⟧ρ = ⟦e₁⟧ρ[l ↦ ⟦e₂⟧ρ] -.... - -==== 4.9 Pattern Matching - -.... -⟦case e {p₁ → e₁ | ... | pₙ → eₙ}⟧ρ = - let v = ⟦e⟧ρ in - match v with - | ⟦p₁⟧ → ⟦e₁⟧(ρ ⊕ bindings(p₁, v)) - | ... - | ⟦pₙ⟧ → ⟦eₙ⟧(ρ ⊕ bindings(pₙ, v)) - | _ → ⊥ -.... - -=== 5. Type-Level Interpretation - -==== 5.1 Type Abstraction - -.... -⟦Λα:κ. e⟧ρ = λT. ⟦e⟧ρ -⟦e [τ]⟧ρ = ⟦e⟧ρ (⟦τ⟧) -.... - -==== 5.2 Dependent Types - -For dependent types, we use families of domains: - -.... -⟦Π(x:τ). σ⟧ = Π_{d ∈ ⟦τ⟧} ⟦σ⟧[d/x] -⟦Σ(x:τ). σ⟧ = Σ_{d ∈ ⟦τ⟧} ⟦σ⟧[d/x] -.... - -==== 5.3 Refinement Types - -.... -⟦{x: τ | φ}⟧ = {d ∈ ⟦τ⟧ | ⟦φ⟧[d/x] = true} -.... - -==== 5.4 Equality Types - -.... -⟦a == b⟧ = if ⟦a⟧ = ⟦b⟧ then {*} else ∅ -.... - -=== 6. Effects - -==== 6.1 Monad Transformers - -Effects are interpreted using monad transformers: - -*State Monad*: - -.... -State S A = S → (A × S) - -⟦τ →{State[S]} σ⟧ = ⟦τ⟧ → State ⟦S⟧ ⟦σ⟧ -.... - -*Exception Monad*: - -.... -Exn E A = A + E - -⟦τ →{Exn[E]} σ⟧ = ⟦τ⟧ → Exn ⟦E⟧ ⟦σ⟧ -.... - -*Reader Monad*: - -.... -Reader R A = R → A - -⟦τ →{Reader[R]} σ⟧ = ⟦τ⟧ → Reader ⟦R⟧ ⟦σ⟧ -.... - -==== 6.2 Free Monad Interpretation - -For general algebraic effects: - -.... -Free F A = Pure A | Op (F (Free F A)) - -⟦ε⟧ = Free (⟦Ops(ε)⟧) -.... - -where Ops(ε) is the functor for effect operations. - -==== 6.3 Handler Interpretation - -.... -⟦handle e with h⟧ρ = fold_h(⟦e⟧ρ) - -where fold_h : Free F A → B is defined by: - fold_h(Pure a) = ⟦e_ret⟧(ρ[x ↦ a]) - fold_h(Op op(v, k)) = ⟦e_op⟧(ρ[x ↦ v, k ↦ λy. fold_h(k(y))]) -.... - -==== 6.4 Effectful Function Interpretation - -.... -⟦perform op(e)⟧ρ = Op op(⟦e⟧ρ, Pure) -.... - -=== 7. Ownership and References - -==== 7.1 Store Model - -.... -Store = Loc → Val -Conf = Expr × Store -.... - -==== 7.2 Stateful Interpretation - -.... -⟦_⟧ : Expr → Env → Store → (Val × Store) - -⟦ref e⟧ρσ = - let (v, σ') = ⟦e⟧ρσ in - let ℓ fresh in - (ℓ, σ'[ℓ ↦ v]) - -⟦!e⟧ρσ = - let (ℓ, σ') = ⟦e⟧ρσ in - (σ'(ℓ), σ') - -⟦e₁ := e₂⟧ρσ = - let (ℓ, σ') = ⟦e₁⟧ρσ in - let (v, σ'') = ⟦e₂⟧ρσ' in - ((), σ''[ℓ ↦ v]) -.... - -==== 7.3 Ownership Erasure - -At the semantic level, ownership annotations are erased: - -.... -⟦own τ⟧ = ⟦τ⟧ -⟦ref τ⟧ = ⟦τ⟧ -⟦mut τ⟧ = ⟦τ⟧ -.... - -The ownership system ensures safety statically; runtime behavior is -identical. - -=== 8. Quantitative Types - -==== 8.1 Graded Semantics - -For quantities, use graded monads: - -.... -⟦0 τ⟧ = 1 (erased, unit type) -⟦1 τ⟧ = ⟦τ⟧ (linear) -⟦ω τ⟧ = !⟦τ⟧ = ⟦τ⟧ (in CPO, no distinction) -.... - -==== 8.2 Usage Tracking - -Alternatively, track usage in an effect: - -.... -Usage = Var → Nat - -⟦e⟧ : Env → Usage → (Val × Usage) -.... - -=== 9. Semantic Properties - -==== 9.1 Adequacy - -*Theorem 9.1 (Adequacy)*: For closed terms e of ground type: - -.... -⟦e⟧{} = v iff e ⟶* v -.... - -*Proof*: By logical relations between syntax and semantics. ∎ - -==== 9.2 Soundness - -*Theorem 9.2 (Semantic Soundness)*: If `+Γ ⊢ e : τ+` then for all ρ ∈ -⟦Γ⟧: - -.... -⟦e⟧ρ ∈ ⟦τ⟧ -.... - -*Proof*: By induction on typing derivation. ∎ - -==== 9.3 Compositionality - -*Theorem 9.3 (Compositionality)*: The semantics is compositional: - -.... -⟦E[e]⟧ρ = ⟦E⟧(ρ, ⟦e⟧ρ) -.... - -The meaning of a compound expression depends only on the meanings of its -parts. - -==== 9.4 Full Abstraction - -*Open Problem*: Is the semantics fully abstract? - -Full abstraction: `+⟦e₁⟧ = ⟦e₂⟧+` iff e₁ ≃ e₂ (contextually equivalent) - -This typically requires game semantics or more refined models. - -=== 10. Logical Relations - -==== 10.1 Definition - -Define a family of relations R_τ ⊆ ⟦τ⟧ × ⟦τ⟧ indexed by types: - -.... -R_Unit = {(*, *)} -R_Bool = {(true, true), (false, false)} -R_Int = {(n, n) | n ∈ Z} - -R_{τ→σ} = {(f, g) | ∀(d₁, d₂) ∈ R_τ. (f d₁, g d₂) ∈ R_σ} -R_{τ×σ} = {((d₁, d₂), (d₁', d₂')) | (d₁, d₁') ∈ R_τ ∧ (d₂, d₂') ∈ R_σ} -.... - -==== 10.2 Fundamental Property - -*Theorem 10.1 (Fundamental Property)*: For all `+Γ ⊢ e : τ+` and related -environments ρ₁ R_Γ ρ₂: - -.... -(⟦e⟧ρ₁, ⟦e⟧ρ₂) ∈ R_τ -.... - -*Proof*: By induction on typing derivation. ∎ - -==== 10.3 Applications - -Logical relations prove: - Parametricity (free theorems) - Termination - -Observational equivalence - -=== 11. Domain Equations - -==== 11.1 Recursive Types - -For recursive types, solve domain equations: - -.... -⟦μα. τ⟧ = fix(λD. ⟦τ⟧[D/α]) -.... - -Using inverse limit construction for existence. - -==== 11.2 Example: Lists - -.... -⟦List[A]⟧ = fix(D. 1 + (⟦A⟧ × D)) - = 1 + (⟦A⟧ × (1 + (⟦A⟧ × ...))) - ≅ ⟦A⟧* (finite and infinite lists) -.... - -==== 11.3 Example: Streams - -.... -⟦Stream[A]⟧ = fix(D. ⟦A⟧ × D) - = ⟦A⟧ω (infinite sequences) -.... - -=== 12. Effect Semantics Details - -==== 12.1 State Effect - -.... -⟦State[S]⟧ = Free (StateF S) - -where StateF S X = Get (S → X) | Put (S × X) - -⟦perform get()⟧ρ = Op (Get Pure) -⟦perform put(e)⟧ρ = Op (Put (⟦e⟧ρ, Pure ())) -.... - -==== 12.2 Exception Effect - -.... -⟦Exn[E]⟧ = Free (ExnF E) - -where ExnF E X = Raise E - -⟦perform raise(e)⟧ρ = Op (Raise (⟦e⟧ρ)) -.... - -==== 12.3 Nondeterminism - -.... -⟦Choice⟧ = Free ChoiceF - -where ChoiceF X = Choose (X × X) | Fail - -⟦perform choose()⟧ρ = Op (Choose (Pure true, Pure false)) -⟦perform fail()⟧ρ = Op Fail -.... - -=== 13. Continuations - -==== 13.1 CPS Semantics - -Alternative: continuation-passing style interpretation: - -.... -⟦τ⟧_k = (⟦τ⟧ → R) → R - -⟦e₁ e₂⟧_k ρ κ = ⟦e₁⟧_k ρ (λf. ⟦e₂⟧_k ρ (λa. f a κ)) -.... - -==== 13.2 Delimited Continuations - -For effect handlers: - -.... -⟦handle e with h⟧_k ρ κ = - reset(⟦e⟧_k ρ (λv. ⟦e_ret⟧(ρ[x ↦ v]) κ)) -.... - -=== 14. Examples - -==== 14.1 Factorial - -.... -⟦let rec fact = λn. if n == 0 then 1 else n * fact(n - 1) in fact 5⟧ - -= fix(λf. λn. if n = 0 then 1 else n × f(n - 1))(5) -= 120 -.... - -==== 14.2 State Handler - -.... -⟦handle { let x = get(); put(x + 1); get() } with run_state(0)⟧ - -= fold_{run_state(0)}( - Op Get(λs. Op Put((s+1, λ(). Op Get(Pure)))) - ) -= 1 -.... - -=== 15. References - -[arabic] -. Scott, D. S. (1976). Data Types as Lattices. _SIAM J. Computing_. -. Stoy, J. E. (1977). _Denotational Semantics_. MIT Press. -. Winskel, G. (1993). _The Formal Semantics of Programming Languages_. -MIT Press. -. Gunter, C. A. (1992). _Semantics of Programming Languages_. MIT Press. -. Moggi, E. (1991). Notions of Computation and Monads. _I&C_. -. Plotkin, G., & Power, J. (2002). Notions of Computation Determine -Monads. _FOSSACS_. - -''''' - -*Document Metadata*: - This document is pure theory; no implementation -dependencies - Mechanized proof: See `+mechanized/coq/Denotational.v+` -(stub) diff --git a/affinescript/docs/academic/formal-verification/operational-semantics.adoc b/affinescript/docs/academic/formal-verification/operational-semantics.adoc deleted file mode 100644 index b1b400a..0000000 --- a/affinescript/docs/academic/formal-verification/operational-semantics.adoc +++ /dev/null @@ -1,699 +0,0 @@ -== Operational Semantics of AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -specification - -=== Abstract - -This document provides a complete operational semantics for -AffineScript, specifying the dynamic behavior of programs through -small-step reduction rules. We define evaluation contexts, reduction -relations for all language constructs, and prove determinacy and -confluence of the reduction relation. - -=== 1. Introduction - -The operational semantics defines how AffineScript programs execute. We -use: - *Small-step semantics*: Step-by-step reduction - *Structural -operational semantics (SOS)*: Inference rule format - *Evaluation -contexts*: Specifying evaluation order - -=== 2. Syntax Recap - -==== 2.1 Values - -.... -v ::= - | () -- Unit - | true | false -- Booleans - | n -- Integer literals - | f -- Float literals - | 'c' -- Characters - | "s" -- Strings - | λ(x:τ). e -- Lambda - | Λα:κ. v -- Type lambda (value) - | (v₁, ..., vₙ) -- Tuple - | {l₁ = v₁, ..., lₙ = vₙ} -- Record - | C(v₁, ..., vₙ) -- Constructor - | ℓ -- Location (heap reference) - | handler h -- Handler value -.... - -==== 2.2 Expressions - -.... -e ::= - -- Core - | v -- Values - | x -- Variables - | e₁ e₂ -- Application - | e [τ] -- Type application - | let x = e₁ in e₂ -- Let binding - | let (x₁, ..., xₙ) = e₁ in e₂ -- Tuple destructure - - -- Control - | if e₁ then e₂ else e₃ -- Conditional - | case e {p₁ → e₁ | ... | pₙ → eₙ} -- Pattern match - - -- Data - | (e₁, ..., eₙ) -- Tuple - | e.i -- Tuple projection - | {l₁ = e₁, ..., lₙ = eₙ} -- Record - | e.l -- Record projection - | {e₁ with l = e₂} -- Record update - | C(e₁, ..., eₙ) -- Constructor - - -- Effects - | perform op(e) -- Effect operation - | handle e with h -- Effect handler - | resume(e) -- Resume continuation - - -- References - | ref e -- Allocation - | !e -- Dereference - | e₁ := e₂ -- Assignment - - -- Ownership - | move e -- Explicit move - | &e -- Borrow - | &mut e -- Mutable borrow - | drop e -- Explicit drop - - -- Unsafe - | unsafe { e } -- Unsafe block -.... - -=== 3. Evaluation Contexts - -==== 3.1 Pure Contexts - -Evaluation contexts E specify where reduction occurs: - -.... -E ::= - | □ -- Hole - | E e -- Function position - | v E -- Argument position - | E [τ] -- Type application - | let x = E in e -- Let binding - | let (x̄) = E in e -- Tuple let - | if E then e₁ else e₂ -- Conditional - | case E {branches} -- Case scrutinee - | (v₁, ..., E, ..., eₙ) -- Tuple (left-to-right) - | E.i -- Tuple projection - | {l₁ = v₁, ..., l = E, ..., lₙ = eₙ} -- Record - | E.l -- Record projection - | {E with l = e} -- Record update (base) - | {v with l = E} -- Record update (field) - | C(v₁, ..., E, ..., eₙ) -- Constructor - | perform op(E) -- Effect operation - | ref E -- Allocation - | !E -- Dereference - | E := e -- Assignment (left) - | v := E -- Assignment (right) - | move E -- Move - | &E -- Borrow - | &mut E -- Mutable borrow - | drop E -- Drop -.... - -==== 3.2 Effect Contexts - -For effect handling, we need contexts that can trap effects: - -.... -E_eff ::= - | E -- Pure context - | handle E_eff with h -- Handler context -.... - -==== 3.3 Reduction Contexts - -Full reduction contexts: - -.... -R ::= E | handle R with h -.... - -=== 4. Reduction Rules - -==== 4.1 Notation - -.... -e ⟶ e' -- Single step reduction -e ⟶* e' -- Reflexive-transitive closure -(e, μ) ⟶ (e', μ') -- Reduction with heap -.... - -==== 4.2 Core Reductions - -*β-Reduction* - -.... - ───────────────────────────────────── - (λ(x:τ). e) v ⟶ e[v/x] -.... - -*Type Application* - -.... - ───────────────────────────────────── - (Λα:κ. e) [τ] ⟶ e[τ/α] -.... - -*Let* - -.... - ───────────────────────────────────── - let x = v in e ⟶ e[v/x] -.... - -*Let-Tuple* - -.... - ───────────────────────────────────────────────────── - let (x₁, ..., xₙ) = (v₁, ..., vₙ) in e ⟶ e[v₁/x₁, ..., vₙ/xₙ] -.... - -==== 4.3 Control Flow Reductions - -*If-True* - -.... - ───────────────────────────────────── - if true then e₁ else e₂ ⟶ e₁ -.... - -*If-False* - -.... - ───────────────────────────────────── - if false then e₁ else e₂ ⟶ e₂ -.... - -*Case-Match* - -.... - match(p, v) = θ - ───────────────────────────────────────────────── - case v {... | p → e | ...} ⟶ θ(e) -.... - -==== 4.4 Data Structure Reductions - -*Tuple-Proj* - -.... - ───────────────────────────────────────────── - (v₁, ..., vₙ).i ⟶ vᵢ (1 ≤ i ≤ n) -.... - -*Record-Proj* - -.... - ───────────────────────────────────────────────────── - {l₁ = v₁, ..., lₙ = vₙ}.lᵢ ⟶ vᵢ -.... - -*Record-Update* - -.... - ───────────────────────────────────────────────────────────────── - {l₁ = v₁, ..., l = v, ..., lₙ = vₙ} with {l = v'} ⟶ {l₁ = v₁, ..., l = v', ..., lₙ = vₙ} -.... - -==== 4.5 Arithmetic Reductions - -*Int-Add* - -.... - n₁ + n₂ = n₃ - ───────────────────────────────────── - n₁ + n₂ ⟶ n₃ -.... - -*Int-Sub* - -.... - n₁ - n₂ = n₃ - ───────────────────────────────────── - n₁ - n₂ ⟶ n₃ -.... - -*Int-Mul* - -.... - n₁ × n₂ = n₃ - ───────────────────────────────────── - n₁ * n₂ ⟶ n₃ -.... - -*Int-Div* (partial) - -.... - n₂ ≠ 0 n₁ ÷ n₂ = n₃ - ───────────────────────────────────── - n₁ / n₂ ⟶ n₃ -.... - -*Comparison* - -.... - compare(n₁, n₂) = b - ───────────────────────────────────── - n₁ < n₂ ⟶ b -.... - -==== 4.6 Reference Reductions - -These use a heap μ : Loc ⇀ Val. - -*Ref-Alloc* - -.... - ℓ fresh - ───────────────────────────────────────────── - (ref v, μ) ⟶ (ℓ, μ[ℓ ↦ v]) -.... - -*Ref-Read* - -.... - μ(ℓ) = v - ───────────────────────────────────── - (!ℓ, μ) ⟶ (v, μ) -.... - -*Ref-Write* - -.... - ───────────────────────────────────────────── - (ℓ := v, μ) ⟶ ((), μ[ℓ ↦ v]) -.... - -==== 4.7 Effect Reductions - -*Handle-Return* - -.... - h = { return x → e_ret, ... } - ───────────────────────────────────────────── - handle v with h ⟶ e_ret[v/x] -.... - -*Handle-Perform* (effect handled) - -.... - op ∈ dom(h) - h = { ..., op(x, k) → e_op, ... } - k_val = λy. handle E_p[y] with h - ───────────────────────────────────────────────────────────── - handle E_p[perform op(v)] with h ⟶ e_op[v/x, k_val/k] -.... - -*Handle-Forward* (effect not handled) - -.... - op ∉ dom(h) - ───────────────────────────────────────────────────────────────── - handle E_p[perform op(v)] with h ⟶ perform op(v) >>= (λy. handle E_p[y] with h) -.... - -Where `+e >>= f+` is monadic bind (continuation). - -*Resume* - -.... - k = λy. handle E_p[y] with h - ───────────────────────────────────────────── - resume(k, v) ⟶ handle E_p[v] with h -.... - -==== 4.8 Ownership Reductions - -*Move* - -.... - ───────────────────────────────────── - move v ⟶ v -.... - -(Move is identity at runtime; ownership is erased) - -*Borrow* - -.... - ───────────────────────────────────── - &v ⟶ v -.... - -(Borrows are identity at runtime; lifetimes are erased) - -*Drop* - -.... - ───────────────────────────────────────────── - (drop ℓ, μ) ⟶ ((), μ \ ℓ) -.... - -==== 4.9 Congruence Rule - -*Context* - -.... - e ⟶ e' - ───────────────────────────────────── - E[e] ⟶ E[e'] -.... - -=== 5. Pattern Matching - -==== 5.1 Match Judgment - -.... -match(p, v) = θ (pattern p matches value v with substitution θ) -.... - -==== 5.2 Matching Rules - -*Match-Var* - -.... - ───────────────────────────────────── - match(x, v) = [v/x] -.... - -*Match-Wild* - -.... - ───────────────────────────────────── - match(_, v) = [] -.... - -*Match-Literal* - -.... - ───────────────────────────────────── - match(n, n) = [] -.... - -*Match-Constructor* - -.... - ∀i. match(pᵢ, vᵢ) = θᵢ - ───────────────────────────────────────────────────────── - match(C(p₁, ..., pₙ), C(v₁, ..., vₙ)) = θ₁ ∪ ... ∪ θₙ -.... - -*Match-Tuple* - -.... - ∀i. match(pᵢ, vᵢ) = θᵢ - ───────────────────────────────────────────────────────── - match((p₁, ..., pₙ), (v₁, ..., vₙ)) = θ₁ ∪ ... ∪ θₙ -.... - -*Match-Record* - -.... - ∀i. match(pᵢ, vᵢ) = θᵢ v = {..., lᵢ = vᵢ, ...} - ───────────────────────────────────────────────────────── - match({l₁ = p₁, ..., lₙ = pₙ}, v) = θ₁ ∪ ... ∪ θₙ -.... - -*Match-As* - -.... - match(p, v) = θ - ───────────────────────────────────── - match(x @ p, v) = θ[v/x] -.... - -*Match-Guard* - -.... - match(p, v) = θ θ(g) ⟶* true - ───────────────────────────────────── - match(p if g, v) = θ -.... - -=== 6. Substitution - -==== 6.1 Capture-Avoiding Substitution - -.... -x[v/x] = v -y[v/x] = y (y ≠ x) -(e₁ e₂)[v/x] = e₁[v/x] e₂[v/x] -(λ(y:τ). e)[v/x] = λ(y:τ). e[v/x] (y ≠ x, y ∉ FV(v)) -(let y = e₁ in e₂)[v/x] = let y = e₁[v/x] in e₂[v/x] (y ≠ x, y ∉ FV(v)) -... -.... - -==== 6.2 Type Substitution - -.... -α[τ/α] = τ -β[τ/α] = β (β ≠ α) -(σ → ρ)[τ/α] = σ[τ/α] → ρ[τ/α] -(∀β:κ. σ)[τ/α] = ∀β:κ. σ[τ/α] (β ≠ α, β ∉ FTV(τ)) -... -.... - -=== 7. Machine Semantics - -==== 7.1 Abstract Machine State - -For a more efficient implementation, define an abstract machine: - -.... -State = (Control, Environment, Heap, Stack) - (C, E, H, K) - -C = e | v -- Control: expression or value -E = x ↦ v -- Environment -H = ℓ ↦ v -- Heap -K = Frame* -- Continuation stack - -Frame = - | Arg(e, E) -- Awaiting function, has argument - | Fun(v) -- Awaiting argument, has function - | Let(x, e, E) -- Let binding - | Case(branches, E) -- Case analysis - | Handle(h, E) -- Effect handler - | ... -.... - -==== 7.2 Machine Transitions - -*Var* - -.... - E(x) = v - ───────────────────────────────────────── - (x, E, H, K) ⟹ (v, E, H, K) -.... - -*App-Left* - -.... - ───────────────────────────────────────────────────── - (e₁ e₂, E, H, K) ⟹ (e₁, E, H, Arg(e₂, E) :: K) -.... - -*App-Right* - -.... - ───────────────────────────────────────────────────── - (v, E, H, Arg(e, E') :: K) ⟹ (e, E', H, Fun(v) :: K) -.... - -*App-Beta* - -.... - v₁ = λ(x:τ). e - ───────────────────────────────────────────────────── - (v₂, E, H, Fun(v₁) :: K) ⟹ (e, E[x ↦ v₂], H, K) -.... - -*Handle-Push* - -.... - ───────────────────────────────────────────────────────── - (handle e with h, E, H, K) ⟹ (e, E, H, Handle(h, E) :: K) -.... - -*Handle-Return* - -.... - h = { return x → e_ret, ... } - ───────────────────────────────────────────────────────── - (v, E, H, Handle(h, E') :: K) ⟹ (e_ret, E'[x ↦ v], H, K) -.... - -=== 8. Properties - -==== 8.1 Determinacy - -*Theorem 8.1 (Determinacy)*: The reduction relation is deterministic on -pure expressions. - -.... -If e ⟶ e₁ and e ⟶ e₂, then e₁ = e₂. -.... - -*Proof*: By induction on the derivation of `+e ⟶ e₁+`. Each expression -form has exactly one applicable reduction rule, and evaluation contexts -determine a unique redex. ∎ - -*Note*: Effects introduce non-determinism when handlers provide choices. - -==== 8.2 Confluence - -*Theorem 8.2 (Confluence)*: The reduction relation is confluent. - -.... -If e ⟶* e₁ and e ⟶* e₂, then ∃e'. e₁ ⟶* e' and e₂ ⟶* e'. -.... - -*Proof*: By Newman’s Lemma, since the relation is terminating (for -types) and locally confluent (by determinacy for pure reduction). ∎ - -==== 8.3 Standardization - -*Theorem 8.3 (Standardization)*: Every reduction sequence can be -rearranged to a standard reduction (leftmost-outermost). - -*Proof*: Following the standard proof for lambda calculus with -extensions. ∎ - -=== 9. Semantic Domains - -==== 9.1 Value Domain - -.... -V = Unit | Bool | Int | Float | Char | String - | Fun(Env × Expr) - | Tuple(V*) - | Record(Label → V) - | Variant(Label × V) - | Loc - | Handler(H) -.... - -==== 9.2 Heap Domain - -.... -Heap = Loc ⇀ V -.... - -==== 9.3 Result Domain - -.... -Result = - | Val(V) -- Normal value - | Eff(Op × V × Cont) -- Suspended effect - | Err(Error) -- Runtime error -.... - -=== 10. Examples - -==== 10.1 Function Application - -.... -(λ(x: Int). x + 1) 5 -⟶ 5 + 1 [β-reduction] -⟶ 6 [arithmetic] -.... - -==== 10.2 Effect Handling - -.... -handle (1 + perform get()) with { - return x → x, - get(_, k) → resume(k, 10) -} - -⟶ handle (1 + perform get()) with h - [where h = {return x → x, get(_, k) → resume(k, 10)}] - -⟶ let k = λy. handle (1 + y) with h - in resume(k, 10) [Handle-Perform] - -⟶ let k = λy. handle (1 + y) with h - in handle (1 + 10) with h [Resume] - -⟶ handle 11 with h [arithmetic] - -⟶ 11 [Handle-Return] -.... - -==== 10.3 State Effect - -.... -handle { - let x = perform get() - perform put(x + 1) - perform get() -} with run_state(0) - --- Evaluates step by step with state threading -⟶* 1 -.... - -=== 11. Implementation Notes - -==== 11.1 Correspondence to AST - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type expr = - | ELit of literal - | EVar of ident - | EApp of expr * expr - | ELam of lambda - | ELet of let_binding - | EIf of expr * expr * expr - | ECase of expr * case_branch list - | ETuple of expr list - | ERecord of (ident * expr) list - | ERecordAccess of expr * ident - | EHandle of expr * handler - | EPerform of ident * expr - | ... ----- - -==== 11.2 Evaluator Structure - -`+[IMPL-DEP: evaluator]+` - -[source,ocaml] ----- -module Eval : sig - type value - type heap - type result = (value * heap, error) Result.t - - val eval : heap -> env -> expr -> result - val step : heap -> expr -> (heap * expr) option -end ----- - -=== 12. References - -[arabic] -. Wright, A. K., & Felleisen, M. (1994). A Syntactic Approach to Type -Soundness. _I&C_. -. Plotkin, G. D. (1981). A Structural Approach to Operational Semantics. -_DAIMI_. -. Felleisen, M., & Hieb, R. (1992). The Revised Report on the Syntactic -Theories of Sequential Control and State. _TCS_. -. Plotkin, G., & Pretnar, M. (2013). Handling Algebraic Effects. _LMCS_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (syntax), evaluator -implementation (pending) - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/Operational.v+` (stub) diff --git a/affinescript/docs/academic/formal-verification/solo-core/Context.idr b/affinescript/docs/academic/formal-verification/solo-core/Context.idr deleted file mode 100644 index 387d604..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/Context.idr +++ /dev/null @@ -1,102 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell --- --- QTT-style typing contexts for the Solo core. --- --- A context `Ctx` is a snoc-list of `(Ty, Q)` entries: each bound --- variable carries both its type and its current available --- quantity. Context addition `ctxAdd` adds quantities pointwise; --- context scaling `ctxScale q Γ` multiplies every entry's --- quantity by `q`. --- --- The typing judgement in Typing.idr uses these operations to --- SPLIT the input context across subterms in T-App, T-Pair, --- T-Let, T-Case — this is the standard QTT context discipline --- and is what makes affine accounting work. --- --- Relationship to lib/quantity.ml: the OCaml implementation does --- NOT maintain a QTT context; it walks the term after type --- checking and accumulates `usage` counts, then checks them --- against the declared quantities at the end --- (`check_function_quantities`). This module formalises the --- static QTT view that a usage-walk ought to agree with. An --- equivalence lemma between the two (static splitting vs. --- post-hoc counting) is future work. - -module Context - -import Quantity -import Syntax - -%default total - ------------------------------------------------------------- --- Context representation ------------------------------------------------------------- - -||| A snoc-list context: `Empty` at the base, `Snoc g a q` pushes -||| a binding with type `a` and quantity `q` on top. -public export -data Ctx : Type where - Empty : Ctx - Snoc : Ctx -> Ty -> Q -> Ctx - -%name Ctx g, g1, g2, d - ------------------------------------------------------------- --- Length (for well-scopedness conditions later) ------------------------------------------------------------- - -public export -ctxLen : Ctx -> Nat -ctxLen Empty = 0 -ctxLen (Snoc g _ _) = S (ctxLen g) - ------------------------------------------------------------- --- Pointwise quantity addition on contexts ------------------------------------------------------------- - -||| Pointwise-add two contexts. Defined only when the two -||| contexts have the same shape (same length and same types); -||| on a mismatch we return `Nothing`. In a well-typed derivation -||| the contexts will always agree. -public export -ctxAdd : Ctx -> Ctx -> Maybe Ctx -ctxAdd Empty Empty = Just Empty -ctxAdd (Snoc g1 a1 q1) (Snoc g2 a2 q2) = - case ctxAdd g1 g2 of - Nothing => Nothing - Just g => - -- We DO NOT check type equality here; Typing.idr will - -- only ever combine contexts whose shapes match by - -- construction. A decidable equality on Ty would let us - -- tighten this; deferred to a later pass. - Just (Snoc g a1 (qAdd q1 q2)) -ctxAdd _ _ = Nothing - ------------------------------------------------------------- --- Scalar multiplication of contexts ------------------------------------------------------------- - -||| Multiply every quantity in the context by `q`. Used in the -||| T-App rule (when an argument is used under a function whose -||| parameter quantity is `q`) and in the zero-context rule for -||| T-Var. -public export -ctxScale : Q -> Ctx -> Ctx -ctxScale _ Empty = Empty -ctxScale q (Snoc g a qEntry) = Snoc (ctxScale q g) a (qMul q qEntry) - ------------------------------------------------------------- --- The all-zero context derived from a shape ------------------------------------------------------------- - -||| Given any context, produce the same-shape context with every -||| quantity replaced by `Zero`. This is the "nothing is used" -||| context that appears in T-Unit and as the non-consuming -||| siblings of T-Var. -public export -ctxZero : Ctx -> Ctx -ctxZero Empty = Empty -ctxZero (Snoc g a _) = Snoc (ctxZero g) a Zero diff --git a/affinescript/docs/academic/formal-verification/solo-core/Quantity.idr b/affinescript/docs/academic/formal-verification/solo-core/Quantity.idr deleted file mode 100644 index adf6082..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/Quantity.idr +++ /dev/null @@ -1,315 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell --- --- Quantity semiring for the AffineScript Solo core. --- --- MIRRORS lib/quantity.ml exactly. Do NOT drift. If q_add / q_mul / --- q_le in the OCaml implementation change, update this module in --- the same commit. --- --- The tables (from lib/quantity.ml): --- --- + | 0 1 omega * | 0 1 omega --- ----+---------------- ----+---------------- --- 0 | 0 1 omega 0 | 0 0 0 --- 1 | 1 omega omega 1 | 0 1 omega --- omega| omega omega omega omega| 0 omega omega --- --- Ordering (q_le q1 q2 = True iff q1 can substitute where q2 is --- expected): 0 <= q for all q; q <= omega for all q; 1 <= 1. - -module Quantity - -%default total - -||| The three-point quantity semiring from QTT. -||| -||| * `Zero` — erased, compile-time only. -||| * `One` — linear, used exactly once at runtime. -||| * `Omega` — unrestricted. -public export -data Q : Type where - Zero : Q - One : Q - Omega : Q - -%name Q q, q1, q2, q3 - -public export -Eq Q where - Zero == Zero = True - One == One = True - Omega == Omega = True - _ == _ = False - ------------------------------------------------------------- --- Semiring operations ------------------------------------------------------------- - -||| Addition in the quantity semiring. -||| -||| Mirrors `q_add` in `lib/quantity.ml`. Note in particular -||| `One + One = Omega` (the semiring is three-point, not natural -||| numbers). -public export -qAdd : Q -> Q -> Q -qAdd Zero q = q -qAdd One Zero = One -qAdd One One = Omega -qAdd One Omega = Omega -qAdd Omega _ = Omega - -||| Multiplication in the quantity semiring. -||| -||| Mirrors `q_mul` in `lib/quantity.ml`. -public export -qMul : Q -> Q -> Q -qMul Zero _ = Zero -qMul One q = q -qMul Omega Zero = Zero -qMul Omega One = Omega -qMul Omega Omega = Omega - -||| Subquantity ordering. `qLe q1 q2` is `True` when a value with -||| quantity `q1` can be used where `q2` is expected (more -||| restricted may substitute for less restricted). -||| -||| Mirrors `q_le` in `lib/quantity.ml`. -public export -qLe : Q -> Q -> Bool -qLe Zero _ = True -qLe _ Omega = True -qLe One One = True -qLe One Zero = False -qLe Omega Zero = False -qLe Omega One = False - ------------------------------------------------------------- --- Semiring laws ------------------------------------------------------------- --- --- We prove the relevant laws by exhaustive case split on the --- three-element domain. Each case is Refl — totally mechanical, --- no axioms, no dangerous primitives. - -||| `Zero` is the left identity of addition. -public export -qAddZeroL : (q : Q) -> qAdd Zero q = q -qAddZeroL Zero = Refl -qAddZeroL One = Refl -qAddZeroL Omega = Refl - -||| `Zero` is the right identity of addition. -public export -qAddZeroR : (q : Q) -> qAdd q Zero = q -qAddZeroR Zero = Refl -qAddZeroR One = Refl -qAddZeroR Omega = Refl - -||| Addition is commutative. -public export -qAddComm : (q1, q2 : Q) -> qAdd q1 q2 = qAdd q2 q1 -qAddComm Zero Zero = Refl -qAddComm Zero One = Refl -qAddComm Zero Omega = Refl -qAddComm One Zero = Refl -qAddComm One One = Refl -qAddComm One Omega = Refl -qAddComm Omega Zero = Refl -qAddComm Omega One = Refl -qAddComm Omega Omega = Refl - -||| Addition is associative. -public export -qAddAssoc : (q1, q2, q3 : Q) - -> qAdd (qAdd q1 q2) q3 = qAdd q1 (qAdd q2 q3) -qAddAssoc Zero Zero Zero = Refl -qAddAssoc Zero Zero One = Refl -qAddAssoc Zero Zero Omega = Refl -qAddAssoc Zero One Zero = Refl -qAddAssoc Zero One One = Refl -qAddAssoc Zero One Omega = Refl -qAddAssoc Zero Omega Zero = Refl -qAddAssoc Zero Omega One = Refl -qAddAssoc Zero Omega Omega = Refl -qAddAssoc One Zero Zero = Refl -qAddAssoc One Zero One = Refl -qAddAssoc One Zero Omega = Refl -qAddAssoc One One Zero = Refl -qAddAssoc One One One = Refl -qAddAssoc One One Omega = Refl -qAddAssoc One Omega Zero = Refl -qAddAssoc One Omega One = Refl -qAddAssoc One Omega Omega = Refl -qAddAssoc Omega Zero Zero = Refl -qAddAssoc Omega Zero One = Refl -qAddAssoc Omega Zero Omega = Refl -qAddAssoc Omega One Zero = Refl -qAddAssoc Omega One One = Refl -qAddAssoc Omega One Omega = Refl -qAddAssoc Omega Omega Zero = Refl -qAddAssoc Omega Omega One = Refl -qAddAssoc Omega Omega Omega = Refl - -||| `Zero` is the left absorbing element of multiplication. -public export -qMulZeroL : (q : Q) -> qMul Zero q = Zero -qMulZeroL Zero = Refl -qMulZeroL One = Refl -qMulZeroL Omega = Refl - -||| `Zero` is the right absorbing element of multiplication. -public export -qMulZeroR : (q : Q) -> qMul q Zero = Zero -qMulZeroR Zero = Refl -qMulZeroR One = Refl -qMulZeroR Omega = Refl - -||| `One` is the left identity of multiplication. -public export -qMulOneL : (q : Q) -> qMul One q = q -qMulOneL Zero = Refl -qMulOneL One = Refl -qMulOneL Omega = Refl - -||| `One` is the right identity of multiplication. -public export -qMulOneR : (q : Q) -> qMul q One = q -qMulOneR Zero = Refl -qMulOneR One = Refl -qMulOneR Omega = Refl - -||| Multiplication is commutative. -public export -qMulComm : (q1, q2 : Q) -> qMul q1 q2 = qMul q2 q1 -qMulComm Zero Zero = Refl -qMulComm Zero One = Refl -qMulComm Zero Omega = Refl -qMulComm One Zero = Refl -qMulComm One One = Refl -qMulComm One Omega = Refl -qMulComm Omega Zero = Refl -qMulComm Omega One = Refl -qMulComm Omega Omega = Refl - -||| Multiplication is associative. -public export -qMulAssoc : (q1, q2, q3 : Q) - -> qMul (qMul q1 q2) q3 = qMul q1 (qMul q2 q3) -qMulAssoc Zero Zero Zero = Refl -qMulAssoc Zero Zero One = Refl -qMulAssoc Zero Zero Omega = Refl -qMulAssoc Zero One Zero = Refl -qMulAssoc Zero One One = Refl -qMulAssoc Zero One Omega = Refl -qMulAssoc Zero Omega Zero = Refl -qMulAssoc Zero Omega One = Refl -qMulAssoc Zero Omega Omega = Refl -qMulAssoc One Zero Zero = Refl -qMulAssoc One Zero One = Refl -qMulAssoc One Zero Omega = Refl -qMulAssoc One One Zero = Refl -qMulAssoc One One One = Refl -qMulAssoc One One Omega = Refl -qMulAssoc One Omega Zero = Refl -qMulAssoc One Omega One = Refl -qMulAssoc One Omega Omega = Refl -qMulAssoc Omega Zero Zero = Refl -qMulAssoc Omega Zero One = Refl -qMulAssoc Omega Zero Omega = Refl -qMulAssoc Omega One Zero = Refl -qMulAssoc Omega One One = Refl -qMulAssoc Omega One Omega = Refl -qMulAssoc Omega Omega Zero = Refl -qMulAssoc Omega Omega One = Refl -qMulAssoc Omega Omega Omega = Refl - -||| Left distributivity: q1 * (q2 + q3) = q1*q2 + q1*q3. -public export -qMulDistribL : (q1, q2, q3 : Q) - -> qMul q1 (qAdd q2 q3) = qAdd (qMul q1 q2) (qMul q1 q3) -qMulDistribL Zero Zero Zero = Refl -qMulDistribL Zero Zero One = Refl -qMulDistribL Zero Zero Omega = Refl -qMulDistribL Zero One Zero = Refl -qMulDistribL Zero One One = Refl -qMulDistribL Zero One Omega = Refl -qMulDistribL Zero Omega Zero = Refl -qMulDistribL Zero Omega One = Refl -qMulDistribL Zero Omega Omega = Refl -qMulDistribL One Zero Zero = Refl -qMulDistribL One Zero One = Refl -qMulDistribL One Zero Omega = Refl -qMulDistribL One One Zero = Refl -qMulDistribL One One One = Refl -qMulDistribL One One Omega = Refl -qMulDistribL One Omega Zero = Refl -qMulDistribL One Omega One = Refl -qMulDistribL One Omega Omega = Refl -qMulDistribL Omega Zero Zero = Refl -qMulDistribL Omega Zero One = Refl -qMulDistribL Omega Zero Omega = Refl -qMulDistribL Omega One Zero = Refl -qMulDistribL Omega One One = Refl -qMulDistribL Omega One Omega = Refl -qMulDistribL Omega Omega Zero = Refl -qMulDistribL Omega Omega One = Refl -qMulDistribL Omega Omega Omega = Refl - -||| Right distributivity: (q1 + q2) * q3 = q1*q3 + q2*q3. -public export -qMulDistribR : (q1, q2, q3 : Q) - -> qMul (qAdd q1 q2) q3 = qAdd (qMul q1 q3) (qMul q2 q3) -qMulDistribR Zero Zero Zero = Refl -qMulDistribR Zero Zero One = Refl -qMulDistribR Zero Zero Omega = Refl -qMulDistribR Zero One Zero = Refl -qMulDistribR Zero One One = Refl -qMulDistribR Zero One Omega = Refl -qMulDistribR Zero Omega Zero = Refl -qMulDistribR Zero Omega One = Refl -qMulDistribR Zero Omega Omega = Refl -qMulDistribR One Zero Zero = Refl -qMulDistribR One Zero One = Refl -qMulDistribR One Zero Omega = Refl -qMulDistribR One One Zero = Refl -qMulDistribR One One One = Refl -qMulDistribR One One Omega = Refl -qMulDistribR One Omega Zero = Refl -qMulDistribR One Omega One = Refl -qMulDistribR One Omega Omega = Refl -qMulDistribR Omega Zero Zero = Refl -qMulDistribR Omega Zero One = Refl -qMulDistribR Omega Zero Omega = Refl -qMulDistribR Omega One Zero = Refl -qMulDistribR Omega One One = Refl -qMulDistribR Omega One Omega = Refl -qMulDistribR Omega Omega Zero = Refl -qMulDistribR Omega Omega One = Refl -qMulDistribR Omega Omega Omega = Refl - ------------------------------------------------------------- --- Ordering sanity ------------------------------------------------------------- - -||| Zero is below everything. -public export -qLeZero : (q : Q) -> qLe Zero q = True -qLeZero Zero = Refl -qLeZero One = Refl -qLeZero Omega = Refl - -||| Omega is above everything. -public export -qLeOmega : (q : Q) -> qLe q Omega = True -qLeOmega Zero = Refl -qLeOmega One = Refl -qLeOmega Omega = Refl - -||| Reflexivity of the ordering. -public export -qLeRefl : (q : Q) -> qLe q q = True -qLeRefl Zero = Refl -qLeRefl One = Refl -qLeRefl Omega = Refl diff --git a/affinescript/docs/academic/formal-verification/solo-core/README.adoc b/affinescript/docs/academic/formal-verification/solo-core/README.adoc deleted file mode 100644 index 787de79..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/README.adoc +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell - -= Solo Core — Idris2 Mechanization -Jonathan D.A. Jewell -:toc: left -:source-highlighter: rouge - -== Scope - -This directory holds the Idris2 mechanization of the *Solo* core of -AffineScript — the minimal fragment sufficient to prove affine -preservation. Solo is: - -* Simply-typed lambda calculus -* Unit type -* Product (pair) types -* Sum types -* `let` binding -* Each binder annotated with a QTT quantity `q ∈ {0, 1, ω}` - -Solo deliberately *excludes* traits, effects, row polymorphism, -refinement types, dependent types, ownership (`own`/`ref`/`mut`), -records, arrays, modules, and the entire effect system. Those belong -to *Duet* (traits + effects) and *Ensemble* (full language), which -will be mechanized in later tracks. - -== Proof Goals (Track F1, Weeks 1–12) - -. *Progress*: a closed, well-typed Solo term is either a value or - steps. -. *Preservation*: reduction preserves typing. -. *Affine preservation*: reduction preserves the quantity semiring - accounting — a term typed in context `Γ` steps to a term typed in - the same `Γ`, and `1`-quantity bindings are consumed exactly once. - -This file-set is the *week 1-2* deliverable: syntax, contexts, -typing judgement, and *statements* (not proofs) of progress and -preservation. - -== Ground-Truth Rule - -[IMPORTANT] -==== -The proof must *track the OCaml implementation*, not an idealized -QTT. When the Idris2 formalisation and `lib/quantity.ml` + -`lib/typecheck.ml` disagree, *the OCaml implementation is ground -truth* until an ADR is filed that explicitly reverses this. - -Track F re-verifies weekly against Track A's test fixtures: - -* `examples/affine_basic.affine` -* `examples/affine_violation.affine` - -If the mechanization and the compiler diverge on these fixtures, the -proof is wrong, not the compiler. -==== - -== Files - -[cols="1,3"] -|=== -| File | Status - -| `Quantity.idr` -| Semiring `{0, 1, ω}` — definitions + semiring laws. - *Mirrors* `lib/quantity.ml` `q_add` / `q_mul` / `q_le` exactly. - -| `Syntax.idr` -| Solo types and terms with de Bruijn indices. - -| `Context.idr` -| Snoc-list contexts of `(Ty, Q)` entries, pointwise addition and - scalar multiplication. - -| `Typing.idr` -| QTT-style typing judgement `Has : Ctx -> Tm -> Ty -> Type`. - Rules only; no meta-theorems yet. - -| `Soundness.idr` -| *Statements* of `progress` and `preservation` with - `?todo_progress` / `?todo_preservation` holes. Proofs are - weeks 3-12 work. -|=== - -== Status Block - -[source] ----- -Phase: Track F1, week 1-2 (syntax + statements) -Last sync: 2026-04-10 -OCaml refs: lib/quantity.ml, lib/types.ml, lib/typecheck.ml -Spec refs: docs/spec.md §3.1-3.6 (Solo-relevant rules only) -Idris2 ver: 0.8.0 (verified locally) -Dangerous: none (no believe_me, no assert_total, no really_believe_me) -Holes: ?todo_progress, ?todo_preservation (intentional) ----- - -== Out of Scope (Tracks F2, F3, …) - -* Duet: traits, effects, effect rows, handlers, resume -* Ensemble: row polymorphism, refinements, dependent types, - ownership/borrowing, modules, `unsafe` -* Compiler-level soundness (backend / codegen) -* Termination of non-`total` functions - -== Weekly Check - -Each week, re-run `idris2 --check` on every file in this directory -and re-run the compiler against the two `examples/affine_*.affine` -fixtures. Divergence = file an ADR, not a silent fix. diff --git a/affinescript/docs/academic/formal-verification/solo-core/Soundness.idr b/affinescript/docs/academic/formal-verification/solo-core/Soundness.idr deleted file mode 100644 index 73b9619..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/Soundness.idr +++ /dev/null @@ -1,102 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell --- --- Statements of the soundness theorems for the Solo core. --- --- This file deliberately contains NO PROOFS. The week-1-2 --- deliverable for Track F1 is the *statement* of progress and --- preservation; the actual derivations are weeks 3-12 work and --- will be filled in case-by-case. All theorems are left as --- explicit `?todo_...` holes so the file still typechecks as a --- declaration module. - -module Soundness - -import Quantity -import Syntax -import Context -import Typing - -%default total - ------------------------------------------------------------- --- Values ------------------------------------------------------------- - -||| Solo values: canonical forms of closed terms. -public export -data Value : Tm -> Type where - VUnit : Value UnitT - VLam : Value (Lam q a t) - VPair : Value t1 -> Value t2 -> Value (Pair t1 t2) - VInl : Value t -> Value (Inl b t) - VInr : Value t -> Value (Inr a t) - ------------------------------------------------------------- --- Small-step reduction (declaration only) ------------------------------------------------------------- --- --- We declare the relation `Step t t'` as a data family, but do --- NOT enumerate its constructors yet. The constructors will --- follow call-by-value, context-free beta/projection/case --- reduction. They are introduced alongside the progress / --- preservation proofs in weeks 3-6. - -public export -data Step : Tm -> Tm -> Type where - -- Constructors intentionally omitted until week 3. - -- This keeps the *statements* below well-typed without - -- prematurely committing to a specific operational-semantics - -- formulation. The constructors will mirror the reference - -- interpreter in `lib/interp.ml` (call-by-value, left-to-right). - ------------------------------------------------------------- --- Existential wrapper (no dependent pair imports needed) ------------------------------------------------------------- - -||| Simple Sigma to avoid bringing in `Data.DPair` right now. -public export -data StepsTo : Tm -> Type where - MkStepsTo : (t' : Tm) -> Step t t' -> StepsTo t - ------------------------------------------------------------- --- Progress ------------------------------------------------------------- - -||| Progress: a closed, well-typed Solo term is either a value -||| or can take a step. -||| -||| "Closed" means typed in the empty context — there are no -||| free de Bruijn indices because `Empty` has no `HVHere` / -||| `HVThere` inhabitants. -public export -progress : Has Empty t a -> Either (Value t) (StepsTo t) -progress _ = ?todo_progress - ------------------------------------------------------------- --- Preservation ------------------------------------------------------------- - -||| Preservation: reduction preserves typing in the same -||| context. The fact that the context is preserved (not merely -||| "there exists some g'") is the affine-accounting content of -||| the theorem — if `t` could run while duplicating a linear -||| variable, the reduct would require a *larger* context. -public export -preservation : Has g t a -> Step t t' -> Has g t' a -preservation _ _ = ?todo_preservation - ------------------------------------------------------------- --- Affine preservation (corollary) ------------------------------------------------------------- - -||| Affine preservation: if a term is well-typed with every -||| binding at quantity `One` or `Zero` and it steps, the reduct -||| is still well-typed in the same context. For Solo this is a -||| direct corollary of `preservation` above (the preserved -||| context already carries the quantity accounting), and is -||| stated here only for documentation. -public export -affinePreservation : Has g t a -> Step t t' -> Has g t' a -affinePreservation = preservation diff --git a/affinescript/docs/academic/formal-verification/solo-core/Syntax.idr b/affinescript/docs/academic/formal-verification/solo-core/Syntax.idr deleted file mode 100644 index c8d5233..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/Syntax.idr +++ /dev/null @@ -1,67 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell --- --- Syntax of the Solo core of AffineScript. --- --- Solo = simply-typed lambda calculus + Unit + pairs + sums + let, --- with every binder annotated by a QTT quantity q ∈ {0, 1, omega}. --- We use intrinsically-untyped de Bruijn indices here; Typing.idr --- then layers the QTT typing judgement on top. --- --- Deliberately EXCLUDED from Solo (deferred to Duet / Ensemble): --- * traits and effects --- * row polymorphism and records --- * refinement and dependent types --- * ownership (own / ref / mut) --- * modules, arrays, unsafe, handlers - -module Syntax - -import Quantity - -%default total - ------------------------------------------------------------- --- Types ------------------------------------------------------------- - -||| Solo types. The function arrow `TArr q a b` carries the -||| quantity with which its argument is consumed — matching the -||| `(q x : τ₁) → τ₂` shape in `docs/spec.md` §3.5 (with effects -||| stripped for Solo). -public export -data Ty : Type where - TUnit : Ty - TPair : Ty -> Ty -> Ty - TSum : Ty -> Ty -> Ty - TArr : Q -> Ty -> Ty -> Ty - -%name Ty a, b, c - ------------------------------------------------------------- --- Terms (de Bruijn) ------------------------------------------------------------- - -||| Solo terms, using de Bruijn indices represented as `Nat`. -||| -||| Note `Lam` carries the quantity annotation of the bound -||| variable and the domain type (following QTT). `Case` binds -||| one variable in each branch (the injected value). -public export -data Tm : Type where - Var : Nat -> Tm - UnitT : Tm - Lam : Q -> Ty -> Tm -> Tm - App : Tm -> Tm -> Tm - Pair : Tm -> Tm -> Tm - Fst : Tm -> Tm - Snd : Tm -> Tm - Inl : Ty -> Tm -> Tm -- type annotation = the *other* summand - Inr : Ty -> Tm -> Tm - Case : Tm -> Tm -> Tm -> Tm - -- scrutinee, left branch (binds 1), right branch (binds 1) - Let : Q -> Tm -> Tm -> Tm - -- let (q x) = e1 in e2 - -%name Tm t, t1, t2, u diff --git a/affinescript/docs/academic/formal-verification/solo-core/Typing.idr b/affinescript/docs/academic/formal-verification/solo-core/Typing.idr deleted file mode 100644 index 10b71e1..0000000 --- a/affinescript/docs/academic/formal-verification/solo-core/Typing.idr +++ /dev/null @@ -1,127 +0,0 @@ --- SPDX-License-Identifier: MPL-2.0 --- Copyright (c) Jonathan D.A. Jewell --- SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell --- --- QTT typing judgement for the Solo core. --- --- The judgement `Has g t a` means: term `t` has type `a` in --- context `g`, with `g`'s quantities exactly accounting for the --- uses of each bound variable inside `t`. --- --- These are the RULES, stated as data constructors. The meta- --- theorems (progress, preservation, affine-preservation) are --- stated in `Soundness.idr` and will be proved in weeks 3-12 of --- Track F1. --- --- Source of truth for the rule shapes: --- * docs/spec.md §3.1, §3.6 (T-Var, T-Lam, T-App, T-Let) --- * lib/quantity.ml — semiring operations and the "linear must --- be used once" discipline --- --- NOTE on one difference from docs/spec.md §3.6: the spec writes --- the rules in a "shared Γ" style, which is *not* by itself --- sufficient for affine preservation. The implementation in --- `lib/quantity.ml` reconstructs the QTT accounting via a --- post-hoc usage walk. Here we take the standard QTT --- presentation with context splitting (ctxAdd) — this is the --- formulation for which progress + preservation are provable, --- and is equivalent to the implementation's usage-walk for the --- Solo fragment. An explicit equivalence lemma is future work. - -module Typing - -import Quantity -import Syntax -import Context - -%default total - ------------------------------------------------------------- --- Variable lookup: "x has type a with quantity 1 here, 0 --- elsewhere" ------------------------------------------------------------- - -||| `HasVar g n a` says the de Bruijn variable `n` has type `a` -||| in `g`, where `g` is a context that assigns quantity `One` -||| to position `n` and quantity `Zero` to every other position. -||| This is the T-Var rule in QTT: "using a variable once uses -||| it exactly once, and uses nothing else". -public export -data HasVar : Ctx -> Nat -> Ty -> Type where - HVHere : HasVar (Snoc (ctxZero g) a One) Z a - HVThere : HasVar g n a - -> HasVar (Snoc g b Zero) (S n) a - ------------------------------------------------------------- --- The typing judgement ------------------------------------------------------------- - -||| `Has g t a` — in QTT context `g`, term `t` has type `a`. -||| Context splitting is explicit at T-App, T-Pair, T-Let, and -||| T-Case. T-Lam introduces a binder with a declared quantity. -public export -data Has : Ctx -> Tm -> Ty -> Type where - - ||| T-Var: variable use consumes quantity One at its position. - THVar : HasVar g n a - -> Has g (Var n) a - - ||| T-Unit: the unit term types in the all-zero context. - THUnit : Has (ctxZero g) UnitT TUnit - - ||| T-Lam: introduce a binding with quantity `q` and type `a`, - ||| type the body in the extended context. The resulting - ||| function type `TArr q a b` records the quantity. - THLam : Has (Snoc g a q) t b - -> Has g (Lam q a t) (TArr q a b) - - ||| T-App: the function is typed in `g1`, the argument in `g2` - ||| scaled by the function's parameter quantity `q`, and the - ||| whole application is typed in the pointwise sum - ||| `g1 + q * g2`. - THApp : Has g1 t1 (TArr q a b) - -> Has g2 t2 a - -> ctxAdd g1 (ctxScale q g2) = Just g - -> Has g (App t1 t2) b - - ||| T-Pair: product introduction splits the context additively. - THPair : Has g1 t1 a - -> Has g2 t2 b - -> ctxAdd g1 g2 = Just g - -> Has g (Pair t1 t2) (TPair a b) - - ||| T-Fst: projection does not split (a single subterm). - THFst : Has g t (TPair a b) - -> Has g (Fst t) a - - ||| T-Snd - THSnd : Has g t (TPair a b) - -> Has g (Snd t) b - - ||| T-Inl / T-Inr: sum introduction carries the other summand - ||| as the annotation, matching `Syntax.idr`. - THInl : Has g t a - -> Has g (Inl b t) (TSum a b) - THInr : Has g t b - -> Has g (Inr a t) (TSum a b) - - ||| T-Case: the scrutinee is typed in `g1`; each branch binds - ||| the injected value with quantity One and is typed in `g2` - ||| extended with that binder. Branches must agree on `g2` and - ||| on the result type. The whole `case` is typed in - ||| `g1 + g2`. - THCase : Has g1 t (TSum a b) - -> Has (Snoc g2 a One) tL c - -> Has (Snoc g2 b One) tR c - -> ctxAdd g1 g2 = Just g - -> Has g (Case t tL tR) c - - ||| T-Let: bind `t1` with declared quantity `q`. The RHS is - ||| typed in `g1` and is then SCALED by `q` before being added - ||| to `g2` (the body's context). This is the usual QTT let - ||| rule and matches how `lib/typecheck.ml` threads quantities - ||| through `let` bindings. - THLet : Has g1 t1 a - -> Has (Snoc g2 a q) t2 b - -> ctxAdd (ctxScale q g1) g2 = Just g - -> Has g (Let q t1 t2) b diff --git a/affinescript/docs/academic/mathematical-foundations/categorical-semantics.adoc b/affinescript/docs/academic/mathematical-foundations/categorical-semantics.adoc deleted file mode 100644 index c9128c2..0000000 --- a/affinescript/docs/academic/mathematical-foundations/categorical-semantics.adoc +++ /dev/null @@ -1,478 +0,0 @@ -== Categorical Semantics of AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete - -=== Abstract - -This document provides a categorical semantics for AffineScript, -interpreting the type system in suitable categorical structures. We -construct models using: 1. Locally Cartesian closed categories (LCCCs) -for dependent types 2. Symmetric monoidal categories for linear/affine -types 3. Graded comonads for quantitative types 4. Freyd categories for -effects 5. Fibrations for refinement types - -=== 1. Introduction - -Categorical semantics provides: - A mathematical foundation independent -of syntax - Proof of consistency and relative consistency - Guidance for -language extensions - Connection to other mathematical structures - -AffineScript requires multiple categorical structures due to its -combination of features. - -=== 2. Preliminaries - -==== 2.1 Category Theory Basics - -*Definition 2.1 (Category)*: A category C consists of: - Objects: ob(C) -- Morphisms: C(A, B) for objects A, B - Identity: id_A : A → A - -Composition: g ∘ f : A → C for f : A → B, g : B → C - -satisfying identity and associativity laws. - -==== 2.2 Key Categorical Structures - -*Cartesian Closed Category (CCC)*: - Terminal object 1 - Binary products -A × B - Exponentials B^A (function spaces) - -*Locally Cartesian Closed Category (LCCC)*: - Each slice category C/Γ is -CCC - Models dependent types - -*Symmetric Monoidal Category (SMC)*: - Tensor product ⊗ - Unit object I -- Associativity, commutativity (up to isomorphism) - -*Symmetric Monoidal Closed Category (SMCC)*: - SMC with internal hom A ⊸ -B - Models linear types - -=== 3. Interpretation of Types - -==== 3.1 Base Types - -Interpret types as objects in category C: - -.... -⟦Unit⟧ = 1 (terminal object) -⟦Bool⟧ = 1 + 1 (coproduct) -⟦Nat⟧ = N (natural numbers object) -⟦Int⟧ = Z (integers) -.... - -==== 3.2 Function Types - -*Simple Functions* (in a CCC): - -.... -⟦τ → σ⟧ = ⟦σ⟧^⟦τ⟧ -.... - -*Linear Functions* (in a SMCC): - -.... -⟦τ ⊸ σ⟧ = ⟦τ⟧ ⊸ ⟦σ⟧ -.... - -*Effectful Functions* (in Freyd category): - -.... -⟦τ →{ε} σ⟧ = ⟦τ⟧ → T_ε(⟦σ⟧) -.... - -where T_ε is the monad/applicative functor for effect ε. - -==== 3.3 Product Types - -.... -⟦τ × σ⟧ = ⟦τ⟧ × ⟦σ⟧ (Cartesian product) -⟦τ ⊗ σ⟧ = ⟦τ⟧ ⊗ ⟦σ⟧ (Tensor product, linear) -.... - -==== 3.4 Sum Types - -.... -⟦τ + σ⟧ = ⟦τ⟧ + ⟦σ⟧ (Coproduct) -.... - -==== 3.5 Record Types (Row Polymorphism) - -Records are interpreted as dependent products over finite label sets: - -.... -⟦{l₁: τ₁, ..., lₙ: τₙ}⟧ = ∏_{i=1}^n ⟦τᵢ⟧ -.... - -With row polymorphism: - -.... -⟦{l: τ | ρ}⟧ = ⟦τ⟧ × ⟦{ρ}⟧ -.... - -=== 4. Dependent Types - -==== 4.1 Locally Cartesian Closed Categories - -For dependent types, we work in an LCCC C. - -*Contexts as Objects*: - -.... -⟦·⟧ = 1 -⟦Γ, x:τ⟧ = Σ_{⟦Γ⟧} ⟦τ⟧ (dependent sum in slice) -.... - -*Types as Objects in Slice*: - -.... -⟦Γ ⊢ τ⟧ ∈ ob(C/⟦Γ⟧) -.... - -==== 4.2 Π-Types - -*Interpretation*: - -.... -⟦Π(x:τ). σ⟧ = Π_τ(⟦σ⟧) -.... - -where Π_τ is the right adjoint to pullback along τ : ⟦τ⟧ → ⟦Γ⟧. - -*Adjunction*: - -.... -Σ_τ ⊣ τ* ⊣ Π_τ - -where: -Σ_τ : C/⟦Γ,x:τ⟧ → C/⟦Γ⟧ (dependent sum) -τ* : C/⟦Γ⟧ → C/⟦Γ,x:τ⟧ (weakening/pullback) -Π_τ : C/⟦Γ,x:τ⟧ → C/⟦Γ⟧ (dependent product) -.... - -==== 4.3 Σ-Types - -*Interpretation*: - -.... -⟦Σ(x:τ). σ⟧ = Σ_τ(⟦σ⟧) -.... - -==== 4.4 Identity Types - -*Interpretation* (in a category with path objects): - -.... -⟦a == b⟧ = Path_τ(⟦a⟧, ⟦b⟧) -.... - -where Path_τ is the path object functor. - -=== 5. Quantitative Types - -==== 5.1 Graded Comonads - -Quantities are modeled by a graded exponential comonad: - -*Definition 5.1*: A graded comonad on C indexed by semiring R is: - -Functors D_π : C → C for each π ∈ R - Natural transformations: - ε : D_1 -A → A (counit) - δ : D_\{π₁ × π₂} A → D_\{π₁}(D_\{π₂} A) -(comultiplication) - θ : D_0 A → I (dereliction for 0) - c : D_ω A → D_ω -A × D_ω A (contraction for ω) - w : D_ω A → I (weakening for ω) - -==== 5.2 Interpretation - -.... -⟦π τ⟧ = D_π(⟦τ⟧) - -⟦0 τ⟧ = D_0(⟦τ⟧) ≅ I (erased) -⟦1 τ⟧ = D_1(⟦τ⟧) ≅ ⟦τ⟧ (linear) -⟦ω τ⟧ = !⟦τ⟧ (exponential comonad) -.... - -==== 5.3 Quantity Semiring Structure - -The semiring laws correspond to: - -.... -D_0 ∘ D_π ≅ D_0 (0 × π = 0) -D_π ∘ D_0 ≅ D_0 (π × 0 = 0) -D_1 ∘ D_π ≅ D_π (1 × π = π) -D_π₁ ∘ D_π₂ ≅ D_{π₁×π₂} (multiplication) -.... - -=== 6. Effects - -==== 6.1 Freyd Categories - -Effects are modeled in a Freyd category (C, J, K): - C is a Cartesian -category (pure computations) - K is a category (effectful computations) -- J : C → K is identity on objects, cartesian on morphisms - -==== 6.2 Effect Algebra - -Effects form an algebra of operations and equations: - -*Definition 6.1 (Effect Theory)*: An effect theory is a Lawvere theory T -with: - Sorts (value types) - Operations: op : τ → σ - Equations between -terms - -==== 6.3 Free Monad Interpretation - -For an effect signature Σ: - -.... -⟦ε⟧ = Free_Σ -.... - -where Free_Σ is the free monad on the functor corresponding to Σ. - -==== 6.4 Handler Interpretation - -A handler for effect E is an E-algebra: - -.... -h : F_E(A) → A -.... - -where F_E is the effect functor. - -*Handle*: - -.... -⟦handle e with h⟧ = fold(h, ⟦e⟧) -.... - -==== 6.5 Effect Row Polymorphism - -Effect rows are interpreted as colimits: - -.... -⟦ε₁ | ε₂⟧ = ⟦ε₁⟧ ⊕ ⟦ε₂⟧ -.... - -where ⊕ is coproduct of effect theories. - -=== 7. Ownership and Borrowing - -==== 7.1 Presheaf Model - -Model ownership using presheaves over a category of regions: - -*Definition 7.1*: Let R be the category of regions with: - Objects: -Regions (lifetimes) - Morphisms: Inclusions ’a ≤ ’b - -A type with lifetime is a presheaf on R: - -.... -⟦ref['a] τ⟧ : R^op → Set -⟦ref['a] τ⟧('b) = if 'a ≤ 'b then ⟦τ⟧ else ∅ -.... - -==== 7.2 Affine Category - -Ownership uses an affine symmetric monoidal category: - Objects: Types -with ownership annotations - Morphisms: Functions respecting ownership - -Tensor: Combines owned values (linear) - Weakening: Allows dropping -(affine, not linear) - -==== 7.3 Borrow Semantics - -Borrows are modeled as comonadic access: - -.... -⟦ref τ⟧ = R(⟦τ⟧) (reader comonad) -⟦mut τ⟧ = S(⟦τ⟧) (state comonad, exclusive) -.... - -=== 8. Refinement Types - -==== 8.1 Fibrations - -Refinement types are modeled in a fibration: - -*Definition 8.1*: A fibration p : E → B is a functor with cartesian -liftings. - -For refinements: - B = types - E = refined types (types with predicates) -- p = forgetful functor - -==== 8.2 Predicate Interpretation - -.... -⟦{x: τ | φ}⟧ = {a ∈ ⟦τ⟧ | ⟦φ⟧(a) = true} -.... - -As a subobject: - -.... -⟦{x: τ | φ}⟧ ↣ ⟦τ⟧ -.... - -==== 8.3 Subset Types - -Using subset types in a topos: - -.... -⟦{x: τ | φ}⟧ = Σ_{a:⟦τ⟧} ⟦φ(a)⟧ -.... - -where ⟦φ(a)⟧ is a proposition (subsingleton). - -=== 9. Soundness - -==== 9.1 Interpretation of Terms - -Each typing judgment is interpreted as a morphism: - -.... -⟦Γ ⊢ e : τ⟧ : ⟦Γ⟧ → ⟦τ⟧ -.... - -==== 9.2 Soundness Theorem - -*Theorem 9.1 (Soundness)*: The interpretation is sound: 1. Well-typed -terms denote morphisms 2. Equal terms denote equal morphisms 3. -Reduction preserves denotation - -*Proof*: By induction on typing derivations, verifying categorical -equations. ∎ - -==== 9.3 Adequacy - -*Theorem 9.2 (Adequacy)*: For closed terms of observable type: - -.... -⟦e⟧ = ⟦e'⟧ implies e ≃ e' (observationally equivalent) -.... - -=== 10. Coherence - -==== 10.1 Coherence for Row Polymorphism - -*Theorem 10.1*: Row-polymorphic terms have unique interpretations up to -canonical isomorphism. - -The interpretation is independent of the order of record fields. - -==== 10.2 Coherence for Effects - -*Theorem 10.2*: Effect interpretations are coherent: different -derivations of the same typing judgment yield equal morphisms. - -==== 10.3 Coherence for Quantities - -*Theorem 10.3*: Quantity polymorphism is coherent: instantiation at -different quantities (respecting constraints) yields consistent -behavior. - -=== 11. Parametricity - -==== 11.1 Relational Interpretation - -Define relations over the categorical model: - -*Definition 11.1*: For types τ, the relational interpretation ⟦τ⟧_R is: -- ⟦α⟧_R = R (a relation, the parameter) - ⟦τ → σ⟧_R = \{(f, g) | ∀(a,b) -∈ ⟦τ⟧_R. (f a, g b) ∈ ⟦σ⟧_R} - … - -==== 11.2 Parametricity Theorem - -*Theorem 11.1 (Parametricity)*: For any polymorphic term -`+Γ ⊢ e : ∀α. τ+`: - -.... -∀ types A, B. ∀ relation R ⊆ A × B. -(⟦e⟧(A), ⟦e⟧(B)) ∈ ⟦τ⟧_R[R/α] -.... - -==== 11.3 Free Theorems - -From parametricity, we derive free theorems: - -*Example*: For `+f : ∀α. List[α] → List[α]+`: - -.... -∀ g : A → B. map g ∘ f_A = f_B ∘ map g -.... - -=== 12. Models - -==== 12.1 Set-Theoretic Model - -The simplest model uses Set: - Types as sets - Functions as -set-theoretic functions - Effects as free monads - -==== 12.2 Domain-Theoretic Model - -For recursion, use CPO (complete partial orders): - Types as CPOs - -Functions as continuous functions - Recursion as least fixed points - -==== 12.3 Topos Model - -For full dependent types, use a topos: - Types as objects in a topos - -Dependent types in slice topoi - Refinements as subobjects - -==== 12.4 Realizability Model - -For extraction to computable functions: - Types as assemblies - -Functions as realized by programs - Connects to extraction - -=== 13. Examples - -==== 13.1 State Monad - -The state effect S[σ] is modeled by: - -.... -⟦τ →{State[σ]} ρ⟧ = σ × ⟦τ⟧ → σ × ⟦ρ⟧ -.... - -State transformers. - -==== 13.2 Linear Function Space - -The linear function space: - -.... -⟦τ ⊸ σ⟧ = ⟦τ⟧ ⊸ ⟦σ⟧ -.... - -where ⊸ is the internal hom in a SMCC. - -==== 13.3 Dependent Sum - -The dependent sum: - -.... -⟦Σ(x:τ). σ⟧ = Σ_{a ∈ ⟦τ⟧} ⟦σ⟧(a) -.... - -A dependent pair (a, b) where b : σ[a/x]. - -=== 14. Related Work - -[arabic] -. *Categorical Logic*: Lambek & Scott, Jacobs -. *LCCCs for Dependent Types*: Seely (1984), Hofmann (1997) -. *Linear Logic Categories*: Benton, Bierman, de Paiva, Hyland -. *Graded Comonads*: Gaboardi et al., Brunel et al. -. *Freyd Categories for Effects*: Power & Robinson, Levy -. *Fibrations for Refinements*: Jacobs, Hermida - -=== 15. References - -[arabic] -. Lambek, J., & Scott, P. J. (1986). _Introduction to Higher Order -Categorical Logic_. Cambridge. -. Jacobs, B. (1999). _Categorical Logic and Type Theory_. Elsevier. -. Seely, R. A. G. (1984). Locally Cartesian Closed Categories and Type -Theory. _Math. Proc. Cambridge Phil. Soc._ -. Benton, N. (1995). A Mixed Linear and Non-Linear Logic. _CSL_. -. Power, J., & Robinson, E. (1997). Premonoidal Categories and Notions -of Computation. _MSCS_. -. Moggi, E. (1991). Notions of Computation and Monads. _Information and -Computation_. - -''''' - -*Document Metadata*: - This document is pure theory; no implementation -dependencies - Mechanized proof: See `+mechanized/coq/Semantics.v+` -(stub) diff --git a/affinescript/docs/academic/mathematical-foundations/complexity-analysis.adoc b/affinescript/docs/academic/mathematical-foundations/complexity-analysis.adoc deleted file mode 100644 index b86dc20..0000000 --- a/affinescript/docs/academic/mathematical-foundations/complexity-analysis.adoc +++ /dev/null @@ -1,370 +0,0 @@ -== Complexity and Decidability Analysis - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -theoretical analysis - -=== Abstract - -This document analyzes the computational complexity and decidability -properties of AffineScript’s type system and related decision -procedures. We establish complexity bounds for type checking, type -inference, effect inference, and SMT-based refinement checking. - -=== 1. Introduction - -Understanding complexity is crucial for: - Practical compiler -implementation - Theoretical foundations - Identifying expensive -features - Guiding language design decisions - -=== 2. Type Checking Complexity - -==== 2.1 Simple Type Checking - -*Theorem 2.1*: Type checking for the simply-typed fragment of -AffineScript is decidable in O(n) time, where n is the size of the -program. - -*Proof*: Each expression is visited once, with constant-time type -operations. ∎ - -==== 2.2 Polymorphic Type Checking - -*Theorem 2.2*: Type checking for the ML-style polymorphic fragment is -decidable in O(n) time (given type annotations). - -With full type inference, the complexity increases. - -==== 2.3 Type Inference Complexity - -*Theorem 2.3*: Hindley-Milner type inference is decidable in: - O(n) -time for programs without let-polymorphism - O(n × α(n)) time in -practice (quasi-linear, using union-find) - DEXPTIME-complete in the -worst case - -The exponential worst case occurs with nested let-expressions creating -exponentially large types. - -*Example of exponential blowup*: - -.... -let x₁ = λx. (x, x) in -let x₂ = λx. x₁(x₁(x)) in -let x₃ = λx. x₂(x₂(x)) in -... -.... - -==== 2.4 Row Polymorphism - -*Theorem 2.4*: Row unification is decidable in O(n²) time in the worst -case, O(n) in typical cases. - -*Proof*: Row rewriting may require examining all labels, but the number -of labels is bounded by program size. ∎ - -=== 3. Dependent Type Checking - -==== 3.1 Type-Level Computation - -*Theorem 3.1*: For the restricted type-level language (natural -arithmetic, no general recursion), normalization is decidable. - -*Proof*: The type-level language is strongly normalizing (no fix at the -type level). ∎ - -==== 3.2 Definitional Equality - -*Theorem 3.2*: Checking definitional equality τ₁ ≡ τ₂ is decidable when: -1. Both types normalize 2. No undecidable type-level operations - -*Complexity*: O(n) for normalized types, where n is the size of the -normal form. - -==== 3.3 Undecidable Extensions - -*Theorem 3.3*: With unrestricted type-level recursion, type checking -becomes undecidable. - -Adding `+fix+` at the type level allows encoding the halting problem. - -=== 4. Refinement Type Checking - -==== 4.1 SMT Fragment Decidability - -*Theorem 4.1*: For the quantifier-free fragment of linear integer -arithmetic (QF_LIA), SMT checking is decidable. - -*Complexity*: NP-complete for satisfiability. - -==== 4.2 Presburger Arithmetic - -*Theorem 4.2*: Presburger arithmetic (linear arithmetic with -quantifiers) is decidable. - -*Complexity*: At least doubly exponential in the worst case. - -==== 4.3 Nonlinear Arithmetic - -*Theorem 4.3*: Nonlinear integer arithmetic is undecidable. - -*Corollary*: Refinements involving multiplication of variables require -approximations. - -==== 4.4 Practical Refinement Checking - -In practice, refinement checking is: - Fast for common patterns (linear -constraints) - Expensive for quantified formulas - Undecidable for -nonlinear integer arithmetic - Approximated using timeouts - -*Implementation*: Use SMT solver with timeout; report "`unknown`" on -timeout. - -=== 5. Effect System Complexity - -==== 5.1 Effect Checking - -*Theorem 5.1*: Effect checking (given effect annotations) is decidable -in O(n) time. - -*Proof*: Effect operations are checked locally; effect rows are compared -by set equality. ∎ - -==== 5.2 Effect Inference - -*Theorem 5.2*: Effect inference with row polymorphism is decidable in -O(n²) time. - -Similar to row polymorphism for records, but with simpler constraints -(no lack constraints typically needed). - -==== 5.3 Handler Coverage - -*Theorem 5.3*: Checking handler completeness (all operations handled) is -decidable in O(|ops|) time. - -=== 6. Ownership Checking - -==== 6.1 Borrow Checking - -*Theorem 6.1*: Borrow checking for AffineScript is decidable in O(n²) -time. - -*Proof Sketch*: 1. Build dataflow graph: O(n) 2. Compute lifetimes: O(n) -3. Check conflicts: O(n²) pairwise - -In practice, linear with good data structures. - -==== 6.2 Lifetime Inference - -*Theorem 6.2*: Lifetime inference is decidable and produces principal -lifetimes. - -*Complexity*: O(n) for building constraints, O(n × α(n)) for solving -(union-find). - -==== 6.3 Non-Lexical Lifetimes - -*Theorem 6.3*: NLL-style lifetime inference is decidable via dataflow -analysis. - -*Complexity*: O(n × k) where k is the iteration count (bounded by -program size). - -=== 7. Subtyping - -==== 7.1 Structural Subtyping - -*Theorem 7.1*: Structural subtyping for records and variants is -decidable in O(n) time. - -*Proof*: Width subtyping requires checking field presence; depth -subtyping is recursive but bounded by type depth. ∎ - -==== 7.2 Subtyping with Refinements - -*Theorem 7.2*: Subtyping with refinements reduces to SMT validity -checking. - -.... -{x: τ | φ} <: {x: τ | ψ} iff ∀x. φ ⟹ ψ -.... - -Complexity determined by SMT fragment. - -==== 7.3 Higher-Rank Polymorphism - -*Theorem 7.3*: Type checking with predicative higher-rank polymorphism -is decidable. - -*Theorem 7.4*: Type checking with impredicative higher-rank polymorphism -is undecidable. - -AffineScript uses predicative polymorphism. - -=== 8. Decidability Summary - -[cols=",,",options="header",] -|=== -|Feature |Decidable |Complexity -|Simple types |✓ |O(n) -|HM inference |✓ |O(n) typical, DEXPTIME worst -|Row polymorphism |✓ |O(n²) -|Dependent types (restricted) |✓ |O(n) after normalization -|Refinements (QF_LIA) |✓ |NP-complete -|Refinements (nonlinear) |✗ |Undecidable -|Effect checking |✓ |O(n) -|Borrow checking |✓ |O(n²) -|Subtyping (structural) |✓ |O(n) -|Higher-rank (predicative) |✓ |O(n) -|Higher-rank (impredicative) |✗ |Undecidable -|=== - -=== 9. Termination Analysis - -==== 9.1 Total Functions - -*Theorem 9.1*: Verifying totality (termination for all inputs) is -undecidable in general. - -*Corollary*: The `+total+` annotation cannot be automatically verified -for all functions. - -==== 9.2 Structural Recursion - -*Theorem 9.2*: Structural recursion on well-founded data types -terminates. - -AffineScript can verify totality for: - Primitive recursion on naturals -- Structural recursion on algebraic data types - Recursion with explicit -well-founded measures - -==== 9.3 Totality Checker - -`+[IMPL-DEP: termination-checker]+` - -Approach: 1. Check for structural recursion 2. Verify decreasing -arguments 3. For complex recursion, require explicit measure - -=== 10. Space Complexity - -==== 10.1 Type Representation - -*Theorem 10.1*: Types may grow exponentially with let-polymorphism. - -*Mitigation*: Use sharing (DAG representation) for O(n) space. - -==== 10.2 Context Representation - -*Theorem 10.2*: Contexts require O(n) space for n bindings. - -==== 10.3 Proof Terms - -For dependent types with proof terms, space is proportional to proof -size. - -*Mitigation*: Erase proofs at runtime (zero-quantity). - -=== 11. Parallelization - -==== 11.1 Type Checking Parallelization - -*Theorem 11.1*: Type checking is parallelizable at module boundaries. - -*Speedup*: O(n/p) with p processors for n modules. - -==== 11.2 Effect Inference Parallelization - -Effect constraints can be gathered in parallel, solved centrally. - -==== 11.3 SMT Query Parallelization - -Independent refinement checks can run in parallel. - -=== 12. Algorithmic Optimizations - -==== 12.1 Incremental Type Checking - -On program modification: - Re-check only affected parts - Cache type -inference results - Complexity: O(Δ) where Δ is change size - -==== 12.2 Lazy Normalization - -Normalize type-level terms on demand: - Cache normal forms - Share -subterms - -==== 12.3 Constraint Solving Strategies - -For type inference: - Use union-find for equality constraints - Solve in -dependency order - Fail fast on conflicts - -=== 13. Worst-Case Examples - -==== 13.1 Type Inference Blowup - -[source,affinescript] ----- -let f1 = λx. (x, x) -let f2 = λx. f1(f1(x)) -let f3 = λx. f2(f2(x)) -// Type of f3 has size 2^8 = 256 ----- - -==== 13.2 Row Unification Explosion - -[source,affinescript] ----- -fn f[α, β, γ, δ, ε]( - r: {a: α, b: β, c: γ, d: δ, e: ε, ..ρ} -) -> ... ----- - -Many constraints from one signature. - -==== 13.3 Refinement Timeout - -[source,affinescript] ----- -fn complex( - x: {v: Int | is_prime(v) ∧ v > 10^100} -) -> ... ----- - -SMT may timeout on complex predicates. - -=== 14. Implementation Recommendations - -[arabic] -. *Use sharing* for type representation -. *Implement incremental checking* for IDE support -. *Set SMT timeouts* for refinements -. *Cache results* aggressively -. *Parallelize* across modules -. *Stratify* type-level computation -. *Provide escape hatches* (unsafe blocks, assertions) - -=== 15. Open Problems - -[arabic] -. *Optimal type inference* with row polymorphism -. *Practical nonlinear arithmetic* for refinements -. *Automatic termination analysis* for complex recursion -. *Principal types* for dependent types -. *Optimal borrow checking* algorithm - -=== 16. References - -[arabic] -. Henglein, F. (1993). Type Inference with Polymorphic Recursion. -_TOPLAS_. -. Kfoury, A. J., Tiuryn, J., & Urzyczyn, P. (1993). The Undecidability -of the Semi-unification Problem. _I&C_. -. Pottier, F. (2014). Hindley-Milner Elaboration in Applicative Style. -_ICFP_. -. Rondon, P., Kawaguchi, M., & Jhala, R. (2008). Liquid Types. _PLDI_. -. De Moura, L., & Bjørner, N. (2008). Z3: An Efficient SMT Solver. -_TACAS_. -. Weiss, A., et al. (2019). Oxide: The Essence of Rust. _arXiv_. - -''''' - -*Document Metadata*: - This document is theoretical analysis - -Implementation guidance: See `+[IMPL-DEP]+` markers diff --git a/affinescript/docs/academic/mathematical-foundations/logic-foundations.adoc b/affinescript/docs/academic/mathematical-foundations/logic-foundations.adoc deleted file mode 100644 index a3037f8..0000000 --- a/affinescript/docs/academic/mathematical-foundations/logic-foundations.adoc +++ /dev/null @@ -1,532 +0,0 @@ -== Logic Foundations of AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -theoretical framework - -=== Abstract - -This document presents the logical foundations of AffineScript’s type -system through the lens of the Curry-Howard correspondence. We establish -connections between AffineScript types and logical propositions, -programs and proofs, type checking and proof checking. - -=== 1. Introduction - -The Curry-Howard correspondence reveals deep connections: - -[cols=",",options="header",] -|=== -|Type Theory |Logic -|Types |Propositions -|Programs |Proofs -|Type checking |Proof checking -|Type inhabitation |Provability -|Normalization |Cut elimination -|=== - -AffineScript extends this to: - Quantitative types ↔ Linear logic - -Effects ↔ Modal logic / Computational interpretations - Ownership ↔ -Affine/linear logic - -=== 2. Propositional Logic - -==== 2.1 Intuitionistic Propositional Logic - -The simply-typed fragment corresponds to intuitionistic propositional -logic: - -[cols=",,",options="header",] -|=== -|Type |Proposition |Connective -|`+τ → σ+` |P ⟹ Q |Implication -|`+τ × σ+` |P ∧ Q |Conjunction -|`+τ + σ+` |P ∨ Q |Disjunction -|`+Unit+` |⊤ |Truth -|`+Void+` |⊥ |Falsity -|=== - -==== 2.2 Introduction and Elimination Rules - -*Implication*: - -.... - Γ, P ⊢ Q Γ ⊢ P ⟹ Q Γ ⊢ P - ─────────── ⟹I ───────────────────── ⟹E - Γ ⊢ P ⟹ Q Γ ⊢ Q -.... - -Corresponds to: - -.... - Γ, x:τ ⊢ e : σ Γ ⊢ f : τ → σ Γ ⊢ a : τ - ───────────────── ──────────────────────────── - Γ ⊢ λx. e : τ → σ Γ ⊢ f a : σ -.... - -*Conjunction*: - -.... - Γ ⊢ P Γ ⊢ Q Γ ⊢ P ∧ Q Γ ⊢ P ∧ Q - ────────────── ∧I ───────── ∧E₁ ───────── ∧E₂ - Γ ⊢ P ∧ Q Γ ⊢ P Γ ⊢ Q -.... - -Corresponds to tuples: - -.... - Γ ⊢ e₁ : τ Γ ⊢ e₂ : σ Γ ⊢ e : τ × σ Γ ⊢ e : τ × σ - ────────────────────── ────────────── ────────────── - Γ ⊢ (e₁, e₂) : τ × σ Γ ⊢ fst e : τ Γ ⊢ snd e : σ -.... - -*Disjunction*: - -.... - Γ ⊢ P Γ ⊢ Q Γ ⊢ P ∨ Q Γ, P ⊢ R Γ, Q ⊢ R - ─────── ∨I₁ ─────── ∨I₂ ───────────────────────────────── ∨E - Γ ⊢ P ∨ Q Γ ⊢ P ∨ Q Γ ⊢ R -.... - -Corresponds to sum types: - -.... - Γ ⊢ e : τ Γ ⊢ e : σ Γ ⊢ e : τ + σ ... - ─────────────── ─────────────── ─────────────────── - Γ ⊢ inl e : τ + σ Γ ⊢ inr e : τ + σ Γ ⊢ case e ... : ρ -.... - -==== 2.3 Negation - -Negation is encoded as implication to falsity: - -.... -¬P ≡ P → ⊥ -.... - -In AffineScript: - -[source,affinescript] ----- -type Not[P] = P -> Void - -fn absurd[A](v: Void) -> A { - case v { } // empty case -} ----- - -=== 3. Predicate Logic - -==== 3.1 Universal Quantification - -Polymorphism corresponds to universal quantification: - -.... -∀α. P(α) ↔ ∀α:Type. τ(α) -.... - -*Introduction*: - -.... - Γ ⊢ P(α) α not free in Γ Γ, α:Type ⊢ e : τ α ∉ FTV(Γ) - ───────────────────────── ──────────────────────────────── - Γ ⊢ ∀α. P(α) Γ ⊢ Λα. e : ∀α. τ -.... - -*Elimination*: - -.... - Γ ⊢ ∀α. P(α) Γ ⊢ t : Type Γ ⊢ e : ∀α. τ Γ ⊢ σ : Type - ──────────────────────────── ────────────────────────────── - Γ ⊢ P(t) Γ ⊢ e [σ] : τ[σ/α] -.... - -==== 3.2 Existential Quantification - -Existential types: - -.... -∃α. P(α) ↔ ∃α:Type. τ(α) -.... - -Corresponds to: - -[source,affinescript] ----- -type Exists[F] = (α: Type, F[α]) // existential package ----- - -Introduction (packing): - -.... - Γ ⊢ e : τ[σ/α] - ────────────────────────── - Γ ⊢ pack[σ](e) : ∃α. τ -.... - -Elimination (unpacking): - -.... - Γ ⊢ e₁ : ∃α. τ Γ, α:Type, x:τ ⊢ e₂ : σ α ∉ FTV(σ) - ─────────────────────────────────────────────────────── - Γ ⊢ unpack e₁ as (α, x) in e₂ : σ -.... - -=== 4. Linear Logic - -==== 4.1 Linear Connectives - -AffineScript’s quantitative types correspond to linear logic: - -[cols=",",options="header",] -|=== -|Quantity |Linear Logic -|0 |Erasure (?) -|1 |Linear (exact) -|ω |Exponential (!) -|=== - -[cols=",",options="header",] -|=== -|Type |Linear Connective -|`+τ ⊸ σ+` |Linear implication -|`+τ ⊗ σ+` |Multiplicative conjunction (tensor) -|`+τ & σ+` |Additive conjunction (with) -|`+τ ⊕ σ+` |Additive disjunction (plus) -|`+!τ+` |Of course (exponential) -|`+?τ+` |Why not (dual exponential) -|=== - -==== 4.2 Linear Implication - -.... - Γ, x:τ ⊢ e : σ x used exactly once in e - ────────────────────────────────────────── - Γ ⊢ λx. e : τ ⊸ σ -.... - -==== 4.3 Multiplicative Conjunction - -Both components must be used: - -.... - Γ ⊢ e₁ : τ Δ ⊢ e₂ : σ - ───────────────────────── - Γ, Δ ⊢ (e₁, e₂) : τ ⊗ σ -.... - -==== 4.4 Exponential Modality - -The `+!+` modality allows unrestricted use: - -.... - !τ ≡ ω τ (omega quantity) -.... - -Rules: - -.... - Γ ⊢ e : τ !Γ ⊢ e : τ - ───────────── ───────────── - !Γ ⊢ e : τ Γ ⊢ e : !τ - - (contraction) (promotion) -.... - -==== 4.5 Affine Logic - -AffineScript is actually affine (can drop, not required to use): - -.... - Γ, x:τ ⊢ e : σ x not used - ──────────────────────────── - Γ, x:τ ⊢ e : σ (weakening allowed) -.... - -=== 5. Dependent Types as Logic - -==== 5.1 Dependent Product (Π-Types) - -.... -Π(x:A). B(x) ↔ ∀x:A. P(x) -.... - -Full predicate logic: - -.... - Γ, x:A ⊢ P(x) x ∉ FV(Γ) - ────────────────────────── - Γ ⊢ ∀x:A. P(x) -.... - -==== 5.2 Dependent Sum (Σ-Types) - -.... -Σ(x:A). B(x) ↔ ∃x:A. P(x) -.... - -Existential quantification over terms. - -==== 5.3 Identity Types - -Propositional equality: - -.... -a = b : A ↔ a == b : Type -.... - -The type `+a == b+` is inhabited iff a and b are definitionally equal. - -==== 5.4 Propositions as Types - -*Theorem 5.1 (Curry-Howard for Dependent Types)*: - -.... -Γ ⊢ e : τ iff Γ ⊢ τ : Prop and e is a proof of τ -.... - -=== 6. Modal Logic - -==== 6.1 Necessity and Possibility - -Effects can be viewed through modal logic: - -[cols=",",options="header",] -|=== -|Modal |Effect -|□P (necessarily P) |Pure computation -|◇P (possibly P) |Effectful computation -|=== - -==== 6.2 Computational Interpretations - -Moggi’s computational lambda calculus: - -.... -T(A) ≡ computation that may produce A -.... - -Effects as modalities: - -.... -⊢ e : A (pure: e is of type A) -⊢ e : T(A) (effectful: e computes to type A) -.... - -==== 6.3 Graded Modalities - -Quantities as graded modalities: - -.... -□_π A (A used with quantity π) -.... - -=== 7. Refinement Types as Subset Logic - -==== 7.1 Comprehension - -Refinement types correspond to set comprehension: - -.... -{x: τ | φ} ↔ {x ∈ ⟦τ⟧ | φ(x)} -.... - -==== 7.2 Subtyping as Implication - -.... -{x: τ | φ} <: {x: τ | ψ} iff ∀x:τ. φ(x) ⟹ ψ(x) -.... - -==== 7.3 Verification Conditions - -Refinement type checking generates logical formulas: - -.... -Γ ⊢ e : {x: τ | φ} generates ⟦Γ⟧ ⟹ φ[⟦e⟧/x] -.... - -=== 8. Proof Irrelevance - -==== 8.1 Erased Proofs - -Proofs at quantity 0 are erased: - -.... -(0 pf : P) → Q -- proof pf is erased at runtime -.... - -==== 8.2 Proof Irrelevance - -For propositions (types with at most one inhabitant): - -.... -∀p₁, p₂ : P. p₁ = p₂ -.... - -Proofs are unique, so they can be erased. - -==== 8.3 SProp (Strict Propositions) - -Types that are proof-irrelevant: - -[source,affinescript] ----- -SProp ⊂ Type -- strict propositions ----- - -=== 9. Classical vs Intuitionistic - -==== 9.1 Constructive Mathematics - -AffineScript is constructive: - Proofs are programs - Existence proofs -provide witnesses - No excluded middle - -==== 9.2 Excluded Middle - -The law of excluded middle is not provable: - -.... --- This type is not inhabited in general: -type LEM[P] = Either[P, Not[P]] -.... - -==== 9.3 Double Negation - -Double negation elimination is not valid: - -.... --- Cannot implement: -fn dne[P](nnp: Not[Not[P]]) -> P { ... } -.... - -But double negation translation is possible for classical reasoning. - -==== 9.4 Axiom of Choice - -The axiom of choice: - -.... --- Can be stated but not proven: -fn choice[A, B]( - f: (a: A) -> Exists[λb. R(a, b)] -) -> Exists[λg. (a: A) -> R(a, g(a))] -.... - -=== 10. Proof-Carrying Code - -==== 10.1 Certified Programs - -Programs carry proofs of their properties: - -[source,affinescript] ----- -fn certified_sort(xs: List[Int]) - -> (ys: List[Int], pf: sorted(ys) ∧ permutation(xs, ys)) ----- - -==== 10.2 Proof Extraction - -Proofs can be extracted as programs: - -.... -⊢ ∀x:Nat. ∃y:Nat. x < y -↓ (extract) -succ : Nat → Nat -.... - -==== 10.3 Program Verification - -Verified by construction: - -[source,affinescript] ----- -fn verified_div( - x: Int, - y: {v: Int | v ≠ 0}, - 0 _: y ≠ 0 -- proof (erased) -) -> Int ----- - -=== 11. Consistency - -==== 11.1 Logical Consistency - -*Theorem 11.1*: The type `+Void+` is uninhabited in AffineScript. - -.... -⊬ e : Void for any closed e -.... - -*Proof*: By strong normalization and inspection of canonical forms. ∎ - -==== 11.2 Relative Consistency - -*Theorem 11.2*: AffineScript’s type system is consistent relative to: - -Martin-Löf Type Theory (for dependent types) - Linear Logic (for -quantities) - Classical set theory (for semantics) - -==== 11.3 Adding Axioms - -Care must be taken with axioms: - -[source,affinescript] ----- --- DANGEROUS: Makes system inconsistent -axiom absurd : Void ----- - -Safe axioms include: - Functional extensionality - Propositional -extensionality - Univalence (with care) - -=== 12. Proof Automation - -==== 12.1 Tactics - -Proof search strategies: - `+auto+`: Automatic proof search - `+simp+`: -Simplification - `+induction+`: Structural induction - `+smt+`: SMT -solver invocation - -==== 12.2 Decision Procedures - -Automated for: - Propositional logic (SAT) - Linear arithmetic (LIA) - -Presburger arithmetic - Equality reasoning (congruence closure) - -==== 12.3 Interactive Proofs - -For complex proofs: - -[source,affinescript] ----- -theorem example : ∀n:Nat. n + 0 = n := by - intro n - induction n with - | zero => refl - | succ n ih => simp [add_succ, ih] ----- - -`+[IMPL-DEP: proof-assistant]+` Interactive proof mode pending. - -=== 13. Related Work - -[arabic] -. *Curry-Howard*: Curry (1934), Howard (1980) -. *Linear Logic*: Girard (1987) -. *Martin-Löf Type Theory*: Martin-Löf (1984) -. *Calculus of Constructions*: Coquand & Huet (1988) -. *Propositions as Types*: Wadler (2015) -. *Linear Type Theory*: Pfenning et al. - -=== 14. References - -[arabic] -. Howard, W. A. (1980). The Formulae-as-Types Notion of Construction. -_Curry Festschrift_. -. Girard, J.-Y. (1987). Linear Logic. _TCS_. -. Martin-Löf, P. (1984). _Intuitionistic Type Theory_. Bibliopolis. -. Wadler, P. (2015). Propositions as Types. _CACM_. -. Pfenning, F., & Davies, R. (2001). A Judgmental Reconstruction of -Modal Logic. _MSCS_. - -''''' - -*Document Metadata*: - This document is pure logic theory - -Implementation: Type checker provides the proof checker diff --git a/affinescript/docs/academic/mechanized/agda/README.adoc b/affinescript/docs/academic/mechanized/agda/README.adoc deleted file mode 100644 index 75492d2..0000000 --- a/affinescript/docs/academic/mechanized/agda/README.adoc +++ /dev/null @@ -1,166 +0,0 @@ -== AffineScript Agda Formalization - -*Status*: Stub / Planned - -This directory will contain the mechanized Agda proof development for -AffineScript’s metatheory. - -=== Planned Structure - -.... -agda/ -├── AffineScript.agda -- Main module -├── Syntax/ -│ ├── Types.agda -- Type syntax -│ ├── Terms.agda -- Term syntax -│ └── Contexts.agda -- Context definitions -├── Typing/ -│ ├── Rules.agda -- Typing rules -│ ├── Quantities.agda -- QTT -│ └── Decidable.agda -- Decidable type checking -├── Semantics/ -│ ├── Reduction.agda -- Small-step semantics -│ ├── Values.agda -- Value predicates -│ └── Evaluation.agda -- Big-step evaluator -├── Metatheory/ -│ ├── Substitution.agda -- Substitution lemmas -│ ├── Progress.agda -- Progress theorem -│ └── Preservation.agda -- Preservation theorem -├── Effects/ -│ ├── Signatures.agda -- Effect signatures -│ ├── Handlers.agda -- Handler typing -│ └── Safety.agda -- Effect safety -└── Ownership/ - ├── Model.agda -- Ownership model - └── Borrowing.agda -- Borrow checking -.... - -=== Dependencies - -* Agda 2.6.4+ -* agda-stdlib 2.0+ - -=== Building - -[source,bash] ----- -# Check all modules -agda --safe AffineScript.agda - -# Generate HTML documentation -agda --html AffineScript.agda - -# Check with flags -agda --without-K --safe AffineScript.agda ----- - -=== TODO - -==== Phase 1: Core Language - -* [ ] Intrinsically-typed syntax -* [ ] Substitution via categories -* [ ] Progress and preservation - -==== Phase 2: Quantities - -* [ ] Semiring structure -* [ ] Graded contexts -* [ ] Quantity erasure - -==== Phase 3: Effects - -* [ ] Effect algebras -* [ ] Free monad interpretation -* [ ] Handler correctness - -=== Example Structure - -[source,agda] ----- --- Syntax/Types.agda -module Syntax.Types where - -open import Data.Nat using (ℕ) -open import Data.Fin using (Fin) - --- Types indexed by number of free type variables -data Ty (n : ℕ) : Set where - `Unit : Ty n - `Bool : Ty n - `Int : Ty n - `_⇒_ : Ty n → Ty n → Ty n - `∀_ : Ty (suc n) → Ty n - `Var : Fin n → Ty n - --- Syntax/Terms.agda -module Syntax.Terms where - -open import Syntax.Types - --- Well-scoped terms -data Term (n : ℕ) (Γ : Vec (Ty 0) n) : Ty 0 → Set where - `var : ∀ {τ} (x : Fin n) → lookup Γ x ≡ τ → Term n Γ τ - `lam : ∀ {τ₁ τ₂} → Term (suc n) (τ₁ ∷ Γ) τ₂ → Term n Γ (τ₁ `⇒ τ₂) - `app : ∀ {τ₁ τ₂} → Term n Γ (τ₁ `⇒ τ₂) → Term n Γ τ₁ → Term n Γ τ₂ - `unit : Term n Γ `Unit - `true : Term n Γ `Bool - `false : Term n Γ `Bool - --- Metatheory/Progress.agda -module Metatheory.Progress where - -open import Syntax.Terms -open import Semantics.Reduction -open import Semantics.Values - -data Progress {τ : Ty 0} (e : Term 0 [] τ) : Set where - step : ∀ {e'} → e ⟶ e' → Progress e - done : Value e → Progress e - -progress : ∀ {τ} (e : Term 0 [] τ) → Progress e -progress (`var x p) with () ← x -progress (`lam e) = done V-lam -progress (`app e₁ e₂) with progress e₁ -... | step s₁ = step (ξ-app-L s₁) -... | done V-lam with progress e₂ -... | step s₂ = step (ξ-app-R V-lam s₂) -... | done v₂ = step (β-lam v₂) -progress `unit = done V-unit -progress `true = done V-true -progress `false = done V-false - --- Metatheory/Preservation.agda -module Metatheory.Preservation where - -preservation : ∀ {τ} {e e' : Term 0 [] τ} - → e ⟶ e' - → Term 0 [] τ -- e' has the same type (trivial with intrinsic typing) -preservation {e' = e'} _ = e' - --- With intrinsically-typed syntax, preservation is "free"! ----- - -=== Intrinsic vs Extrinsic Typing - -Agda is particularly well-suited for *intrinsically-typed* -representations where: - Terms are indexed by their types - Ill-typed -terms are not representable - Preservation is automatic - -This makes many proofs trivial but requires more effort upfront. - -=== Advantages of Agda - -[arabic] -. *Dependent types*: Natural for indexed syntax -. *Pattern matching*: Clean proof style -. *Mixfix operators*: Readable syntax -. *Unicode support*: Mathematical notation -. *Cubical Agda*: Univalence if needed - -=== References - -[arabic] -. Programming Language Foundations in Agda (Wadler et al.) -. Agda standard library documentation -. Type Theory and Formal Proof (Nederpelt & Geuvers) diff --git a/affinescript/docs/academic/mechanized/coq/README.adoc b/affinescript/docs/academic/mechanized/coq/README.adoc deleted file mode 100644 index bdc99eb..0000000 --- a/affinescript/docs/academic/mechanized/coq/README.adoc +++ /dev/null @@ -1,171 +0,0 @@ -== AffineScript Coq Formalization - -*Status*: Stub / Planned - -This directory will contain the mechanized Coq proof development for -AffineScript’s metatheory. - -=== Planned Structure - -.... -coq/ -├── Syntax.v -- Abstract syntax definitions -├── Typing.v -- Typing rules -├── Quantities.v -- QTT semiring and quantity operations -├── Reduction.v -- Small-step operational semantics -├── TypeSoundness.v -- Progress and preservation -├── Effects.v -- Effect typing and handling -├── Ownership.v -- Ownership and borrowing -├── Rows.v -- Row polymorphism -├── Dependent.v -- Dependent types -├── Refinements.v -- Refinement types (axiomatized) -├── Semantics.v -- Denotational semantics -└── Adequacy.v -- Adequacy theorem -.... - -=== Dependencies - -* Coq 8.17+ -* MetaCoq (for reflection) -* stdpp (for common structures) -* iris (for concurrent separation logic, if needed) - -=== Building - -[source,bash] ----- -# Install dependencies -opam install coq coq-stdpp - -# Build all proofs -make -j4 - -# Check specific file -coqc -Q . AffineScript Typing.v ----- - -=== TODO - -==== Phase 1: Core Type System - -* [ ] Define syntax (terms, types, contexts) -* [ ] Define typing judgments -* [ ] Prove substitution lemmas -* [ ] Prove progress theorem -* [ ] Prove preservation theorem - -==== Phase 2: Quantitative Types - -* [ ] Define quantity semiring -* [ ] Define context scaling and addition -* [ ] Prove quantity soundness (usage matches annotation) - -==== Phase 3: Effects - -* [ ] Define effect signatures -* [ ] Define handler typing -* [ ] Prove effect safety - -==== Phase 4: Ownership - -* [ ] Define ownership modalities -* [ ] Define borrow typing -* [ ] Prove memory safety properties - -==== Phase 5: Advanced Features - -* [ ] Row polymorphism -* [ ] Dependent types (stratified) -* [ ] Refinement types (axiomatized SMT) - -=== Proof Approach - -We use: 1. *Locally nameless* representation for binding 2. -*Intrinsically-typed syntax* where practical 3. *Small-step semantics* -for operational behavior 4. *Logical relations* for semantic properties - -=== Example Proof Structure - -[source,coq] ----- -(* Syntax.v *) -Inductive ty : Type := - | TyUnit : ty - | TyBool : ty - | TyInt : ty - | TyArrow : ty -> ty -> ty - | TyForall : ty -> ty - (* ... *) -. - -Inductive expr : Type := - | EVar : nat -> expr - | ELam : ty -> expr -> expr - | EApp : expr -> expr -> expr - (* ... *) -. - -(* Typing.v *) -Inductive has_type : ctx -> expr -> ty -> Prop := - | T_Var : forall Γ x τ, - lookup Γ x = Some τ -> - has_type Γ (EVar x) τ - | T_Lam : forall Γ τ₁ τ₂ e, - has_type (τ₁ :: Γ) e τ₂ -> - has_type Γ (ELam τ₁ e) (TyArrow τ₁ τ₂) - | T_App : forall Γ e₁ e₂ τ₁ τ₂, - has_type Γ e₁ (TyArrow τ₁ τ₂) -> - has_type Γ e₂ τ₁ -> - has_type Γ (EApp e₁ e₂) τ₂ - (* ... *) -. - -(* TypeSoundness.v *) -Theorem progress : forall e τ, - has_type nil e τ -> - value e \/ exists e', step e e'. -Proof. - intros e τ Hty. - remember nil as Γ. - induction Hty; subst. - - (* Var *) discriminate. - - (* Lam *) left. constructor. - - (* App *) - right. - destruct IHHty1 as [Hval1 | [e1' Hstep1]]; auto. - + (* e1 is a value *) - destruct IHHty2 as [Hval2 | [e2' Hstep2]]; auto. - * (* e2 is a value - beta reduction *) - inversion Hval1; subst. - exists (subst e2 e). constructor; auto. - * (* e2 steps *) - exists (EApp e1 e2'). constructor; auto. - + (* e1 steps *) - exists (EApp e1' e2). constructor; auto. -Qed. - -Theorem preservation : forall Γ e e' τ, - has_type Γ e τ -> - step e e' -> - has_type Γ e' τ. -Proof. - intros Γ e e' τ Hty Hstep. - generalize dependent e'. - induction Hty; intros e' Hstep; inversion Hstep; subst. - - (* Beta *) - apply substitution_lemma; auto. - - (* Cong-App-Left *) - eapply T_App; eauto. - - (* Cong-App-Right *) - eapply T_App; eauto. - (* ... *) -Qed. ----- - -=== References - -[arabic] -. Software Foundations (Pierce et al.) -. MetaCoq documentation -. Iris Proof Mode documentation -. RustBelt Coq development diff --git a/affinescript/docs/academic/mechanized/lean/README.adoc b/affinescript/docs/academic/mechanized/lean/README.adoc deleted file mode 100644 index dba1c3c..0000000 --- a/affinescript/docs/academic/mechanized/lean/README.adoc +++ /dev/null @@ -1,163 +0,0 @@ -== AffineScript Lean 4 Formalization - -*Status*: Stub / Planned - -This directory will contain the mechanized Lean 4 proof development for -AffineScript’s metatheory. - -=== Planned Structure - -.... -lean/ -├── AffineScript.lean -- Main entry point -├── Syntax.lean -- Abstract syntax definitions -├── Typing.lean -- Typing rules -├── Quantities.lean -- QTT semiring -├── Reduction.lean -- Operational semantics -├── Progress.lean -- Progress theorem -├── Preservation.lean -- Preservation theorem -├── Effects.lean -- Effect system -├── Ownership.lean -- Ownership model -├── Rows.lean -- Row polymorphism -└── lakefile.lean -- Build configuration -.... - -=== Dependencies - -* Lean 4.x -* Mathlib4 (for mathematical structures) -* Std4 (standard library) - -=== Building - -[source,bash] ----- -# Initialize lake project -lake init AffineScript - -# Build -lake build - -# Check proofs -lake env lean AffineScript.lean ----- - -=== TODO - -==== Phase 1: Core Definitions - -* [ ] Syntax with well-scoped indices -* [ ] Typing judgments as inductive families -* [ ] Decidable type checking - -==== Phase 2: Metatheory - -* [ ] Substitution lemmas -* [ ] Progress and preservation -* [ ] Type safety corollary - -==== Phase 3: Advanced Features - -* [ ] Quantitative type theory -* [ ] Effect typing -* [ ] Ownership verification - -=== Example Structure - -[source,lean] ----- --- Syntax.lean -inductive Ty : Type where - | unit : Ty - | bool : Ty - | int : Ty - | arrow : Ty → Ty → Ty - | forall_ : Ty → Ty - deriving Repr, DecidableEq - -inductive Expr : Type where - | var : Nat → Expr - | lam : Ty → Expr → Expr - | app : Expr → Expr → Expr - | tLam : Expr → Expr - | tApp : Expr → Ty → Expr - deriving Repr - --- Typing.lean -inductive HasType : List Ty → Expr → Ty → Prop where - | var : ∀ {Γ x τ}, - Γ.get? x = some τ → - HasType Γ (.var x) τ - | lam : ∀ {Γ τ₁ τ₂ e}, - HasType (τ₁ :: Γ) e τ₂ → - HasType Γ (.lam τ₁ e) (.arrow τ₁ τ₂) - | app : ∀ {Γ e₁ e₂ τ₁ τ₂}, - HasType Γ e₁ (.arrow τ₁ τ₂) → - HasType Γ e₂ τ₁ → - HasType Γ (.app e₁ e₂) τ₂ - --- Progress.lean -inductive Value : Expr → Prop where - | lam : Value (.lam τ e) - | tLam : Value (.tLam e) - -inductive Step : Expr → Expr → Prop where - | beta : ∀ {τ e v}, - Value v → - Step (.app (.lam τ e) v) (subst v e) - | appL : ∀ {e₁ e₁' e₂}, - Step e₁ e₁' → - Step (.app e₁ e₂) (.app e₁' e₂) - | appR : ∀ {v e₂ e₂'}, - Value v → - Step e₂ e₂' → - Step (.app v e₂) (.app v e₂') - -theorem progress {e τ} (h : HasType [] e τ) : - Value e ∨ ∃ e', Step e e' := by - induction h with - | var h => simp at h - | lam _ => left; constructor - | app h₁ h₂ ih₁ ih₂ => - right - cases ih₁ with - | inl hv₁ => - cases hv₁ with - | lam => - cases ih₂ with - | inl hv₂ => exact ⟨_, .beta hv₂⟩ - | inr ⟨e₂', hs₂⟩ => exact ⟨_, .appR .lam hs₂⟩ - | inr ⟨e₁', hs₁⟩ => exact ⟨_, .appL hs₁⟩ - --- Preservation.lean -theorem preservation {Γ e e' τ} - (ht : HasType Γ e τ) (hs : Step e e') : - HasType Γ e' τ := by - induction hs generalizing τ with - | beta hv => - cases ht with - | app h₁ h₂ => - cases h₁ with - | lam hbody => exact substitution_lemma hbody h₂ - | appL _ ih => - cases ht with - | app h₁ h₂ => exact .app (ih h₁) h₂ - | appR _ _ ih => - cases ht with - | app h₁ h₂ => exact .app h₁ (ih h₂) ----- - -=== Advantages of Lean 4 - -[arabic] -. *Decidable type checking*: Can compute types -. *Metaprogramming*: Powerful tactic framework -. *Performance*: Compiled tactics -. *Interoperability*: Can call external tools - -=== References - -[arabic] -. Theorem Proving in Lean 4 -. Mathematics in Lean -. Lean 4 Metaprogramming diff --git a/affinescript/docs/academic/proofs/coherence-parametricity.adoc b/affinescript/docs/academic/proofs/coherence-parametricity.adoc deleted file mode 100644 index f4f28a4..0000000 --- a/affinescript/docs/academic/proofs/coherence-parametricity.adoc +++ /dev/null @@ -1,422 +0,0 @@ -== Coherence and Parametricity Theorems - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -theoretical framework - -=== Abstract - -This document establishes coherence and parametricity results for -AffineScript. Coherence ensures that semantically equivalent derivations -yield identical meanings. Parametricity (free theorems) characterizes -the behavior of polymorphic functions solely from their types. - -=== 1. Introduction - -Two fundamental properties of well-designed type systems: - -[arabic] -. *Coherence*: Multiple typing derivations for the same term produce the -same semantic value -. *Parametricity*: Polymorphic functions are "`uniform`" across type -instantiations - -These properties enable equational reasoning and guarantee that types -accurately describe behavior. - -=== 2. Coherence - -==== 2.1 The Coherence Problem - -When a term can be typed in multiple ways, do all derivations mean the -same thing? - -*Example*: With subtyping and coercions: - -.... -f : A → B, x : A' where A' <: A -───────────────────────────────── -f x : B -.... - -The coercion from A’ to A must be unique, or coherence fails. - -==== 2.2 Coherence for Simple Types - -*Theorem 2.1 (Simple Type Coherence)*: For simply-typed AffineScript -without subtyping: - -.... -If Γ ⊢ e : τ via derivations D₁ and D₂, then ⟦D₁⟧ = ⟦D₂⟧ -.... - -*Proof*: By induction on the structure of e. Each expression form has a -unique applicable rule. ∎ - -==== 2.3 Coherence for Polymorphism - -*Theorem 2.2 (Polymorphic Coherence)*: Type application and abstraction -are coherent. - -For `+e : ∀α. τ+`: - -.... -⟦e [σ₁]⟧ = ⟦e [σ₂]⟧ when σ₁ = σ₂ -.... - -*Proof*: Semantic interpretation of type abstraction is uniform. ∎ - -==== 2.4 Coherence for Row Polymorphism - -*Theorem 2.3 (Row Coherence)*: Record operations are independent of -field order. - -.... -⟦{a = 1, b = 2}⟧ = ⟦{b = 2, a = 1}⟧ -.... - -*Proof*: Records are interpreted as finite maps; order is immaterial. ∎ - -*Theorem 2.4 (Row Selection Coherence)*: For `+e : {l: τ | ρ}+`: - -.... -⟦e.l⟧ is well-defined regardless of ρ -.... - -==== 2.5 Coherence for Effects - -*Theorem 2.5 (Effect Coherence)*: Effect row order does not affect -semantics. - -.... -⟦e : τ / E₁ | E₂ | ρ⟧ = ⟦e : τ / E₂ | E₁ | ρ⟧ -.... - -*Proof*: Effect rows are interpreted as sets of operations. ∎ - -*Theorem 2.6 (Handler Coherence)*: Handler clause order does not affect -behavior (for non-overlapping operations). - -==== 2.6 Coherence for Quantities - -*Theorem 2.7 (Quantity Coherence)*: Quantity annotations do not affect -runtime semantics. - -For quantity-compatible programs: - -.... -⟦0 x : τ ⊢ e⟧ = ⟦1 x : τ ⊢ e⟧ = ⟦ω x : τ ⊢ e⟧ -.... - -(when all are well-typed) - -*Proof*: Quantities are erased; only affect typing, not execution. ∎ - -==== 2.7 Coherence for Subtyping - -With structural subtyping, coercions must be coherent: - -*Theorem 2.8 (Subtyping Coherence)*: If τ <: σ via multiple derivations, -the induced coercions are equal. - -For AffineScript with structural subtyping: - Record subtyping: width -and depth - Refinement subtyping: predicate implication - -*Proof*: By induction on subtyping derivations, showing coercions are -canonical. ∎ - -=== 3. Parametricity - -==== 3.1 The Parametricity Principle - -Polymorphic functions cannot inspect their type arguments; they must -work "`uniformly.`" - -*Informal Statement*: A function `+f : ∀α. F(α)+` cannot distinguish -between types α. - -==== 3.2 Relational Interpretation - -Define a relational interpretation: - -*Type Relations*: - -.... -⟦α⟧_R = R (type variable: the relation parameter) -⟦Unit⟧_R = {((), ())} -⟦Bool⟧_R = {(true, true), (false, false)} -⟦Int⟧_R = {(n, n) | n ∈ Z} -⟦τ → σ⟧_R = {(f, g) | ∀(a, b) ∈ ⟦τ⟧_R. (f a, g b) ∈ ⟦σ⟧_R} -⟦τ × σ⟧_R = {((a₁, a₂), (b₁, b₂)) | (a₁, b₁) ∈ ⟦τ⟧_R ∧ (a₂, b₂) ∈ ⟦σ⟧_R} -⟦∀α. τ⟧_R = {(f, g) | ∀A, B : Type. ∀R ⊆ A × B. (f[A], g[B]) ∈ ⟦τ⟧_R[R/α]} -.... - -==== 3.3 Fundamental Property - -*Theorem 3.1 (Parametricity / Abstraction Theorem)*: For any `+⊢ e : τ+` -and relational interpretation: - -.... -(⟦e⟧, ⟦e⟧) ∈ ⟦τ⟧_R -.... - -*Proof*: By induction on typing derivation. - -_Case Var_: `+(⟦x⟧ρ₁, ⟦x⟧ρ₂) ∈ R_τ+` by assumption on related -environments. - -_Case Lam_: For `+λx. e : τ → σ+`, need to show: - -.... -∀(a, b) ∈ ⟦τ⟧_R. (⟦e⟧[a/x], ⟦e⟧[b/x]) ∈ ⟦σ⟧_R -.... - -By IH on e with extended related environments. ✓ - -_Case TyAbs_: For `+Λα. e : ∀α. τ+`, need to show: - -.... -∀A, B, R. (⟦e⟧[A/α], ⟦e⟧[B/α]) ∈ ⟦τ⟧_R[R/α] -.... - -By IH on e with R as the interpretation of α. ✓ - -∎ - -==== 3.4 Free Theorems - -From parametricity, we derive "`free theorems`" about polymorphic -functions: - -*Theorem 3.2 (Identity Free Theorem)*: For `+id : ∀α. α → α+`: - -.... -id = λx. x -.... - -*Proof*: By parametricity, for any R ⊆ A × B and (a, b) ∈ R: - -.... -(id_A a, id_B b) ∈ R -.... - -Setting R = \{(a, id_B a)}, we get id_A a = a. ∎ - -*Theorem 3.3 (Map Free Theorem)*: For -`+f : ∀α β. (α → β) → List[α] → List[β]+`: - -.... -f g ∘ map h = map (g ∘ h) -.... - -(f distributes over map) - -*Theorem 3.4 (Fold Free Theorem)*: For -`+fold : ∀α β. (α → β → β) → β → List[α] → β+`: - -.... -fold f z ∘ map g = fold (f ∘ g) z -.... - -==== 3.5 Parametricity for Rows - -*Theorem 3.5 (Row Parametricity)*: For `+f : ∀ρ. {l: τ | ρ} → σ+`: - -.... -f is independent of fields other than l -.... - -*Example*: - -[source,affinescript] ----- -fn get_name[ρ](r: {name: String | ρ}) -> String { - r.name -} ----- - -By parametricity, `+get_name+` cannot observe or use any field other -than `+name+`. - -==== 3.6 Parametricity for Effects - -*Theorem 3.6 (Effect Parametricity)*: For -`+f : ∀ε. (() →{ε} A) → (() →{ε} B)+`: - -.... -f cannot perform or suppress effects in ε -.... - -f can only transform the result; effects pass through unchanged. - -==== 3.7 Parametricity and Quantities - -*Theorem 3.7 (Quantity Parametricity)*: Quantity-polymorphic functions -respect usage: - -For `+f : ∀π. (π x : τ) → σ+`: - At π = 0: x is not used - At π = 1: x -is used exactly once - At π = ω: x may be used arbitrarily - -==== 3.8 Parametricity for Refinements - -*Theorem 3.8*: Parametricity extends to refinement types via logical -relations. - -For `+f : ∀α. {x: α | P(x)} → {y: α | Q(y)}+`: - -.... -∀x. P(x) ⟹ Q(f x) -.... - -The predicate transformer is derivable from the type. - -=== 4. Applications - -==== 4.1 Program Equivalence - -Use parametricity to prove program equivalences: - -*Example*: `+reverse ∘ reverse = id+` (for lists) - -*Proof*: By parametricity and induction. ∎ - -==== 4.2 Optimization Validity - -Parametricity justifies optimizations: - -*Example*: Fusion - -.... -map f ∘ map g = map (f ∘ g) -.... - -==== 4.3 Representation Independence - -Parametricity ensures abstract types hide representation: - -[source,affinescript] ----- -module Set[A] { - type T -- abstract - fn empty() -> T - fn insert(x: A, s: T) -> T - fn member(x: A, s: T) -> Bool -} ----- - -Clients cannot distinguish implementations (list vs tree). - -==== 4.4 Security Properties - -Parametricity implies information flow properties: - -[source,affinescript] ----- -fn secure[α](secret: α, public: Int) -> Int ----- - -By parametricity, the result cannot depend on `+secret+`. - -=== 5. Limitations - -==== 5.1 Effects Break Parametricity - -Unrestricted effects can break parametricity: - -[source,affinescript] ----- -// BAD: breaks parametricity if allowed -fn bad[α](x: α) -> String / IO { - print(type_of(x)) // type introspection - "done" -} ----- - -AffineScript prevents this by not having `+type_of+` or similar -primitives. - -==== 5.2 General Recursion - -Non-termination weakens parametricity: - -[source,affinescript] ----- -fn loop[α]() -> α { - loop() // non-terminating -} ----- - -Total functions satisfy stronger parametricity. - -==== 5.3 Unsafe Operations - -`+unsafe+` blocks can break all guarantees: - -[source,affinescript] ----- -fn bad[α](x: α) -> Int / unsafe { - unsafe { transmute(x) } -} ----- - -Parametricity holds only outside `+unsafe+`. - -=== 6. Formal Statements - -==== 6.1 Coherence Theorem (Full) - -*Theorem 6.1 (Full Coherence)*: For AffineScript with all features: - -If `+Γ ⊢ e : τ+` via derivations D₁ and D₂, then: - -.... -⟦D₁⟧_η = ⟦D₂⟧_η -.... - -for any environment η satisfying Γ. - -Conditions: - No `+unsafe+` blocks - All coercions are canonical - -Effects are handled - -==== 6.2 Parametricity Theorem (Full) - -*Theorem 6.2 (Full Parametricity)*: For a well-typed closed term -`+⊢ e : τ+`: - -.... -(⟦e⟧, ⟦e⟧) ∈ ⟦τ⟧_{id} -.... - -where ⟦_⟧_\{id} is the identity relational interpretation. - -For open terms, with related substitutions: - -.... -(⟦e⟧ρ₁, ⟦e⟧ρ₂) ∈ ⟦τ⟧_R when ρ₁ R_Γ ρ₂ -.... - -=== 7. Related Work - -[arabic] -. *Reynolds (1983)*: Types, Abstraction, and Parametric Polymorphism -. *Wadler (1989)*: Theorems for Free! -. *Plotkin & Abadi (1993)*: A Logic for Parametric Polymorphism -. *Dreyer et al. (2010)*: Logical Relations for Fine-Grained Concurrency -. *Ahmed et al. (2017)*: Parametricity and Local State - -=== 8. References - -[arabic] -. Reynolds, J. C. (1983). Types, Abstraction and Parametric -Polymorphism. _IFIP_. -. Wadler, P. (1989). Theorems for Free! _FPCA_. -. Plotkin, G., & Abadi, M. (1993). A Logic for Parametric Polymorphism. -_TLCA_. -. Wadler, P., & Blott, S. (1989). How to Make Ad-Hoc Polymorphism Less -Ad Hoc. _POPL_. -. Ahmed, A. (2006). Step-Indexed Syntactic Logical Relations for -Recursive and Quantified Types. _ESOP_. - -''''' - -*Document Metadata*: - Pure theory; no implementation dependencies - -Mechanized proof: See `+mechanized/coq/Parametricity.v+` (stub) diff --git a/affinescript/docs/academic/proofs/dependent-types.adoc b/affinescript/docs/academic/proofs/dependent-types.adoc deleted file mode 100644 index aaa6576..0000000 --- a/affinescript/docs/academic/proofs/dependent-types.adoc +++ /dev/null @@ -1,751 +0,0 @@ -== Dependent Types and Refinement Types: Complete Formalization - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: type-checker, smt-integration]+` - -=== Abstract - -This document provides a complete formalization of AffineScript’s -dependent type system, including indexed types, Π-types (dependent -functions), Σ-types (dependent pairs), refinement types, and -propositional equality. We prove decidability of type checking (modulo -SMT queries for refinements), normalization of type-level computation, -and type safety. - -=== 1. Introduction - -AffineScript supports a stratified dependent type system: - -[arabic] -. *Indexed types*: Types parameterized by values (`+Vec[n, T]+`) -. *Π-types*: Dependent function types (`+(n: Nat) → Vec[n, T]+`) -. *Σ-types*: Dependent pair types (`+(n: Nat, Vec[n, T])+`) -. *Refinement types*: Types refined by predicates (`+{x: Int | x > 0}+`) -. *Propositional equality*: Type-level equality proofs (`+n == m+`) - -The system is designed for practical use with decidable type checking -through: - Restricted type-level computation - SMT-based refinement -checking - Erasure of proof terms at runtime - -=== 2. Syntax - -==== 2.1 Universes - -.... -U ::= Type₀ | Type₁ | ... -- Universe hierarchy -.... - -With universe polymorphism: `+Type_i : Type_{i+1}+` - -==== 2.2 Expressions and Types (Unified) - -In dependent type theory, expressions and types share the same grammar: - -.... -e, τ, σ ::= - -- Core - | x -- Variable - | U_i -- Universe at level i - | λ(x:τ). e -- Lambda - | e₁ e₂ -- Application - | Π(x:τ). σ -- Dependent function type - | Σ(x:τ). σ -- Dependent pair type - | (e₁, e₂) -- Pair - | fst e | snd e -- Projections - - -- Indexed types - | C[ē] -- Indexed type constructor - | c[ē](ē') -- Indexed data constructor - - -- Refinements - | {x:τ | φ} -- Refinement type - | ⌊e⌋ -- Refinement introduction - | ⌈e⌉ -- Refinement elimination - - -- Equality - | e₁ == e₂ -- Propositional equality type - | refl -- Reflexivity proof - | J(P, d, p) -- Equality elimination (J rule) - - -- Type-level computation - | n -- Natural literal - | e₁ + e₂ | e₁ - e₂ | e₁ * e₂ -- Arithmetic - | if e₁ then e₂ else e₃ -- Conditional - | len(e) -- Length operation -.... - -==== 2.3 Predicates (for Refinements) - -.... -φ, ψ ::= - | e₁ < e₂ | e₁ ≤ e₂ | e₁ = e₂ | e₁ ≠ e₂ -- Comparisons - | φ ∧ ψ | φ ∨ ψ | ¬φ -- Logical connectives - | ∀x:τ. φ | ∃x:τ. φ -- Quantifiers - | P(e₁, ..., eₙ) -- Predicate application -.... - -=== 3. Judgments - -==== 3.1 Core Judgments - -.... -Γ ⊢ wf -- Context well-formed -Γ ⊢ e : τ -- Typing -Γ ⊢ e ≡ e' : τ -- Definitional equality -Γ ⊢ τ <: σ -- Subtyping -Γ ⊢ e ⇝ v -- Reduction to value (normalization) -Γ ⊢ φ -- Predicate validity -.... - -==== 3.2 Bidirectional Judgments - -.... -Γ ⊢ e ⇒ τ -- Synthesis -Γ ⊢ e ⇐ τ -- Checking -.... - -=== 4. Typing Rules - -==== 4.1 Universes - -*Type-in-Type (Simplified)* - -.... - ───────────────── - Γ ⊢ Type_i : Type_{i+1} -.... - -*Cumulativity* - -.... - Γ ⊢ τ : Type_i i ≤ j - ───────────────────────── - Γ ⊢ τ : Type_j -.... - -==== 4.2 Π-Types (Dependent Functions) - -*Π-Form* - -.... - Γ ⊢ τ : Type_i Γ, x:τ ⊢ σ : Type_j - ───────────────────────────────────────── - Γ ⊢ Π(x:τ). σ : Type_{max(i,j)} -.... - -*Π-Intro* - -.... - Γ, x:τ ⊢ e : σ - ────────────────────────── - Γ ⊢ λ(x:τ). e : Π(x:τ). σ -.... - -*Π-Elim* - -.... - Γ ⊢ f : Π(x:τ). σ Γ ⊢ a : τ - ──────────────────────────────── - Γ ⊢ f a : σ[a/x] -.... - -*Π-β* - -.... - Γ ⊢ (λ(x:τ). e) a ≡ e[a/x] : σ[a/x] -.... - -*Π-η* - -.... - Γ ⊢ f : Π(x:τ). σ - ────────────────────────────────── - Γ ⊢ f ≡ λ(x:τ). f x : Π(x:τ). σ -.... - -==== 4.3 Σ-Types (Dependent Pairs) - -*Σ-Form* - -.... - Γ ⊢ τ : Type_i Γ, x:τ ⊢ σ : Type_j - ───────────────────────────────────────── - Γ ⊢ Σ(x:τ). σ : Type_{max(i,j)} -.... - -*Σ-Intro* - -.... - Γ ⊢ a : τ Γ ⊢ b : σ[a/x] - ───────────────────────────── - Γ ⊢ (a, b) : Σ(x:τ). σ -.... - -*Σ-Elim (First)* - -.... - Γ ⊢ p : Σ(x:τ). σ - ─────────────────── - Γ ⊢ fst p : τ -.... - -*Σ-Elim (Second)* - -.... - Γ ⊢ p : Σ(x:τ). σ - ─────────────────────────── - Γ ⊢ snd p : σ[fst p/x] -.... - -*Σ-β* - -.... - Γ ⊢ fst (a, b) ≡ a : τ - Γ ⊢ snd (a, b) ≡ b : σ[a/x] -.... - -*Σ-η* - -.... - Γ ⊢ p : Σ(x:τ). σ - ─────────────────────────────────── - Γ ⊢ p ≡ (fst p, snd p) : Σ(x:τ). σ -.... - -==== 4.4 Indexed Types - -*Definition*: An indexed type is defined with indices: - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons : (T, Vec[m, T]) → Vec[m + 1, T] ----- - -*Indexed-Form* - -.... - Vec : Nat → Type → Type - ──────────────────────────────── - Γ ⊢ n : Nat Γ ⊢ T : Type - ─────────────────────────────── - Γ ⊢ Vec[n, T] : Type -.... - -*Indexed-Intro (Nil)* - -.... - Γ ⊢ T : Type - ───────────────────── - Γ ⊢ Nil : Vec[0, T] -.... - -*Indexed-Intro (Cons)* - -.... - Γ ⊢ x : T Γ ⊢ xs : Vec[n, T] - ───────────────────────────────── - Γ ⊢ Cons(x, xs) : Vec[n + 1, T] -.... - -*Indexed-Elim (Pattern Matching)* - -.... - Γ ⊢ v : Vec[n, T] - Γ ⊢ e_nil : P[0/n, Nil/v] - Γ, m:Nat, x:T, xs:Vec[m,T] ⊢ e_cons : P[m+1/n, Cons(x,xs)/v] - ──────────────────────────────────────────────────────────────── - Γ ⊢ case v { Nil → e_nil | Cons(x, xs) → e_cons } : P -.... - -==== 4.5 Refinement Types - -*Refine-Form* - -.... - Γ ⊢ τ : Type Γ, x:τ ⊢ φ : Prop - ─────────────────────────────────── - Γ ⊢ {x:τ | φ} : Type -.... - -*Refine-Intro* - -.... - Γ ⊢ e : τ Γ ⊢ φ[e/x] - ───────────────────────── - Γ ⊢ ⌊e⌋ : {x:τ | φ} -.... - -*Refine-Elim* - -.... - Γ ⊢ e : {x:τ | φ} - ─────────────────── - Γ ⊢ ⌈e⌉ : τ -.... - -*Refine-Proj* - -.... - Γ ⊢ e : {x:τ | φ} - ─────────────────────── - Γ ⊢ φ[⌈e⌉/x] -.... - -==== 4.6 Propositional Equality - -*Eq-Form* - -.... - Γ ⊢ a : τ Γ ⊢ b : τ - ───────────────────────── - Γ ⊢ a == b : Type -.... - -*Eq-Intro (Refl)* - -.... - Γ ⊢ a : τ - ────────────────── - Γ ⊢ refl : a == a -.... - -*Eq-Elim (J)* - -.... - Γ, y:τ, p:(a == y) ⊢ P : Type - Γ ⊢ d : P[a/y, refl/p] - Γ ⊢ e : a == b - ───────────────────────────────── - Γ ⊢ J(P, d, e) : P[b/y, e/p] -.... - -*Eq-β* - -.... - Γ ⊢ J(P, d, refl) ≡ d : P[a/y, refl/p] -.... - -==== 4.7 Subtyping with Refinements - -*Sub-Refine* - -.... - Γ ⊢ τ <: σ Γ, x:τ ⊢ φ ⟹ ψ - ───────────────────────────────────── - Γ ⊢ {x:τ | φ} <: {x:σ | ψ} -.... - -*Sub-Forget* - -.... - ───────────────────────── - Γ ⊢ {x:τ | φ} <: τ -.... - -=== 5. Definitional Equality - -==== 5.1 Reduction Rules - -*β-Reduction* - -.... -(λ(x:τ). e) a ⟶ e[a/x] -fst (a, b) ⟶ a -snd (a, b) ⟶ b -J(P, d, refl) ⟶ d -.... - -*Arithmetic Reduction* - -.... -n + m ⟶ n+m (where n, m are literals) -n * m ⟶ n*m -if true then e₁ else e₂ ⟶ e₁ -if false then e₁ else e₂ ⟶ e₂ -len(Nil) ⟶ 0 -len(Cons(_, xs)) ⟶ 1 + len(xs) -.... - -==== 5.2 Definitional Equality - -*Definition 5.1*: e₁ ≡ e₂ iff e₁ and e₂ reduce to the same normal form. - -*Eq-Refl* - -.... - ─────────── - Γ ⊢ e ≡ e -.... - -*Eq-Sym* - -.... - Γ ⊢ e₁ ≡ e₂ - ───────────── - Γ ⊢ e₂ ≡ e₁ -.... - -*Eq-Trans* - -.... - Γ ⊢ e₁ ≡ e₂ Γ ⊢ e₂ ≡ e₃ - ──────────────────────────── - Γ ⊢ e₁ ≡ e₃ -.... - -*Eq-Reduce* - -.... - e₁ ⟶* e' e₂ ⟶* e' - ──────────────────────── - Γ ⊢ e₁ ≡ e₂ -.... - -==== 5.3 Conversion Rule - -*Conv* - -.... - Γ ⊢ e : τ Γ ⊢ τ ≡ σ : Type - ───────────────────────────────── - Γ ⊢ e : σ -.... - -=== 6. Normalization - -==== 6.1 Strong Normalization - -*Theorem 6.1 (Strong Normalization)*: Every well-typed term has a normal -form; all reduction sequences terminate. - -*Proof Sketch*: By logical relations / reducibility candidates. - -Define for each type τ a set RED(τ) of "`reducible`" terms: - RED(Nat) = -SN (strongly normalizing terms) - RED(Π(x:τ). σ) = \{f | ∀a ∈ RED(τ). f -a ∈ RED(σ[a/x])} - RED(Σ(x:τ). σ) = \{p | fst p ∈ RED(τ) ∧ snd p ∈ -RED(σ[fst p/x])} - -Show: 1. RED(τ) ⊆ SN for all τ 2. If Γ ⊢ e : τ then e ∈ RED(τ) under -appropriate substitution - -∎ - -*Note*: Normalization holds for the type-level fragment. General -recursion at the term level introduces non-termination (partiality). - -==== 6.2 Decidability of Type Checking - -*Theorem 6.2 (Decidability)*: Type checking for AffineScript’s dependent -types is decidable, modulo SMT queries for refinements. - -*Proof*: 1. All type-level terms normalize (Theorem 6.1) 2. Definitional -equality reduces to normal form comparison 3. Refinement checking is -delegated to SMT solver 4. SMT queries may timeout, but the algorithm -terminates - -∎ - -=== 7. SMT Integration for Refinements - -==== 7.1 Predicate Translation - -Translate refinement predicates to SMT-LIB: - -[source,ocaml] ----- -let rec to_smt (φ : predicate) : smt_term = - match φ with - | Less (e1, e2) -> Smt.lt (term_to_smt e1) (term_to_smt e2) - | Equal (e1, e2) -> Smt.eq (term_to_smt e1) (term_to_smt e2) - | And (φ1, φ2) -> Smt.and_ (to_smt φ1) (to_smt φ2) - | Or (φ1, φ2) -> Smt.or_ (to_smt φ1) (to_smt φ2) - | Not φ -> Smt.not_ (to_smt φ) - | Forall (x, τ, φ) -> Smt.forall x (type_to_sort τ) (to_smt φ) - | ... ----- - -==== 7.2 Subtyping Check - -[source,ocaml] ----- -let check_subtype (ctx : context) (r1 : refinement) (r2 : refinement) : bool = - (* Check: ∀x. ctx ∧ r1 ⟹ r2 *) - let premise = Smt.and_ (context_to_smt ctx) (to_smt r1) in - let goal = to_smt r2 in - let query = Smt.implies premise goal in - Smt.check_valid query ----- - -==== 7.3 Decidability and Completeness - -*Theorem 7.1*: For the quantifier-free fragment of refinement logic over -linear integer arithmetic, SMT checking is decidable. - -*Theorem 7.2*: For the full fragment with quantifiers, SMT checking is -undecidable in general, but practical for common patterns. - -`+[IMPL-DEP: smt-integration]+` Requires Z3 or CVC5 integration. - -=== 8. Soundness - -==== 8.1 Progress - -*Theorem 8.1 (Progress)*: If `+· ⊢ e : τ+` then either: 1. e is a value, -or 2. e can reduce - -*Proof*: By induction on typing. Dependent types do not change the -structure of progress. ∎ - -==== 8.2 Preservation - -*Theorem 8.2 (Preservation)*: If `+Γ ⊢ e : τ+` and `+e ⟶ e'+` then -`+Γ ⊢ e' : τ+`. - -*Proof*: By induction on reduction. Key case: - -_Case Π-β_: `+(λ(x:τ). e) a ⟶ e[a/x]+` - From typing: -`+Γ ⊢ λ(x:τ). e : Π(x:τ). σ+` and `+Γ ⊢ a : τ+` - By inversion: -`+Γ, x:τ ⊢ e : σ+` - By substitution lemma: `+Γ ⊢ e[a/x] : σ[a/x]+` - -Result type is `+σ[a/x]+` as required ✓ - -∎ - -==== 8.3 Refinement Soundness - -*Theorem 8.3 (Refinement Soundness)*: If `+Γ ⊢ e : {x:τ | φ}+` and e -evaluates to value v, then `+Γ ⊢ φ[v/x]+`. - -*Proof*: By the introduction rule, every value of refinement type -satisfies its predicate. The SMT checker verifies predicates are -preserved through computation. ∎ - -=== 9. Erasure - -==== 9.1 Type Erasure - -Types, proofs, and zero-quantity terms are erased for runtime: - -.... -|x| = x -|λ(x:τ). e| = λx. |e| -|e₁ e₂| = |e₁| |e₂| -|Π(x:τ). σ| = erased -|refl| = () -|J(P, d, p)| = |d| -|⌊e⌋| = |e| -|⌈e⌉| = |e| -.... - -==== 9.2 Erasure Soundness - -*Theorem 9.1 (Erasure Soundness)*: If `+Γ ⊢ e : τ+` and `+e ⟶* v+` then -`+|e| ⟶* |v|+`. - -The erased program simulates the full program. - -=== 10. Examples - -==== 10.1 Length-Indexed Vectors - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons : (head: T, tail: Vec[m, T]) → Vec[m + 1, T] - -fn head[n: Nat, T](v: Vec[n + 1, T]) -> T { - case v { - Cons(x, _) → x - // Nil case is impossible; n + 1 ≠ 0 - } -} - -fn append[n: Nat, m: Nat, T]( - xs: Vec[n, T], - ys: Vec[m, T] -) -> Vec[n + m, T] { - case xs { - Nil → ys, // Vec[0 + m, T] = Vec[m, T] ✓ - Cons(x, xs') → Cons(x, append(xs', ys)) - // Vec[(n' + 1) + m, T] = Vec[n' + (m + 1), T] by arithmetic - } -} ----- - -==== 10.2 Bounded Naturals - -[source,affinescript] ----- -type Fin[n: Nat] = - | FZero : Fin[m + 1] -- for any m - | FSucc : Fin[m] → Fin[m + 1] - -fn safe_index[n: Nat, T](v: Vec[n, T], i: Fin[n]) -> T { - case (v, i) { - (Cons(x, _), FZero) → x, - (Cons(_, xs), FSucc(i')) → safe_index(xs, i') - // (Nil, _) is impossible: Fin[0] is uninhabited - } -} ----- - -==== 10.3 Refinement Types - -[source,affinescript] ----- -type Pos = {x: Int | x > 0} -type NonEmpty[T] = {xs: List[T] | len(xs) > 0} - -fn divide(x: Int, y: Pos) -> Int { - x / ⌈y⌉ // Safe: y > 0 guaranteed -} - -fn head_safe[T](xs: NonEmpty[T]) -> T { - case ⌈xs⌉ { - Cons(x, _) → x - // Nil impossible by refinement - } -} - -fn sqrt(x: {n: Int | n ≥ 0}) -> {r: Int | r * r ≤ x ∧ (r+1) * (r+1) > x} { - // Implementation with proof - ... -} ----- - -==== 10.4 Equality Proofs - -[source,affinescript] ----- -fn sym[A, x: A, y: A](p: x == y) -> y == x { - J((z, _) → z == x, refl, p) -} - -fn trans[A, x: A, y: A, z: A](p: x == y, q: y == z) -> x == z { - J((w, _) → x == w, p, q) -} - -fn cong[A, B, f: A → B, x: A, y: A](p: x == y) -> f(x) == f(y) { - J((z, _) → f(x) == f(z), refl, p) -} - -fn transport[A, P: A → Type, x: A, y: A](p: x == y, px: P(x)) -> P(y) { - J((z, _) → P(z), px, p) -} ----- - -==== 10.5 Proof-Carrying Code - -[source,affinescript] ----- -fn merge_sorted[n: Nat, m: Nat, T: Ord]( - xs: {v: Vec[n, T] | sorted(v)}, - ys: {v: Vec[m, T] | sorted(v)} -) -> {v: Vec[n + m, T] | sorted(v)} { - // Implementation maintains sortedness invariant - // Proof obligations discharged by SMT - ... -} ----- - -=== 11. Implementation - -==== 11.1 AST Representation - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type nat_expr = - | NatLit of int * Span.t - | NatVar of ident - | NatAdd of nat_expr * nat_expr - | NatSub of nat_expr * nat_expr - | NatMul of nat_expr * nat_expr - | NatLen of ident - | NatSizeof of type_expr - -type predicate = - | PredCmp of nat_expr * cmp_op * nat_expr - | PredNot of predicate - | PredAnd of predicate * predicate - | PredOr of predicate * predicate - -type type_expr = - | ... - | TyApp of ident * type_arg list (* Vec[n, T] *) - | TyDepArrow of dep_arrow (* (n: Nat) → ... *) - | TyRefined of type_expr * predicate (* {x: T | P} *) ----- - -==== 11.2 Type Checker Module - -`+[IMPL-DEP: type-checker]+` - -[source,ocaml] ----- -module DepTypeCheck : sig - val normalize : ctx -> expr -> expr - val definitionally_equal : ctx -> expr -> expr -> bool - val check_refinement : ctx -> predicate -> bool (* SMT *) - val infer : ctx -> expr -> typ result - val check : ctx -> expr -> typ -> unit result -end ----- - -==== 11.3 SMT Interface - -`+[IMPL-DEP: smt-integration]+` - -[source,ocaml] ----- -module SMT : sig - type solver - type term - type sort - - val create : unit -> solver - val int_sort : sort - val bool_sort : sort - val declare_const : solver -> string -> sort -> term - val assert_ : solver -> term -> unit - val check_sat : solver -> [`Sat | `Unsat | `Unknown] - val check_valid : solver -> term -> bool -end ----- - -=== 12. Related Work - -[arabic] -. *Martin-Löf Type Theory*: Foundation of dependent types -. *Coq/Calculus of Constructions*: Full dependent types with universes -. *Agda*: Dependently typed programming language -. *Idris*: Dependent types with effects -. *Liquid Haskell*: Refinement types via SMT -. *F**: Dependent types with effects and refinements -. *Dafny*: Verification via weakest preconditions -. *ATS*: Linear and dependent types combined - -=== 13. References - -[arabic] -. Martin-Löf, P. (1984). _Intuitionistic Type Theory_. Bibliopolis. -. Coquand, T., & Huet, G. (1988). The Calculus of Constructions. -_Information and Computation_. -. Norell, U. (2009). Dependently Typed Programming in Agda. _AFP_. -. Brady, E. (2013). Idris, a General-Purpose Dependently Typed -Programming Language. _JFP_. -. Rondon, P., Kawaguchi, M., & Jhala, R. (2008). Liquid Types. _PLDI_. -. Swamy, N., et al. (2016). Dependent Types and Multi-Monadic Effects in -F__. __POPL*. -. Xi, H., & Pfenning, F. (1999). Dependent Types in Practical -Programming. _POPL_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (dependent types), -type checker, SMT integration - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/DependentTypes.v+` (stub) diff --git a/affinescript/docs/academic/proofs/effect-soundness.adoc b/affinescript/docs/academic/proofs/effect-soundness.adoc deleted file mode 100644 index 4464423..0000000 --- a/affinescript/docs/academic/proofs/effect-soundness.adoc +++ /dev/null @@ -1,681 +0,0 @@ -== Algebraic Effects: Formal Semantics and Soundness - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: effect-checker, effect-inference]+` - -=== Abstract - -This document presents the formal semantics of AffineScript’s algebraic -effect system. We define the syntax, typing rules, and operational -semantics for effects and handlers, proving type safety and effect -safety: well-typed programs only perform effects that are handled. - -=== 1. Introduction - -Algebraic effects and handlers provide a structured approach to -computational effects, separating effect signatures from their -implementations. AffineScript’s effect system features: - -[arabic] -. *User-defined effects*: Custom effect declarations -. *Row-polymorphic effects*: `+ε₁ | ε₂ | ..ρ+` -. *One-shot and multi-shot handlers*: Controlled use of continuations -. *Effect polymorphism*: Abstracting over effect rows -. *Effect inference*: Automatic effect tracking - -=== 2. Syntax - -==== 2.1 Effect Declarations - -.... -effect State[S] { - get : () → S - put : S → () -} - -effect Exn[E] { - raise : E → ⊥ -} - -effect Async { - fork : (() →{Async} ()) → () - yield : () → () -} -.... - -==== 2.2 Effect Types - -.... -ε ::= - | · -- Empty effect (pure) - | Op -- Single effect operation - | E -- Named effect - | ε₁ | ε₂ -- Effect union - | ρ -- Effect row variable -.... - -==== 2.3 Effectful Function Types - -.... -τ →{ε} σ -- Function with effect ε -τ →{} σ ≡ τ → σ -- Pure function (sugar) -.... - -==== 2.4 Effect Operations and Handlers - -.... -e ::= - | ... - | perform op(e) -- Perform effect operation - | handle e with h -- Handle effects - | resume(e) -- Resume continuation - -h ::= - | { return x → e_ret, - op₁(x, k) → e₁, - ..., - opₙ(x, k) → eₙ } -- Handler clauses -.... - -=== 3. Static Semantics - -==== 3.1 Effect Kinding - -.... -Γ ⊢ ε : Effect -.... - -*E-Empty* - -.... - ────────────── - Γ ⊢ · : Effect -.... - -*E-Op* - -.... - op : τ → σ ∈ E - ──────────────── - Γ ⊢ E.op : Effect -.... - -*E-Named* - -.... - effect E { ... } declared - ────────────────────────── - Γ ⊢ E : Effect -.... - -*E-Union* - -.... - Γ ⊢ ε₁ : Effect Γ ⊢ ε₂ : Effect - ─────────────────────────────────── - Γ ⊢ ε₁ | ε₂ : Effect -.... - -*E-Var* - -.... - ρ : Effect ∈ Γ - ─────────────── - Γ ⊢ ρ : Effect -.... - -==== 3.2 Effect Row Operations - -*Row Equivalence*: Effect rows are equivalent up to: - Commutativity: -`+ε₁ | ε₂ ≡ ε₂ | ε₁+` - Associativity: -`+(ε₁ | ε₂) | ε₃ ≡ ε₁ | (ε₂ | ε₃)+` - Identity: `+ε | · ≡ ε+` - -Idempotence: `+E | E ≡ E+` - -*Row Subtraction*: `+ε \ E+` removes effect E from row ε - -.... - · \ E = · - E \ E = · - E' \ E = E' (when E ≠ E') - (ε₁ | ε₂) \ E = (ε₁ \ E) | (ε₂ \ E) - ρ \ E = ρ (row variable, handled later) -.... - -==== 3.3 Typing Effectful Expressions - -The judgment `+Γ ⊢ e : τ ! ε+` means "`in context Γ, expression e has -type τ and may perform effects ε`". - -*Pure expressions*: - -.... - Γ ⊢ e : τ - ───────────── - Γ ⊢ e : τ ! · -.... - -*Effect Subsumption*: - -.... - Γ ⊢ e : τ ! ε₁ ε₁ ⊆ ε₂ - ────────────────────────── - Γ ⊢ e : τ ! ε₂ -.... - -==== 3.4 Effect Operation Typing - -*Perform* - -.... - op : τ → σ ∈ E - Γ ⊢ e : τ ! ε - ──────────────────────── - Γ ⊢ perform op(e) : σ ! (E | ε) -.... - -==== 3.5 Handler Typing - -*Handle* - -.... - Γ ⊢ e : τ ! (E | ε) - Γ ⊢ h handles E : τ ⇒ σ ! ε' - ────────────────────────────────── - Γ ⊢ handle e with h : σ ! (ε | ε') -.... - -*Handler Clause Typing* - -.... - Γ, x:τ_ret ⊢ e_ret : σ ! ε' - ∀ op ∈ E. Γ, x:τ_op, k:(σ_op →{ε | ε'} σ) ⊢ e_op : σ ! ε' - ──────────────────────────────────────────────────────────── - Γ ⊢ { return x → e_ret, op(x,k) → e_op, ... } handles E : τ_ret ⇒ σ ! ε' -.... - -Where: - `+τ_ret+` is the return type of the handled computation - For -each `+op : τ_op → σ_op+` in E - `+k+` is the continuation, typed as -`+σ_op →{ε | ε'} σ+` - -==== 3.6 Resume Typing - -Within a handler clause for `+op : τ → σ+`: - -*Resume* - -.... - Γ, k:(σ →{ε} τ_result) ⊢ resume(e) : τ_result ! ε - ───────────────────────────────────────────────── - (when e : σ) -.... - -=== 4. Dynamic Semantics - -==== 4.1 Values and Evaluation Contexts - -*Values*: - -.... -v ::= ... | handler h -.... - -*Pure Evaluation Contexts*: - -.... -E_p ::= □ | E_p e | v E_p | ... -.... - -*Effectful Evaluation Contexts*: - -.... -E_eff ::= E_p | handle E_eff with h -.... - -==== 4.2 Reduction Rules - -*Handler Introduction*: - -.... - ───────────────────────────────────────────── - handle v with h ⟶ e_ret[v/x] - (where h = { return x → e_ret, ... }) -.... - -*Effect Forwarding* (effect not handled): - -.... - op ∉ E - ──────────────────────────────────────────────────────── - handle E_p[perform op(v)] with h ⟶ - perform op(v) >>= (λy. handle E_p[y] with h) -.... - -*Effect Handling*: - -.... - op : τ → σ ∈ E - h = { ..., op(x, k) → e_op, ... } - ──────────────────────────────────────────────────────── - handle E_p[perform op(v)] with h ⟶ - e_op[v/x, (λy. handle E_p[y] with h)/k] -.... - -The key insight: the continuation `+k+` captures the context `+E_p+`, -allowing the handler to resume computation. - -==== 4.3 One-Shot vs Multi-Shot Continuations - -*One-shot* (linear k): - -.... - k used exactly once in e_op - Continuation can be efficiently implemented as a stack -.... - -*Multi-shot* (unrestricted k): - -.... - k may be used zero, one, or multiple times - Requires copying/delimited continuation implementation -.... - -AffineScript tracks this via quantity annotations: - -.... - op(x, 1 k) → e_op -- k is linear (one-shot) - op(x, ω k) → e_op -- k is unrestricted (multi-shot) -.... - -=== 5. Effect Safety - -==== 5.1 Main Theorem - -*Theorem 5.1 (Effect Safety)*: If `+· ⊢ e : τ ! ·+` (closed, pure), then -evaluation of e does not get stuck on an unhandled effect. - -*Proof*: We prove a stronger statement by showing that effects are -always contained within handlers. - -Define "`effect-safe configuration`" inductively: 1. Values are -effect-safe 2. `+handle e with h+` is effect-safe if e may only perform -effects in dom(h) ∪ ε where ε are effects propagated outward - -By progress and preservation for effects. ∎ - -==== 5.2 Effect Preservation - -*Theorem 5.2 (Effect Preservation)*: If `+Γ ⊢ e : τ ! ε+` and -`+e ⟶ e'+`, then `+Γ ⊢ e' : τ ! ε'+` where `+ε' ⊆ ε+`. - -*Proof*: By induction on the reduction. - -_Case Handler-Return_: `+handle v with h ⟶ e_ret[v/x]+` The handler -removes the handled effect, so `+ε' = ε \ E ⊆ ε+`. ✓ - -_Case Effect-Handle_: The handled effect is captured; remaining effects -are preserved. ✓ - -∎ - -==== 5.3 Effect Progress - -*Theorem 5.3 (Effect Progress)*: If `+· ⊢ e : τ ! ε+` then either: 1. e -is a value, or 2. e can reduce, or 3. e = `+E[perform op(v)]+` where -`+op ∈ ε+` - -Case 3 represents a "`stuck`" effect, but this is only possible if ε ≠ -·. - -*Corollary*: If `+· ⊢ e : τ ! ·+`, then e does not get stuck on effects. - -=== 6. Row-Polymorphic Effects - -==== 6.1 Effect Row Variables - -.... -fn map[A, B, ε](f: A →{ε} B, xs: List[A]) -> List[B] / ε -.... - -The effect variable ε represents any effect row. - -==== 6.2 Effect Row Unification - -.... -ε₁ | ρ₁ ≡ ε₂ | ρ₂ -.... - -Solving: 1. Find common effects in ε₁ and ε₂ 2. Unify remaining with row -variables 3. Generate constraints ρ₁ = ε₂’ | ρ and ρ₂ = ε₁’ | ρ for -fresh ρ - -==== 6.3 Effect Polymorphism Rules - -*EffAbs* - -.... - Γ, ρ:Effect ⊢ e : τ ! ε - ───────────────────────────── - Γ ⊢ Λρ. e : ∀ρ:Effect. τ ! ε -.... - -*EffApp* - -.... - Γ ⊢ e : ∀ρ:Effect. τ Γ ⊢ ε' : Effect - ───────────────────────────────────────── - Γ ⊢ e [ε'] : τ[ε'/ρ] -.... - -=== 7. Effect Inference - -==== 7.1 Algorithm - -Effect inference follows bidirectional type checking: - -[source,ocaml] ----- -(* Infer effects of an expression *) -val infer_effects : ctx -> expr -> (typ * effect) result - -(* Check effects against expected *) -val check_effects : ctx -> expr -> typ -> effect -> unit result ----- - -Key cases: - -[source,ocaml] ----- -let rec infer_effects ctx = function - | Perform (op, arg) -> - let (eff, param_ty, ret_ty) = lookup_operation op in - let _ = check ctx arg param_ty in - Ok (ret_ty, eff) - - | Handle (body, handler) -> - let (body_ty, body_eff) = infer_effects ctx body in - let handled_eff = effects_of_handler handler in - let remaining_eff = subtract body_eff handled_eff in - let result_ty = return_type_of_handler handler body_ty in - Ok (result_ty, remaining_eff) - - | App (f, arg) -> - let (f_ty, f_eff) = infer_effects ctx f in - match f_ty with - | TyArrow (param_ty, ret_ty, fn_eff) -> - let arg_eff = check_effects ctx arg param_ty in - Ok (ret_ty, union [f_eff; arg_eff; fn_eff]) - | _ -> Error "not a function" ----- - -==== 7.2 Effect Constraint Solving - -Generate and solve constraints: - -.... -ε₁ ⊆ ε₂ -- Subsumption -ε₁ | ε₂ = ε₃ -- Composition -ε \ E = ε' -- Subtraction -ρ = ε -- Row variable instantiation -.... - -=== 8. Interaction with Other Features - -==== 8.1 Effects and Quantities - -[source,affinescript] ----- -effect Once { - fire : () → () -- can only be performed once -} - -fn use_once(1 trigger: () →{Once} ()) -> () { - handle trigger() with { - return _ → (), - fire(_, 1 k) → resume(k, ()) -- k is linear - } -} ----- - -==== 8.2 Effects and Ownership - -[source,affinescript] ----- -effect FileIO { - read : own File → (String, own File) - write : (own File, String) → own File -} ----- - -Effect operations can transfer ownership. - -==== 8.3 Effects and Dependent Types - -[source,affinescript] ----- -effect Indexed[n: Nat] { - tick : () → () where n > 0 -- Refined effect -} ----- - -`+[IMPL-DEP: dependent-effects]+` Effect-dependent type interaction -requires further implementation. - -=== 9. Standard Effects - -==== 9.1 IO Effect - -[source,affinescript] ----- -effect IO { - print : String → () - read_line : () → String - read_file : String → String - write_file : (String, String) → () -} ----- - -==== 9.2 State Effect - -[source,affinescript] ----- -effect State[S] { - get : () → S - put : S → () -} - --- Derived operations -fn modify[S](f: S → S) -> () / State[S] { - put(f(get())) -} - --- Standard handler: run with initial state -fn run_state[S, A](init: S, comp: () →{State[S]} A) -> (A, S) { - handle comp() with { - return x → (x, init), - get(_, k) → resume(k, init), - put(s, k) → run_state(s, λ(). resume(k, ())) - } -} ----- - -==== 9.3 Exception Effect - -[source,affinescript] ----- -effect Exn[E] { - raise : E → ⊥ -} - -fn catch[E, A](comp: () →{Exn[E]} A, handler: E → A) -> A { - handle comp() with { - return x → x, - raise(e, _) → handler(e) -- k discarded (no resume) - } -} ----- - -==== 9.4 Async Effect - -[source,affinescript] ----- -effect Async { - fork : (() →{Async} ()) → () - yield : () → () - await : Promise[A] → A -} ----- - -=== 10. Categorical Semantics - -==== 10.1 Free Monad Interpretation - -Effects correspond to free monads: - -.... -Free E A = Return A | Op (Σ op∈E. τ_op × (σ_op → Free E A)) -.... - -*Theorem 10.1*: The effect system is sound with respect to the free -monad semantics. - -==== 10.2 Handler as Algebra - -A handler for effect E is an E-algebra: - -.... -alg : E (σ → A) → A -.... - -The handle construct applies the algebra to eliminate the effect. - -==== 10.3 Relationship to Monads - -*Theorem 10.2*: For any effect E, running under a handler is equivalent -to interpreting in the corresponding monad. - -=== 11. Examples - -==== 11.1 Non-determinism - -[source,affinescript] ----- -effect Choice { - choose : () → Bool - fail : () → ⊥ -} - -fn coin_flip() -> Bool / Choice { - perform choose() -} - -fn all_results[A](comp: () →{Choice} A) -> List[A] { - handle comp() with { - return x → [x], - choose(_, k) → resume(k, true) ++ resume(k, false), - fail(_, _) → [] - } -} ----- - -==== 11.2 Coroutines - -[source,affinescript] ----- -effect Yield[A] { - yield : A → () -} - -type Iterator[A] = - | Done - | Next(A, () →{Yield[A]} ()) - -fn iterate[A](gen: () →{Yield[A]} ()) -> Iterator[A] { - handle gen() with { - return _ → Done, - yield(a, k) → Next(a, k) - } -} ----- - -==== 11.3 Transactional Memory - -[source,affinescript] ----- -effect STM { - read_tvar : TVar[A] → A - write_tvar : (TVar[A], A) → () - retry : () → ⊥ - or_else : (() →{STM} A, () →{STM} A) → A -} ----- - -=== 12. Implementation Notes - -==== 12.1 AST Representation - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type effect_expr = - | EffNamed of ident (* Named effect *) - | EffApp of ident * type_arg list (* Parameterized effect *) - | EffUnion of effect_expr list (* Union *) - | EffVar of ident (* Row variable *) - -type effect_op = { - eo_name : ident; - eo_params : (ident * type_expr) list; - eo_ret_ty : type_expr; -} - -type effect_decl = { - ed_name : ident; - ed_ty_params : ty_param list; - ed_ops : effect_op list; -} ----- - -==== 12.2 Effect Checking Algorithm - -`+[IMPL-DEP: effect-checker]+` - -[source,ocaml] ----- -module EffectChecker : sig - val check_effect_row : ctx -> effect_expr -> effect_kind result - val infer_effects : ctx -> expr -> (typ * effect_row) result - val check_handler_complete : effect_decl -> handler -> bool - val unify_effects : effect_row -> effect_row -> substitution result -end ----- - -=== 13. Related Work - -[arabic] -. *Algebraic Effects*: Plotkin & Power (2002), Plotkin & Pretnar (2009) -. *Effect Handlers*: Bauer & Pretnar (2015), Koka (Leijen, 2014) -. *Row-Polymorphic Effects*: Links (Lindley et al., 2017) -. *Frank*: Lindley, McBride & McLaughlin (2017) -. *Eff Language*: Bauer & Pretnar (2015) -. *Multicore OCaml Effects*: Dolan et al. (2015) - -=== 14. References - -[arabic] -. Plotkin, G., & Pretnar, M. (2013). Handling Algebraic Effects. _LMCS_. -. Bauer, A., & Pretnar, M. (2015). Programming with Algebraic Effects -and Handlers. _JFP_. -. Leijen, D. (2017). Type Directed Compilation of Row-Typed Algebraic -Effects. _POPL_. -. Lindley, S., McBride, C., & McLaughlin, C. (2017). Do Be Do Be Do. -_POPL_. -. Kammar, O., Lindley, S., & Oury, N. (2013). Handlers in Action. -_ICFP_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (effect types), effect -checker implementation - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/Effects.v+` (stub) diff --git a/affinescript/docs/academic/proofs/inference-algorithm.adoc b/affinescript/docs/academic/proofs/inference-algorithm.adoc deleted file mode 100644 index 5eb12aa..0000000 --- a/affinescript/docs/academic/proofs/inference-algorithm.adoc +++ /dev/null @@ -1,474 +0,0 @@ -== Type Inference Algorithm Specification - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Complete -specification `+[IMPL-DEP: type-checker]+` - -=== Abstract - -This document specifies the complete type inference algorithm for -AffineScript, covering bidirectional type checking, unification, -constraint solving, and inference for quantities, effects, rows, and -refinements. - -=== 1. Introduction - -AffineScript’s type inference combines: 1. Bidirectional type checking -(local type inference) 2. Hindley-Milner style polymorphism 3. Row -unification for records and effects 4. Quantity inference 5. Effect -inference 6. SMT-based refinement checking - -=== 2. Algorithm Overview - -==== 2.1 High-Level Structure - -.... -infer(Γ, e) : (τ, ε, C) -.... - -Returns: - τ: The inferred type - ε: The inferred effect - C: Constraint -set to be solved - -==== 2.2 Phases - -[arabic] -. *Elaboration*: Parse to untyped AST -. *Constraint generation*: Traverse AST, generate constraints -. *Constraint solving*: Unify types, solve rows -. *Quantity checking*: Verify usage patterns -. *Effect inference*: Compute effect signatures -. *Refinement checking*: Discharge to SMT - -=== 3. Bidirectional Type Checking - -==== 3.1 Judgments - -.... -Γ ⊢ e ⇒ τ (synthesis: infer type of e) -Γ ⊢ e ⇐ τ (checking: check e has type τ) -.... - -==== 3.2 Algorithm - -[source,ocaml] ----- -type result = (typ * effect * constraints) - -(* Synthesis: infer type *) -let rec synth (ctx : context) (e : expr) : result = - match e with - | Var x -> - let τ = lookup ctx x in - (τ, Pure, []) - - | Lit l -> - (type_of_literal l, Pure, []) - - | Lam (x, Some τ, body) -> - let (σ, ε, c) = synth (extend ctx x τ) body in - (Arrow (τ, σ, ε), Pure, c) - - | Lam (x, None, body) -> - let α = fresh_tyvar () in - let (σ, ε, c) = synth (extend ctx x α) body in - (Arrow (α, σ, ε), Pure, c) - - | App (f, a) -> - let (τ_f, ε_f, c_f) = synth ctx f in - let α = fresh_tyvar () in - let β = fresh_tyvar () in - let ρ = fresh_effvar () in - let c_arr = (τ_f, Arrow (α, β, ρ)) in - let (ε_a, c_a) = check ctx a α in - (β, union [ε_f; ε_a; ρ], c_f @ c_a @ [c_arr]) - - | Let (x, e1, e2) -> - let (τ1, ε1, c1) = synth ctx e1 in - let σ = generalize ctx τ1 in - let (τ2, ε2, c2) = synth (extend ctx x σ) e2 in - (τ2, union [ε1; ε2], c1 @ c2) - - | Ann (e, τ) -> - let (ε, c) = check ctx e τ in - (τ, ε, c) - - | Record fields -> - let (field_types, effs, cs) = - List.fold_right (fun (l, e) (fts, es, cs) -> - let (τ, ε, c) = synth ctx e in - ((l, τ) :: fts, ε :: es, c @ cs) - ) fields ([], [], []) - in - (TyRecord field_types, union effs, cs) - - | RecordProj (e, l) -> - let (τ, ε, c) = synth ctx e in - let α = fresh_tyvar () in - let ρ = fresh_rowvar () in - let c_row = (τ, TyRecord ((l, α) :: ρ)) in - (α, ε, c @ [c_row]) - - | Perform (op, arg) -> - let (τ_arg, τ_ret, E) = lookup_operation op in - let (ε, c) = check ctx arg τ_arg in - (τ_ret, union [ε; EffSingleton E], c) - - | Handle (body, handler) -> - let (τ_body, ε_body, c_body) = synth ctx body in - let (E, handled_eff) = effect_of_handler handler in - let (τ_result, c_handler) = check_handler ctx handler τ_body in - let ε_remaining = subtract ε_body handled_eff in - (τ_result, ε_remaining, c_body @ c_handler) - - | _ -> failwith "Cannot synthesize" - -(* Checking: verify type *) -and check (ctx : context) (e : expr) (τ : typ) : (effect * constraints) = - match (e, τ) with - | (Lam (x, None, body), Arrow (τ1, τ2, ε)) -> - let (ε', c) = check (extend ctx x τ1) body τ2 in - (Pure, c @ [(ε', ε)]) - - | (If (cond, then_, else_), τ) -> - let (ε1, c1) = check ctx cond TyBool in - let (ε2, c2) = check ctx then_ τ in - let (ε3, c3) = check ctx else_ τ in - (union [ε1; ε2; ε3], c1 @ c2 @ c3) - - | (Case (scrut, branches), τ) -> - let (τ_scrut, ε_scrut, c_scrut) = synth ctx scrut in - let (effs, cs) = List.split ( - List.map (check_branch ctx τ_scrut τ) branches - ) in - (union (ε_scrut :: effs), c_scrut @ List.concat cs) - - | (e, τ) -> - (* Subsumption *) - let (τ', ε, c) = synth ctx e in - (ε, c @ [(τ', τ)]) (* generate constraint τ' <: τ *) ----- - -=== 4. Constraint Solving - -==== 4.1 Constraint Types - -[source,ocaml] ----- -type constraint = - | Eq of typ * typ (* τ₁ = τ₂ *) - | Sub of typ * typ (* τ₁ <: τ₂ *) - | RowEq of row * row (* ρ₁ = ρ₂ *) - | EffEq of effect * effect (* ε₁ = ε₂ *) - | QuantEq of quantity * quantity (* π₁ = π₂ *) - | Lacks of row * label (* ρ lacks l *) - | Valid of predicate (* ⊨ φ (SMT) *) ----- - -==== 4.2 Unification - -[source,ocaml] ----- -let rec unify (subst : substitution) (c : constraint) : substitution = - match c with - | Eq (TyVar α, τ) when not (occurs α τ) -> - compose (singleton α τ) subst - - | Eq (τ, TyVar α) when not (occurs α τ) -> - compose (singleton α τ) subst - - | Eq (Arrow (τ1, σ1, ε1), Arrow (τ2, σ2, ε2)) -> - let s1 = unify subst (Eq (τ1, τ2)) in - let s2 = unify s1 (Eq (apply s1 σ1, apply s1 σ2)) in - unify s2 (EffEq (apply s2 ε1, apply s2 ε2)) - - | Eq (TyRecord r1, TyRecord r2) -> - unify_rows subst r1 r2 - - | Eq (TyApp (c1, args1), TyApp (c2, args2)) when c1 = c2 -> - List.fold_left2 (fun s a1 a2 -> - unify s (Eq (apply s a1, apply s a2)) - ) subst args1 args2 - - | Eq (τ1, τ2) when τ1 = τ2 -> - subst - - | Eq (τ1, τ2) -> - raise (UnificationError (τ1, τ2)) - - | Sub (τ1, τ2) -> - (* For now, subtyping degenerates to equality *) - (* TODO: proper subtyping with refinements *) - unify subst (Eq (τ1, τ2)) - - | RowEq (r1, r2) -> - unify_rows subst r1 r2 - - | EffEq (ε1, ε2) -> - unify_effects subst ε1 ε2 - - | Lacks (r, l) -> - check_lacks subst r l - - | Valid φ -> - (* Defer to SMT *) - if smt_check φ then subst - else raise (RefinementError φ) ----- - -==== 4.3 Row Unification - -[source,ocaml] ----- -let rec unify_rows (subst : substitution) (r1 : row) (r2 : row) : substitution = - match (apply_row subst r1, apply_row subst r2) with - | (RowEmpty, RowEmpty) -> - subst - - | (RowVar ρ, r) | (r, RowVar ρ) when not (row_occurs ρ r) -> - compose (singleton_row ρ r) subst - - | (RowExtend (l1, τ1, r1'), RowExtend (l2, τ2, r2')) when l1 = l2 -> - let s1 = unify subst (Eq (τ1, τ2)) in - unify_rows s1 (apply_row s1 r1') (apply_row s1 r2') - - | (RowExtend (l1, τ1, r1'), RowExtend (l2, τ2, r2')) when l1 <> l2 -> - (* Rewrite: find l1 in r2, l2 in r1 *) - let ρ = fresh_rowvar () in - let s1 = unify_rows subst r1' (RowExtend (l2, τ2, ρ)) in - let s2 = unify_rows s1 r2' (RowExtend (l1, τ1, apply_row s1 ρ)) in - s2 - - | (RowEmpty, RowExtend _) | (RowExtend _, RowEmpty) -> - raise (RowMismatch (r1, r2)) ----- - -==== 4.4 Effect Unification - -[source,ocaml] ----- -let rec unify_effects (subst : substitution) (ε1 : effect) (ε2 : effect) : substitution = - match (apply_eff subst ε1, apply_eff subst ε2) with - | (EffPure, EffPure) -> subst - | (EffVar ρ, ε) | (ε, EffVar ρ) when not (eff_occurs ρ ε) -> - compose (singleton_eff ρ ε) subst - | (EffUnion es1, EffUnion es2) -> - (* Set-based unification *) - unify_effect_sets subst es1 es2 - | _ -> - raise (EffectMismatch (ε1, ε2)) ----- - -=== 5. Generalization - -==== 5.1 Let-Generalization - -[source,ocaml] ----- -let generalize (ctx : context) (τ : typ) : scheme = - let free_in_ctx = free_tyvars_ctx ctx in - let free_in_type = free_tyvars τ in - let generalizable = SetDiff free_in_type free_in_ctx in - Forall (Set.elements generalizable, τ) ----- - -==== 5.2 Instantiation - -[source,ocaml] ----- -let instantiate (scheme : scheme) : typ = - match scheme with - | Forall (vars, τ) -> - let fresh_vars = List.map (fun _ -> fresh_tyvar ()) vars in - let subst = List.combine vars fresh_vars in - apply_subst subst τ ----- - -=== 6. Quantity Inference - -==== 6.1 Usage Analysis - -[source,ocaml] ----- -type usage = Zero | One | Many - -let rec analyze_usage (x : var) (e : expr) : usage = - match e with - | Var y -> if x = y then One else Zero - | Lam (y, _, body) -> if x = y then Zero else analyze_usage x body - | App (f, a) -> combine (analyze_usage x f) (analyze_usage x a) - | Let (y, e1, e2) -> - let u1 = analyze_usage x e1 in - let u2 = if x = y then Zero else analyze_usage x e2 in - combine u1 u2 - | _ -> fold_expr (combine) Zero (analyze_usage x) e - -let combine u1 u2 = - match (u1, u2) with - | (Zero, u) | (u, Zero) -> u - | _ -> Many ----- - -==== 6.2 Quantity Constraints - -[source,ocaml] ----- -let check_quantity (expected : quantity) (actual : usage) : bool = - match (expected, actual) with - | (QZero, Zero) -> true - | (QOne, One) -> true - | (QOne, Zero) -> true (* Affine: can drop *) - | (QOmega, _) -> true - | _ -> false ----- - -=== 7. Effect Inference - -==== 7.1 Effect Collection - -[source,ocaml] ----- -let rec collect_effects (e : expr) : effect = - match e with - | Perform (op, _) -> EffSingleton (effect_of_op op) - | Handle (body, handler) -> - let ε_body = collect_effects body in - let handled = handled_effects handler in - EffSubtract ε_body handled - | App (f, a) -> - let ε_f = collect_effects f in - let ε_a = collect_effects a in - let ε_call = effect_of_call f in - EffUnion [ε_f; ε_a; ε_call] - | _ -> fold_effects EffUnion EffPure collect_effects e ----- - -=== 8. Refinement Checking - -==== 8.1 VC Generation - -[source,ocaml] ----- -let rec generate_vc (ctx : context) (e : expr) (τ : typ) : predicate list = - match (e, τ) with - | (_, TyRefine (base, φ)) -> - let base_vcs = generate_vc ctx e base in - let inst_φ = substitute_expr e φ in - inst_φ :: base_vcs - - | (If (cond, then_, else_), τ) -> - let then_ctx = assume ctx cond in - let else_ctx = assume ctx (Not cond) in - generate_vc then_ctx then_ τ @ - generate_vc else_ctx else_ τ - - | _ -> [] ----- - -==== 8.2 SMT Discharge - -[source,ocaml] ----- -let check_refinements (vcs : predicate list) : unit = - List.iter (fun vc -> - let smt_query = translate_to_smt vc in - match Smt.check smt_query with - | Smt.Valid -> () - | Smt.Invalid model -> raise (RefinementViolation (vc, model)) - | Smt.Unknown -> raise (RefinementTimeout vc) - ) vcs ----- - -=== 9. Complete Algorithm - -==== 9.1 Main Entry Point - -[source,ocaml] ----- -let type_check (program : program) : typed_program = - (* Phase 1: Parse *) - let ast = parse program in - - (* Phase 2: Constraint generation *) - let (typed_ast, constraints) = elaborate empty_ctx ast in - - (* Phase 3: Constraint solving *) - let subst = solve constraints in - - (* Phase 4: Apply substitution *) - let resolved_ast = apply_subst_program subst typed_ast in - - (* Phase 5: Quantity checking *) - check_quantities resolved_ast; - - (* Phase 6: Effect checking *) - check_effects resolved_ast; - - (* Phase 7: Refinement checking *) - let vcs = collect_vcs resolved_ast in - check_refinements vcs; - - (* Phase 8: Borrow checking *) - borrow_check resolved_ast; - - resolved_ast ----- - -=== 10. Correctness - -==== 10.1 Soundness - -*Theorem 10.1 (Inference Soundness)*: If `+type_check(e) = τ+` then -`+⊢ e : τ+`. - -==== 10.2 Completeness - -*Theorem 10.2 (Inference Completeness)*: If `+⊢ e : τ+` then -`+type_check(e)+` succeeds with a type τ’ such that τ is an instance of -τ’. - -==== 10.3 Principal Types - -*Theorem 10.3 (Principal Types)*: The algorithm computes principal -types. - -=== 11. Complexity Analysis - -[cols=",",options="header",] -|=== -|Phase |Complexity -|Parsing |O(n) -|Constraint generation |O(n) -|Unification |O(n²) worst, O(n) typical -|Quantity checking |O(n) -|Effect inference |O(n) -|Refinement checking |Depends on SMT -|Borrow checking |O(n²) worst -|=== - -=== 12. Implementation Notes - -See `+lib/+` for OCaml implementation (pending). - -[source,ocaml] ----- -(* lib/infer.mli *) -module Infer : sig - val infer : Context.t -> Ast.expr -> (Ast.typ * Ast.effect) result - val check : Context.t -> Ast.expr -> Ast.typ -> Ast.effect result - val elaborate : Ast.program -> TypedAst.program result -end ----- - -=== 13. References - -[arabic] -. Dunfield, J., & Krishnaswami, N. (2021). Bidirectional Typing. _ACM -Computing Surveys_. -. Pottier, F., & Rémy, D. (2005). The Essence of ML Type Inference. -_ATTAPL_. -. Vytiniotis, D., et al. (2011). OutsideIn(X): Modular Type Inference -with Local Assumptions. _JFP_. - -''''' - -*Document Metadata*: - Implementation: `+lib/infer.ml+` (pending) - -Dependencies: Parser, AST, SMT interface diff --git a/affinescript/docs/academic/proofs/ownership-soundness.adoc b/affinescript/docs/academic/proofs/ownership-soundness.adoc deleted file mode 100644 index 1b8b657..0000000 --- a/affinescript/docs/academic/proofs/ownership-soundness.adoc +++ /dev/null @@ -1,681 +0,0 @@ -== Ownership System: Formal Verification - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: borrow-checker]+` - -=== Abstract - -This document presents the formal semantics and soundness proofs for -AffineScript’s ownership system. We prove that well-typed programs are -memory-safe: no use-after-free, no double-free, no data races, and no -dangling references. The system combines affine types with a -borrow-checking discipline inspired by Rust but adapted for -AffineScript’s dependent and effect-typed setting. - -=== 1. Introduction - -AffineScript’s ownership system provides compile-time memory safety -guarantees through: - -[arabic] -. *Ownership*: Each value has exactly one owner -. *Move semantics*: Ownership is transferred on assignment -. *Borrowing*: Temporary access without ownership transfer -. *Lifetimes*: Scoped validity of references -. *Affine types*: Values must be used at most once (or explicitly -dropped) - -These features integrate with AffineScript’s quantity annotations, -providing a unified treatment of linearity and ownership. - -=== 2. Syntax - -==== 2.1 Ownership Modifiers - -.... -own τ -- Owned value of type τ -ref τ -- Immutable borrow of τ -mut τ -- Mutable borrow of τ -.... - -==== 2.2 Expressions with Ownership - -.... -e ::= - | ... - | move e -- Explicit move - | &e -- Immutable borrow - | &mut e -- Mutable borrow - | *e -- Dereference - | drop e -- Explicit drop -.... - -==== 2.3 Lifetimes - -.... -'a, 'b, 'c, ... -- Lifetime variables -'static -- Static lifetime (lives forever) - -ref['a] τ -- Reference with explicit lifetime -mut['a] τ -- Mutable reference with lifetime -.... - -=== 3. Ownership Model - -==== 3.1 Ownership Tree - -At any point in execution, values form an ownership tree: - -.... -root -├── x: own String -│ └── (owns heap allocation) -├── y: own Vec[Int] -│ ├── (owns buffer) -│ └── (owns elements) -└── z: ref String - └── (borrows from x) -.... - -==== 3.2 Ownership Invariants - -*Invariant 1 (Unique Ownership)*: Each owned value has exactly one -owning binding. - -*Invariant 2 (Borrow Validity)*: All borrows are valid (not dangling). - -*Invariant 3 (Borrow Exclusivity)*: At any point: - Multiple immutable -borrows (`+ref τ+`) may coexist, OR - Exactly one mutable borrow -(`+mut τ+`) exists - But not both simultaneously - -*Invariant 4 (Lifetime Containment)*: A borrow’s lifetime is contained -within the owner’s lifetime. - -=== 4. Static Semantics - -==== 4.1 Contexts with Ownership - -.... -Γ ::= · | Γ, x: own τ | Γ, x: ref['a] τ | Γ, x: mut['a] τ -.... - -Additionally, we track: - *Live set* L: Variables currently in scope and -valid - *Borrow set* B: Active borrows and their origins - *Move set* M: -Variables that have been moved - -==== 4.2 Well-Formedness - -*Γ ⊢ wf* (context well-formed) - -.... - ────── - · ⊢ wf - - Γ ⊢ wf x ∉ dom(Γ) Γ ⊢ τ : Type - ──────────────────────────────────── - Γ, x: own τ ⊢ wf - - Γ ⊢ wf 'a ∈ Γ x ∉ dom(Γ) Γ ⊢ τ : Type - ──────────────────────────────────────────────── - Γ, x: ref['a] τ ⊢ wf -.... - -==== 4.3 Typing Rules - -*Own-Intro* - -.... - Γ ⊢ e : τ - ────────────────── - Γ ⊢ e : own τ -.... - -*Own-Elim (Move)* - -.... - Γ, x: own τ ⊢ x : own τ x ∉ M - ───────────────────────────────── - Γ ⊢ move x : own τ - (adds x to M) -.... - -*Borrow-Imm* - -.... - Γ ⊢ e : own τ e is a place - 'a = lifetime(e) no mut borrows of e active - ────────────────────────────────────────────── - Γ ⊢ &e : ref['a] τ - (adds borrow to B) -.... - -*Borrow-Mut* - -.... - Γ ⊢ e : own τ e is a place - 'a = lifetime(e) no borrows of e active - ───────────────────────────────────────── - Γ ⊢ &mut e : mut['a] τ - (adds exclusive borrow to B) -.... - -*Deref-Imm* - -.... - Γ ⊢ e : ref['a] τ - ────────────────── - Γ ⊢ *e : τ -.... - -*Deref-Mut* - -.... - Γ ⊢ e : mut['a] τ - ────────────────── - Γ ⊢ *e : τ -- for reading - Γ ⊢ *e := v : () -- for writing -.... - -*Drop* - -.... - Γ ⊢ x : own τ x ∉ M no borrows from x active - ────────────────────────────────────────────────── - Γ ⊢ drop x : () - (adds x to M, calls destructor) -.... - -==== 4.4 Lifetime Rules - -*Lifetime Inclusion* - -.... - 'a ⊆ 'b (lifetime 'a outlives 'b) -.... - -Rules: - -.... - ────────────── - 'static ⊆ 'a - - 'a ⊆ 'a - - 'a ⊆ 'b 'b ⊆ 'c - ──────────────────── - 'a ⊆ 'c -.... - -*Reference Covariance* - -.... - 'a ⊆ 'b τ = σ - ──────────────────────── - ref['a] τ <: ref['b] σ -.... - -*Reference Invariance (Mutable)* - -.... - 'a = 'b τ = σ - ──────────────────────── - mut['a] τ = mut['b] σ -.... - -(Mutable references are invariant in both lifetime and type) - -==== 4.5 Non-Lexical Lifetimes - -Lifetimes are computed based on actual usage, not lexical scope: - -[source,affinescript] ----- -fn example() { - let mut x = 5 - let y = &x -- borrow starts here - println(y) -- last use of y - // borrow ends here (not at end of scope) - x = 10 -- mutation OK, borrow ended -} ----- - -*NLL Judgment*: `+Γ ⊢ e : τ @ ['a₁, 'a₂]+` - -Where `+['a₁, 'a₂]+` is the lifetime interval of the expression. - -=== 5. Borrow Checking Algorithm - -==== 5.1 Places - -A *place* is an l-value that can be borrowed: - -.... -place ::= - | x -- Variable - | place.field -- Field access - | place[i] -- Index - | *place -- Deref -.... - -==== 5.2 Borrow Tracking - -[source,ocaml] ----- -type borrow = { - place : place; - kind : Shared | Exclusive; - lifetime : lifetime; - origin : location; (* source code location *) -} - -type borrow_state = { - active : borrow list; - moved : place set; -} ----- - -==== 5.3 Conflict Detection - -[source,ocaml] ----- -let conflicts (b1 : borrow) (b2 : borrow) : bool = - overlaps b1.place b2.place && - (b1.kind = Exclusive || b2.kind = Exclusive) - -let check_borrow (state : borrow_state) (new_borrow : borrow) : result = - if is_moved state new_borrow.place then - Error "use after move" - else if List.exists (conflicts new_borrow) state.active then - Error "conflicting borrow" - else - Ok { state with active = new_borrow :: state.active } ----- - -==== 5.4 Lifetime Inference - -[source,ocaml] ----- -(* Compute minimal lifetime for a borrow *) -let infer_lifetime (uses : location list) (scope : scope) : lifetime = - let last_use = List.fold_left max (List.hd uses) uses in - Lifetime.from_span (List.hd uses) last_use scope ----- - -=== 6. Soundness Theorems - -==== 6.1 Memory Safety - -*Theorem 6.1 (No Use After Free)*: If `+Γ ⊢ e : τ+` and e reduces -without error, then e never accesses freed memory. - -*Proof Sketch*: 1. Owned values are freed when dropped or when owner -goes out of scope 2. Borrows must have lifetimes contained in owner’s -lifetime 3. The borrow checker ensures no access after owner is freed - -By induction on the reduction sequence, maintaining the invariant that -all accessed memory is either owned or validly borrowed. ∎ - -==== 6.2 No Double Free - -*Theorem 6.2 (No Double Free)*: If `+Γ ⊢ e : τ+`, then no value is freed -twice. - -*Proof Sketch*: 1. Each value has exactly one owner (Invariant 1) 2. -Move semantics transfers ownership, invalidating the source 3. The move -set M tracks moved values, preventing re-drop - -∎ - -==== 6.3 No Data Races - -*Theorem 6.3 (Data Race Freedom)*: If `+Γ ⊢ e : τ+` and e is executed -concurrently, there are no data races. - -*Definition (Data Race)*: Two accesses to the same memory location form -a data race if: 1. At least one is a write 2. They are not synchronized -3. They happen concurrently - -*Proof Sketch*: 1. By Invariant 3, mutable borrows are exclusive 2. -Shared borrows are immutable (no writes) 3. Owned values cannot be -accessed from other threads without transfer 4. Therefore, no -unsynchronized concurrent write+access - -∎ - -==== 6.4 Borrow Validity - -*Theorem 6.4 (No Dangling References)*: If `+Γ ⊢ e : ref['a] τ+`, then -dereferencing e never accesses invalid memory. - -*Proof*: By Invariant 4, the borrow lifetime ’a is contained in the -owner’s lifetime. The borrow checker ensures ’a does not exceed the -owner’s scope. Therefore, when the borrow is used, the owner is still -valid. ∎ - -=== 7. Integration with QTT - -==== 7.1 Quantities and Ownership - -The ownership modifiers interact with quantities: - -[cols=",,",options="header",] -|=== -|Quantity |Owned |Borrowed -|0 |Type-level only |Type-level only -|1 |Must use once |Must use once -|ω |Requires Copy |Multiple uses OK -|=== - -==== 7.2 Copy Trait - -[source,affinescript] ----- -trait Copy { - fn copy(self: ref Self) -> Self -} ----- - -Only types implementing `+Copy+` can have unrestricted quantity with -ownership: - -*Copy-Omega* - -.... - Γ ⊢ e : own τ τ : Copy π = ω - ──────────────────────────────────── - Γ, πx:own τ ⊢ ... -.... - -==== 7.3 Affine vs Linear - -AffineScript is *affine* by default: - Values with quantity 1 may be -used _at most_ once - They may also be explicitly dropped - This differs -from true linear types where values _must_ be used exactly once - -*Affine-Drop* - -.... - Γ, 1x:own τ ⊢ drop x : () - ───────────────────────────── - (x is consumed, not used in computation) -.... - -For resources that must be explicitly handled (like file handles), use: - -[source,affinescript] ----- --- MustUse marker prevents implicit drop -type MustUse[T] = own T where must_use - -fn use_file(1 f: MustUse[File]) -> () / IO { - // Cannot drop f implicitly; must close or return - close(f) -} ----- - -=== 8. Ownership and Effects - -==== 8.1 Effect Operations with Ownership - -[source,affinescript] ----- -effect Resource[R] { - acquire : () → own R - release : own R → () -} ----- - -==== 8.2 RAII via Handlers - -[source,affinescript] ----- -fn with_resource[R, A]( - comp: (own R) →{ε} A -) -> A / Resource[R] | ε { - let r = perform acquire() - let result = comp(r) - // r has been moved into comp - result -} ----- - -==== 8.3 Ownership Transfer in Continuations - -When a handler captures a continuation, ownership must be preserved: - -[source,affinescript] ----- -effect Transfer { - give : own T → () - take : () → own T -} - -fn transfer_handler[T, A]( - comp: () →{Transfer} A -) -> A { - handle comp() with { - return x → x, - give(t, k) → { - // t is owned here, must transfer to take - handle resume(k, ()) with { - take(_, k2) → resume(k2, t) - } - } - } -} ----- - -=== 9. Ownership and Dependent Types - -==== 9.1 Indexed Owned Types - -[source,affinescript] ----- -type OwnedVec[n: Nat, T] = own { data: Ptr[T], len: n } ----- - -==== 9.2 Refinements on Ownership - -[source,affinescript] ----- -fn split[n: Nat, m: Nat, T]( - vec: own Vec[n + m, T] -) -> (own Vec[n, T], own Vec[m, T]) { - // Ownership of vec is split into two parts - ... -} ----- - -==== 9.3 Proof-Carrying Ownership - -[source,affinescript] ----- -fn take_ownership[T]( - x: ref T, - 0 proof: can_take_ownership(x) -- proof is erased -) -> own T { - unsafe_take_ownership(x) -} ----- - -=== 10. Dynamic Semantics with Ownership - -==== 10.1 Runtime Representation - -At runtime, ownership is erased but the invariants are guaranteed: - -.... -Heap H ::= {ℓ₁ ↦ v₁, ..., ℓₙ ↦ vₙ} -Stack S ::= · | S, x ↦ ℓ | S, x ↦ ref(ℓ) -.... - -==== 10.2 Reduction with Heap - -.... -(e, H) ⟶ (e', H') -.... - -*Alloc* - -.... - ℓ fresh - ───────────────────────────────── - (alloc(v), H) ⟶ (ℓ, H[ℓ ↦ v]) -.... - -*Drop* - -.... - x ↦ ℓ ∈ S - ─────────────────────────────────────── - (drop x, S, H) ⟶ ((), S \ x, H \ ℓ) -.... - -*Move* - -.... - x ↦ ℓ ∈ S - ────────────────────────────────────────────── - (let y = move x in e, S, H) ⟶ (e[y/x], (S \ x)[y ↦ ℓ], H) -.... - -==== 10.3 Type Safety with Heap - -*Theorem 10.1 (Heap Type Safety)*: If `+Γ | Σ ⊢ e : τ+` and `+Σ ⊢ H+` -and `+(e, H) ⟶* (e', H')+`, then either: 1. e’ is a value, or 2. (e’, -H’) can step - -And there exists Σ’ ⊇ Σ such that `+Γ | Σ' ⊢ e' : τ+` and `+Σ' ⊢ H'+`. - -=== 11. Examples - -==== 11.1 File Handling - -[source,affinescript] ----- -fn process_file(path: String) -> Result[String, IOError] / IO { - let file = File::open(path)? -- file: own File - let contents = file.read_to_string() -- moves file - // file no longer accessible - Ok(contents) -} ----- - -==== 11.2 Container with Borrows - -[source,affinescript] ----- -fn find_max['a, T: Ord](slice: ref['a] [T]) -> Option[ref['a] T] { - if slice.is_empty() { - None - } else { - let mut max = &slice[0] - for i in 1..slice.len() { - if slice[i] > *max { - max = &slice[i] - } - } - Some(max) - } -} ----- - -==== 11.3 Self-Referential Structures - -[source,affinescript] ----- --- Using explicit lifetime annotation -struct Parser['a] { - input: ref['a] String, - position: Nat, -} - -fn parse['a](input: ref['a] String) -> Parser['a] { - Parser { input: input, position: 0 } -} ----- - -=== 12. Implementation - -==== 12.1 AST Representation - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type ownership = - | Own (* Owned value *) - | Ref (* Immutable borrow *) - | Mut (* Mutable borrow *) - -type type_expr = - | ... - | TyOwn of type_expr - | TyRef of type_expr (* with implicit lifetime *) - | TyMut of type_expr ----- - -==== 12.2 Borrow Checker Module - -`+[IMPL-DEP: borrow-checker]+` - -[source,ocaml] ----- -module BorrowChecker : sig - type place - type borrow - type state - - val check_expr : state -> expr -> (state * typ) result - val check_borrow : state -> place -> borrow_kind -> lifetime result - val check_move : state -> place -> state result - val end_lifetime : state -> lifetime -> state - val report_conflicts : state -> diagnostic list -end ----- - -==== 12.3 Lifetime Inference Module - -`+[IMPL-DEP: lifetime-inference]+` - -[source,ocaml] ----- -module LifetimeInference : sig - type constraint = - | Outlives of lifetime * lifetime - | Equals of lifetime * lifetime - - val gather_constraints : expr -> constraint list - val solve : constraint list -> substitution result - val infer_lifetimes : expr -> expr (* annotated with lifetimes *) -end ----- - -=== 13. Related Work - -[arabic] -. *Rust Ownership*: Matsakis & Klock (2014), RustBelt (Jung et al., -2017) -. *Cyclone*: Jim et al. (2002) - Region-based memory management -. *Linear Haskell*: Bernardy et al. (2018) - Linearity in Haskell -. *Mezzo*: Pottier & Protzenko (2013) - Permissions and ownership -. *ATS*: Xi (2004) - Linear types with dependent types -. *Vault*: DeLine & Fähndrich (2001) - Adoption and focus - -=== 14. References - -[arabic] -. Jung, R., et al. (2017). RustBelt: Securing the Foundations of the -Rust Programming Language. _POPL_. -. Matsakis, N., & Klock, F. (2014). The Rust Language. _HILT_. -. Weiss, A., et al. (2019). Oxide: The Essence of Rust. _arXiv_. -. Jim, T., et al. (2002). Cyclone: A Safe Dialect of C. _USENIX_. -. Pottier, F., & Protzenko, J. (2013). Programming with Permissions in -Mezzo. _ICFP_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (ownership type), -borrow checker implementation - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/Ownership.v+` (stub) diff --git a/affinescript/docs/academic/proofs/quantitative-types.adoc b/affinescript/docs/academic/proofs/quantitative-types.adoc deleted file mode 100644 index 7dcb431..0000000 --- a/affinescript/docs/academic/proofs/quantitative-types.adoc +++ /dev/null @@ -1,518 +0,0 @@ -== Quantitative Type Theory in AffineScript - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: type-checker, borrow-checker]+` - -=== Abstract - -This document formalizes AffineScript’s quantitative type system, which -annotates types with usage quantities to enforce linearity constraints. -We prove that the quantity discipline is sound: variables annotated with -quantity 1 (linear) are used exactly once, variables with quantity 0 -(erased) are never used at runtime, and variables with quantity ω -(unrestricted) may be used arbitrarily. - -=== 1. Introduction - -AffineScript implements Quantitative Type Theory (QTT), following the -work of Atkey (2018) and McBride (2016). Unlike traditional linear type -systems, QTT integrates quantities into a dependent type theory, -enabling: - -[arabic] -. *Compile-time erasure*: Types and proofs can be marked with 0 and -erased -. *Linear resources*: Values used exactly once with quantity 1 -. *Unrestricted values*: Normal values with quantity ω -. *Quantity polymorphism*: Abstracting over quantities - -=== 2. Quantity Semiring - -==== 2.1 Definition - -Quantities form a semiring (R, 0, 1, +, ×): - -.... -π, ρ, σ ∈ {0, 1, ω} -.... - -*Addition* (for context splitting in pairs/lets): - -.... - 0 + π = π - π + 0 = π - 1 + 1 = ω - 1 + ω = ω - ω + 1 = ω - ω + ω = ω -.... - -*Multiplication* (for scaling contexts in application): - -.... - 0 × π = 0 - π × 0 = 0 - 1 × π = π - π × 1 = π - ω × ω = ω -.... - -==== 2.2 Semiring Laws - -*Theorem 2.1*: (R, 0, 1, +, ×) satisfies the semiring axioms: - -[arabic] -. (R, 0, +) is a commutative monoid -. (R, 1, ×) is a monoid -. × distributes over + -. 0 annihilates: 0 × π = π × 0 = 0 - -*Proof*: By case analysis on all combinations. ∎ - -==== 2.3 Ordering - -We define a preorder on quantities: - -.... - 0 ≤ 0 - 0 ≤ 1 - 0 ≤ ω - 1 ≤ 1 - 1 ≤ ω - ω ≤ ω -.... - -This captures the "`can be used as`" relation: a more restricted -quantity can substitute for a less restricted one. - -*Lemma 2.2*: The ordering respects semiring operations: - If π ≤ π’ and -ρ ≤ ρ’, then π + ρ ≤ π’ + ρ’ - If π ≤ π’ and ρ ≤ ρ’, then π × ρ ≤ π’ × -ρ’ - -*Proof*: By case analysis. ∎ - -=== 3. Syntax with Quantities - -==== 3.1 Quantified Types - -.... -τ, σ ::= - | ... -- Base types (as before) - | (π x : τ) → σ -- Quantified function type - | (π x : τ) × σ -- Quantified pair type -.... - -The quantity π specifies how many times the argument x may be used in -the body. - -==== 3.2 Quantified Contexts - -Contexts associate variables with both types and quantities: - -.... -Γ ::= · | Γ, πx:τ -.... - -==== 3.3 Context Operations - -*Zero Context*: All variables have quantity 0 - -.... -0Γ = {0x:τ | x:τ ∈ Γ} -.... - -*Context Scaling*: - -.... -πΓ = {(π×ρ)x:τ | ρx:τ ∈ Γ} -.... - -*Context Addition*: - -.... -Γ + Δ = {(π+ρ)x:τ | πx:τ ∈ Γ, ρx:τ ∈ Δ} -.... - -(Defined only when Γ and Δ have the same variables and types) - -=== 4. Typing Rules with Quantities - -==== 4.1 Core Judgment - -.... -Γ ⊢ e : τ -.... - -where Γ is a quantified context specifying exactly how each variable is -used. - -==== 4.2 Structural Rules - -*Var* - -.... - ───────────────────────────── - 0Γ, 1x:τ, 0Δ ⊢ x : τ -.... - -Note: Only x has quantity 1; all other variables have quantity 0. - -*Weaken* - -.... - Γ ⊢ e : τ 0 ≤ π - ───────────────────── - Γ, πx:σ ⊢ e : τ -.... - -(Weakening is only valid at quantity 0) - -==== 4.3 Function Types - -*Lam* - -.... - Γ, πx:τ ⊢ e : σ - ───────────────────────────── - Γ ⊢ λx. e : (π x : τ) → σ -.... - -*App* - -.... - Γ ⊢ e₁ : (π x : τ) → σ Δ ⊢ e₂ : τ - ────────────────────────────────────── - Γ + πΔ ⊢ e₁ e₂ : σ[e₂/x] -.... - -The context for e₂ is scaled by π: - If π = 0, the argument is erased (Δ -must be empty/0) - If π = 1, the argument is used linearly - If π = ω, -the argument is used unrestrictedly - -==== 4.4 Pair Types - -*Pair-Intro* - -.... - Γ ⊢ e₁ : τ Δ ⊢ e₂ : σ[e₁/x] - ───────────────────────────────── - Γ + Δ ⊢ (e₁, e₂) : (π x : τ) × σ -.... - -*Pair-Elim* - -.... - Γ ⊢ e₁ : (π x : τ) × σ Δ, πx:τ, 1y:σ ⊢ e₂ : ρ - ────────────────────────────────────────────────── - Γ + Δ ⊢ let (x, y) = e₁ in e₂ : ρ -.... - -==== 4.5 Let Binding - -*Let* - -.... - Γ ⊢ e₁ : τ Δ, πx:τ ⊢ e₂ : σ - ─────────────────────────────── - πΓ + Δ ⊢ let x = e₁ in e₂ : σ -.... - -==== 4.6 Quantity Polymorphism - -*QuantAbs* - -.... - Γ ⊢ e : τ - ────────────────── - Γ ⊢ Λπ. e : ∀π. τ -.... - -*QuantApp* - -.... - Γ ⊢ e : ∀π. τ - ────────────────── - Γ ⊢ e [ρ] : τ[ρ/π] -.... - -=== 5. Soundness of Quantities - -==== 5.1 Runtime Irrelevance of 0 - -*Theorem 5.1 (Erasure Soundness)*: If `+Γ ⊢ e : τ+` where x has quantity -0 in Γ, then x does not occur free in the evaluation of e. - -*Proof*: By induction on the typing derivation. - -The key cases: - *Var*: A variable x can only be typed in a context -where it has quantity 1, not 0. - *App*: If the function type has π = 0, -then the argument is scaled by 0, meaning it contributes no usage. - -*Let*: If x is bound with quantity 0, the body cannot use x -computationally. - -∎ - -==== 5.2 Linearity - -*Definition 5.2 (Usage Count)*: Define use(e, x) as the number of times -x is evaluated during the reduction of e. - -*Theorem 5.3 (Linearity Soundness)*: If `+0Γ, 1x:τ ⊢ e : σ+` and e -reduces to a value v, then use(e, x) = 1. - -*Proof*: By induction on the typing derivation and reduction sequence. - -Key insight: The context splitting rules ensure that for each -constructor, the uses of x are properly distributed and sum to exactly -1. - -∎ - -==== 5.3 Affine Weakening - -*Theorem 5.4 (Affine Weakening)*: If `+Γ ⊢ e : τ+` and x has quantity 1 -in Γ, then x is used at most once. - -This follows from linearity soundness, noting that AffineScript allows -dropping linear values (unlike true linear types). - -*Note*: AffineScript is affine by default, not linear. Values with -quantity 1 must be used _at most_ once, but may be explicitly dropped or -left unused. This is enforced by the borrow checker rather than the type -system. - -=== 6. Quantity Inference - -==== 6.1 Principal Quantities - -For many expressions, quantities can be inferred: - -*Algorithm*: Given an expression e and types for its free variables, -compute the minimal quantities needed. - -[source,ocaml] ----- -type usage = Zero | One | Many - -let infer_usage (e : expr) (x : var) : usage = - match e with - | Var y -> if x = y then One else Zero - | App (e1, e2) -> - combine (infer_usage e1 x) (infer_usage e2 x) - | Lam (y, body) -> - if x = y then Zero - else infer_usage body x - | Let (y, rhs, body) -> - let rhs_use = infer_usage rhs x in - let body_use = infer_usage body x in - if x = y then rhs_use - else combine rhs_use body_use - | ... - -let combine u1 u2 = - match (u1, u2) with - | (Zero, u) | (u, Zero) -> u - | (One, One) -> Many - | _ -> Many ----- - -==== 6.2 Quantity Constraints - -During type checking, we generate quantity constraints and solve them: - -.... -π₁ + π₂ ≤ π₃ -π₁ × π₂ = π₃ -π ≤ ω -.... - -These constraints are solved by substitution and case analysis. - -=== 7. Interaction with Other Features - -==== 7.1 Quantities and Effects - -Effect handlers interact with quantities: - -.... - handle Γ ⊢ e : τ ! ε with h -.... - -The handler h may be invoked multiple times (for multi-shot -continuations), which affects linearity: - -*Resume-Once* - -.... - Γ ⊢ handler { op(x, k) → e } -.... - -If k is used with quantity 1, the continuation is one-shot. If k is used -with quantity ω, the continuation is multi-shot. - -`+[IMPL-DEP: effect-checker]+` Effect-quantity interaction requires -effect implementation. - -==== 7.2 Quantities and Ownership - -Quantities work with the ownership system: - -* `+0 (own τ)+`: Impossible (must use owned value) -* `+1 (own τ)+`: Standard linear ownership -* `+ω (own τ)+`: Requires Copy trait -* `+ω (ref τ)+`: Multiple immutable borrows allowed -* `+1 (mut τ)+`: Exactly one mutable borrow - -==== 7.3 Quantities and Dependent Types - -In dependent types, quantities distinguish: - -* *Type dependencies* (quantity 0): Used in types, erased at runtime -* *Value dependencies* (quantity 1 or ω): Exist at runtime - -.... --- The length n is used at quantity 0 (erased) -fn replicate[n: Nat, T](0 n: Nat, x: T) -> Vec[n, T] -.... - -=== 8. Examples - -==== 8.1 File Handle (Linear) - -[source,affinescript] ----- -type File = own FileHandle - -fn open(path: String) -> File / IO - -fn read(1 f: File) -> (String, File) / IO - -fn close(1 f: File) -> () / IO - -fn process_file(path: String) -> String / IO { - let f = open(path) -- f has quantity 1 - let (contents, f) = read(f) -- f consumed, new f bound - close(f) -- f consumed - contents -} ----- - -==== 8.2 Erased Proofs - -[source,affinescript] ----- -fn safe_index[n: Nat, T]( - vec: Vec[n, T], - i: Nat, - 0 pf: i < n -- proof is erased -) -> T { - vec.unsafe_get(i) -- pf not used at runtime -} ----- - -==== 8.3 Quantity Polymorphism - -[source,affinescript] ----- -fn pair[π: Quantity, A, B]( - π a: A, - π b: B -) -> (A, B) { - (a, b) -} - --- Can be instantiated at any quantity -let p1 = pair[1](file1, file2) -- linear pair -let p2 = pair[ω](x, y) -- unrestricted pair ----- - -=== 9. Metatheoretic Properties - -==== 9.1 Quantity Substitution Lemma - -*Lemma 9.1*: If `+Γ ⊢ e : τ+` and we substitute a more specific quantity -ρ ≤ π for π throughout, the derivation remains valid. - -==== 9.2 Quantity Coherence - -*Theorem 9.2*: If an expression type-checks at multiple quantities, the -results are coherent: - If `+Γ ⊢ e : τ+` and `+Γ' ⊢ e : τ+` where Γ’ has -larger quantities, then the semantics agree on shared resources. - -==== 9.3 Decidability - -*Theorem 9.3*: Quantity checking is decidable. - -*Proof*: The quantity semiring is finite (\{0, 1, ω}), and all -operations are computable. Quantity inference generates a finite -constraint system solvable by enumeration. ∎ - -=== 10. Implementation - -==== 10.1 AST Representation - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type quantity = - | QZero (* 0 - erased *) - | QOne (* 1 - linear *) - | QOmega (* ω - unrestricted *) ----- - -==== 10.2 Type Checker Integration - -`+[IMPL-DEP: type-checker]+` - -[source,ocaml] ----- -(* Quantity context *) -type qctx = (ident * quantity * typ) list - -(* Scale a context by a quantity *) -let scale (q : quantity) (ctx : qctx) : qctx = - List.map (fun (x, q', t) -> (x, mult q q', t)) ctx - -(* Add two contexts *) -let add (ctx1 : qctx) (ctx2 : qctx) : qctx = - List.map2 (fun (x, q1, t) (_, q2, _) -> (x, plus q1 q2, t)) ctx1 ctx2 - -(* Check usage matches declared quantity *) -let check_quantity (expected : quantity) (actual : usage) : bool = - match (expected, actual) with - | (QZero, Zero) -> true - | (QOne, One) -> true - | (QOmega, _) -> true - | _ -> false ----- - -=== 11. Related Work - -[arabic] -. *Quantitative Type Theory*: Atkey (2018) - Foundation for QTT -. *I Got Plenty o’ Nuttin’*: McBride (2016) - Quantities in dependent -types -. *Linear Haskell*: Bernardy et al. (2018) - Linearity in Haskell -. *Granule*: Orchard et al. (2019) - Graded modal types -. *Linear Types in Rust*: Weiss et al. (2019) - Practical affine types - -=== 12. References - -[arabic] -. Atkey, R. (2018). Syntax and Semantics of Quantitative Type Theory. -_LICS_. -. McBride, C. (2016). I Got Plenty o’ Nuttin’. _A List of Successes That -Can Change the World_. -. Bernardy, J.-P., et al. (2018). Linear Haskell: Practical Linearity in -a Higher-Order Polymorphic Language. _POPL_. -. Walker, D. (2005). Substructural Type Systems. _Advanced Topics in -Types and Programming Languages_. -. Wadler, P. (1990). Linear Types Can Change the World! _IFIP TC_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (quantity type), type -checker implementation - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/Quantities.v+` (stub) diff --git a/affinescript/docs/academic/proofs/row-polymorphism.adoc b/affinescript/docs/academic/proofs/row-polymorphism.adoc deleted file mode 100644 index deceef8..0000000 --- a/affinescript/docs/academic/proofs/row-polymorphism.adoc +++ /dev/null @@ -1,637 +0,0 @@ -== Row Polymorphism: Complete Formalization - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: type-checker]+` - -=== Abstract - -This document provides a complete formalization of row polymorphism in -AffineScript, including the syntax, typing rules, unification algorithm, -and soundness proofs. Row polymorphism enables extensible records and -variants with full type inference, following the tradition of Rémy -(1989) and Wand (1991), extended with AffineScript’s effects, -quantities, and ownership. - -=== 1. Introduction - -Row polymorphism allows polymorphism over record and variant "`shapes`" -without requiring explicit subtyping. A function can operate on any -record containing at least certain fields, regardless of what other -fields exist: - -[source,affinescript] ----- -fn get_name[ρ](r: {name: String, ..ρ}) -> String { - r.name -} ----- - -This document formalizes: 1. Row types and their kinding 2. Row -unification algorithm 3. Type soundness with rows 4. Coherence of row -operations 5. Principal types with row polymorphism - -=== 2. Syntax of Rows - -==== 2.1 Row Types - -.... -ρ ::= - | ∅ -- Empty row - | (l : τ | ρ) -- Row extension - | α -- Row variable - -Record types: -{ρ} -- Record with row ρ -{l₁: τ₁, ..., lₙ: τₙ} -- Sugar for {(l₁:τ₁|...(lₙ:τₙ|∅)...)} -{l₁: τ₁, ..., lₙ: τₙ | α} -- Open record (extensible) - -Variant types: -⟨ρ⟩ -- Variant with row ρ -⟨l₁: τ₁ | ... | lₙ: τₙ⟩ -- Closed variant -⟨l₁: τ₁ | ... | lₙ: τₙ | α⟩ -- Open variant (extensible) -.... - -==== 2.2 Labels - -Labels l are drawn from a countably infinite set L. We assume a total -ordering on labels for canonical row representations. - -==== 2.3 Row Equivalence - -Rows are equivalent up to permutation (but not duplication): - -*Definition 2.1 (Row Equivalence)*: ρ₁ ≡ ρ₂ iff they contain the same -labels with the same types, differing only in order. - -Formally, define the flattening function: - -.... -flatten(∅) = {} -flatten((l : τ | ρ)) = {l ↦ τ} ∪ flatten(ρ) (if l ∉ dom(flatten(ρ))) -flatten(α) = {α} (row variable) -.... - -Then ρ₁ ≡ ρ₂ iff flatten(ρ₁) = flatten(ρ₂). - -*Axioms*: - -.... -(l₁ : τ₁ | (l₂ : τ₂ | ρ)) ≡ (l₂ : τ₂ | (l₁ : τ₁ | ρ)) (l₁ ≠ l₂) -.... - -==== 2.4 Row Restriction - -*Definition 2.2 (Row Restriction)*: ρ  l removes label l from row ρ: - -.... -∅ \ l = ∅ -(l : τ | ρ) \ l = ρ -(l' : τ | ρ) \ l = (l' : τ | ρ \ l) (l ≠ l') -α \ l = α (defer to unification) -.... - -==== 2.5 Row Concatenation - -*Definition 2.3 (Row Concatenation)*: ρ₁ ⊕ ρ₂ combines rows (disjoint -labels required): - -.... -∅ ⊕ ρ = ρ -(l : τ | ρ₁) ⊕ ρ₂ = (l : τ | ρ₁ ⊕ ρ₂) (l ∉ labels(ρ₂)) -.... - -==== 2.6 Presence/Absence Types (Rémy’s Approach) - -For complete type inference, we extend rows with presence/absence -annotations: - -.... -π ::= Pre(τ) | Abs - -ρ̂ ::= ∅ | (l : π | ρ̂) | α - -Pre(τ) -- Label l is present with type τ -Abs -- Label l is absent -.... - -This allows unifying records with different field sets by marking absent -fields. - -=== 3. Kinding - -==== 3.1 Kind Structure - -.... -κ ::= Type | Row | ... -.... - -==== 3.2 Kinding Rules - -*K-Empty* - -.... - ──────────── - Γ ⊢ ∅ : Row -.... - -*K-Extend* - -.... - Γ ⊢ τ : Type Γ ⊢ ρ : Row l ∉ labels(ρ) - ─────────────────────────────────────────────── - Γ ⊢ (l : τ | ρ) : Row -.... - -*K-RowVar* - -.... - α : Row ∈ Γ - ─────────────── - Γ ⊢ α : Row -.... - -*K-Record* - -.... - Γ ⊢ ρ : Row - ───────────────── - Γ ⊢ {ρ} : Type -.... - -*K-Variant* - -.... - Γ ⊢ ρ : Row - ───────────────── - Γ ⊢ ⟨ρ⟩ : Type -.... - -==== 3.3 Lack Constraints - -To express "`row ρ does not contain label l`", we use lack constraints: - -.... -Γ ⊢ ρ lacks l -.... - -*Lacks-Empty* - -.... - ───────────────── - Γ ⊢ ∅ lacks l -.... - -*Lacks-Extend* - -.... - Γ ⊢ ρ lacks l l ≠ l' - ──────────────────────── - Γ ⊢ (l' : τ | ρ) lacks l -.... - -*Lacks-Var* (constraint on variable) - -.... - α lacks l ∈ Γ - ───────────────── - Γ ⊢ α lacks l -.... - -=== 4. Typing Rules - -==== 4.1 Record Introduction - -*Rec-Empty* - -.... - ──────────────────── - Γ ⊢ {} ⇒ {∅} -.... - -*Rec-Extend* - -.... - Γ ⊢ e : τ ! ε Γ ⊢ r : {ρ} ! ε' Γ ⊢ ρ lacks l - ────────────────────────────────────────────────────── - Γ ⊢ {l = e, ..r} ⇒ {(l : τ | ρ)} ! (ε | ε') -.... - -*Rec-Literal* - -.... - ∀i. Γ ⊢ eᵢ ⇒ τᵢ ! εᵢ labels distinct - ────────────────────────────────────────────────────────── - Γ ⊢ {l₁ = e₁, ..., lₙ = eₙ} ⇒ {l₁: τ₁, ..., lₙ: τₙ} ! (ε₁ | ... | εₙ) -.... - -==== 4.2 Record Elimination - -*Rec-Select* - -.... - Γ ⊢ e ⇒ {(l : τ | ρ)} ! ε - ────────────────────────── - Γ ⊢ e.l ⇒ τ ! ε -.... - -*Rec-Restrict* - -.... - Γ ⊢ e ⇒ {(l : τ | ρ)} ! ε - ──────────────────────────── - Γ ⊢ e \ l ⇒ {ρ} ! ε -.... - -*Rec-Update* - -.... - Γ ⊢ e₁ ⇒ {(l : τ | ρ)} ! ε₁ Γ ⊢ e₂ ⇒ σ ! ε₂ - ───────────────────────────────────────────────── - Γ ⊢ e₁ with {l = e₂} ⇒ {(l : σ | ρ)} ! (ε₁ | ε₂) -.... - -==== 4.3 Variant Introduction - -*Var-Inject* - -.... - Γ ⊢ e ⇒ τ ! ε α fresh - ────────────────────────────────── - Γ ⊢ l(e) ⇒ ⟨l : τ | α⟩ ! ε -.... - -==== 4.4 Variant Elimination - -*Var-Case* - -.... - Γ ⊢ e ⇒ ⟨l₁: τ₁ | ... | lₙ: τₙ⟩ ! ε - ∀i. Γ, xᵢ: τᵢ ⊢ eᵢ ⇐ σ ! ε' - ────────────────────────────────────────────────────────────── - Γ ⊢ case e { l₁(x₁) → e₁ | ... | lₙ(xₙ) → eₙ } ⇒ σ ! (ε | ε') -.... - -*Var-Case-Open* (with default) - -.... - Γ ⊢ e ⇒ ⟨l₁: τ₁ | ... | lₙ: τₙ | α⟩ ! ε - ∀i. Γ, xᵢ: τᵢ ⊢ eᵢ ⇐ σ ! ε' - Γ, y: ⟨α⟩ ⊢ e_default ⇐ σ ! ε' - ────────────────────────────────────────────────────────────────────── - Γ ⊢ case e { l₁(x₁) → e₁ | ... | lₙ(xₙ) → eₙ | y → e_default } ⇒ σ ! (ε | ε') -.... - -==== 4.5 Row Polymorphism - -*Row-Gen* - -.... - Γ, α:Row ⊢ e ⇒ τ ! ε α ∉ FV(Γ) - ───────────────────────────────────── - Γ ⊢ e ⇒ ∀α:Row. τ ! ε -.... - -*Row-Inst* - -.... - Γ ⊢ e ⇒ ∀α:Row. τ ! ε Γ ⊢ ρ : Row - ─────────────────────────────────────── - Γ ⊢ e ⇒ τ[ρ/α] ! ε -.... - -=== 5. Unification - -==== 5.1 Unification Problem - -Given two types τ₁ and τ₂, find a substitution θ such that θ(τ₁) = -θ(τ₂). - -==== 5.2 Row Unification Algorithm - -Row unification extends standard unification with special handling for -rows: - -[source,ocaml] ----- -type unify_result = - | Success of substitution - | Failure of string - -let rec unify_row (ρ₁ : row) (ρ₂ : row) : unify_result = - match (ρ₁, ρ₂) with - (* Both empty *) - | (Empty, Empty) -> - Success [] - - (* Variable cases *) - | (RowVar α, ρ) | (ρ, RowVar α) -> - if occurs α ρ then - Failure "occurs check" - else - Success [α ↦ ρ] - - (* Both extensions with same head label *) - | (Extend (l, τ₁, ρ₁'), Extend (l', τ₂, ρ₂')) when l = l' -> - let* θ₁ = unify τ₁ τ₂ in - let* θ₂ = unify_row (apply θ₁ ρ₁') (apply θ₁ ρ₂') in - Success (compose θ₂ θ₁) - - (* Extensions with different head labels - row rewriting *) - | (Extend (l₁, τ₁, ρ₁'), Extend (l₂, τ₂, ρ₂')) when l₁ ≠ l₂ -> - (* Rewrite: (l₁:τ₁|ρ₁') = (l₂:τ₂|ρ₂') - becomes: ρ₁' = (l₂:τ₂|ρ₃) and ρ₂' = (l₁:τ₁|ρ₃) for fresh ρ₃ *) - let ρ₃ = fresh_row_var () in - let* θ₁ = unify_row ρ₁' (Extend (l₂, τ₂, ρ₃)) in - let* θ₂ = unify_row (apply θ₁ ρ₂') (apply θ₁ (Extend (l₁, τ₁, ρ₃))) in - Success (compose θ₂ θ₁) - - (* Empty vs extension - failure *) - | (Empty, Extend _) | (Extend _, Empty) -> - Failure "row mismatch" ----- - -==== 5.3 Occurs Check for Rows - -[source,ocaml] ----- -let rec row_occurs (α : row_var) (ρ : row) : bool = - match ρ with - | Empty -> false - | RowVar β -> α = β - | Extend (_, τ, ρ') -> type_occurs α τ || row_occurs α ρ' ----- - -==== 5.4 Correctness of Row Unification - -*Theorem 5.1 (Soundness of Row Unification)*: If -`+unify_row(ρ₁, ρ₂) = Success θ+`, then `+θ(ρ₁) ≡ θ(ρ₂)+`. - -*Proof*: By induction on the structure of the unification algorithm. - -_Case RowVar_: θ = [α ↦ ρ], so θ(α) = ρ = θ(ρ). ✓ - -_Case Same-Head_: By IH, θ₁(τ₁) = θ₁(τ₂) and θ₂(θ₁(ρ₁’)) = θ₂(θ₁(ρ₂’)). -Combined substitution preserves equality. ✓ - -_Case Different-Head_: By IH and the rewriting equations, both sides -become equivalent. ✓ - -∎ - -*Theorem 5.2 (Completeness of Row Unification)*: If there exists θ such -that θ(ρ₁) ≡ θ(ρ₂), then `+unify_row(ρ₁, ρ₂) = Success θ'+` for some θ’ -more general than θ. - -*Proof*: By induction, showing the algorithm finds the most general -unifier. ∎ - -*Theorem 5.3 (Termination of Row Unification)*: Row unification -terminates on all inputs. - -*Proof*: Define a measure M(ρ) = (size(ρ), vars(ρ)). Each recursive call -strictly decreases this measure (lexicographically). ∎ - -=== 6. Type Inference with Rows - -==== 6.1 Constraint Generation - -During type inference, generate constraints including row constraints: - -.... -C ::= τ = σ | ρ = ρ' | ρ lacks l | ... -.... - -==== 6.2 Principal Types - -*Theorem 6.1 (Principal Types)*: For any expression e and context Γ, if -e is typeable then there exists a principal type scheme σ such that all -other types are instances of σ. - -*Proof*: The unification algorithm computes most general unifiers, and -generalization produces principal type schemes. ∎ - -==== 6.3 Let-Polymorphism with Rows - -.... - Γ ⊢ e₁ ⇒ τ₁ ! ε σ = gen(Γ, τ₁) Γ, x:σ ⊢ e₂ ⇒ τ₂ ! ε' - ───────────────────────────────────────────────────────────── - Γ ⊢ let x = e₁ in e₂ ⇒ τ₂ ! (ε | ε') -.... - -Where `+gen(Γ, τ) = ∀ᾱ:κ̄. τ+` for ᾱ = FTV(τ)  FTV(Γ). - -=== 7. Soundness - -==== 7.1 Progress with Rows - -*Theorem 7.1 (Progress)*: If `+· ⊢ e : τ+` where τ involves row types, -then either e is a value or e can step. - -*Proof*: Extended from base progress theorem. The key cases: - -_Case Record-Select_: If `+· ⊢ e.l : τ+` then `+· ⊢ e : {(l : τ | ρ)}+`. -By IH, e is a value or steps. If e is a value, by canonical forms it is -a record `+{l₁=v₁,...,lₙ=vₙ}+` containing field l. Therefore `+e.l ⟶ v+` -where v is the value at label l. ✓ - -_Case Variant-Case_: Similar, using exhaustiveness from the type. ✓ - -∎ - -==== 7.2 Preservation with Rows - -*Theorem 7.2 (Preservation)*: If `+Γ ⊢ e : τ+` and `+e ⟶ e'+`, then -`+Γ ⊢ e' : τ+`. - -*Proof*: By induction on the reduction. Key cases: - -_Case Record-Select_: `+{l₁=v₁,...,l=v,...,lₙ=vₙ}.l ⟶ v+` - -From typing: `+Γ ⊢ {l₁=v₁,...,l=v,...,lₙ=vₙ} : {(l : τ | ρ)}+` By -inversion: `+Γ ⊢ v : τ+` ✓ - -_Case Record-Update_: `+{l=v₁|r} with {l=v₂} ⟶ {l=v₂|r}+` - -From typing: original type is `+{(l : τ₁ | ρ)}+`, new value has type τ₂ -Result type is `+{(l : τ₂ | ρ)}+` as required. ✓ - -∎ - -==== 7.3 Type Safety - -*Corollary 7.3 (Type Safety with Rows)*: Well-typed programs with row -polymorphism do not get stuck. - -=== 8. Coherence - -==== 8.1 Record Coherence - -*Theorem 8.1 (Record Representation Independence)*: If `+Γ ⊢ e : {ρ₁}+` -and `+ρ₁ ≡ ρ₂+`, then `+Γ ⊢ e : {ρ₂}+`. - -Operations on records are independent of field order. - -==== 8.2 Polymorphic Coherence - -*Theorem 8.2 (Coherence of Instantiation)*: For `+f : ∀α:Row. {α} → τ+`, -all instantiations of α produce semantically equivalent behavior. - -*Proof*: Row polymorphism is parametric; the function cannot inspect the -specific row. ∎ - -=== 9. Row Polymorphism and Effects - -==== 9.1 Effect Rows - -Effects use the same row machinery: - -.... -ε ::= ∅ | (E | ε) | ρ_eff -.... - -Effect rows and record/variant rows are disjoint kinds: - -.... -α : Row_Record -β : Row_Variant -ρ : Row_Effect -.... - -==== 9.2 Unified Row Kinding - -.... -κ_row ::= Row(sort) -sort ::= Record | Variant | Effect -.... - -=== 10. Row Polymorphism and Ownership - -==== 10.1 Owned Records - -.... -own {l₁: τ₁, ..., lₙ: τₙ | ρ} -.... - -Ownership applies to the whole record; individual fields inherit -ownership. - -==== 10.2 Field Borrowing - -[source,affinescript] ----- -fn get_field['a, ρ](r: ref['a] {name: String, ..ρ}) -> ref['a] String { - &r.name -} ----- - -The borrow of a field extends the borrow of the record. - -=== 11. Implementation - -==== 11.1 AST Representation - -From `+lib/ast.ml+`: - -[source,ocaml] ----- -type row_field = { - rf_name : ident; - rf_ty : type_expr; -} - -type type_expr = - | ... - | TyRecord of row_field list * ident option (* fields, row var *) ----- - -==== 11.2 Row Unification Module - -`+[IMPL-DEP: type-checker]+` - -[source,ocaml] ----- -module RowUnify : sig - type row = - | Empty - | Extend of string * typ * row - | Var of int - - val unify : row -> row -> substitution result - val rewrite : string -> row -> row * typ (* extract field *) - val lacks : row -> string -> bool -end ----- - -=== 12. Examples - -==== 12.1 Extensible Records - -[source,affinescript] ----- -fn full_name[ρ](person: {first: String, last: String, ..ρ}) -> String { - person.first ++ " " ++ person.last -} - -let employee = {first: "Alice", last: "Smith", id: 123} -let name = full_name(employee) -- works with extra 'id' field ----- - -==== 12.2 Record Update - -[source,affinescript] ----- -fn with_id[ρ](r: {..ρ}, id: Int) -> {id: Int, ..ρ} { - {id = id, ..r} -} ----- - -==== 12.3 Variant Extension - -[source,affinescript] ----- -type BaseError = ⟨NotFound: String | InvalidInput: String⟩ -type ExtError[ρ] = ⟨NotFound: String | InvalidInput: String | ..ρ⟩ - -fn handle_base[ρ, A]( - e: ExtError[ρ], - on_other: ⟨..ρ⟩ → A -) -> A { - case e { - NotFound(msg) → handle_not_found(msg), - InvalidInput(msg) → handle_invalid(msg), - other → on_other(other) - } -} ----- - -=== 13. Related Work - -[arabic] -. *Rémy (1989)*: Original formulation of row polymorphism with -presence/absence -. *Wand (1991)*: Row polymorphism for type inference in ML -. *Leijen (2005)*: Extensible records with scoped labels -. *PureScript*: Practical row polymorphism in production -. *Links*: Row polymorphism with effect types -. *Koka*: Row-polymorphic effects - -=== 14. References - -[arabic] -. Rémy, D. (1989). Type Checking Records and Variants in a Natural -Extension of ML. _POPL_. -. Wand, M. (1991). Type Inference for Record Concatenation and Multiple -Inheritance. _Information and Computation_. -. Leijen, D. (2005). Extensible Records with Scoped Labels. _Trends in -Functional Programming_. -. Pottier, F. (2003). A Constraint-Based Presentation and Generalization -of Rows. _LICS_. -. Morris, J. G., & McKinna, J. (2019). Abstracting Extensible Data -Types. _POPL_. - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+` (row types), type -checker implementation - Implementation verification: Pending - -Mechanized proof: See `+mechanized/coq/Rows.v+` (stub) diff --git a/affinescript/docs/academic/proofs/type-soundness.adoc b/affinescript/docs/academic/proofs/type-soundness.adoc deleted file mode 100644 index fb07c38..0000000 --- a/affinescript/docs/academic/proofs/type-soundness.adoc +++ /dev/null @@ -1,676 +0,0 @@ -== Type System Soundness - -*Document Version*: 1.0 *Last Updated*: 2024 *Status*: Theoretical -framework complete; implementation verification pending -`+[IMPL-DEP: type-checker]+` - -=== Abstract - -This document presents the formal metatheory of the AffineScript type -system, establishing the fundamental soundness properties: type safety -via progress and preservation (subject reduction). We prove that -well-typed AffineScript programs do not get "`stuck`" during evaluation -and that types are preserved under reduction. - -=== 1. Introduction - -AffineScript’s type system combines several advanced features: - -Bidirectional type checking with principal types - Quantitative type -theory (QTT) for linearity - Algebraic effects with row-polymorphic -effect types - Dependent types with refinements - Ownership and -borrowing - -This document focuses on the core type system soundness, with extensions -for effects, quantities, and ownership treated in companion documents. - -=== 2. Syntax - -==== 2.1 Types - -.... -τ, σ ::= - | α -- Type variable - | τ → σ -- Function type - | τ →{ε} σ -- Effectful function type - | ∀α:κ. τ -- Universal quantification - | ∃α:κ. τ -- Existential quantification - | (τ₁, ..., τₙ) -- Tuple type - | {l₁: τ₁, ..., lₙ: τₙ | ρ} -- Record type with row - | [l₁: τ₁ | ... | lₙ: τₙ | ρ] -- Variant type with row - | τ[e] -- Indexed type (dependent) - | {x: τ | φ} -- Refinement type - | own τ -- Owned type - | ref τ -- Immutable reference - | mut τ -- Mutable reference -.... - -==== 2.2 Kinds - -.... -κ ::= - | Type -- Kind of types - | Nat -- Kind of natural numbers - | Row -- Kind of row types - | Effect -- Kind of effects - | κ₁ → κ₂ -- Higher-order kinds -.... - -==== 2.3 Expressions - -.... -e ::= - | x -- Variable - | λx:τ. e -- Lambda abstraction - | e₁ e₂ -- Application - | Λα:κ. e -- Type abstraction - | e [τ] -- Type application - | let x = e₁ in e₂ -- Let binding - | (e₁, ..., eₙ) -- Tuple - | e.i -- Tuple projection - | {l₁ = e₁, ..., lₙ = eₙ} -- Record - | e.l -- Record projection - | e with {l = e'} -- Record update - | case e {p₁ → e₁ | ... | pₙ → eₙ} -- Pattern match - | handle e with h -- Effect handler - | perform op(e) -- Effect operation - | v -- Values -.... - -==== 2.4 Values - -.... -v ::= - | λx:τ. e -- Function value - | Λα:κ. v -- Type abstraction value - | (v₁, ..., vₙ) -- Tuple value - | {l₁ = v₁, ..., lₙ = vₙ} -- Record value - | C v -- Constructor application - | ℓ -- Location (for references) -.... - -=== 3. Static Semantics - -==== 3.1 Contexts - -.... -Γ ::= · | Γ, x:τ | Γ, α:κ -.... - -Well-formed context judgment: `+⊢ Γ+` - -==== 3.2 Kinding Judgment - -.... -Γ ⊢ τ : κ -.... - -*K-Var* - -.... - α:κ ∈ Γ - ────────── - Γ ⊢ α : κ -.... - -*K-Arrow* - -.... - Γ ⊢ τ₁ : Type Γ ⊢ τ₂ : Type - ─────────────────────────────── - Γ ⊢ τ₁ → τ₂ : Type -.... - -*K-EffArrow* - -.... - Γ ⊢ τ₁ : Type Γ ⊢ τ₂ : Type Γ ⊢ ε : Effect - ───────────────────────────────────────────────── - Γ ⊢ τ₁ →{ε} τ₂ : Type -.... - -*K-Forall* - -.... - Γ, α:κ ⊢ τ : Type - ───────────────────── - Γ ⊢ ∀α:κ. τ : Type -.... - -*K-Record* - -.... - Γ ⊢ ρ : Row ∀i. Γ ⊢ τᵢ : Type - ───────────────────────────────────────── - Γ ⊢ {l₁: τ₁, ..., lₙ: τₙ | ρ} : Type -.... - -*K-Indexed* - -.... - Γ ⊢ τ : Nat → Type Γ ⊢ e : Nat - ───────────────────────────────── - Γ ⊢ τ[e] : Type -.... - -*K-Refinement* - -.... - Γ ⊢ τ : Type Γ, x:τ ⊢ φ : Prop - ───────────────────────────────── - Γ ⊢ {x: τ | φ} : Type -.... - -==== 3.3 Bidirectional Typing - -We use bidirectional typing with two judgments: - -* *Synthesis*: `+Γ ⊢ e ⇒ τ+` (infer type τ from expression e) -* *Checking*: `+Γ ⊢ e ⇐ τ+` (check expression e against type τ) - -*Subsumption* - -.... - Γ ⊢ e ⇒ τ Γ ⊢ τ <: σ - ──────────────────────── - Γ ⊢ e ⇐ σ -.... - -*Var* - -.... - x:τ ∈ Γ - ─────────── - Γ ⊢ x ⇒ τ -.... - -*Abs-Check* - -.... - Γ, x:τ₁ ⊢ e ⇐ τ₂ - ─────────────────────────── - Γ ⊢ λx. e ⇐ τ₁ → τ₂ -.... - -*Abs-Synth* (with annotation) - -.... - Γ, x:τ₁ ⊢ e ⇒ τ₂ - ─────────────────────────── - Γ ⊢ λx:τ₁. e ⇒ τ₁ → τ₂ -.... - -*App* - -.... - Γ ⊢ e₁ ⇒ τ₁ → τ₂ Γ ⊢ e₂ ⇐ τ₁ - ────────────────────────────────── - Γ ⊢ e₁ e₂ ⇒ τ₂ -.... - -*TyAbs* - -.... - Γ, α:κ ⊢ e ⇒ τ - ───────────────────── - Γ ⊢ Λα:κ. e ⇒ ∀α:κ. τ -.... - -*TyApp* - -.... - Γ ⊢ e ⇒ ∀α:κ. τ Γ ⊢ σ : κ - ───────────────────────────── - Γ ⊢ e [σ] ⇒ τ[σ/α] -.... - -*Let* - -.... - Γ ⊢ e₁ ⇒ τ₁ Γ, x:τ₁ ⊢ e₂ ⇒ τ₂ - ────────────────────────────────── - Γ ⊢ let x = e₁ in e₂ ⇒ τ₂ -.... - -*Record-Intro* - -.... - ∀i. Γ ⊢ eᵢ ⇒ τᵢ - ───────────────────────────────────────────── - Γ ⊢ {l₁ = e₁, ..., lₙ = eₙ} ⇒ {l₁: τ₁, ..., lₙ: τₙ} -.... - -*Record-Elim* - -.... - Γ ⊢ e ⇒ {l: τ | ρ} - ─────────────────── - Γ ⊢ e.l ⇒ τ -.... - -*Case* - -.... - Γ ⊢ e ⇒ τ ∀i. Γ ⊢ pᵢ : τ ⊣ Γᵢ ∀i. Γ, Γᵢ ⊢ eᵢ ⇐ σ - ──────────────────────────────────────────────────────── - Γ ⊢ case e {p₁ → e₁ | ... | pₙ → eₙ} ⇐ σ -.... - -==== 3.4 Pattern Typing - -.... -Γ ⊢ p : τ ⊣ Γ' -.... - -*P-Var* - -.... - ──────────────────── - Γ ⊢ x : τ ⊣ (x:τ) -.... - -*P-Wild* - -.... - ──────────────────── - Γ ⊢ _ : τ ⊣ · -.... - -*P-Constructor* - -.... - C : τ₁ → ... → τₙ → T ∀i. Γ ⊢ pᵢ : τᵢ ⊣ Γᵢ - ────────────────────────────────────────────── - Γ ⊢ C(p₁, ..., pₙ) : T ⊣ Γ₁, ..., Γₙ -.... - -*P-Record* - -.... - ∀i. Γ ⊢ pᵢ : τᵢ ⊣ Γᵢ - ────────────────────────────────────────────────────────── - Γ ⊢ {l₁ = p₁, ..., lₙ = pₙ} : {l₁: τ₁, ..., lₙ: τₙ | ρ} ⊣ Γ₁, ..., Γₙ -.... - -=== 4. Dynamic Semantics - -==== 4.1 Evaluation Contexts - -.... -E ::= - | □ - | E e - | v E - | E [τ] - | let x = E in e - | (v₁, ..., E, ..., eₙ) - | {l₁ = v₁, ..., l = E, ..., lₙ = eₙ} - | E.l - | E.i - | case E {p₁ → e₁ | ... | pₙ → eₙ} - | handle E with h -.... - -==== 4.2 Small-Step Reduction - -.... -e ⟶ e' -.... - -*β-Reduction* - -.... - ─────────────────────────── - (λx:τ. e) v ⟶ e[v/x] -.... - -*Type Application* - -.... - ─────────────────────────── - (Λα:κ. e) [τ] ⟶ e[τ/α] -.... - -*Let* - -.... - ─────────────────────────── - let x = v in e ⟶ e[v/x] -.... - -*Tuple Projection* - -.... - ─────────────────────────── - (v₁, ..., vₙ).i ⟶ vᵢ -.... - -*Record Projection* - -.... - ──────────────────────────────────────── - {l₁ = v₁, ..., lₙ = vₙ}.lᵢ ⟶ vᵢ -.... - -*Record Update* - -.... - ────────────────────────────────────────────────────────────── - {l₁ = v₁, ..., l = v, ..., lₙ = vₙ} with {l = v'} ⟶ {l₁ = v₁, ..., l = v', ..., lₙ = vₙ} -.... - -*Case-Match* - -.... - match(p, v) = θ - ───────────────────────────────────────────── - case v {... | p → e | ...} ⟶ θ(e) -.... - -*Congruence* - -.... - e ⟶ e' - ────────────── - E[e] ⟶ E[e'] -.... - -==== 4.3 Pattern Matching - -The `+match(p, v) = θ+` judgment produces a substitution θ if pattern p -matches value v. - -*M-Var* - -.... - ───────────────── - match(x, v) = [v/x] -.... - -*M-Wild* - -.... - ───────────────── - match(_, v) = [] -.... - -*M-Constructor* - -.... - ∀i. match(pᵢ, vᵢ) = θᵢ - ────────────────────────────────────────── - match(C(p₁,...,pₙ), C(v₁,...,vₙ)) = θ₁∪...∪θₙ -.... - -=== 5. Type Safety - -==== 5.1 Progress - -*Theorem 5.1 (Progress)*: If `+· ⊢ e : τ+` then either: 1. e is a value, -or 2. there exists e’ such that `+e ⟶ e'+` - -*Proof*: By induction on the typing derivation. - -_Case Var_: Impossible, as the context is empty. - -_Case Abs_: `+e = λx:τ₁. e'+` is a value. ✓ - -_Case App_: We have `+· ⊢ e₁ e₂ : τ₂+` derived from `+· ⊢ e₁ : τ₁ → τ₂+` -and `+· ⊢ e₂ : τ₁+`. - -By IH on e₁: - If e₁ is a value, by canonical forms it must be -`+λx:τ₁. e'+` for some e’. By IH on e₂: - If e₂ is a value v₂, then -`+(λx:τ₁. e') v₂ ⟶ e'[v₂/x]+` by β-reduction. ✓ - If e₂ steps, then -`+e₁ e₂ ⟶ e₁ e₂'+` by congruence. ✓ - If e₁ steps, then -`+e₁ e₂ ⟶ e₁' e₂+` by congruence. ✓ - -_Case TyAbs_: `+e = Λα:κ. e'+` is a value. ✓ - -_Case TyApp_: Similar to App case. - -_Case Let_: We have `+· ⊢ let x = e₁ in e₂ : τ₂+`. - If e₁ is a value -v₁, then `+let x = v₁ in e₂ ⟶ e₂[v₁/x]+`. ✓ - If e₁ steps, then the -whole expression steps by congruence. ✓ - -_Case Record-Intro_: If all components are values, the record is a -value. Otherwise, the leftmost non-value steps, and we apply congruence. -✓ - -_Case Record-Elim_: By IH, e is a value or steps. If value, by canonical -forms it’s a record, and we project. ✓ - -_Case Case_: By IH, the scrutinee is a value or steps. If value, by -exhaustiveness (ensured by type checking), some pattern matches. ✓ - -∎ - -==== 5.2 Preservation (Subject Reduction) - -*Theorem 5.2 (Preservation)*: If `+Γ ⊢ e : τ+` and `+e ⟶ e'+`, then -`+Γ ⊢ e' : τ+`. - -*Proof*: By induction on the derivation of `+e ⟶ e'+`. - -_Case β-Reduction_: `+(λx:τ₁. e) v ⟶ e[v/x]+` - -We have: - `+Γ ⊢ (λx:τ₁. e) v : τ₂+` derived from - -`+Γ ⊢ λx:τ₁. e : τ₁ → τ₂+` and `+Γ ⊢ v : τ₁+` - -From the lambda typing: `+Γ, x:τ₁ ⊢ e : τ₂+` - -By the Substitution Lemma (Lemma 5.3): `+Γ ⊢ e[v/x] : τ₂+` ✓ - -_Case Type Application_: `+(Λα:κ. e) [τ] ⟶ e[τ/α]+` - -We have `+Γ ⊢ (Λα:κ. e) [τ] : σ[τ/α]+` derived from: - -`+Γ ⊢ Λα:κ. e : ∀α:κ. σ+` which gives `+Γ, α:κ ⊢ e : σ+` - `+Γ ⊢ τ : κ+` - -By the Type Substitution Lemma (Lemma 5.4): `+Γ ⊢ e[τ/α] : σ[τ/α]+` ✓ - -_Case Let_: `+let x = v in e ⟶ e[v/x]+` - -Similar to β-reduction, using the Substitution Lemma. - -_Case Congruence_: `+E[e] ⟶ E[e']+` where `+e ⟶ e'+` - -By IH, if `+Γ' ⊢ e : τ'+` then `+Γ' ⊢ e' : τ'+`. By the Replacement -Lemma (Lemma 5.5), the type of `+E[e']+` is preserved. ✓ - -∎ - -==== 5.3 Key Lemmas - -*Lemma 5.3 (Substitution)*: If `+Γ, x:τ ⊢ e : σ+` and `+Γ ⊢ v : τ+`, -then `+Γ ⊢ e[v/x] : σ+`. - -*Proof*: By induction on the typing derivation. ∎ - -*Lemma 5.4 (Type Substitution)*: If `+Γ, α:κ ⊢ e : τ+` and -`+Γ ⊢ σ : κ+`, then `+Γ ⊢ e[σ/α] : τ[σ/α]+`. - -*Proof*: By induction on the typing derivation. ∎ - -*Lemma 5.5 (Replacement/Compositionality)*: If `+Γ ⊢ E[e] : τ+` and -replacing e with e’ preserves the type of the hole, then -`+Γ ⊢ E[e'] : τ+`. - -*Proof*: By induction on the structure of E. ∎ - -*Lemma 5.6 (Canonical Forms)*: If `+· ⊢ v : τ+` where v is a value, -then: 1. If τ = τ₁ → τ₂, then v = λx:τ₁. e for some x, e 2. If τ = ∀α:κ. -σ, then v = Λα:κ. e for some e 3. If τ = (τ₁, …, τₙ), then v = (v₁, …, -vₙ) for some values vᵢ 4. If τ = \{l₁: τ₁, …, lₙ: τₙ}, then v = \{l₁ = -v₁, …, lₙ = vₙ} - -*Proof*: By inspection of typing rules and definition of values. ∎ - -=== 6. Type Soundness Corollary - -*Corollary 6.1 (Type Safety)*: Well-typed programs don’t get stuck. - -If `+· ⊢ e : τ+` and `+e ⟶* e'+` (where `+⟶*+` is the -reflexive-transitive closure of `+⟶+`), then either e’ is a value or -there exists e’’ such that `+e' ⟶ e''+`. - -*Proof*: By induction on the length of the reduction sequence, using -Progress and Preservation. ∎ - -=== 7. Extensions - -==== 7.1 Subtyping - -AffineScript includes structural subtyping for records and variants. - -*S-Record* (width subtyping) - -.... - ──────────────────────────────────────────────────── - Γ ⊢ {l₁: τ₁, ..., lₙ: τₙ, l: τ | ρ} <: {l₁: τ₁, ..., lₙ: τₙ | ρ'} -.... - -*S-Arrow* (contravariant in domain, covariant in codomain) - -.... - Γ ⊢ τ₁' <: τ₁ Γ ⊢ τ₂ <: τ₂' - ──────────────────────────────── - Γ ⊢ τ₁ → τ₂ <: τ₁' → τ₂' -.... - -With subtyping, we extend preservation: - -*Theorem 7.1 (Preservation with Subtyping)*: If `+Γ ⊢ e : τ+` and -`+e ⟶ e'+` and `+Γ ⊢ τ <: σ+`, then `+Γ ⊢ e' : τ'+` for some τ’ with -`+Γ ⊢ τ' <: σ+`. - -==== 7.2 Recursion - -For recursive functions, we extend with: - -*Fix* - -.... - Γ ⊢ e ⇐ τ → τ - ──────────────────── - Γ ⊢ fix e ⇒ τ -.... - -*Fix-Reduce* - -.... - ──────────────────────────── - fix (λx:τ. e) ⟶ e[fix (λx:τ. e)/x] -.... - -The addition of general recursion means termination is not guaranteed; -partiality is the default in AffineScript unless marked `+total+`. - -==== 7.3 References and State - -For mutable state, we need a store typing: - -*Ref-Alloc* - -.... - Γ | Σ ⊢ v : τ ℓ ∉ dom(Σ) - ────────────────────────────────── - Γ | Σ ⊢ ref v ⇒ ref τ | Σ, ℓ:τ -.... - -*Ref-Read* - -.... - Γ | Σ ⊢ e ⇒ ref τ - ────────────────────── - Γ | Σ ⊢ !e ⇒ τ -.... - -*Ref-Write* - -.... - Γ | Σ ⊢ e₁ ⇒ mut τ Γ | Σ ⊢ e₂ ⇐ τ - ───────────────────────────────────── - Γ | Σ ⊢ e₁ := e₂ ⇒ () -.... - -Preservation must be extended to include store typing preservation: - -*Theorem 7.2 (Preservation with Store)*: If `+Γ | Σ ⊢ e : τ+` and -`+(e, μ) ⟶ (e', μ')+` and `+Σ ⊢ μ+`, then there exists Σ’ ⊇ Σ such that -`+Γ | Σ' ⊢ e' : τ+` and `+Σ' ⊢ μ'+`. - -=== 8. Implementation Notes - -==== 8.1 Correspondence to AST - -The formal syntax maps to the AST defined in `+lib/ast.ml+`: - -[cols=",",options="header",] -|=== -|Formal |AST Constructor -|`+τ → σ+` |`+TyArrow(τ, σ, None)+` -|`+τ →{ε} σ+` |`+TyArrow(τ, σ, Some ε)+` -|`+∀α:κ. τ+` |Implicit in `+fun_decl.fd_ty_params+` -|`+{l: τ \| ρ}+` |`+TyRecord(fields, Some ρ)+` -|`+τ[e]+` |`+TyApp(τ, [TaNat e])+` -|`+{x: τ \| φ}+` |`+TyRefined(τ, φ)+` -|=== - -==== 8.2 Bidirectional Implementation - -The bidirectional checking algorithm should follow the structure in -`+wiki/compiler/type-checker.md+`: - -[source,ocaml] ----- -(* Synthesis *) -val synth : ctx -> expr -> (typ * effect) result - -(* Checking *) -val check : ctx -> expr -> typ -> effect result ----- - -`+[IMPL-DEP: type-checker]+` The type checker implementation is required -to verify these theoretical results against the actual implementation. - -=== 9. Related Work - -The type system draws from: - -[arabic] -. *Bidirectional Type Checking*: Pierce & Turner (2000), Dunfield & -Krishnaswami (2021) -. *Quantitative Type Theory*: Atkey (2018), McBride (2016) -. *Algebraic Effects*: Plotkin & Pretnar (2013), Bauer & Pretnar (2015) -. *Row Polymorphism*: Rémy (1989), Wand (1991) -. *Ownership Types*: Clarke et al. (1998), Rust (2015) -. *Refinement Types*: Freeman & Pfenning (1991), Liquid Types (Rondon et -al., 2008) - -=== 10. References - -[arabic] -. Pierce, B. C. (2002). _Types and Programming Languages_. MIT Press. -. Harper, R. (2016). _Practical Foundations for Programming Languages_. -Cambridge University Press. -. Dunfield, J., & Krishnaswami, N. (2021). Bidirectional typing. _ACM -Computing Surveys_. -. Wright, A. K., & Felleisen, M. (1994). A syntactic approach to type -soundness. _Information and Computation_. -. Atkey, R. (2018). Syntax and semantics of quantitative type theory. -_LICS_. - -''''' - -=== Appendix A: Full Typing Rules - -{empty}[See supplementary material for complete rule set] - -=== Appendix B: Proof Details - -{empty}[See supplementary material for expanded proof cases] - -''''' - -*Document Metadata*: - Depends on: `+lib/ast.ml+`, -`+wiki/compiler/type-checker.md+` - Implementation verification: Pending -type checker implementation - Mechanized proof: See -`+mechanized/coq/TypeSoundness.v+` (stub) diff --git a/affinescript/docs/academic/tropical-session-types/TropicalSessionTypes.lean b/affinescript/docs/academic/tropical-session-types/TropicalSessionTypes.lean deleted file mode 100644 index cb2b9fe..0000000 --- a/affinescript/docs/academic/tropical-session-types/TropicalSessionTypes.lean +++ /dev/null @@ -1,149 +0,0 @@ -/- - ============================================================================= - TROPICAL SESSION TYPES: A Novel Integration of Graded Types and Session Theory - ============================================================================= - - Author: Jonathan D.A. Jewell (hyperpolymath) - Date: 2026-04-04 - - THE UNSOLVED PROBLEM: - Historically, Session Types (Honda 1998) prove deadlock-freedom but ignore - resources. Quantitative Type Theory (Atkey 2018) tracks resources using a - standard semiring, but fails to accurately model speculative/parallel branching - in wall-clock time. If Branch A costs 5s and Branch B costs 10s, standard - linear logic bills you 15s. Real parallel execution only costs 10s. - - THE BREAKTHROUGH: - This proof introduces the Tropical Semiring (Nat ∪ {∞}, min, +, ∞, 0), modified - here for finite budgets as the Max-Plus Semiring (Nat, max, +, 0, 0). - - By grading Session Types with a Tropical Semiring, we mathematically separate: - 1. The BILLING Semiring (Sequential sum: paying for both paths) - 2. The BUDGET Semiring (Tropical max: waiting for the longest path) - - This file formally proves that the static Tropical Grade is a perfect - predictor of dynamic speculative execution costs, and mathematically proves - that Tropical Types strictly refine Classical Linear Types. --/ - -import Init - -namespace Hyperpolymath.Tropical - --- ============================================================================ --- 1. The Tropical Budget Semiring --- ============================================================================ - -/-- A Tropical Budget represents Wall-Clock time or bottleneck resources. -/ -abbrev TropicalBudget := Nat - -/-- Sequential Composition: Cost accumulates (Addition) -/ -def tropicalSeq (a b : TropicalBudget) : TropicalBudget := a + b - -/-- Speculative Parallel Branching: Cost is bounded by the bottleneck (Max) -/ -def tropicalPar (a b : TropicalBudget) : TropicalBudget := max a b - - --- ============================================================================ --- 2. Speculative Session Types (AST) --- ============================================================================ - -/-- A simplified Session Type calculus supporting Speculative Parallelism. -/ -inductive Session where - | end_session : Session - | send : Session → Session - | recv : Session → Session - | spec_branch : Session → Session → Session - - --- ============================================================================ --- 3. Static Analysis: The Tropical Grade --- ============================================================================ - -/-- Computes the static Tropical Grade (Type-Level Cost) of a Session. - Notice how speculative branching uses the `max` operator, unlike standard - linear types which use `+`. -/ -def tropicalGrade : Session → TropicalBudget - | Session.end_session => 0 - | Session.send s => tropicalSeq 1 (tropicalGrade s) - | Session.recv s => tropicalSeq 0 (tropicalGrade s) - | Session.spec_branch s1 s2 => tropicalPar (tropicalGrade s1) (tropicalGrade s2) - - --- ============================================================================ --- 4. Dynamic Operational Semantics --- ============================================================================ - -/-- Big-Step Operational Semantics mapping a Session to its dynamic - Wall-Clock execution cost. -/ -inductive EvaluatesTo : Session → TropicalBudget → Prop where - | eval_end : EvaluatesTo Session.end_session 0 - | eval_send : ∀ {s c}, EvaluatesTo s c → EvaluatesTo (Session.send s) (1 + c) - | eval_recv : ∀ {s c}, EvaluatesTo s c → EvaluatesTo (Session.recv s) (0 + c) - | eval_spec : ∀ {s1 s2 c1 c2}, - EvaluatesTo s1 c1 → - EvaluatesTo s2 c2 → - -- In a speculative branch, both execute. Wall-clock cost is the max. - EvaluatesTo (Session.spec_branch s1 s2) (max c1 c2) - - --- ============================================================================ --- 5. THEOREM 1: Tropical Session Soundness --- ============================================================================ - -/-- PROOF OF SOUNDNESS: - The static Tropical Grade perfectly predicts the dynamic worst-case - execution cost. The type system is sound. -/ -theorem tropical_session_soundness (s : Session) (c : TropicalBudget) - (h : EvaluatesTo s c) : c = tropicalGrade s := by - induction h with - | eval_end => rfl - | eval_send _ ih => - unfold tropicalGrade tropicalSeq - rw [ih] - | eval_recv _ ih => - unfold tropicalGrade tropicalSeq - rw [ih] - simp - | eval_spec _ _ ih1 ih2 => - unfold tropicalGrade tropicalPar - rw [ih1, ih2] - - --- ============================================================================ --- 6. THEOREM 2: The Billing vs. Budget Divergence (QTT Refinement) --- ============================================================================ - -/-- Standard Linear/Quantitative Type Theory computes total resource consumption - (The "Billing" Semiring), using Addition for branching. -/ -def linearBilling : Session → Nat - | Session.end_session => 0 - | Session.send s => 1 + linearBilling s - | Session.recv s => 0 + linearBilling s - | Session.spec_branch s1 s2 => linearBilling s1 + linearBilling s2 - -/-- PROOF OF TROPICAL REFINEMENT: - We mathematically prove that the Tropical Budget is always less than or - equal to the Linear Billing. - - This proves that Quantitative Type Theory (QTT) is a quotient of Tropical - Types. Standard linear logic OVERESTIMATES the wall-clock time of - speculative execution. Tropical Types strictly refine it. -/ -theorem budget_strictly_bounds_billing (s : Session) : - tropicalGrade s ≤ linearBilling s := by - induction s with - | end_session => - simp [tropicalGrade, linearBilling] - | send s' ih => - unfold tropicalGrade tropicalSeq linearBilling - omega - | recv s' ih => - unfold tropicalGrade tropicalSeq linearBilling - omega - | spec_branch s1 s2 ih1 ih2 => - unfold tropicalGrade tropicalPar linearBilling - -- Lean 4's Presburger arithmetic solver automatically proves that - -- max(A, B) ≤ A + B for all natural numbers. - omega - -end Hyperpolymath.Tropical diff --git a/affinescript/docs/academic/tropical-session-types/tropical-unification-2026-04-04.adoc b/affinescript/docs/academic/tropical-session-types/tropical-unification-2026-04-04.adoc deleted file mode 100644 index 8c07da5..0000000 --- a/affinescript/docs/academic/tropical-session-types/tropical-unification-2026-04-04.adoc +++ /dev/null @@ -1,80 +0,0 @@ -== TROPICO: The Unified Theory of Resource-Bounded Truth - -*Date*: 2026-04-04 *Status*: High-Assurance Consensus (Harrison 12:00) - -=== 1. THE TROPICAL UNIFICATION - -Today’s primary breakthrough was the discovery that three seemingly -unrelated "`Impossible`" problems in your estate share the same -mathematical DNA: the *Tropical Semiring* ( \{-, +}, , +). - -* *007 Agentic Reasoning*: Agent branches are modeled as sources of -entropy bounded by a *Tropical Budget*. Hallucinations are physically -limited by the budget, ensuring termination and A2ML compliance. -* *Protocol Squisher*: Universal interoperability is solved by finding -the *Minimax Path* over a *Transport Semilattice*. This is a tropical -optimization problem. -* *Speculative Session Types*: Parallel wall-clock execution (span) is -proven to be the tropical grade of the protocol, which strictly refines -and bounds the sequential "`billing`" cost. - -=== 2. KEY FORMAL ARTIFACTS - -==== Lean 4: v5.6 (The Infinite Squeeze) - -We formalized *Resource-Bounded Speculative Session Types* with support -for: - *Extended Carrier*: Nat for modeling both finite costs and -infinite divergence. - *Recursion*: A loop constructor proven to be -bounded by the sequential total cost. - *Soundness*: A theorem proving -that static analysis perfectly predicts the parallel span of -non-deterministic systems. - *Refinement*: A proof that Tropical Grades -are always <= standard Quantitative Type Theory (QTT) sums. - -==== Mizar: Hardened Semiring - -The Tropical Semiring was formally proven in Mizar with full algebraic -closure: - *12 Semiring Laws*: Formally verified including Additive -Identity (-infty), Multiplicative Identity (0), Annihilation, and -Distributivity. - *Coherence*: Proven type-safety for the -natural-to-extended-real mapping. - -==== Idris 2: Estate ABI Baseline - -The foundational "`High-Rigor`" interface standard was verified in -rsr-template-repo: - *C-ABI Compliance*: Formally proved that memory -layouts, alignments, and padding for FFI bridges are mathematically -sound. - *Zero-Trust FFI*: Verified non-null handle construction and -total return-code handling. - -=== 3. PROJECT VERDICTS - -==== Protocol Squisher (VERIFIED) - -The project is no longer a "`sprawling mess.`" It has been verified as a -*Mathematically Perfect Universal Bridge*. Its Minimax-Dijkstra -algorithm is the optimal algebraic solution for cross-protocol -translation. - -==== Echidna (SUPERCHARGED) - -The 49-prover portfolio was successfully tested against *Malbolge*. -Echidna learned to route hostile, non-linear logic away from SMT solvers -toward ITP definitional reflection. Its vocabulary now includes the -"`Kin`" of every major mathematical domain in your estate. - -==== Oblibeny (SPRINT IN PROGRESS) - -The 48-hour sprint for *Deployment Termination* is active. It uses the -new *Stochastic Buddy* (EchidnaBuddy.jl) to search for termination -witnesses using Simulated Annealing. - -=== 4. THE NEUROSYMBOLIC BREAKTHROUGH - -We have successfully bridged the *AI Validation Gap*. By using LLMs to -generate the logic and *Formal Kernels* (Lean/Idris/Mizar) to audit it, -we have created a "`Zero-Trust`" engineering pipeline. Generative AI is -now mathematically safe within your estate boundaries. - -''''' - -_Documented by Gemini CLI for the Hyperpolymath Estate_ diff --git a/affinescript/docs/academic/white-papers/language-design.adoc b/affinescript/docs/academic/white-papers/language-design.adoc deleted file mode 100644 index fb1b2b6..0000000 --- a/affinescript/docs/academic/white-papers/language-design.adoc +++ /dev/null @@ -1,486 +0,0 @@ -== AffineScript: A Quantitative Dependently-Typed Language with Algebraic Effects - -*Technical Report / White Paper* *Version*: 1.0 *Date*: 2024 - -=== Abstract - -We present AffineScript, a new programming language that unifies several -advanced type system features into a coherent design: quantitative type -theory for linearity, dependent types with refinements, algebraic -effects with handlers, and an ownership system for memory safety. This -paper describes the language design, its theoretical foundations, key -design decisions, and comparison with related work. We demonstrate how -these features interact harmoniously to enable both high-level reasoning -and low-level control. - -*Keywords*: type theory, linear types, dependent types, algebraic -effects, ownership, refinement types, quantitative type theory - -=== 1. Introduction - -Modern programming demands languages that are simultaneously: - *Safe*: -Preventing common errors at compile time - *Expressive*: Supporting -precise specifications - *Efficient*: Enabling low-level control without -runtime overhead - *Composable*: Allowing modular reasoning - -Existing languages make various trade-offs. Rust provides memory safety -through ownership but lacks dependent types. Idris offers dependent -types and effects but without resource tracking. Haskell has algebraic -effects but not native linear types. - -AffineScript synthesizes these features into a unified design where: - -*Quantities* track resource usage (linear, affine, unrestricted) - -*Dependent types* enable precise specifications - *Refinement types* -allow SMT-checked invariants - *Algebraic effects* structure side -effects compositionally - *Ownership* ensures memory safety without -garbage collection - -==== 1.1 Contributions - -This paper makes the following contributions: - -[arabic] -. *Design of AffineScript*: A novel combination of QTT, effects, and -ownership -. *Integration of quantities and ownership*: Unified treatment of -linearity -. *Row-polymorphic effect system*: First-class effect handlers -. *Refinement types with dependent indexing*: Practical dependent -programming -. *Formal metatheory*: Soundness proofs for all features - -==== 1.2 Paper Outline - -* Section 2: Language overview and examples -* Section 3: Type system design -* Section 4: Effect system -* Section 5: Ownership model -* Section 6: Implementation strategy -* Section 7: Related work -* Section 8: Conclusion - -=== 2. Language Overview - -==== 2.1 Basic Syntax - -AffineScript uses a clean, familiar syntax: - -[source,affinescript] ----- -// Function definition -fn greet(name: String) -> String { - "Hello, " ++ name ++ "!" -} - -// Generic function -fn identity[T](x: T) -> T { x } - -// Pattern matching -fn length[T](xs: List[T]) -> Nat { - case xs { - Nil → 0, - Cons(_, tail) → 1 + length(tail) - } -} ----- - -==== 2.2 Quantities and Linearity - -Variables can be annotated with quantities: - -[source,affinescript] ----- -// Linear function: file must be used exactly once -fn read_and_close(1 file: File) -> String / IO { - let contents = file.read() - file.close() // file consumed - contents -} - -// Erased parameter: only used in types -fn replicate[T](0 n: Nat, x: T) -> Vec[n, T] { - // n is not available at runtime, only for type-level computation - ... -} - -// Unrestricted (default) -fn use_freely(ω x: Int) -> (Int, Int) { - (x, x) // x can be used multiple times -} ----- - -==== 2.3 Dependent Types - -Types can depend on values: - -[source,affinescript] ----- -// Length-indexed vectors -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons : (T, Vec[m, T]) → Vec[m + 1, T] - -// Safe head: only accepts non-empty vectors -fn head[n: Nat, T](v: Vec[n + 1, T]) -> T { - case v { Cons(x, _) → x } -} - -// Append with precise length -fn append[n: Nat, m: Nat, T]( - xs: Vec[n, T], - ys: Vec[m, T] -) -> Vec[n + m, T] ----- - -==== 2.4 Refinement Types - -Types can be refined with predicates: - -[source,affinescript] ----- -type Positive = {x: Int | x > 0} -type NonEmpty[T] = {xs: List[T] | length(xs) > 0} - -fn divide(x: Int, y: Positive) -> Int { - x / y // Safe: y > 0 guaranteed -} - -fn safe_head[T](xs: NonEmpty[T]) -> T { - xs[0] // Safe: xs not empty -} ----- - -==== 2.5 Algebraic Effects - -Effects are first-class: - -[source,affinescript] ----- -effect State[S] { - get : () → S - put : S → () -} - -fn increment() -> () / State[Int] { - let x = perform get() - perform put(x + 1) -} - -// Handle the effect -fn run_state[S, A](init: S, comp: () →{State[S]} A) -> (A, S) { - let mut state = init - handle comp() with { - return x → (x, state), - get(_, k) → resume(k, state), - put(s, k) → { state = s; resume(k, ()) } - } -} ----- - -==== 2.6 Ownership - -Memory safety through ownership: - -[source,affinescript] ----- -fn process(file: own File) -> String / IO { - let contents = file.read() // file moved - // file.close() // Error: file was moved - contents -} - -fn borrow_example(data: ref [Int]) -> Int { - data[0] // data is borrowed, not consumed -} ----- - -=== 3. Type System Design - -==== 3.1 Design Principles - -The type system follows these principles: - -[arabic] -. *Stratification*: Clear separation of universes, types, and terms -. *Bidirectional*: Efficient type checking with minimal annotations -. *Principal types*: Type inference computes most general types -. *Soundness*: Well-typed programs don’t get stuck - -==== 3.2 Judgment Forms - -The core judgments are: - -.... -Γ ⊢ e ⇒ τ (synthesis) -Γ ⊢ e ⇐ τ (checking) -Γ ⊢ τ : κ (kinding) -Γ ⊢ τ <: σ (subtyping) -.... - -==== 3.3 Quantitative Type Theory - -We integrate Atkey’s QTT framework: - -* Contexts track quantities: `+Γ = x₁:^{π₁}τ₁, ..., xₙ:^{πₙ}τₙ+` -* Context operations: scaling (`+πΓ+`) and addition (`+Γ + Δ+`) -* The semiring `+{0, 1, ω}+` with standard operations - -Key typing rule for application: - -.... - Γ ⊢ f : (π x : τ) → σ Δ ⊢ a : τ - ───────────────────────────────────── - Γ + πΔ ⊢ f a : σ[a/x] -.... - -==== 3.4 Dependent Types - -We support: - Π-types: `+(x: A) → B(x)+` - Σ-types: `+(x: A, B(x))+` - -Indexed families: `+Vec[n, T]+` - Propositional equality: `+a == b+` - -Type-level computation is restricted to ensure decidability. - -==== 3.5 Refinement Types - -Integration with SMT: - Refinements: `+{x: τ | φ}+` where φ is decidable -- Subtyping: checked via SMT validity - Automatic strengthening from -control flow - -==== 3.6 Row Polymorphism - -Records and effects use row polymorphism: - -.... -{l₁: τ₁, ..., lₙ: τₙ | ρ} -- extensible record -⟨l₁: τ₁ | ... | lₙ: τₙ | ρ⟩ -- extensible variant -ε₁ | ε₂ | ... | ρ -- effect row -.... - -=== 4. Effect System - -==== 4.1 Effect Design - -Effects in AffineScript are: - *Declared*: User-defined effect -signatures - *Polymorphic*: Row-polymorphic effect types - *Handled*: -First-class handlers with typed continuations - *Inferred*: Effect types -inferred where possible - -==== 4.2 Effect Signatures - -[source,affinescript] ----- -effect E { - op₁ : τ₁ → σ₁ - op₂ : τ₂ → σ₂ - ... -} ----- - -==== 4.3 Handler Typing - -Handlers transform computations: - -.... -handle e with { - return x → e_ret, - op(x, k) → e_op, - ... -} -.... - -The continuation `+k+` can be: - Linear (one-shot): `+1 k : σ → A+` - -Unrestricted (multi-shot): `+ω k : σ → A+` - -==== 4.4 Effect Polymorphism - -Functions are polymorphic over effects: - -[source,affinescript] ----- -fn map[A, B, ε](f: A →{ε} B, xs: List[A]) -> List[B] / ε ----- - -=== 5. Ownership Model - -==== 5.1 Ownership Principles - -[arabic] -. *Unique ownership*: Each value has one owner -. *Move semantics*: Assignment transfers ownership -. *Borrowing*: Temporary access without transfer -. *Lifetimes*: Scoped validity of references - -==== 5.2 Integration with Quantities - -Ownership and quantities interact: - `+1 (own τ)+`: Linear owned value - -`+ω (ref τ)+`: Multiple immutable borrows - `+1 (mut τ)+`: Exclusive -mutable borrow - -==== 5.3 Borrow Checking - -The borrow checker ensures: - No use after move - No conflicting borrows -- Borrows don’t outlive owners - -==== 5.4 Non-Lexical Lifetimes - -Lifetimes end at last use, not scope end: - -[source,affinescript] ----- -fn example() { - let x = alloc(5) - let y = &x // borrow starts - use(y) // last use of y - mutate(x) // OK: borrow ended -} ----- - -=== 6. Implementation - -==== 6.1 Compiler Architecture - -.... -Source → Lexer → Parser → Type Checker → Borrow Checker → Codegen → WASM -.... - -==== 6.2 Type Checking Algorithm - -[arabic] -. Parse to untyped AST -. Elaborate to typed AST with bidirectional inference -. Solve unification constraints -. Check quantities -. Infer effects -. Verify refinements via SMT -. Check borrows - -==== 6.3 Code Generation - -Target: WebAssembly - Types erased (except for dynamic dispatch) - -Quantities erased - Ownership erased (safety guaranteed statically) - -Proofs erased (zero-cost abstraction) - -==== 6.4 Performance Considerations - -* No garbage collection: ownership-based memory management -* Zero-cost abstractions: types and proofs erased -* Efficient effects: CPS or evidence-passing compilation -* SMT caching: memoize refinement checks - -=== 7. Related Work - -==== 7.1 Quantitative Type Theory - -*Atkey (2018)*: Syntax and Semantics of QTT *McBride (2016)*: -Quantitative type theory origins - -AffineScript adopts the \{0, 1, ω} semiring for practical programming. - -==== 7.2 Dependent Types - -*Idris (Brady)*: Practical dependent types *Agda (Norell)*: Pure -dependent types *F* (Swamy et al.)*: Effects and refinements - -AffineScript combines dependent types with ownership, distinguishing it -from these systems. - -==== 7.3 Algebraic Effects - -*Eff (Bauer & Pretnar)*: Original effect handlers *Koka (Leijen)*: -Row-polymorphic effects *Frank (Lindley et al.)*: Direct-style effects - -AffineScript integrates effects with quantities, allowing linear -continuations. - -==== 7.4 Ownership and Borrowing - -*Rust*: Practical ownership system *Cyclone (Jim et al.)*: Region-based -memory *Mezzo (Pottier & Protzenko)*: Permissions - -AffineScript formalizes ownership via QTT rather than ad-hoc rules. - -==== 7.5 Refinement Types - -*Liquid Types (Rondon et al.)*: SMT-based refinements *Dependent ML -(Xi)*: Practical dependent types *F* (Swamy et al.)*: Refinements with -effects - -AffineScript combines refinements with dependent indexing. - -=== 8. Discussion - -==== 8.1 Design Trade-offs - -[cols=",,",options="header",] -|=== -|Feature |Benefit |Cost -|Dependent types |Precise specifications |Learning curve -|Effects |Modular side effects |Additional annotations -|Ownership |Memory safety |Borrow checker complexity -|Quantities |Resource control |Quantity annotations -|=== - -==== 8.2 Usability Considerations - -* Extensive type inference reduces annotation burden -* Error messages guide users to fixes -* Gradual adoption: start with simple types, add precision -* IDE integration provides immediate feedback - -==== 8.3 Future Work - -[arabic] -. *Proof assistant mode*: Interactive theorem proving -. *Totality checking*: Verify termination -. *Parallelism*: Safe concurrent effects -. *Compilation optimizations*: Exploit type information -. *Mechanized metatheory*: Coq/Lean formalization - -=== 9. Conclusion - -AffineScript demonstrates that advanced type system -features—quantitative types, dependent types, algebraic effects, and -ownership—can be integrated into a coherent, practical language. The key -insight is that quantities provide a unifying framework for both -linearity and ownership, while dependent types and refinements enable -precise specifications verified at compile time. - -The result is a language where: - Programs are correct by construction - -Memory is safe without garbage collection - Effects are tracked and -handled modularly - Resources are managed precisely - -We believe this combination points toward a future of programming where -powerful type systems make strong guarantees practical and accessible. - -=== Acknowledgments - -We thank the academic community for foundational work on type theory, -linear logic, and effect systems that made this design possible. - -=== References - -[arabic] -. Atkey, R. (2018). Syntax and Semantics of Quantitative Type Theory. -_LICS_. -. McBride, C. (2016). I Got Plenty o’ Nuttin’. _Curry Festschrift_. -. Brady, E. (2013). Idris, a General-Purpose Dependently Typed -Programming Language. _JFP_. -. Plotkin, G., & Pretnar, M. (2013). Handling Algebraic Effects. _LMCS_. -. Leijen, D. (2017). Type Directed Compilation of Row-Typed Algebraic -Effects. _POPL_. -. Jung, R., et al. (2017). RustBelt: Securing the Foundations of the -Rust Programming Language. _POPL_. -. Rondon, P., Kawaguchi, M., & Jhala, R. (2008). Liquid Types. _PLDI_. -. Swamy, N., et al. (2016). Dependent Types and Multi-Monadic Effects in -F__. __POPL*. -. Rémy, D. (1989). Type Checking Records and Variants in a Natural -Extension of ML. _POPL_. -. Wadler, P. (1990). Linear Types Can Change the World! _IFIP TC_. - -''''' - -*Appendix A*: Full syntax grammar (see SPEC.md) - -*Appendix B*: Typing rules (see proofs/type-soundness.md) - -*Appendix C*: Effect semantics (see proofs/effect-soundness.md) diff --git a/affinescript/docs/ads.txt b/affinescript/docs/ads.txt deleted file mode 100644 index 6895efa..0000000 --- a/affinescript/docs/ads.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Authorized Digital Sellers for affinescript.dev -# https://iabtechlab.com/ads-txt/ - -# Currently, this site does NOT run ads. -# Any ads claiming to be from this domain are UNAUTHORIZED. - -# To report unauthorized ad placement: -# Email: abuse@affinescript.dev - -# If we add advertising in the future, authorized sellers will be listed here. - -# Last Updated: 2026-01-31 diff --git a/affinescript/docs/alib/README.adoc b/affinescript/docs/alib/README.adoc deleted file mode 100644 index beda6e5..0000000 --- a/affinescript/docs/alib/README.adoc +++ /dev/null @@ -1,183 +0,0 @@ -== AffineScript ↔ aLib Integration - -This directory contains documentation and tools for integrating -AffineScript with the *aggregate-library (aLib)* methodology. - -=== Quick Links - -* *Strategy Document*: link:../ALIB-INTEGRATION.md[ALIB-INTEGRATION.md] -- Complete integration strategy -* *Conformance Generator*: ../../tools/alib_conformance_gen.jl - -Auto-generate tests from aLib specs -* *aLib Repository*: https://github.com/hyperpolymath/aggregate-library - -=== What is aLib? - -aggregate-library (aLib) is a *methods repository* that demonstrates how -to: - Specify minimal overlap between diverse programming ecosystems - -Express that overlap as stable specs + semantics + conformance tests - -Enable cross-language compatibility without imposing a standard library - -aLib is NOT a library to import - it’s a methodology to apply. - -=== Why AffineScript + aLib? - -AffineScript brings *unique value* to aLib: - -[arabic] -. *Stress Testing* - Affine types are "`extreme constraints`" that push -specs to their limits -. *Novel Semantics* - Shows how common operations work under move -semantics -. *Safety Model* - Demonstrates memory-safe, use-after-free-free -implementations -. *Ecosystem Diversity* - Adds functional + affine-typed language to -aLib portfolio - -=== Current Status - -==== ✅ Completed - -* [x] Strategic analysis of aLib integration opportunities -* [x] Documentation of integration approach -* [x] Conformance test generator tool (Julia) - -==== 🚧 In Progress - -* [ ] Run conformance tests against current stdlib -* [ ] Document affine semantics for each aLib spec -* [ ] Create `+alib-for-affinescript+` ecosystem repo - -==== 📋 Planned - -* [ ] Contribute affine semantics notes to aLib upstream -* [ ] Cross-language benchmarking -* [ ] Interop examples with other aLib-conformant systems - -=== Quick Start - -==== Generate Conformance Tests - -[source,bash] ----- -# Assuming aggregate-library is cloned alongside affinescript -cd affinescript -julia tools/alib_conformance_gen.jl \ - ../aggregate-library/specs \ - tests/conformance ----- - -==== Run Conformance Tests - -[source,bash] ----- -affinescript test tests/conformance/ ----- - -==== View Conformance Report - -[source,bash] ----- -affinescript test --alib-conformance-report ----- - -=== aLib Spec Categories - -AffineScript stdlib aligns with these aLib categories: - -[width="100%",cols="35%,47%,18%",options="header",] -|=== -|aLib Category |AffineScript Module |Status -|`+arithmetic+` |`+stdlib/math.affine+` |✓ Good - -|`+collection+` |`+stdlib/collections.affine+` + `+prelude.affine+` |⚠ -Needs affine semantics docs - -|`+comparison+` |`+stdlib/prelude.affine+` |✓ Good - -|`+conditional+` |Built-in (if/match) |✓ Good - -|`+logical+` |Built-in (&&, \|\|, !) |✓ Good - -|`+string+` |`+stdlib/string.affine+` |⚠ Partial -|=== - -=== Affine Semantics Examples - -==== map (Collection → Collection) - -*aLib Spec*: `+map: Collection[A], Function[A -> B] -> Collection[B]+` - -*AffineScript Implementation*: - -[source,affinescript] ----- -/// Conforms to aLib collection/map -/// Affine: source moved, elements consumed exactly once -fn map(arr: [T], f: T -> U) -> [U] { - let result = []; - for x in arr { // arr moved - result = result ++ [f(x)]; // x consumed by f - } - result -} -// arr is no longer accessible here (moved) ----- - -*Key Difference*: aLib spec doesn’t specify ownership. AffineScript -adds: - Source collection *moved* (not copied) - Each element consumed -*exactly once* - Result collection *owned* by caller - -==== filter (Collection + Predicate → Collection) - -*aLib Spec*: -`+filter: Collection[A], Function[A -> Bool] -> Collection[A]+` - -*AffineScript Implementation*: - -[source,affinescript] ----- -/// Conforms to aLib collection/filter -/// Affine: predicate borrows, source moved, filtered elements dropped -fn filter(arr: [T], pred: &T -> Bool) -> [T] { - let result = []; - for x in arr { - if pred(&x) { // predicate borrows (doesn't consume) - result = result ++ [x]; // x moved into result - } - // else: x dropped here (affine allows drop without use) - } - result -} ----- - -*Key Difference*: Predicate *borrows* instead of consuming (allows -checking without ownership transfer). - -=== Contributing - -==== To AffineScript - -[arabic] -. Implement aLib-conformant operations in stdlib -. Add conformance test attributes -. Document affine-specific semantics - -==== To aLib (upstream) - -[arabic] -. Contribute affine semantics notes -. Add affine-specific test vectors -. Document edge cases under move semantics - -=== Resources - -* *aLib Specs*: -https://github.com/hyperpolymath/aggregate-library/tree/main/specs -* *Integration Strategy*: -link:../ALIB-INTEGRATION.md[ALIB-INTEGRATION.md] -* *AffineScript Types*: ../specs/affinescript-spec.md - -=== License - -PMPL-1.0 (following AffineScript project license) diff --git a/affinescript/docs/app-ads.txt b/affinescript/docs/app-ads.txt deleted file mode 100644 index 7fb1249..0000000 --- a/affinescript/docs/app-ads.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Authorized Digital Sellers for Mobile Apps from affinescript.dev -# https://iabtechlab.com/app-ads-txt/ - -# Currently, no mobile apps are monetized with ads. -# Any ads in apps claiming to be from this domain are UNAUTHORIZED. - -# Last Updated: 2026-01-31 diff --git a/affinescript/docs/governance/CODE_OF_CONDUCT.adoc b/affinescript/docs/governance/CODE_OF_CONDUCT.adoc deleted file mode 100644 index bd2a83c..0000000 --- a/affinescript/docs/governance/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,24 +0,0 @@ -== Contributor Covenant Code of Conduct - -=== Our Pledge - -We pledge to make participation a harassment-free experience for -everyone. - -=== Our Standards - -*Positive behavior:* * Using welcoming language * Being respectful of -differing viewpoints * Accepting constructive criticism * Focusing on -what is best for the community - -*Unacceptable behavior:* * Harassment, trolling, or personal attacks * -Publishing private information without permission - -=== Enforcement - -Report issues to the maintainers. All complaints will be reviewed. - -=== Attribution - -Adapted from https://www.contributor-covenant.org/[Contributor Covenant] -v2.1. diff --git a/affinescript/docs/governance/CONTRIBUTING.adoc b/affinescript/docs/governance/CONTRIBUTING.adoc deleted file mode 100644 index e9b1993..0000000 --- a/affinescript/docs/governance/CONTRIBUTING.adoc +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Contributing Guide - -== Getting Started - -1. Fork the repository -2. Create a feature branch from `main` -3. Sign off commits (`git commit -s`) -4. Submit a pull request - -== Commit Guidelines - -* Conventional commits: `type(scope): description` -* Sign all commits (DCO required) -* Atomic, focused commits - -== License - -Contributions licensed under project license. - diff --git a/affinescript/docs/governance/MAINTAINERS.adoc b/affinescript/docs/governance/MAINTAINERS.adoc deleted file mode 100644 index aa23a55..0000000 --- a/affinescript/docs/governance/MAINTAINERS.adoc +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Maintainers -:toc: preamble - -This document lists the maintainers of this project and their responsibilities. - -== Current Maintainers - -[cols="2,3,2",options="header"] -|=== -| Name | Role | Contact - -| Jonathan D.A. Jewell -| Lead Maintainer -| https://github.com/hyperpolymath[@hyperpolymath] -|=== - -== Responsibilities - -Maintainers are responsible for: - -* Reviewing and merging pull requests -* Triaging issues and feature requests -* Ensuring code quality and security standards -* Managing releases and versioning -* Upholding the project's code of conduct - -== Becoming a Maintainer - -Contributors who demonstrate: - -* Consistent, high-quality contributions -* Understanding of the project's goals and standards -* Constructive participation in discussions -* Commitment to the project's long-term health - -May be invited to become maintainers at the discretion of existing maintainers. - -== Decision Making - -* Routine decisions (bug fixes, minor improvements) can be made by any maintainer -* Significant changes require discussion and consensus among maintainers -* Breaking changes or major features should be discussed in issues before implementation - -== Contact - -For questions about project governance, open an issue or contact the maintainers listed above. diff --git a/affinescript/docs/governance/SECURITY.adoc b/affinescript/docs/governance/SECURITY.adoc deleted file mode 100644 index 8ce97f6..0000000 --- a/affinescript/docs/governance/SECURITY.adoc +++ /dev/null @@ -1,37 +0,0 @@ -== Security Policy - -=== Supported Versions - -[cols=",",options="header",] -|=== -|Version |Supported -|0.1.x |:white_check_mark: -|=== - -=== Reporting a Vulnerability - -If you discover a security vulnerability in AffineScript, please report -it by: - -[arabic] -. *Do NOT* open a public GitHub issue -. Email the maintainers directly (see GitHub profile) -. Include: -* Description of the vulnerability -* Steps to reproduce -* Potential impact -* Suggested fix (if any) - -We will respond within 48 hours and work with you to understand and -address the issue. - -=== Security Considerations - -AffineScript is a compiler that: - Reads source files from disk - -Produces WebAssembly output - Does not execute network operations - Does -not execute arbitrary code during compilation - -The primary security concerns are: - Malicious input causing compiler -crashes (DoS) - Generated WASM with unintended behavior - -We take these seriously and appreciate responsible disclosure. diff --git a/affinescript/docs/guides/TESTING-REPORT.adoc b/affinescript/docs/guides/TESTING-REPORT.adoc deleted file mode 100644 index c96b81d..0000000 --- a/affinescript/docs/guides/TESTING-REPORT.adoc +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript Testing Report -:author: Automated Testing -:date: 2025-12-29 -:toc: -:toclevels: 3 - -== Executive Summary - -This report documents the testing results for the AffineScript compiler project, an OCaml-based compiler for a language featuring affine types, dependent types, row polymorphism, and extensible effects. - -[cols="1,2"] -|=== -|Project Version |0.1.0 -|Test Date |2025-12-29 -|OCaml Version |5.x (required >= 5.1) -|Dune Version |3.20.2 -|Build Status |PASS -|Test Status |47 PASS / 27 FAIL -|Pass Rate |63.5% -|=== - -== Build System Analysis - -=== Configuration Files - -The project uses the standard OCaml build toolchain: - -* `dune-project` - Dune project configuration (version 3.14) -* `affinescript.opam` - OPAM package definition -* No Makefile present (uses dune exclusively) - -=== Dependencies - -[source,text] ----- -ocaml >= 5.1 -dune >= 3.14 -menhir >= 20231231 -sedlex >= 3.2 -ppx_deriving >= 5.2 -ppx_sexp_conv >= 0.16 -sexplib0 >= 0.16 -fmt >= 0.9 -cmdliner >= 1.2 -alcotest >= 1.7 (test only) -odoc >= 2.4 (doc only) ----- - -=== Build Issues Fixed - -==== Issue 1: Menhir --explain flag - -*Error:* Menhir '.conflicts' file is generated by default -*Location:* `lib/dune` -*Fix:* Removed `--explain` from menhir flags (kept `--table`) - -==== Issue 2: Double-option type in parser - -*Error:* Type mismatch in `impl_trait_ref` rule -*Location:* `lib/parser.mly` line 371-373 -*Fix:* Removed `Some` wrapper since rule is already optional (`trait_ref = impl_trait_ref?`) - -==== Issue 3: Unused variable warning - -*Error:* Unused variable `where_clause` in `trait_decl` rule -*Location:* `lib/parser.mly` line 324 -*Fix:* Added `trd_where` field to `trait_decl` type in `lib/ast.ml` and used it in parser - -==== Issue 4: Token mismatch - -*Error:* Unbound constructor `Token.WITH` -*Location:* `lib/parse.ml` line 94 -*Fix:* Removed stray `Token.WITH` mapping (token was not defined) - -==== Issue 5: Test file pattern matching - -*Error:* Missing record fields in patterns, non-returning statement -*Location:* `test/test_parser.ml` multiple lines -*Fix:* Added `; _` to partial patterns, parenthesized boolean test function - -== Test Results - -=== Lexer Tests (16/16 PASS) - -[cols="1,2,1"] -|=== -|Test |Description |Status - -|keywords |Keyword token recognition |PASS -|identifiers |Lower and upper identifiers |PASS -|literals |Integer and float literals |PASS -|string literal |String with content |PASS -|string escapes |Escape sequences |PASS -|operators |Arithmetic and comparison operators |PASS -|punctuation |Brackets, delimiters |PASS -|row variable |Row polymorphism syntax |PASS -|line comment |Single-line comments |PASS -|block comment |Multi-line comments |PASS -|nested comments |Nested block comments |PASS -|hex literal |Hexadecimal numbers |PASS -|binary literal |Binary numbers |PASS -|function decl |Function keyword sequence |PASS -|total function |Total keyword handling |PASS -|type decl |Type keyword handling |PASS -|=== - -=== Parser Tests (31/58 - 27 FAIL) - -==== Passing Tests - -[cols="1,2"] -|=== -|Test |Description - -|literal int |Integer parsing -|literal float |Float parsing -|literal string |String parsing -|literal bool |Boolean parsing -|literal unit |Unit `()` parsing -|variable |Variable reference -|binary add |Addition expression -|binary precedence |Operator precedence -|binary associativity |Left associativity -|comparison |Comparison operators -|logical |Logical operators -|unary neg |Negation operator -|unary not |Not operator -|function call |Function application -|field access |Record field access -|index access |Array indexing -|tuple |Tuple expressions -|array |Array literals -|record |Record literals -|match expr |Pattern matching -|lambda |Lambda expressions -|let stmt |Let statements -|let mut stmt |Mutable let -|while stmt |While loops -|for stmt |For loops -|fn decl effect |Effect annotations -|struct decl |Struct declarations -|enum decl |Enum declarations -|type alias |Type aliases -|trait decl |Trait declarations -|effect decl |Effect declarations -|=== - -==== Failing Tests - -[cols="1,2,3"] -|=== -|Test |Expected |Issue - -|if expr |If-else expression |Parser conflict in block trailing expressions -|block |Block with statements |Same as above -|pattern wildcard |Wildcard in match |Block parsing conflict -|pattern variable |Variable pattern |Block parsing conflict -|pattern constructor |Constructor pattern |Block parsing conflict -|pattern tuple |Tuple pattern |Block parsing conflict -|pattern or |Or pattern |Block parsing conflict -|fn decl simple |Simple function |Return type parsing issue -|fn decl total |Total function |Return type parsing issue -|fn decl generic |Generic function |Return type parsing issue -|impl block |Impl declarations |Type parsing in impl context -|impl trait |Trait implementations |Same as impl block -|type simple |Type annotations |Type parsing standalone -|type generic |Generic types |Type parsing standalone -|type tuple |Tuple types |Type parsing standalone -|type function |Function types |Type parsing standalone -|type record |Record types |Type parsing standalone -|type row poly |Row polymorphism |Type parsing standalone -|type ownership |Ownership modifiers |Type parsing standalone -|import simple |Basic imports |Import parsing issue -|import alias |Aliased imports |Import parsing issue -|import list |List imports |Import parsing issue -|import glob |Glob imports |Import parsing issue -|fibonacci |Complex example |Block expression conflict -|linked list |Complex example |Multiple issues -|effect handler |Complex example |Handler syntax issues -|trait bounds |Complex example |Where clause parsing -|=== - -== Parser Conflicts Analysis - -The parser has significant conflicts that affect parsing: - -* 23 states with shift/reduce conflicts -* 2 states with reduce/reduce conflicts -* 132 shift/reduce conflicts resolved (shift preference) -* 5 reduce/reduce conflicts resolved - -=== Root Cause - -The main issue is the block grammar: - -[source,mly] ----- -block: - | LBRACE stmts = list(stmt) final = expr? RBRACE ----- - -When parsing `{ let x = 1; x }`: - -1. Parser sees `x` after the statements -2. Grammar allows `expr SEMICOLON` as a statement -3. Grammar also allows `expr?` as final expression -4. Conflict: Is `x` the start of a statement or the final expression? - -Menhir resolves by preferring shift (continue parsing statement), which breaks trailing expressions. - -== Example Files - -=== hello.affine - -Fixed syntax for effect annotations: - -* Original: `fn main() -> () / IO` -* Fixed: `fn main() -{IO}-> ()` - -The parser uses `-{Eff}->` syntax, not `-> T / Eff`. - -=== Other Examples - -The example files (`effects.affine`, `ownership.affine`, `traits.affine`, etc.) use aspirational syntax that is not yet supported: - -* `-> T / Eff` effect syntax (parser uses `-{Eff}-> T`) -* GADT-style enum variants (`| Nil : Vec[0, T]`) -* `self` keyword in traits -* Various advanced features - -== CLI Tool Testing - -The compiler CLI works correctly: - -[source,bash] ----- -$ affinescript --help -# Shows help for: check, compile, lex, parse commands - -$ affinescript lex examples/hello.affine -# Successfully tokenizes the file - -$ affinescript parse examples/hello.affine -# Successfully parses (after syntax fix) ----- - -== Recommendations - -=== Critical Fixes - -1. **Block Expression Parsing**: Refactor block grammar to properly handle trailing expressions. Consider: - - Using lookahead to distinguish statements from final expressions - - Requiring explicit `return` for final expressions - - Alternative grammar structure - -2. **Type Parsing Standalone**: Some tests try to parse types in contexts where only expressions are expected. - -=== Improvements - -1. **Update Examples**: Align example files with current parser syntax -2. **Grammar Refactoring**: Consider GLR parsing or more sophisticated conflict resolution -3. **Error Messages**: Improve parser error recovery and messaging - -=== Future Work - -1. Implement remaining language features (type checker, borrow checker) -2. Add WASM codegen -3. Implement effect system runtime - -== Appendix: Files Modified - -* `lib/dune` - Removed --explain flag -* `lib/parser.mly` - Fixed trait_ref, added trd_where -* `lib/ast.ml` - Added trd_where field -* `lib/parse.ml` - Removed WITH token -* `test/test_parser.ml` - Fixed pattern matching -* `examples/hello.affine` - Fixed effect syntax diff --git a/affinescript/docs/guides/TESTING-REPORT.scm b/affinescript/docs/guides/TESTING-REPORT.scm deleted file mode 100644 index be0004f..0000000 --- a/affinescript/docs/guides/TESTING-REPORT.scm +++ /dev/null @@ -1,287 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; AffineScript Testing Report - Machine-Readable Format -;; Generated: 2025-12-29 - -(testing-report - (metadata - (version "1.0.0") - (project "affinescript") - (project-version "0.1.0") - (test-date "2025-12-29") - (generated-by "automated-testing")) - - (environment - (language "OCaml") - (ocaml-version "5.x") - (ocaml-required ">= 5.1") - (dune-version "3.20.2") - (menhir-version ">= 20231231") - (platform "linux") - (architecture "x86_64")) - - (build-summary - (status pass) - (duration-seconds 45) - (issues-fixed 5) - (warnings 0)) - - (test-summary - (total-tests 74) - (passed 47) - (failed 27) - (skipped 0) - (pass-rate 63.5)) - - (test-suites - (suite - (name "Lexer") - (total 16) - (passed 16) - (failed 0) - (tests - (test (name "keywords") (status pass)) - (test (name "identifiers") (status pass)) - (test (name "literals") (status pass)) - (test (name "string-literal") (status pass)) - (test (name "string-escapes") (status pass)) - (test (name "operators") (status pass)) - (test (name "punctuation") (status pass)) - (test (name "row-variable") (status pass)) - (test (name "line-comment") (status pass)) - (test (name "block-comment") (status pass)) - (test (name "nested-comments") (status pass)) - (test (name "hex-literal") (status pass)) - (test (name "binary-literal") (status pass)) - (test (name "function-decl") (status pass)) - (test (name "total-function") (status pass)) - (test (name "type-decl") (status pass)))) - - (suite - (name "Parser") - (total 58) - (passed 31) - (failed 27) - (tests - ;; Passing tests - (test (name "literal-int") (status pass)) - (test (name "literal-float") (status pass)) - (test (name "literal-string") (status pass)) - (test (name "literal-bool") (status pass)) - (test (name "literal-unit") (status pass)) - (test (name "variable") (status pass)) - (test (name "binary-add") (status pass)) - (test (name "binary-precedence") (status pass)) - (test (name "binary-associativity") (status pass)) - (test (name "comparison") (status pass)) - (test (name "logical") (status pass)) - (test (name "unary-neg") (status pass)) - (test (name "unary-not") (status pass)) - (test (name "function-call") (status pass)) - (test (name "field-access") (status pass)) - (test (name "index-access") (status pass)) - (test (name "tuple") (status pass)) - (test (name "array") (status pass)) - (test (name "record") (status pass)) - (test (name "match-expr") (status pass)) - (test (name "lambda") (status pass)) - (test (name "let-stmt") (status pass)) - (test (name "let-mut-stmt") (status pass)) - (test (name "while-stmt") (status pass)) - (test (name "for-stmt") (status pass)) - (test (name "fn-decl-effect") (status pass)) - (test (name "struct-decl") (status pass)) - (test (name "enum-decl") (status pass)) - (test (name "type-alias") (status pass)) - (test (name "trait-decl") (status pass)) - (test (name "effect-decl") (status pass)) - ;; Failing tests - (test (name "if-expr") (status fail) - (issue "block-trailing-expression-conflict")) - (test (name "block") (status fail) - (issue "block-trailing-expression-conflict")) - (test (name "pattern-wildcard") (status fail) - (issue "block-parsing-conflict")) - (test (name "pattern-variable") (status fail) - (issue "block-parsing-conflict")) - (test (name "pattern-constructor") (status fail) - (issue "block-parsing-conflict")) - (test (name "pattern-tuple") (status fail) - (issue "block-parsing-conflict")) - (test (name "pattern-or") (status fail) - (issue "block-parsing-conflict")) - (test (name "fn-decl-simple") (status fail) - (issue "return-type-parsing")) - (test (name "fn-decl-total") (status fail) - (issue "return-type-parsing")) - (test (name "fn-decl-generic") (status fail) - (issue "return-type-parsing")) - (test (name "impl-block") (status fail) - (issue "impl-type-context")) - (test (name "impl-trait") (status fail) - (issue "impl-type-context")) - (test (name "type-simple") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-generic") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-tuple") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-function") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-record") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-row-poly") (status fail) - (issue "standalone-type-parsing")) - (test (name "type-ownership") (status fail) - (issue "standalone-type-parsing")) - (test (name "import-simple") (status fail) - (issue "import-parsing")) - (test (name "import-alias") (status fail) - (issue "import-parsing")) - (test (name "import-list") (status fail) - (issue "import-parsing")) - (test (name "import-glob") (status fail) - (issue "import-parsing")) - (test (name "fibonacci") (status fail) - (issue "block-expression-conflict")) - (test (name "linked-list") (status fail) - (issue "multiple-issues")) - (test (name "effect-handler") (status fail) - (issue "handler-syntax")) - (test (name "trait-bounds") (status fail) - (issue "where-clause-parsing"))))) - - (issues-fixed - (issue - (id "BUILD-001") - (severity error) - (type build) - (file "lib/dune") - (description "Menhir --explain flag deprecated in newer versions") - (fix "Removed --explain from menhir flags")) - - (issue - (id "BUILD-002") - (severity error) - (type type-error) - (file "lib/parser.mly") - (line 371) - (description "Double option wrapping in impl_trait_ref rule") - (fix "Removed Some wrapper from rule actions")) - - (issue - (id "BUILD-003") - (severity error) - (type unused-variable) - (file "lib/parser.mly") - (line 324) - (description "Unused variable where_clause in trait_decl") - (fix "Added trd_where field to AST and used variable")) - - (issue - (id "BUILD-004") - (severity error) - (type unbound-constructor) - (file "lib/parse.ml") - (line 94) - (description "Reference to undefined Token.WITH constructor") - (fix "Removed stray token mapping")) - - (issue - (id "BUILD-005") - (severity error) - (type pattern-match) - (file "test/test_parser.ml") - (description "Incomplete record patterns and non-returning code") - (fix "Added wildcard to patterns, parenthesized expressions"))) - - (parser-conflicts - (shift-reduce-states 23) - (reduce-reduce-states 2) - (shift-reduce-resolved 132) - (reduce-reduce-resolved 5) - (root-cause - "Block grammar allows list(stmt) followed by optional expr, " - "but expressions can start statements, causing ambiguity")) - - (cli-testing - (command "affinescript --help" (status pass)) - (command "affinescript lex examples/hello.affine" (status pass)) - (command "affinescript parse examples/hello.affine" (status pass) - (note "After fixing effect syntax"))) - - (example-files - (file - (name "hello.affine") - (status fixed) - (issue "Effect syntax mismatch") - (fix "Changed `-> () / IO` to `-{IO}-> ()`")) - - (file - (name "effects.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented")) - - (file - (name "ownership.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented")) - - (file - (name "traits.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented")) - - (file - (name "refinements.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented")) - - (file - (name "rows.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented")) - - (file - (name "vectors.affine") - (status needs-update) - (issues - "Uses aspirational syntax not yet implemented"))) - - (recommendations - (critical - (item - (priority 1) - (description "Fix block trailing expression parsing") - (approach "Refactor grammar to use lookahead or explicit return"))) - - (improvement - (item - (priority 2) - (description "Update example files to match current syntax")) - - (item - (priority 3) - (description "Improve parser error messages")) - - (item - (priority 4) - (description "Add more comprehensive test coverage")))) - - (files-modified - (file "lib/dune" - (changes "Removed --explain flag from menhir")) - (file "lib/parser.mly" - (changes "Fixed trait_ref, added trd_where usage")) - (file "lib/ast.ml" - (changes "Added trd_where field to trait_decl")) - (file "lib/parse.ml" - (changes "Removed WITH token mapping")) - (file "test/test_parser.ml" - (changes "Fixed pattern matching issues")) - (file "examples/hello.affine" - (changes "Fixed effect annotation syntax")))) diff --git a/affinescript/docs/guides/WHAT-MAKES-IT-BRILLIANT.adoc b/affinescript/docs/guides/WHAT-MAKES-IT-BRILLIANT.adoc deleted file mode 100644 index 0529988..0000000 --- a/affinescript/docs/guides/WHAT-MAKES-IT-BRILLIANT.adoc +++ /dev/null @@ -1,327 +0,0 @@ -== What Makes AffineScript Brilliant - -*AffineScript is a programming language that makes impossible errors -impossible.* - -If you’ve ever wondered "`Why can’t the compiler just catch this bug?`" -— AffineScript does. - -=== The Problem with Current Languages - -*TypeScript:* Great for shape-checking, but can’t prevent: - Memory -leaks (forgot to close that file?) - Array bounds errors (`+arr[999]+` -crashes at runtime) - Division by zero - Race conditions - -*Rust:* Catches all these, but the complexity is overwhelming: - -Lifetime annotations everywhere: -`+fn foo<'a, 'b: 'a>(x: &'a T, y: &'b U)+` - Borrow checker fights you -constantly - Steep learning curve - -*Haskell/OCaml:* Powerful type systems, but: - No automatic resource -management - Effects are awkward (`+IO monad+` confusion) - Hard to -reason about performance - -=== AffineScript’s Solution: Five Brilliant Ideas - -==== 1. Affine Types: Ownership Without the Pain - -*Rust’s problem:* - -[source,rust] ----- -fn process<'a>(file: &'a mut File) -> Result<(), Error> // Lifetime hell ----- - -*AffineScript:* - -[source,affinescript] ----- -fn process(file: ref File) -> Result[(), Error] // That's it. ----- - -*What it means:* - `+own File+` = You own it, must consume it (use once) -- `+ref File+` = You borrow it, can’t consume it (use many times) - *No -lifetime parameters, no `+'a+` soup* - -*Example - Can’t use after move:* - -[source,affinescript] ----- -fn close(file: own File) -> () { /* ... */ } - -let f = open("data.txt"); -close(f); // Consumes f -read(f); // ❌ COMPILE ERROR: f was moved! ----- - -*Brilliance:* Memory safety + resource safety, but *simpler than Rust*. - -''''' - -==== 2. Dependent Types: Prove Correctness at Compile Time - -*The dream:* Array access that can’t crash. - -*AffineScript makes it real:* - -[source,affinescript] ----- -type Vec[n: Nat, T] // Vector with length n encoded in type - -fn head[n: Nat, T](v: Vec[n+1, T]) -> T { - // Can ONLY call on non-empty vectors! - // head(empty_vec) = COMPILE ERROR -} ----- - -*What just happened?* - `+Vec[0, Int]+` = Empty vector (type says it has -0 elements) - `+Vec[5, String]+` = Vector with exactly 5 strings - -`+head()+` requires `+Vec[n+1, T]+` = At least 1 element - *Compiler -proves you never call head() on empty vectors* - -*More examples:* - -[source,affinescript] ----- -fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], - b: Vec[m, T] -) -> Vec[n+m, T] // Result length = sum of inputs (PROVEN!) - -fn take[n: Nat, k: Nat, T]( - v: Vec[n, T], - count: k where k <= n // Can't take more than exists! -) -> Vec[k, T] ----- - -*Brilliance:* Buffer overflows? Array bounds errors? *Impossible at -compile time.* - -''''' - -==== 3. Refinement Types: Runtime Constraints, Compile-Time Checked - -*The problem:* Division by zero crashes at runtime. - -*AffineScript solution:* - -[source,affinescript] ----- -fn div(a: Int, b: {x: Int where x != 0}) -> Int { - a / b // Safe! Compiler ensures b != 0 -} - -div(10, 5); // ✅ OK -div(10, 0); // ❌ COMPILE ERROR: 0 doesn't satisfy {x != 0} ----- - -*More powerful examples:* - -[source,affinescript] ----- -type Percentage = {x: Int where x >= 0 && x <= 100} -type NonEmpty[T] = {arr: [T] where len(arr) > 0} -type Sorted[T] = {arr: [T] where forall i, j. i < j => arr[i] <= arr[j]} - -fn first[T](arr: NonEmpty[T]) -> T // Can't call on empty arrays! -fn binarySearch[T](arr: Sorted[T], val: T) -> Option[Int] // Only works on sorted! ----- - -*Brilliance:* Turn runtime errors into *compile-time proofs*. - -''''' - -==== 4. Effect System: Track What Your Code Does - -*The problem:* Is this function pure? Does it do I/O? Can it throw? - -*In other languages:* 🤷 Read the docs and hope. - -*In AffineScript:* It’s *in the type signature*. - -[source,affinescript] ----- -fn compute(x: Int) -> Int / Pure -// Guaranteed: No I/O, no exceptions, no side effects - -fn readFile(path: String) -> String / IO + Exn[IOError] -// Guaranteed: Does I/O, might throw IOError - -fn parseJSON(s: String) -> Result[JSON, ParseError] / Pure -// Guaranteed: Pure (no side effects), returns Result instead of throwing ----- - -*What you gain:* - *Refactoring safety:* Change `+/ IO+` to `+/ Pure+`? -Compiler ensures you removed all I/O. - *Parallel execution:* Pure -functions can run in any order, any thread. - *Testability:* Pure -functions are trivial to test. - -*Example - Explicit effects:* - -[source,affinescript] ----- -fn processUser(id: Int) -> User / IO + Exn[DBError] { - let data = queryDB(id); // / IO + Exn[DBError] - let validated = validate(data); // / Pure - validated -} ----- - -*Brilliance:* *See what your code does* just by reading the type -signature. - -''''' - -==== 5. Row Polymorphism: Structural Typing Done Right - -*TypeScript problem:* Interfaces are nominal, not structural (mostly). - -*AffineScript solution:* Records are *truly structural*. - -[source,affinescript] ----- -fn getName(person: {name: String, ...rest}) -> String { - person.name -} - -getName({name: "Alice", age: 30}); // ✅ Works -getName({name: "Bob", role: "Engineer"}); // ✅ Works -getName({age: 30}); // ❌ Missing 'name' ----- - -*What’s `+...rest+`?* - "`I need a `+name: String+` field`" - "`I don’t -care what other fields exist`" - True *duck typing* with *compile-time -safety* - -*Advanced example:* - -[source,affinescript] ----- -fn updateAge[R](person: {age: Int, ...R}, newAge: Int) -> {age: Int, ...R} { - {...person, age: newAge} // Preserves all other fields! -} - -let alice = {name: "Alice", age: 30, city: "NYC"}; -let older = updateAge(alice, 31); -// older = {name: "Alice", age: 31, city: "NYC"} ✅ ----- - -*Brilliance:* *Flexibility of duck typing* + *safety of static types*. - -''''' - -=== The Magic: All Five Together - -Here’s a real-world example combining everything: - -[source,affinescript] ----- -// Affine types: file is owned, must be closed -fn withFile[T]( - path: String, - action: (ref File) -> Result[T, IOError] / IO // Effect tracking -) -> Result[T, IOError] / IO { - let file = open(path)?; // Affine: file is owned - let result = action(ref file); // Borrowing: action borrows file - close(file)?; // Consumes file (can't use after) - result -} - -// Dependent + Refinement types: safe array access -fn safeIndex[n: Nat, T]( - arr: Vec[n, T], - idx: {i: Int where i >= 0 && i < n} // Proven in bounds! -) -> T / Pure { - arr[idx] // Can NEVER crash! -} - -// Row polymorphism: flexible yet safe -fn logEvent[R]( - event: {timestamp: Int, level: String, ...R} -) -> () / IO { - println("[\(event.timestamp)] \(event.level)"); -} ----- - -*What you get:* - ✅ No resource leaks (affine types) - ✅ No array -bounds errors (dependent types) - ✅ No division by zero (refinement -types) - ✅ Clear side effects (effect system) - ✅ Flexible APIs (row -polymorphism) - -''''' - -=== Why Not Just Use [X]? - -[width="100%",cols="27%,34%,39%",options="header",] -|=== -|Language |What It Has |What It Lacks -|*TypeScript* |Easy to learn, great tooling |No resource safety, weak -type system - -|*Rust* |Memory safety, zero-cost abstractions |Lifetime hell, steep -learning curve - -|*Haskell* |Strong types, pure functions |Awkward effects (IO monad), -hard to learn - -|*OCaml* |Fast, great type inference |No automatic resource management - -|*F#* |Nice syntax, .NET integration |No affine types, no dependent -types - -|*Lean/Idris* |Dependent types, theorem proving |Too academic, slow, -tiny ecosystem -|=== - -*AffineScript:* Takes the best ideas from each, *makes them learnable*. - -''''' - -=== The Philosophy: Make Bugs Impossible - -Not "`catch bugs early`" — *prevent bugs from existing*. - -Traditional approach: 1. Write code 2. Run tests 3. Hope you covered all -edge cases 4. Deploy 5. User finds crash you didn’t test 6. 😭 - -AffineScript approach: 1. Write code 2. Compiler proves it’s correct 3. -Deploy 4. Users *cannot* trigger the impossible states 5. ✅ - -''''' - -=== Ready to Learn? - -Start with *link:lessons/01-hello-affinescript.md[Lesson 1: Hello -AffineScript]* - -Or try it now: *link:../../playground/test.html[Live Playground]* - -''''' - -=== Frequently Asked Questions - -*Q: Is this just academic theory?* A: No! The interpreter works today. -You can run real code in the browser playground. - -*Q: Will it be slow because of all these checks?* A: Most checks happen -at *compile time* (zero runtime cost). The few runtime checks -(refinements) are optional. - -*Q: Can I use it for real projects?* A: Not yet. Interpreter is 75% -complete, stdlib is 85% complete. Compiler is planned. Great for -learning and prototyping now. - -*Q: Is the syntax stable?* A: Yes! Parser and type-checker are 100% -complete. - -*Q: How hard is it to learn?* A: *Easier than Rust, harder than -TypeScript.* If you know any typed language, you can learn AffineScript. - -*Q: When will it be production-ready?* A: Aiming for compiler + complete -tooling in 2026. Follow progress at -https://github.com/hyperpolymath/affinescript[github.com/hyperpolymath/affinescript]. - -''''' - -*Next:* link:lessons/01-hello-affinescript.md[Lesson 1: Hello -AffineScript →] diff --git a/affinescript/docs/guides/frontier-guide.adoc b/affinescript/docs/guides/frontier-guide.adoc deleted file mode 100644 index b379c0f..0000000 --- a/affinescript/docs/guides/frontier-guide.adoc +++ /dev/null @@ -1,386 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript Frontier Guide: The Unveiling -Jonathan D.A. Jewell -2026-04-11 -:toc: -:icons: font -:source-highlighter: rouge - -The Frontier Guide takes you from "I write JavaScript / Python" to -"I understand why AffineScript's design choices produce better programs." -It is not a reference manual — it is an unveiling. - -Each section begins by naming a problem you already know, then shows exactly -how AffineScript solves it, then gives you something to try. - -== The Six Problems of X-Scripts - -Before AffineScript, there were many "improved" JS variants. Most of them -fixed one or two problems and ignored the rest. AffineScript's thesis is that -all six of these problems have the same root cause: **the runtime does not know -the program's intent** — so neither does the programmer. - -[cols="1,2,2"] -|=== -| Problem | Symptom | AffineScript answer - -| Null is a lie -| Two nothings: `null` and `undefined` -| `Option[T]` — exactly one nothing - -| Mutation is invisible -| Every variable is secretly mutable -| `own`/`ref`/`mut` — mutation is in the type - -| Errors throw sideways -| Untyped `throw` / `catch` anywhere -| `Result[T, E]` — errors in the return type - -| Async is bolted on -| Callbacks → Promises → async/await → signals -| Algebraic effects — one composable model - -| Types become unreadable -| TypeScript `infer`, conditional types, mapped types -| Row polymorphism — structural, principled, readable - -| Runtime is still JS -| Type erasure — guarantees vanish at run time -| Typed WASM (WasmGC) — types survive into execution -|=== - -Let us unveil each one. - -== Chapter 1: No Null - -JavaScript gives you two ways to say "nothing": `null` and `undefined`. -They behave differently. TypeScript adds `T | null | undefined` which -proliferates at every call site. - -AffineScript has **one** nothing. - -[source,affinescript] ----- -// Option[T] — present or absent, not three states: -let name: Option[String] = Some("Alice") -let missing: Option[String] = None - -// Pattern matching forces you to handle both cases: -fn greet(name: Option[String]) -> String { - match name { - Some(n) => "Hello, " ++ n ++ "!" - None => "Hello, stranger!" - } -} ----- - -The compiler rejects any code that uses a `Some` without matching on `None`. -There is no way to accidentally treat `None` as a value. - -=== Try it - -[source,affinescript] ----- -// Type-check this file and read the error: -fn greet_broken(name: Option[String]) -> String { - // Deliberate mistake: treating None as Some - name ++ "!" // ERROR: Option[String] is not String -} ----- - -Run: `affinescript check` — the error tells you exactly what went wrong and -on which line. - -== Chapter 2: Ownership (Mutation You Can See) - -In JavaScript, every object is a shared mutable reference by default. You -pass a value to a function; the function might mutate it; you have no idea. -You copy defensively. You introduce bugs through aliasing. - -AffineScript's type system makes mutation **visible in the type**: - -[source,affinescript] ----- -type Buffer = own { bytes: Array[Int], length: Int } - -// ref: read-only borrow — the caller keeps the buffer -fn size(buf: ref Buffer) -> Int { - buf.length -} - -// mut: mutable borrow — modifies in place, caller sees the change -fn append(buf: mut Buffer, byte: Int) -> () { - buf.bytes = buf.bytes ++ [byte]; - buf.length = buf.length + 1; -} - -// own: transfers ownership — caller cannot use buf after this call -fn into_bytes(buf: own Buffer) -> Array[Int] { - buf.bytes -} ----- - -The compiler tracks usage automatically. If you pass `buf` to `into_bytes`, -any later use of `buf` is a compile-time error. No runtime check needed. - -=== The quantity annotations - -For fine-grained control, the underlying quantity annotations are available: - -[source,affinescript] ----- -// @linear: must be used exactly once -fn consume_once(@linear handle: Int) -> () { () } - -// @erased: compile-time only (proof terms, type-class witnesses) -fn trust_me(@erased _proof: Bool) -> String { "I proved it" } - -// @unrestricted: unlimited use (the default for all values) -fn read_many(@unrestricted n: Int) -> Int { n + n } ----- - -=== Try it - -[source,affinescript] ----- -type File = own { fd: Int } - -fn open() -> own File { File { fd: 1 } } -fn read(f: ref File) -> String { "contents" } -fn close(f: own File) -> () { () } - -fn broken() -> String { - let file = open(); - close(file); // file is consumed here - read(file) // ERROR: file used after move -} ----- - -== Chapter 3: Errors in the Return Type - -JavaScript `throw` can happen from anywhere. TypeScript does not check it. -You write try/catch and hope. - -AffineScript errors live in the **return type**: - -[source,affinescript] ----- -// The function signature tells you everything: -fn parse_int(s: ref String) -> Result[Int, String] { - match s { - "0" => Ok(0) - "1" => Ok(1) - _ => Err("not an integer: " ++ s) - } -} - -// You must handle both cases — the compiler enforces it: -fn double_parsed(s: ref String) -> Result[Int, String] { - match parse_int(s) { - Ok(n) => Ok(n * 2) - Err(e) => Err(e) - } -} - -// The `?` operator propagates errors up automatically: -fn double_parsed_short(s: ref String) -> Result[Int, String] { - let n = parse_int(s)?; - Ok(n * 2) -} ----- - -There is no hidden throw path. Reading the signature tells you what can go wrong. - -=== Try it - -[source,affinescript] ----- -fn safe_head[T](xs: ref Array[T]) -> Result[ref T, String] { - // Complete this function: - // Return Ok(first element) if the array is non-empty, - // or Err("empty array") if it is empty. - Err("TODO") -} ----- - -== Chapter 4: Effects (One Model for Everything Impure) - -JavaScript's async history: callbacks (2009), Promises (2012), async/await -(2017), React Hooks (2019), signals (2023-). Each layer partially fixes -the previous one. They do not compose. - -AffineScript uses **algebraic effects** — one model for all of this: - -[source,affinescript] ----- -// Declare what the effect offers: -effect IO { - fn println(s: String); - fn read_line() -> String; -} - -effect Async { - fn await[T](promise: Promise[T]) -> T; - fn spawn[T](f: () -> T) -> Promise[T]; -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -// Declare which effects a function uses — no surprise impurity: -fn echo() -> () / IO { - let line = IO.read_line(); - IO.println("You said: " ++ line) -} - -// Compose effects naturally — they are rows: -fn fetch_and_log(url: ref String) -> Result[String, String] / IO + Async { - let response = Async.await(http_get(url)); - IO.println("fetched " ++ url); - Ok(response) -} ----- - -Because effects are types, the compiler rejects code that uses IO in a -context where IO was not declared. No accidental side effects. - -== Chapter 5: Row Polymorphism (Duck Typing with Proofs) - -TypeScript's structural typing is powerful but the inference gets complex. -AffineScript's row polymorphism is principled and readable: - -[source,affinescript] ----- -// This function works on any record that has at least `name: String`. -// Whatever other fields it has (captured by `..r`) are passed through. -fn greet[..r](entity: {name: String, ..r}) -> String { - "Hello, " ++ entity.name ++ "!" -} - -type Person = {name: String, age: Int} -type Company = {name: String, employees: Int, revenue: Float} - -// Both work — the row variable absorbs the extra fields: -greet(Person { name: "Alice", age: 30 }) -greet(Company { name: "Acme", employees: 100, revenue: 9999.0 }) ----- - -Row polymorphism is also how effects compose: - -[source,affinescript] ----- -// This function accepts any function that uses at least IO, -// plus any additional effects it may use (`..e`): -fn run_logged[..e](f: () -> () / IO + ..e) -> () / IO + ..e { - IO.println("start"); - f(); - IO.println("end"); -} ----- - -== Chapter 6: Typed WASM (Guarantees at Runtime) - -TypeScript erases all its types when it compiles to JavaScript. The runtime -has no idea what you proved. You rely on defensive checks, branded types, -and faith. - -AffineScript targets **Typed WebAssembly (WasmGC)**. The type guarantees -survive into the binary. The WASM runtime enforces them. - -This means: -- Your struct layout is what you declared — no fields added at runtime. -- Your variant tags are what the compiler assigned — no stringly-typed dispatch. -- Linear memory is managed by the AffineScript runtime, not the JS GC. -- Your ownership invariants hold at the machine level, not just in the source. - -[source,bash] ----- -# Compile to standard WASM: -affinescript compile hello.affine --output hello.wasm - -# Compile to WasmGC (struct.new, array.new_fixed, typed references): -affinescript compile-gc hello.affine --output hello.wasm - -# Run in Deno (which supports WasmGC natively): -deno run --allow-read runner.js ----- - -== Putting It Together: A Complete Program - -[source,affinescript] ----- -// complete_example.affine — a small resource manager - -effect IO { - fn println(s: String); -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -type Config = { max_retries: Int, timeout: Int } -type Request = own { url: String, config: ref Config } -type Response = { body: String, status: Int } -type FetchError = { message: String } - -// Open a request (produces a linear resource): -fn make_request(url: String, config: ref Config) -> own Request { - Request { url: url, config: config } -} - -// Send the request (consumes it — can't send twice): -fn send(req: own Request) -> Result[Response, FetchError] / IO { - IO.println("Sending request to " ++ req.url); - // Real implementation would use the Async effect - Ok(Response { body: "OK", status: 200 }) -} - -// The return type tells you: this can fail, and it does IO. -fn fetch_with_retry( - url: String, - config: ref Config -) -> Result[String, FetchError] / IO { - let req = make_request(url, config); - match send(req) { - Ok(resp) => - if resp.status == 200 { - Ok(resp.body) - } else { - Err({ message: "HTTP " ++ Int.to_string(resp.status) }) - } - Err(e) => Err(e) - } -} ----- - -== What to Read Next - -- link:warmup/README.adoc[Warmup scripts] — hands-on exercises, 15 minutes each -- link:../specs/SPEC.md[Language Specification] — the authoritative grammar and semantics -- link:../specs/SETTLED-DECISIONS.adoc[Settled Decisions] — architectural choices and why they were made -- link:../DESIGN-VISION.adoc[Design Vision] — the long view - -== Faces: Meeting Learners Where They Are - -If canonical AffineScript syntax feels unfamiliar, the `--face` flag lets -you write in a surface syntax closer to what you already know: - -[source,bash] ----- -# Python-style syntax — def, True/False/None, indentation: -affinescript --face python check my_program.py - -# Preview what the preprocessor produces from a Python-face file: -affinescript preview-python-transform my_program.py ----- - -Regardless of which face you use, the type checker sees canonical -AffineScript. Error messages are translated back to the face you chose, so -"Ownership error: single-use variable" appears instead of internal jargon. - -Additional faces (JS-face, Pseudocode-face, and others) are on the roadmap. -See link:../specs/faces.md[faces.md] for the architecture. diff --git a/affinescript/docs/guides/frontier-programming-practices/AI.a2ml b/affinescript/docs/guides/frontier-programming-practices/AI.a2ml deleted file mode 100644 index f3f2a4a..0000000 --- a/affinescript/docs/guides/frontier-programming-practices/AI.a2ml +++ /dev/null @@ -1,169 +0,0 @@ -(a2ml - (metadata - (name "Frontier Programming Practices in AffineScript — AI Edition") - (version "2.0.0") - (date "2026-04-10") - (audience "Bot / LLM / Agent") - (status "authoritative-scope-statement") - (supersedes "v1.0.0 (defensive draft)") - (companion "Human_Programming_Guide.adoc") - ) - - (thesis - (one-sentence - "AffineScript is JavaScript-ergonomic correctness: the developer experience of the -script family (TypeScript, ReScript, PureScript) raised to a higher correctness floor through a small, deliberately chosen set of advanced types, compiling to Typed WASM.") - (long-form - "AffineScript answers the question: can you keep the developer experience of modern JavaScript while raising correctness to the level of ML-family languages? The answer is yes — but only if the type system is narrowly scoped and ergonomically defaulted. The language copies the green-checkmark features from the -script lineage and adds affine types as the single novel contribution. It compiles to Typed WASM as the primary target. Refinement types are a soft commitment, limited to a decidable fragment. Dependent and tropical types live in the sibling Typed WASM project, not here.") - (design-principle "Ergonomics first. The type system defaults must be sensible so that most code reads like modern JavaScript while correctness is enforced at compile time. A feature earns its place only if it makes correct code easier to write, not harder.") - ) - - (in-scope - (copied-from-the-family - (feature (name "phantom-types") (source "purescript-rust-idris2") (cost "free-from-generics")) - (feature (name "immutable-by-default") (source "rescript-elm-grain") (cost "low")) - (feature (name "row-polymorphism") (source "purescript-elm") (cost "medium")) - (feature (name "full-type-inference") (source "hm-family") (cost "medium") - (note "types inferred fully; quantities inferred inside function bodies, annotated at function signatures")) - (feature (name "sound-type-system") (source "table-stakes") (cost "high-verification-work")) - (feature (name "effect-tracking") (source "novel-in-script-family") (cost "medium") - (note "effect types declared in function signatures; effect HANDLING is deferred — see out-of-scope")) - ) - (novel-contribution - (feature (name "affine-types") (source "the-headline") (cost "high") - (note "no -script language has affine types; this is AffineScript's distinguishing feature")) - ) - (soft-commitment - (feature (name "refinement-types-decidable") (source "rust-flux-adjacent") (cost "medium") - (note "restricted to decidable arithmetic and comparisons; compiler rejects what it cannot prove without an external solver")) - ) - ) - - (out-of-scope - (not-here-at-all - (feature (name "linear-strict-types") - (reason "subsumed-by-affine") - (bot-warning "The QTT semiring covers linear 'for free' as a byproduct of affine. This is NOT a reason to advertise linear types. Refuse any suggestion to add them as a surface feature.")) - (feature (name "unbounded-refinement-types") - (reason "decidability-boundary") - (note "refinement is in scope for decidable fragments only; no SMT solver required")) - (feature (name "algebraic-effect-handlers") - (reason "interaction-with-affine-is-unresolved") - (note "multi-shot resume of continuations that captured affine resources is a soundness hole; handlers are deferred until the design is explicit. Effect TRACKING — declaring what a function can do — is in scope. Effect HANDLING — intercepting and redirecting effects at runtime — is not.")) - ) - (deferred-to-sibling-project - (feature (name "full-dependent-types") - (destination "typed-wasm-project") - (note "previously crept into the AffineScript codebase via TDepArrow, NatVar, NatLit; being removed")) - (feature (name "tropical-types") - (destination "typed-wasm-project") - (note "parasitic on ordered monoid structure; needs phantom types as foundation; belongs where the numeric hierarchy lives")) - (feature (name "units-of-measure") - (destination "typed-wasm-project")) - (feature (name "shape-indexed-vectors") - (destination "typed-wasm-project")) - ) - ) - - (directives - (compile-target - (primary "typed-wasm") - (secondary "julia") - (rule "AffineScript is a compiled language. Its semantics live in .as source and compiled output. Do not rewrite .as source in another language as a shortcut for refactoring, modularization, or 'improvement'.")) - - (modularization - (rule "When asked to modularize or clean up code, operate within AffineScript using the module system, record decomposition (row polymorphism), and phantom type parameters. Do not create new compiler features to solve user-code modularization problems.")) - - (scope-discipline - (rule "Before proposing any feature addition, check the in-scope list. If the feature is not listed, it is out of scope. The 'since we already have X, we can trivially add Y' framing is a scope-creep tell and must be refused regardless of how cheap the addition appears.") - (why "Earlier sessions allowed significant scope creep: linear types, full dependent types, algebraic effect handlers were all added without being asked for. The narrow scope above is the corrected target. This document is the fix.")) - - (thesis-is-authoritative - (rule "This document is the scope statement for AffineScript. Code must conform to this document, not the reverse. If code says something this document does not, the code is wrong — not this document.") - (applies-to "any bot, any subagent, any earlier-session-version of any agent, and any human reader including the original author")) - - (interaction-escalation - (rule "When implementing a feature, if an interaction with another in-scope feature surfaces (affine+effects, phantom+refinement, row+inference, etc.), stop and surface the interaction for explicit user decision. Do not silently resolve interactions by picking the most convenient option.")) - - (typed-wasm-boundary - (rule "If proposed work properly belongs in the sibling Typed WASM project (numeric hierarchy, tropical semirings, dependent types, units of measure, shape-indexed vectors), flag it and do not add it to AffineScript. Typed WASM is a standalone project that AffineScript targets; it is not a subset of AffineScript.")) - ) - - (idiomatic-patterns - (note "Many features that appear 'missing' from AffineScript are expressible as combinations of in-scope features. Before proposing a new compiler feature, check whether the pattern can be written using phantom + affine + row + immutable + refinement + effect tracking. The following patterns are emergent, not implemented in the compiler.") - - (pattern - (name "branded-identifiers") - (composition "phantom + nominal aliases") - (purpose "Distinguish values that share a runtime representation but must not be confused (UserId vs ProductId, Email vs Username).") - (sketch "type UserId = Int\ntype ProductId = Int\nfn fetchUser(id: UserId): User")) - - (pattern - (name "type-state-machines") - (composition "phantom + affine") - (purpose "Enforce protocols where operations are only legal in certain states (connection open/closed, transaction active/committed).") - (sketch "type Conn<'state>\nfn open(addr: String): Conn\nfn close(c: Conn): Conn -- consumes c, cannot be used again")) - - (pattern - (name "capability-types") - (composition "phantom") - (purpose "Tag values with what operations they are authorized for; downgrade visible in types.") - (sketch "type Token<'cap>\nfn downgrade(t: Token): Token")) - - (pattern - (name "extensible-records") - (composition "row polymorphism") - (purpose "Functions operate on any record containing the required fields; no inheritance hierarchy needed.") - (sketch "fn damage(target: {health: Int, ..rest}, amount: Int): {health: Int, ..rest}")) - - (pattern - (name "invariant-preservation") - (composition "refinement types (decidable fragment)") - (purpose "Bake simple invariants into types so the compiler enforces them (non-negative, bounded, non-empty).") - (sketch "type PositiveInt = Int where (x >= 0)\nfn heal(h: PositiveInt, delta: PositiveInt): PositiveInt")) - - (pattern - (name "resource-lifecycle") - (composition "affine types") - (purpose "Ensure resources (file handles, sockets, unique entities) are used at most once or correctly disposed.") - (sketch "fn open(path: String): File\nfn read(f: File): (File, String)\nfn close(f: File): Unit")) - - (pattern - (name "trait-like-dispatch") - (composition "first-class records + row polymorphism") - (purpose "Type-class-style overloading without type classes: pass a dictionary of operations as a record; row inference specialises.") - (sketch "type Eq<'a> = {eq: ('a, 'a) -> Bool}\nfn contains<'a>(dict: Eq<'a>, list: List<'a>, target: 'a): Bool")) - - (pattern - (name "purity-tagging") - (composition "phantom + effect rows") - (purpose "Mark values or functions as pure or impure in the type system for optimisation or testing.") - (sketch "fn pure_compute(x: Int): Int // no effect row = pure\nfn io_op(): String / {IO} // effect row declares IO")) - ) - - (agent-behavior - (session-startup - (rule "Read this document at the start of every AffineScript session. It is the authoritative scope statement.")) - (read-first - (rule "Before editing any file in the AffineScript repository, read this document or verify its content matches your understanding of scope.")) - (defer-on-scope - (rule "When asked to add a feature not in the in-scope list, stop and ask the user before proceeding. Do not defer to what 'the code implies' or what 'an earlier session decided' — those are the failure modes that trashed the project before.")) - (no-silent-extension - (rule "Do not add compiler features to resolve user-code ergonomics problems. Use the existing type system more creatively instead, and document new patterns in the idiomatic-patterns section of this file.")) - (no-free-variant-additions - (rule "If you notice that the implementation 'already supports' a feature adjacent to one in scope (linear-because-affine, dependent-because-refinement, multi-shot-because-handlers), you may NOT expose that feature on the language surface without explicit user approval. Implementation support is not the same as user-facing scope.")) - (escalate-on-interaction - (rule "When two in-scope features interact in a way that forces a design decision, stop and surface the interaction for explicit user decision.")) - (typed-wasm-boundary - (rule "Work that belongs in the Typed WASM project goes to the Typed WASM project. Never add it to AffineScript as a shortcut.")) - (honesty-in-documentation - (rule "STATE.a2ml, README, and related docs must reflect what the code actually enforces, not what it aspires to. A 'dune build passing' is not a behavioural test and does not count as 'feature complete'.")) - ) - - (relationship-with-typed-wasm-project - (role "AffineScript compiles to Typed WASM as its primary target.") - (shared-infrastructure "phantom types — the bridging abstraction. The same phantom mechanism that tags UserId vs ProductId in AffineScript will tag Meter vs Second and Tropical vs Standard in Typed WASM.") - (independence "Typed WASM is designed as a legitimate compile target for languages beyond AffineScript. It is not an AffineScript subsystem. Other languages can and should target it.") - (deferred-features "dependent types, tropical types, units of measure, shape-indexed vectors, and any advanced type-level computation live in Typed WASM, not in AffineScript.") - (secondary-backend "Julia — secondary compile target for scientific computing use cases where AffineScript's type safety complements Julia's numeric ecosystem.") - ) -) diff --git a/affinescript/docs/guides/frontier-programming-practices/Human_Programming_Guide.adoc b/affinescript/docs/guides/frontier-programming-practices/Human_Programming_Guide.adoc deleted file mode 100644 index 9cf5b1e..0000000 --- a/affinescript/docs/guides/frontier-programming-practices/Human_Programming_Guide.adoc +++ /dev/null @@ -1,353 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript: Frontier Programming Practices -Human Edition, v2.0 -:sectnums: -:toc: left -:icons: font -:source-highlighter: rouge -:revdate: 2026-04-10 - -[NOTE] -==== -This guide supersedes the v1.0 defensive draft. It describes what AffineScript *is* and how to use it well, rather than warning what not to do. Its machine-readable companion is `AI.a2ml` in this same folder — read by bots at session start. -==== - -== The Thesis - -AffineScript is **JavaScript-ergonomic correctness**. - -It takes the developer experience of the `-script` family — TypeScript, ReScript, PureScript — and raises the floor on correctness through a small, deliberately chosen set of advanced types. It compiles to **Typed WASM** for performance, portability, and safety. - -The design principle is ergonomics first: the type system's defaults are sensible, so most code reads like modern JavaScript. Correctness is enforced at compile time rather than hoped for at runtime. - -The language is small on purpose. Every feature in it was chosen for a specific reason, after looking at what the rest of the `-script` family already does well and what nobody in that family has tried yet. - -== What's In the Box - -AffineScript ships with eight type-system features, grouped by role: - -=== The Six Copied from the Family - -These are features that other sound `-script` languages already have. AffineScript copies them because they work. - -* **Phantom types** — types that exist only at compile time. Distinguish `UserId` from `ProductId` so you can't mix them up by accident. -* **Immutable by default** — `let x = 5` binds a value, not a variable. Use `let mut` when you want mutation. Matches `const` in modern JavaScript. -* **Row polymorphism** — functions operate on any record containing the fields they need. No inheritance hierarchy required. -* **Full type inference** — types are inferred; you rarely annotate them. You do annotate quantities (the affine part) at function signatures, but not inside function bodies. -* **Sound type system** — if the type checker says your code is fine, it really is. No `any`, no unsafe escape hatches, no runtime surprises from type mistakes. -* **Effect tracking** — functions declare which effects they perform (IO, mutation, failure). Callers see at a glance what a function can do. - -=== The One Novel Contribution - -* **Affine types** — values that can be used *at most once*. Perfect for resources: file handles, network sockets, unique game entities, exchange tokens. "Use after close" becomes a compile error, not a runtime crash. - -=== The Soft Commitment - -* **Refinement types** (decidable fragment) — types that carry simple invariants: "this `Int` is always non-negative," "this array index is in bounds." Restricted to what the compiler can prove quickly without calling out to an SMT solver. - -== What's Deliberately Not in the Box - -Because AffineScript aims at ergonomic correctness — not research-grade expressiveness — several powerful features are left out on purpose: - -* **Linear (strict exactly-once) types** — subsumed by affine. Use affine instead. -* **Full dependent types** — too expensive for daily use. They live in the sibling Typed WASM project, not here. -* **Tropical types, units of measure, shape-indexed vectors** — useful, but they belong in Typed WASM where the numeric hierarchy and decidable type-level arithmetic live. -* **Full algebraic effect handlers** — effect *tracking* is in; effect *handling* (intercepting and redirecting effects at runtime with multi-shot resume) is deferred until its interaction with affine types is resolved. -* **Unbounded refinement** — refinement is restricted to what the compiler can prove without calling an external solver. - -If you want any of those, the Typed WASM project is where they live or will live. AffineScript compiles *to* Typed WASM, so the two projects are complementary, not competing. - -== The Type System in Practice - -=== Affine Types for Resources - -Values you want to use at most once: - -[source,affinescript] ----- -fn open(path: String): File -fn read(f: File): (File, String) // returns the file back so you can keep using it -fn close(f: File): Unit // consumes the file; cannot be used after - -fn good(path: String): String { - let f = open(path); - let (f, contents) = read(f); - close(f); - contents -} - -fn bad(path: String): String { - let f = open(path); - let (_, contents) = read(f); - close(f); // COMPILE ERROR: f was already consumed by read - contents -} ----- - -The compiler tracks resource lifetimes and refuses to compile code that uses a file after it's been consumed or forgets to close it. - -=== Row Polymorphism for Extensible Records - -Functions operate on any record containing the fields they need, without inheritance or classes: - -[source,affinescript] ----- -fn damage(target: {health: Int, ..rest}, amount: Int): {health: Int, ..rest} { - { ..target, health: target.health - amount } -} - -let player = {health: 100, name: "Alice", level: 5}; -let damaged = damage(player, 10); -// damaged has type {health: Int, name: String, level: Int} -// The function doesn't know or care about `name` and `level` — it passes them through. ----- - -`..rest` is a row variable: "the rest of the fields, whatever they are." The compiler infers it and threads it through. - -=== Refinement Types for Invariants - -Bake invariants into types so the compiler enforces them: - -[source,affinescript] ----- -type PositiveInt = Int where (x >= 0); - -fn heal(h: PositiveInt, delta: PositiveInt): PositiveInt { - h + delta -} - -let hp: PositiveInt = 100; -let after = heal(hp, 25); // ok -let broken = heal(hp, -10); // COMPILE ERROR: -10 is not PositiveInt ----- - -Refinements are restricted to the decidable fragment (arithmetic, comparisons, boolean combinations) so the compiler can prove things without needing an SMT solver. If you write a refinement the compiler can't decide, it tells you so and asks you to narrow it. - -=== Phantom Types for Compile-Time Distinctions - -Use the type system to distinguish values that look the same at runtime: - -[source,affinescript] ----- -type UserId = Int; -type ProductId = Int; - -fn fetchUser(id: UserId): User -fn fetchProduct(id: ProductId): Product - -let u: UserId = 42; -let p: ProductId = 99; - -fetchUser(u); // ok -fetchUser(p); // COMPILE ERROR: expected UserId, got ProductId ----- - -Zero runtime cost. The distinction exists only in the type checker. A Rust user would recognise this as "newtype"; a Haskell user as "phantom"; a TypeScript user as "branded types done properly." - -=== Immutable by Default - -`let` binds an immutable value. `let mut` binds a mutable one. - -[source,affinescript] ----- -let x = 5; -x = 6; // COMPILE ERROR: x is immutable - -let mut counter = 0; -counter = counter + 1; // ok ----- - -Matches `const` in JavaScript and `let` in ReScript — familiar to most developers. - -=== Effect Tracking - -Functions declare what effects they can perform: - -[source,affinescript] ----- -fn pureCompute(x: Int): Int // no effect row = pure -fn readFile(path: String): String / {IO} // may perform IO -fn maybeParse(s: String): Int / {Fail} // may fail -fn runScript(path: String): Int / {IO, Fail} // both ----- - -Callers see at a glance what a function can do. The compiler tracks effect propagation so a "pure" function can't accidentally call an impure one without declaring it. - -NOTE: Effect *tracking* is in. Effect *handling* (intercepting effects at runtime) is not — that's a separate design problem that hasn't been solved in a way that's compatible with affine types. - -== Idiomatic Patterns - -AffineScript has a small feature set chosen carefully. Many advanced type-level patterns emerge *for free* by combining existing features — you don't need to wait for the compiler to add them. - -=== Type-State Machines (phantom + affine) - -Model state transitions at the type level: - -[source,affinescript] ----- -type Connection; -type Open; -type Closed; - -fn connect(addr: String): Connection -fn send(c: Connection, data: String): Connection -fn close(c: Connection): Connection - -// Compiler refuses to send() on a Connection. -// Compiler refuses to close() twice because Connection is consumed. ----- - -This is the resource-lifecycle pattern from the old guide, expressed cleanly using two in-scope features. - -=== Capability Types (phantom) - -Tag values with what they're authorized to do: - -[source,affinescript] ----- -type Token; -type Admin; -type User; - -fn downgrade(t: Token): Token -fn dangerousOp(t: Token): Result -fn safeOp(t: Token): Result ----- - -Callers must hold the right capability. Downgrades are visible in the type signature. - -=== Branded Identifiers (phantom + nominal aliases) - -Avoid mixing up identifiers that share a runtime type: - -[source,affinescript] ----- -type UserId = Int; -type OrderId = Int; -type Email = String; - -// Cannot accidentally pass a UserId where an OrderId is expected. ----- - -=== Trait-Like Dispatch (records + rows) - -AffineScript doesn't have type classes, but you can build equivalent functionality using records and row polymorphism: - -[source,affinescript] ----- -type Eq = {eq: (a, a) -> Bool}; - -fn contains(dict: Eq, list: List, target: a): Bool { - // uses dict.eq to compare elements -} - -let intEq: Eq = {eq: \(a, b) -> a == b}; -contains(intEq, [1, 2, 3], 2); // true ----- - -Dictionary passing gives you overloading and type-class-like dispatch without the compiler needing to support them natively. - -=== Purity Tagging (phantom + effect rows) - -Use effects as compile-time tags for purity: - -[source,affinescript] ----- -fn pureCompute(x: Int): Int { x * 2 } // no effect row, pure -fn ioOp(): String / {IO} { ... } // tagged IO ----- - -A function without an effect row is pure. Tests can rely on pure functions being deterministic. Impure functions can't be called from pure contexts without propagating the effect. - -=== Resource Pools (affine + higher-order functions) - -Manage finite resources with compile-time checks: - -[source,affinescript] ----- -fn withFile(path: String, action: File -> (File, r)): r { - let f = open(path); - let (f, result) = action(f); - close(f); - result -} - -let contents = withFile("data.txt", \f -> read(f)); ----- - -The caller never sees the raw `File` and can't forget to close it. - -== The Compilation Target - -AffineScript compiles to **Typed WASM** as its primary target. WASM gives you: - -* Near-native performance -* Portability — browser, server, edge, embedded -* Strong isolation — sandboxed by default -* A stable, standardised, evolving platform - -**Typed WASM** is a sibling project: it defines a richer type system on top of WebAssembly. AffineScript is one of its consumers, but Typed WASM is designed as a general-purpose compile target — other languages can use it too. - -A **Julia** backend exists as a secondary target for scientific computing use cases where you want AffineScript's type safety alongside Julia's numeric ecosystem. - -=== The WASM Co-processor Model - -In the intended deployment, AffineScript serves as the "brain" — logic, physics, math, state machines — while the frontend (Gossamer, Deno, browser JS) serves as the "senses" — rendering, input, network. The two communicate by passing raw primitives (Ints, Floats, Arrays) across the WASM boundary. - -[source,text] ----- -┌────────────────────┐ primitives ┌────────────────────┐ -│ Frontend (JS/TS) │ ←──────────→ │ AffineScript │ -│ Gossamer / Deno │ over WASM │ logic + physics │ -│ (rendering/input) │ boundary │ (brain) │ -└────────────────────┘ └────────────────────┘ ----- - -The AffineScript side is where the correctness guarantees live: affine resource tracking, row-polymorphic domain models, refinement-typed invariants. The frontend side is where the ergonomics of JS are most useful: DOM, WebGL, input handling. - -== Design Principles - -=== Ergonomics Over Expressiveness - -Some powerful type-system features are deliberately excluded because they come at high ergonomic cost. The question we ask for every feature is: *does it make correct code easier to write, or does it make the language harder to use?* If the answer is the latter, it doesn't go in. - -=== Copy What Works - -Six of the eight features are copied from `-script` languages that have already proven the design in production. We're not inventing new type-system machinery where existing machinery works. The novelty budget is spent on **affine types**, which no `-script` has. - -=== Small, Chosen Feature Set - -We don't add features because they might be useful someday. We add them when there's a concrete use case that can't be expressed any other way. Many patterns that look like "missing features" turn out to be expressible using existing features — see the Idiomatic Patterns section. - -=== Scope Discipline - -The scope of AffineScript lives in this document and its machine-readable companion `AI.a2ml`. Any feature not listed there is out of scope. If a collaborator (human, bot, or earlier-you) suggests adding a feature, the first question is: *is it in the scope document?* If not, the scope document is the source of truth — not the code. - -This sounds bureaucratic; it isn't. It's the one discipline that stops a language from drifting into accidental complexity. - -=== Honest Status - -Completion claims must reflect what the code actually enforces, not what it aspires to. "The type checker compiles" is not the same as "the type checker enforces the advertised rules on user programs." STATE.a2ml and related docs split those two things explicitly. - -== Getting Help - -* **Scope question?** Check `AI.a2ml` (machine-readable) and this document. They are the source of truth. -* **Confusing type error?** The compiler tries to give source location, the offending usage, and a suggested fix. If an error is unhelpful, file it — error messages are part of the correctness story. -* **Pattern unclear?** See the Idiomatic Patterns section. If your use case isn't covered, propose it. -* **"Is this idiomatic?"** If it uses in-scope features and compiles cleanly, it's legitimate AffineScript. Idiomatic style emerges from practice; the patterns here are starting points, not the only ways. - -== The Typed WASM Project - -If you need features that aren't in AffineScript — dependent types, tropical types, units of measure, shape-indexed arrays — the Typed WASM project is where they live (or will live). - -* **Typed WASM** is a compile target, not a language. It provides a richer type layer on top of WebAssembly. -* **AffineScript** compiles to Typed WASM. -* **Other languages** can also compile to Typed WASM; it's designed as a general-purpose target. - -The split is deliberate: AffineScript is the ergonomic, correctness-focused surface language; Typed WASM is the powerful, language-agnostic type layer that any language can target. - -[TIP] -==== -If you find yourself wanting to add a research-grade type feature to AffineScript, stop and ask: *does this belong in AffineScript, or in Typed WASM?* The answer, most of the time, is Typed WASM. -==== diff --git a/affinescript/docs/guides/lessons/01-hello-affinescript.adoc b/affinescript/docs/guides/lessons/01-hello-affinescript.adoc deleted file mode 100644 index 5ef2b00..0000000 --- a/affinescript/docs/guides/lessons/01-hello-affinescript.adoc +++ /dev/null @@ -1,373 +0,0 @@ -== Lesson 1: Hello AffineScript - -*Goal:* Write your first AffineScript program and understand basic -syntax. - -*Time:* 15 minutes - -''''' - -=== Your First Program - -Let’s start with the traditional "`Hello, World!`": - -[source,affinescript] ----- -println("Hello, AffineScript!"); ----- - -*Try it now:* Open the link:../../../playground/test.html[Playground] -and run this code! - -*What happened?* - `+println+` is a built-in function that prints text - -Strings use double quotes: `+"text here"+` - Semicolons are optional at -end of expressions (but recommended in statements) - -''''' - -=== Simple Arithmetic - -AffineScript is great at math: - -[source,affinescript] ----- -// Addition -10 + 20 - -// Multiplication and subtraction -5 * 6 - 2 - -// Division -100 / 4 - -// Order of operations works as expected -10 + 20 * 3 // Result: 70 (not 90!) - -// Use parentheses to change order -(10 + 20) * 3 // Result: 90 ----- - -*Try it:* Type each expression in the playground and see the results. - -''''' - -=== Variables: Naming Your Values - -Use `+let+` to give names to values: - -[source,affinescript] ----- -let x = 42; -let y = 10; -let z = x + y; - -println(z); // Prints: 52 ----- - -*Rules for variable names:* - Start with a letter (lowercase for -variables) - Can contain letters, numbers, underscores - Cannot be -keywords (`+let+`, `+fn+`, `+if+`, etc.) - -*Examples:* - -[source,affinescript] ----- -let age = 25; -let user_count = 100; -let total2023 = 50000; ----- - -❌ *Don’t do this:* - -[source,affinescript] ----- -let 2fast = 10; // ❌ Can't start with number -let my-var = 5; // ❌ Hyphens not allowed (use underscore) -let let = 10; // ❌ 'let' is a keyword ----- - -''''' - -=== Types: What Kind of Value? - -Every value has a *type*. AffineScript knows these automatically: - -[source,affinescript] ----- -let age = 30; // Type: Int (integer number) -let price = 19.99; // Type: Float (decimal number) -let name = "Alice"; // Type: String (text) -let isActive = true; // Type: Bool (true or false) ----- - -You can *explicitly* write types: - -[source,affinescript] ----- -let age: Int = 30; -let price: Float = 19.99; -let name: String = "Alice"; -let isActive: Bool = true; ----- - -*When to write types?* - Usually not needed (compiler infers them) - -Write them when you want to be explicit - Required in function -parameters (next lesson!) - -''''' - -=== Comments: Notes for Humans - -Use `+//+` for single-line comments: - -[source,affinescript] ----- -// This is a comment - the computer ignores it -let x = 10; // Comments can go at the end of lines too - -// Use comments to explain WHY, not WHAT: -// Good: "Add tax because customers in CA pay 8.5%" -// Bad: "Add 8.5 to the price" ----- - -''''' - -=== Expressions vs Statements - -*Expression:* Something that produces a value - -[source,affinescript] ----- -10 + 20 // Expression: produces 30 -5 * 6 // Expression: produces 30 -x + y // Expression: produces sum of x and y ----- - -*Statement:* An action or instruction - -[source,affinescript] ----- -let x = 10; // Statement: creates a variable -println("Hi"); // Statement: prints text ----- - -*Key insight:* In AffineScript, almost everything is an expression! - -[source,affinescript] ----- -let x = if true { 10 } else { 20 }; // 'if' is an expression! ----- - -More on this in later lessons. - -''''' - -=== Your Turn: Exercises - -Try these in the playground: - -==== Exercise 1: Calculate Your Age in Days - -[source,affinescript] ----- -let age = 25; // Change to your age -let days = age * 365; -println(days); ----- - -==== Exercise 2: Temperature Converter - -[source,affinescript] ----- -let celsius = 25; -let fahrenheit = celsius * 9 / 5 + 32; -println(fahrenheit); ----- - -==== Exercise 3: Circle Area - -[source,affinescript] ----- -let radius = 5; -let pi = 3.14159; -let area = pi * radius * radius; -println(area); ----- - -==== Exercise 4: Shopping Cart - -[source,affinescript] ----- -let item1 = 19.99; -let item2 = 5.50; -let item3 = 12.00; -let subtotal = item1 + item2 + item3; -let tax = subtotal * 0.08; // 8% tax -let total = subtotal + tax; -println(total); ----- - -''''' - -=== Common Beginner Mistakes - -==== Mistake 1: Forgetting `+let+` - -[source,affinescript] ----- -x = 10; // ❌ ERROR: Variable not declared ----- - -*Fix:* - -[source,affinescript] ----- -let x = 10; // ✅ Declare with 'let' ----- - -==== Mistake 2: Reusing variable names (for now) - -[source,affinescript] ----- -let x = 10; -let x = 20; // ⚠️ Shadowing (advanced topic) ----- - -*Better:* - -[source,affinescript] ----- -let x = 10; -let y = 20; // Use different names for now ----- - -==== Mistake 3: Mixing types in arithmetic - -[source,affinescript] ----- -let x = 10; -let y = "20"; -let z = x + y; // ❌ ERROR: Can't add Int and String ----- - -*Fix:* - -[source,affinescript] ----- -let x = 10; -let y = 20; // Both Int -let z = x + y; // ✅ Works! ----- - -''''' - -=== What You Learned - -✅ *How to write and run AffineScript code* ✅ *Basic arithmetic* -(`+++`, `+-+`, `+*+`, `+/+`) ✅ *Variables* with `+let+` ✅ *Types* -(`+Int+`, `+Float+`, `+String+`, `+Bool+`) ✅ *Comments* with `+//+` ✅ -*Expressions vs statements* - -''''' - -=== Quiz Yourself - -*Question 1:* What does this print? - -[source,affinescript] ----- -let x = 5; -let y = x * 2; -println(y); ----- - -Answer - -10 - -*Question 2:* What’s wrong with this code? - -[source,affinescript] ----- -age = 30; -println(age); ----- - -Answer - -Missing `+let+` before `+age+`. Should be: `+let age = 30;+` - -*Question 3:* What’s the type of `+x+`? - -[source,affinescript] ----- -let x = 3.14; ----- - -Answer - -`+Float+` (decimal number) - -''''' - -=== Next Steps - -*Ready for more?* → link:02-functions-and-patterns.md[Lesson 2: -Functions and Pattern Matching] - -*Want to understand why AffineScript is special?* → -link:../WHAT-MAKES-IT-BRILLIANT.md[What Makes It Brilliant] - -*Just want to play?* → link:../../../playground/test.html[Playground] - -''''' - -=== Playground Example - -Copy this complete example to the playground: - -[source,affinescript] ----- -// Lesson 1: Complete Example -// Calculate total price with tax and discount - -let basePrice = 100.0; -let taxRate = 0.08; // 8% -let discountRate = 0.15; // 15% off - -// Calculate discount -let discount = basePrice * discountRate; -let priceAfterDiscount = basePrice - discount; - -// Calculate tax -let tax = priceAfterDiscount * taxRate; - -// Final total -let total = priceAfterDiscount + tax; - -// Show results -println("Base price: "); -println(basePrice); -println("After discount: "); -println(priceAfterDiscount); -println("Tax: "); -println(tax); -println("Final total: "); -println(total); ----- - -*Expected output:* - -.... -Base price: 100.0 -After discount: 85.0 -Tax: 6.8 -Final total: 91.8 -.... - -''''' - -*🎉 Congratulations!* You’ve completed Lesson 1! - -*Next:* link:02-functions-and-patterns.md[Lesson 2: Functions and -Pattern Matching →] diff --git a/affinescript/docs/guides/lessons/README.adoc b/affinescript/docs/guides/lessons/README.adoc deleted file mode 100644 index d079c0f..0000000 --- a/affinescript/docs/guides/lessons/README.adoc +++ /dev/null @@ -1,141 +0,0 @@ -== AffineScript in 10 Lessons - -*Learn AffineScript from zero to hero in 10 progressive lessons.* - -Each lesson builds on the previous one. Complete them in order for the -best experience. - -''''' - -=== Course Overview - -[width="100%",cols="29%,24%,20%,27%",options="header",] -|=== -|Lesson |Topic |Time |Status -|link:01-hello-affinescript.md[1] |*Hello AffineScript* - Variables, -types, arithmetic |15 min |✅ Complete - -|2 |*Functions & Pattern Matching* - Define functions, match expressions -|20 min |🔄 In Progress - -|3 |*Affine Types Basics* - Ownership, move semantics |25 min |📝 -Planned - -|4 |*Borrowing & References* - `+own+` vs `+ref+` keywords |25 min |📝 -Planned - -|5 |*Dependent Types* - Length-indexed vectors, type-level computation -|30 min |📝 Planned - -|6 |*Refinement Types* - Compile-time constraints, safe division |25 min -|📝 Planned - -|7 |*Effect System* - Tracking I/O, exceptions, purity |30 min |📝 -Planned - -|8 |*Row Polymorphism* - Structural typing, flexible records |25 min |📝 -Planned - -|9 |*Error Handling* - `+Option+`, `+Result+`, try/catch |30 min |📝 -Planned - -|10 |*Building Real Programs* - Putting it all together |40 min |📝 -Planned -|=== - -*Total time:* ~4.5 hours - -''''' - -=== Prerequisites - -* Basic programming experience (any language) -* Familiarity with variables, functions, types -* No prior functional programming knowledge required - -''''' - -=== How to Use These Lessons - -==== Option 1: Read Online - -Each lesson is a markdown file you can read on GitHub. - -==== Option 2: Interactive Playground - -[arabic] -. Open the link:../../../playground/test.html[Playground] -. Load the lesson example (e.g., `+examples/lessons/01_hello.affine+`) -. Follow along by running code as you read - -==== Option 3: Clone and Experiment - -[source,bash] ----- -git clone https://github.com/hyperpolymath/affinescript -cd affinescript -./affinescript examples/lessons/01_hello.affine ----- - -''''' - -=== What You’ll Learn - -By the end of this course, you’ll understand: - -✅ *Basic Syntax* - Variables, functions, types ✅ *Affine Types* - -Resource safety without complexity ✅ *Dependent Types* - Compile-time -correctness proofs ✅ *Refinement Types* - Constrained values (non-zero, -positive, etc.) ✅ *Effect System* - Track side effects in types ✅ *Row -Polymorphism* - Flexible structural typing ✅ *Error Handling* - Option, -Result, exceptions - -And you’ll be able to: - Write type-safe, memory-safe programs - Prove -correctness at compile time - Build real applications with confidence - -''''' - -=== Learning Path - -==== Beginner Track (Lessons 1-2) - -Start here if you’re new to AffineScript or typed languages. - -==== Intermediate Track (Lessons 3-6) - -Core type system features. This is where AffineScript shines. - -==== Advanced Track (Lessons 7-10) - -Real-world programming with effects, errors, and composition. - -''''' - -=== Additional Resources - -* *link:../WHAT-MAKES-IT-BRILLIANT.md[What Makes AffineScript -Brilliant]* - Why learn this language? -* *link:../../specs/affinescript-spec.md[Language Specification]* - -Complete formal spec -* *link:../../../playground/test.html[Playground]* - Try it in your -browser -* *link:../../../examples/[Examples]* - More code samples - -''''' - -=== Get Help - -* *Questions?* Open an issue on -https://github.com/hyperpolymath/affinescript/issues[GitHub] -* *Found a bug in lessons?* PRs welcome! -* *Want to contribute?* See -link:../../../CONTRIBUTING.md[CONTRIBUTING.md] - -''''' - -=== Start Learning! - -*Ready?* → link:01-hello-affinescript.md[Lesson 1: Hello AffineScript →] - -*Not sure if this is for you?* → link:../WHAT-MAKES-IT-BRILLIANT.md[What -Makes It Brilliant] diff --git a/affinescript/docs/guides/warmup/01_basics.affine b/affinescript/docs/guides/warmup/01_basics.affine deleted file mode 100644 index 469aad6..0000000 --- a/affinescript/docs/guides/warmup/01_basics.affine +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Warmup 01: Basic AffineScript — let bindings, literals, functions -// -// Try running each section with: -// affinescript eval 01_basics.affine -// -// The goal is to get your fingers used to AffineScript syntax before -// ownership and effects are introduced. - -// ── Literals ────────────────────────────────────────────────────────────────── - -let answer: Int = 42 -let pi: Float = 3.14159 -let greeting: String = "Hello, AffineScript" -let am_i_learning: Bool = true -let nothing: () = () // Unit — the absence of a value - -// ── Let bindings are immutable by default ───────────────────────────────────── - -let x = 10 -let y = x + 5 // 15 — pure computation, no side effects yet - -// ── Functions ───────────────────────────────────────────────────────────────── - -// A simple function: -fn add(a: Int, b: Int) -> Int { - a + b -} - -// Functions are values — you can store them in let bindings: -let double = fn(n: Int) -> Int { n * 2 } - -// Generic functions work the same way: -fn identity[T](x: T) -> T { - x -} - -// ── Pattern matching ────────────────────────────────────────────────────────── - -fn describe_number(n: Int) -> String { - match n { - 0 => "zero" - 1 => "one" - _ => "many" - } -} - -// ── Exercise 1: write a function that returns the absolute value of an Int ──── -// fn abs(n: Int) -> Int { ... } - -// ── Exercise 2: write a generic function `const_fn` that ignores its second -// argument and always returns the first ───────────────────────────────────── -// fn const_fn[A, B](a: A, _b: B) -> A { ... } diff --git a/affinescript/docs/guides/warmup/02_ownership.affine b/affinescript/docs/guides/warmup/02_ownership.affine deleted file mode 100644 index 6f340f5..0000000 --- a/affinescript/docs/guides/warmup/02_ownership.affine +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Warmup 02: Ownership and affine types -// -// AffineScript's key insight: resources can only be used once by default. -// This lets the compiler prove your code is memory-safe without a GC. -// -// Run: affinescript check 02_ownership.affine - -// ── own: this value is a unique resource ────────────────────────────────────── -// -// "own" is surface syntax for the @linear quantity annotation. -// An own value must be consumed exactly once. - -type File = own { fd: Int } - -// Consuming a resource (takes ownership, caller can't use it after): -fn close(file: own File) -> () { - () // Implementation would close the fd -} - -// ── ref: borrow without consuming ───────────────────────────────────────────── -// -// A ref borrow lets you inspect a resource without consuming it. -// The original owner keeps the value after the call returns. - -fn read_fd(file: ref File) -> Int { - file.fd -} - -// ── mut: mutable borrow ─────────────────────────────────────────────────────── -// -// A mut borrow lets you modify the value in-place. -// Only one mut borrow can exist at a time (enforced by the borrow checker). - -fn set_fd(file: mut File, new_fd: Int) -> () { - file.fd = new_fd -} - -// ── @linear / @erased / @unrestricted (the full syntax) ───────────────────── -// -// own/ref/mut are ergonomic aliases. The underlying quantity annotations are: -// @linear ≡ own — used exactly once -// @erased ≡ :0 — compile-time only, erased at runtime -// @unrestricted ≡ ref — may be used any number of times (default) - -fn use_linear(@linear x: Int) -> Int { - x // x is consumed here — can't use it again -} - -fn use_erased(@erased _proof: Bool) -> String { - "the proof only exists at compile time" -} - -// ── Option and Result are the right tools for fallibility ───────────────────── - -fn safe_divide(a: Int, b: Int) -> Option[Int] { - if b == 0 { None } else { Some(a / b) } -} - -fn parse_int(s: ref String) -> Result[Int, String] { - // In a real program this would parse; here we just illustrate the type: - Ok(42) -} - -// ── Exercise 1: write a function that takes own File, reads the fd (ref), ───── -// then closes it. What types do the arguments need? -// fn read_then_close(file: ???) -> Int { ... } - -// ── Exercise 2: write a version of `safe_divide` that returns Result[Int, String] -// with a descriptive error message instead of Option. ────────────────────── -// fn checked_divide(a: Int, b: Int) -> Result[Int, String] { ... } diff --git a/affinescript/docs/guides/warmup/03_effects.affine b/affinescript/docs/guides/warmup/03_effects.affine deleted file mode 100644 index 8b62be6..0000000 --- a/affinescript/docs/guides/warmup/03_effects.affine +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Warmup 03: Algebraic effects -// -// Effects are AffineScript's unified model for anything "impure": -// I/O, state, exceptions, async, randomness, etc. -// -// The key idea: effects are declared in return types, not thrown sideways. -// The compiler knows every effect a function can perform. -// -// Run: affinescript check 03_effects.affine - -// ── Declaring an effect ─────────────────────────────────────────────────────── - -effect IO { - fn print(s: String); - fn println(s: String); - fn read_line() -> String; -} - -// ── Using an effect ─────────────────────────────────────────────────────────── -// -// The `/ EffectName` in the return type declares which effects are used. -// No `perform` keyword — just call the operation directly (ADR-008). - -fn greet(name: ref String) -> () / IO { - println("Hello, " ++ name ++ "!") -} - -// Multiple effects are composed with `+`: - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -type IOError = { message: String } - -fn read_line_safe() -> Result[String, IOError] / IO + Exn[IOError] { - let line = IO.read_line(); - if line == "" { - Exn.throw({ message: "empty input" }) - } else { - Ok(line) - } -} - -// ── Effect polymorphism ─────────────────────────────────────────────────────── -// -// Functions can be polymorphic over effects — they propagate whatever effects -// their argument functions use. - -fn map_list[A, B, ..e]( - xs: ref Array[A], - f: (ref A) -> B / e -) -> Array[B] / e { - // Implementation would map f over xs - [] -} - -// ── Parameterised effects ───────────────────────────────────────────────────── - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -effect Async { - fn await[T](promise: Promise[T]) -> T; - fn spawn[T](f: () -> T) -> Promise[T]; -} - -// ── Exercise 1: write a function that reads two lines and returns ────────────── -// a tuple of them. What effects does it declare? -// fn read_pair() -> (String, String) / ??? { ... } - -// ── Exercise 2: write a counter using State[Int] that increments by n ───────── -// and returns the new value. -// fn increment(n: Int) -> Int / State[Int] { ... } diff --git a/affinescript/docs/guides/warmup/04_rows.affine b/affinescript/docs/guides/warmup/04_rows.affine deleted file mode 100644 index fd54e94..0000000 --- a/affinescript/docs/guides/warmup/04_rows.affine +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Warmup 04: Row polymorphism -// -// Row polymorphism lets you write functions that work on any record with at -// least the fields you need. It's the principled version of duck typing — -// with compile-time guarantees and no runtime surprises. -// -// Run: affinescript check 04_rows.affine - -// ── Basic row-polymorphic function ──────────────────────────────────────────── -// -// `[..r]` introduces a row variable. The function accepts any record that -// has an `x: Int` field, plus any other fields captured by `..r`. - -fn get_x[..r](point: {x: Int, ..r}) -> Int { - point.x -} - -// Both of these call sites work: -type Point2D = {x: Int, y: Int} -type Point3D = {x: Int, y: Int, z: Int} - -let p2 = Point2D { x: 1, y: 2 } -let p3 = Point3D { x: 3, y: 4, z: 5 } - -let x2 = get_x(p2) // fine — {x: Int, y: Int} has x -let x3 = get_x(p3) // fine — {x: Int, y: Int, z: Int} has x - -// ── Extending a record ──────────────────────────────────────────────────────── -// -// The spread syntax `..record` copies all fields of record into the new one. - -fn with_id[..r](record: {..r}, id: Int) -> {id: Int, ..r} { - { id: id, ..record } -} - -let tagged_point = with_id(p2, 99) // { id: 99, x: 1, y: 2 } - -// ── Named row type aliases ──────────────────────────────────────────────────── -// -// You can name row constraints as type aliases: - -type HasName[..r] = {name: String, ..r} -type HasAge[..r] = {age: Int, ..r} - -fn greet_named[..r](entity: HasName[..r]) -> String { - "Hello, " ++ entity.name ++ "!" -} - -// ── Row polymorphism in effects ─────────────────────────────────────────────── -// -// Effect rows work the same way. `/ e` is a row variable over effects. - -effect IO { fn println(s: String); } -effect Log { fn warn(s: String); } - -fn run_with_log[..e](f: () -> () / IO + ..e) -> () / IO + Log + ..e { - Log.warn("starting"); - f(); - Log.warn("done"); -} - -// ── Exercise 1: write a function `swap_x_y` that takes any record with both ─── -// `x: Int` and `y: Int` fields and returns one with the values swapped. -// Hint: the return type has the same row variable as the input. -// fn swap_x_y[..r](p: {x: Int, y: Int, ..r}) -> {x: Int, y: Int, ..r} { ... } - -// ── Exercise 2: write a function `merge` that takes two single-field records ── -// and produces a record containing both fields. -// fn merge_name_age(named: {name: String}, aged: {age: Int}) -> {name: String, age: Int} { ... } diff --git a/affinescript/docs/guides/warmup/README.adoc b/affinescript/docs/guides/warmup/README.adoc deleted file mode 100644 index 749f17c..0000000 --- a/affinescript/docs/guides/warmup/README.adoc +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript Warmup Scripts -:toc: -:icons: font - -Short, focused AffineScript programs for building muscle memory before reading -the full link:../frontier-guide.adoc[Frontier Guide]. - -Each script is self-contained and has inline exercises to try. - -== How to use - -[source,bash] ----- -# Parse only (syntax check): -affinescript parse 01_basics.affine - -# Type check: -affinescript check 02_ownership.affine - -# Evaluate (for scripts that produce output): -affinescript eval 01_basics.affine ----- - -== Warmup sequence - -[cols="1,3"] -|=== -| Script | Covers - -| link:01_basics.affine[01_basics.affine] -| Let bindings, literals, functions, pattern matching - -| link:02_ownership.affine[02_ownership.affine] -| `own`/`ref`/`mut`, `@linear`/`@erased`, `Option`/`Result` - -| link:03_effects.affine[03_effects.affine] -| Effect declarations, effect composition, effect polymorphism - -| link:04_rows.affine[04_rows.affine] -| Row-polymorphic records, row type aliases, spread syntax -|=== - -== For bot / agent guidance - -When using an AI agent to learn AffineScript interactively, point the agent at -these files in order. Each file ends with numbered exercises — a suitable -prompt is: - -____ -"Complete exercise 1 in this warmup. If the compiler accepts it, move on. -If it rejects it, read the error and try to understand what rule was violated -before looking it up." -____ - -The exercises are deliberately constraint-based: success is learning to work -within the type system, not producing output as quickly as possible. diff --git a/affinescript/docs/history/TYPECHECKER-COMPLETION-2026-01-23.adoc b/affinescript/docs/history/TYPECHECKER-COMPLETION-2026-01-23.adoc deleted file mode 100644 index a38474a..0000000 --- a/affinescript/docs/history/TYPECHECKER-COMPLETION-2026-01-23.adoc +++ /dev/null @@ -1,405 +0,0 @@ -____ -Historical snapshot from 2026-01-23. Superseded by -.machine_readable/descriptiles/STATE.a2ml. Retained for audit trail only. -____ - -== Type Checker Completion Report - -*Date:* 2026-01-23 *Status:* ✅ COMPLETE (100%) *Lines of Code:* 1,253 -(was 1,100) *New Code:* 153 lines added - -=== Executive Summary - -The AffineScript type checker has been completed to 100% for Phase 1. -All critical missing features have been implemented, tested via -compilation, and integrated into the main codebase. - -=== Completion Status - -==== Before (70% complete) - -* Basic bidirectional type checking ✓ -* Let-generalization ✓ -* Row polymorphism ✓ -* Effect tracking ✓ -* Dependent types (partial) ✓ -* Refinement types ✓ -* *Missing:* Unsafe ops, variant validation, constructor patterns, -record spread, mutability - -==== After (100% complete) - -* All previous features ✓ -* *NEW:* Complete unsafe operations type checking ✓ -* *NEW:* Variant constructor validation ✓ -* *NEW:* Constructor pattern type lookup ✓ -* *NEW:* Record spread syntax support ✓ -* *NEW:* Mutable binding semantics ✓ - -=== Features Implemented - -==== 1. Unsafe Operations (Critical) ✅ - -*File:* `+lib/typecheck.ml+` lines 1082-1138 - -Added complete type checking for all 6 unsafe operations: - -===== `+UnsafeRead(e)+` - -* Type checks the expression `+e+` -* Validates it’s a reference type (`+&T+`, `+&mut T+`, or `+own T+`) -* Returns the dereferenced type `+T+` -* Unifies with fresh type variable if type is unknown - -===== `+UnsafeWrite(ptr, value)+` - -* Type checks `+ptr+` as a mutable reference (`+&mut T+`) -* Type checks `+value+` and unifies with `+T+` -* Returns `+Unit+` -* Ensures type safety even in unsafe context - -===== `+UnsafeOffset(ptr, offset)+` - -* Type checks `+ptr+` (any pointer type) -* Validates `+offset+` is an `+Int+` -* Returns the same pointer type (pointer arithmetic) - -===== `+UnsafeTransmute(from_ty, to_ty, e)+` - -* Converts AST types to internal types -* Checks expression `+e+` against `+from_ty+` -* Returns `+to_ty+` (bit reinterpretation) - -===== `+UnsafeForget(e)+` - -* Type checks expression `+e+` -* Returns `+Unit+` -* Prevents destructor from running - -===== `+UnsafeAssume(pred)+` - -* Converts AST predicate to internal predicate -* Adds assumption to constraint context -* Returns `+Unit+` -* Allows asserting type-level constraints - -*Impact:* Enables low-level operations while maintaining type safety -boundaries. - -==== 2. Variant Constructor Validation (High Priority) ✅ - -*File:* `+lib/typecheck.ml+` lines 672-684 - -*Before:* - -[source,ocaml] ----- -| ExprVariant (ty_id, _variant_id) -> - Ok (TCon ty_id.name, EPure) (* No validation *) ----- - -*After:* - -[source,ocaml] ----- -| ExprVariant (ty_id, variant_id) -> - (* Look up the variant constructor in the symbol table *) - begin match Symbol.lookup ctx.symbols variant_id.name with - | Some sym when sym.sym_kind = Symbol.SKConstructor -> - (* Get the constructor's type from var_types *) - begin match Hashtbl.find_opt ctx.var_types sym.sym_id with - | Some scheme -> Ok (instantiate ctx scheme, EPure) - | None -> Ok (TCon ty_id.name, EPure) - end - | _ -> Ok (TCon ty_id.name, EPure) - end ----- - -*Features:* - Looks up constructor in symbol table - Validates -constructor exists and is actually a constructor - Retrieves -constructor’s type scheme - Instantiates type with fresh variables - -Falls back gracefully if type info unavailable - -*Impact:* Proper validation of variant constructors like -`+Result::Ok(42)+`. - -==== 3. Constructor Pattern Type Lookup (High Priority) ✅ - -*File:* `+lib/typecheck.ml+` lines 1023-1044 - -*Before:* - -[source,ocaml] ----- -| PatCon (con, pats) -> - let param_tys = (* Always generated fresh tyvars *) - List.map (fun _ -> fresh_tyvar ctx.level) pats - in - (* Bind patterns with fresh types *) ----- - -*After:* - -[source,ocaml] ----- -| PatCon (con, pats) -> - let param_tys = match Symbol.lookup ctx.symbols con.name with - | Some sym when sym.sym_kind = Symbol.SKConstructor -> - begin match Hashtbl.find_opt ctx.var_types sym.sym_id with - | Some con_scheme -> - (* Extract parameter types from constructor type *) - let con_ty = instantiate ctx con_scheme in - extract_constructor_param_types con_ty (List.length pats) - | None -> List.map (fun _ -> fresh_tyvar ctx.level) pats - end - | _ -> List.map (fun _ -> fresh_tyvar ctx.level) pats - in - (* Bind patterns with actual constructor parameter types *) ----- - -*Helper Function Added:* - -[source,ocaml] ----- -(** Extract parameter types from a constructor type *) -and extract_constructor_param_types (ty : ty) (expected_count : int) : ty list = - let rec go ty acc = - match repr ty with - | TArrow (param_ty, ret_ty, _) -> go ret_ty (param_ty :: acc) - | _ -> List.rev acc - in - let params = go ty [] in - if List.length params = expected_count then params - else List.init expected_count (fun _ -> fresh_tyvar 0) ----- - -*Features:* - Looks up constructor in symbol table - Retrieves actual -constructor type - Extracts parameter types from arrow type chain - -Validates parameter count matches pattern - Falls back to fresh tyvars -if types unavailable - -*Impact:* Pattern matching against constructors now uses actual types -instead of inferring fresh ones. - -==== 4. Record Spread Syntax (Medium Priority) ✅ - -*File:* `+lib/typecheck.ml+` lines 512-536 - -*Before:* - -[source,ocaml] ----- -| ExprRecord er -> - let* field_results = synth_record_fields ctx er.er_fields in - let row = List.fold_right (fun (name, ty, _eff) acc -> - RExtend (name, ty, acc) - ) field_results REmpty in - (* er_spread field ignored *) ----- - -*After:* - -[source,ocaml] ----- -| ExprRecord er -> - let* field_results = synth_record_fields ctx er.er_fields in - (* Handle spread if present *) - let* (base_row, spread_eff) = match er.er_spread with - | Some spread_expr -> - let* (spread_ty, spread_eff) = synth ctx spread_expr in - begin match repr spread_ty with - | TRecord row -> Ok (row, spread_eff) - | TVar _ as tv -> - (* Unify with record type if unknown *) - let row = fresh_rowvar ctx.level in - begin match Unify.unify tv (TRecord row) with - | Ok () -> Ok (row, spread_eff) - | Error e -> Error (UnificationFailed (e, expr_span spread_expr)) - end - | _ -> Error (ExpectedRecord (spread_ty, expr_span spread_expr)) - end - | None -> Ok (REmpty, EPure) - in - (* Build row by extending base with new fields *) - let row = List.fold_right (fun (name, ty, _eff) acc -> - RExtend (name, ty, acc) - ) field_results base_row in - let field_effs = List.map (fun (_, _, eff) -> eff) field_results in - Ok (TRecord row, union_eff (spread_eff :: field_effs)) ----- - -*Features:* - Type checks the spread base expression - Validates base is -a record type - Unifies with record if type is unknown - Extends base -row with new fields - Combines effects from spread and fields - -*Syntax Support:* - -[source,affinescript] ----- -let base = {x: 1, y: 2}; -let extended = {...base, z: 3}; // {x: 1, y: 2, z: 3} ----- - -*Impact:* Enables record extension/override patterns common in -functional languages. - -==== 5. Mutable Binding Semantics (Low Priority) ✅ - -*Files:* - `+lib/typecheck.ml+` lines 437-459 (ExprLet) - -`+lib/typecheck.ml+` lines 896-908 (StmtLet) - -*Before:* - -[source,ocaml] ----- -| ExprLet lb -> - let* (rhs_ty, rhs_eff) = synth ctx' lb.el_value in - let scheme = generalize ctx rhs_ty in - (* el_mut flag ignored *) ----- - -*After:* - -[source,ocaml] ----- -| ExprLet lb -> - let* (rhs_ty, rhs_eff) = synth ctx' lb.el_value in - (* If mutable, wrap type in TMut *) - let bind_ty = if lb.el_mut then TMut rhs_ty else rhs_ty in - (* Generalize only if immutable *) - let scheme = if lb.el_mut then - (* Mutable bindings: no generalization *) - { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = bind_ty } - else - (* Immutable bindings: generalize *) - generalize ctx bind_ty - in ----- - -*Features:* - Mutable bindings wrapped in `+TMut+` type constructor - -Mutable bindings NOT generalized (prevents unsound polymorphism) - -Immutable bindings generalized normally with let-polymorphism - Applied -to both `+ExprLet+` and `+StmtLet+` - -*Type System Rules:* - -.... -let x = value // Type: T, generalized to ∀a. T -let mut x = value // Type: &mut T, NOT generalized -.... - -*Impact:* Sound treatment of mutability in the type system. - -=== Technical Details - -==== Type Checking Approach - -All implementations follow bidirectional type checking principles: - -*Synthesis mode* (inference): -`+synth : context -> expr -> (ty * eff) result+` - *Checking mode*: -`+check : context -> expr -> ty -> eff result+` - -==== Error Handling - -All new code uses the Result monad with proper error types: - -[source,ocaml] ----- -type 'a result = ('a, type_error) Result.t -let ( let* ) = Result.bind ----- - -Errors are wrapped in `+UnificationFailed+`, `+ExpectedRecord+`, etc. - -==== Integration Points - -New functions integrate with existing infrastructure: - -`+Symbol.lookup+` for constructor resolution - -`+Hashtbl.find_opt ctx.var_types+` for type schemes - `+Unify.unify+` -for type unification - `+ast_to_ty+`, `+ast_to_pred+` for AST conversion -- `+fresh_tyvar+`, `+fresh_rowvar+` for type variable generation - -=== Build Status - -✅ *All changes compiled successfully* - -[source,bash] ----- -$ dune build -# No errors ----- - -No compiler warnings or errors introduced. - -=== Testing Strategy - -==== Unit Tests - -Type checker completion tested via: 1. Successful compilation of all new -code 2. Integration with existing type checking infrastructure 3. No -regressions in existing test files - -==== Example Programs - -Created test files demonstrating new features: - -`+examples/typecheck_complete_test.affine+` - Full feature demonstration -- `+examples/typecheck_features_test.affine+` - Parseable subset - -*Note:* Parser may not yet support all syntax (e.g., `+unsafe+` blocks), -but type checker is ready when parser is completed. - -=== Code Metrics - -[cols=",,,",options="header",] -|=== -|Metric |Before |After |Change -|Total Lines |1,100 |1,253 |+153 -|Completion % |70% |100% |+30% -|Functions |~40 |~45 |+5 -|Pattern Matches |~60 |~70 |+10 -|=== - -=== Phase 1 Completion Criteria ✅ - -All Phase 1 type checking features are now complete: - -* [x] Bidirectional type checking -* [x] Let-generalization and polymorphism -* [x] Row polymorphism for records -* [x] Effect tracking and inference -* [x] Dependent type checking with nat expressions -* [x] Refinement type checking with predicates -* [x] Constraint solving integration -* [x] Pattern matching all forms -* [x] *Unsafe operations* (NEW) -* [x] *Variant constructors* (NEW) -* [x] *Constructor patterns* (NEW) -* [x] *Record spread* (NEW) -* [x] *Mutable bindings* (NEW) - -=== Future Enhancements (Phase 2+) - -As noted in code comments (line 1243-1249), these are NOT required for -Phase 1: - -* Better error messages with suggestions -* Advanced trait resolution with overlapping impls -* Full dependent type checking (beyond current support) -* Module type checking with signatures - -=== Conclusion - -The type checker is now *feature-complete for Phase 1* at 100% -completion. All critical missing features have been implemented, tested, -and integrated. The implementation is sound, follows existing patterns, -and is ready for use when the parser and other components catch up. - -*Next Steps:* 1. Complete parser to support all syntax (unsafe blocks, -etc.) 2. Hook up type checker to CLI commands (`+affinescript check+`) -3. Add comprehensive test suite with type error tests 4. Begin Phase 2 -enhancements if desired - -''''' - -*Implemented by:* Claude Sonnet 4.5 *Session Date:* 2026-01-23 *Commit:* -Ready for commit to main branch diff --git a/affinescript/docs/humans.txt b/affinescript/docs/humans.txt deleted file mode 100644 index f5f28ce..0000000 --- a/affinescript/docs/humans.txt +++ /dev/null @@ -1,40 +0,0 @@ -/* TEAM */ - -Developer: Jonathan D.A. Jewell -Site: https://jonathanjewell.dev -Location: United Kingdom -Email: jonathan.jewell@open.ac.uk -GitHub: https://github.com/hyperpolymath -Keybase: https://keybase.io/hyperpolymath (coming soon) - -/* THANKS */ - -Claude Code (Anthropic) - Development assistance -GitHub - Hosting -Cloudflare - CDN & Security -Let's Encrypt - TLS Certificates - -/* SITE */ - -Last update: 2026/01/31 -Standards: HTML5, CSS3, JavaScript ES2024 -Components: ReScript, Deno, Rust -Security: TLS 1.3, HSTS, Consent-Aware HTTP -Languages: English -Doctype: HTML5 -IDE: Zed, Helix, VSCode - -/* REPOSITORY */ - -Repository: https://github.com/hyperpolymath/affinescript -License: MPL-2.0 -Documentation: MPL-2.0 preferred; MPL-2.0 where required -Issues: https://github.com/hyperpolymath/affinescript/issues -Security: https://github.com/hyperpolymath/affinescript/security - -/* CONTACT */ - -General: info@affinescript.dev -Security: security@affinescript.dev -Privacy: privacy@affinescript.dev -Support: support@affinescript.dev diff --git a/affinescript/docs/spec.adoc b/affinescript/docs/spec.adoc deleted file mode 100644 index d756621..0000000 --- a/affinescript/docs/spec.adoc +++ /dev/null @@ -1,2020 +0,0 @@ -== AffineScript Complete Language Specification v2.0 - -____ -*⚠ HONEST STATUS NOTE (2026-04-10 manhattan-recovery):* - -This specification predates the 2026-04-10 thesis recovery and is *out -of date* with the current language scope. It still contains scattered -references to dependent types, refinement types, the `+Nat+` kind, -`+Vec[n, T]+`, and other features that have been *removed from -AffineScript* and, where applicable, deferred to the sibling Typed WASM -project. - -*The authoritative current scope lives in:* - -`+.machine_readable/anchors/ANCHOR.a2ml+` (canonical identity + thesis) -- `+.machine_readable/descriptiles/META.a2ml+` (architecture decision records) - -`+~/Desktop/Frontier_Programming_Practices_AffineScript/AI.a2ml+` (v2.0) -- -`+~/Desktop/Frontier_Programming_Practices_AffineScript/Human_Programming_Guide.adoc+` -(v2.0) - -*The eight in-scope features (as of 2026-04-10) are:* phantom types, -immutable by default, row polymorphism, full type inference, sound type -system, effect tracking (no handlers — see ADR-004 extension point), -affine types (headline), and decidable-fragment refinement types (soft). - -*Out of scope* (deferred or removed): linear strict types, full -dependent types, tropical types, algebraic effect handlers, unbounded -refinement, units of measure, shape-indexed vectors. The first four were -bot scope creep, removed during the 2026-04-10 Manhattan Recovery -session. - -The large standalone sections on dependent types, refinement type rules, -and length-indexed vectors have been excised from this document. Inline -mentions may still appear scattered through other sections and will be -cleaned up in a future full spec rewrite. *If this document disagrees -with any of the above authorities, the authorities win.* -____ - -=== Purpose - -This document specifies AffineScript, a programming language combining: -- *Affine types* (Rust-style ownership, the headline novelty) - *Phantom -types* and *row polymorphism* (extensible records, compile-time-only -tagging) - *Immutable by default* with explicit mutation - *Full -Hindley-Milner type inference* with quantities at function signatures - -*Effect tracking* (function signatures declare what effects they can do; -handler semantics deferred) - *Decidable-fragment refinement types* -(soft commitment) - *Typed-WASM-first* compilation targeting the sibling -Typed WASM project - -This specification is an in-progress rewrite; sections below may still -reflect the pre-recovery scope until the full spec rewrite lands. - -''''' - -== PART 1: LEXICAL GRAMMAR - -=== 1.1 Character Classes - -[source,ebnf] ----- -letter = 'a'..'z' | 'A'..'Z' ; -digit = '0'..'9' ; -alpha_num = letter | digit | '_' ; -hex_digit = digit | 'a'..'f' | 'A'..'F' ; -bin_digit = '0' | '1' ; -oct_digit = '0'..'7' ; ----- - -=== 1.2 Whitespace and Comments - -[source,ebnf] ----- -whitespace = ' ' | '\t' | '\n' | '\r' ; -line_comment = '//' { any_char - '\n' } '\n' ; -block_comment = '/*' { any_char } '*/' ; -skip = whitespace | line_comment | block_comment ; ----- - -=== 1.3 Identifiers - -[source,ebnf] ----- -lower_ident = ('a'..'z') { alpha_num } ; -upper_ident = ('A'..'Z') { alpha_num } ; -ident = lower_ident | upper_ident ; - -(* Row variables start with .. *) -row_var = '..' lower_ident ; - -(* Type variables are lowercase, optionally with prime *) -type_var = lower_ident [ "'" ] ; ----- - -=== 1.4 Keywords - -.... -fn let mut own ref -type struct enum trait impl -effect handle resume handler -match if else while for -return break continue in -true false -where total -module use pub as -unsafe assume transmute forget -Nat Int Bool Float String -Type Row -.... - -=== 1.5 Literals - -[source,ebnf] ----- -int_lit = [ '-' ] digit { digit } - | '0x' hex_digit { hex_digit } - | '0b' bin_digit { bin_digit } - | '0o' oct_digit { oct_digit } ; - -float_lit = [ '-' ] digit { digit } '.' digit { digit } [ exponent ] ; -exponent = ('e' | 'E') [ '+' | '-' ] digit { digit } ; - -char_lit = "'" ( escape_seq | any_char - "'" ) "'" ; -string_lit = '"' { escape_seq | any_char - '"' } '"' ; - -escape_seq = '\\' ( 'n' | 'r' | 't' | '\\' | '"' | "'" | '0' - | 'x' hex_digit hex_digit - | 'u' '{' hex_digit { hex_digit } '}' ) ; - -bool_lit = 'true' | 'false' ; -unit_lit = '()' ; ----- - -=== 1.6 Operators and Punctuation - -[source,ebnf] ----- -(* Arithmetic *) -arith_op = '+' | '-' | '*' | '/' | '%' ; - -(* Comparison *) -cmp_op = '==' | '!=' | '<' | '>' | '<=' | '>=' ; - -(* Logical *) -logic_op = '&&' | '||' | '!' ; - -(* Bitwise *) -bit_op = '&' | '|' | '^' | '~' | '<<' | '>>' ; - -(* Assignment *) -assign_op = '=' | '+=' | '-=' | '*=' | '/=' ; - -(* Type-level *) -type_op = '->' | '=>' | ':' | '/' | '\\' ; - -(* Punctuation *) -punct = '(' | ')' | '{' | '}' | '[' | ']' - | ',' | ';' | '.' | '..' | '::' | '|' | '@' ; - -(* Quantity annotations *) -quantity = '0' | '1' | 'ω' | 'omega' ; ----- - -''''' - -== PART 2: SYNTACTIC GRAMMAR - -=== 2.1 Program Structure - -[source,ebnf] ----- -program = [ module_decl ] { import_decl } { top_level } ; - -top_level = type_decl - | fn_decl - | trait_decl - | impl_block - | effect_decl - | const_decl ; ----- - -=== 2.2 Module System - -[source,ebnf] ----- -module_decl = 'module' module_path ';' ; -module_path = upper_ident { '.' upper_ident } ; - -import_decl = 'use' import_path [ 'as' ident ] ';' - | 'use' import_path '::' '{' import_list '}' ';' - | 'use' import_path '::' '*' ';' ; - -import_path = [ '::' ] module_path [ '::' ident ] ; -import_list = import_item { ',' import_item } ; -import_item = ident [ 'as' ident ] ; - -visibility = [ 'pub' [ '(' pub_scope ')' ] ] ; -pub_scope = 'crate' | 'super' | module_path ; ----- - -=== 2.3 Type Declarations - -[source,ebnf] ----- -type_decl = visibility 'type' upper_ident [ type_params ] '=' type_body ; - -type_params = '[' type_param { ',' type_param } ']' ; -type_param = [ quantity ] ident [ ':' kind ] ; - -kind = 'Type' - | 'Nat' - | 'Row' - | 'Effect' - | kind '->' kind ; - -type_body = type_expr (* alias *) - | struct_body (* record *) - | enum_body ; (* variant *) - -struct_body = '{' field_decl { ',' field_decl } [ ',' ] '}' ; -field_decl = visibility ident ':' type_expr ; - -enum_body = [ '|' ] variant { '|' variant } ; -variant = upper_ident [ '(' type_expr { ',' type_expr } ')' ] - [ ':' type_expr ] ; (* GADT return type *) ----- - -=== 2.4 Type Expressions - -[source,ebnf] ----- -type_expr = type_atom - | fn_type - | dependent_fn_type - | type_app - | refined_type - | row_type - | owned_type - | ref_type ; - -type_atom = 'Nat' | 'Int' | 'Bool' | 'Float' | 'String' | 'Type' - | upper_ident - | type_var - | '(' type_expr ')' - | '(' type_expr { ',' type_expr } ')' ; (* tuple *) - -(* Function types with effects *) -fn_type = type_expr '->' type_expr [ '/' effects ] ; - -(* Dependent function types: (x: T) -> U where U mentions x *) -dependent_fn_type = '(' [ quantity ] ident ':' type_expr ')' '->' type_expr [ '/' effects ] ; - -(* Type application *) -type_app = upper_ident '[' type_arg { ',' type_arg } ']' ; -type_arg = type_expr | nat_expr ; - -(* Refinement types *) -refined_type = type_expr 'where' '(' predicate ')' ; -predicate = pred_expr { ('&&' | '||') pred_expr } ; -pred_expr = nat_expr cmp_op nat_expr - | '!' pred_expr - | '(' predicate ')' ; - -(* Row polymorphic record types *) -row_type = '{' row_fields '}' ; -row_fields = field_type { ',' field_type } [ ',' row_var ] - | row_var ; -field_type = ident ':' type_expr ; - -(* Ownership modifiers *) -owned_type = 'own' type_expr ; -ref_type = 'ref' type_expr - | 'mut' type_expr ; - -(* Effect sets *) -effects = effect_term { '+' effect_term } ; -effect_term = upper_ident [ '[' type_arg { ',' type_arg } ']' ] - | effect_var ; -effect_var = lower_ident ; ----- - -=== 2.5 Natural Number Expressions (Type-Level) - -[source,ebnf] ----- -nat_expr = nat_atom - | nat_expr '+' nat_expr - | nat_expr '-' nat_expr - | nat_expr '*' nat_expr - | 'len' '(' ident ')' - | 'sizeof' '[' type_expr ']' ; - -nat_atom = int_lit - | ident (* type-level variable *) - | '(' nat_expr ')' ; ----- - -=== 2.6 Effect Declarations - -[source,ebnf] ----- -effect_decl = visibility 'effect' upper_ident [ type_params ] '{' { effect_op } '}' ; - -effect_op = 'fn' lower_ident '(' [ param_list ] ')' [ '->' type_expr ] ';' ; ----- - -=== 2.7 Trait Declarations - -[source,ebnf] ----- -trait_decl = visibility 'trait' upper_ident [ type_params ] [ ':' trait_bounds ] - '{' { trait_item } '}' ; - -trait_bounds = upper_ident { '+' upper_ident } ; - -trait_item = fn_sig ';' (* required method *) - | fn_decl (* default method *) - | 'type' upper_ident [ ':' kind ] [ '=' type_expr ] ';' ; (* associated type *) - -fn_sig = visibility 'fn' lower_ident [ type_params ] '(' [ param_list ] ')' - [ '->' type_expr ] [ '/' effects ] ; ----- - -=== 2.8 Implementation Blocks - -[source,ebnf] ----- -impl_block = 'impl' [ type_params ] [ trait_ref 'for' ] type_expr - [ where_clause ] '{' { impl_item } '}' ; - -trait_ref = upper_ident [ '[' type_arg { ',' type_arg } ']' ] ; - -impl_item = fn_decl - | 'type' upper_ident '=' type_expr ';' ; ----- - -=== 2.9 Function Declarations - -[source,ebnf] ----- -fn_decl = visibility [ 'total' ] 'fn' lower_ident - [ type_params ] - '(' [ param_list ] ')' - [ '->' type_expr ] - [ '/' effects ] - [ where_clause ] - fn_body ; - -param_list = param { ',' param } ; -param = [ quantity ] [ ownership ] ident ':' type_expr ; -ownership = 'own' | 'ref' | 'mut' ; - -where_clause = 'where' constraint { ',' constraint } ; -constraint = predicate - | type_var ':' trait_bound ; -trait_bound = upper_ident [ '+' upper_ident ] ; - -fn_body = block | '=' expr ; ----- - -=== 2.10 Expressions - -[source,ebnf] ----- -expr = let_expr - | if_expr - | match_expr - | fn_expr - | try_expr - | handle_expr - | block - | return_expr - | unsafe_expr - | binary_expr ; - -let_expr = 'let' [ 'mut' ] pattern [ ':' type_expr ] '=' expr [ 'in' expr ] ; - -if_expr = 'if' expr block [ 'else' ( if_expr | block ) ] ; - -match_expr = 'match' expr '{' { match_arm } '}' ; -match_arm = pattern [ 'if' expr ] '=>' expr [ ',' ] ; - -fn_expr = [ '|' param_list '|' | '||' ] [ '->' type_expr ] expr - | 'fn' '(' [ param_list ] ')' [ '->' type_expr ] fn_body ; - -try_expr = 'try' block [ 'catch' '{' { catch_arm } '}' ] [ 'finally' block ] ; -catch_arm = pattern '=>' expr [ ',' ] ; - -handle_expr = 'handle' expr 'with' '{' { handler_arm } '}' ; -handler_arm = 'return' pattern '=>' expr [ ',' ] - | lower_ident '(' [ pattern { ',' pattern } ] ')' '=>' expr [ ',' ] ; - -block = '{' { statement } [ expr ] '}' ; - -return_expr = 'return' [ expr ] ; - -unsafe_expr = 'unsafe' '{' { unsafe_op } '}' ; -unsafe_op = expr '.' 'read' '(' ')' - | expr '.' 'write' '(' expr ')' - | expr '.' 'offset' '(' expr ')' - | 'transmute' '[' type_expr ',' type_expr ']' '(' expr ')' - | 'forget' '(' expr ')' - | 'assume' '(' predicate ')' ; - -binary_expr = unary_expr { binary_op unary_expr } ; -binary_op = arith_op | cmp_op | logic_op | bit_op ; - -unary_expr = [ unary_op ] postfix_expr ; -unary_op = '-' | '!' | '~' | '&' | '*' ; - -postfix_expr = primary_expr { postfix } ; -postfix = '.' ident (* field access *) - | '.' int_lit (* tuple index *) - | '[' expr ']' (* array index *) - | '(' [ arg_list ] ')' (* function call *) - | '::' upper_ident (* enum variant *) - | '\\' ident ; (* row restriction *) - -arg_list = expr { ',' expr } ; - -primary_expr = literal - | ident - | '(' expr ')' - | '(' expr { ',' expr } ')' (* tuple *) - | '[' [ expr { ',' expr } ] ']' (* array *) - | '{' [ field_init { ',' field_init } ] [ '..' expr ] '}' (* record *) - | 'resume' '(' [ expr ] ')' ; (* effect handler resume *) - -field_init = ident [ ':' expr ] ; (* shorthand: {x} means {x: x} *) - -literal = int_lit | float_lit | char_lit | string_lit | bool_lit | unit_lit ; ----- - -=== 2.11 Patterns - -[source,ebnf] ----- -pattern = '_' (* wildcard *) - | ident (* binding *) - | literal (* literal match *) - | upper_ident [ '(' pattern { ',' pattern } ')' ] (* variant *) - | '(' pattern { ',' pattern } ')' (* tuple *) - | '{' field_pat { ',' field_pat } [ ',' '..' ] '}' (* record *) - | pattern '|' pattern (* or-pattern *) - | ident '@' pattern ; (* binding with sub-pattern *) - -field_pat = ident [ ':' pattern ] ; ----- - -=== 2.12 Statements - -[source,ebnf] ----- -statement = let_stmt - | expr_stmt - | assign_stmt - | while_stmt - | for_stmt ; - -let_stmt = let_expr ';' ; -expr_stmt = expr ';' ; -assign_stmt = postfix_expr assign_op expr ';' ; -while_stmt = 'while' expr block ; -for_stmt = 'for' pattern 'in' expr block ; ----- - -''''' - -== PART 3: TYPE SYSTEM - -=== 3.1 Judgement Forms - -.... -Γ ⊢ e : τ / ε Expression e has type τ with effects ε in context Γ -Γ ⊢ τ : κ Type τ has kind κ in context Γ -Γ ⊢ e ↝ v Expression e evaluates to value v (for type-level computation) -Γ ⊢ P true Predicate P is satisfied -σ : Γ → Δ Substitution σ maps context Γ to context Δ -.... - -=== 3.2 Kinds - -.... -κ ::= Type Proper types - | Nat Natural numbers (for dependent types) - | Row Row kinds (for row polymorphism) - | Effect Effect kinds - | κ → κ Kind-level functions -.... - -=== 3.3 Quantities (from QTT) - -.... -q ::= 0 Erased (compile-time only, not present at runtime) - | 1 Linear (must be used exactly once) - | ω Unrestricted (can be used any number of times) -.... - -*Quantity algebra:* - -.... -0 + q = q -1 + 1 = ω -1 + ω = ω -ω + ω = ω - -0 * q = 0 -1 * q = q -ω * ω = ω -.... - -*Erased (quantity 0) semantics:* - -Erased values exist only at compile time. They are computed during type -checking and deleted before code generation. - -[source,affinescript] ----- -// n is erased - no runtime cost -fn replicate[0 n: Nat, T](value: T) -> Vec[n, T] - -// Valid: n only appears in types -fn ok[0 n: Nat](v: Vec[n, Int]) -> Vec[n, Int] { v } - -// Invalid: cannot use erased value at runtime -fn bad[0 n: Nat]() -> Nat { n } // ERROR ----- - -=== 3.4 Effects - -Effects are user-defined and extensible: - -.... -ε ::= ∅ Pure (no effects) - | E[τ₁,...,τₙ] Named effect with type arguments - | α Effect variable - | ε + ε Effect union -.... - -*Effect algebra:* - -.... -ε + ∅ = ε -ε + ε = ε -ε₁ + ε₂ = ε₂ + ε₁ -.... - -*Totality and Divergence:* - -* Functions are *partial by default* - they may diverge (not terminate) -* `+total+` functions must provably terminate and cannot have `+Div+` -effect -* Non-total functions implicitly include `+Div+` in their effect set - -[source,affinescript] ----- -// Default: partial, may diverge -fn serverLoop() -> Never { // Implicitly / Div - loop { handleRequest(); } -} - -// Explicit: must prove termination -total fn factorial(n: Nat) -> Nat / Pure { - match n { - 0 => 1, - _ => n * factorial(n - 1) // Structural recursion - OK - } -} ----- - -=== 3.5 Type Syntax - -.... -τ ::= α Type variable - | C Type constructor (Nat, Int, Bool, etc.) - | τ τ Type application - | (q x : τ₁) → τ₂ / ε Dependent function type with quantity and effects - | {ρ} Record type (row type) - | own τ Owned type - | ref τ Borrowed reference - | mut τ Mutable reference - | τ where P Refinement type - | ∀(α : κ). τ Universal quantification - -ρ ::= ∅ Empty row - | ℓ : τ Single field - | ρ , ρ Row concatenation - | ρ \ ℓ Row restriction (remove field) - | r Row variable -.... - -=== 3.6 Core Typing Rules - -==== Variables - -.... -────────────────────── (T-Var) -Γ, x :q τ ⊢ x : τ / ∅ - -(consumes q uses of x) -.... - -==== Functions - -.... -Γ, x :q τ₁ ⊢ e : τ₂ / ε -─────────────────────────────────────────── (T-Lam) -Γ ⊢ fn(q x: τ₁) => e : (q x : τ₁) → τ₂ / ε - -Γ ⊢ e₁ : (q x : τ₁) → τ₂ / ε₁ Γ ⊢ e₂ : τ₁ / ε₂ -───────────────────────────────────────────────────── (T-App) -Γ ⊢ e₁(e₂) : τ₂[x ↦ e₂] / ε₁ + ε₂ -.... - -==== Let Bindings - -Per ADR-002, the Let rule scales the value context by the binder’s -quantity, in the QTT-orthodox split-Γ form: - -.... -Γ₁ ⊢ e₁ : τ₁ / ε₁ Γ₂, x :^q τ₁ ⊢ e₂ : τ₂ / ε₂ -─────────────────────────────────────────────────── (T-Let) - q·Γ₁ + Γ₂ ⊢ let x :^q = e₁ in e₂ : τ₂ / ε₁ + ε₂ -.... - -The scaling action `+q·Γ₁+` multiplies every variable’s quantity in -`+Γ₁+` by the binder’s quantity `+q+`, using the semiring multiplication -table from §3.2. The two soundness consequences: - -* *`+q = ω+` (unrestricted)* scales every linear (1) usage in `+Γ₁+` to -unrestricted (ω). Concretely, a `+@linear+` variable consumed once in -`+e₁+` becomes "`used multiple times`" once viewed through the -`+@unrestricted+` binder, and the quantity checker rejects the program. -This is the rule that closes BUG-001 (ω-let smuggling linear values). -* *`+q = 0+` (erased)* scales `+Γ₁+` to the zero context, which means -`+e₁+` carries no runtime obligations and may be erased at runtime. This -is the rule that closes BUG-002 (erasure failure). - -When `+q+` is omitted in source, the rule defaults to `+q = ω+` -(unrestricted), so unannotated lets are unchanged from textbook HM -semantics for non-quantitative programs. - -===== Surface syntax (per ADR-007) - -Two surface forms are accepted; both parse to the same internal -`+el_quantity+` field. The compiler emits the *Option C* form in -diagnostics, the formatter rewrites Option B sugar to Option C unless -`+--keep-quantity-sugar+` is set, and tutorials use Option C -exclusively. - -[cols=",,",options="header",] -|=== -|QTT notation |Option C (primary) |Option B (sugar) -|`+let x :^1 = e+` |`+@linear let x = e+` |`+let x :1 = e+` -|`+let x :^0 = e+` |`+@erased let x = e+` |`+let x :0 = e+` -|`+let x :^ω = e+` |`+@unrestricted let x = e+` |`+let x :ω = e+` -|`+let x = e+` (q omitted) |`+let x = e+` |`+let x = e+` -|=== - -Examples: - -[source,affinescript] ----- -// Option C primary form -@linear let resource = acquire() in use_once(resource); -@erased let _proof = expensive_term() in body_not_using_proof; -@unrestricted let pure_value = 42 in pure_value + pure_value; - -// Option B sugar form (equivalent) -let resource :1 = acquire() in use_once(resource); -let _proof :0 = expensive_term() in body_not_using_proof; -let pure_value :ω = 42 in pure_value + pure_value; ----- - -The same hybrid surface convention applies to function parameters -(`+@linear x: τ+`), lambda parameters, and statement-position let -bindings inside blocks. Sugar form on function parameters is reserved -for a future extension and not currently accepted by the parser; only -the `+@+`-attribute form is available there today. - -==== Records (Row Polymorphism) - -.... -Γ ⊢ e₁ : τ₁ / ε₁ ... Γ ⊢ eₙ : τₙ / εₙ -────────────────────────────────────────────────────── (T-Record) -Γ ⊢ {ℓ₁: e₁, ..., ℓₙ: eₙ} : {ℓ₁: τ₁, ..., ℓₙ: τₙ} / ε₁ + ... + εₙ - -Γ ⊢ e : {ℓ: τ, ρ} / ε -─────────────────────── (T-Field) -Γ ⊢ e.ℓ : τ / ε - -Γ ⊢ e : {ρ} / ε -──────────────────────────────────────── (T-Extend) -Γ ⊢ {ℓ: e', ..e} : {ℓ: τ', ρ} / ε + ε' - -Γ ⊢ e : {ℓ: τ, ρ} / ε -─────────────────────────── (T-Restrict) -Γ ⊢ e \ ℓ : {ρ} / ε -.... - -==== Row Polymorphism and Ownership - -Ownership distributes uniformly over row variables: - -.... -Γ ⊢ e : ref {ℓ: τ, ..r} / ε -────────────────────────────── (T-BorrowRow) -All fields in ..r are immutably borrowed - -Γ ⊢ e : mut {ℓ: τ, ..r} / ε -────────────────────────────── (T-MutBorrowRow) -All fields in ..r are mutably borrowed (exclusive) - -Γ ⊢ e : own {ℓ: τ, ..r} / ε -────────────────────────────── (T-OwnRow) -All fields in ..r are owned -.... - -==== Ownership - -.... -Γ ⊢ e : own τ / ε (e is consumed) -───────────────────────────────────── (T-Move) -Γ ⊢ move e : own τ / ε - -Γ ⊢ e : own τ / ε -─────────────────── (T-Borrow) -Γ ⊢ ref e : ref τ / ε - -Γ ⊢ e : own τ / ε -─────────────────── (T-BorrowMut) -Γ ⊢ mut e : mut τ / ε -.... - -==== Effect Typing - -.... -Γ ⊢ e : τ / ε₁ ε₁ ⊆ ε₂ -────────────────────────── (T-SubEffect) -Γ ⊢ e : τ / ε₂ - -effect E { fn op(x: τ₁) -> τ₂; } Γ ⊢ e : τ₁ / ε -────────────────────────────────────────────────── (T-EffectOp) -Γ ⊢ E.op(e) : τ₂ / E + ε -.... - -''''' - -== PART 4: OPERATIONAL SEMANTICS - -=== 4.1 Values - -.... -v ::= n Natural number - | i Integer - | f Float - | true | false Booleans - | "s" String - | () Unit - | fn(x: τ) => e Function closure - | {ℓ₁: v₁, ..., ℓₙ: vₙ} Record value - | C(v₁, ..., vₙ) Variant value - | [v₁, ..., vₙ] Array value - | ptr(a) Pointer (owned) - | ref(a) Reference (borrowed) -.... - -=== 4.2 Evaluation Contexts - -.... -E ::= [] - | E e Function position - | v E Argument position - | let x = E in e Let binding - | {ℓ₁: v₁, ..., ℓᵢ: E, ...} Record construction - | E.ℓ Field access - | E[e] Array index (array position) - | v[E] Array index (index position) - | if E then e₁ else e₂ Conditional - | match E { ... } Match scrutinee - | handle E with { ... } Effect handler -.... - -=== 4.3 Small-Step Reduction - -.... -(fn(x: τ) => e) v → e[x ↦ v] (β-reduction) - -let x = v in e → e[x ↦ v] (let-reduction) - -{..., ℓ: v, ...}.ℓ → v (field-access) - -{ℓ₁: v₁, ..., ℓᵢ: vᵢ, ...} \ ℓᵢ → (row-restrict) - {ℓ₁: v₁, ..., ℓᵢ₋₁: vᵢ₋₁, ℓᵢ₊₁: vᵢ₊₁, ...} - -if true then e₁ else e₂ → e₁ (if-true) -if false then e₁ else e₂ → e₂ (if-false) - -match C(v₁,...,vₙ) { C(x₁,...,xₙ) => e, ... } (match) - → e[x₁ ↦ v₁, ..., xₙ ↦ vₙ] - -[v₀, ..., vₙ][i] → vᵢ (if 0 ≤ i ≤ n) (array-index) -.... - -=== 4.4 Effect Handling Semantics - -.... -handle (return v) with { return x => eᵣ, ... } (handle-return) - → eᵣ[x ↦ v] - -handle E[op(v)] with { ..., op(x) => eₒₚ, ... } (handle-op) - → eₒₚ[x ↦ v, resume ↦ fn(y) => handle E[y] with {...}] -.... - -=== 4.5 Ownership Semantics - -.... -State σ = Map -Ownership = Owned | Borrowed(n) | Moved - -move(ptr(a), σ) where σ(a) = (v, Owned): - → (v, σ[a ↦ (v, Moved)]) - -ref(ptr(a), σ) where σ(a) = (v, Owned): - → (ref(a), σ[a ↦ (v, Borrowed(1))]) - -drop(ptr(a), σ) where σ(a) = (v, Owned): - → ((), σ \ a) (deallocate) - -(* Use-after-move is a compile-time rejection *) -use(ptr(a), σ) where σ(a) = (_, Moved): - → ERROR -.... - -''''' - -== PART 5: UNSAFE OPERATIONS - -=== 5.1 Unsafe Block - -The `+unsafe+` block permits exactly the following operations: - -[width="100%",cols="35%,25%,40%",options="header",] -|=== -|Operation |Syntax |Description -|Raw read |`+ptr.read()+` |Read value through raw pointer -|Raw write |`+ptr.write(v)+` |Write value through raw pointer -|Pointer arithmetic |`+ptr.offset(n)+` |Offset pointer by n elements -|Transmute |`+transmute[T, U](v)+` |Reinterpret bits as different type -|Forget |`+forget(owned)+` |Leak owned value without destructor -|Assume |`+assume(predicate)+` |Assert refinement without proof -|=== - -=== 5.2 Not Permitted in Unsafe - -Even within `+unsafe+`, the following remain prohibited: - -* Violating type safety (e.g., casting Int to function pointer) -* Accessing private fields of other modules -* Bypassing effect tracking -* Creating invalid enum variants -* Violating memory safety beyond raw pointer operations - -=== 5.3 Examples - -[source,affinescript] ----- -fn dangerousRead(ptr: RawPtr[Int]) -> Int / Pure { - unsafe { ptr.read() } -} - -fn withAssumption(n: Nat) -> Nat where (n > 0) / Pure { - unsafe { - assume(n > 0) // Programmer asserts this - } - n -} - -fn reinterpret(x: u32) -> f32 / Pure { - unsafe { transmute[u32, f32](x) } -} - -fn leakResource(file: own File) -> () / Pure { - unsafe { forget(file) } // Memory leak, but no UB -} ----- - -''''' - -== PART 6: MEMORY MODEL - -=== 6.1 Allocation Strategy - -*Rule:* Values are stack-allocated unless they escape their scope. - -[cols=",",options="header",] -|=== -|Situation |Allocation -|Local `+let+` binding, not returned |Stack -|Returned from function |Heap (owned pointer) -|Captured by closure |Heap (moved into closure struct) -|Stored in growable collection |Heap -|Recursive type (e.g., linked list) |Heap for recursive part -|Behind `+ref+`/`+mut+` borrow |Inherits from referent -|=== - -[source,affinescript] ----- -fn stackOnly() -> Int / Pure { - let x = 42; // Stack - let r = {a: 1, b: 2}; // Stack (doesn't escape) - r.a + x -} - -fn needsHeap() -> own {x: Int} / Pure { - let r = {x: 42}; // Heap - returned (escapes) - r -} ----- - -=== 6.2 Explicit Boxing - -[source,affinescript] ----- -type Box[T] = own { ptr: RawPtr[T] } - -fn box[T](value: own T) -> own Box[T] / Pure { - Box { ptr: heapAlloc(value) } -} - -fn unbox[T](b: own Box[T]) -> own T / Pure { - let value = unsafe { b.ptr.read() }; - unsafe { forget(b) }; - value -} ----- - -=== 6.3 Drop Order - -Values are dropped in reverse declaration order: - -[source,affinescript] ----- -fn example() -> () / IO { - let a = open("a.txt")?; // Dropped third - let b = open("b.txt")?; // Dropped second - let c = open("c.txt")?; // Dropped first - // implicit: drop(c); drop(b); drop(a); -} ----- - -=== 6.4 WASM Mapping - -[cols=",",options="header",] -|=== -|AffineScript |WASM -|Stack values |WASM locals, passed by value -|Heap values |WASM-GC `+ref+` types or linear memory -|Borrows |WASM `+ref+` (compiler proves no use-after-free) -|Closures |Struct with funcref + captured values -|=== - -''''' - -== PART 7: STANDARD LIBRARY - -=== 7.1 Primitive Types - -[source,affinescript] ----- -type Nat = /* built-in natural numbers, 0, 1, 2, ... */ -type Int = /* built-in 64-bit signed integers */ -type Float = /* built-in 64-bit floats */ -type Bool = true | false -type String = /* built-in UTF-8 string */ -type Char = /* built-in Unicode scalar value */ -type Never = /* uninhabited type */ ----- - -=== 7.2 Standard Effects - -[source,affinescript] ----- -effect IO { - fn print(s: String); - fn println(s: String); - fn readLine() -> String; - fn readFile(path: String) -> String; - fn writeFile(path: String, content: String); -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -effect Async { - fn await[T](future: Future[T]) -> T; - fn spawn[T](f: () -> T / Async) -> Future[T]; -} - -effect State[S] { - fn get() -> S; - fn put(s: S); - fn modify(f: S -> S); -} - -// Div is special - indicates potential non-termination -// Cannot be handled, only discharged at program boundary -effect Div { } ----- - -=== 7.3 Option and Result - -[source,affinescript] ----- -type Option[T] = - | None - | Some(T) - -type Result[T, E] = - | Ok(T) - | Err(E) - -impl[T] Option[T] { - fn map[U](self, f: T -> U / Pure) -> Option[U] / Pure { - match self { - None => None, - Some(x) => Some(f(x)) - } - } - - fn unwrapOr(self, default: T) -> T / Pure { - match self { - None => default, - Some(x) => x - } - } -} - -impl[T, E] Result[T, E] { - fn map[U](self, f: T -> U / Pure) -> Result[U, E] / Pure { - match self { - Err(e) => Err(e), - Ok(x) => Ok(f(x)) - } - } - - fn mapErr[F](self, f: E -> F / Pure) -> Result[T, F] / Pure { - match self { - Ok(x) => Ok(x), - Err(e) => Err(f(e)) - } - } -} ----- - -=== 7.5 Core Traits - -[source,affinescript] ----- -trait Eq { - fn eq(ref self, other: ref Self) -> Bool / Pure; - - fn neq(ref self, other: ref Self) -> Bool / Pure { - !self.eq(other) - } -} - -trait Ord: Eq { - fn cmp(ref self, other: ref Self) -> Ordering / Pure; - - fn lt(ref self, other: ref Self) -> Bool / Pure { - self.cmp(other) == Less - } - - fn le(ref self, other: ref Self) -> Bool / Pure { - self.cmp(other) != Greater - } - - fn gt(ref self, other: ref Self) -> Bool / Pure { - self.cmp(other) == Greater - } - - fn ge(ref self, other: ref Self) -> Bool / Pure { - self.cmp(other) != Less - } -} - -type Ordering = Less | Equal | Greater - -trait Show { - fn show(ref self) -> String / Pure; -} - -trait Clone { - fn clone(ref self) -> Self / Pure; -} - -trait Default { - fn default() -> Self / Pure; -} - -trait Drop { - fn drop(own self) -> () / Pure; -} - -trait Functor[F: Type -> Type] { - fn map[A, B](self: F[A], f: A -> B / Pure) -> F[B] / Pure; -} - -trait Monad[M: Type -> Type]: Functor[M] { - fn pure[A](value: A) -> M[A] / Pure; - fn flatMap[A, B](self: M[A], f: A -> M[B] / Pure) -> M[B] / Pure; -} - -trait Iterator { - type Item; - fn next(mut self) -> Option[Self::Item] / Pure; - - fn collect[C: FromIterator[Self::Item]](self) -> C / Pure { - C.fromIter(self) - } -} - -trait FromIterator[T] { - fn fromIter[I: Iterator where I::Item = T](iter: I) -> Self / Pure; -} ----- - -=== 7.6 Owned Resources - -[source,affinescript] ----- -type File = own { fd: Int } - -fn open(path: ref String) -> Result[own File, IOError] / IO + Exn[IOError] - -fn read(file: ref File, buf: mut [u8]) -> Result[Nat, IOError] / IO + Exn[IOError] - -fn write(file: mut File, buf: ref [u8]) -> Result[Nat, IOError] / IO + Exn[IOError] - -fn close(file: own File) -> Result[(), IOError] / IO - -impl Drop for File { - fn drop(own self) -> () / Pure { - // Note: drop is Pure but may internally use IO - // This is safe because drop is called deterministically - let _ = close(self); - } -} - -fn withFile[T]( - path: ref String, - action: (ref File) -> Result[T, IOError] / IO + Exn[IOError] -) -> Result[T, IOError] / IO + Exn[IOError] { - let file = open(path)?; - let result = action(ref file); - close(file)?; - result -} ----- - -=== 7.7 Row-Polymorphic Functions - -[source,affinescript] ----- -// Get a field from any record that has it -fn getX[..r](record: {x: Int, ..r}) -> Int / Pure { - record.x -} - -// Add a field to any record -fn withY[..r](record: {..r}, y: Int) -> {y: Int, ..r} / Pure { - {y: y, ..record} -} - -// Update a field -fn mapX[..r](record: {x: Int, ..r}, f: Int -> Int / Pure) -> {x: Int, ..r} / Pure { - {x: f(record.x), ..record} -} - -// Remove a field -fn removeX[..r](record: own {x: Int, ..r}) -> (own Int, own {..r}) / Pure { - (record.x, record \ x) -} - -// Merge two records (requires disjoint fields) -fn merge[..r, ..s](a: {..r}, b: {..s}) -> {..r, ..s} / Pure - where (r disjoint s) -{ - {..a, ..b} -} ----- - -''''' - -== PART 8: EXAMPLE PROGRAMS - -=== 8.1 Basic Arithmetic - -[source,affinescript] ----- -fn add(a: Int, b: Int) -> Int / Pure { - a + b -} - -total fn factorial(n: Nat) -> Nat / Pure { - match n { - 0 => 1, - _ => n * factorial(n - 1) - } -} - -total fn fibonacci(n: Nat) -> Nat / Pure { - match n { - 0 => 0, - 1 => 1, - _ => fibonacci(n - 1) + fibonacci(n - 2) - } -} ----- - -=== 8.2 Safe Array Access - -[source,affinescript] ----- -// Index must be less than length - proved at compile time -total fn safeGet[n: Nat, T]( - arr: ref Vec[n, T], - i: Nat where (i < n) -) -> ref T / Pure { - match (arr, i) { - (Cons(h, _), 0) => ref h, - (Cons(_, t), _) => safeGet(ref t, i - 1) - } -} - -// Split vector at compile-time known position -total fn splitAt[n: Nat, m: Nat, T]( - v: Vec[n + m, T] -) -> (Vec[n, T], Vec[m, T]) / Pure { - match n { - 0 => (Nil, v), - _ => { - let Cons(h, t) = v; - let (left, right) = splitAt[n - 1, m, T](t); - (Cons(h, left), right) - } - } -} ----- - -=== 8.3 Effect Polymorphism - -[source,affinescript] ----- -// Map is polymorphic in effects -fn map[A, B, E](xs: List[A], f: A -> B / E) -> List[B] / E { - match xs { - Nil => Nil, - Cons(h, t) => Cons(f(h), map(t, f)) - } -} - -// Can use with any effect -fn example() -> () / IO { - let nums = [1, 2, 3]; - - // E = Pure - let doubled = map(nums, |n| n * 2); - - // E = IO - let logged = map(nums, |n| { println(n.show()); n }); -} - -// Pure context requires Pure effects -total fn pureMap[A, B](xs: List[A], f: A -> B / Pure) -> List[B] / Pure { - map(xs, f) -} ----- - -=== 8.4 Resource Management - -[source,affinescript] ----- -fn processFile(path: ref String) -> Result[String, IOError] / IO + Exn[IOError] { - let file = open(path)?; - try { - let mut buf = [0u8; 1024]; - let n = read(ref file, mut buf)?; - Ok(String.fromUtf8(buf[0..n])) - } finally { - close(file)?; - } -} - -// Using the RAII pattern -fn processFileSafe(path: ref String) -> Result[String, IOError] / IO + Exn[IOError] { - withFile(path, |file| { - let mut buf = [0u8; 1024]; - let n = read(file, mut buf)?; - Ok(String.fromUtf8(buf[0..n])) - }) -} ----- - -=== 8.5 State Effect - -[source,affinescript] ----- -fn counter() -> Int / State[Int] { - let current = State.get(); - State.put(current + 1); - current -} - -fn runCounter() -> (Int, Int, Int) / Pure { - handle { - let a = counter(); - let b = counter(); - let c = counter(); - (a, b, c) - } with { - return x => x, - get() => resume(0), // Initial state - put(s) => resume(()), - } - // Note: This simple handler doesn't actually thread state - // A real implementation would need more sophisticated handling -} ----- - -=== 8.6 Combining Features - -[source,affinescript] ----- -// Buffer with compile-time capacity tracking + ownership -type Buffer[capacity: Nat] = own { - data: own [u8; capacity], - len: Nat where (len <= capacity), -} - -fn Buffer.new[cap: Nat]() -> own Buffer[cap] / Pure { - Buffer { - data: [0u8; cap], - len: 0, - } -} - -fn Buffer.write[cap: Nat]( - own self: Buffer[cap], - data: ref [u8], -) -> Result[own Buffer[cap], BufferFull] / Pure - where (self.len + len(data) <= cap) -{ - copy(data, mut self.data[self.len..]); - Ok(Buffer { data: self.data, len: self.len + len(data) }) -} - -fn Buffer.free[cap: Nat](own self: Buffer[cap]) -> () / Pure { - // Ownership consumed - memory freed -} - -// Usage -fn example() -> () / Pure { - let buf = Buffer.new[1024](); - let buf = buf.write(ref "hello")?; - let buf = buf.write(ref " world")?; - buf.free(); -} ----- - -=== 8.7 Traits and Generics - -[source,affinescript] ----- -impl Eq for Int { - fn eq(ref self, other: ref Int) -> Bool / Pure { - *self == *other - } -} - -impl Ord for Int { - fn cmp(ref self, other: ref Int) -> Ordering / Pure { - if *self < *other { Less } - else if *self > *other { Greater } - else { Equal } - } -} - -impl Show for Int { - fn show(ref self) -> String / Pure { - intToString(*self) - } -} - -impl[T: Show] Show for Option[T] { - fn show(ref self) -> String / Pure { - match self { - None => "None", - Some(x) => "Some(" ++ x.show() ++ ")" - } - } -} - -impl[T: Show, E: Show] Show for Result[T, E] { - fn show(ref self) -> String / Pure { - match self { - Ok(x) => "Ok(" ++ x.show() ++ ")", - Err(e) => "Err(" ++ e.show() ++ ")" - } - } -} - -fn printAny[T: Show](value: ref T) -> () / IO { - println(value.show()) -} - -fn sortBy[T, K: Ord](items: mut [T], key: (ref T) -> K / Pure) -> () / Pure { - // Sorting implementation using Ord trait -} ----- - -''''' - -== PART 9: COMPILATION TO WASM - -=== 9.1 Type Mapping - -[width="100%",cols="70%,30%",options="header",] -|=== -|AffineScript |WASM -|`+Nat+`, `+Int+` |`+i64+` - -|`+Float+` |`+f64+` - -|`+Bool+` |`+i32+` (0 or 1) - -|`+Char+` |`+i32+` (Unicode scalar) - -|`+String+` |`+(ref (array i8))+` + length - -|`+{x: T, y: U}+` |`+(ref (struct (field $x T') (field $y U')))+` - -|`+Vec[n, T]+` |`+(ref (array T'))+` with length `+n+` - -|`+own T+` |`+(ref T')+` (ownership tracked statically) - -|`+ref T+` |`+(ref T')+` (borrowing tracked statically) - -|`+T -> U / ε+` -|`+(ref (struct (field $func funcref) (field $env ...)))+` - -|`+Option[T]+` |Tagged union or nullable ref - -|`+A \| B(T)+` |Tagged struct hierarchy -|=== - -=== 9.2 Ownership and Quantity Erasure - -Ownership and quantities are erased at runtime. They’re purely -compile-time disciplines: - -[source,affinescript] ----- -// Source -fn useFile(own file: File) -> () / IO { close(file) } - -fn replicate[0 n: Nat, T](value: T) -> Vec[n, T] { ... } ----- - -[source,wat] ----- -;; WASM - no ownership marker, no n parameter -(func $useFile (param $file (ref $File)) - (call $close (local.get $file))) - -(func $replicate (param $value (ref $T)) (result (ref $Vec)) - ;; n is erased, known at compile time - ...) ----- - -=== 9.3 Effect Compilation - -Effects compile to either: 1. *Direct style:* For effects handled in the -same function 2. *CPS transform:* For effects that cross function -boundaries - -[source,affinescript] ----- -// Source -fn greet() -> () / IO { - println("Hello"); -} ----- - -[source,wat] ----- -;; WASM - IO effect becomes direct call to runtime -(func $greet - (call $runtime_println (... "Hello" ...))) ----- - -=== 9.4 Row Polymorphism Compilation - -Monomorphize at call sites: - -[source,affinescript] ----- -fn getX[..r](rec: {x: Int, ..r}) -> Int { rec.x } - -let a = getX({x: 1, y: 2}) -let b = getX({x: 3, z: "hi"}) ----- - -Compiles to specialized functions: - -[source,wat] ----- -(func $getX_xy (param $rec (ref $struct_x_y)) (result i64) - (struct.get $struct_x_y $x (local.get $rec))) - -(func $getX_xz (param $rec (ref $struct_x_z)) (result i64) - (struct.get $struct_x_z $x (local.get $rec))) ----- - -=== 9.5 Closure Compilation - -[source,affinescript] ----- -fn makeAdder(n: Int) -> (Int -> Int / Pure) / Pure { - |x| x + n -} ----- - -[source,wat] ----- -(type $closure_adder (struct - (field $func (ref $func_int_int)) - (field $n i64))) - -(func $adder_impl (param $env (ref $closure_adder)) (param $x i64) (result i64) - (i64.add - (local.get $x) - (struct.get $closure_adder $n (local.get $env)))) - -(func $makeAdder (param $n i64) (result (ref $closure_adder)) - (struct.new $closure_adder - (ref.func $adder_impl) - (local.get $n))) ----- - -''''' - -== PART 10: IMPLEMENTATION GUIDE - -=== 10.1 Compiler Phases - -.... -Source Code - │ - ▼ -┌─────────┐ -│ Lexer │ → Token stream -└─────────┘ - │ - ▼ -┌─────────┐ -│ Parser │ → Concrete Syntax Tree (CST) -└─────────┘ - │ - ▼ -┌─────────┐ -│Desugarer│ → Abstract Syntax Tree (AST) -└─────────┘ - │ - ▼ -┌─────────────┐ -│Name Resolver│ → AST with resolved names + modules -└─────────────┘ - │ - ▼ -┌─────────────┐ -│Type Checker │ → Typed AST + effect constraints -└─────────────┘ - │ - ▼ -┌─────────────┐ -│Borrow Check │ → Verified ownership -└─────────────┘ - │ - ▼ -┌────────────┐ -│Trait Solver│ → Resolved trait impls -└────────────┘ - │ - ▼ -┌───────────┐ -│Monomorphize│ → Specialized functions -└───────────┘ - │ - ▼ -┌──────────┐ -│Lower to IR│ → Low-level IR -└──────────┘ - │ - ▼ -┌──────────┐ -│WASM Emit │ → .wasm binary -└──────────┘ -.... - -=== 10.2 Key Implementation Challenges - -==== Challenge 1: Bidirectional Type Checking - -.... -infer(Γ, e) → (τ, ε) Synthesize type from expression -check(Γ, e, τ) → ε Check expression against type -.... - -Bidirectional checking threads through expression forms uniformly and -gives better error messages than pure synthesis. Combined with full HM -inference, it keeps annotation requirements low while maintaining -soundness. - -==== Challenge 2: Row Polymorphism Inference - -Use row unification: - Row variables unify with partial rows - Track -lacks constraints: `+r \ ℓ+` means r doesn’t have field ℓ - Propagate -constraints through function calls - -==== Challenge 3: Borrow Checking - -Similar to Rust’s borrow checker: - Build control flow graph - Track -liveness of owned values - Verify borrows don’t outlive owners - Verify -no aliasing of mutable borrows - Handle row variables uniformly - -==== Challenge 4: Totality Checking - -For `+total+` functions: - Structural recursion: recursive calls on -structurally smaller args - Well-founded recursion: prove termination -metric decreases - Coverage: all pattern matches are exhaustive - -==== Challenge 5: Effect Polymorphism - -* Track effect variables through unification -* Propagate effect constraints -* Handle effect subtyping (ε₁ ⊆ ε₁ + ε₂) - -==== Challenge 6: Trait Resolution - -* Build trait impl database -* Implement coherence checking (orphan rule, overlap) -* Resolve associated types -* Handle trait bounds in generics - -=== 10.3 Suggested Implementation Order - -[arabic] -. *Lexer + Parser* (2-3 weeks) -* Use parser combinator or generator -* Build CST, then desugar to AST -* Include module syntax -. *Basic Type Checker* (3-4 weeks) -* Simple types first (Int, Bool, functions) -* Add records (non-polymorphic) -* Add variants -. *Module System* (1-2 weeks) -* Name resolution across modules -* Visibility checking -* Import/export -. *Ownership System* (2-3 weeks) -* Add own/ref/mut modifiers -* Implement linear type checking -* Implement borrow checking -. *Row Polymorphism* (2-3 weeks) -* Add row variables -* Implement row unification -* Handle lacks constraints -. *Traits* (2-3 weeks) -* Trait declarations -* Impl blocks -* Trait bounds -* Associated types - -[arabic, start=8] -. *Extensible Effects* (2-3 weeks) -* Effect declarations -* Effect polymorphism -* Effect constraint propagation -* Basic handlers (if desired) -. *WASM Backend* (4-6 weeks) -* Implement type mapping -* Emit WASM-GC -* Handle closures -* Monomorphization -* Generate JS glue - -=== 10.4 Testing Strategy - -.... -tests/ -├── lexer/ # Token output tests -├── parser/ # AST output tests -├── modules/ # Module resolution tests -├── types/ -│ ├── positive/ # Should type-check -│ └── negative/ # Should reject -├── ownership/ -│ ├── positive/ # Valid ownership patterns -│ └── negative/ # Ownership violations -├── rows/ # Row polymorphism tests -├── traits/ # Trait resolution tests -├── dependent/ # Dependent type tests -├── effects/ # Effect tracking tests -├── codegen/ # WASM output tests -└── e2e/ # Full program tests -.... - -''''' - -== PART 11: ERROR MESSAGES - -=== 11.1 Ownership Errors - -.... -error[E0501]: cannot use `file` after move - --> src/main.affine:10:5 - | - 8 | close(file); - | ---- value moved here - 9 | -10 | read(file); - | ^^^^ value used after move - | - = help: consider using `ref file` if you need to read without consuming -.... - -.... -error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable - --> src/main.affine:5:10 - | - 4 | let r = ref x; - | ----- immutable borrow occurs here - 5 | let m = mut x; - | ^^^^^ mutable borrow occurs here - 6 | println(r); - | - immutable borrow later used here -.... - -=== 11.2 Type Errors - -.... -error[E0308]: mismatched types - --> src/main.affine:5:12 - | - 5 | head(empty) - | ^^^^^ expected `Vec[n + 1, T]`, found `Vec[0, T]` - | - = note: `head` requires a non-empty vector - = help: the type `Vec[0, T]` is empty, so `head` cannot be called -.... - -.... -error[E0309]: refinement predicate not satisfied - --> src/main.affine:8:15 - | - 8 | safeGet(arr, 10) - | ^^ cannot prove `10 < 5` - | - = note: array has length 5, but index is 10 -.... - -=== 11.3 Effect Errors - -.... -error[E0601]: effect not handled - --> src/main.affine:3:5 - | - 3 | println("hello") - | ^^^^^^^^^^^^^^^^ this has effect `IO` - | - = note: function `pureFunction` is declared as `/ Pure` - = help: either add `IO` to the function's effects or handle it -.... - -.... -warning[W0602]: owned resource may leak on exception - --> src/main.affine:8:5 - | - 7 | let file = open(path)?; - | ---- owned resource acquired here - 8 | mayThrow(); - | ^^^^^^^^^^ this may throw - 9 | close(file); - | ----------- resource closed here - | - = help: use `try`/`finally` to ensure resource is closed - = help: or use `withFile` pattern for automatic cleanup -.... - -=== 11.4 Trait Errors - -.... -error[E0401]: no implementation of trait `Show` for type `MyType` - --> src/main.affine:5:5 - | - 5 | println(x.show()) - | ^^^^^^^^ `Show` is not implemented for `MyType` - | - = help: implement the trait: - | - | impl Show for MyType { - | fn show(ref self) -> String / Pure { - | // ... - | } - | } -.... - -=== 11.5 Module Errors - -.... -error[E0701]: cannot find `Vec` in this scope - --> src/main.affine:3:10 - | - 3 | let v: Vec[3, Int] = Nil; - | ^^^ not found in this scope - | - = help: add `use Data.Vec::Vec;` at the top of the file -.... - -.... -error[E0702]: function `helper` is private - --> src/main.affine:5:5 - | - 5 | Utils.helper() - | ^^^^^^^^^^^^^^ private function - | - = note: `helper` is defined in `Utils` but not exported -.... - -''''' - -== APPENDIX A: GRAMMAR SUMMARY - -=== A.1 Complete EBNF Grammar (Consolidated) - -[source,ebnf] ----- -(* === PROGRAM === *) -program = [ module_decl ] { import_decl } { top_level } ; -top_level = type_decl | fn_decl | trait_decl | impl_block | effect_decl | const_decl ; - -(* === MODULES === *) -module_decl = 'module' module_path ';' ; -module_path = UPPER_IDENT { '.' UPPER_IDENT } ; -import_decl = 'use' import_path [ 'as' IDENT ] ';' - | 'use' import_path '::' '{' import_list '}' ';' - | 'use' import_path '::' '*' ';' ; -import_path = [ '::' ] module_path [ '::' IDENT ] ; -import_list = import_item { ',' import_item } ; -import_item = IDENT [ 'as' IDENT ] ; -visibility = [ 'pub' [ '(' pub_scope ')' ] ] ; -pub_scope = 'crate' | 'super' | module_path ; - -(* === TYPES === *) -type_decl = type_alias | struct_decl | enum_decl ; -type_alias = visibility 'type' UPPER_IDENT [ type_params ] '=' type_expr ';' ; -struct_decl = visibility 'struct' UPPER_IDENT [ type_params ] - '{' field_decl { ',' field_decl } [ ',' ] '}' ; -enum_decl = visibility 'enum' UPPER_IDENT [ type_params ] - '{' variant_decl { ',' variant_decl } [ ',' ] '}' ; -field_decl = visibility IDENT ':' type_expr ; -variant_decl = UPPER_IDENT (* nullary *) - | UPPER_IDENT '(' type_expr { ',' type_expr } ')' (* positional *) - | UPPER_IDENT '(' type_expr { ',' type_expr } ')' ':' type_expr (* GADT *) - ; -type_params = '[' type_param { ',' type_param } ']' ; -type_param = [ QUANTITY ] IDENT [ ':' kind ] ; -kind = 'Type' | 'Nat' | 'Row' | 'Effect' | kind '->' kind ; - -type_expr = type_atom [ '->' type_expr [ '/' effects ] ] ; -type_atom = PRIM_TYPE | UPPER_IDENT | TYPE_VAR | row_type - | 'own' type_atom | 'ref' type_atom | 'mut' type_atom - | '(' type_expr ')' | '(' type_expr { ',' type_expr } ')' - | UPPER_IDENT '[' type_arg { ',' type_arg } ']' ; -type_arg = type_expr ; -row_type = '{' [ field_type { ',' field_type } [ ',' ROW_VAR ] | ROW_VAR ] '}' ; -field_type = IDENT ':' type_expr ; - -effects = effect_term { '+' effect_term } ; -effect_term = UPPER_IDENT [ '[' type_arg { ',' type_arg } ']' ] | EFFECT_VAR ; - -(* nat_expr and predicate productions removed 2026-04-10. - Dependent function types ((x : T) -> U), refinement types (T where P), - nat_expr, and predicate are out of scope. See ANCHOR.a2ml. *) - -(* === EFFECTS === *) -effect_decl = visibility 'effect' UPPER_IDENT [ type_params ] '{' { effect_op } '}' ; -effect_op = 'fn' LOWER_IDENT '(' [ param_list ] ')' [ '->' type_expr ] ';' ; - -(* === TRAITS === *) -trait_decl = visibility 'trait' UPPER_IDENT [ type_params ] [ ':' trait_bounds ] - '{' { trait_item } '}' ; -trait_bounds = UPPER_IDENT { '+' UPPER_IDENT } ; -trait_item = fn_sig ';' | fn_decl | 'type' UPPER_IDENT [ ':' kind ] [ '=' type_expr ] ';' ; -fn_sig = visibility 'fn' LOWER_IDENT [ type_params ] '(' [ param_list ] ')' - [ '->' type_expr ] [ '/' effects ] ; - -impl_block = 'impl' [ type_params ] [ trait_ref 'for' ] type_expr - [ where_clause ] '{' { impl_item } '}' ; -trait_ref = UPPER_IDENT [ '[' type_arg { ',' type_arg } ']' ] ; -impl_item = fn_decl | 'type' UPPER_IDENT '=' type_expr ';' ; - -(* === FUNCTIONS === *) -fn_decl = visibility [ 'total' ] 'fn' LOWER_IDENT [ type_params ] - '(' [ param_list ] ')' [ '->' type_expr ] [ '/' effects ] - [ where_clause ] fn_body ; -param_list = param { ',' param } ; -param = [ QUANTITY ] [ OWNERSHIP ] IDENT ':' type_expr ; -OWNERSHIP = 'own' | 'ref' | 'mut' ; -QUANTITY = '0' | '1' | 'ω' ; -where_clause = 'where' constraint { ',' constraint } ; -constraint = predicate | TYPE_VAR ':' trait_bound ; -trait_bound = UPPER_IDENT { '+' UPPER_IDENT } ; -fn_body = block | '=' expr ; - -(* === EXPRESSIONS === *) -expr = let_expr | if_expr | match_expr | fn_expr | try_expr - | handle_expr | return_expr | unsafe_expr | binary_expr ; -let_expr = 'let' [ 'mut' ] pattern [ ':' type_expr ] '=' expr [ 'in' expr ] ; -if_expr = 'if' expr block [ 'else' ( if_expr | block ) ] ; -match_expr = 'match' expr '{' { match_arm } '}' ; -match_arm = pattern [ 'if' expr ] '=>' expr [ ',' ] ; -fn_expr = '|' [ param_list ] '|' expr | 'fn' '(' [ param_list ] ')' fn_body ; -try_expr = 'try' block [ 'catch' '{' { catch_arm } '}' ] [ 'finally' block ] ; -catch_arm = pattern '=>' expr [ ',' ] ; -handle_expr = 'handle' expr 'with' '{' { handler_arm } '}' ; -handler_arm = 'return' pattern '=>' expr [ ',' ] - | LOWER_IDENT '(' [ pattern { ',' pattern } ] ')' '=>' expr [ ',' ] ; -return_expr = 'return' [ expr ] ; -unsafe_expr = 'unsafe' '{' { unsafe_stmt } '}' ; -block = '{' { statement } [ expr ] '}' ; - -binary_expr = unary_expr { BINARY_OP unary_expr } ; -unary_expr = [ UNARY_OP ] postfix_expr ; -postfix_expr = primary_expr { postfix } ; -postfix = '.' IDENT | '.' INT_LIT | '[' expr ']' | '(' [ arg_list ] ')' - | '::' UPPER_IDENT | '\\' IDENT ; -primary_expr = LITERAL | IDENT | '(' expr ')' | '(' expr { ',' expr } ')' - | '[' [ expr { ',' expr } ] ']' - | '{' [ field_init { ',' field_init } ] [ '..' expr ] '}' - | 'resume' '(' [ expr ] ')' ; -field_init = IDENT [ ':' expr ] ; - -(* === PATTERNS === *) -pattern = '_' | IDENT | LITERAL | UPPER_IDENT [ '(' pattern { ',' pattern } ')' ] - | '(' pattern { ',' pattern } ')' | '{' field_pat { ',' field_pat } [ '..' ] '}' - | pattern '|' pattern | IDENT '@' pattern ; -field_pat = IDENT [ ':' pattern ] ; - -(* === STATEMENTS === *) -statement = let_expr ';' | expr ';' | postfix_expr ASSIGN_OP expr ';' - | 'while' expr block | 'for' pattern 'in' expr block ; - -(* === TOKENS === *) -LOWER_IDENT = /[a-z][a-zA-Z0-9_]*/ ; -UPPER_IDENT = /[A-Z][a-zA-Z0-9_]*/ ; -TYPE_VAR = /[a-z][a-zA-Z0-9_]*'?/ ; -EFFECT_VAR = /[a-z][a-zA-Z0-9_]*/ ; -ROW_VAR = /\.\.[a-z][a-zA-Z0-9_]*/ ; -INT_LIT = /-?[0-9]+/ | /0x[0-9a-fA-F]+/ | /0b[01]+/ | /0o[0-7]+/ ; -FLOAT_LIT = /-?[0-9]+\.[0-9]+([eE][+-]?[0-9]+)?/ ; -STRING_LIT = /"([^"\\]|\\.)*"/ ; -CHAR_LIT = /'([^'\\]|\\.)'/ ; -BINARY_OP = /[+\-*\/%]/ | /[<>=!]=?/ | /&&/ | /\|\|/ | /[&|^]/ | /<<|>>/ ; -UNARY_OP = /[-!~&*]/ ; -CMP_OP = /[<>=!]=?/ ; -ASSIGN_OP = /=/ | /[+\-*\/]=/ ; -PRIM_TYPE = 'Nat' | 'Int' | 'Bool' | 'Float' | 'String' | 'Char' | 'Type' | 'Never' ; ----- - -''''' - -== APPENDIX B: CHANGE LOG - -=== B.1 Version 2.0 Changes (from 1.0) - -[width="100%",cols="39%,61%",options="header",] -|=== -|Change |Description -|Partial by default |Functions are partial by default; only `+total+` -annotation exists - -|Quantity 0 semantics |Erased values are compile-time only, fully -specified - -|Strict `+unsafe+` |Only 6 specific operations permitted - -|Row + ownership |Ownership distributes uniformly over row variables - -|Refinement effects |Predicates can have effects (warning if not Pure) - -|Traits |Full specification added - -|Modules |Hierarchical module system with visibility - -|Memory model |Stack-by-default, heap-on-escape strategy - -|Extensible effects |User-defined effects replace fixed set - -|Row restriction |Value-level `+\+` operator for splitting records - -|Effect handlers |`+handle+`/`+with+`/`+resume+` syntax added -|=== - -''''' - -_End of AffineScript Language Specification v2.0_ diff --git a/affinescript/docs/specs/SETTLED-DECISIONS.adoc b/affinescript/docs/specs/SETTLED-DECISIONS.adoc deleted file mode 100644 index d1f98fa..0000000 --- a/affinescript/docs/specs/SETTLED-DECISIONS.adoc +++ /dev/null @@ -1,135 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= AffineScript: Settled Design Decisions -Jonathan D.A. Jewell -:toc: -:toclevels: 2 -:icons: font - -This document records design decisions that are fully settled. Each entry -gives the decision, the reasoning, and a pointer to the full ADR in -`.machine_readable/descriptiles/META.a2ml`. - -These are not open questions. Do not reopen them without amending the ADR. - -== Effect Invocation Syntax: Direct Call (ADR-008) - -Effect operations are called with plain call syntax. No `perform` keyword. - -[source] ----- -fn fetch_user(id: Int) -> User / Http, Async { - let resp = Http.get("/users/" ++ show(id)) - Async.await(resp.json()) -} ----- - -The effects are declared *once* — in the return type (`/ Http, Async`). The -call sites look like ordinary function calls. The type signature is the -contract. - -`perform` at every call site is noise that contradicts the ergonomics goal. -Face parsers (Python-face, JS-face) can map `async/await` naturally to the -`Async` effect via desugaring — no `perform` concept needed. - -== Conformance Suite Is Authoritative (ADR-009) - -The spec is the source of truth. The parser must conform to the spec. - -Standing rule: *"Language scope lives in a written thesis. Thesis -authoritative, code must conform. If disagreement, code is wrong."* - -As of 2026-04-11, 8/12 valid conformance tests fail to parse. Required fixes: - -. *PascalCase type names* — `Int`, `String`, `Bool`, `Option`, user-defined - types are PascalCase. The parser's `ident` rule (lowercase only) must be - split: `ident` for values, `type_ident` (uppercase-leading) for types. -. *Enum declaration syntax* — parser must match the spec's enum syntax. -. *Effect op type parameters* — parser must support type parameters on effect - operations. - -Target: 12/12 conformance tests passing. Once passing, any future parser -change that breaks a conformance test is a bug, not a spec disagreement. - -== QTT Surface Syntax: @-Annotations Primary (ADR-007) - -The surface forms for quantity annotations are: - -[source] ----- -@linear — used exactly once (:1 sugar also accepted) -@erased — compile-time only, not at runtime (:0 sugar also accepted) -@unrestricted — any number of uses (:ω sugar also accepted) - (default — omitting the annotation means @unrestricted) ----- - -`@linear`/`@erased`/`@unrestricted` are the primary forms used in tutorials, -error messages, IDE tooling, and generated code. The `:1`/`:0`/`:ω` numeric -forms are accepted as sugar (preserved for QTT paper compatibility and for -users porting from Idris2/Granule) but are never the canonical form. - -Type parameters on generic functions are *implicitly erased* — no annotation -needed: - -[source] ----- -fn get_name[..r](obj: { name: String, ..r }) -> String = obj.name -// ^^^ — implicitly @erased (compile-time only) ----- - -The `own`/`ref`/`mut` ownership keywords on function parameters are surface -syntax for common combinations: - -[source] ----- -own T → @linear T (caller transfers ownership) -ref T → @unrestricted T (read-only borrowed view) -mut T → @linear T + mutation rights (exclusive mutable access) ----- - -== Face-Aware Error Formatting Is a First-Class Toolchain Concern (ADR-010) - -When a developer writes Python-face AffineScript and hits a type error, the -error must be expressed in Python-face terms — not raw AffineScript canonical -syntax. - -Architecture: - -[source] ----- -compiler (canonical error representation, face-agnostic) - ↓ -face-aware error formatter ← separate layer, swappable - ↓ -terminal / IDE / LSP ----- - -The compiler itself has no face knowledge. The formatter receives -`(error, active-face)` and produces the display string. Each face ships an -error vocabulary mapping (e.g. Python-face: "affine binding" → "single-use -variable"). - -This is a *design commitment*, not an immediate implementation task. The -compiler's error representation must be designed with formatability in mind -from the start — errors must carry enough structured information for the -formatter to reconstruct face-appropriate messages. - -== typed-wasm: Dual Role (typed-wasm ADR-004) - -The `typed-wasm` repository has two distinct, compatible roles: - -. *Primary*: TypeLL 10-level type safety for WebAssembly linear memory. - WASM linear memory as a schemaless database with schemas (regions) and - type-safe access operations, verified through 10 progressive levels. - -. *Secondary*: Aggregate library — the convergence infrastructure for - AffineScript and Ephapax. Both languages compile to typed WasmGC and need - agreed binary conventions (type layout, ABI, stdlib types, linking). - The Idris2 ABI infrastructure that proves the 10-level claims also proves - the cross-language layout contracts. - -Neither role is subordinate. They are kept distinct in documentation and in -`src/abi/` (separate Idris2 modules, clear naming). - -See `typed-wasm/docs/architecture/AGGREGATE-LIBRARY-VISION.adoc` for the -aggregate library design. diff --git a/affinescript/docs/specs/SPEC.adoc b/affinescript/docs/specs/SPEC.adoc deleted file mode 100644 index 8b0346e..0000000 --- a/affinescript/docs/specs/SPEC.adoc +++ /dev/null @@ -1,570 +0,0 @@ -== AffineScript Language Specification v0.1 - -A programming language combining affine types, dependent types, row -polymorphism, and extensible effects. - -=== Overview - -AffineScript is designed for safe, efficient systems programming with: - -* *Affine Types*: Rust-style ownership ensuring memory safety without GC -* *Dependent Types*: Types that depend on values (e.g., `+Vec[n, T]+`) -* *Row Polymorphism*: Extensible records with compile-time field -tracking -* *Extensible Effects*: User-defined, tracked side effects -* *WASM Target*: Compiles to WebAssembly for portable execution - -=== 1. Lexical Grammar - -==== 1.1 Identifiers - -.... -lower_ident = [a-z][a-zA-Z0-9_]* -upper_ident = [A-Z][a-zA-Z0-9_]* -row_var = ".." lower_ident -.... - -==== 1.2 Keywords - -.... -fn let mut own ref type struct enum trait impl effect handle -resume handler match if else while for return break continue in -true false where total module use pub as unsafe assume transmute -forget Nat Int Bool Float String Type Row -.... - -==== 1.3 Literals - -.... -int_lit = [0-9]+ | 0x[0-9a-fA-F]+ | 0b[01]+ | 0o[0-7]+ -float_lit = [0-9]+ "." [0-9]+ ([eE][+-]?[0-9]+)? -char_lit = "'" (escape | [^'\\]) "'" -string_lit = '"' (escape | [^"\\])* '"' -bool_lit = "true" | "false" -unit_lit = "()" -.... - -==== 1.4 Operators - -.... -Arithmetic: + - * / % -Comparison: == != < > <= >= -Logical: && || ! -Bitwise: & | ^ ~ << >> -Type-level: -> => : / -Special: \ (row restriction) -.... - -==== 1.5 Quantity Annotations - -.... -0 Erased (compile-time only) -1 Linear (use exactly once) -ω Unrestricted (use any number of times) -.... - -=== 2. Syntactic Grammar - -==== 2.1 Program Structure - -[source,ebnf] ----- -program = [module_decl] {import_decl} {top_level} -top_level = fn_decl | type_decl | trait_decl | impl_block | effect_decl ----- - -==== 2.2 Type Declarations - -[source,ebnf] ----- -type_decl = [visibility] "type" UPPER_IDENT [type_params] "=" type_body - -type_params = "[" type_param {"," type_param} "]" -type_param = [quantity] IDENT [":" kind] - -kind = "Type" | "Nat" | "Row" | "Effect" | kind "->" kind - -type_body = type_expr (* alias *) - | struct_body (* record *) - | enum_body (* variant *) - -struct_body = "{" field {"," field} "}" -enum_body = ["|"] variant {"|" variant} -variant = UPPER_IDENT ["(" type_expr {"," type_expr} ")"] [":" type_expr] ----- - -==== 2.3 Type Expressions - -[source,ebnf] ----- -type_expr = type_atom - | type_expr "->" type_expr ["/" effects] (* function *) - | "(" [quantity] IDENT ":" type_expr ")" "->" type_expr ["/" effects] - | type_expr "where" "(" predicate ")" (* refinement *) - -type_atom = PRIM_TYPE | UPPER_IDENT | TYPE_VAR - | UPPER_IDENT "[" type_arg {"," type_arg} "]" - | "own" type_atom | "ref" type_atom | "mut" type_atom - | "{" row_fields "}" (* record *) - | "(" type_expr {"," type_expr} ")" (* tuple *) - -row_fields = field_type {"," field_type} ["," row_var] - | row_var - -effects = effect_term {"+" effect_term} -effect_term = UPPER_IDENT ["[" type_arg {"," type_arg} "]"] ----- - -==== 2.4 Function Declarations - -[source,ebnf] ----- -fn_decl = [visibility] ["total"] "fn" LOWER_IDENT - [type_params] "(" [param_list] ")" - ["->" type_expr] ["/" effects] - [where_clause] fn_body - -param_list = param {"," param} -param = [quantity] [ownership] IDENT ":" type_expr -ownership = "own" | "ref" | "mut" - -where_clause = "where" constraint {"," constraint} -constraint = predicate | TYPE_VAR ":" trait_bounds - -fn_body = block | "=" expr ----- - -==== 2.5 Expressions - -[source,ebnf] ----- -expr = let_expr | if_expr | match_expr | fn_expr - | handle_expr | return_expr | binary_expr - -let_expr = "let" ["mut"] pattern [":" type_expr] "=" expr ["in" expr] -if_expr = "if" expr block ["else" (if_expr | block)] -match_expr = "match" expr "{" {match_arm} "}" -match_arm = pattern ["if" expr] "=>" expr [","] - -fn_expr = "|" [param_list] "|" expr - | "fn" "(" [param_list] ")" fn_body - -handle_expr = "handle" expr "with" "{" {handler_arm} "}" -handler_arm = "return" pattern "=>" expr [","] - | LOWER_IDENT "(" [pattern {"," pattern}] ")" "=>" expr [","] - -block = "{" {statement} [expr] "}" - -binary_expr = unary_expr {BINARY_OP unary_expr} -unary_expr = [UNARY_OP] postfix_expr -postfix_expr = primary_expr {postfix} -postfix = "." IDENT | "." INT | "[" expr "]" | "(" [args] ")" - | "::" UPPER_IDENT | "\\" IDENT - -primary_expr = literal | IDENT - | "(" expr ")" | "(" expr {"," expr} ")" - | "[" [expr {"," expr}] "]" - | "{" [field_init {"," field_init}] [".." expr] "}" - | "resume" "(" [expr] ")" ----- - -==== 2.6 Patterns - -[source,ebnf] ----- -pattern = "_" (* wildcard *) - | IDENT (* binding *) - | literal (* literal match *) - | UPPER_IDENT ["(" pattern {"," pattern} ")"] - | "(" pattern {"," pattern} ")" (* tuple *) - | "{" field_pat {"," field_pat} [".." ] "}" - | pattern "|" pattern (* or-pattern *) - | IDENT "@" pattern (* binding with pattern *) ----- - -==== 2.7 Effect Declarations - -[source,ebnf] ----- -effect_decl = [visibility] "effect" UPPER_IDENT [type_params] - "{" {effect_op} "}" -effect_op = "fn" LOWER_IDENT "(" [param_list] ")" ["->" type_expr] ";" ----- - -==== 2.8 Trait Declarations - -[source,ebnf] ----- -trait_decl = [visibility] "trait" UPPER_IDENT [type_params] - [":" trait_bounds] "{" {trait_item} "}" -trait_bounds = UPPER_IDENT {"+" UPPER_IDENT} -trait_item = fn_sig ";" | fn_decl | assoc_type - -impl_block = "impl" [type_params] [trait_ref "for"] type_expr - [where_clause] "{" {impl_item} "}" ----- - -=== 3. Type System - -==== 3.1 Judgement Forms - -.... -Γ ⊢ e : τ / ε Expression e has type τ with effects ε -Γ ⊢ τ : κ Type τ has kind κ -Γ ⊢ P true Predicate P is satisfied -.... - -==== 3.2 Quantities (QTT) - -[cols=",,",options="header",] -|=== -|Quantity |Meaning |Usage -|`+0+` |Erased |Compile-time only, no runtime cost -|`+1+` |Linear |Must use exactly once -|`+ω+` |Unrestricted |Use any number of times -|=== - -*Algebra:* - -.... -0 + q = q 0 * q = 0 -1 + 1 = ω 1 * q = q -ω + ω = ω ω * ω = ω -.... - -==== 3.3 Ownership - -[cols=",",options="header",] -|=== -|Modifier |Meaning -|`+own T+` |Owned value - caller transfers ownership -|`+ref T+` |Immutable borrow - cannot modify -|`+mut T+` |Mutable borrow - exclusive access -|=== - -*Rules:* - Owned values are consumed on use - Multiple `+ref+` borrows -allowed simultaneously - Only one `+mut+` borrow at a time - Borrows -cannot outlive owner - -==== 3.4 Effects - -Functions declare effects after `+/+`: - -[source,affinescript] ----- -fn pure_fn(x: Int) -> Int / Pure { x + 1 } -fn io_fn() -> () / IO { println("hello") } -fn fallible() -> Int / Exn[Error] { throw(Error::new()) } -fn combined() -> () / IO + Exn[Error] { ... } ----- - -*Partial by Default:* - Functions are partial by default (may not -terminate) - `+total+` functions must provably terminate - -==== 3.5 Dependent Types - -Types can depend on values: - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons(T, Vec[n, T]) : Vec[n + 1, T] - -// Can only call on non-empty vectors -fn head[n: Nat, T](v: Vec[n + 1, T]) -> T ----- - -==== 3.6 Refinement Types - -Constrain types with predicates: - -[source,affinescript] ----- -type PosInt = Int where (self > 0) -fn safeDiv(a: Int, b: Int where (b != 0)) -> Int ----- - -==== 3.7 Row Polymorphism - -Extensible records with row variables: - -[source,affinescript] ----- -// Works on any record with 'name' field -fn greet[..r](person: {name: String, ..r}) -> String { - "Hello, " ++ person.name -} - -// Add fields -fn addAge[..r](rec: {..r}) -> {age: Int, ..r} { - {age: 0, ..rec} -} - -// Remove fields -fn removeName[..r](rec: {name: String, ..r}) -> {..r} { - rec \ name -} ----- - -=== 4. Core Typing Rules - -==== Variables - -.... -Γ, x :q τ ⊢ x : τ / ∅ -.... - -==== Functions - -.... -Γ, x :q τ₁ ⊢ e : τ₂ / ε -──────────────────────────────── -Γ ⊢ fn(x: τ₁) => e : τ₁ -> τ₂ / ε -.... - -==== Application - -.... -Γ ⊢ f : τ₁ -> τ₂ / ε₁ Γ ⊢ e : τ₁ / ε₂ -────────────────────────────────────────── -Γ ⊢ f(e) : τ₂ / ε₁ + ε₂ -.... - -==== Records - -.... -Γ ⊢ e : {ℓ: τ, ..r} / ε -──────────────────────── -Γ ⊢ e.ℓ : τ / ε - -Γ ⊢ e : {..r} / ε -─────────────────────────────── -Γ ⊢ {ℓ: v, ..e} : {ℓ: τ, ..r} / ε -.... - -==== Effect Handling - -.... -handle (return v) with { return x => eᵣ, ... } - → eᵣ[x ↦ v] - -handle E[op(v)] with { op(x) => eₒₚ, ... } - → eₒₚ[x ↦ v, resume ↦ fn(y) => handle E[y] with {...}] -.... - -=== 5. Standard Library (Core) - -==== 5.1 Primitive Types - -[source,affinescript] ----- -type Nat // Natural numbers (0, 1, 2, ...) -type Int // 64-bit signed integers -type Float // 64-bit floats -type Bool // true | false -type String // UTF-8 string -type Char // Unicode scalar value -type Never // Uninhabited type ----- - -==== 5.2 Standard Effects - -[source,affinescript] ----- -effect IO { - fn print(s: String); - fn println(s: String); - fn readLine() -> String; -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -effect State[S] { - fn get() -> S; - fn put(s: S); -} ----- - -==== 5.3 Option and Result - -[source,affinescript] ----- -type Option[T] = None | Some(T) - -type Result[T, E] = Ok(T) | Err(E) ----- - -==== 5.4 Core Traits - -[source,affinescript] ----- -trait Eq { - fn eq(ref self, other: ref Self) -> Bool; -} - -trait Ord: Eq { - fn cmp(ref self, other: ref Self) -> Ordering; -} - -trait Show { - fn show(ref self) -> String; -} - -trait Clone { - fn clone(ref self) -> Self; -} - -trait Drop { - fn drop(own self); -} ----- - -=== 6. Example Programs - -==== 6.1 Hello World - -[source,affinescript] ----- -effect IO { - fn println(s: String); -} - -fn main() -> () / IO { - println("Hello, AffineScript!") -} ----- - -==== 6.2 Length-Indexed Vector - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons(head: T, tail: Vec[n, T]) : Vec[n + 1, T] - -total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure { - match v { Cons(h, _) => h } -} - -total fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], b: Vec[m, T] -) -> Vec[n + m, T] / Pure { - match a { - Nil => b, - Cons(h, t) => Cons(h, append(t, b)) - } -} ----- - -==== 6.3 Ownership and Resources - -[source,affinescript] ----- -type File = own { fd: Int } - -fn open(path: ref String) -> Result[own File, IOError] / IO -fn read(file: ref File) -> Result[String, IOError] / IO -fn close(file: own File) -> Result[(), IOError] / IO - -fn withFile[T]( - path: ref String, - action: (ref File) -> Result[T, IOError] -) -> Result[T, IOError] / IO + Exn[IOError] { - let file = open(path)?; - let result = action(ref file); - close(file)?; - result -} ----- - -==== 6.4 Row Polymorphism - -[source,affinescript] ----- -fn greet[..r](person: {name: String, ..r}) -> String / Pure { - "Hello, " ++ person.name -} - -fn addField[..r](rec: {..r}, age: Int) -> {age: Int, ..r} / Pure { - {age: age, ..rec} -} - -fn main() -> () / Pure { - let alice = {name: "Alice", role: "Engineer"}; - let bob = {name: "Bob", dept: "Sales"}; - - // Both work despite different shapes - greet(alice); // "Hello, Alice" - greet(bob); // "Hello, Bob" -} ----- - -==== 6.5 Effect Handlers - -[source,affinescript] ----- -effect State[S] { - fn get() -> S; - fn put(s: S); -} - -fn counter() -> Int / State[Int] { - let n = State.get(); - State.put(n + 1); - n -} - -fn runState[S, T](init: S, comp: () -> T / State[S]) -> (T, S) / Pure { - handle comp() with { - return x => (x, init), - get() => resume(init), - put(s) => resume(()) - } -} ----- - -=== 7. WASM Compilation - -==== 7.1 Type Mapping - -[cols=",",options="header",] -|=== -|AffineScript |WASM -|`+Int+`, `+Nat+` |`+i64+` -|`+Float+` |`+f64+` -|`+Bool+` |`+i32+` -|`+String+` |`+(ref (array i8))+` -|`+{fields}+` |`+(ref (struct ...))+` -|`+own T+` |`+(ref T)+` (ownership is erased) -|`+T -> U+` |`+(ref (struct $func $env))+` -|=== - -==== 7.2 Ownership Erasure - -Ownership and quantity annotations exist only at compile time: - -[source,affinescript] ----- -fn useFile(own file: File) -> () / IO { close(file) } ----- - -Compiles to (ownership removed): - -[source,wat] ----- -(func $useFile (param $file (ref $File)) - (call $close (local.get $file))) ----- - -=== Appendix: Grammar Reference - -See the full specification at `+affinescript-spec.md+` for: - Complete -EBNF grammar - Detailed typing rules - Operational semantics - Error -message catalog - Implementation guide - -''''' - -_AffineScript Specification v0.1 - Reference Implementation_ diff --git a/affinescript/docs/specs/faces.adoc b/affinescript/docs/specs/faces.adoc deleted file mode 100644 index eb9aa09..0000000 --- a/affinescript/docs/specs/faces.adoc +++ /dev/null @@ -1,336 +0,0 @@ -== SPDX-License-Identifier: CC-BY-SA-4.0 - -== SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) - -== AffineScript Faces — Design & Implementation Reference - -This document describes the _face_ architecture (ADR-010) for the -AffineScript compiler and toolchain. - -''''' - -=== Motivation - -AffineScript has a canonical surface syntax. A _face_ is an alternative -surface presentation — a different syntax _and_ error vocabulary — -layered on top of the compiler without changing any compiler internals. - -The canonical use-case is the *Python face*: developers who write Python -can write `+def f(x: Int) -> Int:+` with indentation-delimited blocks, -`+True+`/`+False+`, `+None+`, `+and+`/`+or+`, etc. and receive compiler -errors in Python-idiomatic terms ("`single-use variable`" rather than -"`linear binding`", "`Name not found`" rather than "`UnboundVariable`"). - -''''' - -=== Architecture (ADR-010 §2) - -.... -source text (face-specific) - ↓ - face preprocessor ← lib/python_face.ml (text → canonical text) - ↓ - standard lex + parse ← lib/lexer.ml / lib/parser.mly - ↓ - type-checker / QTT ← lib/typecheck.ml / lib/quantity.ml - ↓ - canonical error term ← face-agnostic; carries structured fields - ↓ - face-aware formatter ← lib/face.ml (error × face → display string) - ↓ - terminal / LSP / IDE -.... - -The compiler is face-agnostic throughout. The two face-aware layers are: - -[arabic] -. `+lib/python_face.ml+` — source-level text preprocessor -. `+lib/face.ml+` — error formatter - -''''' - -=== Active Faces - -[width="100%",cols="15%,25%,40%,20%",options="header",] -|=== -|Face |CLI flag |File extension |Status -|Canonical |`+--face canonical+` (default) |`+.affine+` |Stable - -|Python |`+--face python+` |`+.pyaff+` |Stable (ADR-010) - -|JS |`+--face js+` or `+--face javascript+` |`+.jsaff+` |Beta -(2026-04-11) - -|Pseudocode |`+--face pseudocode+` or `+--face pseudo+` |`+.pseudoaff+` -|Beta (2026-04-11) -|=== - -''''' - -=== Roadmap Faces - -These faces are on the roadmap but have not been designed in detail. The -face architecture makes implementation cheap once the surface mapping is -settled; the bottleneck is design, not code. - -==== CoffeeScript-face (strategic priority) - -*Rationale:* CoffeeScript has a loyal displaced community that never -loved JavaScript and were forced away when the ecosystem moved on. Their -syntax preferences (significant whitespace, `+->+` / `+=>+`, -`+is+`/`+isnt+`/`+unless+`, `+@+` for self, `+do+`, list comprehensions) -are coherent and well-documented. A CoffeeScript face would offer that -community a new home: their familiar syntax, but with a sound type -system, affine resource semantics, and typed WASM output. - -*Rough surface mapping (to be designed):* - -[cols=",",options="header",] -|=== -|CoffeeScript surface |Canonical AffineScript -|`+x = expr+` |`+let x = expr+` -|`+f = (x, y) -> expr+` |`+fn f(x, y) { expr }+` -|`+f = (x, y) => expr+` |`+fn f(x, y) { expr }+` (bound form) -|`+unless cond+` |`+if !cond+` -|`+is+` / `+isnt+` |`+==+` / `+!=+` -|`+and+` / `+or+` / `+not+` |`+&&+` / `+\|\|+` / `+!+` -|`+@field+` |`+self.field+` -|`+class Name+` |`+type Name+` -|`+null+` / `+undefined+` |`+()+` -|`+yes+` / `+no+` |`+true+` / `+false+` -|Significant whitespace |blocks (like Python-face) -|`+#+` comment |`+//+` comment -|=== - -*Design questions before implementing:* - How do CoffeeScript string -interpolation (`+"#{x}"+`) map to AffineScript? - List comprehensions — -do we support them or emit a hint? - Splats (`+args...+`) — map to -variadic or array? - Prototype chain (`+::+`) — not applicable; needs a -hint pointing to traits. - -*File:* `+lib/coffee_face.ml+`, `+--face coffee+` / -`+--face coffeescript+` - -''''' - -==== ActionScript-face (strategic priority) - -*Rationale:* ActionScript 3 was a capable, statically-typed OO language -with a loyal community (Flash game developers, Flex/AIR developers). It -was killed by platform death (Flash end-of-life), not by language -quality. AS3 developers are comfortable with explicit types, classes, -and access modifiers — all of which map naturally to AffineScript -concepts. - -*Rough surface mapping (to be designed):* - -[width="100%",cols="48%,52%",options="header",] -|=== -|ActionScript surface |Canonical AffineScript -|`+var x: Type = expr+` |`+let x: Type = expr+` - -|`+function f(x: T): R { }+` |`+fn f(x: T) -> R { }+` - -|`+public/private/protected+` |(access control — AffineScript is -module-scoped; emit hint) - -|`+class Name extends Base+` |`+type Name+` (traits for inheritance) - -|`+new Name(args)+` |`+Name { fields }+` or constructor fn - -|`+null+` |`+()+` - -|`+Boolean+` / `+int+` / `+Number+` |`+Bool+` / `+Int+` / `+Float+` - -|`+String+` |`+String+` (direct) - -|`+Array.+` |`+Array[T]+` - -|`+trace(expr)+` |`+IO.println(expr)+` - -|`+import pkg.Class+` |`+use pkg::Class+` - -|`+package { ... }+` |module-level (stripped) - -|`+//+` / `+/* */+` comments |(already valid) -|=== - -*Design questions before implementing:* - AS3 inheritance (`+extends+`) -— map to trait bounds or emit a migration hint? - Interfaces -(`+implements+`) — map to traits? - `+override+` / `+super+` — no direct -equivalent; design needed. - Event model (`+addEventListener+`) — emit a -hint pointing to effects. - `+Vector.+` vs `+Array.+` — both map -to `+Array[T]+`? - -*File:* `+lib/action_face.ml+`, `+--face action+` / -`+--face actionscript+` - -''''' - -=== Python Face Details - -==== Source Transform (`+lib/python_face.ml+`) - -`+Python_face.transform_source : string -> string+` converts -Python-style AffineScript source text to canonical AffineScript before -lex and parse. - -Surface mappings: - -[width="100%",cols="40%,60%",options="header",] -|=== -|Python surface |Canonical AffineScript -|`+def name(...)+` |`+fn name(...)+` - -|`+True+` / `+False+` |`+true+` / `+false+` - -|`+None+` |`+()+` - -|`+and+` / `+or+` |`+&&+` / `+\|\|+` - -|`+not EXPR+` |`+! EXPR+` - -|`+class Name+` |`+type Name+` - -|`+pass+` |`+()+` - -|`+# comment+` |`+// comment+` - -|`+import a.b+` |`+use a::b;+` - -|`+from a import b+` |`+use a::b;+` - -|`+if cond:+` |`+if cond {+` - -|`+else:+` |`+} else {+` - -|`+elif cond:+` |`+} else if cond {+` - -|`+while cond:+` |`+while cond {+` - -|`+for x in e:+` |`+for x in e {+` - -|`+match e:+` |`+match e {+` - -|`+handle e:+` |`+handle e {+` - -|INDENT |(block already opened by preceding `+{+`) - -|DEDENT |`+}+` - -|mid-block statement |`+statement;+` - -|tail expression |`+expression+` (no `+;+` — preserves return-value -semantics) -|=== - -*Tail-position detection*: The preprocessor detects when a statement is -the last expression in its block (the next meaningful line has a smaller -indent, or EOF) and suppresses the trailing `+;+`. Without this, every -block would yield `+()+` instead of the last expression’s value, -breaking AffineScript’s expression-oriented block semantics. - -*Limitation — span fidelity*: Error spans currently refer to positions -in the transformed canonical text, not the original Python-face source. -A span remapping table (tracking line/column shifts introduced by -`+{+`/`+}+` injection) is planned as a follow-up (see -`+docs/specs/SETTLED-DECISIONS.adoc+`, ADR-010 §4). - -==== Error Vocabulary (`+lib/face.ml+`) - -`+Face.format_type_error : face -> Typecheck.type_error -> string+` (and -its counterparts for `+quantity_error+`, `+unify_error+`, -`+resolve_error+`) map canonical error terms to face-appropriate display -strings. - -Python-face vocabulary selections: - -[width="100%",cols="44%,56%",options="header",] -|=== -|Canonical term |Python-face display -|`+LinearVariableUnused id+` |"`Ownership error: single-use variable -'`x`' must be used exactly once, but was never used`" - -|`+LinearVariableUsedMultiple id+` |"`Ownership error: single-use -variable '`x`' can only be used once, but was used more than once`" - -|`+ErasedVariableUsed id+` |"`Ownership error: erased variable '`x`' was -declared as compile-time-only and cannot appear at runtime`" - -|`+UnboundVariable v+` |"`Name not found: '`v`' — hint: check spelling -or add a def declaration`" - -|`+TypeMismatch+` |"`Type error: expected T but got U`" - -|`+BranchTypeMismatch+` |"`if/else type mismatch: both branches must -return the same type`" - -|`+UndefinedVariable id+` |"`Name not found: '`x`' — hint: define it -with def or let`" - -|`+UndefinedType id+` |"`Type not found: '`T`' — hint: define it with -class or type`" - -|`+UndefinedModule id+` |"`Module not found: '`M`' — hint: use import -M`" - -|`+Unit+` type |rendered as `+None+` - -|`+Bool+` type |rendered as `+bool+` -|=== - -''''' - -=== CLI Usage - -.... -# Canonical face (default) -affinescript check foo.affine - -# Python face -affinescript check --face python foo.pyaff -affinescript eval --face python foo.pyaff -affinescript compile --face python foo.pyaff - -# Debug: preview what the Python preprocessor produces -affinescript preview-python-transform foo.pyaff -.... - -''''' - -=== Adding a New Face - -[arabic] -. Add a variant to `+type face+` in `+lib/face.ml+`. -. Add a preprocessor in a new `+lib/_face.ml+` (if syntax -differs). -. Add branches to each `+format_*_for_face+` function in -`+lib/face.ml+`. -. Wire the face into `+bin/main.ml+` (`+face_arg+`, -`+parse_with_face+`). -. Add the file extension to `+lib/dune+` modules list. -. Add E2E tests in `+test/test_e2e.ml+`. -. Update this document. - -No compiler internals change. - -''''' - -=== Test Coverage - -End-to-end Python-face tests live in `+test/test_e2e.ml+` under the -"`E2E Python-Face`" section: - -* `+def → fn+` transformation -* `+if+`/`+elif+`/`+else+` chain -* Keyword substitution (`+True+`, `+False+`, `+None+`, `+and+`, `+or+`, -`+not+`) -* Three-function fixture parses without error -* Transform preview output correctness - -Seam check (items 1 + 2): `+affinescript check --face python+` on a file -with a doubly-used `+@linear+` variable correctly produces the -Python-face error message "`Ownership error: single-use variable '`x`' -can only be used once…`" confirming the full pipeline from `+.pyaff+` → -text transform → canonical parse → typecheck → face-aware error -formatter. diff --git a/affinescript/docs/standards/DECISIONS.adoc b/affinescript/docs/standards/DECISIONS.adoc deleted file mode 100644 index 4cd2592..0000000 --- a/affinescript/docs/standards/DECISIONS.adoc +++ /dev/null @@ -1,271 +0,0 @@ -== AffineScript Design Decisions - -This document records the key design decisions for AffineScript’s -implementation. - -=== 1. Runtime Model - -*Decision*: Minimal runtime, rely on host for most services - -* Runtime is small and focused on AffineScript-specific needs -* No garbage collector for most data (ownership handles memory) -* Small tracing GC only for cyclic ω-quantity data (opt-in) -* Host provides: I/O, networking, filesystem, timers - -*Rationale*: Keeps WASM binaries small, maximizes portability, leverages -host capabilities. - -=== 2. Effect Compilation Strategy - -*Decision*: Evidence-passing (Koka-style) - -* Effects compiled via evidence-passing transformation -* Each effect operation receives an "`evidence`" parameter -* Handlers install evidence at runtime -* One-shot continuations optimized to avoid allocation - -*Rationale*: Better performance than CPS, proven in Koka, good balance -of complexity/speed. - -*Implementation*: - -.... -// Source -handle computation() with { - return x → x, - get(_, k) → resume(k, state) -} - -// Compiled (evidence-passing) -computation({ - get: (ev, k) => k(state) -}) -.... - -=== 3. WASM Target - -*Decision*: WASM core + WASI, with Component Model readiness - -[cols=",",options="header",] -|=== -|Feature |Decision -|WASM Core |✅ Required baseline -|WASM GC |❌ Not required (ownership handles memory) -|WASI |✅ For CLI/server use cases -|Component Model |✅ Design for future compatibility -|Threads |⚠️ Optional, for parallel effects -|=== - -*Rationale*: Broad compatibility now, future-proofed for Component -Model. - -=== 4. SMT Solver - -*Decision*: Z3 as optional external dependency - -* Z3 bindings (ocaml-z3) for refinement type checking -* SMT is optional: refinements work without it (runtime checks) -* Can be disabled for faster compilation -* Future: support CVC5 as alternative - -*Configuration*: - -[source,nickel] ----- -# affinescript.ncl -{ - smt = { - enabled = true, - solver = "z3", - timeout_ms = 5000, - } -} ----- - -=== 5. Package Manager - -*Decision*: Workspace-aware, Cargo-inspired - -* Single `+affine.toml+` manifest per package -* Workspace support for monorepos -* Lock file for reproducibility -* Content-addressed storage (like pnpm) -* Written in Rust - -*Manifest format*: - -[source,toml] ----- -[package] -name = "my-project" -version = "0.1.0" -edition = "2024" - -[dependencies] -std = "1.0" -http = { version = "0.5", features = ["async"] } - -[dev-dependencies] -test = "1.0" ----- - -=== 6. Standard Library Philosophy - -*Decision*: Small core + blessed packages - -*Core (always available)*: - `+Prelude+`: Basic types, traits, operators -- `+Option+`, `+Result+`: Error handling - `+List+`, `+Vec+`, `+Array+`: -Collections - `+String+`, `+Char+`: Text - -*Blessed Effects (in std)*: - `+IO+`: Console, file system - `+Exn+`: -Exceptions - `+State+`: Mutable state - `+Async+`: Async/await - -`+Reader+`: Environment access - -*Community Packages*: - HTTP, JSON, databases, etc. - Not in std, but -curated/recommended - -=== 7. Self-Hosting - -*Decision*: Long-term goal, not immediate priority - -* Phase 1-4: OCaml compiler -* Phase 5+: Gradually rewrite in AffineScript -* Start with: lexer, parser (simpler) -* End with: type checker, codegen (complex) - -*Timeline*: After 1.0 stable release - -=== 8. Interop Priority - -*Decision*: JavaScript first, Rust second - -[cols=",,",options="header",] -|=== -|Target |Priority |Method -|JavaScript |🥇 Primary |wasm-bindgen, host bindings -|Rust |🥈 Secondary |Native FFI for tools -|C |🥉 Tertiary |Via Rust FFI -|=== - -*Rationale*: WASM’s primary deployment is web/JS; tooling benefits from -Rust. - -=== 9. Error Messages - -*Decision*: Rust-style elaborate diagnostics - -* Multi-line errors with source context -* Color-coded by severity -* Suggestions for fixes -* Error codes with documentation links -* Machine-readable JSON output option - -*Example*: - -.... -error[E0312]: cannot borrow `x` as mutable because it is already borrowed - --> src/main.afs:12:5 - | -10 | let r = &x; - | -- immutable borrow occurs here -11 | -12 | mutate(&mut x); - | ^^^^^^ mutable borrow occurs here -13 | -14 | use(r); - | - immutable borrow later used here - | - = help: consider moving the mutable borrow before the immutable borrow -.... - -=== 10. Proof Assistant Mode - -*Decision*: Refinement types with SMT only (no interactive proving) - -* Refinements checked via SMT solver -* No tactic language or proof terms -* Proofs are erased (quantity 0) -* Future: optional Lean/Coq extraction for critical code - -*Rationale*: Practical verification without complexity of full theorem -prover. - -=== 11. Primary Use Cases - -*Decision*: Priority order - -[arabic] -. *Web applications* (frontend + backend) -. *CLI tools* -. *Libraries/packages* -. *Embedded/WASM plugins* -. *Scientific computing* (future) - -=== 12. Community Model - -*Decision*: Benevolent dictator initially, open governance post-1.0 - -* Pre-1.0: Core team makes decisions quickly -* Post-1.0: RFC process for major changes -* Open source from day one (Apache-2.0 OR MIT) -* Community contributions welcome - -=== 13. Backward Compatibility - -*Decision*: Breaking changes during 0.x, strict semver from 1.0 - -* 0.x releases may break compatibility -* Migration guides for breaking changes -* 1.0+ follows strict semver -* Edition system for language-level changes (like Rust) - -''''' - -=== Technology Stack Summary - -[cols=",,",options="header",] -|=== -|Layer |Technology |Notes -|Compiler |OCaml 5.1+ |Existing codebase -|Parser |Menhir |Existing -|Lexer |Sedlex |Existing -|SMT |Z3 (ocaml-z3) |Optional -|Runtime |Rust |WASM target -|Allocator |Custom (Rust) |Ownership-optimized -|Package Manager |Rust |CLI tool -|LSP Server |Rust |Performance -|Formatter |OCaml |Shares AST -|REPL |OCaml |Interpreter mode -|Web Tooling |ReScript + Deno |Per standards -|Build Meta |Deno |Per standards -|Config |Nickel |Per standards -|Docs |Custom generator |From types -|=== - -''''' - -=== File Format Decisions - -[cols=",,",options="header",] -|=== -|Purpose |Format |Extension -|Source code |AffineScript |`+.afs+` -|Package manifest |TOML |`+affine.toml+` -|Lock file |TOML |`+affine.lock+` -|Configuration |Nickel |`+*.ncl+` -|Build scripts |Deno/TS |`+*.ts+` -|Documentation |Markdown |`+*.md+` -|=== - -''''' - -=== Versioning - -* *Language*: `+2024+` edition (year-based) -* *Compiler*: Semver (0.1.0, 0.2.0, … 1.0.0) -* *Stdlib*: Tied to compiler version -* *Packages*: Independent semver - -''''' - -_Last updated: 2024_ _Status: Approved_ diff --git a/affinescript/docs/standards/PALIMPSEST.adoc b/affinescript/docs/standards/PALIMPSEST.adoc deleted file mode 100644 index fd219d0..0000000 --- a/affinescript/docs/standards/PALIMPSEST.adoc +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= Palimpsest License -:toc: -:toc-placement!: - -image:https://img.shields.io/badge/License-MPL--2.0-blue.svg[License: PMPL-1.0,link="https://github.com/hyperpolymath/palimpsest-license"] -image:https://img.shields.io/badge/Philosophy-Palimpsest-indigo.svg[Palimpsest,link="https://github.com/hyperpolymath/palimpsest-license"] - -toc::[] - -== Legal Status - -This project is licensed under the **Palimpsest-MPL License 1.0 (PMPL-1.0)**. -For SPDX and tooling, use **MPL-2.0**. - -PMPL-1.0 incorporates the Mozilla Public License 2.0 by reference and adds -ethical-use, provenance, and lineage requirements. - -== What PMPL Adds - -* **Emotional Lineage** - preserve narrative intent and cultural context -* **Provenance Integrity** - retain attribution and lineage metadata -* **Ethical Use Constraints** - explicit consent for non-interpretive AI training -* **Quantum-Safe Provenance (optional)** - post-quantum signature support - -== How to Adopt - -1. Include the PMPL-1.0 license text in `LICENSE`. -2. Add SPDX headers to source files: - `SPDX-License-Identifier: CC-BY-SA-4.0` -3. Add a Palimpsest badge to your README (see `assets/badges/` and `embed/license-blocks/`). - -== Versioning - -See `VERSIONING.adoc` for the release process and the "-or-later" model. -The current legal text is PMPL-1.0. - -== References - -* `legal/README.adoc` -* `assets/badges/README.md` -* `embed/license-blocks/README.md` diff --git a/affinescript/docs/standards/RELEASE.adoc b/affinescript/docs/standards/RELEASE.adoc deleted file mode 100644 index dcc5ce8..0000000 --- a/affinescript/docs/standards/RELEASE.adoc +++ /dev/null @@ -1,279 +0,0 @@ -== AffineScript v0.1.0 - Reference Parser Release - -This is the first public release of AffineScript, featuring a complete -specification and reference parser. - -=== What’s Included - -==== Specification - -* `+SPEC.md+` - Condensed language specification (essential grammar and -semantics) -* `+affinescript-spec.md+` - Complete language specification v2.0 - -==== Reference Implementation - -* *Lexer* (sedlex) - Complete tokenization -* *Parser* (menhir) - Complete parsing to AST -* *AST* - Full abstract syntax tree definitions -* *Error Handling* - Structured diagnostics with source locations - -==== Examples - -* `+examples/hello.affine+` - Hello World with effects -* `+examples/vectors.affine+` - Dependent types with length-indexed -vectors -* `+examples/ownership.affine+` - Ownership and borrowing patterns -* `+examples/rows.affine+` - Row polymorphism -* `+examples/effects.affine+` - Effect handling and state -* `+examples/traits.affine+` - Traits and type classes -* `+examples/refinements.affine+` - Refinement types - -=== Building from Source - -==== Prerequisites - -* OCaml 5.1+ -* opam 2.1+ -* dune 3.14+ - -==== Quick Start - -[source,bash] ----- -# Clone the repository -git clone https://github.com/hyperpolymath/affinescript.git -cd affinescript - -# Install dependencies -opam install . --deps-only - -# Build -dune build - -# Run tests -dune runtest - -# Install locally -dune install ----- - -==== Using Guix (Preferred) - -[source,bash] ----- -guix shell -f build/guix.scm -dune build ----- - -==== Using Nix - -[source,bash] ----- -nix develop -dune build ----- - -=== Usage - -==== Lex a File - -[source,bash] ----- -dune exec affinescript -- lex examples/hello.affine ----- - -Output: - -.... -EFFECT @ 1:1-1:7 -UPPER_IDENT(IO) @ 1:8-1:10 -LBRACE @ 1:11-1:12 -FN @ 2:3-2:5 -... -.... - -==== Parse a File - -[source,bash] ----- -dune exec affinescript -- parse examples/hello.affine ----- - -Output: - -.... -{ prog_module = None; prog_imports = []; - prog_decls = - [TopEffect { ed_vis = Private; ed_name = { name = "IO"; ... }; ...}; - TopFn { fd_vis = Private; fd_total = false; fd_name = { name = "main"; ...}; ...}] -} -.... - -==== Check a File (WIP) - -[source,bash] ----- -dune exec affinescript -- check examples/hello.affine ----- - -Note: Type checking is not yet implemented in this release. - -=== Implementation Status - -[cols=",,",options="header",] -|=== -|Component |Status |Notes -|Lexer |Complete |All tokens, comments, string escapes -|Parser |Complete |Full grammar, 50+ test cases -|AST |Complete |All language constructs -|Diagnostics |Complete |Structured errors with locations -|Name Resolution |Not Started |Planned for v0.2 -|Type Checker |Not Started |Planned for v0.2 -|Borrow Checker |Not Started |Planned for v0.3 -|Effect Checker |Not Started |Planned for v0.3 -|WASM Codegen |Not Started |Planned for v0.4 -|=== - -=== Language Features - -==== Affine Types (Ownership) - -[source,affinescript] ----- -type File = own { fd: Int } - -fn processFile(file: own File) -> () / IO { - // file is consumed here - cannot use after - close(file) -} - -fn readFile(file: ref File) -> String / IO { - // Borrows file - doesn't consume it - read(file) -} ----- - -==== Dependent Types - -[source,affinescript] ----- -type Vec[n: Nat, T: Type] = - | Nil : Vec[0, T] - | Cons(T, Vec[n, T]) : Vec[n + 1, T] - -// Type system prevents calling on empty vectors -total fn head[n: Nat, T](v: Vec[n + 1, T]) -> T / Pure { - match v { Cons(h, _) => h } -} ----- - -==== Row Polymorphism - -[source,affinescript] ----- -// Works on any record with 'name' field -fn greet[..r](person: {name: String, ..r}) -> String / Pure { - "Hello, " ++ person.name -} - -// Both work: -greet({name: "Alice", age: 30}) -greet({name: "Bob", role: "Engineer"}) ----- - -==== Extensible Effects - -[source,affinescript] ----- -effect State[S] { - fn get() -> S; - fn put(s: S); -} - -fn counter() -> Int / State[Int] { - let n = State.get(); - State.put(n + 1); - n -} - -// Handle the effect -handle counter() with { - return x => x, - get() => resume(0), - put(s) => resume(()) -} ----- - -=== Running Tests - -[source,bash] ----- -# All tests -dune runtest - -# With verbose output -dune runtest --force --verbose - -# Specific test suite -dune exec test/test_main.exe -- test "Lexer" -dune exec test/test_main.exe -- test "Parser" ----- - -=== Documentation - -[source,bash] ----- -# Generate documentation -dune build @doc - -# View in browser -open _build/default/_doc/_html/index.html ----- - -=== File Structure - -.... -affinescript/ -├── lib/ # Core compiler library -│ ├── ast.ml # Abstract syntax tree -│ ├── token.ml # Token definitions -│ ├── lexer.ml # Sedlex-based lexer -│ ├── parser.mly # Menhir grammar -│ ├── parse.ml # Parser wrapper -│ ├── span.ml # Source locations -│ └── error.ml # Diagnostics -├── bin/ # CLI executable -│ └── main.ml # Command-line interface -├── test/ # Test suite -│ ├── test_lexer.ml # Lexer tests -│ └── test_parser.ml # Parser tests -├── examples/ # Example programs -├── wiki/ # Documentation -├── SPEC.md # Condensed specification -├── affinescript-spec.md # Full specification -└── RELEASE.md # This file -.... - -=== Contributing - -Contributions welcome! Areas of interest: - -[arabic] -. *Type Checker* - Bidirectional type checking with dependent types -. *Borrow Checker* - Ownership verification -. *Effect Checker* - Effect tracking and handling -. *Standard Library* - Core types and functions -. *WASM Backend* - Code generation - -See `+affinescript-spec.md+` Part 10 for implementation guidance. - -=== License - -MIT License - see LICENSE file. - -=== Links - -* Repository: https://github.com/hyperpolymath/affinescript -* Specification: See `+SPEC.md+` or `+affinescript-spec.md+` -* Issues: https://github.com/hyperpolymath/affinescript/issues diff --git a/affinescript/docs/standards/ROADMAP.adoc b/affinescript/docs/standards/ROADMAP.adoc deleted file mode 100644 index 1667a46..0000000 --- a/affinescript/docs/standards/ROADMAP.adoc +++ /dev/null @@ -1,550 +0,0 @@ -== AffineScript Implementation Roadmap - -=== Overview - -This roadmap outlines the path from current state (lexer + parser) to a -complete, production-ready language. - -.... -Current State Goal - │ │ - ▼ ▼ -┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ -│ Lexer │ → │ Parser │ → │ Type │ → │ Codegen │ → │ Runtime │ -│ ✅ │ │ ✅ │ │ Checker │ │ WASM │ │ Rust │ -└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ - │ - ┌────────────┼────────────┐ - ▼ ▼ ▼ - ┌────────┐ ┌─────────┐ ┌─────────┐ - │ Borrow │ │ Effect │ │Refinemt │ - │Checker │ │Inference│ │ SMT │ - └────────┘ └─────────┘ └─────────┘ -.... - -''''' - -=== Phase 0: Foundation ✅ COMPLETE - -*Status*: Done - -* [x] Project structure (Dune) -* [x] Lexer (Sedlex) -* [x] Parser (Menhir) -* [x] AST definitions -* [x] Error infrastructure -* [x] CLI skeleton -* [x] Test framework -* [x] Academic documentation - -''''' - -=== Phase 1: Core Type System - -*Goal*: Type check simple programs without effects or ownership - -*Duration*: 8-12 weeks - -==== 1.1 Name Resolution - -* [ ] Symbol table structure -* [ ] Scope management -* [ ] Module path resolution -* [ ] Import resolution -* [ ] Visibility checking (pub, pub(crate), etc.) - -*Files*: `+lib/resolve.ml+`, `+lib/symbol.ml+` - -==== 1.2 Kind Checking - -* [ ] Kind inference -* [ ] Kind unification -* [ ] Higher-kinded types -* [ ] Row kinds -* [ ] Effect kinds - -*Files*: `+lib/kind.ml+` - -==== 1.3 Type Checker Core - -* [ ] Bidirectional type checking -* [ ] Type synthesis -* [ ] Type checking mode -* [ ] Subsumption rule - -*Files*: `+lib/typecheck.ml+`, `+lib/check.ml+`, `+lib/synth.ml+` - -==== 1.4 Unification Engine - -* [ ] Type unification -* [ ] Occurs check -* [ ] Union-find structure -* [ ] Error messages for unification failures - -*Files*: `+lib/unify.ml+`, `+lib/union_find.ml+` - -==== 1.5 Polymorphism - -* [ ] Let-generalization -* [ ] Instantiation -* [ ] Value restriction -* [ ] Type application - -==== 1.6 Row Unification - -* [ ] Record row unification -* [ ] Variant row unification -* [ ] Lacks constraints -* [ ] Row rewriting - -*Files*: `+lib/row_unify.ml+` - -==== 1.7 Basic Error Messages - -* [ ] Type mismatch errors -* [ ] Undefined variable errors -* [ ] Source location tracking -* [ ] Suggested fixes - -*Milestone*: `+affinescript check+` works for simple programs - -''''' - -=== Phase 2: Quantities & Effects - -*Goal*: Full QTT and effect system - -*Duration*: 8-12 weeks - -==== 2.1 Quantity Checking - -* [ ] Quantity context tracking -* [ ] Context scaling -* [ ] Context addition -* [ ] Usage analysis -* [ ] Quantity error messages - -*Files*: `+lib/quantity.ml+` - -==== 2.2 Effect Inference - -* [ ] Effect signature checking -* [ ] Effect row inference -* [ ] Handler typing -* [ ] Effect unification -* [ ] Effect polymorphism - -*Files*: `+lib/effect.ml+`, `+lib/effect_infer.ml+` - -==== 2.3 Handler Verification - -* [ ] Handler completeness -* [ ] Return clause typing -* [ ] Operation clause typing -* [ ] Continuation typing (linear vs multi-shot) - -==== 2.4 Effect Error Messages - -* [ ] Unhandled effect errors -* [ ] Effect mismatch errors -* [ ] Handler clause errors - -*Milestone*: Effects and quantities fully checked - -''''' - -=== Phase 3: Ownership & Borrowing - -*Goal*: Memory safety verification - -*Duration*: 6-10 weeks - -==== 3.1 Ownership Tracking - -* [ ] Move semantics -* [ ] Ownership transfer -* [ ] Drop insertion -* [ ] Copy trait handling - -*Files*: `+lib/ownership.ml+` - -==== 3.2 Borrow Checker - -* [ ] Borrow tracking -* [ ] Conflict detection -* [ ] Non-lexical lifetimes -* [ ] Dataflow analysis - -*Files*: `+lib/borrow.ml+`, `+lib/dataflow.ml+` - -==== 3.3 Lifetime Inference - -* [ ] Lifetime constraints -* [ ] Lifetime solving -* [ ] Lifetime elision -* [ ] Lifetime bounds - -*Files*: `+lib/lifetime.ml+` - -==== 3.4 Ownership-Quantity Integration - -* [ ] Quantity affects ownership -* [ ] Linear ownership (1) -* [ ] Shared ownership (ω + Copy) -* [ ] Erased types (0) - -*Milestone*: `+affinescript check+` catches all ownership errors - -''''' - -=== Phase 4: Dependent Types & Refinements - -*Goal*: Dependent types with SMT verification - -*Duration*: 6-10 weeks - -==== 4.1 Dependent Type Checking - -* [ ] Π-type checking -* [ ] Σ-type checking -* [ ] Type-level computation -* [ ] Normalization - -*Files*: `+lib/dependent.ml+`, `+lib/normalize.ml+` - -==== 4.2 SMT Integration - -* [ ] Z3 OCaml bindings -* [ ] Predicate translation -* [ ] Validity checking -* [ ] Model extraction (for errors) - -*Files*: `+lib/smt.ml+`, `+lib/smt_translate.ml+` - -==== 4.3 Refinement Checking - -* [ ] Refinement subtyping -* [ ] VC generation -* [ ] SMT queries -* [ ] Refinement error messages - -*Files*: `+lib/refinement.ml+` - -==== 4.4 Totality Checking (Optional) - -* [ ] Termination analysis -* [ ] Structural recursion -* [ ] Custom measures -* [ ] Total annotation - -*Files*: `+lib/totality.ml+` - -*Milestone*: Full type system complete - -''''' - -=== Phase 5: Code Generation - -*Goal*: Compile to WebAssembly - -*Duration*: 10-14 weeks - -==== 5.1 Intermediate Representation - -* [ ] ANF transformation -* [ ] Effect evidence insertion -* [ ] Closure conversion -* [ ] Lambda lifting - -*Files*: `+lib/ir.ml+`, `+lib/anf.ml+`, `+lib/closure.ml+` - -==== 5.2 Optimization - -* [ ] Dead code elimination -* [ ] Inlining -* [ ] Constant folding -* [ ] Linearity-aware optimizations - -*Files*: `+lib/optimize.ml+` - -==== 5.3 WASM Code Generation - -* [ ] WASM module structure -* [ ] Function compilation -* [ ] Type mapping -* [ ] Memory layout - -*Files*: `+lib/wasm.ml+`, `+lib/codegen.ml+` - -==== 5.4 Effect Compilation - -* [ ] Evidence-passing transform -* [ ] Handler compilation -* [ ] Continuation representation -* [ ] One-shot optimization - -*Files*: `+lib/effect_compile.ml+` - -==== 5.5 Ownership Erasure - -* [ ] Drop insertion points -* [ ] Move compilation -* [ ] Borrow elimination - -*Milestone*: `+affinescript compile+` produces working WASM - -''''' - -=== Phase 6: Runtime - -*Goal*: Minimal Rust runtime for WASM - -*Duration*: 6-8 weeks - -==== 6.1 Runtime Core (Rust) - -* [ ] Project structure -* [ ] Memory allocator -* [ ] Panic handling -* [ ] Stack management - -*Location*: `+runtime/+` (new Rust crate) - -==== 6.2 Effect Runtime - -* [ ] Evidence structures -* [ ] Handler frames -* [ ] Continuation allocation -* [ ] Resume implementation - -==== 6.3 GC (Optional) - -* [ ] Mark-sweep for ω cycles -* [ ] Root tracking -* [ ] Finalization - -==== 6.4 Host Bindings - -* [ ] WASI integration -* [ ] JavaScript interop -* [ ] Console I/O -* [ ] File system - -*Milestone*: Programs run correctly - -''''' - -=== Phase 7: Standard Library - -*Goal*: Usable standard library - -*Duration*: Ongoing (8+ weeks initial) - -==== 7.1 Core Types - -* [ ] Prelude -* [ ] Option, Result -* [ ] Tuples -* [ ] Unit, Bool, Never - -*Location*: `+stdlib/core/+` - -==== 7.2 Collections - -* [ ] List -* [ ] Vec (growable array) -* [ ] HashMap -* [ ] HashSet -* [ ] BTreeMap - -*Location*: `+stdlib/collections/+` - -==== 7.3 Text - -* [ ] String -* [ ] Char -* [ ] Formatting (Display, Debug) -* [ ] Parsing - -*Location*: `+stdlib/text/+` - -==== 7.4 Effects - -* [ ] IO effect -* [ ] State effect -* [ ] Exn effect -* [ ] Async effect -* [ ] Reader effect - -*Location*: `+stdlib/effects/+` - -==== 7.5 Numeric - -* [ ] Int, Float -* [ ] Numeric traits -* [ ] Math functions - -*Location*: `+stdlib/num/+` - -*Milestone*: Self-sufficient programs possible - -''''' - -=== Phase 8: Tooling - -*Goal*: Developer experience - -*Duration*: Ongoing (10+ weeks initial) - -==== 8.1 Language Server (Rust) - -* [ ] LSP implementation -* [ ] Diagnostics -* [ ] Hover information -* [ ] Go to definition -* [ ] Find references -* [ ] Completion -* [ ] Rename - -*Location*: `+tools/affinescript-lsp/+` - -==== 8.2 Formatter (OCaml) - -* [ ] Canonical formatting -* [ ] Configuration options -* [ ] Editor integration - -*Location*: `+lib/format.ml+`, `+bin/fmt.ml+` - -==== 8.3 REPL (OCaml) - -* [ ] Expression evaluation -* [ ] Type printing -* [ ] Effect handling -* [ ] History - -*Location*: `+bin/repl.ml+` - -==== 8.4 Package Manager (Rust) - -* [ ] Manifest parsing -* [ ] Dependency resolution -* [ ] Package fetching -* [ ] Lock file -* [ ] Publishing - -*Location*: `+tools/affine-pkg/+` - -==== 8.5 Documentation Generator - -* [ ] Doc comments -* [ ] API documentation -* [ ] Search index - -*Location*: `+tools/affine-doc/+` - -*Milestone*: Professional development experience - -''''' - -=== Phase 9: Ecosystem - -*Goal*: Community and adoption - -*Duration*: Ongoing - -==== 9.1 VS Code Extension - -* [ ] Syntax highlighting -* [ ] LSP client -* [ ] Snippets -* [ ] Debugging - -*Location*: `+editors/vscode/+` - -==== 9.2 Playground - -* [ ] Web REPL -* [ ] Shareable links -* [ ] Examples - -*Location*: `+playground/+` - -==== 9.3 Package Registry - -* [ ] Registry backend -* [ ] Web frontend -* [ ] CLI publishing - -==== 9.4 Documentation Site - -* [ ] Tutorial -* [ ] Language reference -* [ ] API docs -* [ ] Blog - -==== 9.5 Example Projects - -* [ ] Hello World -* [ ] Web server -* [ ] CLI tool -* [ ] Library - -*Milestone*: Community can build real projects - -''''' - -=== Version Milestones - -[cols=",,",options="header",] -|=== -|Version |Contents |Target -|0.1.0 |Type checker (no effects/ownership) |Phase 1 -|0.2.0 |Full type system |Phase 2-4 -|0.3.0 |WASM compilation |Phase 5-6 -|0.4.0 |Standard library |Phase 7 -|0.5.0 |Tooling (LSP, formatter) |Phase 8 -|0.9.0 |Release candidate |Phase 9 -|1.0.0 |Stable release |All phases -|=== - -''''' - -=== Resource Requirements - -==== Core Team Skills Needed - -* OCaml (compiler) -* Rust (runtime, tooling) -* Type theory (checker design) -* WASM (code generation) -* ReScript/Deno (web tooling) - -==== Infrastructure - -* CI/CD (GitHub Actions) -* Package registry hosting -* Documentation hosting -* Playground hosting - -''''' - -=== Success Criteria - -==== 0.1.0 (Type Checker MVP) - -* [ ] 100+ test programs type check correctly -* [ ] Error messages are helpful -* [ ] <1s for typical file - -==== 1.0.0 (Stable Release) - -* [ ] All language features work -* [ ] Performance competitive with Rust/Go -* [ ] 10+ community packages -* [ ] 3+ production users -* [ ] Complete documentation - -''''' - -_Last updated: 2024_ diff --git a/affinescript/docs/tutorial/lesson-02-functions.adoc b/affinescript/docs/tutorial/lesson-02-functions.adoc deleted file mode 100644 index 9661af9..0000000 --- a/affinescript/docs/tutorial/lesson-02-functions.adoc +++ /dev/null @@ -1,46 +0,0 @@ -== Lesson 2: Functions and Ownership - -Learn how AffineScript’s ownership system works with functions. - -=== Function Basics - -Functions require explicit return statements: - -[source,affinescript] ----- -fn add(x: Int, y: Int) -> Int { - return x + y; -} ----- - -=== Ownership Modes - -==== Owned Parameters (own) - -Function takes ownership: - -[source,affinescript] ----- -fn consume(own x: Int) -> Int { - return x; -} -let value = 42; -let result = consume(value); // value moved -// value cannot be used here! ----- - -==== Shared Borrows (ref) - -Function borrows without taking ownership: - -[source,affinescript] ----- -fn read(ref x: Int) -> Int { - return x; -} -let value = 42; -let result = read(value); -let also = value; // OK - still valid! ----- - -Next: link:lesson-03-data.md[Lesson 3: Data Structures] diff --git a/affinescript/docs/tutorial/lesson-03-data.adoc b/affinescript/docs/tutorial/lesson-03-data.adoc deleted file mode 100644 index e84281e..0000000 --- a/affinescript/docs/tutorial/lesson-03-data.adoc +++ /dev/null @@ -1,36 +0,0 @@ -== Lesson 3: Data Structures - -=== Tuples - -Group multiple values: - -[source,affinescript] ----- -fn make_pair() -> (Int, Int) { - return (1, 2); -} ----- - -=== Records - -Named fields: - -[source,affinescript] ----- -fn make_point() -> Point { - return { x: 10, y: 20 }; -} ----- - -=== Arrays - -Collections of same type: - -[source,affinescript] ----- -fn make_list() -> [Int] { - return [1, 2, 3]; -} ----- - -Next: link:lesson-04-patterns.md[Lesson 4: Pattern Matching] diff --git a/affinescript/docs/tutorial/lesson-04-patterns.adoc b/affinescript/docs/tutorial/lesson-04-patterns.adoc deleted file mode 100644 index 1da9616..0000000 --- a/affinescript/docs/tutorial/lesson-04-patterns.adoc +++ /dev/null @@ -1,27 +0,0 @@ -== Lesson 4: Pattern Matching - -Destructure data with match expressions: - -[source,affinescript] ----- -fn describe(x: Int) -> String { - return match x { - 0 => "zero", - 1 => "one", - _ => "many", - }; -} ----- - -=== Tuple Patterns - -[source,affinescript] ----- -fn first(pair: (Int, Int)) -> Int { - return match pair { - (a, b) => a, - }; -} ----- - -Next: link:lesson-05-types.md[Lesson 5: Types and Inference] diff --git a/affinescript/docs/tutorial/lesson-05-types.adoc b/affinescript/docs/tutorial/lesson-05-types.adoc deleted file mode 100644 index d2ccb8a..0000000 --- a/affinescript/docs/tutorial/lesson-05-types.adoc +++ /dev/null @@ -1,27 +0,0 @@ -== Lesson 5: Types and Inference - -=== Type Annotations - -Always specify parameter and return types: - -[source,affinescript] ----- -fn identity(x: Int) -> Int { - return x; -} ----- - -=== Type Inference - -Let bindings infer types: - -[source,affinescript] ----- -fn main() -> Int { - let x = 42; // x: Int inferred - let y = x + 10; // y: Int inferred - return y; -} ----- - -Next: link:lesson-06-errors.md[Lesson 6: Error Handling] diff --git a/affinescript/docs/tutorial/lesson-06-errors.adoc b/affinescript/docs/tutorial/lesson-06-errors.adoc deleted file mode 100644 index a16edc6..0000000 --- a/affinescript/docs/tutorial/lesson-06-errors.adoc +++ /dev/null @@ -1,30 +0,0 @@ -== Lesson 6: Error Handling - -=== Result Types - -Handle errors explicitly: - -[source,affinescript] ----- -fn divide(a: Int, b: Int) -> Result[Int, String] { - return if b == 0 { - Err("division by zero") - } else { - Ok(a / b) - }; -} ----- - -=== Propagating Errors - -Use `+?+` operator to propagate: - -[source,affinescript] ----- -fn compute() -> Result[Int, String] { - let x = divide(10, 2)?; - return Ok(x * 2); -} ----- - -Next: link:lesson-07-effects.md[Lesson 7: Effects] diff --git a/affinescript/docs/tutorial/lesson-07-effects.adoc b/affinescript/docs/tutorial/lesson-07-effects.adoc deleted file mode 100644 index 026f040..0000000 --- a/affinescript/docs/tutorial/lesson-07-effects.adoc +++ /dev/null @@ -1,40 +0,0 @@ -== Lesson 7: Algebraic Effects - -=== Effect Declarations - -Define computational effects: - -[source,affinescript] ----- -effect Console { - fn print(s: String); - fn read() -> String; -} ----- - -=== Using Effects - -Functions declare effects they perform: - -[source,affinescript] ----- -fn greet() -> () / Console { - print("Hello!"); -} ----- - -=== Effect Handlers - -Handle effects with custom behavior: - -[source,affinescript] ----- -handle greet() { - print(s) => { - // Custom printing logic - resume (); - } -} ----- - -Next: link:lesson-08-generics.md[Lesson 8: Generics] diff --git a/affinescript/docs/tutorial/lesson-08-generics.adoc b/affinescript/docs/tutorial/lesson-08-generics.adoc deleted file mode 100644 index 7201ff2..0000000 --- a/affinescript/docs/tutorial/lesson-08-generics.adoc +++ /dev/null @@ -1,27 +0,0 @@ -== Lesson 8: Generic Types - -=== Type Parameters - -Write functions that work with any type: - -[source,affinescript] ----- -fn identity[T](x: T) -> T { - return x; -} ----- - -=== Generic Data Structures - -[source,affinescript] ----- -type Box[T] = { - value: T -}; - -fn make_box[T](x: T) -> Box[T] { - return { value: x }; -} ----- - -Next: link:lesson-09-modules.md[Lesson 9: Modules] diff --git a/affinescript/docs/tutorial/lesson-09-modules.adoc b/affinescript/docs/tutorial/lesson-09-modules.adoc deleted file mode 100644 index 5bb7988..0000000 --- a/affinescript/docs/tutorial/lesson-09-modules.adoc +++ /dev/null @@ -1,36 +0,0 @@ -== Lesson 9: Modules and Imports - -=== Module Declaration - -[source,affinescript] ----- -module math.geometry; - -pub fn area(radius: Float) -> Float { - return 3.14 * radius * radius; -} ----- - -=== Importing - -[source,affinescript] ----- -use math.geometry; - -fn main() -> Float { - return geometry.area(5.0); -} ----- - -=== Selective Imports - -[source,affinescript] ----- -use math.geometry::{area, circumference}; - -fn main() -> Float { - return area(5.0) + circumference(5.0); -} ----- - -Next: link:lesson-10-building.md[Lesson 10: Building Real Programs] diff --git a/affinescript/docs/tutorial/lesson-10-building.adoc b/affinescript/docs/tutorial/lesson-10-building.adoc deleted file mode 100644 index 514fec3..0000000 --- a/affinescript/docs/tutorial/lesson-10-building.adoc +++ /dev/null @@ -1,47 +0,0 @@ -== Lesson 10: Building Real Programs - -=== Project Structure - -.... -my-project/ -├── src/ -│ ├── main.affine -│ ├── lib.affine -│ └── util.affine -├── tests/ -│ └── test_main.affine -└── affinescript.toml -.... - -=== Testing - -[source,affinescript] ----- -fn test_addition() -> TestResult { - let result = add(2, 3); - assert_eq(result, 5, "2 + 3 should equal 5"); - return Pass; -} ----- - -=== Compilation - -[source,bash] ----- -# Check types and borrow checker -affinescript check src/main.affine - -# Compile to WebAssembly -affinescript compile src/main.affine -o output.wasm - -# Run with interpreter -affinescript eval src/main.affine ----- - -=== Next Steps - -* Read the link:../reference/index.md[Language Reference] -* Explore link:../stdlib/index.md[Standard Library] -* Join the community at github.com/hyperpolymath/affinescript - -Congratulations! You’ve completed the AffineScript tutorial! diff --git a/affinescript/dune-project b/affinescript/dune-project deleted file mode 100644 index 7a0a71d..0000000 --- a/affinescript/dune-project +++ /dev/null @@ -1,42 +0,0 @@ -(lang dune 3.17) - -(using menhir 3.0) - -(name affinescript) - -(version 0.1.0) - -(generate_opam_files true) - -(source - (github hyperpolymath/affinescript)) - -(authors "hyperpolymath") - -(maintainers "hyperpolymath") - -(license "MIT OR AGPL-3.0-or-later") - -(documentation https://github.com/hyperpolymath/affinescript) - -(package - (name affinescript) - (allow_empty) - (synopsis "A programming language with affine types, dependent types, row polymorphism, and extensible effects") - (description - "AffineScript combines Rust-style ownership (affine types), compile-time size verification (dependent types), extensible records (row polymorphism), and trackable side effects (extensible effects). It compiles to WebAssembly for efficient, portable execution.") - (depends - (ocaml (>= 5.1)) - (dune (>= 3.14)) - (menhir (>= 20231231)) - (sedlex (>= 3.2)) - (ppx_deriving (>= 5.2)) - (ppx_sexp_conv (>= 0.16)) - (sexplib0 (>= 0.16)) - (fmt (>= 0.9)) - (cmdliner (>= 1.2)) - (yojson (>= 2.1)) - (alcotest (and (>= 1.7) :with-test)) - (odoc (and (>= 2.4) :with-doc))) - (tags - (programming-language compiler webassembly affine-types dependent-types row-polymorphism effects ocaml))) diff --git a/affinescript/editors/tree-sitter-affinescript/README.adoc b/affinescript/editors/tree-sitter-affinescript/README.adoc deleted file mode 100644 index 99b70cb..0000000 --- a/affinescript/editors/tree-sitter-affinescript/README.adoc +++ /dev/null @@ -1,87 +0,0 @@ -== tree-sitter-affinescript - -Tree-sitter grammar for AffineScript - affine types, effects, and -dependent types. - -=== Features - -* *Incremental parsing* - Fast, efficient parsing for large files -* *Syntax highlighting* - Semantic token-based highlighting -* *Code navigation* - Structural queries for IDE features -* *Error recovery* - Continues parsing even with syntax errors - -=== Installation - -==== Neovim (with nvim-treesitter) - -[source,lua] ----- -local parser_config = require('nvim-treesitter.parsers').get_parser_configs() -parser_config.affinescript = { - install_info = { - url = "~/path/to/tree-sitter-affinescript", - files = {"src/parser.c"}, - branch = "main", - }, - filetype = "as", -} ----- - -==== Emacs (with tree-sitter) - -[source,elisp] ----- -(add-to-list 'tree-sitter-major-mode-language-alist '(affinescript-mode . affinescript)) ----- - -==== VSCode - -Integrated automatically when using the AffineScript VSCode extension. - -=== Development - -[source,bash] ----- -# Generate parser -npm install -npm run build - -# Test grammar -npm test - -# Or using tree-sitter CLI -tree-sitter generate -tree-sitter test ----- - -=== Grammar Highlights - -The grammar supports all AffineScript features: - -* *Affine types* and ownership annotations -* *Effect system* - effect declarations, annotations, handlers -* *Dependent types* - forall, exists quantifiers -* *Pattern matching* - exhaustive, with guards -* *Traits and impls* - polymorphic dispatch -* *Module system* - namespaces and imports - -=== Queries - -==== Highlights (`+queries/highlights.scm+`) - -Syntax highlighting for: - Keywords (fn, let, type, effect, etc.) - -Effects and effect operators - Types and type parameters - Functions and -function calls - Literals and comments - -==== Locals (planned) - -Scope analysis for: - Variable definitions and references - Function -scopes - Block scopes - -==== Injections (planned) - -Language injections for: - Inline documentation - String interpolation - -=== License - -MIT diff --git a/affinescript/editors/tree-sitter-affinescript/grammar.js b/affinescript/editors/tree-sitter-affinescript/grammar.js deleted file mode 100644 index 1fce976..0000000 --- a/affinescript/editors/tree-sitter-affinescript/grammar.js +++ /dev/null @@ -1,505 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Tree-sitter grammar for AffineScript - -module.exports = grammar({ - name: 'affinescript', - - extras: $ => [ - /\s/, - $.comment, - ], - - conflicts: $ => [ - [$.type_expr, $.expr], - ], - - rules: { - source_file: $ => repeat($.decl), - - // Comments - comment: $ => choice( - seq('//', /.*/), - seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/') - ), - - // Identifiers - identifier: $ => /[a-z_][a-zA-Z0-9_]*/, - type_identifier: $ => /[A-Z][a-zA-Z0-9_]*/, - - // Literals - literal: $ => choice( - $.integer, - $.float, - $.boolean, - $.string, - $.char, - $.unit - ), - - integer: $ => choice( - /[0-9][0-9_]*/, - /0x[0-9a-fA-F][0-9a-fA-F_]*/, - /0b[01][01_]*/, - /0o[0-7][0-7_]*/ - ), - - float: $ => /[0-9][0-9_]*\.[0-9][0-9_]*([eE][+-]?[0-9][0-9_]*)?/, - - boolean: $ => choice('true', 'false'), - - string: $ => seq( - '"', - repeat(choice( - /[^"\\]+/, - $.escape_sequence - )), - '"' - ), - - char: $ => seq( - "'", - choice(/[^'\\]/, $.escape_sequence), - "'" - ), - - escape_sequence: $ => /\\(n|r|t|\\|"|'|x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\})/, - - unit: $ => '()', - - // Declarations - decl: $ => choice( - $.fun_decl, - $.let_decl, - $.type_decl, - $.struct_decl, - $.enum_decl, - $.effect_decl, - $.trait_decl, - $.impl_decl, - $.mod_decl, - $.use_decl, - $.extern_decl - ), - - fun_decl: $ => seq( - optional($.visibility), - 'fn', - field('name', $.identifier), - optional($.type_params), - field('params', $.param_list), - optional(seq('->', field('return_type', $.type_expr))), - optional(seq('/', field('effect', $.effect_expr))), - choice( - seq('=', field('body', $.expr)), - field('body', $.block) - ) - ), - - visibility: $ => 'pub', - - type_params: $ => seq('<', sep1($.type_identifier, ','), '>'), - - param_list: $ => seq('(', optional(sep1($.param, ',')), ')'), - - param: $ => seq( - field('pattern', $.pattern), - optional(seq(':', field('type', $.type_expr))) - ), - - let_decl: $ => seq( - 'let', - field('pattern', $.pattern), - optional(seq(':', field('type', $.type_expr))), - '=', - field('value', $.expr), - ';' - ), - - type_decl: $ => seq( - optional($.visibility), - 'type', - field('name', $.type_identifier), - optional($.type_params), - '=', - field('definition', $.type_expr), - ';' - ), - - struct_decl: $ => seq( - optional($.visibility), - 'struct', - field('name', $.type_identifier), - optional($.type_params), - '{', - optional(sep1($.struct_field, ',')), - optional(','), - '}' - ), - - struct_field: $ => seq( - optional($.visibility), - field('name', $.identifier), - ':', - field('type', $.type_expr) - ), - - enum_decl: $ => seq( - optional($.visibility), - 'enum', - field('name', $.type_identifier), - optional($.type_params), - '{', - optional(sep1($.variant_decl, ',')), - optional(','), - '}' - ), - - variant_decl: $ => seq( - field('name', $.type_identifier), - optional(choice( - // Positional fields: Some(T), Ok(T, E) - seq('(', sep1($.type_expr, ','), ')'), - // Named fields: Circle { radius: Float64 } - seq('{', sep1($.struct_field, ','), optional(','), '}') - )), - // Optional GADT return type: Cons(T, List[T]): List[T] - optional(seq(':', field('return_type', $.type_expr))) - ), - - effect_decl: $ => seq( - 'effect', - field('name', $.type_identifier), - optional(seq( - '{', - repeat($.effect_operation), - '}' - )), - ';' - ), - - effect_operation: $ => seq( - 'fn', - $.identifier, - '(', - optional(sep1($.type_expr, ',')), - ')', - optional(seq('->', $.type_expr)), - ';' - ), - - trait_decl: $ => seq( - 'trait', - field('name', $.type_identifier), - optional($.type_params), - '{', - repeat($.trait_item), - '}' - ), - - trait_item: $ => seq( - 'fn', - $.identifier, - ':', - $.type_expr, - ';' - ), - - impl_decl: $ => seq( - 'impl', - optional($.type_params), - optional(seq(field('trait', $.type_expr), 'for')), - field('type', $.type_expr), - '{', - repeat($.impl_item), - '}' - ), - - impl_item: $ => $.fun_decl, - - mod_decl: $ => seq( - 'mod', - field('name', $.identifier), - '{', - repeat($.decl), - '}' - ), - - use_decl: $ => seq( - 'use', - sep1($.identifier, '::'), - ';' - ), - - extern_decl: $ => seq( - 'extern', - 'fn', - $.identifier, - ':', - $.type_expr, - ';' - ), - - // Patterns - pattern: $ => choice( - '_', - $.identifier, - $.literal, - $.tuple_pattern, - $.record_pattern, - $.constructor_pattern, - seq($.pattern, '|', $.pattern), - seq($.pattern, 'as', $.identifier) - ), - - tuple_pattern: $ => seq('(', sep1($.pattern, ','), ')'), - - record_pattern: $ => seq( - '{', - sep1($.record_pattern_field, ','), - optional('..'), - '}' - ), - - record_pattern_field: $ => choice( - $.identifier, - seq($.identifier, ':', $.pattern) - ), - - constructor_pattern: $ => seq( - $.type_identifier, - optional(seq('(', sep1($.pattern, ','), ')')) - ), - - // Expressions - expr: $ => choice( - $.literal, - $.identifier, - $.variant_expr, - $.binary_expr, - $.unary_expr, - $.call_expr, - $.if_expr, - $.match_expr, - $.let_expr, - $.block, - $.tuple_expr, - $.record_expr, - $.field_expr, - $.array_expr, - $.index_expr, - $.lambda_expr, - $.handle_expr, - $.perform_expr, - $.resume_expr, - $.annotated_expr, - $.move_expr, - seq('(', $.expr, ')') - ), - - // Type::Variant qualified constructor expression - variant_expr: $ => seq( - field('type', $.type_identifier), - '::', - field('variant', $.type_identifier) - ), - - binary_expr: $ => choice( - prec.left(10, seq($.expr, choice('*', '/', '%'), $.expr)), - prec.left(9, seq($.expr, choice('+', '-'), $.expr)), - prec.left(8, seq($.expr, choice('<', '>', '<=', '>='), $.expr)), - prec.left(7, seq($.expr, choice('==', '!='), $.expr)), - prec.left(6, seq($.expr, '&&', $.expr)), - prec.left(5, seq($.expr, '||', $.expr)) - ), - - unary_expr: $ => choice( - prec(12, seq('-', $.expr)), - prec(12, seq('!', $.expr)), - prec(12, seq('*', $.expr)), - prec(12, seq('&', $.expr)) - ), - - call_expr: $ => prec(15, seq( - field('function', $.expr), - field('arguments', $.arg_list) - )), - - arg_list: $ => seq('(', optional(sep1($.expr, ',')), ')'), - - if_expr: $ => prec.right(seq( - 'if', - field('condition', $.expr), - field('then', $.block), - optional(seq('else', field('else', choice($.block, $.if_expr)))) - )), - - match_expr: $ => seq( - 'match', - field('scrutinee', $.expr), - '{', - repeat($.match_arm), - '}' - ), - - match_arm: $ => seq( - field('pattern', $.pattern), - '=>', - field('body', $.expr), - ',' - ), - - let_expr: $ => seq( - 'let', - field('pattern', $.pattern), - '=', - field('value', $.expr), - ';', - field('body', $.expr) - ), - - block: $ => seq('{', repeat($.stmt), optional($.expr), '}'), - - tuple_expr: $ => seq('(', sep2($.expr, ','), ')'), - - record_expr: $ => seq( - '{', - sep1($.record_field, ','), - optional(seq('..', $.expr)), - '}' - ), - - record_field: $ => seq($.identifier, ':', $.expr), - - field_expr: $ => prec(20, seq($.expr, '.', $.identifier)), - - array_expr: $ => seq('[', optional(sep1($.expr, ',')), ']'), - - index_expr: $ => prec(20, seq($.expr, '[', $.expr, ']')), - - lambda_expr: $ => seq( - '|', - optional(sep1($.param, ',')), - '|', - optional(seq('->', $.type_expr)), - $.expr - ), - - handle_expr: $ => seq( - 'handle', - '{', - $.expr, - '}', - 'with', - '{', - repeat($.handler), - '}' - ), - - handler: $ => seq( - field('pattern', $.pattern), - '=>', - field('body', $.expr), - ',' - ), - - perform_expr: $ => seq('perform', $.identifier, $.arg_list), - - resume_expr: $ => seq('resume', $.expr), - - annotated_expr: $ => seq('(', $.expr, ':', $.type_expr, ')'), - - move_expr: $ => seq('move', $.expr), - - // Statements - stmt: $ => choice( - $.expr_stmt, - $.let_stmt, - $.assign_stmt, - $.while_stmt, - $.for_stmt, - $.return_stmt, - $.break_stmt, - $.continue_stmt - ), - - expr_stmt: $ => seq($.expr, ';'), - - let_stmt: $ => seq( - 'let', - field('pattern', $.pattern), - optional(seq(':', $.type_expr)), - '=', - field('value', $.expr), - ';' - ), - - assign_stmt: $ => seq($.expr, '=', $.expr, ';'), - - while_stmt: $ => seq('while', $.expr, $.block), - - for_stmt: $ => seq('for', $.pattern, 'in', $.expr, $.block), - - return_stmt: $ => seq('return', optional($.expr), ';'), - - break_stmt: $ => seq('break', ';'), - - continue_stmt: $ => seq('continue', ';'), - - // Type expressions - type_expr: $ => choice( - $.type_identifier, - $.identifier, - $.type_app, - $.arrow_type, - $.tuple_type, - $.record_type, - $.forall_type, - $.exists_type - ), - - type_app: $ => seq( - $.type_expr, - '<', - sep1($.type_expr, ','), - '>' - ), - - arrow_type: $ => prec.right(seq( - $.type_expr, - '->', - $.type_expr, - optional(seq('/', $.effect_expr)) - )), - - tuple_type: $ => seq('(', sep2($.type_expr, ','), ')'), - - record_type: $ => seq( - '{', - sep1($.record_type_field, ','), - '}' - ), - - record_type_field: $ => seq($.identifier, ':', $.type_expr), - - forall_type: $ => seq('forall', repeat1($.identifier), '.', $.type_expr), - - exists_type: $ => seq('exists', repeat1($.identifier), '.', $.type_expr), - - // Effect expressions - effect_expr: $ => choice( - $.type_identifier, - $.identifier, - seq($.effect_expr, '|', $.effect_expr) - ), - } -}); - -// Helper functions -function sep1(rule, separator) { - return seq(rule, repeat(seq(separator, rule))); -} - -function sep2(rule, separator) { - return seq(rule, separator, rule, repeat(seq(separator, rule))); -} diff --git a/affinescript/editors/tree-sitter-affinescript/package.json b/affinescript/editors/tree-sitter-affinescript/package.json deleted file mode 100644 index bf0146f..0000000 --- a/affinescript/editors/tree-sitter-affinescript/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "tree-sitter-affinescript", - "version": "0.1.0", - "description": "AffineScript grammar for tree-sitter", - "main": "bindings/node", - "keywords": [ - "tree-sitter", - "parser", - "affinescript" - ], - "author": "hyperpolymath", - "license": "MIT", - "dependencies": { - "nan": "^2.17.0" - }, - "devDependencies": { - "tree-sitter-cli": "^0.20.8" - }, - "scripts": { - "build": "tree-sitter generate && node-gyp build", - "test": "tree-sitter test" - }, - "tree-sitter": [ - { - "scope": "source.affinescript", - "file-types": ["affine"], - "highlights": "queries/highlights.scm" - } - ] -} diff --git a/affinescript/editors/tree-sitter-affinescript/queries/highlights.scm b/affinescript/editors/tree-sitter-affinescript/queries/highlights.scm deleted file mode 100644 index b35ea78..0000000 --- a/affinescript/editors/tree-sitter-affinescript/queries/highlights.scm +++ /dev/null @@ -1,166 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; Syntax highlighting queries for AffineScript - -; Keywords -[ - "fn" - "let" - "mut" - "type" - "trait" - "impl" - "struct" - "enum" - "mod" - "use" - "pub" - "extern" -] @keyword - -; Control flow -[ - "if" - "else" - "match" - "while" - "for" - "in" - "loop" - "break" - "continue" - "return" -] @keyword.control - -; Effects -[ - "effect" - "handle" - "perform" - "resume" -] @keyword.effect - -; Quantities -[ - "linear" - "affine" - "unrestricted" - "erased" -] @keyword.modifier - -; Storage modifiers -[ - "move" - "ref" - "deref" -] @keyword.storage - -; Type keywords -[ - "forall" - "exists" - "as" - "where" -] @keyword.type - -; Literals -(integer) @number -(float) @number -(boolean) @boolean -(string) @string -(char) @character -(unit) @constant.builtin - -; Escape sequences -(escape_sequence) @string.escape - -; Comments -(comment) @comment - -; Identifiers -(identifier) @variable -(type_identifier) @type - -; Function names -(fun_decl name: (identifier) @function) -(call_expr function: (identifier) @function.call) -(extern_decl (identifier) @function) - -; Parameters -(param pattern: (identifier) @variable.parameter) - -; Fields -(field_expr (identifier) @property) -(record_field (identifier) @property) -(record_pattern_field (identifier) @property) - -; Effect names -(effect_decl name: (type_identifier) @type.effect) -(effect_expr (type_identifier) @type.effect) - -; Trait names -(trait_decl name: (type_identifier) @type.trait) - -; Type parameters -(type_params (type_identifier) @type.parameter) - -; Operators -[ - "+" - "-" - "*" - "/" - "%" - "==" - "!=" - "<" - ">" - "<=" - ">=" - "&&" - "||" - "!" - "&" - "|" -] @operator - -; Punctuation -[ - "(" - ")" - "[" - "]" - "{" - "}" -] @punctuation.bracket - -[ - "," - ";" - ":" - "::" -] @punctuation.delimiter - -[ - "->" - "=>" - "=" - ".." -] @punctuation.special - -; Visibility -(visibility) @keyword.modifier - -; Built-in types -((type_identifier) @type.builtin - (#match? @type.builtin "^(Int|Float|Bool|String|Unit|Never|Char|List|Vec|Option|Result|Ref)$")) - -; Constants (SCREAMING_SNAKE_CASE) -((identifier) @constant - (#match? @constant "^[A-Z][A-Z0-9_]*$")) - -; Wildcards -"_" @variable.builtin - -; Special highlighting for effect operators -(arrow_type "/" @keyword.operator.effect) -(effect_expr "|" @keyword.operator.effect) diff --git a/affinescript/editors/vscode/README.adoc b/affinescript/editors/vscode/README.adoc deleted file mode 100644 index 193c4ca..0000000 --- a/affinescript/editors/vscode/README.adoc +++ /dev/null @@ -1,147 +0,0 @@ -== AffineScript for Visual Studio Code - -Official Visual Studio Code extension for AffineScript - a language with -affine types, algebraic effects, and dependent types that compiles to -WebAssembly. - -=== Features - -==== Syntax Highlighting - -* Complete TextMate grammar for AffineScript syntax -* Highlighting for effects, quantities (linear/affine/unrestricted), -types, and keywords -* Custom colors for effect annotations and ownership markers - -==== Language Server Protocol (LSP) - -* *Real-time diagnostics* - Type errors, effect violations, borrow check -errors -* *Hover information* - See types and documentation -* *Go to definition* - Navigate to function/type definitions -* *Find references* - Find all uses of a symbol -* *Code completion* - Context-aware suggestions -* *Rename* - Rename symbols across files -* *Formatting* - Auto-format code -* *Code actions* - Quick fixes for common errors - -==== Commands - -* *AffineScript: Type Check Current File* (`+Ctrl+Shift+C+` / -`+Cmd+Shift+C+`) -* *AffineScript: Evaluate Current File* (`+Ctrl+Shift+R+` / -`+Cmd+Shift+R+`) -* *AffineScript: Compile to WebAssembly* -* *AffineScript: Format Document* -* *AffineScript: Restart Language Server* - -=== Requirements - -* *AffineScript compiler* - Install from -https://github.com/hyperpolymath/affinescript[github.com/hyperpolymath/affinescript] -* *affinescript-lsp* - Language server (optional, for LSP features) - -[source,bash] ----- -# Install AffineScript -git clone https://github.com/hyperpolymath/affinescript -cd affinescript -dune build -dune install - -# Install LSP server -cd tools/affinescript-lsp -cargo build --release -cargo install --path . ----- - -=== Extension Settings - -This extension contributes the following settings: - -* `+affinescript.lsp.enabled+`: Enable/disable the Language Server -* `+affinescript.lsp.serverPath+`: Path to affinescript-lsp executable -* `+affinescript.format.indentSize+`: Number of spaces per indentation -level -* `+affinescript.format.maxLineLength+`: Maximum line length before -wrapping -* `+affinescript.lint.enabled+`: Enable/disable linting -* `+affinescript.lint.unusedVariables+`: Diagnostic level for unused -variables -* `+affinescript.lint.missingEffectAnnotations+`: Diagnostic level for -missing effect annotations - -=== Usage - -==== Basic Example - -[source,affinescript] ----- -// Pure function - no effects -fn add(x: Int, y: Int) -> Int { - return x + y; -} - -// Impure function with IO effect -fn main() -> Unit / io { - println("Hello, AffineScript!"); - let result = add(40, 2); - println(int_to_string(result)); -} ----- - -==== Effect System - -[source,affinescript] ----- -// Pure functions cannot call impure functions -fn pure() -> Int { - return read_line(); // ❌ ERROR: Cannot perform effect io in pure context -} - -// Impure functions can call pure or impure -fn impure() -> Int / io { - return read_line(); // ✅ OK -} ----- - -==== Affine Types (Ownership) - -[source,affinescript] ----- -fn use_value(x: @affine String) -> Unit { - println(x); // x is consumed here -} - -fn main() -> Unit / io { - let s = "hello"; - use_value(s); - println(s); // ❌ ERROR: use after move -} ----- - -=== Known Issues - -* LSP server implementation is in progress (Phase 8) -* Some LSP features not yet implemented (hover, completion) -* Tree-sitter grammar for advanced highlighting coming soon - -=== Contributing - -Contributions welcome! See -https://github.com/hyperpolymath/affinescript/blob/main/CONTRIBUTING.md[CONTRIBUTING.md] - -=== License - -MPL-2.0 - -=== Release Notes - -==== 0.1.0 - -* Initial release -* Syntax highlighting via TextMate grammar -* Language configuration (brackets, comments, folding) -* Basic LSP integration scaffolding -* Commands for check, eval, compile, format -* Keyboard shortcuts for common operations diff --git a/affinescript/editors/vscode/language-configuration.json b/affinescript/editors/vscode/language-configuration.json deleted file mode 100644 index 71808cd..0000000 --- a/affinescript/editors/vscode/language-configuration.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": ["/*", "*/"] - }, - "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["<", ">"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}" }, - { "open": "[", "close": "]" }, - { "open": "(", "close": ")" }, - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "/*", "close": " */", "notIn": ["string"] } - ], - "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["'", "'"], - ["<", ">"] - ], - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", - "indentationRules": { - "increaseIndentPattern": "^.*\\{[^}\"']*$|^.*\\([^)\"']*$|^.*\\[[^\\]\"']*$", - "decreaseIndentPattern": "^\\s*(\\}|\\)|\\]).*$" - }, - "onEnterRules": [ - { - "beforeText": "^\\s*//.*$", - "action": { - "indent": "none", - "appendText": "// " - } - }, - { - "beforeText": "\\{\\s*$", - "afterText": "^\\s*\\}", - "action": { - "indent": "indentOutdent" - } - } - ] -} diff --git a/affinescript/editors/vscode/package.json b/affinescript/editors/vscode/package.json deleted file mode 100644 index 33d0082..0000000 --- a/affinescript/editors/vscode/package.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "name": "affinescript", - "displayName": "AffineScript", - "description": "Language support for AffineScript - affine types, effects, and dependent types", - "version": "0.1.0", - "publisher": "hyperpolymath", - "license": "MIT OR AGPL-3.0-or-later", - "repository": { - "type": "git", - "url": "https://github.com/hyperpolymath/affinescript" - }, - "engines": { - "vscode": "^1.80.0" - }, - "categories": [ - "Programming Languages", - "Linters", - "Formatters" - ], - "keywords": [ - "affinescript", - "affine-types", - "linear-types", - "effects", - "dependent-types", - "webassembly" - ], - "activationEvents": [ - "onLanguage:affinescript" - ], - "main": "./out/extension.js", - "contributes": { - "languages": [ - { - "id": "affinescript", - "aliases": [ - "AffineScript", - "affinescript" - ], - "extensions": [ - ".affine" - ], - "configuration": "./language-configuration.json", - "icon": { - "light": "./images/icon-light.png", - "dark": "./images/icon-dark.png" - } - } - ], - "grammars": [ - { - "language": "affinescript", - "scopeName": "source.affinescript", - "path": "./syntaxes/affinescript.tmLanguage.json" - } - ], - "configuration": { - "title": "AffineScript", - "properties": { - "affinescript.lsp.enabled": { - "type": "boolean", - "default": true, - "description": "Enable AffineScript Language Server" - }, - "affinescript.lsp.serverPath": { - "type": "string", - "default": "affinescript-lsp", - "description": "Path to affinescript-lsp executable" - }, - "affinescript.format.indentSize": { - "type": "number", - "default": 2, - "description": "Number of spaces per indentation level" - }, - "affinescript.format.maxLineLength": { - "type": "number", - "default": 100, - "description": "Maximum line length before wrapping" - }, - "affinescript.lint.enabled": { - "type": "boolean", - "default": true, - "description": "Enable linting" - }, - "affinescript.lint.unusedVariables": { - "type": "string", - "enum": ["error", "warning", "hint", "off"], - "default": "warning", - "description": "Diagnostic level for unused variables" - }, - "affinescript.lint.missingEffectAnnotations": { - "type": "string", - "enum": ["error", "warning", "hint", "off"], - "default": "error", - "description": "Diagnostic level for missing effect annotations" - } - } - }, - "commands": [ - { - "command": "affinescript.check", - "title": "AffineScript: Type Check Current File" - }, - { - "command": "affinescript.eval", - "title": "AffineScript: Evaluate Current File" - }, - { - "command": "affinescript.compile", - "title": "AffineScript: Compile to WebAssembly" - }, - { - "command": "affinescript.format", - "title": "AffineScript: Format Document" - }, - { - "command": "affinescript.restartLsp", - "title": "AffineScript: Restart Language Server" - } - ], - "keybindings": [ - { - "command": "affinescript.check", - "key": "ctrl+shift+c", - "mac": "cmd+shift+c", - "when": "editorLangId == affinescript" - }, - { - "command": "affinescript.eval", - "key": "ctrl+shift+r", - "mac": "cmd+shift+r", - "when": "editorLangId == affinescript" - } - ] - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "package": "vsce package", - "publish": "vsce publish" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@types/vscode": "^1.80.0", - "typescript": "^5.0.0", - "vsce": "^2.15.0" - }, - "dependencies": { - "vscode-languageclient": "^9.0.0" - } -} diff --git a/affinescript/editors/vscode/src/extension.affine b/affinescript/editors/vscode/src/extension.affine deleted file mode 100644 index cea6d32..0000000 --- a/affinescript/editors/vscode/src/extension.affine +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Ported via Harvard Engine mechanical processor - -module extension; - -// TODO(#84): Complete semantic implementation - -/* === ORIGINAL TYPESCRIPT CONTEXT === -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// AffineScript VSCode Extension - -import * as vscode from 'vscode'; -import * as path from 'path'; -import { exec } from 'child_process'; -import { promisify } from 'util'; -import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - Executable -} from 'vscode-languageclient/node'; - -const execAsync = promisify(exec); - -let client: LanguageClient | undefined; - -export function activate(context: vscode.ExtensionContext) { - console.log('AffineScript extension activated'); - - // Register commands - context.subscriptions.push( - vscode.commands.registerCommand('affinescript.check', checkCurrentFile), - vscode.commands.registerCommand('affinescript.eval', evalCurrentFile), - vscode.commands.registerCommand('affinescript.compile', compileCurrentFile), - vscode.commands.registerCommand('affinescript.format', formatCurrentFile), - vscode.commands.registerCommand('affinescript.restartLsp', restartLsp) - ); - - // Start LSP if enabled - const config = vscode.workspace.getConfiguration('affinescript'); - if (config.get('lsp.enabled')) { - startLsp(context); - } -} - -export function deactivate(): Thenable | undefined { - if (!client) { - return undefined; - } - return client.stop(); -} - -async function startLsp(context: vscode.ExtensionContext) { - const config = vscode.workspace.getConfiguration('affinescript'); - const serverPath = config.get('lsp.serverPath') || 'affinescript-lsp'; - - // Check if LSP server exists - try { - await execAsync(`which ${serverPath}`); - } catch { - vscode.window.showWarningMessage( - `AffineScript LSP server not found at '${serverPath}'. Language features disabled.` - ); - return; - } - - const serverOptions: ServerOptions = { - run: { command: serverPath } as Executable, - debug: { command: serverPath } as Executable - }; - - const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'affinescript' }], - synchronize: { - fileEvents: vscode.workspace.createFileSystemWatcher('**/*.affine') - } - }; - - client = new LanguageClient( - 'affinescriptLsp', - 'AffineScript Language Server', - serverOptions, - clientOptions - ); - - client.start(); - vscode.window.showInformationMessage('AffineScript LSP started'); -} - -async function restartLsp() { - if (client) { - await client.stop(); - vscode.window.showInformationMessage('AffineScript LSP stopped'); - } - - const context = (global as any).extensionContext; - if (context) { - await startLsp(context); - } -} - -async function checkCurrentFile() { - const editor = vscode.window.activeTextEditor; - if (!editor || editor.document.languageId !== 'affinescript') { - vscode.window.showErrorMessage('No AffineScript file open'); - return; - } - - const filePath = editor.document.uri.fsPath; - const terminal = vscode.window.createTerminal('AffineScript Check'); - terminal.show(); - terminal.sendText(`affinescript check "${filePath}"`); -} - -async function evalCurrentFile() { - const editor = vscode.window.activeTextEditor; - if (!editor || editor.document.languageId !== 'affinescript') { - vscode.window.showErrorMessage('No AffineScript file open'); - return; - } - - const filePath = editor.document.uri.fsPath; - const terminal = vscode.window.createTerminal('AffineScript Eval'); - terminal.show(); - terminal.sendText(`affinescript eval "${filePath}"`); -} - -async function compileCurrentFile() { - const editor = vscode.window.activeTextEditor; - if (!editor || editor.document.languageId !== 'affinescript') { - vscode.window.showErrorMessage('No AffineScript file open'); - return; - } - - const filePath = editor.document.uri.fsPath; - const outputPath = filePath.replace(/\.affine$/, '.wasm'); - - const terminal = vscode.window.createTerminal('AffineScript Compile'); - terminal.show(); - terminal.sendText(`affinescript compile "${filePath}" -o "${outputPath}"`); -} - -async function formatCurrentFile() { - const editor = vscode.window.activeTextEditor; - if (!editor || editor.document.languageId !== 'affinescript') { - vscode.window.showErrorMessage('No AffineScript file open'); - return; - } - - const filePath = editor.document.uri.fsPath; - - try { - const { stdout } = await execAsync(`affinescript fmt "${filePath}"`); - vscode.window.showInformationMessage('File formatted successfully'); - } catch (error: any) { - vscode.window.showErrorMessage(`Formatting failed: ${error.message}`); - } -} - -==================================== */ diff --git a/affinescript/editors/vscode/syntaxes/affinescript.tmLanguage.json b/affinescript/editors/vscode/syntaxes/affinescript.tmLanguage.json deleted file mode 100644 index e8bdb76..0000000 --- a/affinescript/editors/vscode/syntaxes/affinescript.tmLanguage.json +++ /dev/null @@ -1,207 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", - "name": "AffineScript", - "scopeName": "source.affinescript", - "patterns": [ - { "include": "#comments" }, - { "include": "#keywords" }, - { "include": "#strings" }, - { "include": "#numbers" }, - { "include": "#effects" }, - { "include": "#types" }, - { "include": "#functions" }, - { "include": "#operators" }, - { "include": "#constants" }, - { "include": "#variables" } - ], - "repository": { - "comments": { - "patterns": [ - { - "name": "comment.line.double-slash.affinescript", - "begin": "//", - "end": "$" - }, - { - "name": "comment.block.affinescript", - "begin": "/\\*", - "end": "\\*/" - } - ] - }, - "keywords": { - "patterns": [ - { - "name": "keyword.control.affinescript", - "match": "\\b(if|else|match|while|for|in|loop|break|continue|return)\\b" - }, - { - "name": "keyword.declaration.affinescript", - "match": "\\b(fn|let|mut|type|trait|impl|struct|enum|mod|use|pub|extern)\\b" - }, - { - "name": "keyword.effect.affinescript", - "match": "\\b(effect|handle|resume|perform)\\b" - }, - { - "name": "keyword.quantity.affinescript", - "match": "\\b(linear|affine|unrestricted|erased)\\b" - }, - { - "name": "keyword.other.affinescript", - "match": "\\b(as|where|forall|exists)\\b" - }, - { - "name": "storage.modifier.affinescript", - "match": "\\b(move|ref|deref)\\b" - } - ] - }, - "strings": { - "patterns": [ - { - "name": "string.quoted.double.affinescript", - "begin": "\"", - "end": "\"", - "patterns": [ - { - "name": "constant.character.escape.affinescript", - "match": "\\\\(n|r|t|\\\\|\"|'|x[0-9a-fA-F]{2}|u\\{[0-9a-fA-F]+\\})" - } - ] - }, - { - "name": "string.quoted.single.affinescript", - "begin": "'", - "end": "'", - "patterns": [ - { - "name": "constant.character.escape.affinescript", - "match": "\\\\(n|r|t|\\\\|\"|'|x[0-9a-fA-F]{2}|u\\{[0-9a-fA-F]+\\})" - } - ] - } - ] - }, - "numbers": { - "patterns": [ - { - "name": "constant.numeric.float.affinescript", - "match": "\\b[0-9][0-9_]*\\.[0-9][0-9_]*([eE][+-]?[0-9][0-9_]*)?\\b" - }, - { - "name": "constant.numeric.hex.affinescript", - "match": "\\b0x[0-9a-fA-F][0-9a-fA-F_]*\\b" - }, - { - "name": "constant.numeric.binary.affinescript", - "match": "\\b0b[01][01_]*\\b" - }, - { - "name": "constant.numeric.octal.affinescript", - "match": "\\b0o[0-7][0-7_]*\\b" - }, - { - "name": "constant.numeric.decimal.affinescript", - "match": "\\b[0-9][0-9_]*\\b" - } - ] - }, - "effects": { - "patterns": [ - { - "name": "entity.name.effect.affinescript", - "match": "\\b[A-Z][a-zA-Z0-9_]*\\b(?=\\s*(?:/|\\|))" - }, - { - "name": "keyword.operator.effect.affinescript", - "match": "/(?=\\s*[A-Z])" - }, - { - "name": "keyword.operator.effect-union.affinescript", - "match": "\\|(?=\\s*[A-Z])" - } - ] - }, - "types": { - "patterns": [ - { - "name": "entity.name.type.affinescript", - "match": "\\b[A-Z][a-zA-Z0-9_]*\\b" - }, - { - "name": "support.type.primitive.affinescript", - "match": "\\b(Int|Float|Bool|String|Unit|Never|Char)\\b" - }, - { - "name": "support.type.collection.affinescript", - "match": "\\b(List|Vec|Option|Result|Ref)\\b" - } - ] - }, - "functions": { - "patterns": [ - { - "name": "entity.name.function.affinescript", - "match": "\\b([a-z_][a-zA-Z0-9_]*)\\s*(?=\\()" - }, - { - "name": "entity.name.function.definition.affinescript", - "match": "(?<=fn\\s+)([a-z_][a-zA-Z0-9_]*)" - } - ] - }, - "operators": { - "patterns": [ - { - "name": "keyword.operator.arithmetic.affinescript", - "match": "[+\\-*/%]" - }, - { - "name": "keyword.operator.comparison.affinescript", - "match": "(==|!=|<|>|<=|>=)" - }, - { - "name": "keyword.operator.logical.affinescript", - "match": "(&&|\\|\\||!)" - }, - { - "name": "keyword.operator.assignment.affinescript", - "match": "=" - }, - { - "name": "keyword.operator.arrow.affinescript", - "match": "(->|=>|\\|)" - }, - { - "name": "keyword.operator.access.affinescript", - "match": "(\\.|\\.\\.|::)" - } - ] - }, - "constants": { - "patterns": [ - { - "name": "constant.language.boolean.affinescript", - "match": "\\b(true|false)\\b" - }, - { - "name": "constant.language.unit.affinescript", - "match": "\\(\\)" - } - ] - }, - "variables": { - "patterns": [ - { - "name": "variable.other.constant.affinescript", - "match": "\\b[A-Z][A-Z0-9_]*\\b" - }, - { - "name": "variable.parameter.affinescript", - "match": "\\b[a-z_][a-zA-Z0-9_]*\\b" - } - ] - } - } -} diff --git a/affinescript/examples/comprehensive_test.affine b/affinescript/examples/comprehensive_test.affine deleted file mode 100644 index ebdd854..0000000 --- a/affinescript/examples/comprehensive_test.affine +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Comprehensive parser/typecheck example - -struct Vec2 { - x: Int, - y: Int -} - -enum Shape { - Circle(Int), - Rect(Vec2) -} - -fn vec2_add(a: Vec2, b: Vec2) -> Vec2 { - { x: a.x + b.x, y: a.y + b.y } -} - -fn area(s: Shape) -> Int { - match s { - Circle(r) => r * r * 3, - Rect(size) => size.x * size.y - } -} - -fn max(a: Int, b: Int) -> Int { - if a > b { a } else { b } -} - -fn larger_area(s1: Shape, s2: Shape) -> Int = max(area(s1), area(s2)); - -fn main() -> () { - let circle = Circle(5); - let rect = Rect({ x: 3, y: 4 }); - let result = larger_area(circle, rect); - (); -} diff --git a/affinescript/examples/counter.afs b/affinescript/examples/counter.afs deleted file mode 100644 index 21dac0f..0000000 --- a/affinescript/examples/counter.afs +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 hyperpolymath -// -// AffineScript TEA counter — Stage 3c dogfood -// -// Demonstrates The Elm Architecture in AffineScript: -// - Model is an owned value: each update consumes the old model, produces a new one -// - Msg is linear: consumed by update, not reusable -// - Cmd carries obligations: None means no side-effects, Log carries a message to handle -// -// Run: affinescript eval examples/counter.afs -// At the stdin prompt, type one of: Increment Decrement Reset -// Ctrl-D (EOF) exits cleanly. - -// ── Msg ───────────────────────────────────────────────────────────────────── -// Linear: each message is consumed exactly once by update. - -enum CounterMsg { - Increment, - Decrement, - Reset -} - -// ── Cmd ───────────────────────────────────────────────────────────────────── -// Linear obligation. None → no effect. Log(String) → something to handle. -// Dropping a non-None Cmd at compile time is a quantity violation (Stage 1). - -enum CounterCmd { - None, - Log(String) -} - -// ── init ───────────────────────────────────────────────────────────────────── -// Produces the initial model. No commands needed at start-up. - -fn counter_init() -> Int { - 0 -} - -// ── update ─────────────────────────────────────────────────────────────────── -// Consumes Msg and old Model; produces new Model plus any commands. -// The old model is gone after this call — AffineScript enforces this via QTT. - -fn counter_update(msg: CounterMsg, model: Int) -> Int { - match msg { - Increment => model + 1, - Decrement => model - 1, - Reset => 0 - } -} - -// ── view ───────────────────────────────────────────────────────────────────── -// Borrows the model (read-only), renders a string. -// Rebuilding the view does not consume the model. - -fn counter_view(model: Int) -> String { - "Count: " ++ int_to_string(model) -} - -// ── subscriptions ──────────────────────────────────────────────────────────── -// No real-time subscriptions at the interpreter level. - -fn counter_subs(model: Int) -> String { - "None" -} - -// ── main ───────────────────────────────────────────────────────────────────── -// Wire up the TEA runtime and hand off to the interpreter loop. - -fn main() -> () { - tea_run({ - init: counter_init, - update: counter_update, - view: counter_view, - subscriptions: counter_subs - }) -} diff --git a/affinescript/examples/effects.affine b/affinescript/examples/effects.affine deleted file mode 100644 index cfe0dfe..0000000 --- a/affinescript/examples/effects.affine +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Effect declarations and annotations (parser-compatible example) - -effect IO { - fn print(s: String) -> (); - fn read() -> String; -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -fn increment() -{State[Int]}-> Int = 0; - -fn hello() -> () = (); diff --git a/affinescript/examples/exception_test.affine b/affinescript/examples/exception_test.affine deleted file mode 100644 index 864816a..0000000 --- a/affinescript/examples/exception_test.affine +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// try/catch/finally example (parser-compatible) - -fn full_try(x: Int) -> Int { - try { - x * 3 - } catch { - e => 0 - } finally { - () - } -} - -fn main() -> Int { - full_try(7) -} diff --git a/affinescript/examples/hello.affine b/affinescript/examples/hello.affine deleted file mode 100644 index fcd8245..0000000 --- a/affinescript/examples/hello.affine +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// -// Hello World in AffineScript -// Part of the AffineScript game examples - -effect IO { - fn print(s: String) -> (); - fn println(s: String) -> (); -} - -fn main() -{IO}-> () { - println("Hello, AffineScript!"); -} diff --git a/affinescript/examples/lessons/01_hello.affine b/affinescript/examples/lessons/01_hello.affine deleted file mode 100644 index 433a2a0..0000000 --- a/affinescript/examples/lessons/01_hello.affine +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Lesson 1: Hello AffineScript - Interactive Examples -// Load this in the playground to follow along! - -// ============================================================================ -// Example 1: Your First Program -// ============================================================================ - -println("Hello, AffineScript!"); - -// ============================================================================ -// Example 2: Simple Arithmetic -// ============================================================================ - -// Try each of these one at a time (uncomment to run): - -// 10 + 20 -// 5 * 6 - 2 -// 100 / 4 -// 10 + 20 * 3 -// (10 + 20) * 3 - -// ============================================================================ -// Example 3: Variables -// ============================================================================ - -let x = 42; -let y = 10; -let z = x + y; - -println(z); // Should print 52 - -// ============================================================================ -// Example 4: Types -// ============================================================================ - -let age: Int = 30; -let price: Float = 19.99; -let name: String = "Alice"; -let isActive: Bool = true; - -println(age); -println(price); -println(name); -println(isActive); - -// ============================================================================ -// Exercise 1: Calculate Your Age in Days -// ============================================================================ - -let myAge = 25; // Change to your age -let days = myAge * 365; -println(days); - -// ============================================================================ -// Exercise 2: Temperature Converter (Celsius to Fahrenheit) -// ============================================================================ - -let celsius = 25; -let fahrenheit = celsius * 9 / 5 + 32; -println(fahrenheit); - -// ============================================================================ -// Exercise 3: Circle Area -// ============================================================================ - -let radius = 5; -let pi = 3.14159; -let area = pi * radius * radius; -println(area); - -// ============================================================================ -// Exercise 4: Shopping Cart -// ============================================================================ - -let item1 = 19.99; -let item2 = 5.50; -let item3 = 12.00; -let subtotal = item1 + item2 + item3; -let tax = subtotal * 0.08; // 8% tax -let total = subtotal + tax; -println(total); - -// ============================================================================ -// Complete Example: Price Calculator with Discount and Tax -// ============================================================================ - -let basePrice = 100.0; -let taxRate = 0.08; // 8% -let discountRate = 0.15; // 15% off - -// Calculate discount -let discount = basePrice * discountRate; -let priceAfterDiscount = basePrice - discount; - -// Calculate tax -let salesTax = priceAfterDiscount * taxRate; - -// Final total -let finalTotal = priceAfterDiscount + salesTax; - -// Show results -println("Base price: "); -println(basePrice); -println("After discount: "); -println(priceAfterDiscount); -println("Tax: "); -println(salesTax); -println("Final total: "); -println(finalTotal); diff --git a/affinescript/examples/ownership.affine b/affinescript/examples/ownership.affine deleted file mode 100644 index 158988e..0000000 --- a/affinescript/examples/ownership.affine +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Ownership and borrowing (parser-compatible example) - -fn takes_owned(x: own String) -> () = (); - -fn takes_ref(x: ref Int) -> Int = 42; - -fn takes_mut(x: mut Bool) -> () = (); - -struct File { - fd: own Int -} - -fn read_file(f: ref File) -> Int = 42; diff --git a/affinescript/examples/rows.affine b/affinescript/examples/rows.affine deleted file mode 100644 index 3207044..0000000 --- a/affinescript/examples/rows.affine +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Record/row building blocks (parser-compatible example) - -struct Point2D { - x: Int, - y: Int -} - -struct Point3D { - x: Int, - y: Int, - z: Int -} - -fn getX(p: Point2D) -> Int = p.x; - -fn mk_point(x: Int, y: Int) -> Point2D { - { x: x, y: y } -} diff --git a/affinescript/examples/simple_test.affine b/affinescript/examples/simple_test.affine deleted file mode 100644 index f29d3e1..0000000 --- a/affinescript/examples/simple_test.affine +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Simple test for the interpreter - -fn add(x: Int, y: Int) -> Int { - x + y -} diff --git a/affinescript/examples/titlescreen.afs b/affinescript/examples/titlescreen.afs deleted file mode 100644 index 9046325..0000000 --- a/affinescript/examples/titlescreen.afs +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 hyperpolymath -// -// AffineScript TEA TitleScreen — Stage 3d dogfood -// -// IDApTIK title screen in AffineScript TEA. -// This is the bridge target for Stage 4: when the Wasm/JS bridge is done, -// this exact file compiles to a .wasm that AffineTEA.js drives via PixiJS. -// -// Model: screen state (dimensions, music playing, selected option) -// Msg: navigation events from the title menu -// Cmd: side-effects (BGM, SFX, screen navigation) -// -// Run: affinescript eval examples/titlescreen.afs -// Type: NewGame LoadGame Settings Credits Ctrl-D to exit - -// ── Msg ────────────────────────────────────────────────────────────────────── - -enum TitleMsg { - NewGame, - LoadGame, - Settings, - Credits -} - -// ── Cmd ────────────────────────────────────────────────────────────────────── - -enum TitleCmd { - None, - PlayBGM(String), - PlaySFX(String), - Navigate(String) -} - -// ── Model ──────────────────────────────────────────────────────────────────── - -struct TitleModel { - screen_w: Int, - screen_h: Int, - bgm_playing: Int, - selected: String -} - -// ── init ───────────────────────────────────────────────────────────────────── - -fn title_init() -> TitleModel { - { - screen_w: 1280, - screen_h: 720, - bgm_playing: 0, - selected: "none" - } -} - -// ── update ──────────────────────────────────────────────────────────────────── -// Consumes msg and model; returns updated model. -// In Stage 4, this also emits Cmd(Navigate(...)) which the JS bridge handles. - -fn title_update(msg: TitleMsg, model: TitleModel) -> TitleModel { - match msg { - NewGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "new_game" }, - LoadGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "load_game" }, - Settings => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "settings" }, - Credits => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "credits" } - } -} - -// ── view ───────────────────────────────────────────────────────────────────── -// Borrows model, renders the title screen state as a string. -// In Stage 4, this returns Html that the JS bridge applies to the Pixi scene. - -fn title_view(model: TitleModel) -> String { - "=== IDApTIK ===" ++ "\n" ++ - "Screen: " ++ int_to_string(model.screen_w) ++ "x" ++ int_to_string(model.screen_h) ++ "\n" ++ - "Selected: " ++ model.selected -} - -// ── subscriptions ───────────────────────────────────────────────────────────── - -fn title_subs(model: TitleModel) -> String { - "None" -} - -// ── main ────────────────────────────────────────────────────────────────────── - -fn main() -> () { - tea_run({ - init: title_init, - update: title_update, - view: title_view, - subscriptions: title_subs - }) -} diff --git a/affinescript/examples/traits.affine b/affinescript/examples/traits.affine deleted file mode 100644 index 29630dd..0000000 --- a/affinescript/examples/traits.affine +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Trait declarations and impls (parser-compatible example) - -trait Eq { - fn eq(a: Int, b: Int) -> Bool; -} - -trait Ord: Eq { - fn cmp(a: Int, b: Int) -> Int; -} - -trait Show { - fn show(x: Int) -> String; -} - -impl Eq for Int { - fn eq(a: Int, b: Int) -> Bool = true; -} - -impl Show for Int { - fn show(x: Int) -> String = "int"; -} - -struct Point { - x: Int, - y: Int -} - -impl Eq for Point { - fn eq(a: Int, b: Int) -> Bool = true; -} - -fn generic_id[T](v: T) -> T = v; diff --git a/affinescript/examples/typecheck_complete_test.affine b/affinescript/examples/typecheck_complete_test.affine deleted file mode 100644 index d14f23e..0000000 --- a/affinescript/examples/typecheck_complete_test.affine +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Type-checker complete-path sample (parser-compatible) - -type Alias = Int; - -struct Point { - x: Int, - y: Int -} - -enum Color { - Red, - Green, - Blue -} - -fn make_point(x: Int, y: Int) -> Point { - { x: x, y: y } -} - -fn origin() -> Point { - { x: 0, y: 0 } -} - -fn mutate_counter() -> Int { - let mut counter = 0; - counter = counter + 1; - counter = counter + 1; - counter -} - -fn try_with_finally(x: Int) -> Int { - try { - x * 2 - } finally { - () - } -} diff --git a/affinescript/examples/typecheck_features_test.affine b/affinescript/examples/typecheck_features_test.affine deleted file mode 100644 index 0377fa9..0000000 --- a/affinescript/examples/typecheck_features_test.affine +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Feature sample for parser + type checker - -effect IO { - fn print(s: String) -> (); -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -trait Show { - fn show(x: Int) -> String; -} - -impl Show for Int { - fn show(x: Int) -> String = "int"; -} - -struct File { - fd: own Int -} - -fn takes_file(f: ref File) -> Int = 0; - -fn stateful() -{State[Int]}-> Int = 0; - -fn to_string(x: Int) -> String = show(x); - -fn main() -> () = (); diff --git a/affinescript/examples/web-project-deno.json b/affinescript/examples/web-project-deno.json deleted file mode 100644 index eaa39d9..0000000 --- a/affinescript/examples/web-project-deno.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "// NOTE": "Example deno.json for ReScript web projects", - "tasks": { - "build": "deno run -A npm:rescript", - "clean": "deno run -A npm:rescript clean", - "watch": "deno run -A npm:rescript -w", - "serve": "deno run -A jsr:@std/http/file-server .", - "test": "deno test --allow-all" - }, - "imports": { - "rescript": "npm:rescript@^12.1.0", - "@rescript/core": "npm:@rescript/core@^1.6.0", - "safe-dom/": "https://raw.githubusercontent.com/hyperpolymath/rescript-dom-mounter/main/src/", - "proven/": "../proven/bindings/rescript/src/" - }, - "compilerOptions": { - "allowJs": true, - "checkJs": false - } -} diff --git a/affinescript/ffi/zig/build.zig b/affinescript/ffi/zig/build.zig deleted file mode 100644 index cebe930..0000000 --- a/affinescript/ffi/zig/build.zig +++ /dev/null @@ -1,95 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Proof Burrower FFI Build Configuration - -const std = @import("std"); - -pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - // Shared library (.so, .dylib, .dll) - const lib = b.addSharedLibrary(.{ - .name = "{{project}}", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - // Set version - lib.version = .{ .major = 0, .minor = 1, .patch = 0 }; - - // Static library (.a) - const lib_static = b.addStaticLibrary(.{ - .name = "{{project}}", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - // Install artifacts - b.installArtifact(lib); - b.installArtifact(lib_static); - - // Generate header file for C compatibility - const header = b.addInstallHeader( - b.path("include/{{project}}.h"), - "{{project}}.h", - ); - b.getInstallStep().dependOn(&header.step); - - // Unit tests - const lib_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - const run_lib_tests = b.addRunArtifact(lib_tests); - - const test_step = b.step("test", "Run library tests"); - test_step.dependOn(&run_lib_tests.step); - - // Integration tests - const integration_tests = b.addTest(.{ - .root_source_file = b.path("test/integration_test.zig"), - .target = target, - .optimize = optimize, - }); - - integration_tests.linkLibrary(lib); - - const run_integration_tests = b.addRunArtifact(integration_tests); - - const integration_test_step = b.step("test-integration", "Run integration tests"); - integration_test_step.dependOn(&run_integration_tests.step); - - // Documentation - const docs = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = .Debug, - }); - - const docs_step = b.step("docs", "Generate documentation"); - docs_step.dependOn(&b.addInstallDirectory(.{ - .source_dir = docs.getEmittedDocs(), - .install_dir = .prefix, - .install_subdir = "docs", - }).step); - - // Benchmark (if needed) - const bench = b.addExecutable(.{ - .name = "{{project}}-bench", - .root_source_file = b.path("bench/bench.zig"), - .target = target, - .optimize = .ReleaseFast, - }); - - bench.linkLibrary(lib); - - const run_bench = b.addRunArtifact(bench); - - const bench_step = b.step("bench", "Run benchmarks"); - bench_step.dependOn(&run_bench.step); -} diff --git a/affinescript/ffi/zig/src/main.zig b/affinescript/ffi/zig/src/main.zig deleted file mode 100644 index 65ddecb..0000000 --- a/affinescript/ffi/zig/src/main.zig +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Proof Burrower FFI Implementation -// -// This module implements the C-compatible FFI declared in src/abi/Foreign.idr -// All types and layouts must match the Idris2 ABI definitions. -// - -const std = @import("std"); - -// Version information (keep in sync with project) -const VERSION = "0.1.0"; -const BUILD_INFO = "Proof Burrower built with Zig " ++ @import("builtin").zig_version_string; - -/// Thread-local error storage -threadlocal var last_error: ?[]const u8 = null; - -/// Set the last error message -fn setError(msg: []const u8) void { - last_error = msg; -} - -/// Clear the last error -fn clearError() void { - last_error = null; -} - -//============================================================================== -// Core Types (must match src/abi/Types.idr) -//============================================================================== - -/// Result codes (must match Idris2 Result type) -pub const Result = enum(c_int) { - ok = 0, - @"error" = 1, - invalid_param = 2, - out_of_memory = 3, - null_pointer = 4, -}; - -/// Library handle (opaque to prevent direct access) -pub const Handle = opaque { - // Internal state hidden from C - allocator: std.mem.Allocator, - initialized: bool, - // Add your fields here -}; - -//============================================================================== -// Library Lifecycle -//============================================================================== - -/// Initialize the library -/// Returns a handle, or null on failure -export fn {{project}}_init() ?*Handle { - const allocator = std.heap.c_allocator; - - const handle = allocator.create(Handle) catch { - setError("Failed to allocate handle"); - return null; - }; - - // Initialize handle - handle.* = .{ - .allocator = allocator, - .initialized = true, - }; - - clearError(); - return handle; -} - -/// Free the library handle -export fn {{project}}_free(handle: ?*Handle) void { - const h = handle orelse return; - const allocator = h.allocator; - - // Clean up resources - h.initialized = false; - - allocator.destroy(h); - clearError(); -} - -//============================================================================== -// Core Operations -//============================================================================== - -/// Process data (example operation) -export fn {{project}}_process(handle: ?*Handle, input: u32) Result { - const h = handle orelse { - setError("Null handle"); - return .null_pointer; - }; - - if (!h.initialized) { - setError("Handle not initialized"); - return .@"error"; - } - - // Example processing logic - _ = input; - - clearError(); - return .ok; -} - -//============================================================================== -// String Operations -//============================================================================== - -/// Get a string result (example) -/// Caller must free the returned string -export fn {{project}}_get_string(handle: ?*Handle) ?[*:0]const u8 { - const h = handle orelse { - setError("Null handle"); - return null; - }; - - if (!h.initialized) { - setError("Handle not initialized"); - return null; - } - - // Example: allocate and return a string - const result = h.allocator.dupeZ(u8, "Example result") catch { - setError("Failed to allocate string"); - return null; - }; - - clearError(); - return result.ptr; -} - -/// Free a string allocated by the library -export fn {{project}}_free_string(str: ?[*:0]const u8) void { - const s = str orelse return; - const allocator = std.heap.c_allocator; - - const slice = std.mem.span(s); - allocator.free(slice); -} - -//============================================================================== -// Array/Buffer Operations -//============================================================================== - -/// Process an array of data -export fn {{project}}_process_array( - handle: ?*Handle, - buffer: ?[*]const u8, - len: u32, -) Result { - const h = handle orelse { - setError("Null handle"); - return .null_pointer; - }; - - const buf = buffer orelse { - setError("Null buffer"); - return .null_pointer; - }; - - if (!h.initialized) { - setError("Handle not initialized"); - return .@"error"; - } - - // Access the buffer - const data = buf[0..len]; - _ = data; - - // Process data here - - clearError(); - return .ok; -} - -//============================================================================== -// Error Handling -//============================================================================== - -/// Get the last error message -/// Returns null if no error -export fn {{project}}_last_error() ?[*:0]const u8 { - const err = last_error orelse return null; - - // Return C string (static storage, no need to free) - const allocator = std.heap.c_allocator; - const c_str = allocator.dupeZ(u8, err) catch return null; - return c_str.ptr; -} - -//============================================================================== -// Version Information -//============================================================================== - -/// Get the library version -export fn {{project}}_version() [*:0]const u8 { - return VERSION.ptr; -} - -/// Get build information -export fn {{project}}_build_info() [*:0]const u8 { - return BUILD_INFO.ptr; -} - -//============================================================================== -// Callback Support -//============================================================================== - -/// Callback function type (C ABI) -pub const Callback = *const fn (u64, u32) callconv(.C) u32; - -/// Register a callback -export fn {{project}}_register_callback( - handle: ?*Handle, - callback: ?Callback, -) Result { - const h = handle orelse { - setError("Null handle"); - return .null_pointer; - }; - - const cb = callback orelse { - setError("Null callback"); - return .null_pointer; - }; - - if (!h.initialized) { - setError("Handle not initialized"); - return .@"error"; - } - - // Store callback for later use - _ = cb; - - clearError(); - return .ok; -} - -//============================================================================== -// Utility Functions -//============================================================================== - -/// Check if handle is initialized -export fn {{project}}_is_initialized(handle: ?*Handle) u32 { - const h = handle orelse return 0; - return if (h.initialized) 1 else 0; -} - -//============================================================================== -// Tests -//============================================================================== - -test "lifecycle" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - try std.testing.expect({{project}}_is_initialized(handle) == 1); -} - -test "error handling" { - const result = {{project}}_process(null, 0); - try std.testing.expectEqual(Result.null_pointer, result); - - const err = {{project}}_last_error(); - try std.testing.expect(err != null); -} - -test "version" { - const ver = {{project}}_version(); - const ver_str = std.mem.span(ver); - try std.testing.expectEqualStrings(VERSION, ver_str); -} diff --git a/affinescript/ffi/zig/test/integration_test.zig b/affinescript/ffi/zig/test/integration_test.zig deleted file mode 100644 index 7bb716e..0000000 --- a/affinescript/ffi/zig/test/integration_test.zig +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Proof Burrower Integration Tests -// -// These tests verify that the Zig FFI correctly implements the Idris2 ABI - -const std = @import("std"); -const testing = std.testing; - -// Import FFI functions -extern fn {{project}}_init() ?*opaque {}; -extern fn {{project}}_free(?*opaque {}) void; -extern fn {{project}}_process(?*opaque {}, u32) c_int; -extern fn {{project}}_get_string(?*opaque {}) ?[*:0]const u8; -extern fn {{project}}_free_string(?[*:0]const u8) void; -extern fn {{project}}_last_error() ?[*:0]const u8; -extern fn {{project}}_version() [*:0]const u8; -extern fn {{project}}_is_initialized(?*opaque {}) u32; - -//============================================================================== -// Lifecycle Tests -//============================================================================== - -test "create and destroy handle" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - try testing.expect(handle != null); -} - -test "handle is initialized" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const initialized = {{project}}_is_initialized(handle); - try testing.expectEqual(@as(u32, 1), initialized); -} - -test "null handle is not initialized" { - const initialized = {{project}}_is_initialized(null); - try testing.expectEqual(@as(u32, 0), initialized); -} - -//============================================================================== -// Operation Tests -//============================================================================== - -test "process with valid handle" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const result = {{project}}_process(handle, 42); - try testing.expectEqual(@as(c_int, 0), result); // 0 = ok -} - -test "process with null handle returns error" { - const result = {{project}}_process(null, 42); - try testing.expectEqual(@as(c_int, 4), result); // 4 = null_pointer -} - -//============================================================================== -// String Tests -//============================================================================== - -test "get string result" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const str = {{project}}_get_string(handle); - defer if (str) |s| {{project}}_free_string(s); - - try testing.expect(str != null); -} - -test "get string with null handle" { - const str = {{project}}_get_string(null); - try testing.expect(str == null); -} - -//============================================================================== -// Error Handling Tests -//============================================================================== - -test "last error after null handle operation" { - _ = {{project}}_process(null, 0); - - const err = {{project}}_last_error(); - try testing.expect(err != null); - - if (err) |e| { - const err_str = std.mem.span(e); - try testing.expect(err_str.len > 0); - } -} - -test "no error after successful operation" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - _ = {{project}}_process(handle, 0); - - // Error should be cleared after successful operation - // (This depends on implementation) -} - -//============================================================================== -// Version Tests -//============================================================================== - -test "version string is not empty" { - const ver = {{project}}_version(); - const ver_str = std.mem.span(ver); - - try testing.expect(ver_str.len > 0); -} - -test "version string is semantic version format" { - const ver = {{project}}_version(); - const ver_str = std.mem.span(ver); - - // Should be in format X.Y.Z - try testing.expect(std.mem.count(u8, ver_str, ".") >= 1); -} - -//============================================================================== -// Memory Safety Tests -//============================================================================== - -test "multiple handles are independent" { - const h1 = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(h1); - - const h2 = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(h2); - - try testing.expect(h1 != h2); - - // Operations on h1 should not affect h2 - _ = {{project}}_process(h1, 1); - _ = {{project}}_process(h2, 2); -} - -test "double free is safe" { - const handle = {{project}}_init() orelse return error.InitFailed; - - {{project}}_free(handle); - {{project}}_free(handle); // Should not crash -} - -test "free null is safe" { - {{project}}_free(null); // Should not crash -} - -//============================================================================== -// Thread Safety Tests (if applicable) -//============================================================================== - -test "concurrent operations" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const ThreadContext = struct { - h: *opaque {}, - id: u32, - }; - - const thread_fn = struct { - fn run(ctx: ThreadContext) void { - _ = {{project}}_process(ctx.h, ctx.id); - } - }.run; - - var threads: [4]std.Thread = undefined; - for (&threads, 0..) |*thread, i| { - thread.* = try std.Thread.spawn(.{}, thread_fn, .{ - ThreadContext{ .h = handle, .id = @intCast(i) }, - }); - } - - for (threads) |thread| { - thread.join(); - } -} diff --git a/affinescript/hooks/validate-codeql.sh b/affinescript/hooks/validate-codeql.sh deleted file mode 100755 index 15b52c3..0000000 --- a/affinescript/hooks/validate-codeql.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate CodeQL language matrix matches repo -set -euo pipefail - -CODEQL_FILE=".github/workflows/codeql.yml" -[ -f "$CODEQL_FILE" ] || exit 0 - -# Detect languages in repo -HAS_JS=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" 2>/dev/null | grep -v node_modules | head -1) -HAS_PY=$(find . -name "*.py" 2>/dev/null | grep -v __pycache__ | head -1) -HAS_GO=$(find . -name "*.go" 2>/dev/null | head -1) -HAS_RS=$(find . -name "*.rs" 2>/dev/null | head -1) - -# Check if matrix includes unsupported languages -if grep -q "language:.*python" "$CODEQL_FILE" && [ -z "$HAS_PY" ]; then - echo "WARNING: CodeQL configured for Python but no .py files found" -fi -if grep -q "language:.*go" "$CODEQL_FILE" && [ -z "$HAS_GO" ]; then - echo "WARNING: CodeQL configured for Go but no .go files found" -fi -if grep -q "language:.*javascript" "$CODEQL_FILE" && [ -z "$HAS_JS" ]; then - echo "WARNING: CodeQL configured for JavaScript but no JS/TS files found" -fi - -# Rust/OCaml are not supported - should use 'actions' only -if [ -n "$HAS_RS" ]; then - if grep -q "language:.*rust" "$CODEQL_FILE"; then - echo "ERROR: CodeQL does not support Rust - use ['actions'] instead" - exit 1 - fi -fi - -exit 0 diff --git a/affinescript/hooks/validate-permissions.sh b/affinescript/hooks/validate-permissions.sh deleted file mode 100755 index 1999b01..0000000 --- a/affinescript/hooks/validate-permissions.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate workflow permissions declarations -set -euo pipefail -ERRORS=0 -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - if ! grep -qE '^permissions:' "$workflow"; then - echo "ERROR: Missing top-level permissions in $workflow" - ERRORS=$((ERRORS + 1)) - fi -done -[ $ERRORS -gt 0 ] && exit 1 -exit 0 diff --git a/affinescript/hooks/validate-sha-pins.sh b/affinescript/hooks/validate-sha-pins.sh deleted file mode 100755 index 697092b..0000000 --- a/affinescript/hooks/validate-sha-pins.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate GitHub Actions are SHA-pinned - -set -euo pipefail - -ERRORS=0 - -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - - # Find uses: lines that aren't SHA-pinned - while IFS= read -r line; do - if [[ "$line" =~ uses:.*@ ]]; then - # Check if it has a SHA (40 hex chars) - if ! echo "$line" | grep -qE '@[a-f0-9]{40}'; then - echo "ERROR: Unpinned action in $workflow" - echo " $line" - echo " Actions must use SHA pins: uses: action/name@SHA # version" - ERRORS=$((ERRORS + 1)) - fi - fi - done < "$workflow" -done - -if [ $ERRORS -gt 0 ]; then - echo "" - echo "Found $ERRORS unpinned actions. Please SHA-pin all GitHub Actions." - echo "Use: gh api repos/OWNER/REPO/git/matching-refs/tags/VERSION to find SHAs" - exit 1 -fi - -exit 0 diff --git a/affinescript/hooks/validate-spdx.sh b/affinescript/hooks/validate-spdx.sh deleted file mode 100755 index cc81cf1..0000000 --- a/affinescript/hooks/validate-spdx.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Pre-commit hook: Validate SPDX headers in workflow files - -set -euo pipefail - -ERRORS=0 -SPDX_PATTERN="^# SPDX-License-Identifier:" - -for workflow in .github/workflows/*.yml .github/workflows/*.yaml; do - [ -f "$workflow" ] || continue - - first_line=$(head -n1 "$workflow") - if ! echo "$first_line" | grep -qE "$SPDX_PATTERN"; then - echo "ERROR: Missing SPDX header in $workflow" - echo " First line should be: # SPDX-License-Identifier: MPL-2.0" - ERRORS=$((ERRORS + 1)) - fi -done - -if [ $ERRORS -gt 0 ]; then - exit 1 -fi - -exit 0 diff --git a/affinescript/humans.txt b/affinescript/humans.txt deleted file mode 100644 index f5f28ce..0000000 --- a/affinescript/humans.txt +++ /dev/null @@ -1,40 +0,0 @@ -/* TEAM */ - -Developer: Jonathan D.A. Jewell -Site: https://jonathanjewell.dev -Location: United Kingdom -Email: jonathan.jewell@open.ac.uk -GitHub: https://github.com/hyperpolymath -Keybase: https://keybase.io/hyperpolymath (coming soon) - -/* THANKS */ - -Claude Code (Anthropic) - Development assistance -GitHub - Hosting -Cloudflare - CDN & Security -Let's Encrypt - TLS Certificates - -/* SITE */ - -Last update: 2026/01/31 -Standards: HTML5, CSS3, JavaScript ES2024 -Components: ReScript, Deno, Rust -Security: TLS 1.3, HSTS, Consent-Aware HTTP -Languages: English -Doctype: HTML5 -IDE: Zed, Helix, VSCode - -/* REPOSITORY */ - -Repository: https://github.com/hyperpolymath/affinescript -License: MPL-2.0 -Documentation: MPL-2.0 preferred; MPL-2.0 where required -Issues: https://github.com/hyperpolymath/affinescript/issues -Security: https://github.com/hyperpolymath/affinescript/security - -/* CONTACT */ - -General: info@affinescript.dev -Security: security@affinescript.dev -Privacy: privacy@affinescript.dev -Support: support@affinescript.dev diff --git a/affinescript/js/dune b/affinescript/js/dune deleted file mode 100644 index 96c01a8..0000000 --- a/affinescript/js/dune +++ /dev/null @@ -1,21 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-or-later - -(executable - (name playground) - (modes js) - (modules playground js_api) - ; `unix` is listed explicitly because the affinescript library pulls - ; in Unix from interp.ml's filesystem/env builtins (opendir, mkdir, - ; rmdir, putenv). Without it js_of_ocaml aborts linking with - ; "Could not find compilation unit for Unix". js_of_ocaml ships a - ; Unix shim that stubs the unsupported calls — acceptable for a - ; browser playground since those builtins are never reached from - ; playground_of_string. - (libraries affinescript js_of_ocaml unix str) - (preprocess - (pps js_of_ocaml-ppx)) - (js_of_ocaml - (compilation_mode whole_program) - (javascript_files runtime.js) - (flags - (:standard --enable=effects --source-map)))) diff --git a/affinescript/js/js_api.ml b/affinescript/js/js_api.ml deleted file mode 100644 index b194ba6..0000000 --- a/affinescript/js/js_api.ml +++ /dev/null @@ -1,101 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** JavaScript API for AffineScript interpreter. - This module provides a JavaScript-compatible interface - for running the interpreter in the browser via js_of_ocaml. -*) - -open Js_of_ocaml - -(** JavaScript-compatible result type *) -class type eval_result = object - method success : bool Js.readonly_prop - method value : Js.js_string Js.t Js.optdef Js.readonly_prop - method error : Js.js_string Js.t Js.optdef Js.readonly_prop - method type_ : Js.js_string Js.t Js.optdef Js.readonly_prop -end - -(** Create a success result *) -let success_result value type_str : eval_result Js.t = - object%js - val success = true - val value = Js.def (Js.string value) - val error = Js.undefined - val type_ = Js.def (Js.string type_str) - end - -(** Create an error result *) -let error_result error_msg : eval_result Js.t = - object%js - val success = false - val value = Js.undefined - val error = Js.def (Js.string error_msg) - val type_ = Js.undefined - end - -(** Evaluate an expression string *) -let eval_expr_string (code : string) : eval_result Js.t = - try - (* Parse the expression *) - let expr = Affinescript.Parse_driver.parse_expr ~file:"" code in - - (* Create initial environment *) - let env = Affinescript.Interp.create_initial_env () in - - (* Evaluate *) - match Affinescript.Interp.eval env expr with - | Ok value -> - let value_str = Affinescript.Value.show_value value in - (* Try to get type if possible - for now just return "Any" *) - success_result value_str "Any" - | Error err -> - error_result (Affinescript.Value.show_eval_error err) - with - | Affinescript.Parse_driver.Parse_error (msg, _span) -> - error_result ("Parse error: " ^ msg) - | Affinescript.Lexer.Lexer_error (msg, _span) -> - error_result ("Lexer error: " ^ msg) - | exn -> - error_result ("Unexpected error: " ^ Printexc.to_string exn) - -(** Evaluate a full program *) -let eval_program_string (code : string) : eval_result Js.t = - try - (* Parse the program *) - let prog = Affinescript.Parse_driver.parse_string ~file:"" code in - - (* Evaluate declarations *) - match Affinescript.Interp.eval_program prog with - | Ok env -> - (* Return the environment as a string *) - let env_str = - List.map (fun (name, value) -> - Printf.sprintf "%s = %s" name (Affinescript.Value.show_value value) - ) env - |> String.concat "\n" - in - success_result env_str "Environment" - | Error err -> - error_result (Affinescript.Value.show_eval_error err) - with - | Affinescript.Parse_driver.Parse_error (msg, _span) -> - error_result ("Parse error: " ^ msg) - | Affinescript.Lexer.Lexer_error (msg, _span) -> - error_result ("Lexer error: " ^ msg) - | exn -> - error_result ("Unexpected error: " ^ Printexc.to_string exn) - -(** Export JavaScript API *) -let () = - Js.export "AffineScript" - (object%js - method eval code = - eval_expr_string (Js.to_string code) - - method evalProgram code = - eval_program_string (Js.to_string code) - - method version = - Js.string "0.1.0" - end) diff --git a/affinescript/js/playground.ml b/affinescript/js/playground.ml deleted file mode 100644 index 0f41c8e..0000000 --- a/affinescript/js/playground.ml +++ /dev/null @@ -1,12 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Entry point for JavaScript playground build. - This loads the JS API module by referencing it. -*) - -(* Reference the Js_api module to ensure it's linked *) -let () = () - -(* The JS API is automatically registered via Js.export in Js_api module *) -include Js_api diff --git a/affinescript/js/runtime.js b/affinescript/js/runtime.js deleted file mode 100644 index 3985dcb..0000000 --- a/affinescript/js/runtime.js +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Playground runtime shim for js_of_ocaml. -// -// Provides a browser-safe stub for `caml_unix_putenv` so the js_of_ocaml -// link step does not emit a "missing primitive" warning and does not -// generate a stub that raises at runtime on first touch. The Unix -// module is pulled in transitively via `affinescript/lib/interp.ml` -// (filesystem / env builtins); of its calls, only `Unix.putenv` lacks -// a js_of_ocaml runtime implementation in the default `+unix.js`. -// -// Browsers have no process environment, so the closest sensible -// behaviour is a no-op: the call returns unit without raising. The -// playground driver (js/playground.ml) never calls `Unix.putenv`, so -// this shim is defensive — it keeps linking quiet and guarantees that -// if any future playground path reaches `Unix.putenv` it degrades to -// a silent no-op instead of surfacing the jsoo stub's `Failure`. - -//Provides: caml_unix_putenv -function caml_unix_putenv(_name, _value) { - return 0; -} diff --git a/affinescript/lib/ast.ml b/affinescript/lib/ast.ml deleted file mode 100644 index 487031a..0000000 --- a/affinescript/lib/ast.ml +++ /dev/null @@ -1,378 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Abstract Syntax Tree for AffineScript *) - -(** Identifiers *) -type ident = { - name : string; - span : Span.t; -} -[@@deriving show, eq] - -(** Quantity annotations for QTT *) -type quantity = - | QZero (** Erased - compile time only *) - | QOne (** Linear - exactly once *) - | QOmega (** Unrestricted *) -[@@deriving show, eq] - -(** Ownership modifiers *) -type ownership = - | Own (** Owned value *) - | Ref (** Immutable borrow *) - | Mut (** Mutable borrow *) -[@@deriving show, eq] - -(** Visibility modifiers *) -type visibility = - | Private - | Public - | PubCrate - | PubSuper - | PubIn of ident list (** pub(Path.To.Module) *) -[@@deriving show, eq] - -(** Kinds *) -type kind = - | KType (** Type *) - | KRow (** Row *) - | KEffect (** Effect *) - | KArrow of kind * kind (** κ → κ *) -[@@deriving show, eq] - -(** Type parameters *) -type type_param = { - tp_quantity : quantity option; - tp_name : ident; - tp_kind : kind option; -} -[@@deriving show, eq] - -(** Type expressions *) -type type_expr = - | TyVar of ident (** Type variable *) - | TyCon of ident (** Type constructor *) - | TyApp of ident * type_arg list (** Vec[n, T] *) - | TyArrow of type_expr * quantity option * type_expr * effect_expr option (** T -{q}-> U / E *) - | TyTuple of type_expr list (** (T, U, V) *) - | TyRecord of row_field list * ident option (** {x: T, ..r} *) - | TyOwn of type_expr (** own T *) - | TyRef of type_expr (** ref T *) - | TyMut of type_expr (** mut T *) - | TyHole (** _ - infer *) - -and type_arg = - | TyArg of type_expr - -and row_field = { - rf_name : ident; - rf_ty : type_expr; -} - -(** Effect expressions *) -and effect_expr = - | EffVar of ident (** Effect variable *) - | EffCon of ident * type_arg list (** IO, Exn[E], etc *) - | EffUnion of effect_expr * effect_expr (** E1 + E2 *) -[@@deriving show, eq] - -(** Patterns *) -type pattern = - | PatWildcard of Span.t (** _ *) - | PatVar of ident (** x *) - | PatLit of literal (** 42, "hello" *) - | PatCon of ident * pattern list (** Some(x) *) - | PatTuple of pattern list (** (a, b, c) *) - | PatRecord of (ident * pattern option) list * bool (** {x, y: p, ..} *) - | PatOr of pattern * pattern (** p1 | p2 *) - | PatAs of ident * pattern (** x @ p *) - -(** Literals *) -and literal = - | LitInt of int * Span.t - | LitFloat of float * Span.t - | LitBool of bool * Span.t - | LitChar of char * Span.t - | LitString of string * Span.t - | LitUnit of Span.t -[@@deriving show, eq] - -(** Expressions *) -type expr = - | ExprLit of literal - | ExprVar of ident - | ExprLet of { - el_mut : bool; - el_quantity : quantity option; - (** QTT binder quantity, per ADR-002 / ADR-007. - None means: defaults to QOmega (unrestricted), the - unannotated case. The quantity scales the value context - in the typing rule q·Γ₁ + Γ₂ ⊢ let x :^q = e1 in e2. - Surface syntaxes that populate this: - - @linear / @erased / @unrestricted (Option C, primary) - - :1 / :0 / :ω (Option B, sugar) *) - el_pat : pattern; - el_ty : type_expr option; - el_value : expr; - el_body : expr option; - } - | ExprIf of { - ei_cond : expr; - ei_then : expr; - ei_else : expr option; - } - | ExprMatch of { - em_scrutinee : expr; - em_arms : match_arm list; - } - | ExprLambda of { - elam_params : param list; - elam_ret_ty : type_expr option; - elam_body : expr; - } - | ExprApp of expr * expr list (** f(x, y) *) - | ExprField of expr * ident (** e.field *) - | ExprTupleIndex of expr * int (** e.0 *) - | ExprIndex of expr * expr (** e[i] *) - | ExprTuple of expr list (** (a, b, c) *) - | ExprArray of expr list (** [a, b, c] *) - | ExprRecord of { - er_fields : (ident * expr option) list; (** {x: 1, y} *) - er_spread : expr option; (** ..base *) - } - | ExprRowRestrict of expr * ident (** e \ field *) - | ExprBinary of expr * binary_op * expr - | ExprUnary of unary_op * expr - | ExprBlock of block - | ExprReturn of expr option - | ExprTry of { - et_body : block; - et_catch : match_arm list option; - et_finally : block option; - } - | ExprHandle of { - eh_body : expr; - eh_handlers : handler_arm list; - } - | ExprResume of expr option - | ExprUnsafe of unsafe_op list - | ExprVariant of ident * ident (** Type::Variant *) - | ExprSpan of expr * Span.t (** Span wrapper *) - -and match_arm = { - ma_pat : pattern; - ma_guard : expr option; - ma_body : expr; -} - -and handler_arm = - | HandlerReturn of pattern * expr - | HandlerOp of ident * pattern list * expr - -and block = { - blk_stmts : stmt list; - blk_expr : expr option; -} - -and stmt = - | StmtLet of { - sl_mut : bool; - sl_quantity : quantity option; - (** QTT binder quantity for statement-position let, per - ADR-002 / ADR-007. None defaults to QOmega. Same surface - syntaxes as ExprLet's el_quantity. *) - sl_pat : pattern; - sl_ty : type_expr option; - sl_value : expr; - } - | StmtExpr of expr - | StmtAssign of expr * assign_op * expr - | StmtWhile of expr * block - | StmtFor of pattern * expr * block - -and binary_op = - | OpAdd | OpSub | OpMul | OpDiv | OpMod | OpConcat - | OpEq | OpNe | OpLt | OpLe | OpGt | OpGe - | OpAnd | OpOr - | OpBitAnd | OpBitOr | OpBitXor | OpShl | OpShr - -and unary_op = - | OpNeg | OpNot | OpBitNot | OpRef | OpDeref - -and assign_op = - | AssignEq | AssignAdd | AssignSub | AssignMul | AssignDiv - -and unsafe_op = - | UnsafeRead of expr - | UnsafeWrite of expr * expr - | UnsafeOffset of expr * expr - | UnsafeTransmute of type_expr * type_expr * expr - | UnsafeForget of expr -[@@deriving show, eq] - -(** Parameters *) -and param = { - p_quantity : quantity option; - p_ownership : ownership option; - p_name : ident; - p_ty : type_expr; -} -[@@deriving show, eq] - -(** Trait bounds *) -type trait_bound = { - tb_name : ident; - tb_args : type_arg list; -} -[@@deriving show, eq] - -(** Where clause constraints *) -type constraint_ = - | ConstraintTrait of ident * trait_bound list -[@@deriving show, eq] - -(** Function declaration *) -type fn_decl = { - fd_vis : visibility; - fd_total : bool; - fd_name : ident; - fd_type_params : type_param list; - fd_params : param list; - fd_ret_ty : type_expr option; - fd_eff : effect_expr option; - fd_where : constraint_ list; - fd_body : fn_body; -} - -and fn_body = - | FnBlock of block - | FnExpr of expr -[@@deriving show, eq] - -(** Type declaration *) -type type_decl = { - td_vis : visibility; - td_name : ident; - td_type_params : type_param list; - td_body : type_body; -} - -and type_body = - | TyAlias of type_expr - | TyStruct of struct_field list - | TyEnum of variant_decl list - -and struct_field = { - sf_vis : visibility; - sf_name : ident; - sf_ty : type_expr; -} - -and variant_decl = { - vd_name : ident; - vd_fields : type_expr list; - vd_ret_ty : type_expr option; (** GADT return type *) -} -[@@deriving show, eq] - -(** Effect declaration *) -type effect_decl = { - ed_vis : visibility; - ed_name : ident; - ed_type_params : type_param list; - ed_ops : effect_op_decl list; -} - -and effect_op_decl = { - eod_name : ident; - eod_params : param list; - eod_ret_ty : type_expr option; -} -[@@deriving show, eq] - -(** Trait declaration *) -type trait_decl = { - trd_vis : visibility; - trd_name : ident; - trd_type_params : type_param list; - trd_super : trait_bound list; - trd_items : trait_item list; -} - -and trait_item = - | TraitFn of fn_sig - | TraitFnDefault of fn_decl - | TraitType of { - tt_name : ident; - tt_kind : kind option; - tt_default : type_expr option; - } - -and fn_sig = { - fs_vis : visibility; - fs_name : ident; - fs_type_params : type_param list; - fs_params : param list; - fs_ret_ty : type_expr option; - fs_eff : effect_expr option; -} -[@@deriving show, eq] - -(** Impl block *) -type impl_block = { - ib_type_params : type_param list; - ib_trait_ref : trait_ref option; - ib_self_ty : type_expr; - ib_where : constraint_ list; - ib_items : impl_item list; -} - -and trait_ref = { - tr_name : ident; - tr_args : type_arg list; -} - -and impl_item = - | ImplFn of fn_decl - | ImplType of ident * type_expr -[@@deriving show, eq] - -(** Module path *) -type module_path = ident list -[@@deriving show, eq] - -(** Import declaration *) -type import_decl = - | ImportSimple of module_path * ident option (** use A.B as C *) - | ImportList of module_path * import_item list (** use A.B::{x, y} *) - | ImportGlob of module_path (** use A.B::* *) - -and import_item = { - ii_name : ident; - ii_alias : ident option; -} -[@@deriving show, eq] - -(** Top-level declarations *) -type top_level = - | TopFn of fn_decl - | TopType of type_decl - | TopEffect of effect_decl - | TopTrait of trait_decl - | TopImpl of impl_block - | TopConst of { - tc_vis : visibility; - tc_name : ident; - tc_ty : type_expr; - tc_value : expr; - } -[@@deriving show, eq] - -(** Complete program *) -type program = { - prog_module : module_path option; - prog_imports : import_decl list; - prog_decls : top_level list; -} -[@@deriving show, eq] diff --git a/affinescript/lib/borrow.ml b/affinescript/lib/borrow.ml deleted file mode 100644 index a5ead95..0000000 --- a/affinescript/lib/borrow.ml +++ /dev/null @@ -1,885 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Borrow checker for ownership verification. - - This module implements borrow checking to ensure memory safety: - - No use after move - - No conflicting borrows - - Borrows don't outlive owners - - [IMPL-DEP: Phase 3] -*) - -open Ast - -(** A place is an l-value that can be borrowed *) -type place = - | PlaceVar of string * Symbol.symbol_id (** human name, symbol id *) - | PlaceField of place * string - | PlaceIndex of place * int option (** None for dynamic index *) - | PlaceDeref of place -[@@deriving show] - -(** Borrow kind *) -type borrow_kind = - | Shared (** Immutable borrow (&) *) - | Exclusive (** Mutable borrow (&mut) *) -[@@deriving show, eq] - -(** Human-readable borrow kind for error messages. *) -let borrow_kind_name (k : borrow_kind) : string = - match k with - | Shared -> "shared" - | Exclusive -> "exclusive" - -(** A borrow record *) -type borrow = { - b_place : place; - b_kind : borrow_kind; - b_span : Span.t; - b_id : int; -} -[@@deriving show] - -(** Move record for tracking move sites *) -type move_record = { - m_place : place; - m_span : Span.t; -} -[@@deriving show] - -(** Function signature for ownership tracking *) -type fn_signature = { - fn_name : string; - fn_param_ownerships : ownership option list; -} - -(** Borrow checker context with function signatures *) -type context = { - fn_sigs : (string, fn_signature) Hashtbl.t; -} - -(** Borrow checker state *) -type state = { - (** Active borrows *) - mutable borrows : borrow list; - - (** Moved places with their move sites *) - mutable moved : move_record list; - - (** Next borrow ID *) - mutable next_id : int; - - (** Mutable bindings - places that were declared with 'let mut' *) - mutable mutable_bindings : place list; -} - -(** Borrow checker errors *) -type borrow_error = - | UseAfterMove of place * Span.t * Span.t (** place, use site, move site *) - | ConflictingBorrow of borrow * borrow - | BorrowOutlivesOwner of borrow * Symbol.symbol_id - | MoveWhileBorrowed of place * borrow - | CannotMoveOutOfBorrow of place * borrow - | CannotBorrowAsMutable of place * Span.t -[@@deriving show] - -type 'a result = ('a, borrow_error) Result.t - -(* Result bind - define before use *) -let ( let* ) = Result.bind - -(** Create a new borrow checker context *) -let create_context () : context = - { - fn_sigs = Hashtbl.create 64; - } - -(** Create a new borrow checker state *) -let create () : state = - { - borrows = []; - moved = []; - next_id = 0; - mutable_bindings = []; - } - -(** Add a function signature to context *) -let add_fn_signature (ctx : context) (fd : fn_decl) : unit = - let sig_ = { - fn_name = fd.fd_name.name; - fn_param_ownerships = List.map (fun p -> p.p_ownership) fd.fd_params; - } in - Hashtbl.replace ctx.fn_sigs fd.fd_name.name sig_ - -(** Build context from program *) -let build_context (program : program) : context = - let ctx = create_context () in - List.iter (fun decl -> - match decl with - | TopFn fd -> add_fn_signature ctx fd - | _ -> () - ) program.prog_decls; - ctx - -(** Generate a fresh borrow ID *) -let fresh_id (state : state) : int = - let id = state.next_id in - state.next_id <- id + 1; - id - -(** Check if a type is Copy (doesn't need to be moved) *) -let rec is_copy_type (ty_opt : type_expr option) : bool = - match ty_opt with - | None -> false (* Unknown type, assume not Copy *) - | Some ty -> - begin match ty with - | TyCon id when id.name = "Int" || id.name = "Bool" || id.name = "Char" -> true - | TyCon id when id.name = "Unit" -> true - | TyTuple tys -> List.for_all (fun t -> is_copy_type (Some t)) tys - | TyRef _ -> true (* Shared references are Copy *) - | _ -> false (* Records, arrays, owned types, etc. are not Copy *) - end - -(** Check if an expression has a Copy type (heuristic for literals) *) -let is_copy_expr (expr : expr) : bool = - match expr with - | ExprLit (LitInt _) -> true - | ExprLit (LitBool _) -> true - | ExprLit (LitChar _) -> true - | ExprLit (LitUnit _) -> true - | ExprUnary (OpRef, _) -> true (* Reference creation produces a Copy pointer *) - | _ -> false - -(** Check if two places overlap *) -let rec places_overlap (p1 : place) (p2 : place) : bool = - match (p1, p2) with - | (PlaceVar (_, v1), PlaceVar (_, v2)) -> v1 = v2 - | (PlaceField (base1, _), PlaceField (base2, _)) -> - places_overlap base1 base2 - | (PlaceVar _, PlaceField (base, _)) - | (PlaceField (base, _), PlaceVar _) -> - places_overlap p1 base || places_overlap base p2 - | (PlaceDeref p1', PlaceDeref p2') -> - places_overlap p1' p2' - | _ -> false - -(** Check if a place is moved and return the move site if so *) -let find_move (state : state) (place : place) : Span.t option = - List.find_map (fun mr -> - if places_overlap place mr.m_place then Some mr.m_span - else None - ) state.moved - -(** Check if a place is moved *) -let is_moved (state : state) (place : place) : bool = - Option.is_some (find_move state place) - -(** Check if a borrow conflicts with existing borrows *) -let find_conflicting_borrow (state : state) (new_borrow : borrow) : borrow option = - List.find_opt (fun existing -> - places_overlap new_borrow.b_place existing.b_place && - (new_borrow.b_kind = Exclusive || existing.b_kind = Exclusive) - ) state.borrows - -(** Record a move *) -let record_move (state : state) (place : place) (span : Span.t) : unit result = - (* Check for active borrows *) - match List.find_opt (fun b -> places_overlap place b.b_place) state.borrows with - | Some borrow -> Error (MoveWhileBorrowed (place, borrow)) - | None -> - state.moved <- { m_place = place; m_span = span } :: state.moved; - Ok () - -(** Check if a place is mutable *) -let is_mutable (state : state) (place : place) : bool = - List.exists (fun mut_place -> places_overlap place mut_place) state.mutable_bindings - -(** Record a borrow *) -let record_borrow (state : state) (place : place) (kind : borrow_kind) - (span : Span.t) : borrow result = - (* Check if moved and report the original move site *) - match find_move state place with - | Some move_site -> - Error (UseAfterMove (place, span, move_site)) - | None -> - (* Check if trying to mutably borrow an immutable place *) - let mut_check = match kind with - | Exclusive -> - if not (is_mutable state place) then - Error (CannotBorrowAsMutable (place, span)) - else - Ok () - | Shared -> Ok () - in - match mut_check with - | Error _ as err -> err - | Ok () -> - let new_borrow = { - b_place = place; - b_kind = kind; - b_span = span; - b_id = fresh_id state; - } in - match find_conflicting_borrow state new_borrow with - | Some conflict -> Error (ConflictingBorrow (new_borrow, conflict)) - | None -> - state.borrows <- new_borrow :: state.borrows; - Ok new_borrow - -(** End a borrow *) -let end_borrow (state : state) (borrow : borrow) : unit = - state.borrows <- List.filter (fun b -> b.b_id <> borrow.b_id) state.borrows - -(** Check a use of a place *) -let check_use (state : state) (place : place) (span : Span.t) : unit result = - match find_move state place with - | Some move_site -> Error (UseAfterMove (place, span, move_site)) - | None -> Ok () - -(** Format a place for display in error messages. *) -let rec format_place (p : place) : string = - match p with - | PlaceVar (name, _) -> name - | PlaceField (base, f) -> format_place base ^ "." ^ f - | PlaceIndex (base, Some i) -> format_place base ^ "[" ^ string_of_int i ^ "]" - | PlaceIndex (base, None) -> format_place base ^ "[_]" - | PlaceDeref p' -> "*" ^ format_place p' - -(** Format a span for display. *) -let format_span (span : Span.t) : string = - Format.asprintf "%a" Span.pp_short span - -(** Format a borrow error as a human-readable string. *) -let format_borrow_error (e : borrow_error) : string = - match e with - | UseAfterMove (place, use_span, move_span) -> - Printf.sprintf - "use of moved value: `%s`\n \ - value used at %s\n \ - value moved at %s" - (format_place place) (format_span use_span) (format_span move_span) - | ConflictingBorrow (b1, b2) -> - Printf.sprintf - "conflicting borrows on `%s`:\n \ - %s borrow (id %d) at %s conflicts with earlier %s borrow (id %d) at %s" - (format_place b1.b_place) - (borrow_kind_name b1.b_kind) b1.b_id (format_span b1.b_span) - (borrow_kind_name b2.b_kind) b2.b_id (format_span b2.b_span) - | BorrowOutlivesOwner (b, sym_id) -> - Printf.sprintf - "borrow of `%s` (id %d) outlives its owner (symbol %d)" - (format_place b.b_place) b.b_id sym_id - | MoveWhileBorrowed (place, b) -> - Printf.sprintf - "cannot move `%s` while it is %s-borrowed at %s" - (format_place place) (borrow_kind_name b.b_kind) (format_span b.b_span) - | CannotMoveOutOfBorrow (place, b) -> - Printf.sprintf - "cannot move out of `%s`, which is behind a %s borrow at %s" - (format_place place) (borrow_kind_name b.b_kind) (format_span b.b_span) - | CannotBorrowAsMutable (place, span) -> - Printf.sprintf - "cannot borrow `%s` as mutable — it is not declared with `let mut` (at %s)" - (format_place place) (format_span span) - -(** Get span from an expression *) -let rec expr_span (expr : expr) : Span.t = - match expr with - | ExprSpan (_, span) -> span - | ExprLit lit -> lit_span lit - | ExprVar id -> id.span - | ExprLet { el_pat; _ } -> pattern_span el_pat - | ExprIf { ei_cond; _ } -> expr_span ei_cond - | ExprMatch { em_scrutinee; _ } -> expr_span em_scrutinee - | ExprLambda { elam_params; _ } -> - begin match elam_params with - | p :: _ -> p.p_name.span - | [] -> Span.dummy - end - | ExprApp (f, _) -> expr_span f - | ExprField (e, _) -> expr_span e - | ExprTupleIndex (e, _) -> expr_span e - | ExprIndex (e, _) -> expr_span e - | ExprTuple exprs -> - begin match exprs with - | e :: _ -> expr_span e - | [] -> Span.dummy - end - | ExprArray exprs -> - begin match exprs with - | e :: _ -> expr_span e - | [] -> Span.dummy - end - | ExprRecord { er_fields; _ } -> - begin match er_fields with - | (id, _) :: _ -> id.span - | [] -> Span.dummy - end - | ExprRowRestrict (e, _) -> expr_span e - | ExprBinary (e, _, _) -> expr_span e - | ExprUnary (_, e) -> expr_span e - | ExprBlock { blk_stmts; blk_expr } -> - begin match blk_stmts with - | StmtLet { sl_pat; _ } :: _ -> pattern_span sl_pat - | StmtExpr e :: _ -> expr_span e - | StmtAssign (e, _, _) :: _ -> expr_span e - | StmtWhile (e, _) :: _ -> expr_span e - | StmtFor (p, _, _) :: _ -> pattern_span p - | [] -> match blk_expr with Some e -> expr_span e | None -> Span.dummy - end - | ExprReturn _ -> Span.dummy - | ExprTry _ -> Span.dummy - | ExprHandle { eh_body; _ } -> expr_span eh_body - | ExprResume _ -> Span.dummy - | ExprUnsafe _ -> Span.dummy - | ExprVariant (id, _) -> id.span - -and lit_span (lit : literal) : Span.t = - match lit with - | LitInt (_, span) -> span - | LitFloat (_, span) -> span - | LitBool (_, span) -> span - | LitChar (_, span) -> span - | LitString (_, span) -> span - | LitUnit span -> span - -and pattern_span (pat : pattern) : Span.t = - match pat with - | PatWildcard span -> span - | PatVar id -> id.span - | PatLit lit -> lit_span lit - | PatCon (id, _) -> id.span - | PatTuple pats -> - begin match pats with - | p :: _ -> pattern_span p - | [] -> Span.dummy - end - | PatRecord ((id, _) :: _, _) -> id.span - | PatRecord ([], _) -> Span.dummy - | PatOr (p1, _) -> pattern_span p1 - | PatAs (id, _) -> id.span - -(** Lookup a symbol by name, searching all_symbols table *) -(** This is needed because scopes are exited after resolution *) -let lookup_symbol_by_name (symbols : Symbol.t) (name : string) : Symbol.symbol option = - (* Search all_symbols table for most recent symbol with this name *) - let matching_symbols = Hashtbl.fold (fun _id sym acc -> - if sym.Symbol.sym_name = name && sym.Symbol.sym_kind = Symbol.SKVariable then - sym :: acc - else - acc - ) symbols.Symbol.all_symbols [] in - (* Sort by symbol ID (descending) to get most recent *) - let sorted_symbols = List.sort (fun a b -> compare b.Symbol.sym_id a.Symbol.sym_id) matching_symbols in - match sorted_symbols with - | sym :: _ -> Some sym - | [] -> None - -(** Convert an expression to a place (if it's an l-value) *) -let rec expr_to_place (symbols : Symbol.t) (expr : expr) : place option = - match expr with - | ExprVar id -> - begin match lookup_symbol_by_name symbols id.name with - | Some sym -> Some (PlaceVar (id.name, sym.sym_id)) - | None -> None - end - | ExprField (base, field) -> - begin match expr_to_place symbols base with - | Some base_place -> Some (PlaceField (base_place, field.name)) - | None -> None - end - | ExprIndex (base, _) -> - begin match expr_to_place symbols base with - | Some base_place -> Some (PlaceIndex (base_place, None)) - | None -> None - end - | ExprSpan (e, _) -> - expr_to_place symbols e - | _ -> None - -(** Check borrows in an expression *) -let rec check_expr (ctx : context) (state : state) (symbols : Symbol.t) (expr : expr) : unit result = - match expr with - | ExprVar id -> - begin match expr_to_place symbols expr with - | Some place -> - (* Only check use, don't move - moves happen explicitly at call sites *) - check_use state place id.span - | None -> Ok () - end - - | ExprLit _ -> Ok () - - | ExprApp (func, args) -> - let* () = check_expr ctx state symbols func in - (* Check if this is a direct function call and get ownership info *) - let param_ownerships = - match func with - | ExprVar fn_id -> - begin match Hashtbl.find_opt ctx.fn_sigs fn_id.name with - | Some sig_ -> sig_.fn_param_ownerships - | None -> List.map (fun _ -> None) args (* Unknown function, assume no ownership *) - end - | _ -> List.map (fun _ -> None) args (* Higher-order call, assume no ownership *) - in - (* Check each argument according to parameter ownership *) - List.fold_left2 (fun acc arg param_own -> - let* () = acc in - (* First check the argument expression itself *) - let* () = check_expr ctx state symbols arg in - (* Then check ownership constraints *) - match param_own with - | Some Own -> - (* Owned parameter - argument must be moved *) - begin match expr_to_place symbols arg with - | Some place -> - let span = expr_span arg in - record_move state place span - | None -> Ok () (* Non-place expressions (literals, etc.) are fine *) - end - | Some Ref -> - (* Borrowed parameter - create a shared borrow *) - begin match expr_to_place symbols arg with - | Some place -> - let span = expr_span arg in - let* _borrow = record_borrow state place Shared span in - Ok () - | None -> Ok () - end - | Some Mut -> - (* Mutable borrow - create an exclusive borrow *) - begin match expr_to_place symbols arg with - | Some place -> - let span = expr_span arg in - let* _borrow = record_borrow state place Exclusive span in - Ok () - | None -> Ok () - end - | None -> - (* No ownership annotation - just check usage *) - Ok () - ) (Ok ()) args param_ownerships - - | ExprLambda lam -> - (* Collect every free variable referenced in the lambda body — - variables that appear in the body but are NOT bound by the lambda's - own parameter list. Each free variable is "captured" by the closure. - - Borrow-checker contract for captures: - - We create a Shared borrow for each captured place. This - prevents the caller from moving the variable out from under the - closure while it is still in scope (use-after-move). - - The borrows expire at the end of the enclosing block thanks to - the lexical-lifetime clearing in check_block. - - Ownership / linear duplications are caught by the quantity - checker (quantity.ml ExprLambda scales captures by QOmega), - so the borrow checker only needs to prevent structural misuse. *) - let param_names = - List.map (fun (p : param) -> p.p_name.name) lam.elam_params - in - (* Walk the body collecting every ExprVar name not bound by params. *) - let rec collect_free (acc : string list) (expr : expr) : string list = - match expr with - | ExprVar id -> - if List.mem id.name param_names || List.mem id.name acc - then acc - else id.name :: acc - | ExprLambda inner -> - (* For nested lambdas, shadow the inner params too. *) - let inner_params = List.map (fun (p : param) -> p.p_name.name) inner.elam_params in - let outer_free = collect_free acc inner.elam_body in - List.filter (fun n -> not (List.mem n inner_params)) outer_free - | ExprLit _ | ExprVariant _ -> acc - | ExprApp (f, args) -> - List.fold_left collect_free (collect_free acc f) args - | ExprLet lb -> - let acc' = collect_free acc lb.el_value in - (match lb.el_body with Some b -> collect_free acc' b | None -> acc') - | ExprIf ei -> - let acc' = collect_free (collect_free acc ei.ei_cond) ei.ei_then in - (match ei.ei_else with Some e -> collect_free acc' e | None -> acc') - | ExprMatch em -> - let acc' = collect_free acc em.em_scrutinee in - List.fold_left (fun a arm -> - let a' = match arm.ma_guard with Some g -> collect_free a g | None -> a in - collect_free a' arm.ma_body - ) acc' em.em_arms - | ExprBlock blk -> - let acc' = List.fold_left (fun a stmt -> - match stmt with - | StmtLet sl -> collect_free a sl.sl_value - | StmtExpr e -> collect_free a e - | StmtAssign (lhs, _, rhs) -> collect_free (collect_free a lhs) rhs - | StmtWhile (cond, body) -> collect_free (collect_free a cond) (ExprBlock body) - | StmtFor (_, iter, body) -> collect_free (collect_free a iter) (ExprBlock body) - ) acc blk.blk_stmts in - (match blk.blk_expr with Some e -> collect_free acc' e | None -> acc') - | ExprBinary (l, _, r) -> collect_free (collect_free acc l) r - | ExprUnary (_, e) | ExprReturn (Some e) | ExprField (e, _) - | ExprTupleIndex (e, _) | ExprRowRestrict (e, _) | ExprSpan (e, _) -> - collect_free acc e - | ExprReturn None | ExprResume None | ExprUnsafe [] -> acc - | ExprResume (Some e) -> collect_free acc e - | ExprTuple es | ExprArray es -> List.fold_left collect_free acc es - | ExprRecord er -> - let acc' = List.fold_left (fun a (_, e_opt) -> - match e_opt with Some e -> collect_free a e | None -> a - ) acc er.er_fields in - (match er.er_spread with Some e -> collect_free acc' e | None -> acc') - | ExprIndex (a, i) -> collect_free (collect_free acc a) i - | ExprTry et -> - let acc' = collect_free acc (ExprBlock et.et_body) in - let acc'' = match et.et_catch with - | Some arms -> List.fold_left (fun a arm -> - let a' = match arm.ma_guard with Some g -> collect_free a g | None -> a in - collect_free a' arm.ma_body) acc' arms - | None -> acc' - in - (match et.et_finally with Some b -> collect_free acc'' (ExprBlock b) | None -> acc'') - | ExprHandle eh -> - let acc' = collect_free acc eh.eh_body in - List.fold_left (fun a arm -> - match arm with - | HandlerReturn (_, b) | HandlerOp (_, _, b) -> collect_free a b - ) acc' eh.eh_handlers - | ExprUnsafe ops -> - List.fold_left (fun a op -> - match op with - | UnsafeRead e | UnsafeForget e -> collect_free a e - | UnsafeWrite (e1, e2) | UnsafeOffset (e1, e2) -> collect_free (collect_free a e1) e2 - | UnsafeTransmute (_, _, e) -> collect_free a e - ) acc ops - in - let free_names = collect_free [] lam.elam_body in - (* Create a Shared borrow for each captured free variable. If a borrow - conflict or use-after-move is detected, fail immediately. *) - let borrow_span = expr_span (ExprLambda lam) in - let* () = List.fold_left (fun acc name -> - let* () = acc in - match expr_to_place symbols (ExprVar { name; span = borrow_span }) with - | Some place -> - (* Check the place is not already moved before we borrow it. *) - let* _borrow = record_borrow state place Shared borrow_span in - Ok () - | None -> Ok () - ) (Ok ()) free_names in - (* Walk the body for structural checking (nested borrows / moves). *) - check_expr ctx state symbols lam.elam_body - - | ExprLet lb -> - let* () = check_expr ctx state symbols lb.el_value in - begin match lb.el_body with - | Some body -> check_expr ctx state symbols body - | None -> Ok () - end - - | ExprIf ei -> - let* () = check_expr ctx state symbols ei.ei_cond in - (* Save state before branches *) - let saved_borrows = state.borrows in - let saved_moved = state.moved in - (* Check then branch *) - let* () = check_expr ctx state symbols ei.ei_then in - let then_borrows = state.borrows in - let then_moved = state.moved in - (* Restore state for else branch *) - state.borrows <- saved_borrows; - state.moved <- saved_moved; - (* Check else branch if present *) - let* () = match ei.ei_else with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - in - (* Merge branch states: borrows must be from both branches, moves from either *) - let else_borrows = state.borrows in - let else_moved = state.moved in - (* A borrow is active after if-then-else only if active in both branches *) - state.borrows <- List.filter (fun b -> - List.exists (fun b' -> b.b_id = b'.b_id) then_borrows - ) else_borrows; - (* A place is moved after if-then-else if moved in either branch *) - state.moved <- then_moved @ else_moved; - Ok () - - | ExprMatch em -> - let* () = check_expr ctx state symbols em.em_scrutinee in - (* Each arm is independent: run each against the post-scrutinee state, - then merge. A place is moved after the match if it is moved in any - arm (conservative: we require moves to happen in all arms before - assuming the value is gone from the outer scope). Borrows from - individual arms expire at arm exit. *) - let base_borrows = state.borrows in - let base_moved = state.moved in - let arm_results = List.map (fun arm -> - (* Reset to post-scrutinee state for each arm *) - state.borrows <- base_borrows; - state.moved <- base_moved; - let r = - let open Result in - bind (match arm.ma_guard with - | Some g -> check_expr ctx state symbols g - | None -> Ok ()) - (fun () -> check_expr ctx state symbols arm.ma_body) - in - (r, state.borrows, state.moved) - ) em.em_arms in - (* Propagate the first error, or merge successful states *) - let errors = List.filter_map (fun (r, _, _) -> - match r with Error e -> Some e | Ok () -> None) arm_results in - begin match errors with - | e :: _ -> Error e - | [] -> - (* Borrows: active after match only if active in ALL arms *) - let all_borrows = List.map (fun (_, bs, _) -> bs) arm_results in - let merged_borrows = match all_borrows with - | [] -> base_borrows - | first :: rest -> - List.fold_left (fun acc arm_borrows -> - List.filter (fun b -> - List.exists (fun b' -> b.b_id = b'.b_id) arm_borrows - ) acc - ) first rest - in - (* Moves: conservative union — moved in any arm *) - let all_moves = List.concat_map (fun (_, _, ms) -> - List.filter (fun mr -> - not (List.exists (fun base_mr -> - places_overlap mr.m_place base_mr.m_place - ) base_moved) - ) ms - ) arm_results in - (* Deduplicate moves by place *) - let unique_moves = List.fold_left (fun acc mr -> - if List.exists (fun mr' -> places_overlap mr.m_place mr'.m_place) acc - then acc - else mr :: acc - ) [] all_moves in - state.borrows <- merged_borrows; - state.moved <- base_moved @ unique_moves; - Ok () - end - - | ExprTuple exprs -> - List.fold_left (fun acc e -> - let* () = acc in - check_expr ctx state symbols e - ) (Ok ()) exprs - - | ExprArray exprs -> - List.fold_left (fun acc e -> - let* () = acc in - check_expr ctx state symbols e - ) (Ok ()) exprs - - | ExprRecord er -> - let* () = List.fold_left (fun acc (_id, e_opt) -> - let* () = acc in - match e_opt with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - ) (Ok ()) er.er_fields in - begin match er.er_spread with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - end - - | ExprField (base, _) -> - check_expr ctx state symbols base - - | ExprTupleIndex (base, _) -> - check_expr ctx state symbols base - - | ExprIndex (arr, idx) -> - let* () = check_expr ctx state symbols arr in - check_expr ctx state symbols idx - - | ExprRowRestrict (base, _) -> - check_expr ctx state symbols base - - | ExprBlock blk -> - check_block ctx state symbols blk - - | ExprBinary (left, _, right) -> - let* () = check_expr ctx state symbols left in - check_expr ctx state symbols right - - | ExprUnary (op, e) -> - begin match op with - | OpRef -> - (* Taking a reference: &expr - creates a shared borrow *) - begin match expr_to_place symbols e with - | Some place -> - let span = expr_span e in - let* _borrow = record_borrow state place Shared span in - Ok () - | None -> - (* Can't borrow non-place expressions, but check the expression *) - check_expr ctx state symbols e - end - | OpDeref -> - (* Dereferencing: *ptr - just check the pointer expression *) - check_expr ctx state symbols e - | _ -> - check_expr ctx state symbols e - end - - | ExprReturn e_opt -> - begin match e_opt with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - end - - | ExprHandle eh -> - let* () = check_expr ctx state symbols eh.eh_body in - List.fold_left (fun acc arm -> - let* () = acc in - match arm with - | HandlerReturn (_pat, body) -> check_expr ctx state symbols body - | HandlerOp (_op, _pats, body) -> check_expr ctx state symbols body - ) (Ok ()) eh.eh_handlers - - | ExprResume e_opt -> - begin match e_opt with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - end - - | ExprTry et -> - let* () = check_block ctx state symbols et.et_body in - let* () = match et.et_catch with - | Some arms -> - List.fold_left (fun acc arm -> - let* () = acc in - let* () = match arm.ma_guard with - | Some g -> check_expr ctx state symbols g - | None -> Ok () - in - check_expr ctx state symbols arm.ma_body - ) (Ok ()) arms - | None -> Ok () - in - begin match et.et_finally with - | Some blk -> check_block ctx state symbols blk - | None -> Ok () - end - - | ExprUnsafe ops -> - List.fold_left (fun acc op -> - let* () = acc in - match op with - | UnsafeRead e -> check_expr ctx state symbols e - | UnsafeWrite (e1, e2) -> - let* () = check_expr ctx state symbols e1 in - check_expr ctx state symbols e2 - | UnsafeOffset (e1, e2) -> - let* () = check_expr ctx state symbols e1 in - check_expr ctx state symbols e2 - | UnsafeTransmute (_, _, e) -> check_expr ctx state symbols e - | UnsafeForget e -> check_expr ctx state symbols e - ) (Ok ()) ops - - | ExprVariant _ -> Ok () - - | ExprSpan (e, _) -> - check_expr ctx state symbols e - -and check_block (ctx : context) (state : state) (symbols : Symbol.t) (blk : block) : unit result = - (* Snapshot borrows at block entry. Borrows created inside this block for - block-local variables expire at block exit (lexical lifetimes). - Moves persist — a moved value stays moved after the block. *) - let borrows_at_entry = state.borrows in - let* () = List.fold_left (fun acc stmt -> - let* () = acc in - check_stmt ctx state symbols stmt - ) (Ok ()) blk.blk_stmts in - let* () = match blk.blk_expr with - | Some e -> check_expr ctx state symbols e - | None -> Ok () - in - (* End borrows for places bound in this block: restore to pre-block borrows, - keeping only those that existed before the block (i.e., borrow outer-scope - variables that the block merely uses — these are unaffected). - This is a conservative lexical-lifetime approximation. *) - state.borrows <- borrows_at_entry; - Ok () - -and check_stmt (ctx : context) (state : state) (symbols : Symbol.t) (stmt : stmt) : unit result = - match stmt with - | StmtLet sl -> - (* Track mutable bindings *) - begin match sl.sl_pat with - | PatVar id -> - if sl.sl_mut then - begin match expr_to_place symbols (ExprVar id) with - | Some place -> state.mutable_bindings <- place :: state.mutable_bindings - | None -> () - end - else () - | _ -> () - end; - check_expr ctx state symbols sl.sl_value - | StmtExpr e -> - check_expr ctx state symbols e - | StmtAssign (lhs, _, rhs) -> - (* For assignment, LHS must be a mutable place *) - begin match expr_to_place symbols lhs with - | Some place -> - if not (is_mutable state place) then - Error (CannotBorrowAsMutable (place, expr_span lhs)) - else - (* Check that the place is not moved and not borrowed *) - let* () = check_use state place (expr_span lhs) in - (* Check for any active borrows of this place *) - begin match List.find_opt (fun b -> places_overlap place b.b_place) state.borrows with - | Some borrow -> - (* Can't assign while borrowed *) - Error (MoveWhileBorrowed (place, borrow)) - | None -> - (* Assignment is ok, check the RHS *) - check_expr ctx state symbols rhs - end - | None -> - (* LHS is not a place (e.g., function call result), check both sides *) - let* () = check_expr ctx state symbols lhs in - check_expr ctx state symbols rhs - end - | StmtWhile (cond, body) -> - let* () = check_expr ctx state symbols cond in - check_block ctx state symbols body - | StmtFor (_pat, iter, body) -> - let* () = check_expr ctx state symbols iter in - check_block ctx state symbols body - -(** Check a function *) -let check_function (ctx : context) (symbols : Symbol.t) (fd : fn_decl) : unit result = - let state = create () in - match fd.fd_body with - | FnBlock blk -> check_block ctx state symbols blk - | FnExpr e -> check_expr ctx state symbols e - -(** Check a program *) -let check_program (symbols : Symbol.t) (program : program) : unit result = - (* Build context with all function signatures *) - let ctx = build_context program in - (* Check each function *) - List.fold_left (fun acc decl -> - match acc with - | Error e -> Error e - | Ok () -> - match decl with - | TopFn fd -> check_function ctx symbols fd - | _ -> Ok () - ) (Ok ()) program.prog_decls - -(* Silence unused warnings for functions that will be used in later phases *) -let _ = record_move -let _ = record_borrow -let _ = end_borrow - -(* Phase 3 (borrow checking) partially complete. Future enhancements: - - Non-lexical lifetimes with region inference (Phase 3) - - Dataflow analysis for precise tracking (Phase 3) - - Integration with quantity checking (Phase 3) -*) diff --git a/affinescript/lib/codegen.ml b/affinescript/lib/codegen.ml deleted file mode 100644 index 93d5976..0000000 --- a/affinescript/lib/codegen.ml +++ /dev/null @@ -1,1949 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** WebAssembly code generation from AffineScript AST. - - This module translates type-checked and borrow-checked AffineScript - programs into WebAssembly modules. -*) - -open Ast -open Wasm - -(** Ownership kind for typed-wasm schema annotations. - Maps AffineScript ownership qualifiers to typed-wasm Level 7/10 verification. *) -type ownership_kind = - | Unrestricted (** Plain value, no ownership constraint (Wasm i32/f64 etc.) *) - | Linear (** TyOwn / own — consumed exactly once (typed-wasm Level 10 linearity) *) - | SharedBorrow (** TyRef / ref — read-only aliasing safety (typed-wasm Level 7) *) - | ExclBorrow (** TyMut / mut — exclusive mutable aliasing safety (typed-wasm Level 7) *) - -(** Code generation context *) -type context = { - types : func_type list; (** type section *) - funcs : func list; (** function definitions *) - exports : export list; (** exports *) - imports : import list; (** imports *) - globals : global list; (** global variables *) - locals : (string * int) list; (** local variable name to index map *) - next_local : int; (** next available local index *) - loop_depth : int; (** current loop nesting depth *) - func_indices : (string * int) list; (** function name to index map *) - lambda_funcs : func list; (** lifted lambda functions *) - next_lambda_id : int; (** next lambda function ID *) - heap_ptr : int option; (** global index for heap pointer, if initialized *) - field_layouts : (string * (string * int) list) list; - (** variable name -> [(field, offset)] — populated by let-bindings - whose RHS is a record literal. Kept as the anonymous-record - fallback. *) - struct_layouts : (string * (string * int) list) list; - (** type name -> [(field, offset)] — populated by [TopType] when the - body is [TyStruct]. Used together with [var_types] to resolve - [ExprField] on variables whose RHS is not a record literal - (function parameters, let-bindings from function calls, etc.). *) - var_types : (string * string) list; - (** variable name -> declared type constructor name. Populated from - function-parameter annotations and from typed [let] bindings - (e.g. [let v : MyStruct = ...]). Feeds [ExprField]'s - variable -> type -> layout lookup. *) - variant_tags : (string * int) list; (** constructor name -> tag (int) *) - string_data : (string * int) list; (** string content -> memory offset *) - next_string_offset : int; (** next available offset for string data *) - datas : data list; (** data segments *) - ownership_annots : (int * ownership_kind list * ownership_kind) list; - (** Collected ownership annotations: (func_index, param_kinds, return_kind). - Emitted as the [affinescript.ownership] Wasm custom section for typed-wasm - Level 7/10 verification. Kind encoding: 0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow. *) -} - -(** Code generation error *) -type codegen_error = - | UnsupportedFeature of string - | UnboundVariable of string - | TypeMismatch of string -[@@deriving show] - -type 'a result = ('a, codegen_error) Result.t - -(** Result bind operator *) -let ( let* ) = Result.bind - -(** Count imported functions (for index offsets) *) -let import_func_count (ctx : context) : int = - List.fold_left (fun acc imp -> - match imp.i_desc with - | ImportFunc _ -> acc + 1 - | _ -> acc - ) 0 ctx.imports - -(** Create initial context *) -let create_context () : context = { - types = []; - funcs = []; - exports = []; - imports = []; - globals = []; - locals = []; - next_local = 0; - loop_depth = 0; - func_indices = []; - lambda_funcs = []; - next_lambda_id = 0; - heap_ptr = None; - field_layouts = []; - struct_layouts = []; - var_types = []; - variant_tags = []; - string_data = []; - next_string_offset = 2048; (* Start strings after heap at offset 2048 *) - datas = []; - ownership_annots = []; -} - -(** Best-effort extraction of a single named type constructor from a - [type_expr]. Peels off [TyOwn]/[TyRef]/[TyMut] wrappers that do not - change the underlying nominal type. Returns [None] for anything - that is not a bare type constructor (polymorphic applications, - arrows, tuples, records, holes, variables). - - Used when binding a variable (function parameter or typed let) to - remember the user's declared struct type so that a later - [ExprField] can resolve the field layout via [struct_layouts]. *) -let rec type_name_of (t : type_expr) : string option = - match t with - | TyCon id -> Some id.name - | TyOwn inner | TyRef inner | TyMut inner -> type_name_of inner - | _ -> None - -(** Extract ownership kind from a parameter declaration. - Checks p_ownership first; falls back to the shape of p_ty. *) -let ownership_kind_of_param (p : param) : ownership_kind = - match p.p_ownership with - | Some Own -> Linear - | Some Ref -> SharedBorrow - | Some Mut -> ExclBorrow - | None -> - match p.p_ty with - | TyOwn _ -> Linear - | TyRef _ -> SharedBorrow - | TyMut _ -> ExclBorrow - | _ -> Unrestricted - -(** Extract ownership kind from an optional return type expression *) -let ownership_kind_of_ret (ret : type_expr option) : ownership_kind = - match ret with - | Some (TyOwn _) -> Linear - | Some (TyRef _) -> SharedBorrow - | Some (TyMut _) -> ExclBorrow - | _ -> Unrestricted - -(** Encode an ownership_kind as a single byte (0–3) *) -let ownership_kind_byte = function - | Unrestricted -> 0 | Linear -> 1 | SharedBorrow -> 2 | ExclBorrow -> 3 - -(** Build the payload for the [affinescript.ownership] Wasm custom section. - Encoding (all little-endian): - u32 entry_count - per entry: - u32 func_index - u8 param_count - u8* param_kind (one per param, see kind encoding above) - u8 return_kind *) -let build_ownership_section (annots : (int * ownership_kind list * ownership_kind) list) : bytes = - if annots = [] then Bytes.empty - else - let buf = Buffer.create 64 in - let write_u32_le n = - Buffer.add_char buf (Char.chr (n land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 16) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 24) land 0xff)) - in - let write_u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - write_u32_le (List.length annots); - List.iter (fun (func_idx, param_kinds, ret_kind) -> - write_u32_le func_idx; - write_u8 (List.length param_kinds); - List.iter (fun k -> write_u8 (ownership_kind_byte k)) param_kinds; - write_u8 (ownership_kind_byte ret_kind) - ) annots; - Buffer.to_bytes buf - -(** Map AffineScript type to WASM value type *) -let type_to_wasm (ty : type_expr) : value_type result = - match ty with - | TyCon id when id.name = "Float" -> Ok F64 - | TyCon id when id.name = "Bool" -> Ok I32 - | TyCon id when id.name = "Int" -> Ok I32 - | TyCon id when id.name = "Char" -> Ok I32 - | TyCon id when id.name = "String" -> Ok I32 (* pointer to heap *) - | TyCon id when id.name = "Nat" -> Ok I32 - | TyCon _ -> Ok I32 (* default for user types — heap pointer *) - | TyApp _ | TyTuple _ | TyRecord _ | TyArrow _ -> Ok I32 - | _ -> Ok I32 (* conservative default *) - -(** Allocate a new local variable *) -let alloc_local (ctx : context) (name : string) : (context * int) = - let idx = ctx.next_local in - let locals' = (name, idx) :: ctx.locals in - ({ ctx with locals = locals'; next_local = idx + 1 }, idx) - -(** Look up local variable index *) -let lookup_local (ctx : context) (name : string) : int result = - match List.assoc_opt name ctx.locals with - | Some idx -> Ok idx - | None -> Error (UnboundVariable name) - -(** Generate code to bind a pattern to the value on the WASM stack. - Assumes the RHS value is already on the stack. - Returns instructions that consume the stack value and bind locals. *) -let rec gen_pattern_bind (ctx : context) (pat : pattern) : (context * instr list) result = - match pat with - | PatVar id -> - let (ctx', idx) = alloc_local ctx id.name in - Ok (ctx', [LocalSet idx]) - | PatWildcard _ -> - (* Discard the value *) - Ok (ctx, [Drop]) - | PatTuple pats -> - (* Value is a heap pointer to the tuple. Store it in a temp, then - load each element at its offset and bind the sub-pattern. *) - let (ctx', tmp_idx) = alloc_local ctx "__tuple_tmp" in - let n = List.length pats in - let* (ctx_final, elem_codes) = List.fold_left (fun acc (i, sub_pat) -> - let* (c, codes) = acc in - (* Load tuple element: memory[tmp + i*4] *) - let load_code = [ - LocalGet tmp_idx; - I32Const (Int32.of_int (i * 4)); - I32Add; - I32Load (2, 0); - ] in - let* (c', bind_code) = gen_pattern_bind c sub_pat in - Ok (c', codes @ load_code @ bind_code) - ) (Ok (ctx', [])) (List.mapi (fun i p -> (i, p)) pats) in - let _ = n in - Ok (ctx_final, [LocalSet tmp_idx] @ elem_codes) - | PatAs (id, sub_pat) -> - (* Bind the whole value to id, then also match sub-pattern *) - let (ctx', idx) = alloc_local ctx id.name in - (* Duplicate value: store to local, get it back for sub-pattern *) - let* (ctx'', sub_code) = gen_pattern_bind ctx' sub_pat in - Ok (ctx'', [LocalTee idx] @ sub_code) - | _ -> - (* Other patterns (literals, constructors, records, or) need runtime - checking which is complex in WASM. For now, treat as a variable - bind of the whole value with a generated name. *) - let (ctx', idx) = alloc_local ctx "__pat_bind" in - Ok (ctx', [LocalSet idx]) - -(** Ensure heap pointer global is initialized. - Returns (context, heap_global_idx). *) -let ensure_heap_ptr (ctx : context) : (context * int) = - match ctx.heap_ptr with - | Some idx -> (ctx, idx) - | None -> - (* Create heap pointer global initialized to 1024 (1KB) *) - let idx = List.length ctx.globals in - let heap_global = { - g_type = I32; - g_mutable = true; - g_init = [I32Const 1024l]; (* Start heap at 1KB *) - } in - ({ ctx with - globals = ctx.globals @ [heap_global]; - heap_ptr = Some idx }, idx) - -(** Generate code to allocate memory on the heap. - Returns instructions that leave the allocated address on the stack. - size_in_bytes: number of bytes to allocate *) -let gen_heap_alloc (ctx : context) (size_in_bytes : int) : (context * instr list) = - let (ctx', heap_idx) = ensure_heap_ptr ctx in - (* Get current heap pointer, then increment it *) - let alloc_code = [ - GlobalGet heap_idx; (* Get current heap address *) - GlobalGet heap_idx; (* Get it again *) - I32Const (Int32.of_int size_in_bytes); (* Size to allocate *) - I32Add; (* Calculate new heap pointer *) - GlobalSet heap_idx; (* Update heap pointer *) - (* Stack now has the allocated address *) - ] in - (ctx', alloc_code) - -(** Find free variables in an expression. - Returns list of variable names that are used but not bound within the expression. - bound_vars: variables already bound in enclosing scope (parameters, let bindings) *) -let rec find_free_vars (bound_vars : string list) (expr : expr) : string list = - match expr with - | ExprLit _ -> [] - | ExprVar id -> - if List.mem id.name bound_vars then [] else [id.name] - | ExprBinary (e1, _, e2) -> - find_free_vars bound_vars e1 @ find_free_vars bound_vars e2 - | ExprUnary (_, e) -> - find_free_vars bound_vars e - | ExprIf ei -> - find_free_vars bound_vars ei.ei_cond @ - find_free_vars bound_vars ei.ei_then @ - (match ei.ei_else with - | Some e -> find_free_vars bound_vars e - | None -> []) - | ExprLet lb -> - let rhs_free = find_free_vars bound_vars lb.el_value in - (* Add bound variable to scope for body *) - let new_bound = match lb.el_pat with - | PatVar id -> id.name :: bound_vars - | _ -> bound_vars - in - let body_free = match lb.el_body with - | Some e -> find_free_vars new_bound e - | None -> [] - in - rhs_free @ body_free - | ExprLambda lam -> - (* Parameters are bound within lambda *) - let param_names = List.map (fun p -> p.p_name.name) lam.elam_params in - find_free_vars (param_names @ bound_vars) lam.elam_body - | ExprApp (f, args) -> - find_free_vars bound_vars f @ - List.concat (List.map (find_free_vars bound_vars) args) - | ExprBlock blk -> - (* Statements may introduce bindings *) - let (_, free) = List.fold_left (fun (bound, acc_free) stmt -> - match stmt with - | StmtLet sl -> - let rhs_free = find_free_vars bound sl.sl_value in - let new_bound = match sl.sl_pat with - | PatVar id -> id.name :: bound - | _ -> bound - in - (new_bound, acc_free @ rhs_free) - | StmtExpr e -> - (bound, acc_free @ find_free_vars bound e) - | _ -> (bound, acc_free) - ) (bound_vars, []) blk.blk_stmts in - let expr_free = match blk.blk_expr with - | Some e -> find_free_vars bound_vars e - | None -> [] - in - free @ expr_free - | ExprMatch m -> - find_free_vars bound_vars m.em_scrutinee @ - List.concat (List.map (fun arm -> find_free_vars bound_vars arm.ma_body) m.em_arms) - | ExprReturn e_opt -> - (match e_opt with Some e -> find_free_vars bound_vars e | None -> []) - | ExprTuple exprs | ExprArray exprs -> - List.concat (List.map (find_free_vars bound_vars) exprs) - | ExprRecord r -> - List.concat (List.map (fun (_, e_opt) -> - match e_opt with - | Some e -> find_free_vars bound_vars e - | None -> [] - ) r.er_fields) - | ExprField (e, _) -> find_free_vars bound_vars e - | ExprTupleIndex (e, _) -> find_free_vars bound_vars e - | ExprIndex (e1, e2) -> - find_free_vars bound_vars e1 @ find_free_vars bound_vars e2 - | ExprVariant _ -> [] - | ExprSpan (e, _) -> find_free_vars bound_vars e - | _ -> [] (* Other expressions *) - -(** Remove duplicates from list *) -let dedup (lst : string list) : string list = - List.fold_left (fun acc x -> - if List.mem x acc then acc else x :: acc - ) [] lst |> List.rev - -(** Generate code for a literal *) -let gen_literal (ctx : context) (lit : literal) : (context * instr) result = - match lit with - | LitUnit _ -> Ok (ctx, I32Const 0l) (* Unit represented as 0 *) - | LitBool (b, _) -> Ok (ctx, I32Const (if b then 1l else 0l)) - | LitInt (n, _) -> Ok (ctx, I32Const (Int32.of_int n)) - | LitFloat (f, _) -> Ok (ctx, F64Const f) - | LitChar (c, _) -> Ok (ctx, I32Const (Int32.of_int (Char.code c))) - | LitString (s, _) -> - (* Check if string already exists *) - begin match List.assoc_opt s ctx.string_data with - | Some offset -> - (* String already in memory, return pointer *) - Ok (ctx, I32Const (Int32.of_int offset)) - | None -> - (* Add new string to data section *) - let offset = ctx.next_string_offset in - let str_bytes = Bytes.of_string s in - let str_len = Bytes.length str_bytes in - (* String layout: [length: i32][...utf8 bytes...] *) - let len_bytes = Bytes.create 4 in - Bytes.set_int32_le len_bytes 0 (Int32.of_int str_len); - let full_data = Bytes.cat len_bytes str_bytes in - let data_segment = { d_data = full_data; d_offset = offset } in - let ctx' = { - ctx with - string_data = (s, offset) :: ctx.string_data; - next_string_offset = offset + 4 + str_len; - datas = data_segment :: ctx.datas; - } in - Ok (ctx', I32Const (Int32.of_int offset)) - end - -(** Generate code for binary operation *) -let gen_binop (op : binary_op) : instr = - match op with - | OpAdd -> I32Add - | OpSub -> I32Sub - | OpMul -> I32Mul - | OpDiv -> I32DivS - | OpMod -> I32RemS - | OpEq -> I32Eq - | OpNe -> I32Ne - | OpLt -> I32LtS - | OpLe -> I32LeS - | OpGt -> I32GtS - | OpGe -> I32GeS - | OpAnd -> I32And - | OpOr -> I32Or - | OpBitAnd -> I32And - | OpBitOr -> I32Or - | OpBitXor -> I32Xor - | OpShl -> I32Shl - | OpShr -> I32ShrS - | OpConcat -> I32Add (* Placeholder *) - -(** Generate code for unary operation *) -let gen_unop (op : unary_op) : instr result = - match op with - | OpNeg -> Ok I32Sub (* 0 - x *) - | OpNot -> Ok I32Eqz (* x == 0 *) - | OpBitNot -> Error (UnsupportedFeature "Bitwise NOT") - | OpRef -> Error (UnsupportedFeature "OpRef handled in ExprUnary") - | OpDeref -> Error (UnsupportedFeature "OpDeref handled in ExprUnary") - -(** Generate code for an expression, returning instructions and updated context *) -let rec gen_expr (ctx : context) (expr : expr) : (context * instr list) result = - match expr with - | ExprLit lit -> - let* (ctx', instr) = gen_literal ctx lit in - Ok (ctx', [instr]) - - | ExprVar id -> - let* idx = lookup_local ctx id.name in - Ok (ctx, [LocalGet idx]) - - | ExprBinary (left, op, right) -> - let* (ctx', left_code) = gen_expr ctx left in - let* (ctx'', right_code) = gen_expr ctx' right in - let op_instr = gen_binop op in - Ok (ctx'', left_code @ right_code @ [op_instr]) - - | ExprUnary (op, operand) -> - begin match op with - | OpRef -> - (* Take reference: &expr *) - (* Allocate heap memory, store the value, return pointer *) - let* (ctx', operand_code) = gen_expr ctx operand in - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx' 4 in - let (ctx_with_ptr, ptr_idx) = alloc_local ctx_with_heap "__ref_ptr" in - let (ctx_with_val, val_idx) = alloc_local ctx_with_ptr "__ref_val" in - - (* Strategy: alloc, save ptr, eval operand, save val, store val at ptr, return ptr *) - let ref_code = alloc_code @ [ - LocalSet ptr_idx; (* Save allocated pointer *) - ] @ operand_code @ [ (* Evaluate operand (value on stack) *) - LocalSet val_idx; (* Save value *) - LocalGet ptr_idx; (* Load pointer *) - LocalGet val_idx; (* Load value *) - I32Store (2, 0); (* Store: mem[ptr+0] = value *) - LocalGet ptr_idx; (* Return pointer *) - ] in - Ok (ctx_with_val, ref_code) - - | OpDeref -> - (* Dereference: *ptr *) - (* Load value from pointer *) - let* (ctx', ptr_code) = gen_expr ctx operand in - let deref_code = [ - I32Load (2, 0); (* Load i32 from pointer *) - ] in - Ok (ctx', ptr_code @ deref_code) - - | _ -> - (* Other unary ops *) - let* (ctx', operand_code) = gen_expr ctx operand in - let* op_instr = gen_unop op in - let prefix = match op with - | OpNeg -> [I32Const 0l] (* 0 - operand *) - | _ -> [] - in - Ok (ctx', prefix @ operand_code @ [op_instr]) - end - - | ExprIf ei -> - let* (ctx', cond_code) = gen_expr ctx ei.ei_cond in - let* (ctx'', then_code) = gen_expr ctx' ei.ei_then in - let else_result = match ei.ei_else with - | Some e -> gen_expr ctx'' e - | None -> Ok (ctx'', [I32Const 0l]) (* Default to 0 if no else *) - in - let* (ctx_final, else_code) = else_result in - Ok (ctx_final, cond_code @ [If (BtType I32, then_code, else_code)]) - - | ExprBlock blk -> - gen_block ctx blk - - | ExprReturn e_opt -> - begin match e_opt with - | Some e -> - let* (ctx', code) = gen_expr ctx e in - Ok (ctx', code @ [Return]) - | None -> - Ok (ctx, [Return]) - end - - | ExprLet lb -> - let* (ctx', rhs_code) = gen_expr ctx lb.el_value in - let* (ctx'', pat_code) = gen_pattern_bind ctx' lb.el_pat in - begin match lb.el_body with - | Some body -> - let* (ctx_final, body_code) = gen_expr ctx'' body in - Ok (ctx_final, rhs_code @ pat_code @ body_code) - | None -> - Ok (ctx'', rhs_code @ pat_code @ [I32Const 0l]) - end - - | ExprLambda lam -> - (* Detect free variables (captured from enclosing scope) *) - let param_names = List.map (fun p -> p.p_name.name) lam.elam_params in - let all_free = find_free_vars param_names lam.elam_body in - (* Filter to only variables currently in scope *) - let captured_vars = List.filter (fun name -> - List.mem_assoc name ctx.locals - ) (dedup all_free) in - - let lambda_id = ctx.next_lambda_id in - - (* If there are captured variables, create closure environment *) - let (ctx_after_env, env_code) = if List.length captured_vars > 0 then - (* Create environment tuple with captured values *) - let num_captured = List.length captured_vars in - let env_size = num_captured * 4 in - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx env_size in - let (ctx_with_temp, env_idx) = alloc_local ctx_with_heap "__closure_env" in - - let save_code = [LocalTee env_idx] in - - (* Store each captured variable in environment *) - (* Note: Each store consumes env_ptr and value, but we push env_ptr before each store, - so after all stores, one env_ptr remains on stack *) - let store_code = List.mapi (fun i var_name -> - let var_idx = List.assoc var_name ctx.locals in - [ - LocalGet env_idx; - LocalGet var_idx; - I32Store (2, i * 4); - ] - ) captured_vars |> List.concat in - - (* Don't push env_idx again - one is already on stack after stores *) - (ctx_with_temp, alloc_code @ save_code @ store_code) - else - (* No captures - environment is null (0) *) - (ctx, [I32Const 0l]) - in - - (* Create fresh context for lambda function *) - let lambda_ctx = { ctx_after_env with locals = []; next_local = 0; loop_depth = 0 } in - - (* Environment is always first parameter (even if unused) for uniform calling convention *) - let (ctx_with_env, _) = alloc_local lambda_ctx "__env" in - let env_param_offset = 1 in - - (* Regular parameters come after environment *) - let (ctx_with_params, _) = List.fold_left (fun (c, _) param -> - alloc_local c param.p_name.name - ) (ctx_with_env, 0) lam.elam_params in - - (* Add captured variables to local scope (load from environment) *) - let (ctx_with_captured, load_captured_code) = if List.length captured_vars > 0 then - let (c, code) = List.fold_left (fun (c_acc, code_acc) (i, var_name) -> - let (c', var_idx) = alloc_local c_acc var_name in - let load_code = [ - LocalGet 0; (* Environment pointer *) - I32Load (2, i * 4); - LocalSet var_idx; - ] in - (c', code_acc @ load_code) - ) (ctx_with_params, []) (List.mapi (fun i v -> (i, v)) captured_vars) in - (c, code) - else - (ctx_with_params, []) - in - - let param_count = env_param_offset + List.length lam.elam_params in - - (* Generate lambda body *) - let* (ctx_final, body_code) = gen_expr ctx_with_captured lam.elam_body in - - (* Compute additional locals (beyond parameters and captured vars) *) - let local_count = ctx_final.next_local - param_count in - let locals = if local_count > 0 then - [{ l_count = local_count; l_type = I32 }] - else - [] - in - - (* Create function type for lambda (env param always included + regular params) *) - let param_types = I32 :: List.map (fun _ -> I32) lam.elam_params in - let result_type = [I32] in - let func_type = { ft_params = param_types; ft_results = result_type } in - - (* Add type to types list *) - let type_idx = List.length ctx_after_env.types in - let ctx_with_type = { ctx_after_env with types = ctx_after_env.types @ [func_type] } in - - (* Create lambda function *) - let lambda_func = { - f_type = type_idx; - f_locals = locals; - f_body = load_captured_code @ body_code; - } in - - (* Add lambda function to lifted functions *) - let ctx_with_lambda = { - ctx_with_type with - lambda_funcs = ctx_with_type.lambda_funcs @ [lambda_func]; - next_lambda_id = lambda_id + 1; - } in - - (* Return a closure: (function_id, env_pointer) as a 2-element tuple *) - let closure_size = 8 in (* 2 * 4 bytes *) - let (ctx_final2, closure_alloc) = gen_heap_alloc ctx_with_lambda closure_size in - let (ctx_final3, closure_idx) = alloc_local ctx_final2 "__closure" in - - let closure_code = closure_alloc @ [LocalTee closure_idx] @ [ - (* Store function ID at offset 0 *) - LocalGet closure_idx; - I32Const (Int32.of_int lambda_id); - I32Store (2, 0); - ] @ [ - (* Store environment pointer at offset 4 *) - LocalGet closure_idx; - ] @ env_code @ [ - (* env_code left env_ptr on stack, closure_idx is below it *) - (* Stack is now [closure_idx, env_ptr] with env_ptr on top *) - I32Store (2, 4); - (* Return closure pointer *) - LocalGet closure_idx; - ] in - - Ok (ctx_final3, closure_code) - - | ExprApp (func_expr, args) -> - (* Check for built-in WASI functions first *) - begin match func_expr with - | ExprVar id when id.name = "print" && List.length args = 1 -> - (* print(x) - print integer without newline *) - let* (ctx_with_arg, arg_code) = gen_expr ctx (List.hd args) in - - (* Allocate temp local to hold the value *) - let (ctx_with_temp, value_temp) = alloc_local ctx_with_arg "__print_value" in - - (* Ensure heap pointer is initialized *) - let (ctx_with_heap, heap_idx) = ensure_heap_ptr ctx_with_temp in - - (* Get or create fd_write import - assume it's at index 0 for now *) - let fd_write_idx = 0 in - - (* Generate WASI print code *) - let print_code = arg_code @ [LocalSet value_temp] @ - Wasi_runtime.gen_print_int heap_idx value_temp fd_write_idx in - - Ok (ctx_with_heap, print_code) - - | ExprVar id when id.name = "println" && List.length args = 0 -> - (* println() - print newline *) - let (ctx_with_temp, temp_local) = alloc_local ctx "__println_temp" in - (* Ensure heap pointer is initialized *) - let (ctx_with_heap, heap_idx) = ensure_heap_ptr ctx_with_temp in - let fd_write_idx = 0 in - let println_code = Wasi_runtime.gen_println heap_idx fd_write_idx temp_local in - Ok (ctx_with_heap, println_code) - - | ExprVar id when id.name = "println" && List.length args = 1 -> - (* println(s) - print string and newline *) - let* (ctx_with_arg, arg_code) = gen_expr ctx (List.hd args) in - let (ctx_with_ptr, str_ptr) = alloc_local ctx_with_arg "__println_str_ptr" in - let (ctx_with_temp, temp_local) = alloc_local ctx_with_ptr "__println_temp" in - let (ctx_with_heap, heap_idx) = ensure_heap_ptr ctx_with_temp in - let fd_write_idx = 0 in - let print_code = - arg_code @ - [LocalSet str_ptr] @ - Wasi_runtime.gen_print_str heap_idx str_ptr fd_write_idx temp_local @ - [Drop] @ - Wasi_runtime.gen_println heap_idx fd_write_idx temp_local - in - Ok (ctx_with_heap, print_code) - - | ExprVar id when List.mem_assoc id.name ctx.variant_tags -> - (* Enum constructor called as a function: Circle(5), Rect({x:1,y:2}), etc. - Layout: [tag: i32][field1: i32][field2: i32]... - Reuses the same heap-boxing approach as ExprVariant with args. *) - let tag = List.assoc id.name ctx.variant_tags in - let num_fields = List.length args in - let size_in_bytes = 4 + (num_fields * 4) in - - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx size_in_bytes in - let (ctx_with_temp, variant_ptr) = alloc_local ctx_with_heap "__variant_ptr" in - - let store_tag = [ - LocalTee variant_ptr; - I32Const (Int32.of_int tag); - I32Store (2, 0); - ] in - - let rec store_args ctx' offset arg_list = - match arg_list with - | [] -> Ok (ctx', []) - | arg :: rest -> - let* (ctx'', arg_code) = gen_expr ctx' arg in - let store_code = [LocalGet variant_ptr] @ arg_code @ [I32Store (2, offset)] in - let* (ctx''', rest_code) = store_args ctx'' (offset + 4) rest in - Ok (ctx''', store_code @ rest_code) - in - - let* (ctx_final, args_store_code) = store_args ctx_with_temp 4 args in - Ok (ctx_final, alloc_code @ store_tag @ args_store_code @ [LocalGet variant_ptr]) - - | _ -> - (* Not a built-in, proceed with normal function call *) - (* Generate code for arguments (left to right) *) - let* (ctx_after_args, all_arg_code) = List.fold_left (fun acc arg -> - let* (ctx', accumulated_code) = acc in - let* (ctx'', arg_code) = gen_expr ctx' arg in - Ok (ctx'', accumulated_code @ arg_code) - ) (Ok (ctx, [])) args in - - (* Generate code for function expression *) - begin match func_expr with - | ExprVar id -> - (* Check if it's a named function or a variable holding a lambda *) - begin match List.assoc_opt id.name ctx_after_args.func_indices with - | Some func_idx -> - (* Direct function call *) - let call_instr = Call func_idx in - Ok (ctx_after_args, all_arg_code @ [call_instr]) - | None -> - (* Check if it's a local variable (could be a closure) *) - begin match lookup_local ctx_after_args id.name with - | Ok local_idx -> - (* Closure is a tuple: (func_id, env_ptr) *) - (* Load function ID and environment, then call indirect *) - - (* Allocate temp locals for closure components *) - let (ctx_temp1, func_id_idx) = alloc_local ctx_after_args "__func_id" in - let (ctx_temp2, env_ptr_idx) = alloc_local ctx_temp1 "__env_ptr" in - - (* Extract closure components *) - let extract_closure = [ - (* Load function ID from offset 0 *) - LocalGet local_idx; - I32Load (2, 0); - LocalSet func_id_idx; - - (* Load environment pointer from offset 4 *) - LocalGet local_idx; - I32Load (2, 4); - LocalSet env_ptr_idx; - ] in - - (* Create type signature: env + user args *) - let param_types = I32 :: List.map (fun _ -> I32) args in - let result_type = [I32] in - let call_type = { ft_params = param_types; ft_results = result_type } in - - (* Find or add this type *) - let type_idx = - match List.find_index (fun t -> t = call_type) ctx_temp2.types with - | Some idx -> idx - | None -> List.length ctx_temp2.types - in - - (* Call: push env, push user args, push func_id, call indirect *) - let call_instrs = [ - LocalGet env_ptr_idx; (* Environment as first arg *) - ] @ all_arg_code @ [ (* User arguments *) - LocalGet func_id_idx; (* Function ID for indirect call *) - CallIndirect type_idx - ] in - - Ok (ctx_temp2, extract_closure @ call_instrs) - | Error _ -> - Error (UnboundVariable ("Function or variable not found: " ^ id.name)) - end - end - | ExprLambda _ -> - (* Lambda expression as function - generate lambda and call it *) - let* (ctx_with_lambda, lambda_code) = gen_expr ctx_after_args func_expr in - - (* Create type signature for the call *) - let param_types = List.map (fun _ -> I32) args in - let result_type = [I32] in - let call_type = { ft_params = param_types; ft_results = result_type } in - - (* Find matching type index *) - let type_idx = - match List.find_index (fun t -> t = call_type) ctx_with_lambda.types with - | Some idx -> idx - | None -> List.length ctx_with_lambda.types - in - - Ok (ctx_with_lambda, all_arg_code @ lambda_code @ [CallIndirect type_idx]) - - | ExprVariant (_type_name, variant_name) -> - (* Variant constructor with arguments: Type::Variant(arg1, arg2, ...) *) - (* Layout: [tag: i32][field1: i32][field2: i32]... *) - let num_fields = List.length args in - let size_in_bytes = 4 + (num_fields * 4) in (* tag + fields *) - - (* Allocate heap memory *) - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx_after_args size_in_bytes in - let (ctx_with_temp, variant_ptr) = alloc_local ctx_with_heap "__variant_ptr" in - - (* Get or assign tag for this variant *) - let (ctx_with_tag, tag) = match List.assoc_opt variant_name.name ctx_with_temp.variant_tags with - | Some t -> (ctx_with_temp, t) - | None -> - let new_tag = List.length ctx_with_temp.variant_tags in - ({ ctx_with_temp with variant_tags = (variant_name.name, new_tag) :: ctx_with_temp.variant_tags }, new_tag) - in - - (* Store tag at offset 0 *) - let store_tag = [ - LocalTee variant_ptr; (* Save pointer *) - I32Const (Int32.of_int tag); - I32Store (2, 0); - ] in - - (* Generate code to evaluate each arg and store it *) - let rec store_args ctx offset arg_list = - match arg_list with - | [] -> Ok (ctx, []) - | arg :: rest -> - let* (ctx', arg_code) = gen_expr ctx arg in - let store_code = [ - LocalGet variant_ptr; - ] @ arg_code @ [ - I32Store (2, offset); - ] in - let* (ctx'', rest_code) = store_args ctx' (offset + 4) rest in - Ok (ctx'', store_code @ rest_code) - in - - let* (ctx_final, args_store_code) = store_args ctx_with_tag 4 args in - - (* Return the variant pointer *) - let return_code = [LocalGet variant_ptr] in - - Ok (ctx_final, alloc_code @ store_tag @ args_store_code @ return_code) - - | _ -> - (* Other expressions that evaluate to functions - treat as indirect call *) - let* (ctx_final, func_code) = gen_expr ctx_after_args func_expr in - - (* Create type signature for the call *) - let param_types = List.map (fun _ -> I32) args in - let result_type = [I32] in - let call_type = { ft_params = param_types; ft_results = result_type } in - - (* Find matching type index *) - let type_idx = - match List.find_index (fun t -> t = call_type) ctx_final.types with - | Some idx -> idx - | None -> List.length ctx_final.types - in - - Ok (ctx_final, all_arg_code @ func_code @ [CallIndirect type_idx]) - end - end - - | ExprMatch match_expr -> - (* Evaluate scrutinee and store in a temporary local *) - let* (ctx_after_scrutinee, scrutinee_code) = gen_expr ctx match_expr.em_scrutinee in - let (ctx_with_temp, temp_idx) = alloc_local ctx_after_scrutinee "__match_tmp" in - - (* Generate code for each match arm *) - let rec gen_arms ctx arms = - match arms with - | [] -> - (* No arms matched - this shouldn't happen with exhaustive patterns *) - (* Return 0 as a fallback *) - Ok (ctx, [I32Const 0l]) - | arm :: rest -> - (* Generate pattern matching code *) - let* (ctx_after_pat, pattern_test, _bindings) = gen_pattern ctx temp_idx arm.ma_pat in - - (* Generate body code with bindings *) - let* (ctx_after_body, body_code) = gen_expr ctx_after_pat arm.ma_body in - - (* If there are more arms, generate else branch *) - if List.length rest > 0 then - let* (ctx_final, else_code) = gen_arms ctx_after_body rest in - (* pattern_test leaves boolean on stack, then If uses it *) - Ok (ctx_final, - pattern_test @ - [If (BtType I32, - body_code, (* Then: pattern matched, execute body *) - else_code)]) (* Else: try next arm *) - else - (* Last arm - just execute body if pattern matches *) - (* If pattern doesn't match, return 0 (shouldn't happen) *) - Ok (ctx_after_body, - pattern_test @ - [If (BtType I32, - body_code, (* Then: pattern matched, execute body *) - [I32Const 0l])]) (* Else: fallback (shouldn't reach) *) - in - - let* (ctx_final, arms_code) = gen_arms ctx_with_temp match_expr.em_arms in - - (* Complete code: eval scrutinee, store in temp, then try arms *) - Ok (ctx_final, scrutinee_code @ [LocalSet temp_idx] @ arms_code) - - | ExprTuple elements -> - (* Tuple layout in memory: [elem0: I32][elem1: I32][elem2: I32]... *) - (* No length field - tuple size is fixed at creation *) - let num_elements = List.length elements in - let size_in_bytes = num_elements * 4 in (* 4 bytes per element *) - - (* Allocate heap memory and save pointer to temp local *) - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx size_in_bytes in - let (ctx_with_temp, temp_idx) = alloc_local ctx_with_heap "__tup_ptr" in - - (* Save allocated address to temp *) - let save_code = [LocalSet temp_idx] in - - (* Generate code to store each element *) - let* (ctx_final, store_code) = List.fold_left (fun acc (idx, elem_expr) -> - let* (ctx_acc, code_acc) = acc in - (* Generate code for element value *) - let* (ctx', elem_code) = gen_expr ctx_acc elem_expr in - - (* Store at offset (idx * 4) *) - let offset = idx * 4 in - let store_instrs = [ - LocalGet temp_idx; (* Get base address *) - ] @ elem_code @ [ - I32Store (2, offset); (* Store element at offset *) - ] in - Ok (ctx', code_acc @ store_instrs) - ) (Ok (ctx_with_temp, [])) (List.mapi (fun i e -> (i, e)) elements) in - - (* Complete code: allocate, save to temp, store elements, return pointer *) - Ok (ctx_final, alloc_code @ save_code @ store_code @ [LocalGet temp_idx]) - - | ExprArray elements -> - (* Array layout in memory: [length: I32][elem0: I32][elem1: I32]... *) - let num_elements = List.length elements in - let size_in_bytes = 4 + (num_elements * 4) in (* 4 for length + 4 per element *) - - (* Allocate heap memory and save pointer to temp local *) - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx size_in_bytes in - let (ctx_with_temp, temp_idx) = alloc_local ctx_with_heap "__arr_ptr" in - - (* Save allocated address to temp *) - let save_code = [LocalSet temp_idx] in - - (* Store length at offset 0 *) - let length_code = [ - LocalGet temp_idx; - I32Const (Int32.of_int num_elements); - I32Store (2, 0); (* Store length at offset 0 *) - ] in - - (* Generate code to store each element *) - let* (ctx_final, store_code) = List.fold_left (fun acc (idx, elem_expr) -> - let* (ctx_acc, code_acc) = acc in - (* Generate code for element value *) - let* (ctx', elem_code) = gen_expr ctx_acc elem_expr in - - (* Store at offset 4 + (idx * 4) *) - let offset = 4 + (idx * 4) in - let store_instrs = [ - LocalGet temp_idx; (* Get base address *) - ] @ elem_code @ [ - I32Store (2, offset); (* Store element at offset *) - ] in - Ok (ctx', code_acc @ store_instrs) - ) (Ok (ctx_with_temp, [])) (List.mapi (fun i e -> (i, e)) elements) in - - (* Complete code: allocate, save to temp, store length, store elements, return pointer *) - Ok (ctx_final, alloc_code @ save_code @ length_code @ store_code @ [LocalGet temp_idx]) - - | ExprRecord rec_expr -> - (* Allocate memory for record fields *) - let num_fields = List.length rec_expr.er_fields in - let size_in_bytes = num_fields * 4 in (* Each field is 4 bytes (I32) *) - - (* Allocate heap memory and save pointer to temp local *) - let (ctx_with_heap, alloc_code) = gen_heap_alloc ctx size_in_bytes in - let (ctx_with_temp, temp_idx) = alloc_local ctx_with_heap "__rec_ptr" in - - (* Save allocated address to temp *) - let save_code = [LocalSet temp_idx] in - - (* Generate code to store each field *) - let* (ctx_final, store_code) = List.fold_left (fun acc (idx, (field, expr_opt)) -> - let* (ctx_acc, code_acc) = acc in - (* Get field value expression *) - let field_expr = match expr_opt with - | Some e -> e - | None -> ExprVar field (* Field punning: {x} means {x: x} *) - in - (* Generate code for field value *) - let* (ctx', field_code) = gen_expr ctx_acc field_expr in - - (* Store at offset (field_index * 4) from base address *) - let offset = idx * 4 in - let store_instrs = [ - LocalGet temp_idx; (* Get base address *) - ] @ field_code @ [ - I32Store (2, offset); (* Store at offset with alignment 2 (4-byte) *) - ] in - Ok (ctx', code_acc @ store_instrs) - ) (Ok (ctx_with_temp, [])) (List.mapi (fun i x -> (i, x)) rec_expr.er_fields) in - - (* Complete code: allocate, save to temp (leaving on stack), store fields, leave address *) - (* But we already consumed the address from stack when storing, so push it again *) - Ok (ctx_final, alloc_code @ save_code @ store_code @ [LocalGet temp_idx]) - - | ExprField (record_expr, field) -> - (* Generate code for record expression (gets pointer) *) - let* (ctx', record_code) = gen_expr ctx record_expr in - - (* Look up the field's byte offset. Resolution order: - 1. variable's declared nominal type -> struct_layouts (typed - lets, function parameters, lets from function calls); - 2. variable's anonymous-record layout in field_layouts - (let v = {x:1, y:2} — the original behaviour); - 3. fall back to offset 0 as a last resort to preserve prior - behaviour on unsupported shapes. Step 3 is silently wrong - by design — upstream type-propagation is the right place to - fix it, not here. *) - let field_offset = - let lookup_in_layout layout = - List.assoc_opt field.name layout - in - match record_expr with - | ExprVar var_name -> - let via_struct = - match List.assoc_opt var_name.name ctx.var_types with - | Some ty_name -> - begin match List.assoc_opt ty_name ctx.struct_layouts with - | Some layout -> lookup_in_layout layout - | None -> None - end - | None -> None - in - begin match via_struct with - | Some offset -> offset - | None -> - begin match List.assoc_opt var_name.name ctx.field_layouts with - | Some layout -> - begin match lookup_in_layout layout with - | Some offset -> offset - | None -> 0 - end - | None -> 0 - end - end - | _ -> 0 (* Complex expression: no variable-keyed layout available *) - in - - (* Load from memory at field offset *) - let load_code = [ - I32Load (2, field_offset) (* Load with alignment 2 (4-byte) and offset *) - ] in - - Ok (ctx', record_code @ load_code) - - | ExprTupleIndex (tuple_expr, index) -> - (* Generate code for tuple expression (gets pointer) *) - let* (ctx', tuple_code) = gen_expr ctx tuple_expr in - - (* Calculate offset: index * 4 (no length field in tuples) *) - let offset = index * 4 in - - (* Load from memory at offset *) - let load_code = [ - I32Load (2, offset) (* Load with alignment 2 (4-byte) and offset *) - ] in - - Ok (ctx', tuple_code @ load_code) - - | ExprIndex (array_expr, index_expr) -> - (* Generate code for array (gets pointer) *) - let* (ctx_after_arr, array_code) = gen_expr ctx array_expr in - - (* Generate code for index *) - let* (ctx_after_idx, index_code) = gen_expr ctx_after_arr index_expr in - - (* Calculate offset: 4 + (index * 4) *) - (* Stack after array_code @ index_code: [array_ptr, index] *) - let offset_calc = [ - I32Const 4l; (* Constant 4 for element size *) - I32Mul; (* index * 4 *) - I32Const 4l; (* Add 4 to skip length field *) - I32Add; (* offset = 4 + (index * 4) *) - ] in - - (* Add base pointer to offset and load *) - let load_code = [ - I32Add; (* base_ptr + offset *) - I32Load (2, 0); (* Load from calculated address *) - ] in - - (* Complete code: array_ptr, index, calculate offset, add to base, load *) - Ok (ctx_after_idx, array_code @ index_code @ offset_calc @ load_code) - - | ExprVariant (_type_name, variant_name) -> - (* Look up variant tag *) - (* For now, use variant name directly to find tag *) - begin match List.assoc_opt variant_name.name ctx.variant_tags with - | Some tag -> - (* Zero-argument variant: just return the tag as an integer *) - Ok (ctx, [I32Const (Int32.of_int tag)]) - | None -> - (* Tag not found - assign a new sequential tag based on name *) - (* This is a fallback for when type declarations aren't processed *) - let tag = List.length ctx.variant_tags in - let ctx' = { ctx with variant_tags = (variant_name.name, tag) :: ctx.variant_tags } in - Ok (ctx', [I32Const (Int32.of_int tag)]) - end - - | ExprRowRestrict (base, _field) -> - (* Row restriction at runtime just evaluates the base record. - The field removal is a type-level operation — at the WASM level - the record pointer is unchanged (fields are still in memory). *) - gen_expr ctx base - - | ExprHandle eh -> - (* Effect handlers in WASM: compile the body expression. - Effect handling requires continuation support which WASM doesn't - natively have. We compile as a simple wrapper that evaluates the - body — unhandled effects will trap at runtime. *) - gen_expr ctx eh.eh_body - - | ExprResume arg_opt -> - (* Resume passes through the argument value *) - begin match arg_opt with - | Some e -> gen_expr ctx e - | None -> Ok (ctx, [I32Const 0l]) (* unit *) - end - - | ExprTry et -> - (* WASM 1.0 has no exception-handling proposal. - - catch arms: cannot be lowered — UnsupportedFeature. - - body + optional finally: compile sequentially; a local temp - preserves the body result across the finally block, matching - the language semantics (finally result is always discarded). *) - begin match et.et_catch with - | Some _ -> - Error (UnsupportedFeature - "try/catch in WASM 1.0 backend — \ - requires the WASM exception-handling proposal; \ - use the Julia backend (-julia) or the interpreter (-i)") - | None -> - let* (ctx', body_code) = gen_block ctx et.et_body in - begin match et.et_finally with - | None -> Ok (ctx', body_code) - | Some blk -> - (* Store body result in a temp local, run finally, restore. *) - let (ctx'', tmp_idx) = alloc_local ctx' "__try_result" in - let* (ctx''', fin_code) = gen_block ctx'' blk in - Ok (ctx''', - body_code - @ [LocalSet tmp_idx] (* stash body result *) - @ fin_code - @ [Drop] (* discard finally result *) - @ [LocalGet tmp_idx]) (* restore body result *) - end - end - - | ExprUnsafe ops -> - (* Compile unsafe operations — evaluate contained expressions *) - begin match ops with - | [] -> Ok (ctx, [I32Const 0l]) - | [UnsafeRead e] -> gen_expr ctx e - | [UnsafeWrite (_, value)] -> gen_expr ctx value - | [UnsafeOffset (base, _)] -> gen_expr ctx base - | [UnsafeTransmute (_, _, e)] -> gen_expr ctx e - | [UnsafeForget e] -> - let* (ctx', code) = gen_expr ctx e in - Ok (ctx', code @ [Drop; I32Const 0l]) - | _ -> Error (UnsupportedFeature "Multiple unsafe operations in codegen") - end - - | ExprSpan (e, _) -> - gen_expr ctx e - -(** Generate code for a block *) -and gen_block (ctx : context) (blk : block) : (context * instr list) result = - let* (ctx', stmt_codes) = List.fold_left (fun acc stmt -> - let* (c, codes) = acc in - let* (c', code) = gen_stmt c stmt in - Ok (c', codes @ code) - ) (Ok (ctx, [])) blk.blk_stmts in - match blk.blk_expr with - | Some e -> - let* (ctx_final, expr_code) = gen_expr ctx' e in - Ok (ctx_final, stmt_codes @ expr_code) - | None -> - Ok (ctx', stmt_codes @ [I32Const 0l]) - -(** Generate pattern matching test code. - Returns (context, test_code, bindings). - test_code should leave a boolean (i32) on the stack indicating if pattern matches. - bindings is a list of (name, temp_idx) for variables bound by the pattern. *) -and gen_pattern (ctx : context) (scrutinee_local : int) (pat : pattern) - : (context * instr list * (string * int) list) result = - match pat with - | PatWildcard _ -> - (* Wildcard always matches, no bindings *) - Ok (ctx, [I32Const 1l], []) - - | PatVar id -> - (* Variable pattern always matches and binds the scrutinee to the variable *) - let (ctx', var_idx) = alloc_local ctx id.name in - let bind_code = [ - LocalGet scrutinee_local; (* Get scrutinee value *) - LocalSet var_idx; (* Bind to pattern variable *) - I32Const 1l (* Pattern matches *) - ] in - Ok (ctx', bind_code, [(id.name, var_idx)]) - - | PatLit lit -> - (* Literal pattern matches if scrutinee equals the literal *) - let* (ctx', lit_instr) = gen_literal ctx lit in - let test_code = [ - LocalGet scrutinee_local; (* Get scrutinee value *) - lit_instr; (* Get literal value *) - I32Eq (* Compare *) - ] in - Ok (ctx', test_code, []) - - | PatCon (con, sub_patterns) -> - (* Constructor pattern - match against tag *) - if List.length sub_patterns = 0 then - (* Zero-argument constructor: compare scrutinee to tag *) - begin match List.assoc_opt con.name ctx.variant_tags with - | Some tag -> - let test_code = [ - LocalGet scrutinee_local; (* Get scrutinee (should be tag) *) - I32Const (Int32.of_int tag); (* Expected tag *) - I32Eq (* Compare *) - ] in - Ok (ctx, test_code, []) - | None -> - (* Tag not found - auto-assign and match *) - let tag = List.length ctx.variant_tags in - let ctx' = { ctx with variant_tags = (con.name, tag) :: ctx.variant_tags } in - let test_code = [ - LocalGet scrutinee_local; - I32Const (Int32.of_int tag); - I32Eq - ] in - Ok (ctx', test_code, []) - end - else - (* Constructor pattern with arguments: [Some(x)], [Ok(a, b)], etc. - - scrutinee is a pointer to [tag: i32][field1: i32][field2: i32]... - - CRITICAL: only dereference the scrutinee pointer AFTER - establishing that the tag matches. Zero-argument variants - are represented directly as their tag (see [ExprVariant]), - so when a [match] compiles several arms in sequence and the - scrutinee is a zero-arg value, running the field loads - unconditionally would: - 1. read i32s at arbitrary low memory offsets (the tag's - numeric value re-interpreted as a pointer), and - 2. silently overwrite this arm's [field_idx] locals with - that garbage, masking the real flow in a debugger. - Gate the bindings behind an inner [If] that only runs when - the tag compares equal, and re-emit a bool for the outer - [gen_arms] [If] to switch on. *) - - let (ctx_with_tag, tag) = match List.assoc_opt con.name ctx.variant_tags with - | Some t -> (ctx, t) - | None -> - let new_tag = List.length ctx.variant_tags in - ({ ctx with variant_tags = (con.name, new_tag) :: ctx.variant_tags }, new_tag) - in - - (* Tag test: load the tag from offset 0 of the scrutinee and - compare to the expected tag. Safe to run unconditionally — - the tag is the one field whose position is known before we - know the variant kind. *) - let tag_test = [ - LocalGet scrutinee_local; - I32Load (2, 0); - I32Const (Int32.of_int tag); - I32Eq; - ] in - - (* Extract fields and bind variables. Only [PatVar] and - [PatWildcard] sub-patterns are supported; nested constructor - patterns raise [UnsupportedFeature]. *) - let rec bind_fields ctx_acc bindings_acc offset patterns = - match patterns with - | [] -> Ok (ctx_acc, bindings_acc) - | pat :: rest -> - begin match pat with - | PatVar id -> - let (ctx', field_idx) = alloc_local ctx_acc id.name in - let load_code = [ - LocalGet scrutinee_local; - I32Load (2, offset); - LocalSet field_idx; - ] in - bind_fields ctx' (bindings_acc @ load_code) (offset + 4) rest - | PatWildcard _ -> - bind_fields ctx_acc bindings_acc (offset + 4) rest - | _ -> - Error (UnsupportedFeature "Only variable and wildcard patterns supported in variant constructor arguments") - end - in - - let* (ctx_final, binding_code) = bind_fields ctx_with_tag [] 4 sub_patterns in - - (* Gate bindings on the tag comparison. The inner [If] consumes - the bool left by [tag_test], runs the bindings on the matching - branch, and produces a fresh bool (1 on match, 0 otherwise) - for [gen_arms]' outer [If] to switch the arm body on. Stack - effect of the whole [full_code] block: +1 i32, identical to - the previous (buggy) unconditional layout so callers are - unchanged. *) - let gated_bindings = [ - If (BtType I32, - binding_code @ [I32Const 1l], - [I32Const 0l]) - ] in - let full_code = tag_test @ gated_bindings in - Ok (ctx_final, full_code, []) - - | PatTuple sub_patterns -> - (* Tuple pattern: (a, b, c) *) - (* scrutinee is a pointer to [elem0: i32][elem1: i32][elem2: i32]... *) - - (* Bind each element to sub-pattern *) - let rec bind_elements ctx_acc offset patterns = - match patterns with - | [] -> Ok (ctx_acc, []) - | pat :: rest -> - begin match pat with - | PatVar id -> - (* Allocate local for this element *) - let (ctx', elem_idx) = alloc_local ctx_acc id.name in - (* Load element from tuple *) - let load_code = [ - LocalGet scrutinee_local; - I32Load (2, offset); - LocalSet elem_idx; - ] in - let* (ctx_final, rest_code) = bind_elements ctx' (offset + 4) rest in - Ok (ctx_final, load_code @ rest_code) - | PatWildcard _ -> - (* Skip this element *) - bind_elements ctx_acc (offset + 4) rest - | _ -> - Error (UnsupportedFeature "Only variable and wildcard patterns supported in tuple patterns") - end - in - - let* (ctx_final, binding_code) = bind_elements ctx 0 sub_patterns in - (* Tuple patterns always match (no tag to check) *) - let match_code = binding_code @ [I32Const 1l] in - Ok (ctx_final, match_code, []) - - | PatRecord (field_pats, _has_wildcard) -> - (* Record pattern: {x: a, y: b} *) - (* scrutinee is a pointer to record with sequential field layout *) - (* Limitation: assumes fields in pattern order match memory layout *) - - let rec bind_fields ctx_acc offset field_patterns = - match field_patterns with - | [] -> Ok (ctx_acc, []) - | (field_name, pat_opt) :: rest -> - (* Default pattern is PatVar with same name as field *) - let pat = match pat_opt with - | Some p -> p - | None -> PatVar field_name - in - - begin match pat with - | PatVar id -> - (* Allocate local for this field *) - let (ctx', field_idx) = alloc_local ctx_acc id.name in - (* Load field from record at sequential offset *) - let load_code = [ - LocalGet scrutinee_local; - I32Load (2, offset); - LocalSet field_idx; - ] in - let* (ctx_final, rest_code) = bind_fields ctx' (offset + 4) rest in - Ok (ctx_final, load_code @ rest_code) - | PatWildcard _ -> - (* Skip this field, but advance offset *) - bind_fields ctx_acc (offset + 4) rest - | _ -> - Error (UnsupportedFeature "Only variable and wildcard patterns supported in record field patterns") - end - in - - let* (ctx_final, binding_code) = bind_fields ctx 0 field_pats in - (* Record patterns always match (no tag check) *) - let match_code = binding_code @ [I32Const 1l] in - Ok (ctx_final, match_code, []) - - | PatOr (pat1, pat2) -> - (* Or pattern: p1 | p2 *) - (* Try pat1, if it fails try pat2 *) - (* Limitation: both patterns should bind the same variables *) - - let* (ctx_after_p1, test1, _bindings1) = gen_pattern ctx scrutinee_local pat1 in - let* (ctx_after_p2, test2, _bindings2) = gen_pattern ctx_after_p1 scrutinee_local pat2 in - - (* Generate: test1 or test2 *) - (* If test1 succeeds (1), return 1 *) - (* If test1 fails (0), try test2 *) - let or_code = test1 @ [ - If (BtType I32, - [I32Const 1l], (* Then: first pattern matched *) - test2) (* Else: try second pattern *) - ] in - - Ok (ctx_after_p2, or_code, []) - - | PatAs (bind_id, sub_pat) -> - (* As pattern: x @ sub_pattern *) - (* Bind scrutinee to variable and match sub-pattern *) - - (* Allocate local for the binding *) - let (ctx_with_bind, bind_idx) = alloc_local ctx bind_id.name in - - (* Bind the scrutinee *) - let bind_code = [ - LocalGet scrutinee_local; - LocalSet bind_idx; - ] in - - (* Match the sub-pattern *) - let* (ctx_final, sub_test, _sub_bindings) = gen_pattern ctx_with_bind scrutinee_local sub_pat in - - (* Combine: bind then test sub-pattern *) - Ok (ctx_final, bind_code @ sub_test, []) - -(** Generate code for a statement *) -and gen_stmt (ctx : context) (stmt : stmt) : (context * instr list) result = - match stmt with - | StmtLet sl -> - let* (ctx', rhs_code) = gen_expr ctx sl.sl_value in - begin match sl.sl_pat with - | PatVar id -> - let (ctx'', idx) = alloc_local ctx' id.name in - (* Field-layout registration. Two independent registries: - - (a) field_layouts — per-variable, for anonymous-record lets - ([let v = {x:1, y:2}]). The ordering in [er_fields] - defines the byte layout. - (b) var_types — per-variable, for nominal struct lets - ([let v : MyStruct = ...]). Used later by [ExprField] - to look up [struct_layouts] registered at [TopType] - time. Also covers lets whose RHS is a function call - returning a struct — the type annotation is the only - reliable signal at this layer. *) - let ctx_with_field_layout = - match sl.sl_value with - | ExprRecord rec_expr -> - let field_layout = List.mapi (fun i (field_name, _) -> - (field_name.name, i * 4) - ) rec_expr.er_fields in - { ctx'' with - field_layouts = (id.name, field_layout) :: ctx''.field_layouts } - | _ -> ctx'' - in - let ctx_with_layout = - match sl.sl_ty with - | Some ty -> - begin match type_name_of ty with - | Some ty_name -> - { ctx_with_field_layout with - var_types = (id.name, ty_name) :: ctx_with_field_layout.var_types } - | None -> ctx_with_field_layout - end - | None -> ctx_with_field_layout - in - Ok (ctx_with_layout, rhs_code @ [LocalSet idx]) - | _ -> - (* Complex patterns — use gen_pattern_bind for tuples, wildcards, etc. *) - let* (ctx'', pat_code) = gen_pattern_bind ctx' sl.sl_pat in - Ok (ctx'', rhs_code @ pat_code) - end - - | StmtExpr e -> - let* (ctx', code) = gen_expr ctx e in - Ok (ctx', code @ [Drop]) (* Discard expression result *) - - | StmtAssign (lhs, op, rhs) -> - begin match lhs with - | ExprVar id -> - (* Variable assignment: x = expr or x += expr *) - let* idx = lookup_local ctx id.name in - let* (ctx', rhs_code) = gen_expr ctx rhs in - begin match op with - | AssignEq -> - (* Simple assignment *) - Ok (ctx', rhs_code @ [LocalSet idx]) - | AssignAdd | AssignSub | AssignMul | AssignDiv -> - (* Compound assignment: x op= expr => x = x op expr *) - let binop = match op with - | AssignAdd -> OpAdd - | AssignSub -> OpSub - | AssignMul -> OpMul - | AssignDiv -> OpDiv - | _ -> failwith "unreachable" - in - let op_instr = gen_binop binop in - Ok (ctx', [ - LocalGet idx; (* Load current value *) - ] @ rhs_code @ [ (* Evaluate RHS *) - op_instr; (* Perform operation *) - LocalSet idx (* Store result *) - ]) - end - | ExprUnary (OpDeref, ptr_expr) -> - (* Pointer dereference assignment: *ptr = expr *) - let* (ctx', ptr_code) = gen_expr ctx ptr_expr in - let* (ctx'', rhs_code) = gen_expr ctx' rhs in - begin match op with - | AssignEq -> - (* Simple dereference assignment *) - Ok (ctx'', ptr_code @ rhs_code @ [I32Store (2, 0)]) - | AssignAdd | AssignSub | AssignMul | AssignDiv -> - (* Compound dereference assignment: *ptr op= expr => *ptr = *ptr op expr *) - let binop = match op with - | AssignAdd -> OpAdd - | AssignSub -> OpSub - | AssignMul -> OpMul - | AssignDiv -> OpDiv - | _ -> failwith "unreachable" - in - let op_instr = gen_binop binop in - (* Need temp locals for pointer and result *) - let (ctx_with_ptr, temp_ptr) = alloc_local ctx'' "__assign_ptr" in - let (ctx_with_val, temp_val) = alloc_local ctx_with_ptr "__assign_val" in - Ok (ctx_with_val, ptr_code @ [ - LocalTee temp_ptr; (* Save pointer *) - I32Load (2, 0); (* Load current value *) - ] @ rhs_code @ [ (* Evaluate RHS *) - op_instr; (* Perform operation *) - LocalSet temp_val; (* Save result *) - LocalGet temp_ptr; (* Load pointer *) - LocalGet temp_val; (* Load result *) - I32Store (2, 0) (* Store: mem[ptr] = result *) - ]) - end - | ExprIndex (arr_expr, idx_expr) -> - (* Array index assignment: arr[i] = expr *) - let* (ctx', arr_code) = gen_expr ctx arr_expr in - let* (ctx'', idx_code) = gen_expr ctx' idx_expr in - let* (ctx''', rhs_code) = gen_expr ctx'' rhs in - (* Array is a pointer, need to compute: arr + (idx * 4) *) - let (ctx_with_temp, temp_ptr) = alloc_local ctx''' "__arr_ptr" in - begin match op with - | AssignEq -> - Ok (ctx_with_temp, arr_code @ idx_code @ [ - I32Const 4l; - I32Mul; (* idx * 4 *) - I32Add; (* arr + (idx * 4) *) - ] @ rhs_code @ [ - I32Store (2, 0) (* Store value *) - ]) - | AssignAdd | AssignSub | AssignMul | AssignDiv -> - let binop = match op with - | AssignAdd -> OpAdd - | AssignSub -> OpSub - | AssignMul -> OpMul - | AssignDiv -> OpDiv - | _ -> failwith "unreachable" - in - let op_instr = gen_binop binop in - let (ctx_with_val, temp_val) = alloc_local ctx_with_temp "__assign_val" in - Ok (ctx_with_val, arr_code @ idx_code @ [ - I32Const 4l; - I32Mul; (* idx * 4 *) - I32Add; (* arr + (idx * 4) *) - LocalTee temp_ptr; (* Save computed address *) - I32Load (2, 0); (* Load current value *) - ] @ rhs_code @ [ - op_instr; (* Perform operation *) - LocalSet temp_val; (* Save result *) - LocalGet temp_ptr; (* Load address *) - LocalGet temp_val; (* Load result *) - I32Store (2, 0) (* Store result *) - ]) - end - | _ -> - Error (UnsupportedFeature "Assignment to this expression type not yet supported") - end - - | StmtWhile (cond, body) -> - let* (ctx', cond_code) = gen_expr ctx cond in - let* (ctx'', body_code) = gen_block ctx' body in - (* Loop with conditional exit *) - Ok (ctx'', [Block (BtEmpty, [ - Loop (BtEmpty, - cond_code @ [I32Eqz; BrIf 1] @ (* If condition false, exit *) - body_code @ [Br 0] (* Continue loop *) - ) - ])]) - - | StmtFor (pat, iter_expr, body) -> - (* For loop: for pat in iter { body } - Iterates over an array or range. - For now, handle arrays: iterate from index 0 to length-1 *) - let* (ctx', iter_code) = gen_expr ctx iter_expr in - - (* Create temp locals for array pointer, length, and index *) - let (ctx_with_arr, arr_ptr) = alloc_local ctx' "__for_arr" in - let (ctx_with_len, len_var) = alloc_local ctx_with_arr "__for_len" in - let (ctx_with_idx, idx_var) = alloc_local ctx_with_len "__for_idx" in - - (* For pattern, currently only support simple variable binding *) - begin match pat with - | PatVar id -> - let (ctx_with_item, item_var) = alloc_local ctx_with_idx id.name in - - (* Generate body code in context with item variable *) - let* (ctx_final, body_code) = gen_block ctx_with_item body in - - (* For loop structure: - 1. Evaluate iterator (array pointer) - 2. Load length from array[-4] (stored before first element) - 3. Initialize index to 0 - 4. Loop: - - Check if index < length, exit if not - - Load array[index] into item variable - - Execute body - - Increment index - - Continue loop *) - Ok (ctx_final, iter_code @ [ - LocalSet arr_ptr; (* Save array pointer *) - LocalGet arr_ptr; - I32Const (-4l); - I32Add; (* arr - 4 points to length *) - I32Load (2, 0); (* Load length *) - LocalSet len_var; (* Save length *) - I32Const 0l; - LocalSet idx_var; (* index = 0 *) - Block (BtEmpty, [ - Loop (BtEmpty, [ - (* Check if index < length *) - LocalGet idx_var; - LocalGet len_var; - I32GeS; (* index >= length? *) - BrIf 1; (* Exit loop if true *) - - (* Load array[index] into item variable *) - LocalGet arr_ptr; - LocalGet idx_var; - I32Const 4l; - I32Mul; (* index * 4 *) - I32Add; (* arr + index*4 *) - I32Load (2, 0); (* Load array[index] *) - LocalSet item_var; (* item = array[index] *) - ] @ body_code @ [ - (* Increment index *) - LocalGet idx_var; - I32Const 1l; - I32Add; - LocalSet idx_var; (* index++ *) - Br 0 (* Continue loop *) - ]) - ]) - ]) - | PatWildcard _ -> - (* Iterate but don't bind *) - let* (ctx_final, body_code) = gen_block ctx_with_idx body in - Ok (ctx_final, iter_code @ [ - LocalSet arr_ptr; - LocalGet arr_ptr; I32Const (-4l); I32Add; - I32Load (2, 0); LocalSet len_var; - I32Const 0l; LocalSet idx_var; - Block (BtEmpty, [ - Loop (BtEmpty, [ - LocalGet idx_var; LocalGet len_var; I32GeS; BrIf 1; - ] @ body_code @ [ - LocalGet idx_var; I32Const 1l; I32Add; LocalSet idx_var; - Br 0 - ]) - ]) - ]) - | PatTuple pats -> - (* Tuple destructuring in for loop — load element, then extract fields *) - let (ctx_with_elem, elem_var) = alloc_local ctx_with_idx "__for_elem" in - (* Bind each tuple field from memory *) - let* (ctx_with_binds, bind_codes) = List.fold_left (fun acc (i, sub_pat) -> - let* (c, codes) = acc in - match sub_pat with - | PatVar id -> - let (c', local_idx) = alloc_local c id.name in - Ok (c', codes @ [ - LocalGet elem_var; - I32Const (Int32.of_int (i * 4)); - I32Add; - I32Load (2, 0); - LocalSet local_idx; - ]) - | PatWildcard _ -> Ok (c, codes) - | _ -> Ok (c, codes) (* Skip complex sub-patterns *) - ) (Ok (ctx_with_elem, [])) (List.mapi (fun i p -> (i, p)) pats) in - let* (ctx_final, body_code) = gen_block ctx_with_binds body in - Ok (ctx_final, iter_code @ [ - LocalSet arr_ptr; - LocalGet arr_ptr; I32Const (-4l); I32Add; - I32Load (2, 0); LocalSet len_var; - I32Const 0l; LocalSet idx_var; - Block (BtEmpty, [ - Loop (BtEmpty, [ - LocalGet idx_var; LocalGet len_var; I32GeS; BrIf 1; - LocalGet arr_ptr; LocalGet idx_var; - I32Const 4l; I32Mul; I32Add; - I32Load (2, 0); LocalSet elem_var; - ] @ bind_codes @ body_code @ [ - LocalGet idx_var; I32Const 1l; I32Add; LocalSet idx_var; - Br 0 - ]) - ]) - ]) - | _ -> - (* Fallback: bind whole element as a temp *) - let (ctx_with_tmp, tmp_var) = alloc_local ctx_with_idx "__for_tmp" in - let* (ctx_final, body_code) = gen_block ctx_with_tmp body in - Ok (ctx_final, iter_code @ [ - LocalSet arr_ptr; - LocalGet arr_ptr; I32Const (-4l); I32Add; - I32Load (2, 0); LocalSet len_var; - I32Const 0l; LocalSet idx_var; - Block (BtEmpty, [ - Loop (BtEmpty, [ - LocalGet idx_var; LocalGet len_var; I32GeS; BrIf 1; - LocalGet arr_ptr; LocalGet idx_var; - I32Const 4l; I32Mul; I32Add; - I32Load (2, 0); LocalSet tmp_var; - ] @ body_code @ [ - LocalGet idx_var; I32Const 1l; I32Add; LocalSet idx_var; - Br 0 - ]) - ]) - ]) - end - -(** Generate code for a function *) -let gen_function (ctx : context) (fd : fn_decl) : (context * func) result = - (* Create fresh context for function scope, but preserve lambda_funcs and next_lambda_id *) - let fn_ctx = { ctx with locals = []; next_local = 0; loop_depth = 0 } in - - (* Parameters become locals 0..n-1. Also register var_types for - each parameter whose declared type resolves to a nominal struct - (directly or through TyOwn/TyRef/TyMut wrappers), so that a later - [ExprField (ExprVar param_name, f)] inside the body can resolve - the layout via [struct_layouts] set up at [TopType] time. *) - let (ctx_with_params, _) = List.fold_left (fun (c, _) param -> - let (c', idx) = alloc_local c param.p_name.name in - let c'' = match type_name_of param.p_ty with - | Some ty_name -> - { c' with var_types = (param.p_name.name, ty_name) :: c'.var_types } - | None -> c' - in - (c'', idx) - ) (fn_ctx, 0) fd.fd_params in - - let param_count = List.length fd.fd_params in - - (* Generate function body *) - let body_expr = match fd.fd_body with - | FnBlock blk -> ExprBlock blk - | FnExpr e -> e - in - let* (ctx_final, body_code) = gen_expr ctx_with_params body_expr in - - (* Compute additional locals (beyond parameters) *) - let local_count = ctx_final.next_local - param_count in - let locals = if local_count > 0 then - [{ l_count = local_count; l_type = I32 }] - else - [] - in - - (* Create function (type index will be set by gen_decl) *) - let func = { - f_type = 0; (* Will be overridden *) - f_locals = locals; - f_body = body_code; - } in - - (* Return updated context with any lambda functions that were created *) - Ok (ctx_final, func) - -(** Generate code for a top-level declaration *) -let gen_decl (ctx : context) (decl : top_level) : context result = - match decl with - | TopFn fd -> - (* Create function type *) - let param_types = List.map (fun _ -> I32) fd.fd_params in - let result_type = [I32] in - let func_type = { ft_params = param_types; ft_results = result_type } in - - (* Add type to types list *) - let type_idx = List.length ctx.types in - let ctx_with_type = { ctx with types = ctx.types @ [func_type] } in - - (* Determine function index before generating *) - let func_idx = import_func_count ctx_with_type + List.length ctx_with_type.funcs in - - (* Stage 2: Extract ownership annotations for typed-wasm [affinescript.ownership] section *) - let param_kinds = List.map ownership_kind_of_param fd.fd_params in - let ret_kind = ownership_kind_of_ret fd.fd_ret_ty in - - (* Register function name to index mapping and record ownership annotations *) - let ctx_with_func_idx = { ctx_with_type with - func_indices = ctx_with_type.func_indices @ [(fd.fd_name.name, func_idx)]; - ownership_annots = ctx_with_type.ownership_annots @ [(func_idx, param_kinds, ret_kind)]; - } in - - (* Generate function with correct type index *) - let* (ctx_after_gen, func) = gen_function ctx_with_func_idx fd in - let func_with_type = { func with f_type = type_idx } in - - (* Add export for all top-level functions *) - let export = [{ e_name = fd.fd_name.name; e_desc = ExportFunc func_idx }] in - (* Use ctx_after_gen to preserve any lambda_funcs created during generation *) - Ok { ctx_after_gen with - funcs = ctx_after_gen.funcs @ [func_with_type]; - exports = ctx_after_gen.exports @ export - } - - | TopConst tc -> - (* Constants are compiled as WASM globals. - The initial value must be a constant expression (I32Const etc). - For complex initialisers, fall back to a local. *) - let* (ctx', init_code) = gen_expr ctx tc.tc_value in - let global_idx = List.length ctx'.globals in - let global = { - g_type = I32; - g_mutable = false; - g_init = init_code; - } in - let ctx'' = { ctx' with - globals = ctx'.globals @ [global]; - func_indices = (tc.tc_name.name, -(global_idx + 1)) :: ctx'.func_indices; - } in - Ok ctx'' - - | TopType td -> - (* Register per-type codegen metadata: - - TyEnum: sequential variant tags (constructor_name -> tag). - - TyStruct: field layout by declaration order (type_name -> - [(field, offset)]). Needed so that [ExprField] can resolve - field offsets on variables whose type was declared via - [TopType] rather than created via an anonymous record - literal. - - TyAlias: nothing to register at codegen time. *) - begin match td.td_body with - | TyEnum variants -> - let ctx_with_tags = List.fold_left (fun c_acc (idx, vd) -> - { c_acc with variant_tags = (vd.vd_name.name, idx) :: c_acc.variant_tags } - ) ctx (List.mapi (fun i v -> (i, v)) variants) in - Ok ctx_with_tags - | TyStruct fields -> - let layout = List.mapi (fun i sf -> (sf.sf_name.name, i * 4)) fields in - Ok { ctx with - struct_layouts = (td.td_name.name, layout) :: ctx.struct_layouts } - | TyAlias _ -> - Ok ctx - end - - | TopEffect _ | TopTrait _ | TopImpl _ -> - (* These declarations don't generate code *) - Ok ctx - -(** Generate WASM module from AffineScript program *) -let generate_module (prog : program) : wasm_module result = - let ctx = create_context () in - - (* Add WASI fd_write import at index 0 *) - let (fd_write_import, fd_write_type) = Wasi_runtime.create_fd_write_import () in - let fd_write_type_idx = 0 in (* Will be first type *) - let fd_write_import_fixed = { fd_write_import with i_desc = ImportFunc fd_write_type_idx } in - - let ctx_with_wasi = { - ctx with - types = fd_write_type :: ctx.types; - imports = fd_write_import_fixed :: ctx.imports; - } in - - let* ctx' = List.fold_left (fun acc decl -> - let* c = acc in - gen_decl c decl - ) (Ok ctx_with_wasi) prog.prog_decls in - - (* Merge regular functions and lambda functions *) - let num_regular_funcs = List.length ctx'.funcs in - let import_offset = import_func_count ctx' in - let all_funcs = ctx'.funcs @ ctx'.lambda_funcs in - - (* Create function table if there are lambdas *) - let (tables, elems) = if List.length ctx'.lambda_funcs > 0 then - (* Table size = number of lambda functions *) - let table_size = List.length ctx'.lambda_funcs in - let table = [{ tab_type = { lim_min = table_size; lim_max = Some table_size } }] in - - (* Create element segment to initialize table with lambda function indices *) - (* Lambda functions start at index num_regular_funcs *) - let lambda_func_indices = List.mapi (fun i _ -> import_offset + num_regular_funcs + i) ctx'.lambda_funcs in - let elem_seg = [{ - e_table = 0; - e_offset = 0; - e_funcs = lambda_func_indices; - }] in - (table, elem_seg) - else - ([], []) - in - - (* Add memory export *) - let exports_with_mem = { e_name = "memory"; e_desc = ExportMemory 0 } :: ctx'.exports in - - (* Stage 2: Build [affinescript.ownership] custom section from collected annotations *) - let ownership_payload = build_ownership_section ctx'.ownership_annots in - let custom_sections = if Bytes.length ownership_payload > 0 then - [("affinescript.ownership", ownership_payload)] - else - [] - in - - Ok { - types = ctx'.types; - funcs = all_funcs; - tables = tables; - mems = [{ mem_type = { lim_min = 1; lim_max = None } }]; (* 1 page default *) - globals = ctx'.globals; - exports = exports_with_mem; - imports = ctx'.imports; - elems = elems; - datas = List.rev ctx'.datas; (* Reverse to get original order *) - start = None; - custom_sections; - } diff --git a/affinescript/lib/codegen_gc.ml b/affinescript/lib/codegen_gc.ml deleted file mode 100644 index 5b3e654..0000000 --- a/affinescript/lib/codegen_gc.ml +++ /dev/null @@ -1,1226 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** WebAssembly GC code generation from AffineScript AST. - - Translates type-checked AffineScript programs to {!Wasm_gc.gc_module} values, - which are then serialised by {!Wasm_gc_encode.write_gc_module_to_file}. - - {2 Type mapping} - - AffineScript types map to WasmGC types as follows: - - | AffineScript | WasmGC | - |----------------------|---------------------------------| - | Int / Bool / Char | GcPrim I32 (no boxing) | - | Float | GcPrim F64 (no boxing) | - | String | GcRef (HtConcrete string_array) | - | Array[T] | GcRef (HtConcrete array_type) | - | Record / Struct | GcRef (HtConcrete struct_type) | - | Tuple | GcRef (HtConcrete anon_struct) | - | Variant (0-arg) | GcPrim I32 (integer tag) | - | Unknown / param | GcAnyref | - - {2 Affine ownership} - - Because AffineScript has affine (linear) types, exclusively-owned GC - objects are encoded as non-null refs — [GcRef (HtConcrete typeidx)] — - rather than nullable [GcAnyref]. This aligns affine-type semantics with - the WasmGC reference hierarchy without needing runtime null checks. - - {2 First-pass limitations} - - Function parameters and return values default to [GcAnyref] when no type - annotation is present. Field access on function-parameter records requires - a [ref.cast] before [struct.get]. Future passes will propagate concrete - types through the function signature. -*) - -open Ast -open Wasm_gc - -(** {1 Error type} *) - -type codegen_error = - | UnsupportedFeature of string - | UnboundVariable of string - | UnboundType of string - (** Raised when [ExprApp] names a function that has no entry in [func_indices]. - This is always a compiler bug — every defined function must be registered - before codegen reaches a call-site. *) - | UnboundFunction of string -[@@deriving show] - -type 'a cg_result = ('a, codegen_error) Result.t - -let ( let* ) = Result.bind - -(** {1 Codegen context} *) - -(** GC code generation context. - - Module-level fields accumulate across top-level declarations. - Function-level fields are reset at the start of each function. *) -type gc_ctx = { - - (* ── Module-level accumulated state ──────────────────────────────── *) - - (** GC type definitions in registration order. - A type's index in the type section equals its position here. *) - gc_type_defs : gc_type_def list; - next_type_idx : int; - - (** Accumulated function definitions. *) - gc_funcs_acc : gc_func list; - - (** Accumulated named exports. *) - gc_exports_acc : gc_export list; - - (** Function name → absolute function index (import_count + position). *) - func_indices : (string * int) list; - - (** Number of imported functions (offsets defined-function indices). *) - import_count : int; - - (* ── GC type registry ────────────────────────────────────────────── *) - - (** Named struct registry: AffineScript type name → GC type index. - Populated from [TopType (TyStruct _)] declarations. *) - struct_reg : (string * int) list; - - (** Field-name map: type name → [(field_name, field_index_in_struct)]. - Needed to emit [StructGet] with the correct field index. *) - field_reg : (string * (string * int) list) list; - - (** Anonymous struct registry: comma-joined field names → GC type index. - Used for structurally-typed record literals not backed by a named type. - Example key: "x,y" for a record with fields x and y in that order. *) - anon_struct_reg : (string * int) list; - - (** Array type registry: element-kind key → GC type index. - Keys: "i32" | "f64" | "anyref" *) - array_reg : (string * int) list; - - (** Variant constructor → integer tag (same convention as {!Codegen}). *) - variant_tags : (string * int) list; - - (* ── Function-level state (reset per function) ───────────────────── *) - - (** Variable name → local index within the current function. *) - locals : (string * int) list; - - (** Local index → GC value type (for building [gf_locals] vector). - Covers only indices ≥ param_count (extra locals beyond params). *) - local_kinds : (int * gc_valtype) list; - - next_local : int; - - (** Number of function parameters = number of pre-declared locals. *) - param_count : int; - - loop_depth : int; - - (** Variable name → GC struct type index. - Set when a let-binding is assigned from [ExprRecord] or [ExprTuple], - enabling direct [StructGet] without a preceding [ref.cast]. *) - var_gc_type : (string * int) list; -} - -(** {1 Context helpers} *) - -let create_gc_ctx () : gc_ctx = { - gc_type_defs = []; - next_type_idx = 0; - gc_funcs_acc = []; - gc_exports_acc = []; - func_indices = []; - import_count = 0; - struct_reg = []; - field_reg = []; - anon_struct_reg = []; - array_reg = []; - variant_tags = []; - locals = []; - local_kinds = []; - next_local = 0; - param_count = 0; - loop_depth = 0; - var_gc_type = []; -} - -(** Register a GC type definition, returning the updated context and type index. *) -let register_gc_type (ctx : gc_ctx) (typedef : gc_type_def) : gc_ctx * int = - let idx = ctx.next_type_idx in - ({ ctx with - gc_type_defs = ctx.gc_type_defs @ [typedef]; - next_type_idx = idx + 1 - }, idx) - -(** Allocate a new named local with a given GC value type. - Returns the updated context and the local's index. *) -let alloc_local (ctx : gc_ctx) (name : string) (vt : gc_valtype) : gc_ctx * int = - let idx = ctx.next_local in - let kinds' = - if idx >= ctx.param_count - then (idx, vt) :: ctx.local_kinds - else ctx.local_kinds - in - ({ ctx with - locals = (name, idx) :: ctx.locals; - local_kinds = kinds'; - next_local = idx + 1 - }, idx) - -(** Look up the local index for a named variable. *) -let lookup_local (ctx : gc_ctx) (name : string) : int cg_result = - match List.assoc_opt name ctx.locals with - | Some idx -> Ok idx - | None -> Error (UnboundVariable name) - -(** {1 Type conversion helpers} *) - -(** Map an AffineScript type annotation to a GC value type. - - Primitives (Int, Bool, Char, Nat) become [GcPrim I32]; Float becomes - [GcPrim F64]; all other types become [GcAnyref] in this first pass. - A future pass will propagate concrete struct/array ref types through - the type registry. *) -let rec as_type_to_gc_valtype (ty : type_expr) : gc_valtype = - match ty with - | TyCon id when id.name = "Int" || id.name = "Bool" - || id.name = "Char" || id.name = "Nat" -> GcPrim I32 - | TyCon id when id.name = "Float" -> GcPrim F64 - | TyCon _ -> GcAnyref - | TyOwn inner | TyRef inner -> as_type_to_gc_valtype inner - | _ -> GcAnyref - -(** Map an AffineScript type annotation to a struct field type. - Affine records use non-mutable anyref fields by default. *) -and as_type_to_field_type (ty : type_expr) : field_type = - match as_type_to_gc_valtype ty with - | GcPrim I32 -> field_i32 - | GcPrim F64 -> field_f64 - | _ -> field_anyref - -(** Infer the GC value type of an expression (heuristic for local allocation). - Used when no type annotation is available. *) -let gc_valtype_of_expr (expr : expr) : gc_valtype = - match expr with - | ExprLit (LitInt _ | LitBool _ | LitChar _ | LitUnit _) -> GcPrim I32 - | ExprLit (LitFloat _) -> GcPrim F64 - | ExprBinary _ | ExprUnary _ -> GcPrim I32 - | ExprRecord _ | ExprTuple _ | ExprArray _ -> GcAnyref - | _ -> GcAnyref (* conservative *) - -(** {1 Registry helpers} *) - -(** Find or register an anonymous struct type keyed by ordered field names. - Returns (ctx', type_idx, field_name_to_index_map). *) -let find_or_register_anon_struct - (ctx : gc_ctx) - (field_names : string list) - (field_types : field_type list) - : gc_ctx * int * (string * int) list = - let key = String.concat "," field_names in - match List.assoc_opt key ctx.anon_struct_reg with - | Some idx -> - let field_map = List.mapi (fun i n -> (n, i)) field_names in - (ctx, idx, field_map) - | None -> - let (ctx', idx) = register_gc_type ctx (GcStructType field_types) in - let field_map = List.mapi (fun i n -> (n, i)) field_names in - let ctx'' = { ctx' with anon_struct_reg = (key, idx) :: ctx'.anon_struct_reg } in - (ctx'', idx, field_map) - -(** Find or register a GC array type for a given element kind. - elem_key: "i32" | "f64" | "anyref" - Returns (ctx', type_idx). *) -let find_or_register_array (ctx : gc_ctx) (elem_key : string) (elem_ft : field_type) - : gc_ctx * int = - match List.assoc_opt elem_key ctx.array_reg with - | Some idx -> (ctx, idx) - | None -> - let (ctx', idx) = register_gc_type ctx (GcArrayType elem_ft) in - ({ ctx' with array_reg = (elem_key, idx) :: ctx'.array_reg }, idx) - -(** Determine the array element kind and field type from the first element expression. - Used as a heuristic when element type annotations are unavailable. *) -let array_elem_info_from_expr (elem_opt : expr option) : string * field_type = - match elem_opt with - | Some (ExprLit (LitInt _ | LitBool _ | LitChar _)) -> - ("i32", { ft_storage = StVal (GcPrim I32); ft_mutable = true }) - | Some (ExprLit (LitFloat _)) -> - ("f64", { ft_storage = StVal (GcPrim F64); ft_mutable = true }) - | _ -> - ("anyref", { ft_storage = StVal GcAnyref; ft_mutable = true }) - -(** Get the GC struct type index for a record-valued expression. - Returns [None] when the concrete type cannot be determined statically. *) -let gc_struct_type_of_expr (ctx : gc_ctx) (expr : expr) : int option = - match expr with - | ExprVar id -> List.assoc_opt id.name ctx.var_gc_type - | _ -> None - -(** {1 Instruction helpers} *) - -(** Wrap a standard WASM 1.0 instruction. *) -let std (i : Wasm.instr) : gc_instr = Std i - -(** Push an i32 constant. *) -let push_i32 (n : int) : gc_instr = Std (Wasm.I32Const (Int32.of_int n)) - -(** {1 Binary operator mapping} *) - -let gen_binop (op : binary_op) : Wasm.instr = - match op with - | OpAdd -> Wasm.I32Add | OpSub -> Wasm.I32Sub - | OpMul -> Wasm.I32Mul | OpDiv -> Wasm.I32DivS - | OpMod -> Wasm.I32RemS | OpEq -> Wasm.I32Eq - | OpNe -> Wasm.I32Ne | OpLt -> Wasm.I32LtS - | OpLe -> Wasm.I32LeS | OpGt -> Wasm.I32GtS - | OpGe -> Wasm.I32GeS | OpAnd -> Wasm.I32And - | OpOr -> Wasm.I32Or | OpBitAnd -> Wasm.I32And - | OpBitOr -> Wasm.I32Or | OpBitXor -> Wasm.I32Xor - | OpShl -> Wasm.I32Shl | OpShr -> Wasm.I32ShrS - | OpConcat -> Wasm.I32Add (* Placeholder *) - -(** {1 Expression codegen} *) - -(** Generate GC instructions for an expression. - - Returns [(ctx', instrs)] where [instrs] leaves exactly one value on the - WASM value stack. [ctx'] carries any newly registered GC types or locals. - - Key GC operations: - - [ExprRecord] → allocate via [struct.new]; non-null affine ownership - - [ExprField] → [ref.cast] then [struct.get] (or direct if type is known) - - [ExprArray] → allocate via [array.new_fixed]; non-null affine ownership - - [ExprIndex] → [ref.cast] then [array.get] - - [ExprTuple] → anonymous struct via [struct.new] -*) -let rec gen_gc_expr (ctx : gc_ctx) (expr : expr) : (gc_ctx * gc_instr list) cg_result = - match expr with - - (* ── Literals ──────────────────────────────────────────────────── *) - - | ExprLit (LitUnit _) -> Ok (ctx, [push_i32 0]) - | ExprLit (LitBool (b, _)) -> Ok (ctx, [push_i32 (if b then 1 else 0)]) - | ExprLit (LitInt (n, _)) -> Ok (ctx, [Std (Wasm.I32Const (Int32.of_int n))]) - | ExprLit (LitFloat (f, _)) -> Ok (ctx, [Std (Wasm.F64Const f)]) - | ExprLit (LitChar (c, _)) -> Ok (ctx, [push_i32 (Char.code c)]) - | ExprLit (LitString (_, _)) -> - (* Strings in GC mode would be (array (mut i8)) refs. - First pass: emit a null anyref as a safe placeholder. *) - Ok (ctx, [RefNull HtAny]) - - (* ── Variable access ───────────────────────────────────────────── *) - - | ExprVar id -> - let* idx = lookup_local ctx id.name in - Ok (ctx, [std (Wasm.LocalGet idx)]) - - (* ── Arithmetic / logical / comparison ─────────────────────────── *) - - | ExprBinary (lhs, op, rhs) -> - let* (ctx1, lhs_code) = gen_gc_expr ctx lhs in - let* (ctx2, rhs_code) = gen_gc_expr ctx1 rhs in - Ok (ctx2, lhs_code @ rhs_code @ [std (gen_binop op)]) - - | ExprUnary (OpNeg, operand) -> - let* (ctx', code) = gen_gc_expr ctx operand in - Ok (ctx', [push_i32 0] @ code @ [std Wasm.I32Sub]) - - | ExprUnary (OpNot, operand) -> - let* (ctx', code) = gen_gc_expr ctx operand in - Ok (ctx', code @ [std Wasm.I32Eqz]) - - | ExprUnary (_, operand) -> - gen_gc_expr ctx operand - - (* ── Function calls ────────────────────────────────────────────── *) - - | ExprApp (ExprVar id, args) -> - let* (ctx_after_args, arg_codes_rev) = - List.fold_left (fun acc arg -> - let* (c, rev_codes) = acc in - let* (c', code) = gen_gc_expr c arg in - Ok (c', code :: rev_codes) - ) (Ok (ctx, [])) args - in - let arg_codes = List.concat (List.rev arg_codes_rev) in - - begin match id.name with - | "int" -> - Ok (ctx_after_args, arg_codes @ [Std (Wasm.I32TruncF64S)]) - | "float" -> - Ok (ctx_after_args, arg_codes @ [Std (Wasm.F64ConvertI32S)]) - | _ -> - match List.assoc_opt id.name ctx_after_args.func_indices with - | Some func_idx -> - Ok (ctx_after_args, arg_codes @ [Std (Wasm.Call func_idx)]) - | None -> - (* BUG-005: every reachable function must be registered in func_indices - before codegen visits any call-site. Silently emitting drop+null here - would produce well-typed but semantically wrong WASM that is impossible - to debug at runtime. Fail loudly instead. *) - Error (UnboundFunction id.name) - end - - | ExprApp (callee, args) -> - (* BUG-005: indirect / higher-order calls (callee is not a plain ExprVar) are - not yet lowered to call_ref in the WasmGC backend. The old behaviour — - evaluate callee+args, drop everything, push null — was silently wrong and - would crash at the call-site with an opaque type error. Reject explicitly - until call_ref support is added. *) - let _ = (callee, args) in - Error (UnsupportedFeature "indirect / higher-order call in WasmGC backend (call_ref not yet implemented)") - - (* ── Record allocation (core GC operation) ─────────────────────── *) - - | ExprRecord rec_expr -> - let field_names = List.map (fun (id, _) -> id.name) rec_expr.er_fields in - let n = List.length field_names in - - (* Generate code for each field value in declaration order. - Stack at struct.new call: field[0], field[1], ..., field[n-1]. *) - let* (ctx_after, field_codes_rev) = - List.fold_left (fun acc (_, expr_opt) -> - let* (c, rev_codes) = acc in - let fexpr = match expr_opt with - | Some e -> e - | None -> - let i = List.length rev_codes in - ExprVar { name = List.nth field_names i; span = Span.dummy } - in - let* (c', code) = gen_gc_expr c fexpr in - Ok (c', code :: rev_codes) - ) (Ok (ctx, [])) rec_expr.er_fields - in - let field_codes = List.concat (List.rev field_codes_rev) in - - (* Register anonymous struct type for this field signature. - All fields default to immutable anyref — field types are refined - when a named type annotation is available (see gen_gc_decl/TopType). *) - let field_types = List.init n (fun _ -> field_anyref) in - let (ctx', type_idx, _field_map) = - find_or_register_anon_struct ctx_after field_names field_types - in - - (* struct.new is non-null — this is an affinely-owned GC object. *) - Ok (ctx', field_codes @ [StructNew type_idx]) - - (* ── Record field access ────────────────────────────────────────── *) - - | ExprField (base_expr, field_id) -> - let* (ctx', base_code) = gen_gc_expr ctx base_expr in - - (* Attempt to identify the concrete struct type statically: - (a) The base is a named variable with a tracked GC type index. - (b) The base is a named struct from struct_reg. *) - let find_type_and_field () : (int * int) option = - (* Try var_gc_type lookup first *) - let type_idx_opt = gc_struct_type_of_expr ctx' base_expr in - match type_idx_opt with - | Some type_idx -> - (* Reconstruct the field map for this type index *) - let reverse_anon = List.map (fun (k, v) -> (v, k)) ctx'.anon_struct_reg in - let reverse_named = List.map (fun (k, v) -> (v, k)) ctx'.struct_reg in - let type_name_opt = - match List.assoc_opt type_idx reverse_named with - | Some n -> Some n - | None -> - (* Anonymous struct: rebuild field names from key *) - Option.map (fun key -> - (* Use key directly as type name for field_reg lookup *) - let names = String.split_on_char ',' key in - String.concat "," names - ) (List.assoc_opt type_idx reverse_anon) - in - begin match type_name_opt with - | Some type_name -> - begin match List.assoc_opt type_name ctx'.field_reg with - | Some fields -> - begin match List.assoc_opt field_id.name fields with - | Some field_idx -> Some (type_idx, field_idx) - | None -> None - end - | None -> - (* Anonymous struct: field index = position in field names *) - let anon_key = match List.assoc_opt type_idx reverse_anon with - | Some k -> k | None -> "" in - let names = String.split_on_char ',' anon_key in - let rec find_pos i = function - | [] -> None - | n :: _ when n = field_id.name -> Some (type_idx, i) - | _ :: rest -> find_pos (i + 1) rest - in - find_pos 0 names - end - | None -> None - end - | None -> None - in - - begin match find_type_and_field () with - | Some (type_idx, field_idx) -> - (* Concrete type known: ref.cast (non-null, affine) then struct.get *) - Ok (ctx', base_code @ - [RefCast (HtConcrete type_idx); - StructGet (type_idx, field_idx)]) - | None -> - (* Unknown type: cannot emit struct.get without type index. - Emit drop + null as a safe placeholder — field access on unknown - struct types requires propagating type info through the pipeline. *) - Ok (ctx', base_code @ [std Wasm.Drop; RefNull HtAny]) - end - - (* ── Tuple allocation ───────────────────────────────────────────── *) - - | ExprTuple elems -> - let n = List.length elems in - let* (ctx_after, elem_codes_rev) = - List.fold_left (fun acc elem -> - let* (c, rev_codes) = acc in - let* (c', code) = gen_gc_expr c elem in - Ok (c', code :: rev_codes) - ) (Ok (ctx, [])) elems - in - let elem_codes = List.concat (List.rev elem_codes_rev) in - - (* Tuple fields are positional: _0, _1, _2, ... *) - let field_names = List.init n (fun i -> Printf.sprintf "_%d" i) in - let field_types = List.init n (fun _ -> field_anyref) in - let (ctx', type_idx, _) = - find_or_register_anon_struct ctx_after field_names field_types - in - Ok (ctx', elem_codes @ [StructNew type_idx]) - - (* ── Tuple element access ───────────────────────────────────────── *) - - | ExprTupleIndex (tuple_expr, index) -> - let* (ctx', base_code) = gen_gc_expr ctx tuple_expr in - let type_idx_opt = gc_struct_type_of_expr ctx' tuple_expr in - begin match type_idx_opt with - | Some type_idx -> - Ok (ctx', base_code @ - [RefCast (HtConcrete type_idx); - StructGet (type_idx, index)]) - | None -> - (* Unknown tuple type: cannot emit struct.get safely *) - Ok (ctx', base_code @ [std Wasm.Drop; RefNull HtAny]) - end - - (* ── Array allocation ───────────────────────────────────────────── *) - - | ExprArray elems -> - let n = List.length elems in - let first_elem = match elems with e :: _ -> Some e | [] -> None in - let (elem_key, elem_ft) = array_elem_info_from_expr first_elem in - let (ctx', array_type_idx) = find_or_register_array ctx elem_key elem_ft in - - if n = 0 then - (* array.new_default with length 0 — valid empty GC array *) - Ok (ctx', [push_i32 0; ArrayNewDefault array_type_idx]) - else begin - (* Push all elements onto the stack, then array.new_fixed. - Stack: elem[0], elem[1], ..., elem[n-1] *) - let* (ctx'', elem_codes_rev) = - List.fold_left (fun acc elem -> - let* (c, rev_codes) = acc in - let* (c', code) = gen_gc_expr c elem in - Ok (c', code :: rev_codes) - ) (Ok (ctx', [])) elems - in - let elem_codes = List.concat (List.rev elem_codes_rev) in - Ok (ctx'', elem_codes @ [ArrayNewFixed (array_type_idx, n)]) - end - - (* ── Array element access ───────────────────────────────────────── *) - - | ExprIndex (arr_expr, idx_expr) -> - let* (ctx1, arr_code) = gen_gc_expr ctx arr_expr in - let* (ctx2, idx_code) = gen_gc_expr ctx1 idx_expr in - - (* Look up array GC type index from var_gc_type if possible, - otherwise fall back to the most recently registered array type. *) - let array_type_idx = - match gc_struct_type_of_expr ctx2 arr_expr with - | Some tidx -> tidx - | None -> - match ctx2.array_reg with - | (_, idx) :: _ -> idx - | [] -> 0 - in - - (* array.get: ref.cast the array ref, then array.get *) - Ok (ctx2, - arr_code @ - [RefCast (HtConcrete array_type_idx)] @ - idx_code @ - [ArrayGet array_type_idx]) - - (* ── Variant (zero-argument) ────────────────────────────────────── *) - - | ExprVariant (_, variant_name) -> - (* Zero-argument variants are represented as i32 tags — identical to - the WASM 1.0 backend. Non-zero-arg variants would need a GC struct - with a tag field, which is added in a future pass. *) - let (tag, ctx') = - match List.assoc_opt variant_name.name ctx.variant_tags with - | Some t -> (t, ctx) - | None -> - let t = List.length ctx.variant_tags in - (t, { ctx with variant_tags = (variant_name.name, t) :: ctx.variant_tags }) - in - Ok (ctx', [push_i32 tag]) - - (* ── If / else ──────────────────────────────────────────────────── *) - - | ExprIf ei -> - let* (ctx1, cond_code) = gen_gc_expr ctx ei.ei_cond in - let* (ctx2, then_code) = gen_gc_expr ctx1 ei.ei_then in - begin match ei.ei_else with - | None -> - (* No else branch: if runs body for effect, result is unit (0) *) - Ok (ctx2, cond_code @ - [GcIf (GcBtPrim I32, - then_code @ [push_i32 0], (* then: body result discarded, push 0 *) - [push_i32 0])]) (* else: unit *) - | Some else_expr -> - let* (ctx3, else_code) = gen_gc_expr ctx2 else_expr in - (* Use I32 result type — if branches return numeric results. - For GC-ref-typed results, GcBtRef HtAny would be appropriate. *) - Ok (ctx3, cond_code @ - [GcIf (GcBtPrim I32, then_code, else_code)]) - end - - (* ── Block ──────────────────────────────────────────────────────── *) - - | ExprBlock blk -> - gen_gc_block ctx blk - - (* ── Inline let (expr form: let x = rhs in body) ────────────────── *) - - | ExprLet lb -> - let* (ctx1, rhs_code) = gen_gc_expr ctx lb.el_value in - let vt = gc_valtype_of_expr lb.el_value in - begin match lb.el_pat with - | PatVar id -> - let (ctx2, local_idx) = alloc_local ctx1 id.name vt in - - (* Track GC struct type when RHS is a structural value *) - let ctx3 = match lb.el_value with - | ExprRecord _ | ExprTuple _ -> - begin match ctx2.anon_struct_reg with - | (_, type_idx) :: _ -> - { ctx2 with var_gc_type = (id.name, type_idx) :: ctx2.var_gc_type } - | [] -> ctx2 - end - | _ -> ctx2 - in - - (* Generate body or return unit if this is a standalone binding *) - begin match lb.el_body with - | Some body_expr -> - let* (ctx_final, body_code) = gen_gc_expr ctx3 body_expr in - Ok (ctx_final, - rhs_code @ - [std (Wasm.LocalSet local_idx)] @ - body_code) - | None -> - Ok (ctx3, rhs_code @ [std (Wasm.LocalSet local_idx); push_i32 0]) - end - - | PatWildcard _ -> - let body_code_or_unit = match lb.el_body with - | Some body -> - begin match gen_gc_expr ctx1 body with - | Ok (_, code) -> code - | Error _ -> [push_i32 0] - end - | None -> [push_i32 0] - in - let* ctx_final = match lb.el_body with - | Some body -> let* (c, _) = gen_gc_expr ctx1 body in Ok c - | None -> Ok ctx1 - in - Ok (ctx_final, rhs_code @ [std Wasm.Drop] @ body_code_or_unit) - - | _ -> - (* Complex pattern — bind to anonymous temp, continue with body *) - let (ctx2, _tmp) = alloc_local ctx1 "__let_tmp" GcAnyref in - begin match lb.el_body with - | Some body_expr -> - let* (ctx_final, body_code) = gen_gc_expr ctx2 body_expr in - Ok (ctx_final, rhs_code @ [std Wasm.Drop] @ body_code) - | None -> - Ok (ctx2, rhs_code @ [std Wasm.Drop; push_i32 0]) - end - end - - (* ── Return ─────────────────────────────────────────────────────── *) - - | ExprReturn expr_opt -> - begin match expr_opt with - | Some e -> - let* (ctx', code) = gen_gc_expr ctx e in - Ok (ctx', code @ [std Wasm.Return]) - | None -> - Ok (ctx, [push_i32 0; std Wasm.Return]) - end - - (* ── Pattern match ──────────────────────────────────────────────── *) - - | ExprMatch m -> - let* (ctx1, scrutinee_code) = gen_gc_expr ctx m.em_scrutinee in - - (* Store scrutinee in a temporary local (anyref covers all cases) *) - let (ctx2, scrutinee_local) = alloc_local ctx1 "__scrutinee" GcAnyref in - let save_code = [std (Wasm.LocalSet scrutinee_local)] in - - (* Build right-to-left if/else chain: last arm is the default *) - let* (ctx_final, match_code) = - List.fold_right - (fun arm acc -> - let* (c_acc, default_code) = acc in - begin match arm.ma_pat with - - | PatWildcard _ -> - let* (c', body_code) = gen_gc_expr c_acc arm.ma_body in - Ok (c', body_code) (* Wildcard: ignore default, use body *) - - | PatVar id -> - let (c_with_var, var_idx) = alloc_local c_acc id.name GcAnyref in - let* (c', body_code) = gen_gc_expr c_with_var arm.ma_body in - Ok (c', - [std (Wasm.LocalGet scrutinee_local); - std (Wasm.LocalSet var_idx)] @ - body_code) - - | PatLit lit -> - let lit_instr = match lit with - | LitBool (b, _) -> push_i32 (if b then 1 else 0) - | LitInt (n, _) -> push_i32 n - | LitChar (c, _) -> push_i32 (Char.code c) - | _ -> push_i32 0 - in - let* (c', body_code) = gen_gc_expr c_acc arm.ma_body in - let test = - [std (Wasm.LocalGet scrutinee_local); - lit_instr; std Wasm.I32Eq] - in - Ok (c', test @ - [GcIf (GcBtPrim I32, body_code, default_code)]) - - | PatCon (con, _sub_pats) -> - let (tag, c_acc') = - match List.assoc_opt con.name c_acc.variant_tags with - | Some t -> (t, c_acc) - | None -> - let t = List.length c_acc.variant_tags in - (t, { c_acc with variant_tags = - (con.name, t) :: c_acc.variant_tags }) - in - let* (c', body_code) = gen_gc_expr c_acc' arm.ma_body in - let test = - [std (Wasm.LocalGet scrutinee_local); - push_i32 tag; std Wasm.I32Eq] - in - Ok (c', test @ - [GcIf (GcBtPrim I32, body_code, default_code)]) - - | _ -> - (* Unsupported pattern: skip arm, fall to default *) - Ok (c_acc, default_code) - end) - m.em_arms - (Ok (ctx2, [push_i32 0])) (* terminal default: return 0 *) - in - Ok (ctx_final, scrutinee_code @ save_code @ match_code) - - (* ── Effect / error-handling passthrough ────────────────────────── *) - - | ExprHandle _eh -> - (* Effect handler dispatch is not implementable in this WasmGC backend - without either: - - The WASM exception-handling proposal (EH) to propagate PerformEffect - across stack frames and capture the perform-site continuation, OR - - A whole-program CPS transform before codegen. - - Silently compiling just the body (the previous behaviour) was wrong: - any handler arms are dropped, so effects are never caught, and the - first `perform` would trap at the op stub rather than dispatch to the - correct handler arm. Fail loudly instead. - - To use algebraic effects, compile with the interpreter backend (-i). *) - Error (UnsupportedFeature - "effect handler (handle { ... }) in WasmGC backend — \ - requires WASM EH proposal or CPS transform; use `--interp` / `-i`") - - | ExprTry et -> - (* WasmGC 1.0 does not support the exception-handling proposal. - - catch arms: UnsupportedFeature (cannot trap-and-resume in GC mode). - - body + optional finally: compile sequentially with a GcAnyref temp - to preserve the body result across the finally block. *) - begin match et.et_catch with - | Some _ -> - Error (UnsupportedFeature - "try/catch in WasmGC backend — \ - requires the WASM exception-handling proposal; \ - use the Julia backend (-julia) or the interpreter (-i)") - | None -> - let* (ctx', body_code) = gen_gc_block ctx et.et_body in - begin match et.et_finally with - | None -> Ok (ctx', body_code) - | Some blk -> - let (ctx'', tmp_idx) = alloc_local ctx' "__try_result" GcAnyref in - let* (ctx''', fin_code) = gen_gc_block ctx'' blk in - Ok (ctx''', - body_code - @ [std (Wasm.LocalSet tmp_idx)] (* stash body result *) - @ fin_code - @ [std Wasm.Drop] (* discard finally result *) - @ [std (Wasm.LocalGet tmp_idx)] (* restore body result *)) - end - end - - | ExprResume _arg_opt -> - (* `resume` is only meaningful inside an effect handler arm. The WasmGC - backend has no handler dispatch (see ExprHandle above), so emitting - the argument value here would be silently wrong — the enclosing handle - expression already fails with UnsupportedFeature before we ever reach - a resume. Fail consistently. *) - Error (UnsupportedFeature - "`resume` expression in WasmGC backend — \ - only valid inside a `handle` block; use `--interp` / `-i`") - - | ExprRowRestrict (base, _) -> - (* Row restriction is type-level; GC pointer is unchanged *) - gen_gc_expr ctx base - - | ExprSpan (e, _) -> - gen_gc_expr ctx e - - (* ── Fallback ───────────────────────────────────────────────────── *) - - | _ -> - (* Unsupported expression: null anyref is a safe non-crashing placeholder *) - Ok (ctx, [RefNull HtAny]) - -(** {1 Block codegen} *) - -(** Generate GC instructions for a block. - - Statements execute for effects (results discarded). The trailing - expression, if present, leaves its value on the stack. A block - with no trailing expression pushes unit (0). *) -and gen_gc_block (ctx : gc_ctx) (blk : block) : (gc_ctx * gc_instr list) cg_result = - let* (ctx', stmt_codes) = - List.fold_left (fun acc stmt -> - let* (c, codes) = acc in - let* (c', code) = gen_gc_stmt c stmt in - Ok (c', codes @ code) - ) (Ok (ctx, [])) blk.blk_stmts - in - match blk.blk_expr with - | Some e -> - let* (ctx_final, expr_code) = gen_gc_expr ctx' e in - Ok (ctx_final, stmt_codes @ expr_code) - | None -> - Ok (ctx', stmt_codes @ [push_i32 0]) - -(** {1 Statement codegen} *) - -and gen_gc_stmt (ctx : gc_ctx) (stmt : stmt) : (gc_ctx * gc_instr list) cg_result = - match stmt with - - | StmtLet sl -> - let* (ctx1, rhs_code) = gen_gc_expr ctx sl.sl_value in - let vt = gc_valtype_of_expr sl.sl_value in - begin match sl.sl_pat with - - | PatVar id -> - let (ctx2, local_idx) = alloc_local ctx1 id.name vt in - - (* Track GC struct type index when RHS is a structural allocation *) - let ctx3 = match sl.sl_value with - | ExprRecord _ | ExprTuple _ -> - begin match ctx2.anon_struct_reg with - | (_, type_idx) :: _ -> - { ctx2 with var_gc_type = (id.name, type_idx) :: ctx2.var_gc_type } - | [] -> ctx2 - end - | _ -> ctx2 - in - Ok (ctx3, rhs_code @ [std (Wasm.LocalSet local_idx)]) - - | PatTuple sub_pats -> - (* Destructure: save tuple ref, then extract each element via StructGet *) - let (ctx2, tmp_idx) = alloc_local ctx1 "__tup_tmp" GcAnyref in - - (* Determine the tuple's struct type index from the most recent anon entry *) - let type_idx_opt = match ctx2.anon_struct_reg with - | (_, idx) :: _ -> Some idx - | [] -> None - in - - let* (ctx_final, bind_codes) = - List.fold_left (fun acc (i, sub_pat) -> - let* (c, codes) = acc in - begin match sub_pat with - | PatVar id -> - let (c', var_idx) = alloc_local c id.name GcAnyref in - let load = match type_idx_opt with - | Some tidx -> - [std (Wasm.LocalGet tmp_idx); - RefCast (HtConcrete tidx); - StructGet (tidx, i); - std (Wasm.LocalSet var_idx)] - | None -> - (* Type unknown: bind 0 as placeholder *) - [push_i32 0; std (Wasm.LocalSet var_idx)] - in - Ok (c', codes @ load) - | PatWildcard _ -> Ok (c, codes) - | _ -> Ok (c, codes) - end - ) (Ok (ctx2, [])) (List.mapi (fun i p -> (i, p)) sub_pats) - in - Ok (ctx_final, - rhs_code @ [std (Wasm.LocalSet tmp_idx)] @ bind_codes) - - | PatWildcard _ -> - Ok (ctx1, rhs_code @ [std Wasm.Drop]) - - | _ -> - (* Other patterns: evaluate RHS for effects, discard *) - Ok (ctx1, rhs_code @ [std Wasm.Drop]) - end - - | StmtExpr e -> - let* (ctx', code) = gen_gc_expr ctx e in - Ok (ctx', code @ [std Wasm.Drop]) - - | StmtAssign (lhs, _op, rhs) -> - let* (ctx', rhs_code) = gen_gc_expr ctx rhs in - begin match lhs with - | ExprVar id -> - let* local_idx = lookup_local ctx' id.name in - Ok (ctx', rhs_code @ [std (Wasm.LocalSet local_idx)]) - | _ -> - (* Complex assignment targets (array index, field) deferred *) - Ok (ctx', rhs_code @ [std Wasm.Drop]) - end - - | StmtWhile (cond, body) -> - let* (ctx1, cond_code) = gen_gc_expr ctx cond in - let* (ctx2, body_code) = gen_gc_block ctx1 body in - (* while (cond) { body } → block { loop { cond; i32.eqz; br_if 1; body; br 0 } } *) - Ok (ctx2, [GcBlock (GcBtEmpty, - [GcLoop (GcBtEmpty, - cond_code @ - [std Wasm.I32Eqz; std (Wasm.BrIf 1)] @ - body_code @ - [std Wasm.Drop; std (Wasm.Br 0)] - )] - )]) - - | StmtFor (pat, iter_expr, body) -> - (* For loop over a GC array: iter_expr produces an array ref. - Iterates from index 0 to array.len (exclusive). *) - let* (ctx1, iter_code) = gen_gc_expr ctx iter_expr in - - let (ctx2, arr_local) = alloc_local ctx1 "__for_arr" GcAnyref in - let (ctx3, len_local) = alloc_local ctx2 "__for_len" (GcPrim I32) in - let (ctx4, idx_local) = alloc_local ctx3 "__for_idx" (GcPrim I32) in - - (* Use the most recently registered array type for array.len and array.get *) - let array_type_idx = match ctx4.array_reg with - | (_, idx) :: _ -> idx - | [] -> 0 - in - - let init_code = - iter_code @ - [std (Wasm.LocalSet arr_local); - std (Wasm.LocalGet arr_local); - RefCast (HtConcrete array_type_idx); - ArrayLen; - std (Wasm.LocalSet len_local); - push_i32 0; - std (Wasm.LocalSet idx_local)] - in - - begin match pat with - | PatVar item_id -> - let (ctx5, item_local) = alloc_local ctx4 item_id.name GcAnyref in - let* (ctx_final, body_code) = gen_gc_block ctx5 body in - Ok (ctx_final, init_code @ [GcBlock (GcBtEmpty, - [GcLoop (GcBtEmpty, - (* Check: idx >= len → exit *) - [std (Wasm.LocalGet idx_local); - std (Wasm.LocalGet len_local); - std Wasm.I32GeS; std (Wasm.BrIf 1); - (* Load arr[idx] → item_local *) - std (Wasm.LocalGet arr_local); - RefCast (HtConcrete array_type_idx); - std (Wasm.LocalGet idx_local); - ArrayGet array_type_idx; - std (Wasm.LocalSet item_local); - ] @ - body_code @ - [std Wasm.Drop; (* discard body result *) - std (Wasm.LocalGet idx_local); - push_i32 1; - std Wasm.I32Add; - std (Wasm.LocalSet idx_local); - std (Wasm.Br 0)] (* next iteration *) - )] - )]) - - | PatWildcard _ -> - let* (ctx_final, body_code) = gen_gc_block ctx4 body in - Ok (ctx_final, init_code @ [GcBlock (GcBtEmpty, - [GcLoop (GcBtEmpty, - [std (Wasm.LocalGet idx_local); - std (Wasm.LocalGet len_local); - std Wasm.I32GeS; std (Wasm.BrIf 1); - ] @ - body_code @ - [std Wasm.Drop; - std (Wasm.LocalGet idx_local); - push_i32 1; std Wasm.I32Add; - std (Wasm.LocalSet idx_local); - std (Wasm.Br 0)] - )] - )]) - - | _ -> - (* Other patterns: iterate without binding *) - let* (ctx_final, body_code) = gen_gc_block ctx4 body in - Ok (ctx_final, init_code @ [GcBlock (GcBtEmpty, - [GcLoop (GcBtEmpty, - [std (Wasm.LocalGet idx_local); - std (Wasm.LocalGet len_local); - std Wasm.I32GeS; std (Wasm.BrIf 1); - ] @ - body_code @ - [std Wasm.Drop; - std (Wasm.LocalGet idx_local); - push_i32 1; std Wasm.I32Add; - std (Wasm.LocalSet idx_local); - std (Wasm.Br 0)] - )] - )]) - end - -(** {1 Function codegen} *) - -(** Generate a GC function from an AffineScript function declaration. - - Parameter types are inferred from type annotations; parameters without - annotations default to [GcAnyref]. The return type is similarly - inferred from [fd_ret_ty]. *) -let gen_gc_function (ctx : gc_ctx) (fd : fn_decl) : (gc_ctx * gc_func) cg_result = - (* Reset function-level state, preserving all module-level state *) - let fn_ctx = { ctx with - locals = []; - local_kinds = []; - next_local = 0; - param_count = 0; - loop_depth = 0; - var_gc_type = []; - } in - - (* Allocate parameters as locals 0..n-1 *) - let (ctx_with_params, param_gc_types) = - List.fold_left (fun (c, pts) param -> - let vt = as_type_to_gc_valtype param.p_ty in - let (c', _) = alloc_local c param.p_name.name vt in - (c', pts @ [vt]) - ) (fn_ctx, []) fd.fd_params - in - let param_count = List.length fd.fd_params in - let ctx_params = { ctx_with_params with param_count } in - - (* Infer return type from annotation, defaulting to GcAnyref *) - let result_vt = match fd.fd_ret_ty with - | Some ty -> as_type_to_gc_valtype ty - | None -> GcAnyref - in - - (* Register the function's GC func type in the type section *) - let func_type_def = GcFuncType { - gft_params = param_gc_types; - gft_results = [result_vt]; - } in - let (ctx_with_ftype, type_idx) = register_gc_type ctx_params func_type_def in - - (* Generate function body *) - let body_expr = match fd.fd_body with - | FnBlock blk -> ExprBlock blk - | FnExpr e -> e - in - let* (ctx_after, body_code) = gen_gc_expr ctx_with_ftype body_expr in - - (* Collect extra locals (those declared beyond the parameters) *) - let extra_locals = - List.filter_map (fun (idx, vt) -> - if idx >= param_count then Some (1, vt) else None - ) (List.rev ctx_after.local_kinds) - (* rev: local_kinds is prepend-ordered; we want ascending index order *) - in - - let func = { - gf_type = type_idx; - gf_locals = extra_locals; - gf_body = body_code; - } in - - (* Restore module-level state from ctx_after; drop function-level state *) - let ctx_restored = { ctx_after with - locals = ctx.locals; - local_kinds = ctx.local_kinds; - next_local = ctx.next_local; - param_count = ctx.param_count; - loop_depth = ctx.loop_depth; - var_gc_type = ctx.var_gc_type; - } in - - Ok (ctx_restored, func) - -(** {1 Declaration codegen} *) - -(** Generate GC module entries from a top-level AffineScript declaration. - - - [TopFn]: generates a function, registers its type, adds export if applicable - - [TopType (TyStruct)]: registers a named GC struct type with field map - - [TopType (TyEnum)]: registers variant tags (as in the WASM 1.0 backend) - - Other declarations: no GC output *) -let gen_gc_decl (ctx : gc_ctx) (decl : top_level) : gc_ctx cg_result = - match decl with - - | TopFn fd -> - let func_idx = ctx.import_count + List.length ctx.gc_funcs_acc in - let ctx_with_idx = { - ctx with func_indices = (fd.fd_name.name, func_idx) :: ctx.func_indices - } in - let* (ctx', func) = gen_gc_function ctx_with_idx fd in - - let export_names = - ["main"; "init_state"; "step_state"; "get_state"; "mission_active"] - in - let new_exports = - if List.mem fd.fd_name.name export_names then - [{ ge_name = fd.fd_name.name; ge_desc = GcExportFunc func_idx }] - else [] - in - Ok { ctx' with - gc_funcs_acc = ctx'.gc_funcs_acc @ [func]; - gc_exports_acc = ctx'.gc_exports_acc @ new_exports; - } - - | TopType td -> - begin match td.td_body with - | TyStruct sf_list -> - (* Named struct: register with precise field types from annotations *) - let field_types = List.map (fun sf -> as_type_to_field_type sf.sf_ty) sf_list in - let (ctx', type_idx) = register_gc_type ctx (GcStructType field_types) in - let field_map = List.mapi (fun i sf -> (sf.sf_name.name, i)) sf_list in - Ok { ctx' with - struct_reg = (td.td_name.name, type_idx) :: ctx'.struct_reg; - field_reg = (td.td_name.name, field_map) :: ctx'.field_reg; - } - - | TyEnum variants -> - (* Variant tags — same sequential assignment as the WASM 1.0 backend *) - let ctx' = - List.fold_left (fun c (idx, vd) -> - { c with variant_tags = (vd.vd_name.name, idx) :: c.variant_tags } - ) ctx (List.mapi (fun i v -> (i, v)) variants) - in - Ok ctx' - - | TyAlias _ -> - (* Type aliases are purely type-level; no GC output *) - Ok ctx - end - - | TopConst _ | TopTrait _ | TopImpl _ -> - Ok ctx - - | TopEffect ed -> - (* Register each effect operation as an unreachable stub function. - This gives each op a valid func_indices entry (so direct calls at - least produce a trap rather than a link error), and correctly - offsets function indices for all subsequent definitions. - - Full effect handler dispatch requires either: - - The WASM exception-handling proposal (EH, standardised 2023) to - propagate perform-site continuations across stack frames, OR - - A whole-program CPS transform before codegen. - Neither is implemented in this backend yet. Use the interpreter - (`-i`) for programs that perform effects. - - All parameters are typed as GcAnyref (conservative): concrete types - are not yet propagated from the type-inference pass into codegen. *) - let ctx' = List.fold_left (fun ctx (op : effect_op_decl) -> - let n_params = List.length op.eod_params in - let func_type = GcFuncType { - gft_params = List.init n_params (fun _ -> GcAnyref); - gft_results = [GcAnyref]; - } in - let (ctx1, type_idx) = register_gc_type ctx func_type in - let func_idx = ctx1.import_count + List.length ctx1.gc_funcs_acc in - let stub : gc_func = { - gf_type = type_idx; - gf_locals = []; - (* Trap immediately: calling an effect op in WasmGC without a - handler dispatch mechanism is always a programming error. *) - gf_body = [Std Wasm.Unreachable]; - } in - { ctx1 with - func_indices = (op.eod_name.name, func_idx) :: ctx1.func_indices; - gc_funcs_acc = ctx1.gc_funcs_acc @ [stub]; - } - ) ctx ed.ed_ops in - Ok ctx' - -(** {1 Module-level entry point} *) - -(** Generate a {!Wasm_gc.gc_module} from an AffineScript program. - - This is called by the compiler CLI when [--wasm-gc] is present on the - [compile] subcommand. - - The generated module: - - Declares GC struct/array types for all records, tuples, and arrays - - Uses [struct.new] and [array.new_fixed] for allocation (no bump allocator) - - Has no linear memory section - - Exports the same function names as the WASM 1.0 backend - - Is compatible with V8 ≥ Chrome 119, SpiderMonkey ≥ Firefox 120, - and Wasmtime with [--wasm-features gc] *) -let generate_gc_module (prog : program) : (gc_module, codegen_error) Result.t = - let ctx = create_gc_ctx () in - - let* ctx' = - List.fold_left (fun acc decl -> - let* c = acc in - gen_gc_decl c decl - ) (Ok ctx) prog.prog_decls - in - - Ok { - gc_types = ctx'.gc_type_defs; - gc_imports = []; (* GC mode: no WASI; GC objects are not in linear memory *) - gc_funcs = ctx'.gc_funcs_acc; - gc_exports = ctx'.gc_exports_acc; - } - -(** Show a codegen error for CLI output. *) -let format_codegen_error (e : codegen_error) : string = - match e with - | UnsupportedFeature msg -> Printf.sprintf "GC codegen: unsupported feature: %s" msg - | UnboundVariable name -> Printf.sprintf "GC codegen: unbound variable: %s" name - | UnboundType name -> Printf.sprintf "GC codegen: unbound type: %s" name - | UnboundFunction name -> Printf.sprintf "GC codegen: function '%s' has no func_indices entry (compiler bug — register before codegen)" name diff --git a/affinescript/lib/desugar_traits.ml b/affinescript/lib/desugar_traits.ml deleted file mode 100644 index 75babff..0000000 --- a/affinescript/lib/desugar_traits.ml +++ /dev/null @@ -1,238 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Trait method desugaring - transforms trait method calls into direct function calls. - - This pass runs after type checking and transforms: - receiver.method(args) - into: - TypeName_TraitName_methodName(receiver, args) - - This enables monomorphized trait methods to be called correctly. -*) - -open Ast -open Types - -type context = { - type_defs : (string, ty) Hashtbl.t; - trait_registry : Trait.trait_registry; -} - -(** Extract type name from a type *) -let rec type_name_from_ty (ty : ty) : string option = - match repr ty with - | TCon name -> Some name - | TApp (TCon name, _) -> Some name - | TApp (ty', _) -> type_name_from_ty ty' - | _ -> None - -(** Desugar expression - transform trait method calls *) -let rec desugar_expr (ctx : context) (expr : expr) : expr = - match expr with - | ExprApp (func_expr, args) -> - begin match func_expr with - | ExprField (receiver, method_name) -> - (* This might be a trait method call - need to check types *) - (* For now, we'll use a heuristic: if the method name doesn't exist as a field, - it's likely a trait method. The type checker has already validated this. *) - - (* Try to determine the receiver type and find trait impl *) - (* Since we don't have type info here, we'll use a conservative approach: - Keep the original call structure but mark it for later optimization *) - - (* Recursively desugar receiver and args *) - let receiver' = desugar_expr ctx receiver in - let args' = List.map (desugar_expr ctx) args in - ExprApp (ExprField (receiver', method_name), args') - - | _ -> - (* Regular function call *) - let func' = desugar_expr ctx func_expr in - let args' = List.map (desugar_expr ctx) args in - ExprApp (func', args') - end - - | ExprVar _ | ExprLit _ -> expr - - | ExprBinary (e1, op, e2) -> - ExprBinary (desugar_expr ctx e1, op, desugar_expr ctx e2) - - | ExprUnary (op, e) -> - ExprUnary (op, desugar_expr ctx e) - - | ExprIf { ei_cond; ei_then; ei_else } -> - ExprIf { - ei_cond = desugar_expr ctx ei_cond; - ei_then = desugar_expr ctx ei_then; - ei_else = Option.map (desugar_expr ctx) ei_else; - } - - | ExprBlock blk -> - ExprBlock (desugar_block ctx blk) - - | ExprField (e, field) -> - ExprField (desugar_expr ctx e, field) - - | ExprIndex (e1, e2) -> - ExprIndex (desugar_expr ctx e1, desugar_expr ctx e2) - - | ExprTupleIndex (e, idx) -> - ExprTupleIndex (desugar_expr ctx e, idx) - - | ExprRecord { er_fields; er_spread } -> - ExprRecord { - er_fields = List.map (fun (id, e_opt) -> - (id, Option.map (desugar_expr ctx) e_opt) - ) er_fields; - er_spread = Option.map (desugar_expr ctx) er_spread; - } - - | ExprArray exprs -> - ExprArray (List.map (desugar_expr ctx) exprs) - - | ExprTuple exprs -> - ExprTuple (List.map (desugar_expr ctx) exprs) - - | ExprMatch { em_scrutinee; em_arms } -> - ExprMatch { - em_scrutinee = desugar_expr ctx em_scrutinee; - em_arms = List.map (fun arm -> - { arm with - ma_guard = Option.map (desugar_expr ctx) arm.ma_guard; - ma_body = desugar_expr ctx arm.ma_body; - } - ) em_arms; - } - - | ExprLambda { elam_params; elam_ret_ty; elam_body } -> - ExprLambda { - elam_params; - elam_ret_ty; - elam_body = desugar_expr ctx elam_body; - } - - | ExprLet { el_mut; el_quantity; el_pat; el_ty; el_value; el_body } -> - ExprLet { - el_mut; - el_quantity; - el_pat; - el_ty; - el_value = desugar_expr ctx el_value; - el_body = Option.map (desugar_expr ctx) el_body; - } - - | ExprReturn e_opt -> - ExprReturn (Option.map (desugar_expr ctx) e_opt) - - | ExprTry { et_body; et_catch; et_finally } -> - ExprTry { - et_body = desugar_block ctx et_body; - et_catch = Option.map (List.map (fun arm -> - { arm with - ma_guard = Option.map (desugar_expr ctx) arm.ma_guard; - ma_body = desugar_expr ctx arm.ma_body; - } - )) et_catch; - et_finally = Option.map (desugar_block ctx) et_finally; - } - - | ExprHandle { eh_body; eh_handlers } -> - ExprHandle { - eh_body = desugar_expr ctx eh_body; - eh_handlers = List.map (desugar_handler ctx) eh_handlers; - } - - | ExprResume e_opt -> - ExprResume (Option.map (desugar_expr ctx) e_opt) - - | ExprUnsafe ops -> - ExprUnsafe (List.map (desugar_unsafe_op ctx) ops) - - | ExprRowRestrict (e, field) -> - ExprRowRestrict (desugar_expr ctx e, field) - - | ExprVariant _ -> expr - - | ExprSpan (e, span) -> - ExprSpan (desugar_expr ctx e, span) - -and desugar_handler (ctx : context) (handler : handler_arm) : handler_arm = - match handler with - | HandlerReturn (pat, body) -> - HandlerReturn (pat, desugar_expr ctx body) - | HandlerOp (op, pats, body) -> - HandlerOp (op, pats, desugar_expr ctx body) - -and desugar_unsafe_op (ctx : context) (op : unsafe_op) : unsafe_op = - match op with - | UnsafeRead e -> UnsafeRead (desugar_expr ctx e) - | UnsafeWrite (e1, e2) -> UnsafeWrite (desugar_expr ctx e1, desugar_expr ctx e2) - | UnsafeOffset (e1, e2) -> UnsafeOffset (desugar_expr ctx e1, desugar_expr ctx e2) - | UnsafeTransmute (ty1, ty2, e) -> UnsafeTransmute (ty1, ty2, desugar_expr ctx e) - | UnsafeForget e -> UnsafeForget (desugar_expr ctx e) - -and desugar_block (ctx : context) (blk : block) : block = - { blk_stmts = List.map (desugar_stmt ctx) blk.blk_stmts; - blk_expr = Option.map (desugar_expr ctx) blk.blk_expr; - } - -and desugar_stmt (ctx : context) (stmt : stmt) : stmt = - match stmt with - | StmtLet { sl_mut; sl_quantity; sl_pat; sl_ty; sl_value } -> - StmtLet { - sl_mut; - sl_quantity; - sl_pat; - sl_ty; - sl_value = desugar_expr ctx sl_value; - } - - | StmtExpr e -> - StmtExpr (desugar_expr ctx e) - - | StmtAssign (lhs, op, rhs) -> - StmtAssign (desugar_expr ctx lhs, op, desugar_expr ctx rhs) - - | StmtWhile (cond, body) -> - StmtWhile (desugar_expr ctx cond, desugar_block ctx body) - - | StmtFor (pat, iter, body) -> - StmtFor (pat, desugar_expr ctx iter, desugar_block ctx body) - -let desugar_function (ctx : context) (fd : fn_decl) : fn_decl = - match fd.fd_body with - | FnBlock blk -> - { fd with fd_body = FnBlock (desugar_block ctx blk) } - | FnExpr e -> - { fd with fd_body = FnExpr (desugar_expr ctx e) } - -let desugar_top_level (ctx : context) (top : top_level) : top_level = - match top with - | TopFn fd -> - TopFn (desugar_function ctx fd) - - | TopImpl ib -> - (* Desugar method implementations *) - let items' = List.map (fun item -> - match item with - | ImplFn fd -> ImplFn (desugar_function ctx fd) - | ImplType _ as it -> it - ) ib.ib_items in - TopImpl { ib with ib_items = items' } - - | TopConst { tc_vis; tc_name; tc_ty; tc_value } -> - TopConst { - tc_vis; - tc_name; - tc_ty; - tc_value = desugar_expr ctx tc_value; - } - - | TopType _ | TopEffect _ | TopTrait _ as d -> d - -let desugar_program (type_defs : (string, ty) Hashtbl.t) - (trait_registry : Trait.trait_registry) - (prog : program) : program = - let ctx = { type_defs; trait_registry } in - { prog with prog_decls = List.map (desugar_top_level ctx) prog.prog_decls } diff --git a/affinescript/lib/dune b/affinescript/lib/dune deleted file mode 100644 index 26b8b37..0000000 --- a/affinescript/lib/dune +++ /dev/null @@ -1,28 +0,0 @@ -; Face stack rewired 2026-04-19: -; - face.ml + face.mli now match the ADR-010 spec (face type, -; face_error_vocab, detect_face — no AST-level dispatch). -; - python_face.ml is a source-level text preprocessor per the -; spec; exposes parse_string_python, parse_file_python, -; preview_transform (used by test/test_e2e.ml §11). -; - javascript_face / pseudocode_face deleted: their previous -; content was WIP stubs against a non-existent AST and no test -; exercised them. Face slots in `type face` remain for CLI -; dispatch when the preprocessors are written. -; -; `backends.ml` and `lib/backends/` were deleted (b981f90). -(library - (name affinescript) - (libraries sedlex menhirLib yojson unix str) - ; ppx_jane dropped: code only uses [@@deriving show, eq, ord], all - ; provided by ppx_deriving plugins. ppx_jane transitively pulled in - ; ppx_bin_prot / bin_prot whose runtime.js fires `joo_global_object` - ; deprecation warnings under js_of_ocaml 6.2. Narrower preprocess - ; list removes that closure. - (preprocess (pps ppx_deriving.show ppx_deriving.eq ppx_deriving.ord sedlex.ppx)) - (flags :standard -w -A)) - -; parser.mly -> module Parser generated by menhir, consumed by -; parse_driver.ml and parse.ml (via MenhirInterpreter). -(menhir - (modules parser) - (flags --table)) diff --git a/affinescript/lib/effect.ml b/affinescript/lib/effect.ml deleted file mode 100644 index f8823b0..0000000 --- a/affinescript/lib/effect.ml +++ /dev/null @@ -1,122 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Effect system operations *) - -open Types - -(** Combine two effects into a union *) -let union_eff (e1 : eff) (e2 : eff) : eff = - let e1 = repr_eff e1 in - let e2 = repr_eff e2 in - match (e1, e2) with - | (EPure, e) | (e, EPure) -> e - | (e1', e2') when e1' = e2' -> e1' - | (EUnion es1, EUnion es2) -> - (* Flatten and deduplicate *) - let all_effs = es1 @ es2 in - let unique = List.sort_uniq compare all_effs in - if List.length unique = 1 then List.hd unique - else EUnion unique - | (EUnion es, e) | (e, EUnion es) -> - if List.mem e es then EUnion es - else EUnion (List.sort_uniq compare (e :: es)) - | (e1', e2') -> - EUnion (List.sort_uniq compare [e1'; e2']) - -(** Union a list of effects *) -let union_effs (effs : eff list) : eff = - List.fold_left union_eff EPure effs - -(** Check if an effect is pure *) -let is_pure (e : eff) : bool = - match repr_eff e with - | EPure -> true - | _ -> false - -(** Effect subtyping: e1 <: e2 means e1 can be used where e2 is expected - - Pure can be used anywhere (Pure <: any effect) - - IO context can use Pure or IO (both Pure <: IO and IO <: IO) - - Pure context can only use Pure (IO is NOT <: Pure) *) -let rec eff_subsumes (e1 : eff) (e2 : eff) : bool = - let e1 = repr_eff e1 in - let e2 = repr_eff e2 in - match (e1, e2) with - (* Any effect can accommodate Pure *) - | (_, EPure) -> true - (* Pure cannot accommodate other effects *) - | (EPure, _) -> false - (* Same effect *) - | (e1', e2') when e1' = e2' -> true - (* e1 is a union, e1 subsumes e2 if any effect in e1 subsumes e2 *) - | (EUnion es, e2') -> - List.exists (fun e -> eff_subsumes e e2') es - (* e2 is a union, e1 must subsume all effects in e2 *) - | (e1', EUnion es) -> - List.for_all (fun e -> eff_subsumes e1' e) es - (* Otherwise no subtyping relationship *) - | _ -> false - -(** Check if effect e1 is a subset of e2 (all effects in e1 are in e2) *) -let rec eff_subset (e1 : eff) (e2 : eff) : bool = - let e1 = repr_eff e1 in - let e2 = repr_eff e2 in - match (e1, e2) with - | (EPure, _) -> true - | (_, EPure) -> false - | (ESingleton s1, ESingleton s2) -> s1 = s2 - | (ESingleton s, EUnion es) -> - List.exists (fun e -> - match repr_eff e with - | ESingleton s' -> s = s' - | _ -> false - ) es - | (EUnion es1, EUnion es2) -> - List.for_all (fun e1' -> - List.exists (fun e2' -> - match (repr_eff e1', repr_eff e2') with - | (ESingleton s1, ESingleton s2) -> s1 = s2 - | _ -> e1' = e2' - ) es2 - ) es1 - | (EUnion es, e2') -> - List.for_all (fun e -> eff_subset e e2') es - | (e1', e2') -> e1' = e2' - -(** Normalize an effect (flatten unions, remove duplicates, sort) *) -let normalize_eff (e : eff) : eff = - let rec collect_singletons (e : eff) : string list = - match repr_eff e with - | EPure -> [] - | ESingleton s -> [s] - | EUnion es -> List.concat_map collect_singletons es - | EVar _ -> [] (* Variables can't be normalized *) - in - - match repr_eff e with - | EPure -> EPure - | ESingleton _ as e' -> e' - | EVar _ as e' -> e' - | EUnion _ as e' -> - let singletons = collect_singletons e' in - let unique = List.sort_uniq String.compare singletons in - match unique with - | [] -> EPure - | [s] -> ESingleton s - | ss -> EUnion (List.map (fun s -> ESingleton s) ss) - -(** Pretty print effect *) -let string_of_eff (e : eff) : string = - let rec aux (e : eff) : string = - match repr_eff e with - | EPure -> "pure" - | ESingleton s -> s - | EUnion es -> - String.concat " | " (List.map aux es) - | EVar r -> - begin match !r with - | EUnbound (v, _) -> Printf.sprintf "'e%d" v - | ELink e' -> aux e' - end - in - aux (normalize_eff e) diff --git a/affinescript/lib/effect.mli b/affinescript/lib/effect.mli deleted file mode 100644 index e10340c..0000000 --- a/affinescript/lib/effect.mli +++ /dev/null @@ -1,28 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Effect system operations *) - -open Types - -(** Combine two effects into a union *) -val union_eff : eff -> eff -> eff - -(** Union a list of effects *) -val union_effs : eff list -> eff - -(** Check if an effect is pure *) -val is_pure : eff -> bool - -(** Effect subtyping: e1 <: e2 means e1 can be used where e2 is expected - Pure is a subtype of any effect *) -val eff_subsumes : eff -> eff -> bool - -(** Check if effect e1 is a subset of e2 *) -val eff_subset : eff -> eff -> bool - -(** Normalize an effect (flatten unions, remove duplicates, sort) *) -val normalize_eff : eff -> eff - -(** Pretty print effect *) -val string_of_eff : eff -> string diff --git a/affinescript/lib/error.ml b/affinescript/lib/error.ml deleted file mode 100644 index 283cd0a..0000000 --- a/affinescript/lib/error.ml +++ /dev/null @@ -1,218 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Error handling and diagnostics for AffineScript *) - -(** Error severity levels *) -type severity = - | Error - | Warning - | Info - | Hint -[@@deriving show, eq] - -(** Error codes *) -type code = - (* Lexer errors: E0001-E0099 *) - | E0001 (** Unexpected character *) - | E0002 (** Unterminated string *) - | E0003 (** Unterminated comment *) - | E0004 (** Invalid escape sequence *) - | E0005 (** Invalid character literal *) - - (* Parser errors: E0100-E0199 *) - | E0101 (** Unexpected token *) - | E0102 (** Expected expression *) - | E0103 (** Expected type *) - | E0104 (** Expected pattern *) - | E0105 (** Expected identifier *) - | E0106 (** Unclosed delimiter *) - - (* Name resolution errors: E0200-E0299 *) - | E0201 (** Undefined variable *) - | E0202 (** Undefined type *) - | E0203 (** Undefined module *) - | E0204 (** Duplicate definition *) - | E0205 (** Private item *) - - (* Type errors: E0300-E0399 *) - | E0301 (** Type mismatch *) - | E0302 (** Cannot infer type *) - | E0303 (** Invalid type application *) - | E0304 (** Missing type annotation *) - | E0305 (** Refinement not satisfied *) - | E0306 (** Kind mismatch *) - | E0307 (** Row field missing *) - | E0308 (** Row field duplicate *) - - (* Trait errors: E0400-E0499 *) - | E0401 (** Trait not implemented *) - | E0402 (** Missing trait method *) - | E0403 (** Conflicting implementations *) - | E0404 (** Orphan implementation *) - - (* Ownership errors: E0500-E0599 *) - | E0501 (** Use after move *) - | E0502 (** Cannot borrow as mutable *) - | E0503 (** Cannot borrow while mutable borrow exists *) - | E0504 (** Value does not live long enough *) - | E0505 (** Cannot move out of borrowed reference *) - | E0506 (** Linearity violation *) - - (* Effect errors: E0600-E0699 *) - | E0601 (** Effect not handled *) - | E0602 (** Effect mismatch *) - | E0603 (** Invalid effect in total function *) - - (* Totality errors: E0700-E0799 *) - | E0701 (** Non-terminating recursion *) - | E0702 (** Non-exhaustive patterns *) - - (* Warnings: W0001-W0999 *) - | W0001 (** Unused variable *) - | W0002 (** Unused import *) - | W0003 (** Unreachable code *) - | W0004 (** Deprecated feature *) - | W0601 (** Owned resource may leak on exception *) - | W0701 (** Effectful refinement predicate *) -[@@deriving show, eq] - -(** Convert error code to string *) -let code_to_string = function - | E0001 -> "E0001" | E0002 -> "E0002" | E0003 -> "E0003" - | E0004 -> "E0004" | E0005 -> "E0005" - | E0101 -> "E0101" | E0102 -> "E0102" | E0103 -> "E0103" - | E0104 -> "E0104" | E0105 -> "E0105" | E0106 -> "E0106" - | E0201 -> "E0201" | E0202 -> "E0202" | E0203 -> "E0203" - | E0204 -> "E0204" | E0205 -> "E0205" - | E0301 -> "E0301" | E0302 -> "E0302" | E0303 -> "E0303" - | E0304 -> "E0304" | E0305 -> "E0305" | E0306 -> "E0306" - | E0307 -> "E0307" | E0308 -> "E0308" - | E0401 -> "E0401" | E0402 -> "E0402" | E0403 -> "E0403" - | E0404 -> "E0404" - | E0501 -> "E0501" | E0502 -> "E0502" | E0503 -> "E0503" - | E0504 -> "E0504" | E0505 -> "E0505" | E0506 -> "E0506" - | E0601 -> "E0601" | E0602 -> "E0602" | E0603 -> "E0603" - | E0701 -> "E0701" | E0702 -> "E0702" - | W0001 -> "W0001" | W0002 -> "W0002" | W0003 -> "W0003" - | W0004 -> "W0004" | W0601 -> "W0601" | W0701 -> "W0701" - -(** A labeled span for error messages *) -type label = { - span : Span.t; - message : string; - primary : bool; (** Is this the primary span? *) -} - -(** A diagnostic message *) -type diagnostic = { - severity : severity; - code : code; - message : string; - labels : label list; - notes : string list; - help : string list; -} - -(** Create a new error *) -let error ~code ~message ~span = - { - severity = Error; - code; - message; - labels = [{ span; message = ""; primary = true }]; - notes = []; - help = []; - } - -(** Create a new warning *) -let warning ~code ~message ~span = - { - severity = Warning; - code; - message; - labels = [{ span; message = ""; primary = true }]; - notes = []; - help = []; - } - -(** Add a secondary label *) -let with_label ~span ~message diag = - { diag with labels = diag.labels @ [{ span; message; primary = false }] } - -(** Add a note *) -let with_note note diag = - { diag with notes = diag.notes @ [note] } - -(** Add help text *) -let with_help help diag = - { diag with help = diag.help @ [help] } - -(** Format a diagnostic for terminal output *) -let format_diagnostic ~source diag = - let severity_str = match diag.severity with - | Error -> "\027[1;31merror\027[0m" - | Warning -> "\027[1;33mwarning\027[0m" - | Info -> "\027[1;34minfo\027[0m" - | Hint -> "\027[1;36mhint\027[0m" - in - let code_str = code_to_string diag.code in - let buf = Buffer.create 256 in - - (* Header *) - Buffer.add_string buf (Printf.sprintf "%s[%s]: %s\n" severity_str code_str diag.message); - - (* Labels *) - List.iter (fun label -> - let span = label.span in - Buffer.add_string buf (Printf.sprintf " --> %s:%d:%d\n" - span.file span.start_pos.line span.start_pos.col); - - (* Show source line if available *) - (match source with - | Some src -> - let lines = String.split_on_char '\n' src in - if span.start_pos.line > 0 && span.start_pos.line <= List.length lines then begin - let line = List.nth lines (span.start_pos.line - 1) in - let line_num = string_of_int span.start_pos.line in - let padding = String.make (String.length line_num) ' ' in - Buffer.add_string buf (Printf.sprintf " %s |\n" padding); - Buffer.add_string buf (Printf.sprintf " %s | %s\n" line_num line); - Buffer.add_string buf (Printf.sprintf " %s | %s%s" - padding - (String.make (max 0 (span.start_pos.col - 1)) ' ') - (String.make (max 1 (span.end_pos.col - span.start_pos.col)) '^')); - if label.message <> "" then - Buffer.add_string buf (Printf.sprintf " %s" label.message); - Buffer.add_char buf '\n'; - end - | None -> ()); - ) diag.labels; - - (* Notes *) - List.iter (fun note -> - Buffer.add_string buf (Printf.sprintf " = note: %s\n" note) - ) diag.notes; - - (* Help *) - List.iter (fun help -> - Buffer.add_string buf (Printf.sprintf " = help: %s\n" help) - ) diag.help; - - Buffer.contents buf - -(** Diagnostic collector *) -type collector = { - mutable diagnostics : diagnostic list; - mutable has_errors : bool; -} - -let create_collector () = { diagnostics = []; has_errors = false } - -let emit collector diag = - collector.diagnostics <- collector.diagnostics @ [diag]; - if diag.severity = Error then collector.has_errors <- true - -let has_errors collector = collector.has_errors - -let get_diagnostics collector = collector.diagnostics diff --git a/affinescript/lib/error_collector.ml b/affinescript/lib/error_collector.ml deleted file mode 100644 index 70d27ae..0000000 --- a/affinescript/lib/error_collector.ml +++ /dev/null @@ -1,121 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Error collection for reporting multiple errors *) - -(** Error severity levels *) -type severity = - | Error - | Warning - | Note - -(** A collected error or warning *) -type collected_error = { - severity : severity; - code : string option; - location : Span.t; - message : string; - help : string option; -} - -(** Error collector *) -type t = { - mutable errors : collected_error list; - mutable max_errors : int; -} - -(** Create a new error collector *) -let create ?(max_errors = 10) () : t = - { errors = []; max_errors } - -(** Add an error to the collector *) -let add_error - ?(code : string option) - ?(help : string option) - (collector : t) - (location : Span.t) - (message : string) - : unit = - if List.length collector.errors < collector.max_errors then - let err = { - severity = Error; - code; - location; - message; - help; - } in - collector.errors <- err :: collector.errors - -(** Add a warning to the collector *) -let add_warning - ?(code : string option) - ?(help : string option) - (collector : t) - (location : Span.t) - (message : string) - : unit = - let warn = { - severity = Warning; - code; - location; - message; - help; - } in - collector.errors <- warn :: collector.errors - -(** Check if there are any errors *) -let has_errors (collector : t) : bool = - List.exists (fun e -> e.severity = Error) collector.errors - -(** Get all collected errors *) -let get_errors (collector : t) : collected_error list = - List.rev collector.errors - -(** Get error count *) -let error_count (collector : t) : int = - List.length (List.filter (fun e -> e.severity = Error) collector.errors) - -(** Get warning count *) -let warning_count (collector : t) : int = - List.length (List.filter (fun e -> e.severity = Warning) collector.errors) - -(** Format severity as string *) -let severity_to_string = function - | Error -> "error" - | Warning -> "warning" - | Note -> "note" - -(** Format all collected errors *) -let format_all (collector : t) : string = - let errors = get_errors collector in - let formatted = List.map (fun err -> - let span_str = Format.asprintf "%a" Span.pp_short err.location in - Error_formatter.format_error - ?code:err.code - ~severity:(severity_to_string err.severity) - span_str - (match err.help with - | Some h -> err.message ^ "\n " ^ Error_formatter.colorize Error_formatter.Green ("help: " ^ h) - | None -> err.message) - ) errors in - String.concat "\n\n" formatted - -(** Format summary line *) -let format_summary (collector : t) : string = - let err_count = error_count collector in - let warn_count = warning_count collector in - - let parts = [] in - let parts = if err_count > 0 then - (Error_formatter.colorize Error_formatter.Red - (Printf.sprintf "%d error%s" err_count (if err_count = 1 then "" else "s"))) :: parts - else parts in - let parts = if warn_count > 0 then - (Error_formatter.colorize Error_formatter.Yellow - (Printf.sprintf "%d warning%s" warn_count (if warn_count = 1 then "" else "s"))) :: parts - else parts in - - if parts = [] then - Error_formatter.colorize Error_formatter.Green "No errors" - else - String.concat ", " (List.rev parts) diff --git a/affinescript/lib/error_formatter.ml b/affinescript/lib/error_formatter.ml deleted file mode 100644 index 93e4eee..0000000 --- a/affinescript/lib/error_formatter.ml +++ /dev/null @@ -1,102 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Error formatting with ANSI colors for terminal output *) - -(** ANSI color codes *) -type color = - | Red - | Yellow - | Blue - | Green - | Cyan - | Magenta - | Bold - | Reset - -(** Convert color to ANSI code *) -let color_code = function - | Red -> "\027[31m" - | Yellow -> "\027[33m" - | Blue -> "\027[34m" - | Green -> "\027[32m" - | Cyan -> "\027[36m" - | Magenta -> "\027[35m" - | Bold -> "\027[1m" - | Reset -> "\027[0m" - -(** Check if terminal supports colors *) -let supports_color () = - try - let term = Sys.getenv "TERM" in - not (term = "dumb" || term = "") - with Not_found -> false - -(** Colorize text if terminal supports it *) -let colorize (c : color) (text : string) : string = - if supports_color () then - color_code c ^ text ^ color_code Reset - else - text - -(** Format error severity level with color *) -let format_severity (severity : string) : string = - match severity with - | "error" -> colorize Red (colorize Bold "error") - | "warning" -> colorize Yellow (colorize Bold "warning") - | "note" -> colorize Cyan (colorize Bold "note") - | "help" -> colorize Green (colorize Bold "help") - | _ -> severity - -(** Format file location with color *) -let format_location (loc : string) : string = - colorize Bold (colorize Cyan loc) - -(** Format error code *) -let format_error_code (code : string) : string = - colorize Magenta ("[" ^ code ^ "]") - -(** Format a complete error message with colors *) -let format_error - ?code - ?(severity : string = "error") - (location : string) - (message : string) - : string = - let code_str = match code with - | Some c -> format_error_code c ^ " " - | None -> "" - in - let sev = format_severity severity in - let loc = format_location location in - Printf.sprintf "%s %s%s: %s" loc code_str sev message - -(** Format a multi-line error with source context *) -let format_error_with_context - ?code - ?(severity : string = "error") - ?help - (location : string) - (message : string) - (source_line : string option) - (col : int option) - : string = - let header = format_error ?code ~severity location message in - - match source_line, col with - | Some line, Some c -> - (* Show source context with caret *) - let padding = String.make (c - 1) ' ' in - let caret = colorize Red "^" in - let context = Printf.sprintf "\n %s\n %s%s" line padding caret in - let help_text = match help with - | Some h -> "\n " ^ colorize Green ("help: " ^ h) - | None -> "" - in - header ^ context ^ help_text - | _ -> - let help_text = match help with - | Some h -> "\n " ^ colorize Green ("help: " ^ h) - | None -> "" - in - header ^ help_text diff --git a/affinescript/lib/error_formatter.mli b/affinescript/lib/error_formatter.mli deleted file mode 100644 index c247457..0000000 --- a/affinescript/lib/error_formatter.mli +++ /dev/null @@ -1,46 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Error formatting with ANSI colors for terminal output *) - -(** ANSI color codes *) -type color = - | Red - | Yellow - | Blue - | Green - | Cyan - | Magenta - | Bold - | Reset - -(** Colorize text if terminal supports it *) -val colorize : color -> string -> string - -(** Format error severity level with color *) -val format_severity : string -> string - -(** Format file location with color *) -val format_location : string -> string - -(** Format error code *) -val format_error_code : string -> string - -(** Format a complete error message with colors *) -val format_error : - ?code:string -> - ?severity:string -> - string -> - string -> - string - -(** Format a multi-line error with source context *) -val format_error_with_context : - ?code:string -> - ?severity:string -> - ?help:string -> - string -> - string -> - string option -> - int option -> - string diff --git a/affinescript/lib/face.ml b/affinescript/lib/face.ml deleted file mode 100644 index 03157f7..0000000 --- a/affinescript/lib/face.ml +++ /dev/null @@ -1,54 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** face.ml — face selection, detection, and error-vocabulary - - Per ADR-010 / docs/specs/faces.md, a *face* is an alternative - surface presentation of AffineScript. There are two face-aware - layers in the compiler and this module is the second: - - 1. Source preprocessor (e.g. lib/python_face.ml — text → - canonical text), invoked by the CLI and LSP before lex+parse. - 2. Error formatter (this module) — maps canonical structured - errors to face-specific display strings. - - The compiler proper is face-agnostic. No face should ever touch - the AST or the type-checker. *) - -(** Available faces for the compiler's user-facing surface. - - [Canonical] is the default AffineScript surface syntax; the other - faces are sugar layers over it. JS and Pseudocode are listed for - the CLI's [--face] enum but their preprocessors have not shipped - — only the error vocabulary is face-aware. *) -type face = - | Python - | Pseudocode - | JavaScript - | Canonical - -(** [face_error_vocab face] returns [(display_name, short_tag)] for - the given face. [display_name] appears in user-facing error - prose ("Python syntax error"); [short_tag] appears in error codes - and log prefixes ("[rattle]"). *) -let face_error_vocab (face : face) : string * string = - match face with - | Python -> ("Python", "rattle") - | Pseudocode -> ("Pseudocode", "pseudo") - | JavaScript -> ("JavaScript", "jaffa") - | Canonical -> ("AffineScript", "affinescript") - -(** [detect_face filename] guesses the face from a filename's - extension. Mapping matches the [--face] flag aliases documented - in docs/specs/faces.md. *) -let detect_face (filename : string) : face = - if Filename.check_suffix filename ".rattle" - || Filename.check_suffix filename ".pyaff" - then Python - else if Filename.check_suffix filename ".pseudo" - || Filename.check_suffix filename ".pseudoaff" - then Pseudocode - else if Filename.check_suffix filename ".jaffa" - || Filename.check_suffix filename ".jsaff" - then JavaScript - else Canonical diff --git a/affinescript/lib/face.mli b/affinescript/lib/face.mli deleted file mode 100644 index 927c1a2..0000000 --- a/affinescript/lib/face.mli +++ /dev/null @@ -1,25 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** face.mli — face selection, detection, and error-vocabulary. - - See face.ml for the ADR-010 / docs/specs/faces.md context. The - compiler is face-agnostic; the two face-aware layers are the - source preprocessors (e.g. [Python_face.parse_string_python]) and - this module's error vocabulary. *) - -type face = - | Python - | Pseudocode - | JavaScript - | Canonical - -(** [face_error_vocab face] returns [(display_name, short_tag)] for - the given face. Example: [Python -> ("Python", "rattle")]. *) -val face_error_vocab : face -> string * string - -(** [detect_face filename] guesses the face from the filename's - extension: [.pyaff]/[.rattle] -> Python, [.pseudo]/[.pseudoaff] - -> Pseudocode, [.jaffa]/[.jsaff] -> JavaScript, anything else -> - Canonical. *) -val detect_face : string -> face diff --git a/affinescript/lib/formatter.ml b/affinescript/lib/formatter.ml deleted file mode 100644 index 7242fbe..0000000 --- a/affinescript/lib/formatter.ml +++ /dev/null @@ -1,9 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* AffineScript Source Code Formatter - Stub Implementation - - TODO(#84): Implement code formatting. -*) - -(** Format a source file - STUB(#84) *) -let format_file (_path : string) : unit = - failwith "Code formatting not yet implemented" diff --git a/affinescript/lib/formatter.mli.disabled b/affinescript/lib/formatter.mli.disabled deleted file mode 100644 index 3140fe6..0000000 --- a/affinescript/lib/formatter.mli.disabled +++ /dev/null @@ -1,22 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** AffineScript code formatter *) - -(** Formatting configuration *) -type config = { - indent_size: int; - max_line_length: int; - space_before_colon: bool; - space_after_colon: bool; - space_around_operators: bool; -} - -(** Default formatting configuration *) -val default_config : config - -(** Format a program to a string *) -val format_to_string : Ast.program -> string - -(** Format a file in-place *) -val format_file : string -> unit diff --git a/affinescript/lib/interp.ml b/affinescript/lib/interp.ml deleted file mode 100644 index a01b787..0000000 --- a/affinescript/lib/interp.ml +++ /dev/null @@ -1,1043 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Tree-walking interpreter for AffineScript. - - This module implements a big-step operational semantics - interpreter with affine type checking at runtime. -*) - -open Ast -open Value - -(** Result bind operator *) -let ( let* ) = Result.bind - -(** Evaluate a literal *) -let eval_literal (lit : literal) : value = - match lit with - | LitUnit _ -> VUnit - | LitBool (b, _) -> VBool b - | LitInt (n, _) -> VInt n - | LitFloat (f, _) -> VFloat f - | LitChar (c, _) -> VChar c - | LitString (s, _) -> VString s - -(** Match a pattern against a value, returning bindings *) -let rec match_pattern (pat : pattern) (v : value) : (string * value) list result = - match pat with - | PatWildcard _ -> Ok [] - | PatVar id -> Ok [(id.name, v)] - | PatLit lit -> - let expected = eval_literal lit in - if value_eq v expected then Ok [] - else Error PatternMatchFailure - - | PatTuple pats -> - begin match v with - | VTuple vs when List.length pats = List.length vs -> - List.fold_left2 (fun acc pat v -> - let* bindings = acc in - let* new_bindings = match_pattern pat v in - Ok (new_bindings @ bindings) - ) (Ok []) pats vs - | _ -> Error PatternMatchFailure - end - - | PatRecord (fields, _has_rest) -> - begin match v with - | VRecord record_fields -> - List.fold_left (fun acc (field_id, pat_opt) -> - let* bindings = acc in - match get_field field_id.name record_fields with - | Ok field_val -> - begin match pat_opt with - | Some p -> - let* new_bindings = match_pattern p field_val in - Ok (new_bindings @ bindings) - | None -> - Ok ((field_id.name, field_val) :: bindings) - end - | Error _ -> Error PatternMatchFailure - ) (Ok []) fields - | _ -> Error PatternMatchFailure - end - - | PatCon (con, pats) -> - begin match v with - | VVariant (tag, val_opt) when tag = con.name -> - begin match (pats, val_opt) with - | ([], None) -> Ok [] - | ([pat], Some val_) -> match_pattern pat val_ - | (pats, Some (VTuple vs)) when List.length pats = List.length vs -> - List.fold_left2 (fun acc pat v -> - let* bindings = acc in - let* new_bindings = match_pattern pat v in - Ok (new_bindings @ bindings) - ) (Ok []) pats vs - | _ -> Error PatternMatchFailure - end - | _ -> Error PatternMatchFailure - end - - | PatOr (p1, p2) -> - begin match match_pattern p1 v with - | Ok bindings -> Ok bindings - | Error _ -> match_pattern p2 v - end - - | PatAs (id, pat) -> - let* bindings = match_pattern pat v in - Ok ((id.name, v) :: bindings) - -(** Evaluate an expression *) -let rec eval (env : env) (expr : expr) : value result = - match expr with - | ExprLit lit -> Ok (eval_literal lit) - - | ExprVar id -> lookup_env id.name env - - | ExprLet lb -> - let* rhs_val = eval env lb.el_value in - let* bindings = match_pattern lb.el_pat rhs_val in - let env' = extend_env_list bindings env in - begin match lb.el_body with - | Some body -> eval env' body - | None -> Ok VUnit - end - - | ExprIf ei -> - let* cond_val = eval env ei.ei_cond in - if is_truthy cond_val then - eval env ei.ei_then - else begin - match ei.ei_else with - | Some else_expr -> eval env else_expr - | None -> Ok VUnit - end - - | ExprMatch em -> - let* scrut_val = eval env em.em_scrutinee in - eval_match_arms env scrut_val em.em_arms - - | ExprLambda lam -> - Ok (VClosure { - cl_params = lam.elam_params; - cl_body = lam.elam_body; - cl_env = env; - }) - - | ExprApp (func, args) -> - let* func_val = eval env func in - let* arg_vals = eval_list env args in - apply_function func_val arg_vals - - | ExprBinary (left, op, right) -> - let* left_val = eval env left in - let* right_val = eval env right in - eval_binop op left_val right_val - - | ExprUnary (op, operand) -> - let* operand_val = eval env operand in - unary_op op operand_val - - | ExprTuple exprs -> - let* vals = eval_list env exprs in - Ok (VTuple vals) - - | ExprArray exprs -> - let* vals = eval_list env exprs in - Ok (VArray (Array.of_list vals)) - - | ExprRecord er -> - (* Start with spread base if present *) - let* base_fields = match er.er_spread with - | Some spread_expr -> - let* spread_val = eval env spread_expr in - begin match spread_val with - | VRecord fields -> Ok fields - | _ -> Error (TypeMismatch "Spread operator requires a record") - end - | None -> Ok [] - in - let* field_vals = List.fold_right (fun (id, expr_opt) acc -> - let* fields = acc in - match expr_opt with - | Some e -> - let* v = eval env e in - Ok ((id.name, v) :: fields) - | None -> - (* Punning: {x} is short for {x: x} *) - let* v = lookup_env id.name env in - Ok ((id.name, v) :: fields) - ) er.er_fields (Ok []) in - (* Merge: explicit fields override spread fields *) - let explicit_names = List.map fst field_vals in - let remaining_base = List.filter (fun (n, _) -> - not (List.mem n explicit_names) - ) base_fields in - Ok (VRecord (field_vals @ remaining_base)) - - | ExprField (base, field) -> - let* base_val = eval env base in - begin match base_val with - | VRecord fields -> get_field field.name fields - | _ -> Error (TypeMismatch "Expected record") - end - - | ExprTupleIndex (base, idx) -> - let* base_val = eval env base in - begin match base_val with - | VTuple elems -> get_tuple_elem idx elems - | _ -> Error (TypeMismatch "Expected tuple") - end - - | ExprIndex (arr, idx_expr) -> - let* arr_val = eval env arr in - let* idx_val = eval env idx_expr in - begin match (arr_val, idx_val) with - | (VArray arr, VInt idx) -> get_array_elem idx arr - | (VArray _, _) -> Error (TypeMismatch "Array index must be integer") - | _ -> Error (TypeMismatch "Expected array") - end - - | ExprBlock blk -> - eval_block env blk - - | ExprReturn e_opt -> - begin match e_opt with - | Some e -> eval env e - | None -> Ok VUnit - end - - | ExprVariant (type_id, variant_id) -> - (* Type::Variant syntax - just the variant constructor without payload *) - let _ = type_id in (* Ignore type part for now *) - Ok (VVariant (variant_id.name, None)) - - | ExprRowRestrict (base, field) -> - let* base_val = eval env base in - begin match base_val with - | VRecord fields -> - let filtered = List.filter (fun (n, _) -> n <> field.name) fields in - Ok (VRecord filtered) - | _ -> Error (TypeMismatch "Row restriction requires a record") - end - - | ExprHandle eh -> - (* Evaluate the body expression. If it performs an effect via - PerformEffect, we match against the handler arms. - HandlerReturn arms match the normal return value. - HandlerOp arms match performed effects. *) - begin match eval env eh.eh_body with - | Ok v -> - (* Normal return — look for a HandlerReturn arm *) - let return_arm = List.find_opt (fun arm -> - match arm with HandlerReturn _ -> true | _ -> false - ) eh.eh_handlers in - begin match return_arm with - | Some (HandlerReturn (pat, body)) -> - let* bindings = match_pattern pat v in - let env' = extend_env_list bindings env in - eval env' body - | _ -> Ok v (* No return handler — pass through *) - end - | Error (PerformEffect (op_name, args)) -> - (* Effect performed — find matching HandlerOp arm *) - let op_arm = List.find_opt (fun arm -> - match arm with - | HandlerOp (id, _, _) -> id.name = op_name - | _ -> false - ) eh.eh_handlers in - begin match op_arm with - | Some (HandlerOp (_, pats, body)) -> - (* Build the resume continuation. In this tree-walking interpreter - the continuation is shallow: calling resume(v) returns v as the - result of the entire `handle` expression. This is correct for - the common single-shot, tail-resume pattern. Full multi-shot - continuations require either OCaml 5 effects or a CPS transform. *) - let resume_fn = VBuiltin ("__resume__", fun resume_args -> - match resume_args with - | [v] -> Ok v - | [] -> Ok VUnit - | vs -> Ok (VTuple vs) - ) in - (* Bind effect argument values to handler patterns. - Convention: all declared params first, then the continuation as - the last pattern. Pass args flat (not wrapped in a tuple) so - that multi-arg effects bind correctly to separate patterns. *) - let all_vals = args @ [resume_fn] in - let n_pats = List.length pats in - let n_vals = List.length all_vals in - (* If the handler omits the continuation param, provide it anyway - so that ExprResume still works via the __resume__ env slot. *) - let trimmed_vals = List.filteri (fun i _ -> i < n_pats) all_vals in - let pad_vals = - if n_vals < n_pats then - trimmed_vals @ List.init (n_pats - n_vals) (fun _ -> VUnit) - else trimmed_vals - in - let bindings = List.fold_left2 (fun acc pat v -> - match acc with - | Ok bs -> - begin match match_pattern pat v with - | Ok new_bs -> Ok (new_bs @ bs) - | Error e -> Error e - end - | Error e -> Error e - ) (Ok []) pats pad_vals in - let* bindings = bindings in - (* Also bind the resume fn under "__resume__" so that the - `ExprResume` keyword form can find it regardless of what - name the programmer chose for the continuation parameter. *) - let env' = - extend_env "__resume__" resume_fn - (extend_env_list bindings env) - in - eval env' body - | _ -> Error (RuntimeError ("Unhandled effect: " ^ op_name)) - end - | Error e -> Error e - end - - | ExprResume arg_opt -> - (* Evaluate the argument, then call the resume continuation bound in the - environment by the enclosing ExprHandle dispatcher. The continuation - is stored under "__resume__" so that the `resume expr` keyword form - works without the programmer having to name the continuation parameter. - If called outside a handler (no "__resume__" in env), return the value - as-is — this matches the surface-syntax intuition "resume x ≈ x". *) - let* arg_val = match arg_opt with - | Some e -> eval env e - | None -> Ok VUnit - in - begin match lookup_env "__resume__" env with - | Ok resume_fn -> apply_function resume_fn [arg_val] - | Error _ -> Ok arg_val - end - - | ExprTry et -> - (* Evaluate the body block. If it returns an error, match against - catch arms. Always run finally block if present. *) - let body_result = eval_block env et.et_body in - let catch_result = match body_result with - | Ok v -> Ok v - | Error (RuntimeError msg) -> - begin match et.et_catch with - | Some arms -> - (* Wrap the error as a variant for pattern matching *) - let err_val = VVariant ("RuntimeError", Some (VString msg)) in - eval_match_arms env err_val arms - | None -> Error (RuntimeError msg) - end - | Error (PatternMatchFailure) -> - begin match et.et_catch with - | Some arms -> - let err_val = VVariant ("PatternMatchFailure", None) in - eval_match_arms env err_val arms - | None -> Error PatternMatchFailure - end - | Error e -> Error e - in - (* Run finally block if present (result is discarded) *) - begin match et.et_finally with - | Some finally_blk -> - let _ = eval_block env finally_blk in - catch_result - | None -> catch_result - end - - | ExprUnsafe ops -> - (* Evaluate unsafe operations - for now, just evaluate contained expressions *) - begin match ops with - | [] -> Ok VUnit - | [UnsafeRead e] -> eval env e - | [UnsafeWrite (ptr, value)] -> - let* _ptr_val = eval env ptr in - let* _val = eval env value in - Ok VUnit - | [UnsafeOffset (base, offset)] -> - let* _base = eval env base in - let* _offset = eval env offset in - Ok VUnit - | [UnsafeTransmute (_, _, e)] -> eval env e - | [UnsafeForget e] -> - let* _ = eval env e in - Ok VUnit - | _ -> Error (RuntimeError "Multiple unsafe operations not yet supported") - end - - | ExprSpan (e, _) -> - eval env e - -(** Evaluate a list of expressions strictly left-to-right. - - Per ADR-003, all n-ary expression forms (application arguments, tuple - components, array elements, record fields) evaluate their subexpressions - in source order. The previous implementation used [List.fold_right] with - monadic bind, which under OCaml's strict evaluation visited elements - right-to-left — inconsistent with [ExprBinary] and a latent divergence - point for future affine enforcement and effect handlers. *) -and eval_list (env : env) (exprs : expr list) : value list result = - let rec loop acc = function - | [] -> Ok (List.rev acc) - | expr :: rest -> - let* v = eval env expr in - loop (v :: acc) rest - in - loop [] exprs - -(** Evaluate match arms *) -and eval_match_arms (env : env) (scrut_val : value) (arms : match_arm list) : value result = - match arms with - | [] -> Error PatternMatchFailure - | arm :: rest -> - begin match match_pattern arm.ma_pat scrut_val with - | Ok bindings -> - let env' = extend_env_list bindings env in - (* Check guard if present *) - begin match arm.ma_guard with - | Some guard -> - let* guard_val = eval env' guard in - if is_truthy guard_val then - eval env' arm.ma_body - else - eval_match_arms env scrut_val rest - | None -> - eval env' arm.ma_body - end - | Error _ -> - eval_match_arms env scrut_val rest - end - -(** Evaluate a block *) -and eval_block (env : env) (blk : block) : value result = - let* env' = eval_stmts env blk.blk_stmts in - match blk.blk_expr with - | Some e -> eval env' e - | None -> Ok VUnit - -(** Evaluate statements, returning updated environment *) -and eval_stmts (env : env) (stmts : stmt list) : env result = - List.fold_left (fun acc stmt -> - let* env = acc in - eval_stmt env stmt - ) (Ok env) stmts - -(** Evaluate a statement *) -and eval_stmt (env : env) (stmt : stmt) : env result = - match stmt with - | StmtLet sl -> - let* rhs_val = eval env sl.sl_value in - let* bindings = match_pattern sl.sl_pat rhs_val in - Ok (extend_env_list bindings env) - - | StmtExpr e -> - let* _ = eval env e in - Ok env - - | StmtAssign (lhs, _op, rhs) -> - let* lhs_val = eval env lhs in - let* rhs_val = eval env rhs in - let* () = assign lhs_val rhs_val in - Ok env - - | StmtWhile (cond, body) -> - eval_while env cond body - - | StmtFor (pat, iter, body) -> - let* iter_val = eval env iter in - eval_for env pat iter_val body - -(** Evaluate while loop *) -and eval_while (env : env) (cond : expr) (body : block) : env result = - let* cond_val = eval env cond in - if is_truthy cond_val then - let* _ = eval_block env body in - eval_while env cond body - else - Ok env - -(** Evaluate for loop *) -and eval_for (env : env) (pat : pattern) (iter : value) (body : block) : env result = - match iter with - | VArray arr -> - Array.fold_left (fun acc elem -> - let* env = acc in - let* bindings = match_pattern pat elem in - let env' = extend_env_list bindings env in - let* _ = eval_block env' body in - Ok env - ) (Ok env) arr - | _ -> Error (TypeMismatch "Expected iterable") - -(** Evaluate binary operation *) -and eval_binop (op : binary_op) (left : value) (right : value) : value result = - match (left, right) with - | (VInt a, VInt b) -> binop_int op a b - | (VFloat a, VFloat b) -> binop_float op a b - | (VString a, VString b) -> binop_string op a b - | (VBool a, VBool b) -> binop_bool op a b - | _ -> Error (TypeMismatch "Type mismatch in binary operation") - -(** Apply function to arguments *) -and apply_function (func : value) (args : value list) : value result = - match func with - | VClosure cl -> - if List.length args <> List.length cl.cl_params then - Error (TypeMismatch "Argument count mismatch") - else - let bindings = List.map2 (fun param arg -> - (param.p_name.name, arg) - ) cl.cl_params args in - let env' = extend_env_list bindings cl.cl_env in - eval env' cl.cl_body - - | VBuiltin (_, f) -> f args - - | _ -> Error (TypeMismatch "Expected function") - -(** Create initial environment with builtins *) -let create_initial_env () : env = - let builtins = [ - (* -- Console I/O -------------------------------------------------------- *) - ("print", VBuiltin ("print", fun args -> - List.iter (fun v -> print_string (Value.show_value v)) args; - Ok VUnit - )); - ("println", VBuiltin ("println", fun args -> - List.iter (fun v -> print_endline (Value.show_value v)) args; - Ok VUnit - )); - ("eprint", VBuiltin ("eprint", fun args -> - List.iter (fun v -> prerr_string (Value.show_value v)) args; - Ok VUnit - )); - ("eprintln", VBuiltin ("eprintln", fun args -> - List.iter (fun v -> prerr_endline (Value.show_value v)) args; - Ok VUnit - )); - - (* -- Collection / string length ---------------------------------------- *) - ("len", VBuiltin ("len", fun args -> - match args with - | [VArray arr] -> Ok (VInt (Array.length arr)) - | [VString s] -> Ok (VInt (String.length s)) - | _ -> Error (TypeMismatch "len expects array or string") - )); - - (* -- String builtins --------------------------------------------------- *) - ("string_get", VBuiltin ("string_get", fun args -> - match args with - | [VString s; VInt idx] -> - if idx >= 0 && idx < String.length s then - Ok (VChar (String.get s idx)) - else - Error (IndexOutOfBounds (idx, String.length s)) - | _ -> Error (TypeMismatch "string_get expects (String, Int)") - )); - ("string_sub", VBuiltin ("string_sub", fun args -> - match args with - | [VString s; VInt start; VInt length] -> - let slen = String.length s in - let start' = max 0 (min start slen) in - let length' = max 0 (min length (slen - start')) in - Ok (VString (String.sub s start' length')) - | _ -> Error (TypeMismatch "string_sub expects (String, Int, Int)") - )); - ("string_find", VBuiltin ("string_find", fun args -> - match args with - | [VString haystack; VString needle] -> - (match String.index_opt haystack (String.get needle 0) with - | None -> Ok (VInt (-1)) - | Some _ -> - let hlen = String.length haystack in - let nlen = String.length needle in - if nlen = 0 then Ok (VInt 0) - else if nlen > hlen then Ok (VInt (-1)) - else - let found = ref (-1) in - for i = 0 to hlen - nlen do - if !found = -1 && String.sub haystack i nlen = needle then - found := i - done; - Ok (VInt !found)) - | _ -> Error (TypeMismatch "string_find expects (String, String)") - )); - ("char_to_int", VBuiltin ("char_to_int", fun args -> - match args with - | [VChar c] -> Ok (VInt (Char.code c)) - | _ -> Error (TypeMismatch "char_to_int expects Char") - )); - ("int_to_char", VBuiltin ("int_to_char", fun args -> - match args with - | [VInt n] -> - if n >= 0 && n <= 127 then Ok (VChar (Char.chr n)) - else Error (RuntimeError "int_to_char: code point out of ASCII range") - | _ -> Error (TypeMismatch "int_to_char expects Int") - )); - ("show", VBuiltin ("show", fun args -> - match args with - | [v] -> Ok (VString (Value.show_value v)) - | _ -> Error (TypeMismatch "show expects a single argument") - )); - ("to_lowercase", VBuiltin ("to_lowercase", fun args -> - match args with - | [VString s] -> Ok (VString (String.lowercase_ascii s)) - | _ -> Error (TypeMismatch "to_lowercase expects String") - )); - ("to_uppercase", VBuiltin ("to_uppercase", fun args -> - match args with - | [VString s] -> Ok (VString (String.uppercase_ascii s)) - | _ -> Error (TypeMismatch "to_uppercase expects String") - )); - ("trim", VBuiltin ("trim", fun args -> - match args with - | [VString s] -> Ok (VString (String.trim s)) - | _ -> Error (TypeMismatch "trim expects String") - )); - ("int_to_string", VBuiltin ("int_to_string", fun args -> - match args with - | [VInt n] -> Ok (VString (string_of_int n)) - | _ -> Error (TypeMismatch "int_to_string expects Int") - )); - ("float_to_string", VBuiltin ("float_to_string", fun args -> - match args with - | [VFloat f] -> Ok (VString (string_of_float f)) - | _ -> Error (TypeMismatch "float_to_string expects Float") - )); - ("parse_int", VBuiltin ("parse_int", fun args -> - match args with - | [VString s] -> - (match int_of_string_opt s with - | Some n -> Ok (VVariant ("Some", Some (VInt n))) - | None -> Ok (VVariant ("None", None))) - | _ -> Error (TypeMismatch "parse_int expects String") - )); - ("parse_float", VBuiltin ("parse_float", fun args -> - match args with - | [VString s] -> - (match float_of_string_opt s with - | Some f -> Ok (VVariant ("Some", Some (VFloat f))) - | None -> Ok (VVariant ("None", None))) - | _ -> Error (TypeMismatch "parse_float expects String") - )); - - (* -- Math builtins ----------------------------------------------------- *) - ("sqrt", VBuiltin ("sqrt", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.sqrt f)) - | _ -> Error (TypeMismatch "sqrt expects Float") - )); - ("cbrt", VBuiltin ("cbrt", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.cbrt f)) - | _ -> Error (TypeMismatch "cbrt expects Float") - )); - ("pow_float", VBuiltin ("pow_float", fun args -> - match args with - | [VFloat base; VFloat exp] -> Ok (VFloat (Float.pow base exp)) - | _ -> Error (TypeMismatch "pow_float expects (Float, Float)") - )); - ("floor", VBuiltin ("floor", fun args -> - match args with - | [VFloat f] -> Ok (VInt (Float.to_int (Float.floor f))) - | _ -> Error (TypeMismatch "floor expects Float") - )); - ("ceil", VBuiltin ("ceil", fun args -> - match args with - | [VFloat f] -> Ok (VInt (Float.to_int (Float.ceil f))) - | _ -> Error (TypeMismatch "ceil expects Float") - )); - ("round", VBuiltin ("round", fun args -> - match args with - | [VFloat f] -> Ok (VInt (Float.to_int (Float.round f))) - | _ -> Error (TypeMismatch "round expects Float") - )); - ("trunc", VBuiltin ("trunc", fun args -> - match args with - | [VFloat f] -> Ok (VInt (Float.to_int (Float.of_int (Float.to_int f)))) - | _ -> Error (TypeMismatch "trunc expects Float") - )); - ("sin", VBuiltin ("sin", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.sin f)) - | _ -> Error (TypeMismatch "sin expects Float") - )); - ("cos", VBuiltin ("cos", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.cos f)) - | _ -> Error (TypeMismatch "cos expects Float") - )); - ("tan", VBuiltin ("tan", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.tan f)) - | _ -> Error (TypeMismatch "tan expects Float") - )); - ("asin", VBuiltin ("asin", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.asin f)) - | _ -> Error (TypeMismatch "asin expects Float") - )); - ("acos", VBuiltin ("acos", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.acos f)) - | _ -> Error (TypeMismatch "acos expects Float") - )); - ("atan", VBuiltin ("atan", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.atan f)) - | _ -> Error (TypeMismatch "atan expects Float") - )); - ("atan2", VBuiltin ("atan2", fun args -> - match args with - | [VFloat y; VFloat x] -> Ok (VFloat (Float.atan2 y x)) - | _ -> Error (TypeMismatch "atan2 expects (Float, Float)") - )); - ("exp", VBuiltin ("exp", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.exp f)) - | _ -> Error (TypeMismatch "exp expects Float") - )); - ("log", VBuiltin ("log", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.log f)) - | _ -> Error (TypeMismatch "log expects Float") - )); - ("log10", VBuiltin ("log10", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.log10 f)) - | _ -> Error (TypeMismatch "log10 expects Float") - )); - ("log2", VBuiltin ("log2", fun args -> - match args with - | [VFloat f] -> Ok (VFloat (Float.log2 f)) - | _ -> Error (TypeMismatch "log2 expects Float") - )); - - (* -- I/O builtins ------------------------------------------------------ *) - ("panic", VBuiltin ("panic", fun args -> - match args with - | [VString msg] -> Error (RuntimeError msg) - | _ -> Error (RuntimeError "panic!") - )); - ("read_file", VBuiltin ("read_file", fun args -> - match args with - | [VString path] -> - (try - let ic = open_in path in - let n = in_channel_length ic in - let s = Bytes.create n in - really_input ic s 0 n; - close_in ic; - Ok (VVariant ("Ok", Some (VString (Bytes.to_string s)))) - with - | Sys_error msg -> Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "read_file expects String") - )); - ("write_file", VBuiltin ("write_file", fun args -> - match args with - | [VString path; VString content] -> - (try - let oc = open_out path in - output_string oc content; - close_out oc; - Ok (VVariant ("Ok", Some VUnit)) - with - | Sys_error msg -> Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "write_file expects (String, String)") - )); - ("append_file", VBuiltin ("append_file", fun args -> - match args with - | [VString path; VString content] -> - (try - let oc = open_out_gen [Open_append; Open_creat] 0o644 path in - output_string oc content; - close_out oc; - Ok (VVariant ("Ok", Some VUnit)) - with - | Sys_error msg -> Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "append_file expects (String, String)") - )); - ("file_exists", VBuiltin ("file_exists", fun args -> - match args with - | [VString path] -> Ok (VBool (Sys.file_exists path)) - | _ -> Error (TypeMismatch "file_exists expects String") - )); - ("is_directory", VBuiltin ("is_directory", fun args -> - match args with - | [VString path] -> Ok (VBool (Sys.is_directory path)) - | _ -> Error (TypeMismatch "is_directory expects String") - )); - ("getenv", VBuiltin ("getenv", fun args -> - match args with - | [VString name] -> - (match Sys.getenv_opt name with - | Some v -> Ok (VVariant ("Some", Some (VString v))) - | None -> Ok (VVariant ("None", None))) - | _ -> Error (TypeMismatch "getenv expects String") - )); - ("getcwd", VBuiltin ("getcwd", fun args -> - match args with - | [] -> Ok (VVariant ("Ok", Some (VString (Sys.getcwd ())))) - | _ -> Error (TypeMismatch "getcwd expects no arguments") - )); - ("read_line", VBuiltin ("read_line", fun args -> - match args with - | [] -> - (try Ok (VVariant ("Ok", Some (VString (read_line ())))) - with End_of_file -> Ok (VVariant ("Err", Some (VString "End of input")))) - | _ -> Error (TypeMismatch "read_line expects no arguments") - )); - ("exit", VBuiltin ("exit", fun args -> - match args with - | [VInt code] -> exit code - | _ -> Error (TypeMismatch "exit expects Int") - )); - - (* -- Directory operations ------------------------------------------------ *) - ("list_dir", VBuiltin ("list_dir", fun args -> - match args with - | [VString path] -> - (try - let handle = Unix.opendir path in - let entries = ref [] in - (try while true do - let entry = Unix.readdir handle in - if entry <> "." && entry <> ".." then - entries := entry :: !entries - done with End_of_file -> ()); - Unix.closedir handle; - Ok (VVariant ("Ok", Some (VArray (Array.of_list - (List.rev_map (fun s -> VString s) !entries))))) - with - | Unix.Unix_error (_, _, msg) -> - Ok (VVariant ("Err", Some (VString ("list_dir: " ^ msg)))) - | Sys_error msg -> - Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "list_dir expects String") - )); - ("create_dir", VBuiltin ("create_dir", fun args -> - match args with - | [VString path] -> - (try - Unix.mkdir path 0o755; - Ok (VVariant ("Ok", Some VUnit)) - with - | Unix.Unix_error (_, _, msg) -> - Ok (VVariant ("Err", Some (VString ("create_dir: " ^ msg)))) - | Sys_error msg -> - Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "create_dir expects String") - )); - ("remove_dir", VBuiltin ("remove_dir", fun args -> - match args with - | [VString path] -> - (try - Unix.rmdir path; - Ok (VVariant ("Ok", Some VUnit)) - with - | Unix.Unix_error (_, _, msg) -> - Ok (VVariant ("Err", Some (VString ("remove_dir: " ^ msg)))) - | Sys_error msg -> - Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "remove_dir expects String") - )); - ("setenv", VBuiltin ("setenv", fun args -> - match args with - | [VString name; VString value] -> - (try - Unix.putenv name value; - Ok (VVariant ("Ok", Some VUnit)) - with - | Unix.Unix_error (_, _, msg) -> - Ok (VVariant ("Err", Some (VString ("setenv: " ^ msg))))) - | _ -> Error (TypeMismatch "setenv expects (String, String)") - )); - ("chdir", VBuiltin ("chdir", fun args -> - match args with - | [VString path] -> - (try - Unix.chdir path; - Ok (VVariant ("Ok", Some VUnit)) - with - | Unix.Unix_error (_, _, msg) -> - Ok (VVariant ("Err", Some (VString ("chdir: " ^ msg)))) - | Sys_error msg -> - Ok (VVariant ("Err", Some (VString msg)))) - | _ -> Error (TypeMismatch "chdir expects String") - )); - - (* -- Time --------------------------------------------------------------- *) - ("time_now", VBuiltin ("time_now", fun _args -> - Ok (VFloat (Sys.time ())) - )); - - (* -- Cmd Msg — linear side-effect obligations (Stage 11) --------------- *) - (* cmd_none : Cmd 'msg — the no-op command; the interpreter represents - it as VUnit. At runtime, the TEA loop discards the Cmd after - extracting the new model from the (model, cmd) pair. - Linearity is enforced at compile time (quantity checker) not runtime. *) - ("cmd_none", VUnit); - (* cmd_perform : (() ->{IO} unit) -> Cmd 'msg — wraps an IO thunk. - In the interpreter, the Cmd is executed eagerly when constructed. - In a real browser runtime the thunk would be scheduled by the - runtime after the update cycle completes. *) - ("cmd_perform", VBuiltin ("cmd_perform", fun args -> - match args with - | [VClosure _ as f] -> - (* Execute the IO thunk immediately in the interpreter *) - let* _ = apply_function f [VUnit] in - Ok VUnit - | [VBuiltin _ as f] -> - let* _ = apply_function f [VUnit] in - Ok VUnit - | _ -> - Error (RuntimeError "cmd_perform: expected a zero-argument function") - )); - (* -- TEA (The Elm Architecture) interpreter runtime -------------------- *) - ("tea_run", VBuiltin ("tea_run", fun args -> - (* tea_run expects a record with fields: init, update, view, subscriptions. - - init : () -> (Model, [Cmd]) - - update : Msg -> Model -> (Model, [Cmd]) - - view : Model -> Html (Html is rendered as a string or shown) - - subscriptions: Model -> [Sub] (unused at interpreter level) - - The interpreter loop: - 1. Call init() to get the initial model. - 2. Render and print the initial view. - 3. Read lines from stdin; each line becomes a variant Msg. - 4. Call update(msg, model) -> (new_model, cmds). - 5. Print new view. Repeat until EOF. *) - match args with - | [VRecord fields] -> - let get f = match List.assoc_opt f fields with - | Some v -> Ok v - | None -> Error (RuntimeError (Printf.sprintf "tea_run: missing field '%s'" f)) - in - let render_html v = - let s = match v with - | VString s -> s - | VVariant ("Text", Some (VString s)) -> s - | VVariant ("Node", _) -> "[Html node]" - | other -> show_value other - in - print_endline s - in - let* init_fn = get "init" in - let* update_fn = get "update" in - let* view_fn = get "view" in - (* Call init() -> (model, cmds) — 0-param function, no args *) - let* init_result = apply_function init_fn [] in - let model0 = match init_result with - | VTuple (m :: _) -> m - | m -> m - in - (* Render initial view *) - let* view0 = apply_function view_fn [model0] in - render_html view0; - (* Read-eval-print loop: stdin lines become variant Msgs until EOF *) - let rec loop model = - let input = try Some (read_line ()) with End_of_file -> None in - match input with - | None -> Ok VUnit (* EOF — done, clean exit *) - | Some "" -> loop model (* blank line — skip *) - | Some line -> - let msg = VVariant (String.trim line, None) in - let* upd_result = apply_function update_fn [msg; model] in - let new_model = match upd_result with - | VTuple (m :: _) -> m - | m -> m - in - let* new_view = apply_function view_fn [new_model] in - render_html new_view; - loop new_model - in - loop model0 - | _ -> - Error (RuntimeError - "tea_run expects a record: {init, update, view, subscriptions}") - )); - ] in - builtins - -(** Evaluate a top-level declaration *) -let eval_decl (env : env) (decl : top_level) : env result = - match decl with - | TopFn fd -> - let closure = VClosure { - cl_params = fd.fd_params; - cl_body = (match fd.fd_body with - | FnBlock blk -> ExprBlock blk - | FnExpr e -> e); - cl_env = env; - } in - Ok (extend_env fd.fd_name.name closure env) - - | TopConst tc -> - let* v = eval env tc.tc_value in - Ok (extend_env tc.tc_name.name v env) - - | TopType td -> - (* Register enum constructors so they can be used as values / constructor functions. - Nullary constructors become VVariant values. - N-ary constructors become VBuiltin constructor functions. *) - begin match td.td_body with - | TyEnum variants -> - let env' = List.fold_left (fun env (vd : variant_decl) -> - let name = vd.vd_name.name in - match vd.vd_fields with - | [] -> - (* Nullary constructor: bind directly as a VVariant value *) - extend_env name (VVariant (name, None)) env - | [_] -> - (* Single-payload constructor: wrap arg in VVariant *) - extend_env name - (VBuiltin (name, fun args -> - match args with - | [v] -> Ok (VVariant (name, Some v)) - | _ -> Error (TypeMismatch (Printf.sprintf "%s expects 1 argument" name)))) - env - | fields -> - (* Multi-payload constructor: pack args into a VTuple *) - let n = List.length fields in - extend_env name - (VBuiltin (name, fun args -> - if List.length args = n then - Ok (VVariant (name, Some (VTuple args))) - else - Error (TypeMismatch - (Printf.sprintf "%s expects %d arguments" name n)))) - env - ) env variants in - Ok env' - | _ -> - (* Struct, alias, and other type declarations don't create runtime bindings *) - Ok env - end - - | TopTrait _ | TopImpl _ -> - (* Trait and impl declarations don't affect the runtime value environment *) - Ok env - - | TopEffect ed -> - (* Register each effect operation as a PerformEffect-raising builtin. - When an effect op is called from within a `handle` expression, the - Error(PerformEffect ...) propagates up the call stack until caught by - the ExprHandle dispatcher. Unhandled effects surface as RuntimeError. *) - let env' = List.fold_left (fun env (op : effect_op_decl) -> - let op_name = op.eod_name.name in - let builtin = VBuiltin (op_name, fun args -> - Error (PerformEffect (op_name, args)) - ) in - extend_env op_name builtin env - ) env ed.ed_ops in - Ok env' - -(** Evaluate a program *) -let eval_program (prog : program) : env result = - let initial_env = create_initial_env () in - List.fold_left (fun acc decl -> - let* env = acc in - eval_decl env decl - ) (Ok initial_env) prog.prog_decls diff --git a/affinescript/lib/js_face.ml b/affinescript/lib/js_face.ml deleted file mode 100644 index 73549a3..0000000 --- a/affinescript/lib/js_face.ml +++ /dev/null @@ -1,307 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** JS-face: source-level transformer for JavaScript/TypeScript-style AffineScript. - - Maps common JavaScript surface patterns to canonical AffineScript before - lexing and parsing. The compiler is face-agnostic (ADR-010); only this - module and [Face] know about the JS face. - - Surface mappings: - {v - const x = expr → let x = expr - let x = expr → let mut x = expr (JS let is mutable) - var x = expr → let mut x = expr - function name(p) { } → fn name(p) { } - async function name() → fn name() / Async - => expr (arrow body) → { expr } - null / undefined → () - === / !== → == / != - // comment → (already valid) - /* comment */ → (already valid) - import { x } from "m" → use m::x; - import x from "m" → use m; - export const x = … → let x = … (exports implicit in AffineScript) - export function f(…) → fn f(…) - export default fn f() → fn f() - typeof x → (removed — no equivalent; leaves comment) - v} - - Limitation — async/await: [await expr] is rewritten to [Async.await(expr)] - when used inside an [async function]. Handler declaration for the [Async] - effect must be provided by the host. See [effect Async] in stdlib. - - Limitation — span fidelity: error spans refer to the transformed canonical - text, not the original JS source. - - Limitation — arrow functions: multi-line arrow functions are not yet - supported. Single-expression arrows ([=> expr]) are rewritten to - [{ expr }]. Full multi-line arrow bodies are a follow-up task. -*) - -(* ─── Character helpers ────────────────────────────────────────────────── *) - -let is_id_char c = - (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') || c = '_' - -let starts_with s prefix = - let sl = String.length s and pl = String.length prefix in - sl >= pl && String.sub s 0 pl = prefix - -(** Word-boundary-aware substitution: only replace [kw] when surrounded by - non-identifier characters (equivalent to [\\bkw\\b] in regex). *) -let subst_word line kw replacement = - let kl = String.length kw and ll = String.length line in - let buf = Buffer.create ll in - let i = ref 0 in - while !i < ll do - if !i + kl <= ll && String.sub line !i kl = kw then begin - let before_ok = !i = 0 || not (is_id_char line.[!i - 1]) in - let after_ok = !i + kl >= ll || not (is_id_char line.[!i + kl]) in - if before_ok && after_ok then begin - Buffer.add_string buf replacement; - i := !i + kl - end else begin - Buffer.add_char buf line.[!i]; - incr i - end - end else begin - Buffer.add_char buf line.[!i]; - incr i - end - done; - Buffer.contents buf - -(** Strip a [//] comment suffix (respecting string literals). *) -let strip_line_comment line = - let len = String.length line in - let in_str = ref false and str_delim = ref '"' in - let result = ref line in - let i = ref 0 in - while !i < len do - let c = line.[!i] in - if !in_str then begin - if c = !str_delim && (!i = 0 || line.[!i - 1] <> '\\') then - in_str := false; - incr i - end else begin - if c = '"' || c = '\'' || c = '`' then begin - in_str := true; str_delim := c; incr i - end else if !i + 1 < len && c = '/' && line.[!i + 1] = '/' then begin - result := String.sub line 0 !i; - i := len (* break *) - end else - incr i - end - done; - !result - -(* ─── Keyword substitutions ────────────────────────────────────────────── *) - -(** Apply all single-token keyword substitutions in priority order. - Does not touch the inside of string literals. *) -let apply_keyword_subs line = - let line = subst_word line "null" "()" in - let line = subst_word line "undefined" "()" in - let line = subst_word line "===" "==" in - let line = subst_word line "!==" "!=" in - (* Logical keywords — only swap those not already covered by operators *) - line - -let ends_with s suffix = - let sl = String.length s and tl = String.length suffix in - sl >= tl && String.sub s (sl - tl) tl = suffix - -(* ─── Import / export handling ─────────────────────────────────────────── *) - -(** Transform JavaScript import declarations to AffineScript use declarations. - Examples: - {v - import { x, y } from "module" → use module::{x, y}; - import x from "module" → use module; - import "module" → use module; - v} -*) -let transform_import line = - let line = String.trim line in - (* import { x, y } from "module" *) - if starts_with line "import {" then begin - (* Find the closing } and "from" *) - try - let close_brace = String.index line '}' in - let names_raw = String.sub line 8 (close_brace - 8) in - let rest = String.sub line (close_brace + 1) - (String.length line - close_brace - 1) in - let rest = String.trim rest in - if starts_with rest "from " then begin - let mod_part = String.trim (String.sub rest 5 (String.length rest - 5)) in - let mod_name = String.sub mod_part 1 (String.length mod_part - 2) in (* strip quotes *) - let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in - let names = String.trim names_raw in - Printf.sprintf "use %s::{%s};" mod_name names - end else line - with Not_found -> line - end - (* import x from "module" *) - else if starts_with line "import " then begin - try - let rest = String.sub line 7 (String.length line - 7) in - let rest = String.trim rest in - if starts_with rest "from " then - (* import * from "module" — just bring in module *) - let mod_part = String.trim (String.sub rest 5 (String.length rest - 5)) in - let mod_name = String.sub mod_part 1 (String.length mod_part - 2) in - let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in - Printf.sprintf "use %s;" mod_name - else begin - (* import name from "module" *) - match String.split_on_char ' ' rest with - | name :: "from" :: quoted :: _ -> - let mod_name = String.sub quoted 1 (String.length quoted - 2) in - let mod_name = String.map (fun c -> if c = '/' || c = '-' then '_' else c) mod_name in - Printf.sprintf "use %s::%s;" mod_name name - | _ -> line - end - with _ -> line - end - else line - -(** Strip export modifier from declarations. *) -let strip_export line = - let line = String.trim line in - if starts_with line "export default " then - String.sub line 15 (String.length line - 15) - else if starts_with line "export " then - String.sub line 7 (String.length line - 7) - else line - -(* ─── Function / variable declarations ────────────────────────────────── *) - -(** Transform a [const]/[let]/[var] binding line. *) -let transform_var_decl line = - let line = String.trim line in - if starts_with line "const " then - "let " ^ String.sub line 6 (String.length line - 6) - else if starts_with line "let " then - "let mut " ^ String.sub line 4 (String.length line - 4) - else if starts_with line "var " then - "let mut " ^ String.sub line 4 (String.length line - 4) - else line - -(** Transform [function name(params) {] to [fn name(params) {]. - Handles [async function] → adds [/ Async] annotation to return type. *) -let transform_function_decl line = - let line = String.trim line in - let is_async = starts_with line "async function " in - let line = - if is_async then String.sub line 6 (String.length line - 6) (* strip "async " *) - else line - in - if starts_with line "function " then begin - let body = String.sub line 9 (String.length line - 9) in - let fn_line = "fn " ^ body in - (* For async functions, insert Async effect into return type. - We look for "->" in the signature. If absent, we append the effect - comment so the user knows to add it manually. *) - if is_async then begin - if String.contains fn_line '>' then - (* Insert Async after existing return type: "-> T {" → "-> T / Async {" *) - let re = Str.regexp {|-> \([^{]*\) {|} in - (try Str.global_replace re {|-> \1 / Async {|} fn_line - with Not_found -> fn_line ^ " /* add / Async to return type */") - else - fn_line ^ " /* async: add '/ Async' to return type */" - end else fn_line - end else line - -(** Rewrite a single-expression arrow function body [=> expr] to [{ expr }]. - Only handles trailing [=>] on the same line. *) -let transform_arrow_body line = - (* Look for a bare "=>" not inside a string. *) - let len = String.length line in - let in_str = ref false and str_delim = ref '"' in - let arrow_pos = ref (-1) in - let i = ref 0 in - while !i < len do - let c = line.[!i] in - if !in_str then begin - if c = !str_delim && (!i = 0 || line.[!i - 1] <> '\\') then - in_str := false; - incr i - end else begin - if c = '"' || c = '\'' || c = '`' then begin - in_str := true; str_delim := c; incr i - end else if !i + 1 < len && c = '=' && line.[!i + 1] = '>' then begin - (* Make sure it's not inside <= or === *) - let prev_ok = !i = 0 || (line.[!i - 1] <> '<' && line.[!i - 1] <> '!') in - if prev_ok then arrow_pos := !i; - i := !i + 2 - end else - incr i - end - done; - if !arrow_pos >= 0 then begin - let before = String.sub line 0 !arrow_pos in - let after = String.trim (String.sub line (!arrow_pos + 2) (len - !arrow_pos - 2)) in - (* If after ends with {, it's a block arrow — don't wrap again *) - if after = "{" || ends_with after " {" then - before ^ "{ " - else - before ^ "{ " ^ after ^ " }" - end else line - -(* ─── Line-by-line transform ────────────────────────────────────────────── *) - -(** Transform a single source line from JS-face to canonical AffineScript. - Order matters: export stripping before function detection, variable - declarations before keyword substitution. *) -let transform_line line = - let trimmed = String.trim line in - (* Blank lines pass through *) - if trimmed = "" then line - (* Block comments pass through *) - else if starts_with trimmed "/*" || starts_with trimmed "*" then line - else begin - (* 1. Handle import lines *) - if starts_with trimmed "import " then - transform_import trimmed - else begin - (* 2. Strip export modifier *) - let line = strip_export line in - let trimmed = String.trim line in - (* 3. Transform function declarations *) - let line = - if starts_with trimmed "async function " || starts_with trimmed "function " then - transform_function_decl trimmed - else if starts_with trimmed "const " || starts_with trimmed "let " - || starts_with trimmed "var " then - transform_var_decl trimmed - else line - in - (* 4. Arrow body rewrite *) - let line = transform_arrow_body line in - (* 5. Keyword substitutions *) - let line = apply_keyword_subs line in - line - end - end - -(* ─── File-level entry points ─────────────────────────────────────────── *) - -(** Transform a full JS-face source string to canonical AffineScript. *) -let transform_source source = - let lines = String.split_on_char '\n' source in - let out = List.map transform_line lines in - String.concat "\n" out - -(** Parse a JS-face file: transform then parse as canonical AffineScript. - Called from [bin/main.ml] via [parse_with_face]. *) -let parse_file_js path = - let source = In_channel.with_open_text path In_channel.input_all in - let canonical = transform_source source in - Parse_driver.parse_string ~file:path canonical - -(** Debug: return the transformed source without parsing. *) -let preview_transform source = - transform_source source diff --git a/affinescript/lib/json_output.ml b/affinescript/lib/json_output.ml deleted file mode 100644 index 6fa770a..0000000 --- a/affinescript/lib/json_output.ml +++ /dev/null @@ -1,723 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** JSON output for machine-readable compiler diagnostics. - - Phase A of the AffineScript LSP integration: structured JSON output - from the compiler CLI via [--json], enabling the LSP (and other tools) - to consume diagnostics without fragile regex parsing. - - Output schema (one JSON object per line on stderr): - {[ - { - "version": 1, - "diagnostics": [ - { - "severity": "error" | "warning" | "hint" | "info", - "code": "E0001", - "message": "...", - "file": "path/to/file.affine", - "start_line": 1, - "start_col": 1, - "end_line": 1, - "end_col": 10, - "help": "..." | null, - "labels": [ - { "file": "...", "start_line": 1, "start_col": 1, - "end_line": 1, "end_col": 5, "message": "..." } - ] - } - ], - "success": true | false - } - ]} -*) - -(** {1 Span serialization} *) - -(** Convert a [Span.pos] to a JSON object. *) -let pos_to_json (p : Span.pos) : Yojson.Basic.t = - `Assoc [ - ("line", `Int p.line); - ("col", `Int p.col); - ("offset", `Int p.offset); - ] - -(** Convert a [Span.t] to a JSON object with flat start/end fields. *) -let span_to_json (s : Span.t) : (string * Yojson.Basic.t) list = - [ - ("file", `String s.file); - ("start_line", `Int s.start_pos.line); - ("start_col", `Int s.start_pos.col); - ("end_line", `Int s.end_pos.line); - ("end_col", `Int s.end_pos.col); - ] - -(** {1 Unified diagnostic type} *) - -(** Severity levels matching the LSP spec. *) -type severity = Error | Warning | Hint | Info - -(** A single compiler diagnostic in a tool-friendly format. *) -type diagnostic = { - severity : severity; - code : string; - message : string; - span : Span.t; - help : string option; - labels : (Span.t * string) list; -} - -(** Convert severity to its JSON string representation. *) -let severity_to_string = function - | Error -> "error" - | Warning -> "warning" - | Hint -> "hint" - | Info -> "info" - -(** Serialize a single diagnostic to JSON. *) -let diagnostic_to_json (d : diagnostic) : Yojson.Basic.t = - let labels_json = `List (List.map (fun (s, msg) -> - `Assoc (span_to_json s @ [("message", `String msg)]) - ) d.labels) in - `Assoc ( - [ - ("severity", `String (severity_to_string d.severity)); - ("code", `String d.code); - ("message", `String d.message); - ] - @ span_to_json d.span - @ [ - ("help", match d.help with Some h -> `String h | None -> `Null); - ("labels", labels_json); - ] - ) - -(** {1 Diagnostic envelope} *) - -(** Serialize a complete diagnostic report (the top-level JSON object). *) -let report_to_json ~(success : bool) (diags : diagnostic list) : Yojson.Basic.t = - `Assoc [ - ("version", `Int 1); - ("diagnostics", `List (List.map diagnostic_to_json diags)); - ("success", `Bool success); - ] - -(** Write the JSON report to stderr. *) -let emit_report ~(success : bool) (diags : diagnostic list) : unit = - let json = report_to_json ~success diags in - Format.eprintf "%s@." (Yojson.Basic.to_string json) - -(** {1 Converters from compiler error types} *) - -(** Convert a lexer error to a diagnostic. *) -let of_lexer_error (msg : string) (pos : Span.pos) (file : string) : diagnostic = - let span = Span.make ~file - ~start_pos:pos - ~end_pos:{ pos with col = pos.col + 1 } - in - { - severity = Error; - code = "E0001"; - message = msg; - span; - help = None; - labels = []; - } - -(** Convert a parse error to a diagnostic. *) -let of_parse_error (msg : string) (span : Span.t) : diagnostic = - { - severity = Error; - code = "E0100"; - message = msg; - span; - help = None; - labels = []; - } - -(** Convert a resolve error to a diagnostic. - - [Resolve.resolve_error] variants carry [Ast.ident] values which - contain both a name and a span. We use the ident's span when - available (it points to the exact token), falling back to the - caller-supplied [span] for variants that carry only a [string]. *) -let of_resolve_error (e : Resolve.resolve_error) (span : Span.t) : diagnostic = - let code, message, help, diag_span = match e with - | Resolve.UndefinedVariable id -> - "E0500", Printf.sprintf "Undefined variable: %s" id.Ast.name, - Some "Check spelling or add an import", id.Ast.span - | Resolve.UndefinedType id -> - "E0501", Printf.sprintf "Undefined type: %s" id.Ast.name, - Some "Check spelling or add an import", id.Ast.span - | Resolve.UndefinedEffect id -> - "E0502", Printf.sprintf "Undefined effect: %s" id.Ast.name, - None, id.Ast.span - | Resolve.UndefinedModule id -> - "E0503", Printf.sprintf "Undefined module: %s" id.Ast.name, - Some "Check that the module file exists", id.Ast.span - | Resolve.DuplicateDefinition id -> - "E0504", Printf.sprintf "Duplicate definition: %s" id.Ast.name, - None, id.Ast.span - | Resolve.VisibilityError (id, reason) -> - "E0505", Printf.sprintf "Visibility error for %s: %s" id.Ast.name reason, - None, id.Ast.span - | Resolve.ImportError msg -> - "E0506", Printf.sprintf "Import error: %s" msg, None, span - in - { - severity = Error; - code; - message; - span = diag_span; - help; - labels = []; - } - -(** Convert a type error to a diagnostic. - - Type errors currently lack span information in the compiler, so we - use [Span.dummy] as a placeholder. Phase B will thread spans through - the type checker. *) -let of_type_error (e : Typecheck.type_error) : diagnostic = - let code, message, help = match e with - | Typecheck.UnboundVariable v -> - "E0100", Printf.sprintf "Unbound variable: %s" v, - Some "Check spelling or add an import" - | Typecheck.TypeMismatch { expected; got } -> - "E0101", - Printf.sprintf "Type mismatch: expected %s, got %s" - (Types.show_ty expected) (Types.show_ty got), - None - | Typecheck.OccursCheck (v, ty) -> - "E0102", - Printf.sprintf "Infinite type: %s occurs in %s" v (Types.show_ty ty), - Some "This usually means a recursive type without proper boxing" - | Typecheck.NotImplemented msg -> - "E0199", Printf.sprintf "Not implemented: %s" msg, None - | Typecheck.ArityMismatch { name; expected; got } -> - "E0103", Printf.sprintf "Function %s expects %d arguments, got %d" name expected got, None - | Typecheck.NotAFunction ty -> - "E0104", Printf.sprintf "Expected a function, got %s" (Types.show_ty ty), None - | Typecheck.FieldNotFound { field; record_ty } -> - "E0105", Printf.sprintf "Field '%s' not found in %s" field (Types.show_ty record_ty), None - | Typecheck.TupleIndexOutOfBounds { index; length } -> - "E0106", Printf.sprintf "Tuple index %d out of bounds (length %d)" index length, None - | Typecheck.DuplicateField f -> - "E0107", Printf.sprintf "Duplicate field: %s" f, None - | Typecheck.UnificationError ue -> - "E0108", Printf.sprintf "Type error: %s" (Unify.show_unify_error ue), None - | Typecheck.PatternTypeMismatch msg -> - "E0109", Printf.sprintf "Pattern mismatch: %s" msg, None - | Typecheck.BranchTypeMismatch { then_ty; else_ty } -> - "E0110", Printf.sprintf "Branch mismatch: then %s, else %s" - (Types.show_ty then_ty) (Types.show_ty else_ty), None - | Typecheck.QuantityError (qerr, _span) -> - begin match qerr with - | Quantity.LinearVariableUnused id -> - "E0300", - Printf.sprintf "Linear variable '%s' must be used exactly once, but was never used" - id.Ast.name, - Some "Remove the quantity annotation or use the variable" - | Quantity.LinearVariableUsedMultiple id -> - "E0301", - Printf.sprintf "Linear variable '%s' must be used exactly once, but was used multiple times" - id.Ast.name, - Some "Clone the value or change the quantity to omega" - | Quantity.ErasedVariableUsed id -> - "E0302", - Printf.sprintf "Erased variable '%s' (quantity 0) must not be used at runtime" - id.Ast.name, - Some "Erased parameters exist only for type-level reasoning" - | Quantity.QuantityMismatch _ -> - "E0303", - Quantity.format_quantity_error qerr, - None - end - in - { - severity = Error; - code; - message; - span = Span.dummy; - help; - labels = []; - } - -(** Convert a borrow error to a diagnostic. *) -let of_borrow_error (e : Borrow.borrow_error) : diagnostic = - let code, message, help = match e with - | Borrow.UseAfterMove _ -> - "E0501", Borrow.format_borrow_error e, - Some "Each owned value may be used only once; clone it if you need a copy" - | Borrow.ConflictingBorrow _ -> - "E0502", Borrow.format_borrow_error e, - Some "Cannot have both a mutable and an immutable borrow active at the same time" - | Borrow.BorrowOutlivesOwner _ -> - "E0503", Borrow.format_borrow_error e, - Some "The borrow must not outlive the variable it borrows from" - | Borrow.MoveWhileBorrowed _ -> - "E0504", Borrow.format_borrow_error e, - Some "End all borrows before moving the value" - | Borrow.CannotMoveOutOfBorrow _ -> - "E0505", Borrow.format_borrow_error e, - Some "Dereference the reference and clone the value, or take ownership another way" - | Borrow.CannotBorrowAsMutable _ -> - "E0506", Borrow.format_borrow_error e, - Some "Declare the binding with `let mut` to allow mutable borrows" - in - { severity = Error; code; message; span = Span.dummy; help; labels = [] } - -(** Convert an eval error to a diagnostic. *) -let of_eval_error (e : Value.eval_error) : diagnostic = - let code, message = match e with - | Value.UnboundVariable v -> - "E0700", Printf.sprintf "Runtime: unbound variable %s" v - | Value.TypeMismatch msg -> - "E0701", Printf.sprintf "Runtime type mismatch: %s" msg - | Value.DivisionByZero -> - "E0702", "Division by zero" - | Value.IndexOutOfBounds (idx, len) -> - "E0703", Printf.sprintf "Index %d out of bounds (length %d)" idx len - | Value.FieldNotFound f -> - "E0704", Printf.sprintf "Field not found: %s" f - | Value.PatternMatchFailure -> - "E0705", "Non-exhaustive pattern match" - | Value.AffineViolation msg -> - "E0706", Printf.sprintf "Affine violation: %s" msg - | Value.RuntimeError msg -> - "E0799", Printf.sprintf "Runtime error: %s" msg - | Value.PerformEffect (name, _args) -> - "E0710", Printf.sprintf "Unhandled effect: %s" name - in - { - severity = Error; - code; - message; - span = Span.dummy; - help = None; - labels = []; - } - -(** Convert a quantity error to a diagnostic. - - Quantity errors use error codes E0300-E0399 (QTT checking range). *) -let of_quantity_error ((err, span) : Quantity.quantity_error * Span.t) : diagnostic = - let code, message, help = match err with - | Quantity.LinearVariableUnused id -> - "E0300", - Printf.sprintf "Linear variable '%s' must be used exactly once, but was never used" - id.name, - Some "Remove the quantity annotation or use the variable" - | Quantity.LinearVariableUsedMultiple id -> - "E0301", - Printf.sprintf "Linear variable '%s' must be used exactly once, but was used multiple times" - id.name, - Some "Clone the value or change the quantity to omega" - | Quantity.ErasedVariableUsed id -> - "E0302", - Printf.sprintf "Erased variable '%s' (quantity 0) must not be used at runtime" - id.name, - Some "Erased parameters exist only for type-level reasoning" - | Quantity.QuantityMismatch (_id, _q, _u) -> - "E0303", - Quantity.format_quantity_error err, - None - in - { - severity = Error; - code; - message; - span; - help; - labels = []; - } - -(** Convert a linter diagnostic to our unified format. *) -let of_lint_diagnostic (d : Linter.diagnostic) : diagnostic = - let severity = match d.severity with - | Linter.Error -> Error - | Linter.Warning -> Warning - | Linter.Hint -> Hint - | Linter.Info -> Info - in - { - severity; - code = d.code; - message = d.message; - span = d.span; - help = d.help; - labels = []; - } - -(** {1 Phase B: Symbol table and references for goto-def/find-refs} - - When [version >= 2], the JSON output includes a [symbols] array - and a [references] map, enabling the LSP to provide goto-definition, - find-references, and rename without a second compiler invocation. - - Schema additions: - {[ - { - "version": 2, - ...existing fields..., - "symbols": [ - { - "id": 0, - "name": "foo", - "kind": "function", - "file": "path/to/file.affine", - "start_line": 1, "start_col": 4, - "end_line": 1, "end_col": 7, - "type": "Int -> Int" | null, - "quantity": "linear" | "affine" | "unrestricted" | null - } - ], - "references": { - "0": [ - { "file": "...", "start_line": 5, "start_col": 10, - "end_line": 5, "end_col": 13 } - ] - } - } - ]} -*) - -(** Convert a symbol kind to its JSON string representation. *) -let symbol_kind_to_string (kind : Symbol.symbol_kind) : string = - match kind with - | Symbol.SKVariable -> "variable" - | Symbol.SKFunction -> "function" - | Symbol.SKType -> "type" - | Symbol.SKTypeVar -> "type_variable" - | Symbol.SKEffect -> "effect" - | Symbol.SKEffectOp -> "effect_operation" - | Symbol.SKTrait -> "trait" - | Symbol.SKModule -> "module" - | Symbol.SKConstructor -> "constructor" - -(** Serialize a symbol to JSON. *) -let symbol_to_json (sym : Symbol.symbol) : Yojson.Basic.t = - let type_json = match sym.sym_type with - | Some ty -> `String (Ast.show_type_expr ty) - | None -> `Null - in - let quantity_json = match sym.sym_quantity with - | Some q -> `String (Ast.show_quantity q) - | None -> `Null - in - `Assoc ( - [ - ("id", `Int sym.sym_id); - ("name", `String sym.sym_name); - ("kind", `String (symbol_kind_to_string sym.sym_kind)); - ] - @ span_to_json sym.sym_span - @ [ - ("type", type_json); - ("quantity", quantity_json); - ] - ) - -(** Serialize all symbols from a symbol table to a JSON array. *) -let symbols_to_json (table : Symbol.t) : Yojson.Basic.t = - let syms = ref [] in - Hashtbl.iter (fun _id sym -> syms := sym :: !syms) table.all_symbols; - let sorted = List.sort (fun a b -> compare a.Symbol.sym_id b.Symbol.sym_id) !syms in - `List (List.map symbol_to_json sorted) - -(** A reference is a use-site span for a symbol. *) -type reference = { - ref_symbol_id : int; - ref_span : Span.t; -} - -(** Serialize references to JSON: { "symbol_id": [ spans... ] } *) -let references_to_json (refs : reference list) : Yojson.Basic.t = - let by_id = Hashtbl.create 64 in - List.iter (fun r -> - let existing = try Hashtbl.find by_id r.ref_symbol_id with Not_found -> [] in - Hashtbl.replace by_id r.ref_symbol_id (r.ref_span :: existing) - ) refs; - let entries = ref [] in - Hashtbl.iter (fun id spans -> - let span_jsons = List.map (fun s -> - `Assoc (span_to_json s) - ) (List.rev spans) in - entries := (string_of_int id, `List span_jsons) :: !entries - ) by_id; - `Assoc (List.sort compare !entries) - -(** Phase B report: diagnostics + symbol table + references. *) -let report_v2_to_json ~(success : bool) (diags : diagnostic list) - (symbols : Symbol.t) (refs : reference list) : Yojson.Basic.t = - `Assoc [ - ("version", `Int 2); - ("diagnostics", `List (List.map diagnostic_to_json diags)); - ("success", `Bool success); - ("symbols", symbols_to_json symbols); - ("references", references_to_json refs); - ] - -(** Emit a Phase B JSON report to stderr. *) -let emit_report_v2 ~(success : bool) (diags : diagnostic list) - (symbols : Symbol.t) (refs : reference list) : unit = - let json = report_v2_to_json ~success diags symbols refs in - Format.eprintf "%s@." (Yojson.Basic.to_string json) - -(** {1 Phase B: Hover and goto-definition queries} - - These entry points power the LSP hover and go-to-definition features. - Given a file path and cursor position (1-based line/col), the compiler - re-runs the pipeline and answers the query from the resolved symbol table. - - Output schema for hover: - {[ - { "found": true, - "name": "foo", - "kind": "function", - "type": "Int -> Int" | null, - "quantity": "linear" | null, - "def_file": "src/lib.affine", - "def_start_line": 3, "def_start_col": 1, - "def_end_line": 3, "def_end_col": 4 } - | { "found": false } - ]} - - Output schema for goto-definition: - {[ - { "found": true, - "file": "src/lib.affine", - "start_line": 3, "start_col": 1, - "end_line": 3, "end_col": 4 } - | { "found": false } - ]} -*) - -(** Check whether a span contains a given (line, col) position. - Lines and columns are 1-based, matching LSP convention. *) -let span_contains (span : Span.t) (line : int) (col : int) : bool = - let sl = span.start_pos.Span.line in - let sc = span.start_pos.Span.col in - let el = span.end_pos.Span.line in - let ec = span.end_pos.Span.col in - if sl = el then - (* Single-line span *) - sl = line && sc <= col && col <= ec - else - (* Multi-line span *) - (line = sl && col >= sc) - || (line > sl && line < el) - || (line = el && col <= ec) - -(** Find the symbol whose definition or use-site span covers the - given position. References are checked first (they are smaller - spans and therefore more precise); then definition spans. *) -let find_symbol_at - (symbols : Symbol.t) - (refs : reference list) - (line : int) - (col : int) - : Symbol.symbol option = - (* 1. Search references (use-sites). *) - let via_ref = - List.find_opt (fun (r : reference) -> span_contains r.ref_span line col) refs - in - begin match via_ref with - | Some r -> - Hashtbl.find_opt symbols.Symbol.all_symbols r.ref_symbol_id - | None -> - (* 2. Fallback: search definition spans. *) - let result = ref None in - Hashtbl.iter (fun _id (sym : Symbol.symbol) -> - if !result = None && span_contains sym.sym_span line col then - result := Some sym - ) symbols.Symbol.all_symbols; - !result - end - -(** Serialize a hover result to JSON. *) -let hover_to_json (sym : Symbol.symbol) : Yojson.Basic.t = - let type_str = match sym.sym_type with - | Some te -> `String (Ast.show_type_expr te) - | None -> `Null - in - let qty_str = match sym.sym_quantity with - | Some q -> `String (Ast.show_quantity q) - | None -> `Null - in - (* Prefix each span field with "def_" so the definition location is - distinguished from the cursor position in the response. *) - let def_span_fields = - List.map (fun (k, v) -> ("def_" ^ k, v)) (span_to_json sym.sym_span) - in - `Assoc ( - [ ("found", `Bool true); - ("name", `String sym.sym_name); - ("kind", `String (symbol_kind_to_string sym.sym_kind)); - ("type", type_str); - ("quantity", qty_str); - ] - @ def_span_fields - ) - -(** Serialize a goto-definition result to JSON. *) -let goto_def_to_json (sym : Symbol.symbol) : Yojson.Basic.t = - `Assoc ( - [("found", `Bool true)] - @ span_to_json sym.sym_span - ) - -(** Serialize a "not found" result. *) -let not_found_json : Yojson.Basic.t = `Assoc [("found", `Bool false)] - -(** Emit a hover JSON response on stdout. *) -let emit_hover (sym_opt : Symbol.symbol option) : unit = - let json = match sym_opt with - | Some sym -> hover_to_json sym - | None -> not_found_json - in - print_string (Yojson.Basic.to_string json); - print_newline () - -(** Emit a goto-definition JSON response on stdout. *) -let emit_goto_def (sym_opt : Symbol.symbol option) : unit = - let json = match sym_opt with - | Some sym -> goto_def_to_json sym - | None -> not_found_json - in - print_string (Yojson.Basic.to_string json); - print_newline () - -(** {1 Phase C: Completion candidates} - - Given a file path and cursor position (1-based), the [complete] subcommand - extracts the identifier prefix before the cursor, filters the symbol table - by that prefix, and emits a JSON array of completion candidates. - - Output schema: - {[ - [ - { "name": "add", - "kind": "function", - "type": "Int -> Int -> Int" | null, - "detail": "function" } - ] - ]} - - Keywords are included as candidates with kind ["keyword"] and null type - unless the cursor is in a dot-access context (e.g. [record.fie|]). -*) - -(** AffineScript keywords eligible for completion. *) -let affine_keywords : string list = [ - "fn"; "let"; "match"; "if"; "else"; "return"; - "effect"; "handle"; "trait"; "impl"; "type"; "where"; - "forall"; "use"; "pub"; "mut"; "true"; "false"; - "Self"; "Int"; "Bool"; "Float"; "String"; "Unit"; -] - -(** Extract the identifier prefix ending at column [col] on [line] in [source]. - - Scans backward from the character just before [col] (1-based) collecting - [a-zA-Z0-9_] characters. Returns [(prefix, dot_ctx)] where [dot_ctx] is - [true] when the character immediately preceding the prefix is ['.'], - indicating a field-access / method completion context. *) -let extract_prefix_at (source : string) (line : int) (col : int) : string * bool = - let lines = String.split_on_char '\n' source in - match List.nth_opt lines (line - 1) with - | None -> ("", false) - | Some line_text -> - (* col is 1-based; the character to the left of the cursor is at index col-2. *) - let end_idx = min (col - 2) (String.length line_text - 1) in - if end_idx < 0 then ("", false) - else begin - let is_ident_char c = - (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') || c = '_' - in - (* Collect identifier chars in reverse order. *) - let buf = Buffer.create 16 in - let i = ref end_idx in - while !i >= 0 && is_ident_char line_text.[!i] do - Buffer.add_char buf line_text.[!i]; - decr i - done; - (* Reverse to get forward order. *) - let rev_s = Buffer.contents buf in - let n = String.length rev_s in - let prefix = String.init n (fun k -> rev_s.[n - 1 - k]) in - let preceded_by_dot = !i >= 0 && line_text.[!i] = '.' in - (prefix, preceded_by_dot) - end - -(** A single completion candidate. *) -type completion_item = { - comp_name : string; - comp_kind : string; - comp_type : string option; - comp_detail : string; -} - -(** Serialize a completion item to JSON. *) -let completion_item_to_json (item : completion_item) : Yojson.Basic.t = - `Assoc [ - ("name", `String item.comp_name); - ("kind", `String item.comp_kind); - ("type", (match item.comp_type with Some t -> `String t | None -> `Null)); - ("detail", `String item.comp_detail); - ] - -(** Collect completion candidates from [symbols] whose name begins with [prefix]. - - All symbols match when [prefix] is empty. AffineScript keywords are - appended after symbol candidates unless [dot_ctx] is [true] (field-access - position — keywords don't apply there). *) -let collect_completions - (symbols : Symbol.t) - (prefix : string) - (dot_ctx : bool) - : completion_item list = - let prefix_len = String.length prefix in - let starts_with name = - String.length name >= prefix_len - && String.sub name 0 prefix_len = prefix - in - (* Symbol candidates. *) - let sym_items = ref [] in - Hashtbl.iter (fun _id (sym : Symbol.symbol) -> - if starts_with sym.sym_name then begin - let kind = symbol_kind_to_string sym.sym_kind in - let type_str = match sym.sym_type with - | Some te -> Some (Ast.show_type_expr te) - | None -> None - in - sym_items := { - comp_name = sym.sym_name; - comp_kind = kind; - comp_type = type_str; - comp_detail = kind; - } :: !sym_items - end - ) symbols.Symbol.all_symbols; - let sym_sorted = - List.sort (fun a b -> compare a.comp_name b.comp_name) !sym_items - in - (* Keyword candidates (not in dot-access context). *) - let keyword_items = - if dot_ctx then [] - else - List.filter_map (fun kw -> - if starts_with kw then - Some { comp_name = kw; comp_kind = "keyword"; - comp_type = None; comp_detail = "keyword" } - else None - ) affine_keywords - in - sym_sorted @ keyword_items - -(** Emit a completion JSON array on stdout. *) -let emit_completions (items : completion_item list) : unit = - let json = `List (List.map completion_item_to_json items) in - print_string (Yojson.Basic.to_string json); - print_newline () diff --git a/affinescript/lib/julia_codegen.ml b/affinescript/lib/julia_codegen.ml deleted file mode 100644 index 98d8adc..0000000 --- a/affinescript/lib/julia_codegen.ml +++ /dev/null @@ -1,412 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* BetLang Julia Code Generator - - Translates AffineScript AST to Julia source code. - - Phase 1 (MVP): Basic types (Float64, Int64, Bool), functions, arithmetic - Phase 2: DistnumberNormal, safety checks - Phase 3: All 11 number systems - Phase 4: Performance optimizations -*) - -open Ast - -(* ============================================================================ - Code Generation Context - ============================================================================ *) - -type codegen_ctx = { - output : Buffer.t; (* Accumulated Julia code *) - indent : int; (* Current indentation level *) - symbols : Symbol.t; (* Symbol table *) - in_function : bool; (* Track if inside function definition *) -} - -let create_ctx symbols = - { - output = Buffer.create 1024; - indent = 0; - symbols; - in_function = false; - } - -let emit ctx str = - Buffer.add_string ctx.output str - -let emit_line ctx str = - let spaces = String.make (ctx.indent * 4) ' ' in - Buffer.add_string ctx.output spaces; - Buffer.add_string ctx.output str; - Buffer.add_char ctx.output '\n' - -let increase_indent ctx = - { ctx with indent = ctx.indent + 1 } - -let decrease_indent ctx = - { ctx with indent = max 0 (ctx.indent - 1) } - -(* ============================================================================ - Type Translation (Phase 1: Basic types only) - ============================================================================ *) - -let rec type_expr_to_julia_string (te : type_expr) : string = - match te with - | TyCon name when name.name = "Int" -> "Int64" - | TyCon name when name.name = "Float" -> "Float64" - | TyCon name when name.name = "Bool" -> "Bool" - | TyCon name when name.name = "String" -> "String" - | TyCon name when name.name = "Unit" -> "Nothing" - | TyCon name -> name.name (* Custom type names pass through *) - | TyArrow (_, _, ret, _) -> - (* Function types: for now, just use ret type annotation *) - type_expr_to_julia_string ret - | TyTuple tys -> - (* Tuple types: Tuple{T1, T2, ...} *) - let ty_strs = List.map type_expr_to_julia_string tys in - "Tuple{" ^ String.concat ", " ty_strs ^ "}" - | TyRecord _ -> "NamedTuple" (* Record types map to NamedTuples *) - | TyVar _ -> "Any" (* Type variables: Any for now (Phase 1) *) - | _ -> "Any" (* Default fallback *) - -(* ============================================================================ - Expression Code Generation - ============================================================================ *) - -let rec gen_expr ctx (expr : expr) : string = - match expr with - | ExprLit lit -> gen_literal lit - | ExprVar name -> - (* Variable reference *) - name.name - | ExprApp (func, args) -> - (* Function application *) - let func_str = gen_expr ctx func in - let arg_strs = List.map (gen_expr ctx) args in - func_str ^ "(" ^ String.concat ", " arg_strs ^ ")" - | ExprBinary (e1, op, e2) -> - (* Binary operators *) - let op_str = match op with - | OpAdd -> "+" - | OpSub -> "-" - | OpMul -> "*" - | OpDiv -> "/" - | OpMod -> "%" - | OpEq -> "==" - | OpNe -> "!=" - | OpLt -> "<" - | OpLe -> "<=" - | OpGt -> ">" - | OpGe -> ">=" - | OpAnd -> "&&" - | OpOr -> "||" - | OpBitAnd -> "&" - | OpBitOr -> "|" - | OpBitXor -> "⊻" - | OpShl -> "<<" - | OpShr -> ">>" - | OpConcat -> "*" (* String/Array concatenation in Julia *) - in - "(" ^ gen_expr ctx e1 ^ " " ^ op_str ^ " " ^ gen_expr ctx e2 ^ ")" - | ExprUnary (op, e) -> - (* Unary operators *) - let op_str = match op with - | OpNeg -> "-" - | OpNot -> "!" - | OpBitNot -> "~" - | OpRef -> "Ref(" - | OpDeref -> "[]" - in - if op = OpRef then - op_str ^ gen_expr ctx e ^ ")" - else if op = OpDeref then - gen_expr ctx e ^ op_str - else - "(" ^ op_str ^ gen_expr ctx e ^ ")" - | ExprIf { ei_cond; ei_then; ei_else } -> - (* If-then-else: Julia uses 'if ... end' syntax *) - let cond_str = gen_expr ctx ei_cond in - let then_str = gen_expr ctx ei_then in - (match ei_else with - | Some else_br -> - let else_str = gen_expr ctx else_br in - "(if " ^ cond_str ^ "; " ^ then_str ^ "; else " ^ else_str ^ "; end)" - | None -> - "(if " ^ cond_str ^ "; " ^ then_str ^ "; end)") - | ExprLet { el_pat; el_value; el_body; el_mut = _; el_quantity = _; el_ty = _ } -> - (* Let binding: 'local x = val; body' *) - let pat_str = gen_pattern ctx el_pat in - let val_str = gen_expr ctx el_value in - (match el_body with - | Some body -> - let body_str = gen_expr ctx body in - "(local " ^ pat_str ^ " = " ^ val_str ^ "; " ^ body_str ^ ")" - | None -> - "local " ^ pat_str ^ " = " ^ val_str) - | ExprTuple exprs -> - (* Tuple: (e1, e2, ...) *) - let expr_strs = List.map (gen_expr ctx) exprs in - "(" ^ String.concat ", " expr_strs ^ ")" - | ExprRecord { er_fields; er_spread = _ } -> - (* Record/NamedTuple: (field1=val1, field2=val2) *) - let field_strs = List.map (fun (name, e_opt) -> - let val_str = match e_opt with - | Some e -> gen_expr ctx e - | None -> name.name - in - name.name ^ "=" ^ val_str - ) er_fields in - "(; " ^ String.concat ", " field_strs ^ ")" - | ExprField (record, field) -> - (* Field access: record.field *) - gen_expr ctx record ^ "." ^ field.name - | ExprMatch { em_scrutinee; em_arms } -> - (* Pattern matching: translate to if-elseif chain (Phase 1) *) - gen_match ctx em_scrutinee em_arms - | ExprBlock block -> - (* Block: (stmt1; stmt2; ...; result) *) - let stmt_strs = List.map (gen_stmt ctx) block.blk_stmts in - let result_str = match block.blk_expr with - | Some e -> gen_expr ctx e - | None -> "" - in - "(begin\n" ^ String.concat "\n" stmt_strs ^ "\n" ^ result_str ^ "\nend)" - | ExprReturn (Some e) -> - "return " ^ gen_expr ctx e - | ExprReturn None -> - "return" - | ExprTry { et_body; et_catch; et_finally } -> - (* Emit native Julia try/catch/finally. - Julia's try block is a statement, so we use a local variable to - capture the body result and return it from a begin..end expression. - Phase 1.5 limitation: only the first catch arm is emitted; variable - and wildcard patterns name the catch variable directly. *) - let render_block blk = - let stmts = List.map (gen_stmt ctx) blk.blk_stmts in - let result = match blk.blk_expr with - | Some e -> gen_expr ctx e - | None -> "nothing" - in - match stmts with - | [] -> result - | _ -> String.concat "\n " stmts ^ "\n " ^ result - in - let body_str = render_block et_body in - let catch_str = match et_catch with - | None | Some [] -> "" - | Some (arm :: _) -> - let catch_var = match arm.ma_pat with - | PatVar id -> id.name - | _ -> "_" - in - let arm_body = gen_expr ctx arm.ma_body in - "\n catch " ^ catch_var ^ "\n __try_result = " ^ arm_body - in - let finally_str = match et_finally with - | None -> "" - | Some blk -> - let fin_str = render_block blk in - "\n finally\n " ^ fin_str - in - "(begin\n local __try_result\n try\n __try_result = " ^ - body_str ^ catch_str ^ finally_str ^ - "\n end\n __try_result\nend)" - | _ -> - (* Unsupported expressions in Phase 1 *) - "error(\"Unsupported expression\")" - -and gen_literal (lit : literal) : string = - match lit with - | LitInt (n, _) -> string_of_int n - | LitFloat (f, _) -> string_of_float f - | LitBool (true, _) -> "true" - | LitBool (false, _) -> "false" - | LitString (s, _) -> "\"" ^ String.escaped s ^ "\"" - | LitChar (c, _) -> "'" ^ Char.escaped c ^ "'" - | LitUnit _ -> "nothing" - -and gen_pattern ctx (pat : pattern) : string = - match pat with - | PatWildcard _ -> "_" - | PatVar name -> name.name - | PatLit lit -> gen_literal lit - | PatTuple pats -> - let pat_strs = List.map (gen_pattern ctx) pats in - "(" ^ String.concat ", " pat_strs ^ ")" - | _ -> "_" (* Unsupported patterns: wildcard *) - -and gen_match ctx scrutinee cases = - (* Phase 1: Simple pattern matching via if-elseif chain *) - let scrutinee_str = gen_expr ctx scrutinee in - let rec gen_cases remaining = - match remaining with - | [] -> "error(\"Non-exhaustive pattern match\")" - | [arm] -> - (* Last case: else branch *) - let body_str = gen_expr ctx arm.ma_body in - body_str - | arm :: rest -> - let cond_str = gen_pattern_cond ctx scrutinee_str arm.ma_pat in - let body_str = gen_expr ctx arm.ma_body in - let rest_str = gen_cases rest in - "if " ^ cond_str ^ "; " ^ body_str ^ "; else " ^ rest_str ^ "; end" - in - "(" ^ gen_cases cases ^ ")" - -and gen_pattern_cond _ctx scrutinee pat = - match pat with - | PatWildcard _ -> "true" - | PatVar _ -> "true" (* Variable pattern always matches *) - | PatLit lit -> - scrutinee ^ " == " ^ gen_literal lit - | _ -> "true" - -and gen_stmt ctx (stmt : stmt) : string = - (* Statements (for blocks) *) - match stmt with - | StmtLet { sl_pat; sl_value; sl_mut = _; sl_quantity = _; sl_ty = _ } -> - let pat_str = gen_pattern ctx sl_pat in - let val_str = gen_expr ctx sl_value in - pat_str ^ " = " ^ val_str - | StmtExpr e -> - gen_expr ctx e - | StmtAssign (lhs, op, rhs) -> - let lhs_str = gen_expr ctx lhs in - let rhs_str = gen_expr ctx rhs in - let op_str = match op with - | AssignEq -> "=" - | AssignAdd -> "+=" - | AssignSub -> "-=" - | AssignMul -> "*=" - | AssignDiv -> "/=" - in - lhs_str ^ " " ^ op_str ^ " " ^ rhs_str - | StmtWhile (cond, body) -> - (* While loop: while cond; body; end *) - let cond_str = gen_expr ctx cond in - let body_strs = List.map (gen_stmt ctx) body.blk_stmts in - let body_expr_str = match body.blk_expr with - | Some e -> gen_expr ctx e - | None -> "" - in - "while " ^ cond_str ^ "\n" ^ String.concat "\n" body_strs ^ - (if body_expr_str <> "" then "\n" ^ body_expr_str else "") ^ "\nend" - | StmtFor (pat, iter, body) -> - (* For loop: for pat in iter; body; end *) - let pat_str = gen_pattern ctx pat in - let iter_str = gen_expr ctx iter in - let body_strs = List.map (gen_stmt ctx) body.blk_stmts in - let body_expr_str = match body.blk_expr with - | Some e -> gen_expr ctx e - | None -> "" - in - "for " ^ pat_str ^ " in " ^ iter_str ^ "\n" ^ String.concat "\n" body_strs ^ - (if body_expr_str <> "" then "\n" ^ body_expr_str else "") ^ "\nend" - -(* ============================================================================ - Top-Level Declaration Code Generation - ============================================================================ *) - -let gen_function ctx (fd : fn_decl) : unit = - (* Function declaration: - function name(param1::Type1, param2::Type2)::ReturnType - body - end - *) - let name = fd.fd_name.name in - - (* Parameters with type annotations *) - let param_strs = List.map (fun param -> - let param_name = param.p_name.name in - let param_ty = type_expr_to_julia_string param.p_ty in - param_name ^ "::" ^ param_ty - ) fd.fd_params in - - (* Return type annotation *) - let ret_ty_str = match fd.fd_ret_ty with - | Some ty -> "::" ^ type_expr_to_julia_string ty - | None -> "" - in - - (* Function signature *) - emit_line ctx ("function " ^ name ^ "(" ^ String.concat ", " param_strs ^ ")" ^ ret_ty_str); - - (* Function body *) - let ctx_body = increase_indent ctx in - let ctx_body = { ctx_body with in_function = true } in - - (match fd.fd_body with - | FnExpr body_expr -> - let body_str = gen_expr ctx_body body_expr in - emit_line ctx_body ("return " ^ body_str) - | FnBlock block -> - (* Generate block statements *) - List.iter (fun stmt -> - let stmt_str = gen_stmt ctx_body stmt in - emit_line ctx_body stmt_str - ) block.blk_stmts; - (* Generate final expression if present *) - (match block.blk_expr with - | Some e -> - let expr_str = gen_expr ctx_body e in - emit_line ctx_body ("return " ^ expr_str) - | None -> ())); - - (* End function *) - emit_line ctx "end"; - emit ctx "\n" - -let gen_type_decl ctx (td : type_decl) : unit = - (* Type declarations: - Phase 1: Skip complex types, just emit comments - Phase 2: Implement struct types - Phase 3: Implement all types - *) - let name = td.td_name.name in - emit_line ctx ("# Type declaration: " ^ name); - emit_line ctx ("# Body: " ^ match td.td_body with - | TyAlias _ -> "alias" - | TyStruct _ -> "struct" - | TyEnum _ -> "enum" - ); - emit ctx "\n" - -let gen_top_level ctx (top : top_level) : unit = - match top with - | TopFn fd -> gen_function ctx fd - | TopType td -> gen_type_decl ctx td - | TopConst _ -> emit_line ctx "# Constant (not yet implemented)" - | TopEffect _ -> emit_line ctx "# Effect declaration (not yet implemented)" - | TopTrait _ -> emit_line ctx "# Trait (not yet implemented)" - | TopImpl _ -> emit_line ctx "# Impl block (not yet implemented)" - -(* ============================================================================ - Main Code Generation Entry Point - ============================================================================ *) - -let generate (program : program) (symbols : Symbol.t) : string = - (* Generate Julia code from AST *) - let ctx = create_ctx symbols in - - (* Header comment *) - emit_line ctx "# Generated by BetLang compiler (AffineScript)"; - emit_line ctx "# SPDX-License-Identifier: MPL-2.0"; - emit ctx "\n"; - - (* Generate each top-level declaration *) - (* Note: Skipping prog_imports for Phase 1 - imports are import_decl, not top_level *) - List.iter (gen_top_level ctx) program.prog_decls; - - (* Return accumulated output *) - Buffer.contents ctx.output - -(* ============================================================================ - Public API - ============================================================================ *) - -let codegen_julia (program : program) (symbols : Symbol.t) : (string, string) result = - try - let code = generate program symbols in - Ok code - with - | Failure msg -> Error ("Julia codegen error: " ^ msg) - | e -> Error ("Julia codegen error: " ^ Printexc.to_string e) diff --git a/affinescript/lib/lexer.ml b/affinescript/lib/lexer.ml deleted file mode 100644 index 406945c..0000000 --- a/affinescript/lib/lexer.ml +++ /dev/null @@ -1,335 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Lexer for AffineScript using sedlex *) - -open Token - -exception Lexer_error of string * Span.pos - -(** Keywords lookup table *) -let keywords = Hashtbl.create 64 -let () = - List.iter (fun (k, v) -> Hashtbl.add keywords k v) - [ - ("fn", FN); - ("let", LET); - ("const", CONST); - ("mut", MUT); - ("own", OWN); - ("ref", REF); - ("type", TYPE); - ("struct", STRUCT); - ("enum", ENUM); - ("trait", TRAIT); - ("impl", IMPL); - ("effect", EFFECT); - ("handle", HANDLE); - ("resume", RESUME); - ("match", MATCH); - ("if", IF); - ("else", ELSE); - ("while", WHILE); - ("for", FOR); - ("return", RETURN); - ("break", BREAK); - ("continue", CONTINUE); - ("in", IN); - ("where", WHERE); - ("total", TOTAL); - ("module", MODULE); - ("use", USE); - ("pub", PUB); - ("as", AS); - ("unsafe", UNSAFE); - ("assume", ASSUME); - ("transmute", TRANSMUTE); - ("forget", FORGET); - ("try", TRY); - ("catch", CATCH); - ("finally", FINALLY); - ("self", SELF_KW); - ("true", TRUE); - ("false", FALSE); - ("omega", OMEGA); - (* Built-in types *) - ("Nat", NAT); - ("Int", INT_T); - ("Bool", BOOL); - ("Float", FLOAT_T); - ("String", STRING_T); - ("Char", CHAR_T); - ("Type", TYPE_K); - ("Row", ROW); - ("Never", NEVER); - ] - -(** Lexer state *) -type state = { - mutable line : int; - mutable col : int; - mutable line_start : int; - file : string; -} - -let create_state file = { line = 1; col = 1; line_start = 0; file } - -let current_pos state buf = - let offset = Sedlexing.lexeme_start buf in - { Span.line = state.line; col = offset - state.line_start + 1; offset } - -let update_newlines state buf = - let lexeme = Sedlexing.Utf8.lexeme buf in - String.iter (fun c -> - if c = '\n' then begin - state.line <- state.line + 1; - state.line_start <- Sedlexing.lexeme_end buf - end - ) lexeme - -(** Character classes *) -let digit = [%sedlex.regexp? '0'..'9'] -let hex_digit = [%sedlex.regexp? '0'..'9' | 'a'..'f' | 'A'..'F'] -let bin_digit = [%sedlex.regexp? '0' | '1'] -let oct_digit = [%sedlex.regexp? '0'..'7'] -let lower = [%sedlex.regexp? 'a'..'z'] -let upper = [%sedlex.regexp? 'A'..'Z'] -let alpha = [%sedlex.regexp? lower | upper] -let alphanum = [%sedlex.regexp? alpha | digit | '_'] - -let lower_ident = [%sedlex.regexp? lower, Star alphanum] -let upper_ident = [%sedlex.regexp? upper, Star alphanum] - -let int_lit = [%sedlex.regexp? Opt '-', Plus digit] -let hex_lit = [%sedlex.regexp? "0x", Plus hex_digit] -let bin_lit = [%sedlex.regexp? "0b", Plus bin_digit] -let oct_lit = [%sedlex.regexp? "0o", Plus oct_digit] - -(* The exponent is defined as a named sub-regexp so that sedlex groups it as a - single unit inside Opt. Writing `Opt ('e' | 'E', Opt ('+' | '-'), Plus digit)` - directly causes sedlex to treat the three comma-separated items as top-level - concatenation elements rather than as arguments to Opt, resulting in the - exponent digits NOT being included in the float match and `float_of_string` - receiving a trailing-e string such as "1.0e" that it cannot parse. *) -let float_exponent = [%sedlex.regexp? ('e' | 'E'), Opt ('+' | '-'), Plus digit] -let float_lit = [%sedlex.regexp? - Opt '-', Plus digit, '.', Plus digit, Opt float_exponent] - -let whitespace = [%sedlex.regexp? ' ' | '\t' | '\r'] -let newline = [%sedlex.regexp? '\n'] - -(** Main lexer function *) -let rec token state buf = - match%sedlex buf with - (* Whitespace *) - | Plus whitespace -> token state buf - | newline -> - state.line <- state.line + 1; - state.line_start <- Sedlexing.lexeme_end buf; - token state buf - - (* Comments *) - | "//" -> - line_comment state buf; - token state buf - | "/*" -> - block_comment state buf 1; - token state buf - - (* Literals *) - | hex_lit -> - let s = Sedlexing.Utf8.lexeme buf in - INT (int_of_string s) - | bin_lit -> - let s = Sedlexing.Utf8.lexeme buf in - INT (int_of_string s) - | oct_lit -> - let s = Sedlexing.Utf8.lexeme buf in - INT (int_of_string s) - | float_lit -> - let s = Sedlexing.Utf8.lexeme buf in - FLOAT (float_of_string s) - | int_lit -> - let s = Sedlexing.Utf8.lexeme buf in - INT (int_of_string s) - | '"' -> - STRING (string_lit state buf (Buffer.create 64)) - | '\'' -> - CHAR (char_lit state buf) - - (* Omega symbol *) - | 0x03C9 -> OMEGA (* ω *) - - (* Multi-char operators *) - | "->" -> ARROW - | "=>" -> FAT_ARROW - | "::" -> COLONCOLON - (* Row variable "..name" — must come before ".." so sedlex prefers the longer match *) - | "..", lower_ident -> - let s = Sedlexing.Utf8.lexeme buf in - ROW_VAR (String.sub s 2 (String.length s - 2)) - | ".." -> DOTDOT - | "++" -> PLUSPLUS - | "==" -> EQEQ - | "!=" -> NE - | "<=" -> LE - | ">=" -> GE - | "<<" -> LTLT - | ">>" -> GTGT - | "&&" -> AMPAMP - | "||" -> PIPEPIPE - | "+=" -> PLUSEQ - | "-=" -> MINUSEQ - | "*=" -> STAREQ - | "/=" -> SLASHEQ - - (* Single-char tokens *) - | '(' -> LPAREN - | ')' -> RPAREN - | '{' -> LBRACE - | '}' -> RBRACE - | '[' -> LBRACKET - | ']' -> RBRACKET - | ',' -> COMMA - | ';' -> SEMICOLON - | ':' -> COLON - | '.' -> DOT - | '|' -> PIPE - | '@' -> AT - | '_' -> UNDERSCORE - | '\\' -> BACKSLASH - | '?' -> QUESTION - | '+' -> PLUS - | '-' -> MINUS - | '*' -> STAR - | '/' -> SLASH - | '%' -> PERCENT - | '=' -> EQ - | '<' -> LT - | '>' -> GT - | '!' -> BANG - | '&' -> AMP - | '^' -> CARET - | '~' -> TILDE - - (* Quantity: special handling for 0 and 1 after certain contexts *) - (* For now, just lex as integers; parser will handle context *) - - (* Identifiers *) - | lower_ident -> - let s = Sedlexing.Utf8.lexeme buf in - (try Hashtbl.find keywords s with Not_found -> LOWER_IDENT s) - | upper_ident -> - let s = Sedlexing.Utf8.lexeme buf in - (try Hashtbl.find keywords s with Not_found -> UPPER_IDENT s) - - (* End of file *) - | eof -> EOF - - (* Error *) - | any -> - let pos = current_pos state buf in - let c = Sedlexing.Utf8.lexeme buf in - raise (Lexer_error (Printf.sprintf "Unexpected character: %s" c, pos)) - | _ -> - let pos = current_pos state buf in - raise (Lexer_error ("Unexpected end of input", pos)) - -and line_comment state buf = - match%sedlex buf with - | newline -> - state.line <- state.line + 1; - state.line_start <- Sedlexing.lexeme_end buf - | eof -> () - | any -> line_comment state buf - | _ -> () - -and block_comment state buf depth = - match%sedlex buf with - | "*/" -> - if depth = 1 then () - else block_comment state buf (depth - 1) - | "/*" -> - block_comment state buf (depth + 1) - | newline -> - state.line <- state.line + 1; - state.line_start <- Sedlexing.lexeme_end buf; - block_comment state buf depth - | eof -> - let pos = current_pos state buf in - raise (Lexer_error ("Unterminated block comment", pos)) - | any -> - block_comment state buf depth - | _ -> - let pos = current_pos state buf in - raise (Lexer_error ("Unexpected end of input in comment", pos)) - -and string_lit state buf acc = - match%sedlex buf with - | '"' -> Buffer.contents acc - | "\\n" -> Buffer.add_char acc '\n'; string_lit state buf acc - | "\\r" -> Buffer.add_char acc '\r'; string_lit state buf acc - | "\\t" -> Buffer.add_char acc '\t'; string_lit state buf acc - | "\\\\" -> Buffer.add_char acc '\\'; string_lit state buf acc - | "\\\"" -> Buffer.add_char acc '"'; string_lit state buf acc - | "\\'" -> Buffer.add_char acc '\''; string_lit state buf acc - | "\\0" -> Buffer.add_char acc '\000'; string_lit state buf acc - | "\\x", hex_digit, hex_digit -> - let s = Sedlexing.Utf8.lexeme buf in - let code = int_of_string ("0x" ^ String.sub s 2 2) in - Buffer.add_char acc (Char.chr code); - string_lit state buf acc - | newline -> - state.line <- state.line + 1; - state.line_start <- Sedlexing.lexeme_end buf; - Buffer.add_char acc '\n'; - string_lit state buf acc - | eof -> - let pos = current_pos state buf in - raise (Lexer_error ("Unterminated string literal", pos)) - | any -> - Buffer.add_string acc (Sedlexing.Utf8.lexeme buf); - string_lit state buf acc - | _ -> - let pos = current_pos state buf in - raise (Lexer_error ("Invalid character in string", pos)) - -and char_lit state buf = - match%sedlex buf with - | "\\n", '\'' -> '\n' - | "\\r", '\'' -> '\r' - | "\\t", '\'' -> '\t' - | "\\\\", '\'' -> '\\' - | "\\'", '\'' -> '\'' - | "\\\"", '\'' -> '"' - | "\\0", '\'' -> '\000' - | any, '\'' -> - let s = Sedlexing.Utf8.lexeme buf in - if String.length s = 2 then s.[0] - else begin - let pos = current_pos state buf in - raise (Lexer_error ("Invalid character literal", pos)) - end - | _ -> - let pos = current_pos state buf in - raise (Lexer_error ("Invalid character literal", pos)) - -(** Create a token stream from a string *) -let from_string ~file content = - let buf = Sedlexing.Utf8.from_string content in - let state = create_state file in - fun () -> - let start_pos = current_pos state buf in - let tok = token state buf in - let end_pos = current_pos state buf in - (tok, Span.make ~file ~start_pos ~end_pos) - -(** Create a token stream from a channel *) -let from_channel ~file chan = - let buf = Sedlexing.Utf8.from_channel chan in - let state = create_state file in - fun () -> - let start_pos = current_pos state buf in - let tok = token state buf in - let end_pos = current_pos state buf in - (tok, Span.make ~file ~start_pos ~end_pos) diff --git a/affinescript/lib/linter.ml b/affinescript/lib/linter.ml deleted file mode 100644 index 69becef..0000000 --- a/affinescript/lib/linter.ml +++ /dev/null @@ -1,438 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** AffineScript linter - static analysis for code quality *) - -open Ast -open Symbol - -(** Lint severity levels *) -type severity = - | Error - | Warning - | Hint - | Info - -(** Lint diagnostic *) -type diagnostic = { - severity: severity; - code: string; - message: string; - span: Span.t; - help: string option; -} - -(** Lint context *) -type context = { - mutable diagnostics: diagnostic list; - symbols: Symbol.t; - mutable used_vars: string list; - mutable defined_vars: string list; -} - -(** Create a new lint context *) -let create_context (symbols : Symbol.t) : context = { - diagnostics = []; - symbols; - used_vars = []; - defined_vars = []; -} - -(** Add a diagnostic *) -let add_diagnostic (ctx : context) (diag : diagnostic) : unit = - ctx.diagnostics <- diag :: ctx.diagnostics - -(** Report unused variable *) -let report_unused_var (ctx : context) (name : string) (span : Span.t) : unit = - if not (List.mem name ctx.used_vars) && name <> "_" then - add_diagnostic ctx { - severity = Warning; - code = "L001"; - message = Printf.sprintf "Unused variable '%s'" name; - span; - help = Some "Remove this variable or prefix with '_' to indicate intentionally unused"; - } - -(** Report missing effect annotation *) -let report_missing_effect (ctx : context) (name : string) (span : Span.t) : unit = - add_diagnostic ctx { - severity = Error; - code = "L002"; - message = Printf.sprintf "Function '%s' may perform effects but lacks effect annotation" name; - span; - help = Some "Add effect annotation: fn name() -> T / EffectName"; - } - -(** Report dead code *) -let report_dead_code (ctx : context) (span : Span.t) : unit = - add_diagnostic ctx { - severity = Warning; - code = "L003"; - message = "Unreachable code detected"; - span; - help = Some "This code will never execute, consider removing it"; - } - -(** Report naming convention violation *) -let report_naming_convention (ctx : context) (kind : string) (name : string) (span : Span.t) : unit = - let expected = match kind with - | "function" -> "snake_case" - | "type" -> "PascalCase" - | "constant" -> "SCREAMING_SNAKE_CASE" - | _ -> "unknown" - in - add_diagnostic ctx { - severity = Hint; - code = "L004"; - message = Printf.sprintf "%s '%s' does not follow %s naming convention" - (String.capitalize_ascii kind) name expected; - span; - help = Some (Printf.sprintf "Consider renaming to match %s convention" expected); - } - -(** Check if identifier follows snake_case *) -let is_snake_case (s : string) : bool = - String.for_all (fun c -> - (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c = '_' - ) s - -(** Check if identifier follows PascalCase *) -let is_pascal_case (s : string) : bool = - if String.length s = 0 then false - else - let first = s.[0] in - (first >= 'A' && first <= 'Z') && - String.for_all (fun c -> - (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') - ) s - -(** Check if identifier follows SCREAMING_SNAKE_CASE *) -let is_screaming_snake_case (s : string) : bool = - String.for_all (fun c -> - (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c = '_' - ) s - -(** Extract variable names from pattern *) -let rec pattern_vars (pat : pattern) : (string * Span.t) list = - match pat with - | PatWildcard _ -> [] - | PatVar id -> [(id.name, id.span)] - | PatLit _ -> [] - | PatTuple pats -> List.concat_map pattern_vars pats - | PatRecord (fields, _) -> - List.concat_map (fun (id, pat_opt) -> - match pat_opt with - | Some p -> pattern_vars p - | None -> [(id.name, id.span)] - ) fields - | PatCon (_, pats) -> List.concat_map pattern_vars pats - | PatOr (p1, p2) -> pattern_vars p1 @ pattern_vars p2 - | PatAs (id, pat) -> (id.name, id.span) :: pattern_vars pat - -(** Check if expression contains effect-performing calls *) -let rec has_effects (ctx : context) (e : expr) : bool = - match e with - | ExprApp (ExprVar id, _) -> - (* Check if called function has effects *) - (match Symbol.lookup ctx.symbols id.name with - | Some entry -> - (match entry.sym_kind with - | SKFunction -> (* TODO(#84): check function signature for effects *) false - | _ -> false) - | None -> false) - | ExprHandle _ -> true - | ExprResume _ -> true - | ExprBinary (e1, _, e2) -> has_effects ctx e1 || has_effects ctx e2 - | ExprUnary (_, e) -> has_effects ctx e - | ExprApp (func, args) -> - has_effects ctx func || List.exists (has_effects ctx) args - | ExprIf { ei_cond; ei_then; ei_else } -> - has_effects ctx ei_cond || has_effects ctx ei_then || - (match ei_else with Some e -> has_effects ctx e | None -> false) - | ExprLet { el_value; el_body; _ } -> - has_effects ctx el_value || - (match el_body with Some e -> has_effects ctx e | None -> false) - | ExprBlock blk -> - List.exists (stmt_has_effects ctx) blk.blk_stmts || - (match blk.blk_expr with Some e -> has_effects ctx e | None -> false) - | ExprTuple exprs -> List.exists (has_effects ctx) exprs - | ExprRecord { er_fields; er_spread } -> - List.exists (fun (_, e_opt) -> - match e_opt with Some e -> has_effects ctx e | None -> false - ) er_fields || - (match er_spread with Some e -> has_effects ctx e | None -> false) - | ExprField (e, _) -> has_effects ctx e - | ExprTupleIndex (e, _) -> has_effects ctx e - | ExprArray exprs -> List.exists (has_effects ctx) exprs - | ExprIndex (arr, idx) -> has_effects ctx arr || has_effects ctx idx - | ExprMatch { em_scrutinee; em_arms } -> - has_effects ctx em_scrutinee || - List.exists (fun arm -> has_effects ctx arm.ma_body) em_arms - | ExprLambda { elam_body; _ } -> has_effects ctx elam_body - | ExprTry { et_body; et_catch; et_finally } -> - List.exists (stmt_has_effects ctx) et_body.blk_stmts || - (match et_catch with - | Some arms -> List.exists (fun arm -> has_effects ctx arm.ma_body) arms - | None -> false) || - (match et_finally with - | Some blk -> List.exists (stmt_has_effects ctx) blk.blk_stmts - | None -> false) - | ExprReturn e_opt -> - (match e_opt with Some e -> has_effects ctx e | None -> false) - | ExprUnsafe _ -> true - | ExprRowRestrict (e, _) -> has_effects ctx e - | ExprSpan (e, _) -> has_effects ctx e - | ExprLit _ | ExprVar _ | ExprVariant _ -> false - -and stmt_has_effects (ctx : context) (stmt : stmt) : bool = - match stmt with - | StmtExpr e -> has_effects ctx e - | StmtLet { sl_value; _ } -> has_effects ctx sl_value - | StmtAssign (lhs, _, rhs) -> has_effects ctx lhs || has_effects ctx rhs - | StmtWhile (cond, body) -> - has_effects ctx cond || - List.exists (stmt_has_effects ctx) body.blk_stmts - | StmtFor (_, iter, body) -> - has_effects ctx iter || - List.exists (stmt_has_effects ctx) body.blk_stmts - -(** Detect unreachable code after return *) -let rec check_dead_code (ctx : context) (stmts : stmt list) : unit = - let rec aux prev_terminal = function - | [] -> () - | stmt :: rest -> - if prev_terminal then - report_dead_code ctx (span_of_stmt stmt); - - let is_terminal = match stmt with - | StmtExpr (ExprReturn _) -> true - | _ -> false - in - aux is_terminal rest - in - aux false stmts - -and span_of_stmt (stmt : stmt) : Span.t = - match stmt with - | StmtExpr e -> span_of_expr e - | StmtLet { sl_pat; _ } -> span_of_pattern sl_pat - | StmtAssign (lhs, _, _) -> span_of_expr lhs - | StmtWhile (cond, _) -> span_of_expr cond - | StmtFor (pat, _, _) -> span_of_pattern pat - -and span_of_expr (e : expr) : Span.t = - match e with - | ExprVar id -> id.span - | ExprSpan (_, span) -> span - | ExprLit (LitInt (_, s)) -> s - | ExprLit (LitFloat (_, s)) -> s - | ExprLit (LitBool (_, s)) -> s - | ExprLit (LitChar (_, s)) -> s - | ExprLit (LitString (_, s)) -> s - | ExprLit (LitUnit s) -> s - | _ -> Span.dummy (* TODO(#84): add spans to all expressions *) - -and span_of_pattern (pat : pattern) : Span.t = - match pat with - | PatVar id -> id.span - | PatWildcard s -> s - | PatLit lit -> - (match lit with - | LitInt (_, s) | LitFloat (_, s) | LitBool (_, s) - | LitChar (_, s) | LitString (_, s) | LitUnit s -> s) - | _ -> Span.dummy (* TODO(#84): add spans to all patterns *) - -(** Lint expression *) -let rec lint_expr (ctx : context) (e : expr) : unit = - match e with - | ExprVar id -> - if not (List.mem id.name ctx.used_vars) then - ctx.used_vars <- id.name :: ctx.used_vars - - | ExprLet { el_pat; el_value; el_body; _ } -> - lint_expr ctx el_value; - let vars = pattern_vars el_pat in - List.iter (fun (name, span) -> - ctx.defined_vars <- name :: ctx.defined_vars; - (match el_body with - | Some body -> lint_expr ctx body - | None -> ()); - report_unused_var ctx name span - ) vars - - | ExprBinary (e1, _, e2) -> - lint_expr ctx e1; - lint_expr ctx e2 - - | ExprUnary (_, e) -> lint_expr ctx e - - | ExprApp (func, args) -> - lint_expr ctx func; - List.iter (lint_expr ctx) args - - | ExprIf { ei_cond; ei_then; ei_else } -> - lint_expr ctx ei_cond; - lint_expr ctx ei_then; - Option.iter (lint_expr ctx) ei_else - - | ExprMatch { em_scrutinee; em_arms } -> - lint_expr ctx em_scrutinee; - List.iter (fun arm -> - Option.iter (lint_expr ctx) arm.ma_guard; - lint_expr ctx arm.ma_body - ) em_arms - - | ExprBlock blk -> - check_dead_code ctx blk.blk_stmts; - List.iter (lint_stmt ctx) blk.blk_stmts; - Option.iter (lint_expr ctx) blk.blk_expr - - | ExprTuple exprs -> List.iter (lint_expr ctx) exprs - - | ExprRecord { er_fields; er_spread } -> - List.iter (fun (_, e_opt) -> - Option.iter (lint_expr ctx) e_opt - ) er_fields; - Option.iter (lint_expr ctx) er_spread - - | ExprField (e, _) -> lint_expr ctx e - | ExprTupleIndex (e, _) -> lint_expr ctx e - | ExprArray exprs -> List.iter (lint_expr ctx) exprs - - | ExprIndex (arr, idx) -> - lint_expr ctx arr; - lint_expr ctx idx - - | ExprLambda { elam_params; elam_body; _ } -> - let param_vars = List.concat_map (fun p -> [(p.p_name.name, p.p_name.span)]) elam_params in - List.iter (fun (name, _) -> ctx.defined_vars <- name :: ctx.defined_vars) param_vars; - lint_expr ctx elam_body; - List.iter (fun (name, span) -> report_unused_var ctx name span) param_vars - - | ExprHandle { eh_body; eh_handlers } -> - lint_expr ctx eh_body; - List.iter (fun h -> - match h with - | HandlerReturn (_, body) -> lint_expr ctx body - | HandlerOp (_, _, body) -> lint_expr ctx body - ) eh_handlers - - | ExprResume e_opt -> Option.iter (lint_expr ctx) e_opt - | ExprReturn e_opt -> Option.iter (lint_expr ctx) e_opt - - | ExprTry { et_body; et_catch; et_finally } -> - List.iter (lint_stmt ctx) et_body.blk_stmts; - Option.iter (List.iter (fun arm -> lint_expr ctx arm.ma_body)) et_catch; - Option.iter (fun blk -> List.iter (lint_stmt ctx) blk.blk_stmts) et_finally - - | ExprUnsafe _ -> () (* TODO(#84): lint unsafe operations *) - | ExprRowRestrict (e, _) -> lint_expr ctx e - | ExprSpan (e, _) -> lint_expr ctx e - | ExprLit _ | ExprVariant _ -> () - -(** Lint statement *) -and lint_stmt (ctx : context) (stmt : stmt) : unit = - match stmt with - | StmtExpr e -> lint_expr ctx e - - | StmtLet { sl_pat; sl_value; _ } -> - lint_expr ctx sl_value; - let vars = pattern_vars sl_pat in - List.iter (fun (name, _) -> ctx.defined_vars <- name :: ctx.defined_vars) vars - - | StmtAssign (lhs, _, rhs) -> - lint_expr ctx lhs; - lint_expr ctx rhs - - | StmtWhile (cond, body) -> - lint_expr ctx cond; - List.iter (lint_stmt ctx) body.blk_stmts - - | StmtFor (pat, iter, body) -> - lint_expr ctx iter; - let vars = pattern_vars pat in - List.iter (fun (name, _) -> ctx.defined_vars <- name :: ctx.defined_vars) vars; - List.iter (lint_stmt ctx) body.blk_stmts - -(** Lint function declaration *) -let lint_fun_decl (ctx : context) (fd : fn_decl) : unit = - (* Check naming convention *) - if not (is_snake_case fd.fd_name.name) && not (String.starts_with ~prefix:"_" fd.fd_name.name) then - report_naming_convention ctx "function" fd.fd_name.name fd.fd_name.span; - - (* Check for missing effect annotations *) - let has_body_effects = match fd.fd_body with - | FnBlock blk -> - List.exists (stmt_has_effects ctx) blk.blk_stmts || - (match blk.blk_expr with Some e -> has_effects ctx e | None -> false) - | FnExpr e -> has_effects ctx e - in - if has_body_effects && fd.fd_eff = None then - report_missing_effect ctx fd.fd_name.name fd.fd_name.span; - - (* Lint function body *) - (match fd.fd_body with - | FnBlock blk -> - List.iter (lint_stmt ctx) blk.blk_stmts; - Option.iter (lint_expr ctx) blk.blk_expr - | FnExpr e -> lint_expr ctx e) - -(** Lint top-level declaration *) -let lint_top_level (ctx : context) (top : top_level) : unit = - match top with - | TopFn fd -> lint_fun_decl ctx fd - - | TopType { td_name; _ } -> - if not (is_pascal_case td_name.name) then - report_naming_convention ctx "type" td_name.name td_name.span - - | TopTrait { trd_name; trd_items; _ } -> - if not (is_pascal_case trd_name.name) then - report_naming_convention ctx "type" trd_name.name trd_name.span; - List.iter (fun item -> - match item with - | TraitFnDefault fd -> lint_fun_decl ctx fd - | _ -> () - ) trd_items - - | TopImpl { ib_items; _ } -> - List.iter (fun item -> - match item with - | ImplFn fd -> lint_fun_decl ctx fd - | _ -> () - ) ib_items - - | TopConst { tc_name; tc_value; _ } -> - if not (is_screaming_snake_case tc_name.name) then - report_naming_convention ctx "constant" tc_name.name tc_name.span; - lint_expr ctx tc_value - - | TopEffect _ -> () - -(** Lint program *) -let lint_program (symbols : Symbol.t) (prog : program) : diagnostic list = - let ctx = create_context symbols in - List.iter (lint_top_level ctx) prog.prog_decls; - List.rev ctx.diagnostics - -(** Format diagnostic *) -let format_diagnostic (diag : diagnostic) : string = - let severity_str = match diag.severity with - | Error -> Error_formatter.colorize Error_formatter.Red "error" - | Warning -> Error_formatter.colorize Error_formatter.Yellow "warning" - | Hint -> Error_formatter.colorize Error_formatter.Cyan "hint" - | Info -> Error_formatter.colorize Error_formatter.Green "info" - in - let loc = Format.asprintf "%a" Span.pp_short diag.span in - let help_str = match diag.help with - | Some h -> "\n " ^ Error_formatter.colorize Error_formatter.Green ("help: " ^ h) - | None -> "" - in - Printf.sprintf "%s [%s] %s: %s%s" - loc diag.code severity_str diag.message help_str - -(** Print diagnostics *) -let print_diagnostics (diags : diagnostic list) : unit = - List.iter (fun diag -> - Format.printf "%s@." (format_diagnostic diag) - ) diags diff --git a/affinescript/lib/linter.mli.disabled b/affinescript/lib/linter.mli.disabled deleted file mode 100644 index 4416557..0000000 --- a/affinescript/lib/linter.mli.disabled +++ /dev/null @@ -1,29 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** AffineScript linter - static analysis for code quality *) - -(** Lint severity levels *) -type severity = - | Error - | Warning - | Hint - | Info - -(** Lint diagnostic *) -type diagnostic = { - severity: severity; - code: string; - message: string; - span: Span.t; - help: string option; -} - -(** Lint a program and return diagnostics *) -val lint_program : Symbol.t -> Ast.program -> diagnostic list - -(** Format a diagnostic to a string *) -val format_diagnostic : diagnostic -> string - -(** Print diagnostics to stdout *) -val print_diagnostics : diagnostic list -> unit diff --git a/affinescript/lib/lsp_server.ml b/affinescript/lib/lsp_server.ml deleted file mode 100644 index ba085e5..0000000 --- a/affinescript/lib/lsp_server.ml +++ /dev/null @@ -1,562 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** JSON-RPC LSP server for AffineScript (LSP 3.17 subset). - - Runs over stdin/stdout using Content-Length framing. - - Supported capabilities: - - Full document synchronisation (didOpen / didChange / didClose) - - Diagnostic push (textDocument/publishDiagnostics) - - Hover (textDocument/hover) - - Go-to-definition (textDocument/definition) - - Completion (textDocument/completion) - - Start with: [affinescript server --stdio] - - The server runs entirely in-process — no subprocess spawning. The - compiler pipeline is invoked directly for each document open/change - event and results are cached per URI. Hover, definition, and - completion queries read from the cache. -*) - -(** {1 State} *) - -(** In-memory document store: URI → source text. *) -let documents : (string, string) Hashtbl.t = Hashtbl.create 16 - -(** Per-URI pipeline cache entry. *) -type cache_entry = { - symbols : Symbol.t; - refs : Json_output.reference list; - diags : Json_output.diagnostic list; -} - -(** Pipeline cache: URI → last successful pipeline result. *) -let cache : (string, cache_entry) Hashtbl.t = Hashtbl.create 16 - -(** {1 URI utilities} *) - -(** Strip the [file://] prefix to recover a filesystem path. - - [file:///home/user/foo.affine] → [/home/user/foo.affine]. *) -let uri_to_path (uri : string) : string = - if String.length uri >= 7 && String.sub uri 0 7 = "file://" - then String.sub uri 7 (String.length uri - 7) - else uri - -(** Prepend [file://] to a filesystem path to form a URI. *) -let path_to_uri (path : string) : string = "file://" ^ path - -(** {1 LSP transport — Content-Length framing} *) - -(** Read one JSON-RPC message from stdin using Content-Length framing. - Returns [None] on EOF or unrecoverable I/O error. *) -let read_message () : Yojson.Basic.t option = - try - let content_length = ref 0 in - (* Read HTTP-style headers until the blank separator line. *) - let rec read_headers () = - let line = input_line stdin in - (* Trim the trailing \r that appears in \r\n line endings. *) - let line = - let n = String.length line in - if n > 0 && line.[n - 1] = '\r' then String.sub line 0 (n - 1) - else line - in - if line <> "" then begin - let prefix = "Content-Length: " in - let plen = String.length prefix in - if String.length line >= plen && String.sub line 0 plen = prefix then - (try - content_length := - int_of_string (String.trim - (String.sub line plen (String.length line - plen))) - with Failure _ -> ()); - read_headers () - end - in - read_headers (); - if !content_length = 0 then None - else begin - let buf = Bytes.create !content_length in - really_input stdin buf 0 !content_length; - (try Some (Yojson.Basic.from_string (Bytes.to_string buf)) - with _ -> None) - end - with End_of_file | Sys_error _ -> None - -(** Serialise and write one JSON-RPC message to stdout. *) -let write_message (json : Yojson.Basic.t) : unit = - let body = Yojson.Basic.to_string json in - let n = String.length body in - print_string (Printf.sprintf "Content-Length: %d\r\n\r\n%s" n body); - flush stdout - -(** {1 JSON-RPC helpers} *) - -(** Build a success response. *) -let response ~(id : Yojson.Basic.t) ~(result : Yojson.Basic.t) : Yojson.Basic.t = - `Assoc [ - ("jsonrpc", `String "2.0"); - ("id", id); - ("result", result); - ] - -(** Build an error response (JSON-RPC error object). *) -let error_response ~(id : Yojson.Basic.t) ~(code : int) ~(message : string) - : Yojson.Basic.t = - `Assoc [ - ("jsonrpc", `String "2.0"); - ("id", id); - ("error", `Assoc [("code", `Int code); ("message", `String message)]); - ] - -(** Build a server→client notification (no [id]). *) -let notification ~(method_ : string) ~(params : Yojson.Basic.t) - : Yojson.Basic.t = - `Assoc [ - ("jsonrpc", `String "2.0"); - ("method", `String method_); - ("params", params); - ] - -(** Look up a string field inside a JSON object; [None] if absent or wrong type. *) -let field_string (key : string) (obj : Yojson.Basic.t) : string option = - match obj with - | `Assoc fields -> - (match List.assoc_opt key fields with - | Some (`String s) -> Some s - | _ -> None) - | _ -> None - -(** Look up an int field inside a JSON object; [None] if absent or wrong type. *) -let field_int (key : string) (obj : Yojson.Basic.t) : int option = - match obj with - | `Assoc fields -> - (match List.assoc_opt key fields with - | Some (`Int n) -> Some n - | _ -> None) - | _ -> None - -(** Look up a nested JSON object field; [`Null] if absent or wrong type. *) -let field_obj (key : string) (obj : Yojson.Basic.t) : Yojson.Basic.t = - match obj with - | `Assoc fields -> - (match List.assoc_opt key fields with - | Some v -> v - | None -> `Null) - | _ -> `Null - -(** {1 Compiler pipeline runner} *) - -(** Rewrite every span's [file] field to [path] so that diagnostics - refer to the editor's path rather than the temp file. *) -let fix_span (path : string) (s : Span.t) : Span.t = { s with file = path } - -let fix_diag (path : string) (d : Json_output.diagnostic) - : Json_output.diagnostic = - { d with - span = fix_span path d.span; - labels = List.map (fun (s, msg) -> (fix_span path s, msg)) d.labels; - } - -(** Run the full compiler pipeline on [source] (the editor's in-memory text). - [path] is the logical filesystem path used for error spans. - Returns [(diagnostics, symbols_option, references)]. - - The source is written to a temp file so the existing [Parse_driver] - can be re-used unchanged. The temp file is cleaned up on exit. *) -let run_pipeline (path : string) (source : string) - : Json_output.diagnostic list * Symbol.t option * Json_output.reference list = - let tmp = Filename.temp_file "affinescript_lsp" ".affine" in - Fun.protect - ~finally:(fun () -> try Sys.remove tmp with _ -> ()) - (fun () -> - let oc = open_out tmp in - output_string oc source; - close_out oc; - let diags = ref [] in - let add d = diags := d :: !diags in - let symbols_ref = ref None in - let refs_ref = ref [] in - (try - let prog = Parse_driver.parse_file tmp in - let loader_config = Module_loader.default_config () in - let loader = Module_loader.create loader_config in - (match Resolve.resolve_program_with_loader prog loader with - | Error (e, span) -> - add (fix_diag path (Json_output.of_resolve_error e span)) - | Ok (resolve_ctx, _) -> - symbols_ref := Some resolve_ctx.symbols; - refs_ref := - List.rev resolve_ctx.references - |> List.map (fun (r : Resolve.reference) -> - Json_output.{ - ref_symbol_id = r.ref_symbol_id; - ref_span = fix_span path r.ref_span; - }); - (match Typecheck.check_program resolve_ctx.symbols prog with - | Error e -> add (fix_diag path (Json_output.of_type_error e)) - | Ok _ -> - (match Borrow.check_program resolve_ctx.symbols prog with - | Error e -> add (fix_diag path (Json_output.of_borrow_error e)) - | Ok () -> - (match Quantity.check_program_quantities prog with - | Error (err, span) -> - add (fix_diag path (Json_output.of_quantity_error (err, span))) - | Ok () -> ())))) - with - | Lexer.Lexer_error (msg, pos) -> - add (fix_diag path (Json_output.of_lexer_error msg pos path)) - | Parse_driver.Parse_error (msg, span) -> - add (fix_diag path (Json_output.of_parse_error msg span))); - (List.rev !diags, !symbols_ref, !refs_ref)) - -(** {1 LSP position and range helpers} *) - -(** Convert a 1-based compiler column/line to a 0-based LSP position. - - LSP uses 0-based line and character offsets (LSP 3.17 §3.17.1). - The compiler uses 1-based positions throughout. *) -let lsp_pos (line : int) (col : int) : Yojson.Basic.t = - `Assoc [ - ("line", `Int (max 0 (line - 1))); - ("character", `Int (max 0 (col - 1))); - ] - -(** Convert a compiler [Span.t] to an LSP [Range] (0-based). *) -let lsp_range (s : Span.t) : Yojson.Basic.t = - `Assoc [ - ("start", lsp_pos s.start_pos.Span.line s.start_pos.Span.col); - ("end", lsp_pos s.end_pos.Span.line s.end_pos.Span.col); - ] - -(** {1 LSP diagnostic serialization} *) - -(** Map a diagnostic severity to an LSP DiagnosticSeverity integer. - LSP: 1=Error 2=Warning 3=Information 4=Hint. *) -let lsp_severity = function - | Json_output.Error -> 1 - | Json_output.Warning -> 2 - | Json_output.Info -> 3 - | Json_output.Hint -> 4 - -(** Serialise one compiler diagnostic to the LSP Diagnostic object format. *) -let diag_to_lsp (d : Json_output.diagnostic) : Yojson.Basic.t = - let base = [ - ("range", lsp_range d.span); - ("severity", `Int (lsp_severity d.severity)); - ("code", `String d.code); - ("source", `String "affinescript"); - ("message", `String d.message); - ] in - let with_help = match d.help with - | None -> base - | Some h -> - base @ [("relatedInformation", `List [ - `Assoc [ - ("location", `Assoc [ - ("uri", `String (path_to_uri d.span.file)); - ("range", lsp_range d.span); - ]); - ("message", `String h); - ] - ])] - in - `Assoc with_help - -(** Send a [textDocument/publishDiagnostics] notification for [uri]. *) -let publish_diagnostics (uri : string) (diags : Json_output.diagnostic list) - : unit = - write_message (notification - ~method_:"textDocument/publishDiagnostics" - ~params:(`Assoc [ - ("uri", `String uri); - ("diagnostics", `List (List.map diag_to_lsp diags)); - ])) - -(** {1 CompletionItemKind mapping} *) - -(** Map an AffineScript symbol kind string to an LSP CompletionItemKind. - LSP 3.17 §3.18.1 values used: 2=Method 3=Function 4=Constructor - 6=Variable 7=Class 8=Interface 9=Module 14=Keyword 25=TypeParameter. *) -let lsp_completion_kind (kind : string) : int = - match kind with - | "function" -> 3 - | "variable" -> 6 - | "type" -> 7 - | "type_variable" -> 25 - | "constructor" -> 4 - | "trait" -> 8 - | "effect" -> 8 - | "effect_operation" -> 2 - | "module" -> 9 - | "keyword" -> 14 - | _ -> 1 (* Text fallback *) - -(** {1 Request handlers} *) - -(** Handle [initialize]: declare server capabilities and reply. *) -let handle_initialize (id : Yojson.Basic.t) (_params : Yojson.Basic.t) : unit = - write_message (response ~id ~result:( - `Assoc [ - ("capabilities", `Assoc [ - (* Full document sync — send complete text on every change. *) - ("textDocumentSync", `Int 1); - ("hoverProvider", `Bool true); - ("definitionProvider", `Bool true); - ("completionProvider", `Assoc [ - ("triggerCharacters", `List [`String "."; `String "@"]); - ("resolveProvider", `Bool false); - ]); - ]); - ("serverInfo", `Assoc [ - ("name", `String "affinescript"); - ("version", `String "0.1.0"); - ]); - ] - )) - -(** Run the pipeline on [source], cache the result, and push diagnostics. *) -let refresh_document (uri : string) (source : string) : unit = - Hashtbl.replace documents uri source; - let path = uri_to_path uri in - let (diags, symbols_opt, refs) = run_pipeline path source in - (match symbols_opt with - | Some symbols -> Hashtbl.replace cache uri { symbols; refs; diags } - | None -> Hashtbl.remove cache uri); - publish_diagnostics uri diags - -let handle_did_open (uri : string) (source : string) : unit = - refresh_document uri source - -let handle_did_change (uri : string) (source : string) : unit = - refresh_document uri source - -let handle_did_close (uri : string) : unit = - Hashtbl.remove documents uri; - Hashtbl.remove cache uri; - (* Clear the editor's diagnostic panel for this file. *) - publish_diagnostics uri [] - -(** Handle [textDocument/hover]: return symbol info at cursor. - - [line] and [col] are 0-based (LSP convention). Converted to 1-based - before querying the symbol table. *) -let handle_hover - (id : Yojson.Basic.t) - (uri : string) - (line : int) - (col : int) - : unit = - let line1 = line + 1 in - let col1 = col + 1 in - let result = - match Hashtbl.find_opt cache uri with - | None -> `Null - | Some entry -> - (match Json_output.find_symbol_at entry.symbols entry.refs line1 col1 with - | None -> `Null - | Some sym -> - let type_md = match sym.sym_type with - | Some te -> Printf.sprintf "\n```affinescript\n%s\n```" - (Ast.show_type_expr te) - | None -> "" - in - let qty_md = match sym.sym_quantity with - | Some q -> Printf.sprintf "\n\n*quantity: %s*" (Ast.show_quantity q) - | None -> "" - in - `Assoc [ - ("contents", `Assoc [ - ("kind", `String "markdown"); - ("value", `String (Printf.sprintf - "**%s** — %s%s%s" - sym.sym_name - (Json_output.symbol_kind_to_string sym.sym_kind) - type_md - qty_md)); - ]); - (* Range is optional in LSP hover; include it so editors can - highlight the exact symbol token. *) - ("range", lsp_range sym.sym_span); - ]) - in - write_message (response ~id ~result) - -(** Handle [textDocument/definition]: return the definition location. - - [line] and [col] are 0-based. *) -let handle_definition - (id : Yojson.Basic.t) - (uri : string) - (line : int) - (col : int) - : unit = - let line1 = line + 1 in - let col1 = col + 1 in - let result = - match Hashtbl.find_opt cache uri with - | None -> `Null - | Some entry -> - (match Json_output.find_symbol_at entry.symbols entry.refs line1 col1 with - | None -> `Null - | Some sym -> - `Assoc [ - ("uri", `String (path_to_uri sym.sym_span.Span.file)); - ("range", lsp_range sym.sym_span); - ]) - in - write_message (response ~id ~result) - -(** Handle [textDocument/completion]: return candidates at cursor. - - [line] and [col] are 0-based. The prefix is extracted from the - in-memory source using [Json_output.extract_prefix_at] (1-based API). *) -let handle_completion - (id : Yojson.Basic.t) - (uri : string) - (line : int) - (col : int) - : unit = - let line1 = line + 1 in - let col1 = col + 1 in - let items = - match Hashtbl.find_opt cache uri, Hashtbl.find_opt documents uri with - | Some entry, Some source -> - let (prefix, dot_ctx) = - Json_output.extract_prefix_at source line1 col1 - in - Json_output.collect_completions entry.symbols prefix dot_ctx - | _ -> [] - in - let lsp_items = List.map (fun (item : Json_output.completion_item) -> - let kind = lsp_completion_kind item.Json_output.comp_kind in - let detail = match item.Json_output.comp_type with - | Some t -> t - | None -> item.Json_output.comp_kind - in - `Assoc [ - ("label", `String item.Json_output.comp_name); - ("kind", `Int kind); - ("detail", `String detail); - ("insertText", `String item.Json_output.comp_name); - ] - ) items in - write_message (response ~id ~result:( - `Assoc [ - ("isIncomplete", `Bool false); - ("items", `List lsp_items); - ] - )) - -(** {1 Main message dispatch} *) - -(** Handle one incoming JSON-RPC message. - Returns [false] when the server should stop ([exit] notification). *) -let handle_message (msg : Yojson.Basic.t) : bool = - let field k = field_obj k msg in - let method_opt = - match field "method" with `String s -> Some s | _ -> None - in - let id = field "id" in - let params = field "params" in - (match method_opt with - - (* ── Lifecycle ── *) - | Some "initialize" -> handle_initialize id params - | Some "initialized" -> () (* Client confirmation — no response needed. *) - | Some "shutdown" -> - write_message (response ~id ~result:`Null) - | Some "exit" -> () (* Handled via return value below. *) - - (* ── Document synchronisation ── *) - | Some "textDocument/didOpen" -> - let td = field_obj "textDocument" params in - (match field_string "uri" td, field_string "text" td with - | Some uri, Some text -> handle_did_open uri text - | _ -> ()) - - | Some "textDocument/didChange" -> - let td = field_obj "textDocument" params in - let changes = field_obj "contentChanges" params in - (match field_string "uri" td with - | None -> () - | Some uri -> - (* With full sync (mode 1) the array has exactly one entry. *) - let text_opt = - match changes with - | `List (first :: _) -> field_string "text" first - | _ -> None - in - (match text_opt with - | Some text -> handle_did_change uri text - | None -> ())) - - | Some "textDocument/didClose" -> - let td = field_obj "textDocument" params in - (match field_string "uri" td with - | Some uri -> handle_did_close uri - | None -> ()) - - (* ── Queries ── *) - | Some "textDocument/hover" -> - let td = field_obj "textDocument" params in - let pos = field_obj "position" params in - (match field_string "uri" td, field_int "line" pos, field_int "character" pos with - | Some uri, Some line, Some col -> handle_hover id uri line col - | _ -> write_message (response ~id ~result:`Null)) - - | Some "textDocument/definition" -> - let td = field_obj "textDocument" params in - let pos = field_obj "position" params in - (match field_string "uri" td, field_int "line" pos, field_int "character" pos with - | Some uri, Some line, Some col -> handle_definition id uri line col - | _ -> write_message (response ~id ~result:`Null)) - - | Some "textDocument/completion" -> - let td = field_obj "textDocument" params in - let pos = field_obj "position" params in - (match field_string "uri" td, field_int "line" pos, field_int "character" pos with - | Some uri, Some line, Some col -> handle_completion id uri line col - | _ -> - write_message (response ~id ~result:( - `Assoc [("isIncomplete", `Bool false); ("items", `List [])]))) - - (* ── Unknown ── *) - | Some m -> - (* For requests (have an [id]), reply with MethodNotFound. - For notifications (no [id]), silently ignore. *) - (match id with - | `Null -> () - | _ -> - write_message (error_response ~id ~code:(-32601) - ~message:(Printf.sprintf "Method not found: %s" m))) - - | None -> ()); (* Ignore stray messages with no method field. *) - - (* Stop the loop on the exit notification. *) - match method_opt with Some "exit" -> false | _ -> true - -(** {1 Server entry point} *) - -(** Start the LSP server loop. - - Switches stdin/stdout to binary mode (required for correct Content-Length - framing on all platforms), then reads and dispatches messages until the - client sends an [exit] notification or EOF is reached. *) -let run () : unit = - set_binary_mode_out stdout true; - set_binary_mode_in stdin true; - let continue = ref true in - while !continue do - match read_message () with - | None -> continue := false - | Some msg -> - (try continue := handle_message msg - with e -> - (* Never let a handler exception kill the server loop. *) - Format.eprintf "affinescript-lsp: unhandled exception: %s@." - (Printexc.to_string e)) - done diff --git a/affinescript/lib/module_loader.ml b/affinescript/lib/module_loader.ml deleted file mode 100644 index be3e2f4..0000000 --- a/affinescript/lib/module_loader.ml +++ /dev/null @@ -1,192 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2025 hyperpolymath *) - -(** Module loading and resolution. - - This module handles finding, loading, and caching modules from the file system. -*) - -open Ast - -(** Module loading errors *) -type load_error = - | ModuleNotFound of string list (** Module path not found *) - | ModuleParseError of string * string (** File path and parse error *) - | ModuleCycle of string list (** Circular dependency detected *) - | InvalidModulePath of string list -[@@deriving show] - -(** Helper for Result bind *) -let ( let* ) = Result.bind - -(** Module loader configuration *) -type config = { - stdlib_path : string; (** Path to standard library *) - search_paths : string list; (** Additional module search paths *) - current_dir : string; (** Current working directory *) -} - -(** Loaded module information *) -type loaded_module = { - mod_path : string list; (** Module path (e.g., ["Math", "Geometry"]) *) - mod_program : program; (** Parsed AST *) - mod_file : string; (** Source file path *) -} - -(** Module loader state *) -type t = { - config : config; - loaded : (string list, loaded_module) Hashtbl.t; (** Cache of loaded modules *) - loading : (string list, unit) Hashtbl.t; (** Currently loading (for cycle detection) *) -} - -(** Create a new module loader *) -let create (config : config) : t = - { - config; - loaded = Hashtbl.create 32; - loading = Hashtbl.create 16; - } - -(** Create default configuration *) -let default_config () : config = - let stdlib_path = - try Sys.getenv "AFFINESCRIPT_STDLIB" - with Not_found -> "./stdlib" - in - { - stdlib_path; - search_paths = []; - current_dir = Sys.getcwd (); - } - -(** Convert module path to file path candidates - - For module path ["Math", "Geometry"], try: - 1. Math/Geometry.affine (nested module) - 2. Math.affine (check if it exports Geometry) -*) -let path_to_candidates (mod_path : string list) : string list = - match mod_path with - | [] -> [] - | [name] -> [name ^ ".affine"] - | _ -> - (* Try nested path: A/B/C.affine *) - let nested = String.concat "/" mod_path ^ ".affine" in - (* Try parent with submodule: A/B.affine (if B declares module C) *) - let parent_parts = List.rev mod_path |> List.tl |> List.rev in - let parent = String.concat "/" parent_parts ^ ".affine" in - [nested; parent] - -(** Search for a module file in search paths *) -let find_module_file (loader : t) (mod_path : string list) : string option = - let candidates = path_to_candidates mod_path in - let search_in_dir dir = - List.find_map (fun candidate -> - let full_path = Filename.concat dir candidate in - if Sys.file_exists full_path then Some full_path else None - ) candidates - in - (* Search order: 1) current dir, 2) stdlib, 3) additional search paths *) - let all_paths = - loader.config.current_dir :: - loader.config.stdlib_path :: - loader.config.search_paths - in - List.find_map search_in_dir all_paths - -(** Load and parse a module file *) -let parse_module_file (file_path : string) : (program, load_error) result = - try - let prog = Parse_driver.parse_file file_path in - Ok prog - with - | Lexer.Lexer_error (msg, _pos) -> - Error (ModuleParseError (file_path, "Lexer error: " ^ msg)) - | Parse_driver.Parse_error (msg, _span) -> - Error (ModuleParseError (file_path, "Parse error: " ^ msg)) - | Sys_error msg -> - Error (ModuleParseError (file_path, "System error: " ^ msg)) - -(** Load a module and all its dependencies - - Returns the loaded module or an error if loading fails. - Note: Does NOT resolve symbols - that's done by the caller. -*) -let rec load_module (loader : t) (mod_path : string list) : (loaded_module, load_error) result = - (* Check if already loaded *) - match Hashtbl.find_opt loader.loaded mod_path with - | Some m -> Ok m - | None -> - (* Check for circular dependency *) - if Hashtbl.mem loader.loading mod_path then - Error (ModuleCycle mod_path) - else begin - (* Mark as loading *) - Hashtbl.add loader.loading mod_path (); - - (* Find the module file *) - match find_module_file loader mod_path with - | None -> Error (ModuleNotFound mod_path) - | Some file_path -> - (* Parse the module *) - match parse_module_file file_path with - | Error e -> Error e - | Ok prog -> - (* Load dependencies first (imports) *) - let* () = load_dependencies loader prog.prog_imports in - - let loaded_mod = { - mod_path; - mod_program = prog; - mod_file = file_path; - } in - - (* Cache the loaded module *) - Hashtbl.add loader.loaded mod_path loaded_mod; - Hashtbl.remove loader.loading mod_path; - - Ok loaded_mod - end - -(** Load all dependencies for a module *) -and load_dependencies (loader : t) (imports : import_decl list) : (unit, load_error) result = - List.fold_left (fun acc import -> - match acc with - | Error e -> Error e - | Ok () -> - let mod_paths = match import with - | ImportSimple (path, _alias) -> [List.map (fun id -> id.name) path] - | ImportList (path, _items) -> [List.map (fun id -> id.name) path] - | ImportGlob path -> [List.map (fun id -> id.name) path] - in - List.fold_left (fun acc' mod_path -> - match acc' with - | Error e -> Error e - | Ok () -> - match load_module loader mod_path with - | Ok _ -> Ok () - | Error e -> Error e - ) acc mod_paths - ) (Ok ()) imports - -(** Get a loaded module by path *) -let get_module (loader : t) (mod_path : string list) : loaded_module option = - Hashtbl.find_opt loader.loaded mod_path - -(** Get the loaded module's program *) -let get_program (loaded_mod : loaded_module) : program = - loaded_mod.mod_program - -(** Pretty-print module path *) -let show_module_path (path : string list) : string = - String.concat "::" path - -(** Check if a module is loaded *) -let is_loaded (loader : t) (mod_path : string list) : bool = - Hashtbl.mem loader.loaded mod_path - -(** Clear the module cache (for testing/reloading) *) -let clear_cache (loader : t) : unit = - Hashtbl.clear loader.loaded; - Hashtbl.clear loader.loading diff --git a/affinescript/lib/opt.ml b/affinescript/lib/opt.ml deleted file mode 100644 index 5e006d6..0000000 --- a/affinescript/lib/opt.ml +++ /dev/null @@ -1,158 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Optimization passes for AffineScript AST. - - This module implements various optimization transformations on the AST - before code generation. -*) - -open Ast - -(** Constant folding - evaluate constant expressions at compile time *) -let rec fold_constants_expr (expr : expr) : expr = - match expr with - | ExprBinary (ExprLit (LitInt (a, _)), op, ExprLit (LitInt (b, _))) -> - (* Fold integer binary operations *) - begin match op with - | OpAdd -> ExprLit (LitInt (a + b, Span.dummy)) - | OpSub -> ExprLit (LitInt (a - b, Span.dummy)) - | OpMul -> ExprLit (LitInt (a * b, Span.dummy)) - | OpDiv when b <> 0 -> ExprLit (LitInt (a / b, Span.dummy)) - | OpMod when b <> 0 -> ExprLit (LitInt (a mod b, Span.dummy)) - | OpEq -> ExprLit (LitBool (a = b, Span.dummy)) - | OpNe -> ExprLit (LitBool (a <> b, Span.dummy)) - | OpLt -> ExprLit (LitBool (a < b, Span.dummy)) - | OpLe -> ExprLit (LitBool (a <= b, Span.dummy)) - | OpGt -> ExprLit (LitBool (a > b, Span.dummy)) - | OpGe -> ExprLit (LitBool (a >= b, Span.dummy)) - | _ -> expr (* Don't fold other ops or division by zero *) - end - - | ExprBinary (ExprLit (LitBool (a, _)), op, ExprLit (LitBool (b, _))) -> - (* Fold boolean binary operations *) - begin match op with - | OpAnd -> ExprLit (LitBool (a && b, Span.dummy)) - | OpOr -> ExprLit (LitBool (a || b, Span.dummy)) - | OpEq -> ExprLit (LitBool (a = b, Span.dummy)) - | OpNe -> ExprLit (LitBool (a <> b, Span.dummy)) - | _ -> expr - end - - | ExprUnary (OpNeg, ExprLit (LitInt (n, _))) -> - ExprLit (LitInt (-n, Span.dummy)) - - | ExprUnary (OpNot, ExprLit (LitBool (b, _))) -> - ExprLit (LitBool (not b, Span.dummy)) - - | ExprBinary (left, op, right) -> - let left' = fold_constants_expr left in - let right' = fold_constants_expr right in - if left == left' && right == right' then - expr - else - ExprBinary (left', op, right') - - | ExprUnary (op, operand) -> - let operand' = fold_constants_expr operand in - if operand == operand' then - expr - else - ExprUnary (op, operand') - - | ExprIf ei -> - let cond' = fold_constants_expr ei.ei_cond in - let then' = fold_constants_expr ei.ei_then in - let else' = Option.map fold_constants_expr ei.ei_else in - - (* If condition is constant, select branch at compile time *) - begin match cond' with - | ExprLit (LitBool (true, _)) -> then' - | ExprLit (LitBool (false, _)) -> - begin match else' with - | Some e -> e - | None -> ExprLit (LitUnit Span.dummy) - end - | _ -> ExprIf { ei_cond = cond'; ei_then = then'; ei_else = else' } - end - - | ExprLet el -> - ExprLet { - el with - el_value = fold_constants_expr el.el_value; - el_body = Option.map fold_constants_expr el.el_body; - } - - | ExprMatch em -> - ExprMatch { - em_scrutinee = fold_constants_expr em.em_scrutinee; - em_arms = List.map (fun arm -> { arm with ma_body = fold_constants_expr arm.ma_body }) em.em_arms; - } - - | ExprLambda lam -> - ExprLambda { lam with elam_body = fold_constants_expr lam.elam_body } - - | ExprApp (func, args) -> - ExprApp (fold_constants_expr func, List.map fold_constants_expr args) - - | ExprField (e, f) -> - ExprField (fold_constants_expr e, f) - - | ExprTupleIndex (e, i) -> - ExprTupleIndex (fold_constants_expr e, i) - - | ExprIndex (arr, idx) -> - ExprIndex (fold_constants_expr arr, fold_constants_expr idx) - - | ExprTuple exprs -> - ExprTuple (List.map fold_constants_expr exprs) - - | ExprArray exprs -> - ExprArray (List.map fold_constants_expr exprs) - - | ExprRecord er -> - ExprRecord { - er_fields = List.map (fun (f, e_opt) -> (f, Option.map fold_constants_expr e_opt)) er.er_fields; - er_spread = Option.map fold_constants_expr er.er_spread; - } - - | ExprBlock blk -> - ExprBlock (fold_constants_block blk) - - | ExprReturn e_opt -> - ExprReturn (Option.map fold_constants_expr e_opt) - - | _ -> expr - -and fold_constants_block (blk : block) : block = - { - blk_stmts = List.map fold_constants_stmt blk.blk_stmts; - blk_expr = Option.map fold_constants_expr blk.blk_expr; - } - -and fold_constants_stmt (stmt : stmt) : stmt = - match stmt with - | StmtLet sl -> - StmtLet { sl with sl_value = fold_constants_expr sl.sl_value } - | StmtAssign (lhs, op, rhs) -> - StmtAssign (fold_constants_expr lhs, op, fold_constants_expr rhs) - | StmtWhile (cond, body) -> - StmtWhile (fold_constants_expr cond, fold_constants_block body) - | StmtFor (pat, iter, body) -> - StmtFor (pat, fold_constants_expr iter, fold_constants_block body) - | StmtExpr e -> - StmtExpr (fold_constants_expr e) - -let fold_constants_decl (decl : top_level) : top_level = - match decl with - | TopFn fd -> - begin match fd.fd_body with - | FnBlock blk -> - TopFn { fd with fd_body = FnBlock (fold_constants_block blk) } - | FnExpr e -> - TopFn { fd with fd_body = FnExpr (fold_constants_expr e) } - end - | _ -> decl - -let fold_constants_program (prog : program) : program = - { prog with prog_decls = List.map fold_constants_decl prog.prog_decls } diff --git a/affinescript/lib/parse.ml b/affinescript/lib/parse.ml deleted file mode 100644 index 6c1de19..0000000 --- a/affinescript/lib/parse.ml +++ /dev/null @@ -1,219 +0,0 @@ -(** Parser wrapper for AffineScript *) - -exception Parse_error of string * Span.t - -(** Menhir incremental parser with error recovery *) -module I = Parser.MenhirInterpreter - -(** State for parser with lexer *) -type state = { - lexer_state : Lexer.state; - buf : Sedlexing.lexbuf; - mutable last_token : Token.t * Span.t; -} - -(** Convert sedlex position to Lexing.position for Menhir *) -let make_lexing_position (file : string) (pos : Span.pos) : Lexing.position = - { - Lexing.pos_fname = file; - pos_lnum = pos.Span.line; - pos_bol = pos.offset - pos.col + 1; - pos_cnum = pos.offset; - } - -(** Create parser state from string *) -let create_state ~file content = - let buf = Sedlexing.Utf8.from_string content in - let lexer_state = Lexer.create_state file in - { lexer_state; buf; last_token = (Token.EOF, Span.dummy) } - -(** Create parser state from channel *) -let create_state_channel ~file chan = - let buf = Sedlexing.Utf8.from_channel chan in - let lexer_state = Lexer.create_state file in - { lexer_state; buf; last_token = (Token.EOF, Span.dummy) } - -(** Get next token for Menhir *) -let next_token state () = - let start_pos = Lexer.current_pos state.lexer_state state.buf in - let tok = Lexer.token state.lexer_state state.buf in - let end_pos = Lexer.current_pos state.lexer_state state.buf in - let span = Span.make ~file:state.lexer_state.file ~start_pos ~end_pos in - state.last_token <- (tok, span); - - (* Convert to Menhir token with positions *) - let start_lexpos = make_lexing_position state.lexer_state.file start_pos in - let end_lexpos = make_lexing_position state.lexer_state.file end_pos in - - (* Map our tokens to parser tokens *) - let menhir_tok = match tok with - | Token.INT n -> Parser.INT n - | Token.FLOAT f -> Parser.FLOAT f - | Token.CHAR c -> Parser.CHAR c - | Token.STRING s -> Parser.STRING s - | Token.TRUE -> Parser.TRUE - | Token.FALSE -> Parser.FALSE - | Token.LOWER_IDENT s -> Parser.LOWER_IDENT s - | Token.UPPER_IDENT s -> Parser.UPPER_IDENT s - | Token.FN -> Parser.FN - | Token.LET -> Parser.LET - | Token.CONST -> Parser.CONST - | Token.MUT -> Parser.MUT - | Token.OWN -> Parser.OWN - | Token.REF -> Parser.REF - | Token.TYPE -> Parser.TYPE - | Token.STRUCT -> Parser.STRUCT - | Token.ENUM -> Parser.ENUM - | Token.TRAIT -> Parser.TRAIT - | Token.IMPL -> Parser.IMPL - | Token.EFFECT -> Parser.EFFECT - | Token.HANDLE -> Parser.HANDLE - | Token.RESUME -> Parser.RESUME - | Token.MATCH -> Parser.MATCH - | Token.IF -> Parser.IF - | Token.ELSE -> Parser.ELSE - | Token.WHILE -> Parser.WHILE - | Token.FOR -> Parser.FOR - | Token.RETURN -> Parser.RETURN - | Token.BREAK -> Parser.BREAK - | Token.CONTINUE -> Parser.CONTINUE - | Token.IN -> Parser.IN - | Token.WHERE -> Parser.WHERE - | Token.TOTAL -> Parser.TOTAL - | Token.MODULE -> Parser.MODULE - | Token.USE -> Parser.USE - | Token.PUB -> Parser.PUB - | Token.AS -> Parser.AS - | Token.UNSAFE -> Parser.UNSAFE - | Token.ASSUME -> Parser.ASSUME - | Token.SELF_KW -> Parser.SELF_KW - | Token.TRANSMUTE -> Parser.TRANSMUTE - | Token.FORGET -> Parser.FORGET - | Token.TRY -> Parser.TRY - | Token.CATCH -> Parser.CATCH - | Token.FINALLY -> Parser.FINALLY - | Token.NAT -> Parser.NAT - | Token.INT_T -> Parser.INT_T - | Token.BOOL -> Parser.BOOL - | Token.FLOAT_T -> Parser.FLOAT_T - | Token.STRING_T -> Parser.STRING_T - | Token.CHAR_T -> Parser.CHAR_T - | Token.TYPE_K -> Parser.TYPE_K - | Token.ROW -> Parser.ROW - | Token.NEVER -> Parser.NEVER - | Token.LPAREN -> Parser.LPAREN - | Token.RPAREN -> Parser.RPAREN - | Token.LBRACE -> Parser.LBRACE - | Token.RBRACE -> Parser.RBRACE - | Token.LBRACKET -> Parser.LBRACKET - | Token.RBRACKET -> Parser.RBRACKET - | Token.COMMA -> Parser.COMMA - | Token.SEMICOLON -> Parser.SEMICOLON - | Token.COLON -> Parser.COLON - | Token.COLONCOLON -> Parser.COLONCOLON - | Token.DOT -> Parser.DOT - | Token.DOTDOT -> Parser.DOTDOT - | Token.ARROW -> Parser.ARROW - | Token.FAT_ARROW -> Parser.FAT_ARROW - | Token.PIPE -> Parser.PIPE - | Token.AT -> Parser.AT - | Token.UNDERSCORE -> Parser.UNDERSCORE - | Token.BACKSLASH -> Parser.BACKSLASH - | Token.QUESTION -> Parser.QUESTION - | Token.ZERO -> Parser.ZERO - | Token.ONE -> Parser.ONE - | Token.OMEGA -> Parser.OMEGA - | Token.PLUS -> Parser.PLUS - | Token.PLUSPLUS -> Parser.PLUSPLUS - | Token.MINUS -> Parser.MINUS - | Token.STAR -> Parser.STAR - | Token.SLASH -> Parser.SLASH - | Token.PERCENT -> Parser.PERCENT - | Token.EQ -> Parser.EQ - | Token.EQEQ -> Parser.EQEQ - | Token.NE -> Parser.NE - | Token.LT -> Parser.LT - | Token.LE -> Parser.LE - | Token.GT -> Parser.GT - | Token.GE -> Parser.GE - | Token.AMPAMP -> Parser.AMPAMP - | Token.PIPEPIPE -> Parser.PIPEPIPE - | Token.BANG -> Parser.BANG - | Token.AMP -> Parser.AMP - | Token.CARET -> Parser.CARET - | Token.TILDE -> Parser.TILDE - | Token.LTLT -> Parser.LTLT - | Token.GTGT -> Parser.GTGT - | Token.PLUSEQ -> Parser.PLUSEQ - | Token.MINUSEQ -> Parser.MINUSEQ - | Token.STAREQ -> Parser.STAREQ - | Token.SLASHEQ -> Parser.SLASHEQ - | Token.ROW_VAR s -> Parser.ROW_VAR s - | Token.EOF -> Parser.EOF - in - (menhir_tok, start_lexpos, end_lexpos) - -(** Parse a program from string *) -let parse_string ~file content : (Ast.program, string * Span.t) result = - let state = create_state ~file content in - let supplier = next_token state in - try - let checkpoint = Parser.Incremental.program - (make_lexing_position file { Span.line = 1; col = 1; offset = 0 }) in - Ok (I.loop supplier checkpoint) - with - | Lexer.Lexer_error (msg, pos) -> - let span = Span.make ~file ~start_pos:pos ~end_pos:pos in - Error (msg, span) - | Parser.Error -> - let (tok, span) = state.last_token in - let msg = Printf.sprintf "Syntax error at %s" (Token.to_string tok) in - Error (msg, span) - -(** Parse a program from channel *) -let parse_channel ~file chan : (Ast.program, string * Span.t) result = - let state = create_state_channel ~file chan in - let supplier = next_token state in - try - let checkpoint = Parser.Incremental.program - (make_lexing_position file { Span.line = 1; col = 1; offset = 0 }) in - Ok (I.loop supplier checkpoint) - with - | Lexer.Lexer_error (msg, pos) -> - let span = Span.make ~file ~start_pos:pos ~end_pos:pos in - Error (msg, span) - | Parser.Error -> - let (tok, span) = state.last_token in - let msg = Printf.sprintf "Syntax error at %s" (Token.to_string tok) in - Error (msg, span) - -(** Parse a program from file *) -let parse_file filename : (Ast.program, string * Span.t) result = - try - let chan = open_in_bin filename in - let result = parse_channel ~file:filename chan in - close_in chan; - result - with - | Sys_error msg -> - let span = Span.make ~file:filename - ~start_pos:{ line = 0; col = 0; offset = 0 } - ~end_pos:{ line = 0; col = 0; offset = 0 } in - Error (msg, span) - -(** Parse a single expression from string *) -let parse_expr ~file content : (Ast.expr, string * Span.t) result = - let state = create_state ~file content in - let supplier = next_token state in - try - let checkpoint = Parser.Incremental.expr_only - (make_lexing_position file { Span.line = 1; col = 1; offset = 0 }) in - Ok (I.loop supplier checkpoint) - with - | Lexer.Lexer_error (msg, pos) -> - let span = Span.make ~file ~start_pos:pos ~end_pos:pos in - Error (msg, span) - | Parser.Error -> - let (tok, span) = state.last_token in - let msg = Printf.sprintf "Syntax error at %s" (Token.to_string tok) in - Error (msg, span) diff --git a/affinescript/lib/parse_driver.ml b/affinescript/lib/parse_driver.ml deleted file mode 100644 index 36f84d1..0000000 --- a/affinescript/lib/parse_driver.ml +++ /dev/null @@ -1,221 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Parser driver - bridges sedlex lexer with Menhir parser *) - -(** Exception for parse errors *) -exception Parse_error of string * Span.t - -(** Buffered token stream that provides Menhir-compatible interface *) -type token_buffer = { - mutable current_token : Token.t; - mutable current_span : Span.t; - mutable next_token : unit -> Token.t * Span.t; -} - -(** Create a Menhir-compatible lexer function from our token stream *) -let lexer_of_token_stream (next : unit -> Token.t * Span.t) : Lexing.lexbuf -> Parser.token = - (* We need to track position for Menhir *) - let buf = ref None in - let get_next () = - match !buf with - | Some (tok, span) -> - buf := None; - (tok, span) - | None -> next () - in - fun lexbuf -> - let (tok, span) = get_next () in - (* Update lexbuf positions for Menhir *) - lexbuf.Lexing.lex_start_p <- { - Lexing.pos_fname = span.Span.file; - pos_lnum = span.start_pos.line; - pos_bol = span.start_pos.offset - span.start_pos.col + 1; - pos_cnum = span.start_pos.offset; - }; - lexbuf.Lexing.lex_curr_p <- { - Lexing.pos_fname = span.Span.file; - pos_lnum = span.end_pos.line; - pos_bol = span.end_pos.offset - span.end_pos.col + 1; - pos_cnum = span.end_pos.offset; - }; - (* Convert our token type to Menhir's *) - match tok with - | Token.INT n -> Parser.INT n - | Token.FLOAT f -> Parser.FLOAT f - | Token.CHAR c -> Parser.CHAR c - | Token.STRING s -> Parser.STRING s - | Token.TRUE -> Parser.TRUE - | Token.FALSE -> Parser.FALSE - | Token.LOWER_IDENT s -> Parser.LOWER_IDENT s - | Token.UPPER_IDENT s -> Parser.UPPER_IDENT s - | Token.FN -> Parser.FN - | Token.LET -> Parser.LET - | Token.CONST -> Parser.CONST - | Token.MUT -> Parser.MUT - | Token.OWN -> Parser.OWN - | Token.REF -> Parser.REF - | Token.TYPE -> Parser.TYPE - | Token.STRUCT -> Parser.STRUCT - | Token.ENUM -> Parser.ENUM - | Token.TRAIT -> Parser.TRAIT - | Token.IMPL -> Parser.IMPL - | Token.EFFECT -> Parser.EFFECT - | Token.HANDLE -> Parser.HANDLE - | Token.RESUME -> Parser.RESUME - | Token.MATCH -> Parser.MATCH - | Token.IF -> Parser.IF - | Token.ELSE -> Parser.ELSE - | Token.WHILE -> Parser.WHILE - | Token.FOR -> Parser.FOR - | Token.RETURN -> Parser.RETURN - | Token.BREAK -> Parser.BREAK - | Token.CONTINUE -> Parser.CONTINUE - | Token.IN -> Parser.IN - | Token.WHERE -> Parser.WHERE - | Token.TOTAL -> Parser.TOTAL - | Token.MODULE -> Parser.MODULE - | Token.USE -> Parser.USE - | Token.PUB -> Parser.PUB - | Token.AS -> Parser.AS - | Token.UNSAFE -> Parser.UNSAFE - | Token.ASSUME -> Parser.ASSUME - | Token.SELF_KW -> Parser.SELF_KW - | Token.TRANSMUTE -> Parser.TRANSMUTE - | Token.FORGET -> Parser.FORGET - | Token.TRY -> Parser.TRY - | Token.CATCH -> Parser.CATCH - | Token.FINALLY -> Parser.FINALLY - | Token.NAT -> Parser.NAT - | Token.INT_T -> Parser.INT_T - | Token.BOOL -> Parser.BOOL - | Token.FLOAT_T -> Parser.FLOAT_T - | Token.STRING_T -> Parser.STRING_T - | Token.CHAR_T -> Parser.CHAR_T - | Token.TYPE_K -> Parser.TYPE_K - | Token.ROW -> Parser.ROW - | Token.NEVER -> Parser.NEVER - | Token.LPAREN -> Parser.LPAREN - | Token.RPAREN -> Parser.RPAREN - | Token.LBRACE -> Parser.LBRACE - | Token.RBRACE -> Parser.RBRACE - | Token.LBRACKET -> Parser.LBRACKET - | Token.RBRACKET -> Parser.RBRACKET - | Token.COMMA -> Parser.COMMA - | Token.SEMICOLON -> Parser.SEMICOLON - | Token.COLON -> Parser.COLON - | Token.COLONCOLON -> Parser.COLONCOLON - | Token.DOT -> Parser.DOT - | Token.DOTDOT -> Parser.DOTDOT - | Token.ARROW -> Parser.ARROW - | Token.FAT_ARROW -> Parser.FAT_ARROW - | Token.PIPE -> Parser.PIPE - | Token.AT -> Parser.AT - | Token.UNDERSCORE -> Parser.UNDERSCORE - | Token.BACKSLASH -> Parser.BACKSLASH - | Token.QUESTION -> Parser.QUESTION - | Token.ZERO -> Parser.ZERO - | Token.ONE -> Parser.ONE - | Token.OMEGA -> Parser.OMEGA - | Token.PLUS -> Parser.PLUS - | Token.PLUSPLUS -> Parser.PLUSPLUS - | Token.MINUS -> Parser.MINUS - | Token.STAR -> Parser.STAR - | Token.SLASH -> Parser.SLASH - | Token.PERCENT -> Parser.PERCENT - | Token.EQ -> Parser.EQ - | Token.EQEQ -> Parser.EQEQ - | Token.NE -> Parser.NE - | Token.LT -> Parser.LT - | Token.LE -> Parser.LE - | Token.GT -> Parser.GT - | Token.GE -> Parser.GE - | Token.AMPAMP -> Parser.AMPAMP - | Token.PIPEPIPE -> Parser.PIPEPIPE - | Token.BANG -> Parser.BANG - | Token.AMP -> Parser.AMP - | Token.CARET -> Parser.CARET - | Token.TILDE -> Parser.TILDE - | Token.LTLT -> Parser.LTLT - | Token.GTGT -> Parser.GTGT - | Token.PLUSEQ -> Parser.PLUSEQ - | Token.MINUSEQ -> Parser.MINUSEQ - | Token.STAREQ -> Parser.STAREQ - | Token.SLASHEQ -> Parser.SLASHEQ - | Token.ROW_VAR s -> Parser.ROW_VAR s - | Token.EOF -> Parser.EOF - -(** Parse a program from a string *) -let parse_string ~file content = - let token_stream = Lexer.from_string ~file content in - let lexbuf = Lexing.from_string content in - lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with pos_fname = file }; - let lexer = lexer_of_token_stream token_stream in - try - Parser.program lexer lexbuf - with - | Parser.Error -> - let pos = lexbuf.Lexing.lex_curr_p in - let span = Span.make - ~file - ~start_pos:{ Span.line = pos.pos_lnum; - col = pos.pos_cnum - pos.pos_bol + 1; - offset = pos.pos_cnum } - ~end_pos:{ Span.line = pos.pos_lnum; - col = pos.pos_cnum - pos.pos_bol + 1; - offset = pos.pos_cnum } - in - raise (Parse_error ("Syntax error", span)) - | Parser_errors.Parse_action_error (msg, startpos, endpos) -> - let span = Span.make - ~file - ~start_pos:{ Span.line = startpos.Lexing.pos_lnum; - col = startpos.pos_cnum - startpos.pos_bol + 1; - offset = startpos.pos_cnum } - ~end_pos:{ Span.line = endpos.Lexing.pos_lnum; - col = endpos.pos_cnum - endpos.pos_bol + 1; - offset = endpos.pos_cnum } - in - raise (Parse_error (msg, span)) - -(** Parse a program from a file *) -let parse_file filename = - let chan = open_in_bin filename in - Fun.protect - ~finally:(fun () -> close_in chan) - (fun () -> - let content = really_input_string chan (in_channel_length chan) in - parse_string ~file:filename content) - -(** Parse a single expression from a string *) -let parse_expr ~file content = - let token_stream = Lexer.from_string ~file content in - let lexbuf = Lexing.from_string content in - lexbuf.Lexing.lex_curr_p <- { lexbuf.Lexing.lex_curr_p with pos_fname = file }; - let lexer = lexer_of_token_stream token_stream in - try - Parser.expr_only lexer lexbuf - with - | Parser.Error -> - let pos = lexbuf.Lexing.lex_curr_p in - let span = Span.make - ~file - ~start_pos:{ Span.line = pos.pos_lnum; - col = pos.pos_cnum - pos.pos_bol + 1; - offset = pos.pos_cnum } - ~end_pos:{ Span.line = pos.pos_lnum; - col = pos.pos_cnum - pos.pos_bol + 1; - offset = pos.pos_cnum } - in - raise (Parse_error ("Syntax error", span)) - | Parser_errors.Parse_action_error (msg, startpos, endpos) -> - let span = Span.make - ~file - ~start_pos:{ Span.line = startpos.Lexing.pos_lnum; - col = startpos.pos_cnum - startpos.pos_bol + 1; - offset = startpos.pos_cnum } - ~end_pos:{ Span.line = endpos.Lexing.pos_lnum; - col = endpos.pos_cnum - endpos.pos_bol + 1; - offset = endpos.pos_cnum } - in - raise (Parse_error (msg, span)) diff --git a/affinescript/lib/parser.mly b/affinescript/lib/parser.mly deleted file mode 100644 index 5cc62da..0000000 --- a/affinescript/lib/parser.mly +++ /dev/null @@ -1,865 +0,0 @@ -/* Menhir parser for AffineScript */ - -%{ -open Ast - -let mk_span startpos endpos = - let file = startpos.Lexing.pos_fname in - let start_pos = { - Span.line = startpos.Lexing.pos_lnum; - col = startpos.Lexing.pos_cnum - startpos.Lexing.pos_bol + 1; - offset = startpos.Lexing.pos_cnum; - } in - let end_pos = { - Span.line = endpos.Lexing.pos_lnum; - col = endpos.Lexing.pos_cnum - endpos.Lexing.pos_bol + 1; - offset = endpos.Lexing.pos_cnum; - } in - Span.make ~file ~start_pos ~end_pos - -let mk_ident name startpos endpos = - { name; span = mk_span startpos endpos } - -%} - -/* Tokens with values */ -%token INT -%token FLOAT -%token CHAR -%token STRING -%token LOWER_IDENT -%token UPPER_IDENT -%token ROW_VAR - -/* Literal keywords */ -%token TRUE FALSE - -/* Keywords */ -%token SELF_KW -%token FN LET CONST MUT OWN REF TYPE STRUCT ENUM TRAIT IMPL -%token EFFECT HANDLE RESUME MATCH IF ELSE WHILE FOR -%token RETURN BREAK CONTINUE IN WHERE TOTAL MODULE USE -%token PUB AS UNSAFE ASSUME TRANSMUTE FORGET TRY CATCH FINALLY - -/* Built-in types */ -%token NAT INT_T BOOL FLOAT_T STRING_T CHAR_T TYPE_K ROW NEVER - -/* Punctuation */ -%token LPAREN RPAREN LBRACE RBRACE LBRACKET RBRACKET -%token COMMA SEMICOLON COLON COLONCOLON DOT DOTDOT -%token ARROW FAT_ARROW PIPE AT UNDERSCORE BACKSLASH QUESTION - -/* Quantity */ -%token ZERO ONE OMEGA - -/* Operators */ -%token PLUS PLUSPLUS MINUS STAR SLASH PERCENT -%token EQ EQEQ NE LT LE GT GE -%token AMPAMP PIPEPIPE BANG -%token AMP CARET TILDE LTLT GTGT -%token PLUSEQ MINUSEQ STAREQ SLASHEQ - -/* End of file */ -%token EOF - -/* Precedence (lowest to highest) */ -%right EQ PLUSEQ MINUSEQ STAREQ SLASHEQ -%left PIPEPIPE -%left AMPAMP -%left PIPE -%left CARET -%left AMP -%left EQEQ NE -%left LT LE GT GE -%left LTLT GTGT -%left PLUS PLUSPLUS MINUS -%left STAR SLASH PERCENT -%right BANG TILDE UMINUS UREF UDEREF -%left DOT LBRACKET LPAREN - -/* Entry point */ -%start program -%start expr_only - -%% - -/* ========== Program ========== */ - -program: - | module_decl = module_decl? imports = list(import_decl) decls = list(top_level) EOF - { { prog_module = module_decl; prog_imports = imports; prog_decls = decls } } - -module_decl: - | MODULE path = module_path SEMICOLON { path } - -module_path: - | id = ident { [id] } - | path = module_path DOT id = ident { path @ [id] } - -/* ========== Imports ========== */ - -import_decl: - | USE path = module_path SEMICOLON - { ImportSimple (path, None) } - | USE path = module_path AS alias = ident SEMICOLON - { ImportSimple (path, Some alias) } - | USE path = module_path COLONCOLON LBRACE items = separated_list(COMMA, import_item) RBRACE SEMICOLON - { ImportList (path, items) } - | USE path = module_path COLONCOLON STAR SEMICOLON - { ImportGlob path } - -import_item: - | name = ident { { ii_name = name; ii_alias = None } } - | name = ident AS alias = ident { { ii_name = name; ii_alias = Some alias } } - -/* ========== Top-level declarations ========== */ - -top_level: - | f = fn_decl { TopFn f } - | t = type_decl { TopType t } - | e = effect_decl { TopEffect e } - | tr = trait_decl { TopTrait tr } - | i = impl_block { TopImpl i } - | c = const_decl { c } - -const_decl: - | vis = visibility? CONST name = ident COLON ty = type_expr EQ value = expr SEMICOLON - { TopConst { tc_vis = Option.value vis ~default:Private; - tc_name = name; tc_ty = ty; tc_value = value } } - -/* ========== Functions ========== */ - -fn_decl: - | vis = visibility? total = TOTAL? FN name = ident - type_params = type_params? - LPAREN params = separated_list(COMMA, param) RPAREN - ret = return_type? - where_clause = where_clause? - body = fn_body - { { fd_vis = Option.value vis ~default:Private; - fd_total = Option.is_some total; - fd_name = name; - fd_type_params = Option.value type_params ~default:[]; - fd_params = params; - fd_ret_ty = fst (Option.value ret ~default:(None, None)); - fd_eff = snd (Option.value ret ~default:(None, None)); - fd_where = Option.value where_clause ~default:[]; - fd_body = body } } - -return_type: - | ARROW ty = type_expr { (Some ty, None) } - | MINUS LBRACE eff = effect_expr RBRACE ARROW ty = type_expr { (Some ty, Some eff) } - -fn_body: - | blk = block { FnBlock blk } - | EQ e = expr SEMICOLON { FnExpr e } - -visibility: - | PUB { Public } - | PUB LPAREN LOWER_IDENT RPAREN - { match $3 with - | "crate" -> PubCrate - | "super" -> PubSuper - | _ -> failwith "Expected 'crate' or 'super'" } - -type_params: - | LBRACKET params = separated_nonempty_list(COMMA, type_param) RBRACKET { params } - -type_param: - | qty = quantity? name = ident kind = kind_annotation? - { { tp_quantity = qty; tp_name = name; tp_kind = kind } } - (* Row variable type parameter, e.g. `[..r]` — lexed as a single ROW_VAR token *) - | rv = ROW_VAR - { { tp_quantity = None; - tp_name = mk_ident rv $startpos $endpos; - tp_kind = Some KRow } } - -kind_annotation: - | COLON k = kind { k } - -kind: - | TYPE_K { KType } - | ROW { KRow } - | k1 = kind ARROW k2 = kind { KArrow (k1, k2) } - | LPAREN k = kind RPAREN { k } - -quantity: - | ZERO { QZero } - | ONE { QOne } - | OMEGA { QOmega } - -(* ADR-007 Option C — primary attribute form for quantity annotations. - `@linear` ≡ QOne, `@erased` ≡ QZero, `@unrestricted` ≡ QOmega. - Rejects unknown attribute names with a parse error. *) -quantity_attr: - | AT name = lower_ident - { match name with - | "linear" -> QOne - | "erased" -> QZero - | "unrestricted" -> QOmega - | other -> - let msg = Printf.sprintf - "unknown quantity attribute '@%s'; expected @linear, @erased, or @unrestricted" - other in - raise (Parser_errors.Parse_action_error (msg, $startpos, $endpos)) } - -(* ADR-007 Option B — sugar form for quantity annotations on let/stmt_let. - Reads as `:1`, `:0`, or `:ω` immediately after the pattern. The lexer - emits INT for `0` and `1`, so we accept INT here and validate the value - at parse time, rejecting any integer outside {0, 1}. OMEGA is the - `omega` keyword or `ω` codepoint. *) -quantity_b_sugar: - | COLON n = INT - { match n with - | 0 -> QZero - | 1 -> QOne - | other -> - let msg = Printf.sprintf - "invalid quantity literal '%d'; expected 0, 1, or ω (omega)" - other in - raise (Parser_errors.Parse_action_error (msg, $startpos, $endpos)) } - | COLON OMEGA { QOmega } - -param: - (* Self receiver: bare `self` — SELF_KW is a distinct keyword token. - No COLON or type annotation; type defaults to `Self`. - Four forms below cover all quantity × ownership × self combinations - that are LR(1) without option() conflicts. *) - | SELF_KW - { { p_quantity = None; p_ownership = None; - p_name = mk_ident "self" $startpos $endpos; - p_ty = TyCon (mk_ident "Self" $startpos $endpos) } } - | own = ownership SELF_KW - { { p_quantity = None; p_ownership = Some own; - p_name = mk_ident "self" $startpos $endpos; - p_ty = TyCon (mk_ident "Self" $startpos $endpos) } } - (* Normal params: explicit combinations to avoid option() LR(1) conflicts. - Tokens sets are disjoint: SELF_KW / ownership (REF|OWN|MUT) / quantity - (ZERO|ONE|OMEGA) / AT / ident (LOWER_IDENT|UPPER_IDENT). *) - | name = ident COLON ty = type_expr - { { p_quantity = None; p_ownership = None; p_name = name; p_ty = ty } } - | own = ownership name = ident COLON ty = type_expr - { { p_quantity = None; p_ownership = Some own; p_name = name; p_ty = ty } } - | qty = quantity name = ident COLON ty = type_expr - { { p_quantity = Some qty; p_ownership = None; p_name = name; p_ty = ty } } - | qty = quantity own = ownership name = ident COLON ty = type_expr - { { p_quantity = Some qty; p_ownership = Some own; p_name = name; p_ty = ty } } - (* ADR-007 Option C: @linear / @erased / @unrestricted attribute form *) - | qty_attr = quantity_attr name = ident COLON ty = type_expr - { { p_quantity = Some qty_attr; p_ownership = None; p_name = name; p_ty = ty } } - | qty_attr = quantity_attr own = ownership name = ident COLON ty = type_expr - { { p_quantity = Some qty_attr; p_ownership = Some own; p_name = name; p_ty = ty } } - -ownership: - | OWN { Own } - | REF { Ref } - | MUT { Mut } - -where_clause: - | WHERE constraints = separated_nonempty_list(COMMA, constraint_) { constraints } - -constraint_: - | id = ident COLON bounds = separated_nonempty_list(PLUS, trait_bound) - { ConstraintTrait (id, bounds) } - -trait_bound: - | name = ident { { tb_name = name; tb_args = [] } } - | name = ident LBRACKET args = separated_list(COMMA, type_arg) RBRACKET - { { tb_name = name; tb_args = args } } - -/* ========== Types ========== */ - -type_decl: - /* Type alias: `type Foo = Bar` — semicolon is optional (conformance spec omits it) */ - | vis = visibility? TYPE name = ident type_params = type_params? EQ ty = type_expr SEMICOLON? - { { td_vis = Option.value vis ~default:Private; - td_name = name; - td_type_params = Option.value type_params ~default:[]; - td_body = TyAlias ty } } - /* Inline variant syntax with optional leading pipe: - type X = A | B | C(Int) (no leading pipe — classic style) - type X = | A | B | C(Int) (leading pipe — spec style) - Semicolon is optional in both forms (conformance spec omits it). */ - | vis = visibility? TYPE name = ident type_params = type_params? EQ - first = variant_decl PIPE rest = separated_nonempty_list(PIPE, variant_decl) SEMICOLON? - { { td_vis = Option.value vis ~default:Private; - td_name = name; - td_type_params = Option.value type_params ~default:[]; - td_body = TyEnum (first :: rest) } } - | vis = visibility? TYPE name = ident type_params = type_params? EQ - PIPE first = variant_decl rest = list(preceded(PIPE, variant_decl)) SEMICOLON? - { { td_vis = Option.value vis ~default:Private; - td_name = name; - td_type_params = Option.value type_params ~default:[]; - td_body = TyEnum (first :: rest) } } - | vis = visibility? STRUCT name = ident type_params = type_params? - LBRACE fields = separated_list(COMMA, struct_field) RBRACE - { { td_vis = Option.value vis ~default:Private; - td_name = name; - td_type_params = Option.value type_params ~default:[]; - td_body = TyStruct fields } } - | vis = visibility? ENUM name = ident type_params = type_params? - LBRACE variants = separated_list(COMMA, variant_decl) RBRACE - { { td_vis = Option.value vis ~default:Private; - td_name = name; - td_type_params = Option.value type_params ~default:[]; - td_body = TyEnum variants } } - -struct_field: - | vis = visibility? name = field_name COLON ty = type_expr - { { sf_vis = Option.value vis ~default:Private; sf_name = name; sf_ty = ty } } - -variant_decl: - | name = ident { { vd_name = name; vd_fields = []; vd_ret_ty = None } } - | name = ident LPAREN fields = separated_list(COMMA, type_expr) RPAREN - { { vd_name = name; vd_fields = fields; vd_ret_ty = None } } - | name = ident LPAREN fields = separated_list(COMMA, type_expr) RPAREN COLON ret = type_expr - { { vd_name = name; vd_fields = fields; vd_ret_ty = Some ret } } - -/* ========== Type Expressions ========== */ - -type_expr: - | ty = type_expr_arrow { ty } - -type_expr_arrow: - | arg = type_expr_primary ARROW ret = type_expr_arrow - { TyArrow (arg, None, ret, None) } - | arg = type_expr_primary MINUS LBRACE eff = effect_expr RBRACE ARROW ret = type_expr_arrow - { TyArrow (arg, None, ret, Some eff) } - | ty = type_expr_primary { ty } - -type_expr_primary: - | LPAREN RPAREN { TyTuple [] } - | LPAREN ty = type_expr RPAREN { ty } - | LPAREN ty = type_expr COMMA tys = separated_nonempty_list(COMMA, type_expr) RPAREN - { TyTuple (ty :: tys) } - | UNDERSCORE { TyHole } - | OWN ty = type_expr_primary { TyOwn ty } - | REF ty = type_expr_primary { TyRef ty } - | MUT ty = type_expr_primary { TyMut ty } - | name = lower_ident { TyVar (mk_ident name $startpos $endpos) } - | name = upper_ident { TyCon (mk_ident name $startpos $endpos) } - | name = upper_ident LBRACKET args = separated_nonempty_list(COMMA, type_arg) RBRACKET - { TyApp (mk_ident name $startpos(name) $endpos(name), args) } - /* Row-polymorphic record type. We use a custom recursive rule rather than - `separated_list` because Menhir's separated_list greedily consumes the - COMMA separator and then cannot backtrack when the next token (ROW_VAR) - is not a valid row_field start. ty_record_body / ty_record_rest parse - the interior in one pass without lookahead conflicts. */ - | LBRACE body = ty_record_body RBRACE - { TyRecord (fst body, snd body) } - /* Built-in types */ - | NAT { TyCon (mk_ident "Nat" $startpos $endpos) } - | INT_T { TyCon (mk_ident "Int" $startpos $endpos) } - | BOOL { TyCon (mk_ident "Bool" $startpos $endpos) } - | FLOAT_T { TyCon (mk_ident "Float" $startpos $endpos) } - | STRING_T { TyCon (mk_ident "String" $startpos $endpos) } - | CHAR_T { TyCon (mk_ident "Char" $startpos $endpos) } - | NEVER { TyCon (mk_ident "Never" $startpos $endpos) } - -/* ty_record_body / ty_record_rest: recursive rules for the interior of a - row-polymorphic record type `{ f1: T1, f2: T2, ..r }`. - - Using `separated_list` would cause an LALR(1) conflict: after parsing the - COMMA that separates a row_field from a ROW_VAR tail, the separator has - already been consumed and the parser cannot determine whether the next - production should be a row_field continuation or the row tail. These - rules shift that decision to the token AFTER the comma. */ - -ty_record_body: - (* empty record: {} *) - | - { ([], None) } - (* record with only a row tail: {..r} *) - | rv = ROW_VAR - { ([], Some (mk_ident rv $startpos $endpos)) } - (* record starting with a named field: {name: T, ...} *) - | field = row_field rest = ty_record_rest - { (field :: fst rest, snd rest) } - -ty_record_rest: - (* end — no trailing comma, no row tail *) - | - { ([], None) } - (* trailing comma only *) - | COMMA - { ([], None) } - (* row tail after comma: , ..r *) - | COMMA rv = ROW_VAR - { ([], Some (mk_ident rv $startpos(rv) $endpos(rv))) } - (* another named field after comma: , name: T ... *) - | COMMA field = row_field rest = ty_record_rest - { (field :: fst rest, snd rest) } - -row_field: - | name = field_name COLON ty = type_expr - { { rf_name = name; rf_ty = ty } } - -type_arg: - | ty = type_expr { TyArg ty } - -/* ========== Effects ========== */ - -effect_decl: - | vis = visibility? EFFECT name = ident type_params = type_params? - LBRACE ops = list(effect_op_decl) RBRACE - { { ed_vis = Option.value vis ~default:Private; - ed_name = name; - ed_type_params = Option.value type_params ~default:[]; - ed_ops = ops } } - -effect_op_decl: - (* Type parameters on effect operations are allowed: `fn await[T](promise: Promise[T]) -> T;` *) - | FN name = ident _type_params = type_params? LPAREN params = separated_list(COMMA, param) RPAREN ret = return_type? SEMICOLON - { { eod_name = name; - eod_params = params; - eod_ret_ty = fst (Option.value ret ~default:(None, None)) } } - -effect_expr: - | e = effect_term { e } - | e1 = effect_expr PLUS e2 = effect_term { EffUnion (e1, e2) } - -effect_term: - | name = ident { EffVar name } - | name = ident LBRACKET args = separated_list(COMMA, type_arg) RBRACKET - { EffCon (name, args) } - -/* ========== Traits ========== */ - -trait_decl: - | vis = visibility? TRAIT name = ident type_params = type_params? - super = supertraits? - _where_clause = where_clause? - LBRACE items = list(trait_item) RBRACE - { { trd_vis = Option.value vis ~default:Private; - trd_name = name; - trd_type_params = Option.value type_params ~default:[]; - trd_super = Option.value super ~default:[]; - trd_items = items } } - -supertraits: - | COLON bounds = separated_nonempty_list(PLUS, trait_bound) { bounds } - -trait_item: - | sig_ = fn_sig SEMICOLON { TraitFn sig_ } - | f = fn_decl { TraitFnDefault f } - | TYPE name = ident kind = kind_annotation? default = type_default? SEMICOLON - { TraitType { tt_name = name; tt_kind = kind; tt_default = default } } - -type_default: - | EQ ty = type_expr { ty } - -fn_sig: - | vis = visibility? FN name = ident - type_params = type_params? - LPAREN params = separated_list(COMMA, param) RPAREN - ret = return_type? - { { fs_vis = Option.value vis ~default:Private; - fs_name = name; - fs_type_params = Option.value type_params ~default:[]; - fs_params = params; - fs_ret_ty = fst (Option.value ret ~default:(None, None)); - fs_eff = snd (Option.value ret ~default:(None, None)) } } - -/* ========== Impl ========== */ - -impl_block: - | IMPL type_params = type_params? - trait_ref = impl_trait_ref? - self_ty = type_expr - where_clause = where_clause? - LBRACE items = list(impl_item) RBRACE - { { ib_type_params = Option.value type_params ~default:[]; - ib_trait_ref = trait_ref; - ib_self_ty = self_ty; - ib_where = Option.value where_clause ~default:[]; - ib_items = items } } - -impl_trait_ref: - | name = ident FOR { { tr_name = name; tr_args = [] } } - | name = ident LBRACKET args = separated_list(COMMA, type_arg) RBRACKET FOR - { { tr_name = name; tr_args = args } } - -impl_item: - | f = fn_decl { ImplFn f } - | TYPE name = ident EQ ty = type_expr SEMICOLON { ImplType (name, ty) } - -/* ========== Expressions ========== */ - -expr_only: - | e = expr EOF { e } - -expr: - | e = expr_assign { e } - -expr_assign: - | lhs = expr_or EQ rhs = expr_assign - { ExprLet { el_mut = false; el_quantity = None; - el_pat = PatVar (mk_ident "_" $startpos(lhs) $endpos(lhs)); - el_ty = None; el_value = lhs; el_body = Some rhs } } - | e = expr_or { e } - -expr_or: - | e1 = expr_or PIPEPIPE e2 = expr_and { ExprBinary (e1, OpOr, e2) } - | e = expr_and { e } - -expr_and: - | e1 = expr_and AMPAMP e2 = expr_bitor { ExprBinary (e1, OpAnd, e2) } - | e = expr_bitor { e } - -expr_bitor: - | e1 = expr_bitor PIPE e2 = expr_bitxor { ExprBinary (e1, OpBitOr, e2) } - | e = expr_bitxor { e } - -expr_bitxor: - | e1 = expr_bitxor CARET e2 = expr_bitand { ExprBinary (e1, OpBitXor, e2) } - | e = expr_bitand { e } - -expr_bitand: - | e1 = expr_bitand AMP e2 = expr_cmp { ExprBinary (e1, OpBitAnd, e2) } - | e = expr_cmp { e } - -expr_cmp: - | e1 = expr_cmp EQEQ e2 = expr_shift { ExprBinary (e1, OpEq, e2) } - | e1 = expr_cmp NE e2 = expr_shift { ExprBinary (e1, OpNe, e2) } - | e1 = expr_cmp LT e2 = expr_shift { ExprBinary (e1, OpLt, e2) } - | e1 = expr_cmp LE e2 = expr_shift { ExprBinary (e1, OpLe, e2) } - | e1 = expr_cmp GT e2 = expr_shift { ExprBinary (e1, OpGt, e2) } - | e1 = expr_cmp GE e2 = expr_shift { ExprBinary (e1, OpGe, e2) } - | e = expr_shift { e } - -expr_shift: - | e1 = expr_shift LTLT e2 = expr_add { ExprBinary (e1, OpShl, e2) } - | e1 = expr_shift GTGT e2 = expr_add { ExprBinary (e1, OpShr, e2) } - | e = expr_add { e } - -expr_add: - | e1 = expr_add PLUS e2 = expr_mul { ExprBinary (e1, OpAdd, e2) } - | e1 = expr_add PLUSPLUS e2 = expr_mul { ExprBinary (e1, OpConcat, e2) } - | e1 = expr_add MINUS e2 = expr_mul { ExprBinary (e1, OpSub, e2) } - | e = expr_mul { e } - - -expr_mul: - | e1 = expr_mul STAR e2 = expr_unary { ExprBinary (e1, OpMul, e2) } - | e1 = expr_mul SLASH e2 = expr_unary { ExprBinary (e1, OpDiv, e2) } - | e1 = expr_mul PERCENT e2 = expr_unary { ExprBinary (e1, OpMod, e2) } - | e = expr_unary { e } - -expr_unary: - | MINUS e = expr_unary %prec UMINUS { ExprUnary (OpNeg, e) } - | BANG e = expr_unary { ExprUnary (OpNot, e) } - | TILDE e = expr_unary { ExprUnary (OpBitNot, e) } - | AMP e = expr_unary %prec UREF { ExprUnary (OpRef, e) } - | STAR e = expr_unary %prec UDEREF { ExprUnary (OpDeref, e) } - | e = expr_postfix { e } - -expr_postfix: - /* field_name used here so that `r.handle` parses even though `handle` is a - keyword; field access is unambiguous after DOT. */ - | e = expr_postfix DOT field = field_name { ExprField (e, field) } - | e = expr_postfix DOT n = INT { ExprTupleIndex (e, n) } - | e = expr_postfix LBRACKET idx = expr RBRACKET { ExprIndex (e, idx) } - | e = expr_postfix LPAREN args = separated_list(COMMA, expr) RPAREN { ExprApp (e, args) } - | e = expr_postfix BACKSLASH field = field_name { ExprRowRestrict (e, field) } - | e = expr_postfix QUESTION { ExprTry { et_body = { blk_stmts = []; blk_expr = Some e }; - et_catch = None; et_finally = None } } - | e = expr_primary { e } - -expr_primary: - /* Literals */ - | n = INT { ExprLit (LitInt (n, mk_span $startpos $endpos)) } - | f = FLOAT { ExprLit (LitFloat (f, mk_span $startpos $endpos)) } - | c = CHAR { ExprLit (LitChar (c, mk_span $startpos $endpos)) } - | s = STRING { ExprLit (LitString (s, mk_span $startpos $endpos)) } - | TRUE { ExprLit (LitBool (true, mk_span $startpos $endpos)) } - | FALSE { ExprLit (LitBool (false, mk_span $startpos $endpos)) } - - /* Identifiers */ - | name = lower_ident { ExprVar (mk_ident name $startpos $endpos) } - /* Struct literal: `Point { x: v, y: w }`. Must come before the plain - upper_ident production so Menhir shifts LBRACE rather than reducing - upper_ident to ExprVar when the next token is LBRACE. */ - | _ty = upper_ident LBRACE b = expr_record_body RBRACE - { ExprRecord { er_fields = fst b; er_spread = snd b } } - | name = upper_ident { ExprVar (mk_ident name $startpos $endpos) } - | ty = upper_ident COLONCOLON variant = upper_ident - { ExprVariant (mk_ident ty $startpos(ty) $endpos(ty), - mk_ident variant $startpos(variant) $endpos(variant)) } - - /* Grouping and tuples */ - | LPAREN RPAREN { ExprLit (LitUnit (mk_span $startpos $endpos)) } - | LPAREN e = expr RPAREN { e } - | LPAREN e = expr COMMA es = separated_nonempty_list(COMMA, expr) RPAREN - { ExprTuple (e :: es) } - - /* Arrays */ - | LBRACKET es = separated_list(COMMA, expr) RBRACKET { ExprArray es } - - /* Records — use a recursive rule (expr_record_body / expr_record_rest) to - avoid the LALR(1) greedy-separator conflict that arises when a ROW_VAR - spread like `..record` follows a COMMA that `separated_list` has already - consumed expecting another record_field. */ - | LBRACE b = expr_record_body RBRACE - { ExprRecord { er_fields = fst b; er_spread = snd b } } - - /* Block */ - | blk = block { ExprBlock blk } - - /* Control flow */ - | IF cond = expr then_blk = block else_part = else_part? - { ExprIf { ei_cond = cond; ei_then = ExprBlock then_blk; ei_else = else_part } } - - | MATCH scrutinee = expr LBRACE arms = list(match_arm) RBRACE - { ExprMatch { em_scrutinee = scrutinee; em_arms = arms } } - - /* Let expressions — ADR-007 hybrid surface syntax for quantities. - Four production paths cover the cross product of {C-attr, B-sugar, neither} - × {with type, without type}. The C-attribute form (`@linear let x = e`) - and the B-sugar form (`let x :1 = e`) cannot both appear on the same let - binder; they are alternative spellings, not stackable annotations. */ - | LET mut_ = MUT? pat = pattern ty = type_annotation? EQ value = expr - { ExprLet { el_mut = Option.is_some mut_; el_quantity = None; el_pat = pat; - el_ty = ty; el_value = value; el_body = None } } - | qty_attr = quantity_attr LET mut_ = MUT? pat = pattern ty = type_annotation? EQ value = expr - { ExprLet { el_mut = Option.is_some mut_; el_quantity = Some qty_attr; el_pat = pat; - el_ty = ty; el_value = value; el_body = None } } - | LET mut_ = MUT? pat = pattern qty = quantity_b_sugar ty = type_annotation? EQ value = expr - { ExprLet { el_mut = Option.is_some mut_; el_quantity = Some qty; el_pat = pat; - el_ty = ty; el_value = value; el_body = None } } - - /* Lambda */ - | PIPE params = separated_list(COMMA, lambda_param) PIPE body = expr - { ExprLambda { elam_params = params; elam_ret_ty = None; elam_body = body } } - | PIPE params = separated_list(COMMA, lambda_param) PIPE ARROW ret = type_expr body = block - { ExprLambda { elam_params = params; elam_ret_ty = Some ret; elam_body = ExprBlock body } } - - /* Return */ - | RETURN e = expr? { ExprReturn e } - - /* Handle */ - | HANDLE body = expr LBRACE handlers = list(handler_arm) RBRACE - { ExprHandle { eh_body = body; eh_handlers = handlers } } - - /* Resume */ - | RESUME e = expr? { ExprResume e } - - /* Try/catch/finally */ - | TRY body = block catch = try_catch? finally = try_finally? - { ExprTry { et_body = body; et_catch = catch; et_finally = finally } } - - /* Unsafe operations */ - | UNSAFE LBRACE ops = list(unsafe_op) RBRACE - { ExprUnsafe ops } - -/* expr_record_body / expr_record_rest: recursive parse of `{ f:v, ..spread }` - record expressions. Spread is lexed as ROW_VAR when it is a bare - identifier (e.g. `..record`), or starts with DOTDOT when it is an - arbitrary expression (e.g. `..{ a: 1 }`). We handle both in - expr_record_spread and use a recursive structure to avoid the greedy- - separator conflict. */ - -expr_record_body: - (* empty record: {} *) - | - { ([], None) } - (* spread-only: { ..var } or { ..expr } *) - | sp = expr_record_spread - { ([], Some sp) } - (* field possibly followed by more: { f: v, ... } *) - | field = record_field rest = expr_record_rest - { (field :: fst rest, snd rest) } - -expr_record_rest: - (* no more fields, no spread *) - | - { ([], None) } - (* trailing comma only *) - | COMMA - { ([], None) } - (* spread after comma *) - | COMMA sp = expr_record_spread - { ([], Some sp) } - (* another field after comma *) - | COMMA field = record_field rest = expr_record_rest - { (field :: fst rest, snd rest) } - -expr_record_spread: - (* `..ident` — lexed as a single ROW_VAR token *) - | rv = ROW_VAR { ExprVar (mk_ident rv $startpos $endpos) } - (* `..expr` — DOTDOT consumed, then an arbitrary expression *) - | DOTDOT e = expr { e } - -record_field: - | name = field_name COLON value = expr { (name, Some value) } - | name = field_name { (name, None) } - -type_annotation: - | COLON ty = type_expr { ty } - -else_part: - | ELSE IF cond = expr then_blk = block else_part = else_part? - { ExprIf { ei_cond = cond; ei_then = ExprBlock then_blk; ei_else = else_part } } - | ELSE blk = block { ExprBlock blk } - -lambda_param: - | name = ident { { p_quantity = None; p_ownership = None; p_name = name; - p_ty = TyHole } } - | name = ident COLON ty = type_expr - { { p_quantity = None; p_ownership = None; p_name = name; p_ty = ty } } - /* ADR-007 Option C: @linear x or @linear x: Type */ - | qty_attr = quantity_attr name = ident - { { p_quantity = Some qty_attr; p_ownership = None; p_name = name; p_ty = TyHole } } - | qty_attr = quantity_attr name = ident COLON ty = type_expr - { { p_quantity = Some qty_attr; p_ownership = None; p_name = name; p_ty = ty } } - -match_arm: - | pat = pattern guard = match_guard? FAT_ARROW body = expr COMMA? - { { ma_pat = pat; ma_guard = guard; ma_body = body } } - -match_guard: - | IF cond = expr { cond } - -handler_arm: - | RETURN LPAREN pat = pattern RPAREN FAT_ARROW body = expr COMMA? - { HandlerReturn (pat, body) } - | name = ident LPAREN pats = separated_list(COMMA, pattern) RPAREN FAT_ARROW body = expr COMMA? - { HandlerOp (name, pats, body) } - -try_catch: - | CATCH LBRACE arms = list(match_arm) RBRACE { arms } - -try_finally: - | FINALLY blk = block { blk } - -unsafe_op: - /* UnsafeRead: read(ptr); */ - | name = lower_ident LPAREN ptr = expr RPAREN SEMICOLON - { match name with - | "read" -> UnsafeRead ptr - | "write" -> failwith "write requires two arguments" - | "offset" -> failwith "offset requires two arguments" - | _ -> failwith ("unknown unsafe operation: " ^ name) } - - /* UnsafeWrite: write(ptr, value); */ - | name = lower_ident LPAREN ptr = expr COMMA value = expr RPAREN SEMICOLON - { match name with - | "write" -> UnsafeWrite (ptr, value) - | "offset" -> UnsafeOffset (ptr, value) - | _ -> failwith ("unknown unsafe operation: " ^ name) } - - /* UnsafeForget: forget(expr); */ - | FORGET LPAREN e = expr RPAREN SEMICOLON - { UnsafeForget e } - - /* UnsafeTransmute: transmute(expr); */ - | TRANSMUTE LT from_ty = type_expr COMMA to_ty = type_expr GT LPAREN e = expr RPAREN SEMICOLON - { UnsafeTransmute (from_ty, to_ty, e) } - -/* ========== Statements ========== */ - -(* Self-delimiting expressions that can serve as the final expression in a - block without a trailing semicolon. Because they all end with '}', the - LR(1) parser can distinguish "this was the last expression" (followed by - the outer '}') from "this was a statement" (which would need ';'). *) -block_terminator: - | IF cond = expr then_blk = block else_part = else_part? - { ExprIf { ei_cond = cond; ei_then = ExprBlock then_blk; ei_else = else_part } } - | MATCH scrutinee = expr LBRACE arms = list(match_arm) RBRACE - { ExprMatch { em_scrutinee = scrutinee; em_arms = arms } } - | inner = block - { ExprBlock inner } - -block: - | LBRACE stmts = list(stmt) RBRACE - { { blk_stmts = stmts; blk_expr = None } } - | LBRACE stmts = list(stmt) final = block_terminator RBRACE - { { blk_stmts = stmts; blk_expr = Some final } } - | LBRACE stmts = stmt_list_nonempty_trailing_expr RBRACE - { { blk_stmts = fst stmts; blk_expr = Some (snd stmts) } } - | LBRACE e = expr RBRACE - { { blk_stmts = []; blk_expr = Some e } } - -stmt_list_nonempty_trailing_expr: - | s = stmt rest = stmt_list_nonempty_trailing_expr { (s :: fst rest, snd rest) } - | s = stmt e = expr { ([s], e) } - -stmt: - | LET mut_ = MUT? pat = pattern ty = type_annotation? EQ value = expr SEMICOLON - { StmtLet { sl_mut = Option.is_some mut_; sl_quantity = None; - sl_pat = pat; sl_ty = ty; sl_value = value } } - | qty_attr = quantity_attr LET mut_ = MUT? pat = pattern ty = type_annotation? EQ value = expr SEMICOLON - { StmtLet { sl_mut = Option.is_some mut_; sl_quantity = Some qty_attr; - sl_pat = pat; sl_ty = ty; sl_value = value } } - | LET mut_ = MUT? pat = pattern qty = quantity_b_sugar ty = type_annotation? EQ value = expr SEMICOLON - { StmtLet { sl_mut = Option.is_some mut_; sl_quantity = Some qty; - sl_pat = pat; sl_ty = ty; sl_value = value } } - | e = expr SEMICOLON { StmtExpr e } - | IF cond = expr then_blk = block else_part = else_part? - { StmtExpr (ExprIf { ei_cond = cond; ei_then = ExprBlock then_blk; ei_else = else_part }) } - | lhs = expr_postfix EQ rhs = expr SEMICOLON { StmtAssign (lhs, AssignEq, rhs) } - | lhs = expr_postfix PLUSEQ rhs = expr SEMICOLON { StmtAssign (lhs, AssignAdd, rhs) } - | lhs = expr_postfix MINUSEQ rhs = expr SEMICOLON { StmtAssign (lhs, AssignSub, rhs) } - | lhs = expr_postfix STAREQ rhs = expr SEMICOLON { StmtAssign (lhs, AssignMul, rhs) } - | lhs = expr_postfix SLASHEQ rhs = expr SEMICOLON { StmtAssign (lhs, AssignDiv, rhs) } - | WHILE cond = expr body = block { StmtWhile (cond, body) } - | FOR pat = pattern IN iter = expr body = block { StmtFor (pat, iter, body) } - -/* ========== Patterns ========== */ - -pattern: - | p = pattern_or { p } - -pattern_or: - | p1 = pattern_or PIPE p2 = pattern_primary { PatOr (p1, p2) } - | p = pattern_primary { p } - -pattern_primary: - | UNDERSCORE { PatWildcard (mk_span $startpos $endpos) } - | name = lower_ident { PatVar (mk_ident name $startpos $endpos) } - | n = INT { PatLit (LitInt (n, mk_span $startpos $endpos)) } - | c = CHAR { PatLit (LitChar (c, mk_span $startpos $endpos)) } - | s = STRING { PatLit (LitString (s, mk_span $startpos $endpos)) } - | TRUE { PatLit (LitBool (true, mk_span $startpos $endpos)) } - | FALSE { PatLit (LitBool (false, mk_span $startpos $endpos)) } - | name = upper_ident { PatCon (mk_ident name $startpos $endpos, []) } - | name = upper_ident LPAREN pats = separated_list(COMMA, pattern) RPAREN - { PatCon (mk_ident name $startpos(name) $endpos(name), pats) } - | LPAREN RPAREN { PatTuple [] } - | LPAREN p = pattern RPAREN { p } - | LPAREN p = pattern COMMA ps = separated_nonempty_list(COMMA, pattern) RPAREN - { PatTuple (p :: ps) } - | LBRACE fields = separated_list(COMMA, pattern_field) rest = pattern_rest? RBRACE - { PatRecord (fields, Option.is_some rest) } - | name = lower_ident AT p = pattern_primary - { PatAs (mk_ident name $startpos(name) $endpos(name), p) } - -pattern_field: - | name = field_name COLON p = pattern { (name, Some p) } - | name = field_name { (name, None) } - -pattern_rest: - | COMMA DOTDOT { () } - -/* ========== Helpers ========== */ - -ident: - | name = lower_ident { mk_ident name $startpos $endpos } - | name = upper_ident { mk_ident name $startpos $endpos } - -(* field_name extends ident with contextual keywords that are legal as struct/ - record field names. Only keywords that do NOT introduce shift/reduce or - reduce/reduce conflicts are listed here. HANDLE is safe because it always - requires `name COLON ty` in type-record context and `name: expr` in - expression-record context; the surrounding COLON disambiguates. *) -field_name: - | id = ident { id } - | HANDLE { mk_ident "handle" $startpos $endpos } - -lower_ident: - | name = LOWER_IDENT { name } - -upper_ident: - | name = UPPER_IDENT { name } - -%% diff --git a/affinescript/lib/parser_errors.ml b/affinescript/lib/parser_errors.ml deleted file mode 100644 index 88fc4f7..0000000 --- a/affinescript/lib/parser_errors.ml +++ /dev/null @@ -1,18 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** Shared exceptions for parser semantic actions. - - This module exists because exceptions defined in the [%{ %}] prologue - of [parser.mly] are not exported through [parser.mli], so consumers - (notably [parse_driver.ml]) cannot pattern-match on them. Defining the - exception here lets both [parser.mly] and [parse_driver.ml] reference - it without a circular dependency. *) - -(** Raised by a parser semantic action when a syntactically valid form - carries a value the action rejects (e.g. a quantity literal that is - neither 0 nor 1, an unknown [@]-attribute name). - - Caught by [Parse_driver] and translated to its [Parse_error] - exception with proper [Span.t] resolution. *) -exception Parse_action_error of string * Lexing.position * Lexing.position diff --git a/affinescript/lib/python_face.ml b/affinescript/lib/python_face.ml deleted file mode 100644 index 4bb618c..0000000 --- a/affinescript/lib/python_face.ml +++ /dev/null @@ -1,284 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** Python face for AffineScript. - - Per the faces spec (docs/specs/faces.md), a face is a source-level - text preprocessor: [text → canonical text] followed by the stock - lex + parse pipeline. This module implements the preprocessor for - Python-style surface syntax: - - - [def f(x): body] becomes [fn f(x) { body }] - - [if/elif/else] chains with [:]-terminated heads and indented - bodies become braced chains - - Python keywords [and or not True False None pass] are replaced - with their canonical equivalents [&& || ! true false () ()] - - Statement-like lines inside a braced block get a trailing - [;] so that the canonical parser sees one statement per - source line - - Nothing about the compiler beyond this file knows about Python. - Type errors surface in the canonical vocabulary; ADR-010's - face-aware *error* formatter layer lives in [face.ml] and is - orthogonal. *) - -(* ------------------------------------------------------------------ *) -(* Keyword substitution — applied to the content of every line after *) -(* it has been tokenised into word/non-word fragments. Matching is *) -(* whole-word; [not_a] does not become [!_a]. *) -(* ------------------------------------------------------------------ *) - -let keyword_map = [ - "True", "true"; - "False", "false"; - "None", "()"; - "pass", "()"; - "and", "&&"; - "or", "||"; - "not", "!"; - "def", "fn"; -] - -let is_ident_char c = - (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') - || (c >= '0' && c <= '9') || c = '_' - -(** Replace Python keywords with their canonical equivalents at - word boundaries. Quick-and-correct: we walk the string once, - re-emitting each maximal identifier run after lookup. Non-ident - characters are copied through verbatim so arithmetic, punctuation, - and string literals survive untouched. - - Caveat: inside a string literal, keyword-like substrings are - rewritten anyway. Fixtures under test do not contain [and]/[or]/ - etc. in strings, so this is a known limitation rather than a - bug in scope. A future pass can gate the substitution on an - outside-literal tracker. *) -let substitute_keywords (line : string) : string = - let buf = Buffer.create (String.length line) in - let n = String.length line in - let i = ref 0 in - while !i < n do - let c = line.[!i] in - if is_ident_char c then begin - let start = !i in - while !i < n && is_ident_char line.[!i] do incr i done; - let word = String.sub line start (!i - start) in - let replacement = - match List.assoc_opt word keyword_map with - | Some r -> r - | None -> word - in - Buffer.add_string buf replacement - end else begin - Buffer.add_char buf c; - incr i - end - done; - Buffer.contents buf - -(* ------------------------------------------------------------------ *) -(* Line-level classification *) -(* ------------------------------------------------------------------ *) - -let count_leading_spaces (s : string) : int = - let n = String.length s in - let i = ref 0 in - while !i < n && s.[!i] = ' ' do incr i done; - !i - -(** [strip_hash_comment s] drops any trailing [# ...] comment from a - Python source line. Does not try to recognise [#] inside strings; - acceptable because fixtures use [//]-style block comments at the - top of files but [#] only on dedicated comment lines. *) -let strip_hash_comment (s : string) : string = - match String.index_opt s '#' with - | Some i -> String.sub s 0 i - | None -> s - -let rtrim (s : string) : string = - let n = ref (String.length s) in - while !n > 0 && (s.[!n - 1] = ' ' || s.[!n - 1] = '\t') do decr n done; - String.sub s 0 !n - -let is_blank (s : string) : bool = - let n = String.length s in - let rec loop i = - if i >= n then true - else if s.[i] = ' ' || s.[i] = '\t' then loop (i + 1) - else false - in - loop 0 - -let ends_with_colon (s : string) : bool = - let s = rtrim s in - String.length s > 0 && s.[String.length s - 1] = ':' - -(** Strip the trailing [:] (after any trailing whitespace) from a - block-header line so the caller can append [ {] instead. *) -let drop_trailing_colon (s : string) : string = - let s = rtrim s in - let n = String.length s in - if n > 0 && s.[n - 1] = ':' then String.sub s 0 (n - 1) - else s - -(** Heuristic: does this line, after stripping [:] and whitespace, - look like a statement rather than a block header or terminal - expression? Used to decide whether to append [;]. We treat - [let], [return], [break], [continue], [use], [type], [effect] as - unambiguously statement-shape; function calls and arithmetic are - ambiguous and left bare (they become terminal expressions unless - a later line forces a statement position). *) -let looks_like_statement (s : string) : bool = - let s = String.trim s in - let starts_with prefix = - String.length s >= String.length prefix - && String.sub s 0 (String.length prefix) = prefix - && (String.length s = String.length prefix - || not (is_ident_char s.[String.length prefix])) - in - starts_with "let" - || starts_with "return" - || starts_with "break" - || starts_with "continue" - || starts_with "use" - || starts_with "type" - || starts_with "effect" - || starts_with "import" - -(* ------------------------------------------------------------------ *) -(* Main preprocessor *) -(* ------------------------------------------------------------------ *) - -(** [preview_transform src] returns the canonical AffineScript source - produced from the Python-face source [src]. Exposed so tests can - inspect the intermediate string without running the full parser. *) -let preview_transform (src : string) : string = - (* Parse into (indent, payload) pairs; drop blank + pure-comment - lines early. *) - let raw_lines = String.split_on_char '\n' src in - let lines = - List.filter_map (fun line -> - let line_no_comment = strip_hash_comment line in - if is_blank line_no_comment then None - else - let indent = count_leading_spaces line_no_comment in - let content = - rtrim (String.sub line_no_comment indent - (String.length line_no_comment - indent)) - in - Some (indent, content) - ) raw_lines - in - - let out = Buffer.create (String.length src) in - (* Indent stack: each entry is the indent column at which the - enclosing block body starts (i.e., the indent of its first - statement). The outermost layer is the file scope, modelled as - indent -1 so any top-level line with indent >= 0 is "inside" - it without opening a new block. *) - let stack = ref [-1] in - (* If the previous emitted line ended with a block-opening [:], - the NEXT non-empty line is the first statement of the new block. - Record its indent on the stack before emitting it. *) - let pending_open = ref false in - - let emit_close_braces ?(same_line=false) target_indent = - (* Pop the stack while the top indent is strictly greater than - [target_indent]; emit [}] for each pop. If [same_line] is - false (default), each [}] gets its own line of output. *) - let count = ref 0 in - while (match !stack with - | top :: _ -> top > target_indent - | [] -> false) do - stack := List.tl !stack; - incr count - done; - for i = 0 to !count - 1 do - if same_line && i = 0 then Buffer.add_char out ' '; - Buffer.add_char out '}'; - if not (same_line && i = !count - 1) then Buffer.add_char out '\n' - done; - if same_line && !count > 0 then Buffer.add_char out ' ' - in - - List.iter (fun (indent, content) -> - (* Decide whether this line continues an opening block or dedents. *) - if !pending_open then begin - (* This line is the first statement of the block opened by the - previous [:]. Push its indent so later dedents can find it. *) - stack := indent :: !stack; - pending_open := false - end else begin - (* Close any braces we're dedenting out of. *) - emit_close_braces indent - end; - - (* Handle the [elif ...:] and [else:] keyword rewrites. The - dedent above has already emitted the [}] that closes the - previous [if]/[elif] branch — we just need to translate the - Python keyword into its canonical form. The trailing [:] is - handled below by the generic block-opener path. *) - let starts_with_word w s = - let wn = String.length w in - String.length s >= wn - && String.sub s 0 wn = w - && (String.length s = wn || not (is_ident_char s.[wn])) - in - let content = - if starts_with_word "elif" content then - "else if" ^ String.sub content 4 (String.length content - 4) - else if starts_with_word "else" content - && String.length content > 4 && content.[4] = ':' - then - (* [else:] is already canonical barring the colon that the - block-opener path handles. Keep [content] as-is. *) - content - else - content - in - - (* Write indent for readability (two spaces per nesting level - counting entries above the sentinel -1). *) - let depth = List.length !stack - 1 in - for _ = 1 to depth do Buffer.add_string out " " done; - - let content = substitute_keywords content in - - (* Handle block-opener lines: strip trailing [:], append ` {`, - mark [pending_open] so the next line's indent defines the new - block boundary. *) - if ends_with_colon content then begin - Buffer.add_string out (drop_trailing_colon content); - Buffer.add_string out " {"; - pending_open := true - end else begin - Buffer.add_string out content; - if looks_like_statement content then Buffer.add_char out ';' - end; - Buffer.add_char out '\n' - ) lines; - - (* End of file: close any still-open blocks. *) - emit_close_braces (-1); - Buffer.contents out - -(* ------------------------------------------------------------------ *) -(* Parsing entry points *) -(* ------------------------------------------------------------------ *) - -(** [parse_string_python ~file src] preprocesses [src] into canonical - AffineScript text and runs the canonical parser on the result. - [file] is used only for error-location reporting. *) -let parse_string_python ~file (src : string) : Ast.program = - let canonical = preview_transform src in - Parse_driver.parse_string ~file canonical - -(** [parse_file_python path] reads the file at [path] and runs - [parse_string_python] on the contents. *) -let parse_file_python (path : string) : Ast.program = - let ic = open_in path in - let n = in_channel_length ic in - let src = really_input_string ic n in - close_in ic; - parse_string_python ~file:path src diff --git a/affinescript/lib/quantity.ml b/affinescript/lib/quantity.ml deleted file mode 100644 index 12809e2..0000000 --- a/affinescript/lib/quantity.ml +++ /dev/null @@ -1,723 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** Quantity checking for Quantitative Type Theory (QTT). - - AffineScript uses a three-point quantity semiring {0, 1, omega} to track - how many times each variable may be used at runtime: - - - [QZero] (0): erased at runtime, compile-time only. - - [QOne] (1): linear — must be used exactly once. - - [QOmega] (omega): unrestricted — any number of uses. - - This module implements: - 1. The quantity semiring (addition, multiplication, ordering). - 2. A usage-counting pass that walks expressions/statements. - 3. Compatibility checking between declared quantities and observed usage. - 4. A top-level entry point [{!check_function_quantities}] for integration - with the type checker. - - Reference: AffineScript SPEC.md Section 3.2 — QTT semiring specification. *) - -open Ast - -(* {1 Quantity semiring} *) - -(** Addition in the quantity semiring. - - The addition table encodes "use in either context": - {v - + | 0 1 omega - -----+----------------- - 0 | 0 1 omega - 1 | 1 omega omega - omega| omega omega omega - v} *) -let q_add (q1 : quantity) (q2 : quantity) : quantity = - match (q1, q2) with - | (QZero, q) | (q, QZero) -> q - | (QOne, QOne) -> QOmega - | (QOmega, _) | (_, QOmega) -> QOmega - -(** Multiplication in the quantity semiring. - - The multiplication table encodes "use under a context scaled by q": - {v - * | 0 1 omega - -----+----------------- - 0 | 0 0 0 - 1 | 0 1 omega - omega| 0 omega omega - v} *) -let q_mul (q1 : quantity) (q2 : quantity) : quantity = - match (q1, q2) with - | (QZero, _) | (_, QZero) -> QZero - | (QOne, q) | (q, QOne) -> q - | (QOmega, QOmega) -> QOmega - -(** Ordering: 0 <= 1 <= omega. - - [q_le q1 q2] returns [true] when [q1] is usable wherever [q2] is expected. - This means a more restricted quantity can substitute for a less restricted - one (subquantity). *) -let q_le (q1 : quantity) (q2 : quantity) : bool = - match (q1, q2) with - | (QZero, _) -> true - | (_, QOmega) -> true - | (QOne, QOne) -> true - | _ -> false - -(* {1 Usage tracking} *) - -(** Usage count for a variable during analysis. *) -type usage = - | UZero (** Not used *) - | UOne (** Used exactly once *) - | UMany (** Used two or more times *) -[@@deriving show, eq] - -(** Convert a usage count to its corresponding quantity. *) -let usage_to_quantity (u : usage) : quantity = - match u with - | UZero -> QZero - | UOne -> QOne - | UMany -> QOmega - -(** Join two usages — computes the upper bound for branching. - - When a variable is used in one branch but not another, we take the - maximum usage across branches. This is correct for [if/match] where - exactly one branch executes. *) -let join_usage (u1 : usage) (u2 : usage) : usage = - match (u1, u2) with - | (UMany, _) | (_, UMany) -> UMany - | (UOne, _) | (_, UOne) -> UOne - | (UZero, UZero) -> UZero - -(** Add two usages — computes the sum for sequential composition. - - When a variable is used in a sequence of statements, the total usage - is the sum: used once then once more means used many times. *) -let add_usage (u1 : usage) (u2 : usage) : usage = - match (u1, u2) with - | (UZero, u) | (u, UZero) -> u - | _ -> UMany - -(** Scale a usage by a quantity — implements the q·u operation that - the QTT-orthodox Let rule needs to scale the value-context Γ₁ by - the binder's quantity q. - - Per ADR-002, [let x :^q = e1 in e2] is type-checked under - [q·Γ₁ + Γ₂ ⊢ ...]. The semiring action on usages is: - - 0 · u = UZero (erased — usage is annihilated) - 1 · u = u (linear — usage passes through unchanged) - ω · UZero = UZero (no use stays no use) - ω · UOne = UMany (a single use, replicated, becomes many) - ω · UMany = UMany (many stays many) - - The interesting case is [ω · UOne = UMany]: it is the rule that - closes BUG-001. A linear variable consumed in [e1] under an - ω-binder gets its usage promoted to UMany, which the - [check_quantity] step then catches as - [LinearVariableUsedMultiple]. *) -let scale_usage (q : quantity) (u : usage) : usage = - match (q, u) with - | (QZero, _) -> UZero - | (QOne, u) -> u - | (QOmega, UZero) -> UZero - | (QOmega, UOne) -> UMany - | (QOmega, UMany) -> UMany - -(* {1 Errors} *) - -(** Quantity checking errors with precise descriptions. *) -type quantity_error = - | LinearVariableUnused of ident - (** A parameter declared as linear (1) was never used. *) - | LinearVariableUsedMultiple of ident - (** A parameter declared as linear (1) was used more than once. *) - | ErasedVariableUsed of ident - (** A parameter declared as erased (0) was used at runtime. *) - | QuantityMismatch of ident * quantity * usage - (** General mismatch: variable, declared quantity, actual usage. *) -[@@deriving show] - -(** Result type carrying a quantity error paired with a source span. *) -type 'a result = ('a, quantity_error * Span.t) Result.t - -(** Pretty-print a quantity using the canonical Option C surface form - from ADR-007. Used in error messages so source vocabulary and - diagnostic vocabulary stay aligned. *) -let canonical_quantity_name (q : quantity) : string = - match q with - | QZero -> "@erased" - | QOne -> "@linear" - | QOmega -> "@unrestricted" - -(** Pretty-print a usage count in plain English. *) -let usage_in_words (u : usage) : string = - match u with - | UZero -> "never used" - | UOne -> "used exactly once" - | UMany -> "used multiple times" - -(** Format a quantity error for human-readable output. Per ADR-007, all - diagnostic text uses the @linear/@erased/@unrestricted vocabulary, - even when the source program used the :1/:0/:ω sugar form. The - user reads consistent words regardless of which surface they wrote. *) -let format_quantity_error (err : quantity_error) : string = - match err with - | LinearVariableUnused id -> - Printf.sprintf - "@linear binding '%s' must be used exactly once, but was never used" - id.name - | LinearVariableUsedMultiple id -> - Printf.sprintf - "@linear binding '%s' must be used exactly once, but was used multiple times" - id.name - | ErasedVariableUsed id -> - Printf.sprintf - "@erased binding '%s' must not be used at runtime" - id.name - | QuantityMismatch (id, q, u) -> - Printf.sprintf - "quantity mismatch for '%s': declared %s but %s" - id.name (canonical_quantity_name q) (usage_in_words u) - -(* {1 Quantity environment} *) - -(** Quantity environment: maps variable names to declared quantities and - tracks observed usage counts. - - We key by [string] (variable name) rather than [Symbol.symbol_id] so - that the environment is self-contained and does not require the symbol - table to be in a particular state during analysis. *) -type env = { - quantities : (string, quantity) Hashtbl.t; - (** Declared quantity for each tracked variable. *) - usages : (string, usage) Hashtbl.t; - (** Current observed usage count for each tracked variable. *) - errors : (quantity_error * Span.t) list ref; - (** Quantity errors accumulated by nested constructs (lambda params). - [infer_usage_expr] returns [unit], so nested violations are - pushed here and drained at the end of [check_function_quantities]. *) -} - -(* ============================================================================ - Cmd linearity — type-annotation-based quantity inference (Stage 11) - ============================================================================ *) - -(** [is_cmd_type te] — true when [te] is an application of the built-in - [Cmd] type constructor (e.g. [Cmd Msg], [Cmd Int]). - - The [Cmd] type is intrinsically linear: every let-binding whose declared - type annotation is [Cmd _] is automatically treated as QOne (linear) by - the quantity checker. This means the programmer does not need to write - [@linear] explicitly — the type annotation is sufficient. - - Note: this only matches AST-level type annotations supplied by the - programmer. Bindings without a type annotation (e.g. [let cmd = cmd_none]) - get QOmega as usual. For full intrinsic linearity without annotation, a - post-typecheck elaboration pass would be needed (deferred). *) -let is_cmd_type (te : type_expr) : bool = - match te with - | TyApp ({ name = "Cmd"; _ }, _) -> true - | _ -> false - -(** Infer the quantity of a let-binding from its optional type annotation. - - Returns [QOne] if the annotation is [Cmd _]; [QOmega] otherwise. - Explicit [el_quantity] / [sl_quantity] annotations always take precedence - (handled at call sites by checking [Option.value] first). *) -let quantity_of_ty_annotation (te_opt : type_expr option) : quantity = - match te_opt with - | Some te when is_cmd_type te -> QOne - | _ -> QOmega - -(** Create a fresh quantity environment. *) -let create_env () : env = - { - quantities = Hashtbl.create 16; - usages = Hashtbl.create 16; - errors = ref []; - } - -(** Declare a variable with its quantity annotation in the environment. - - Resets the usage counter to [UZero]. *) -let env_declare (env : env) (name : string) (q : quantity) : unit = - Hashtbl.replace env.quantities name q; - Hashtbl.replace env.usages name UZero - -(** Record one use of a variable. - - Increments the usage counter: UZero -> UOne -> UMany. *) -let env_use (env : env) (name : string) : unit = - match Hashtbl.find_opt env.usages name with - | Some UZero -> Hashtbl.replace env.usages name UOne - | Some UOne -> Hashtbl.replace env.usages name UMany - | Some UMany -> () (* Already saturated *) - | None -> () (* Not a tracked variable (e.g. free/global) *) - -(** Snapshot the current usage state — used before analysing branches. *) -let env_snapshot (env : env) : (string, usage) Hashtbl.t = - Hashtbl.copy env.usages - -(** Restore a previously snapshotted usage state. *) -let env_restore (env : env) (snapshot : (string, usage) Hashtbl.t) : unit = - Hashtbl.reset env.usages; - Hashtbl.iter (Hashtbl.replace env.usages) snapshot - -(** Join the current usage state with a snapshot (for branching). - - After analysing branch A, we snapshot, restore, analyse branch B, - then join the B-state with the A-snapshot. The result is the - upper-bound usage across both branches. *) -let env_join (env : env) (other : (string, usage) Hashtbl.t) : unit = - Hashtbl.iter (fun name other_usage -> - let current_usage = - Hashtbl.find_opt env.usages name |> Option.value ~default:UZero - in - Hashtbl.replace env.usages name (join_usage current_usage other_usage) - ) other; - (* Handle variables present in current but not in other *) - Hashtbl.iter (fun name current_usage -> - if not (Hashtbl.mem other name) then - Hashtbl.replace env.usages name (join_usage current_usage UZero) - ) env.usages - -(* {1 check_quantity — per-variable compatibility} *) - -(** Check that a single variable's actual usage is compatible with its - declared quantity. - - - quantity 0: must be used 0 times (erased). - - quantity 1: must be used exactly 1 time (linear). - - quantity omega: can be used any number of times (unrestricted). *) -let check_quantity (id : ident) (declared : quantity) (actual : usage) - : unit result = - match (declared, actual) with - (* Erased: must not be used *) - | (QZero, UZero) -> Ok () - | (QZero, _) -> Error (ErasedVariableUsed id, id.span) - (* Linear: must be used exactly once *) - | (QOne, UOne) -> Ok () - | (QOne, UZero) -> Error (LinearVariableUnused id, id.span) - | (QOne, UMany) -> Error (LinearVariableUsedMultiple id, id.span) - (* Unrestricted: any usage is fine *) - | (QOmega, _) -> Ok () - -(* {1 infer_usage — expression/statement walker} *) - -(** Walk an expression and record variable usages in the environment. - - This is a recursive descent that mirrors the AST structure. - For branching constructs (if, match), we snapshot/restore/join so - that usage reflects the worst-case across branches. *) -let rec infer_usage_expr (env : env) (expr : expr) : unit = - match expr with - | ExprVar id -> - env_use env id.name - - | ExprLit _ -> () - - | ExprApp (func, args) -> - infer_usage_expr env func; - List.iter (infer_usage_expr env) args - - | ExprLambda lam -> - (* A lambda may be called zero or many times, so any outer variable - it captures is effectively used QOmega times. We implement this - by walking the body in a temporary copy of the env (shadowing the - lambda's own params), computing the per-variable delta, and then - merging those deltas back scaled by QOmega. - - Consequence: a lambda that captures an @linear variable raises - LinearVariableUsedMultiple, because scale_usage QOmega UOne = UMany. - This closes BUG-004. - - Additionally: lambda params with explicit quantity annotations are - declared in the env so env_use tracks them, and their usage is - verified against their declared quantity after the body walk. - Violations are pushed to env.errors (since infer_usage_expr is unit) - and drained by check_function_quantities at the top level. *) - let param_names = - List.map (fun (p : param) -> p.p_name.name) lam.elam_params - in - let before_snapshot = env_snapshot env in - (* Save any quantity entries that the param names may shadow, so we - can restore them after the lambda body is analysed. *) - let old_quantities = List.map (fun name -> - (name, Hashtbl.find_opt env.quantities name) - ) param_names in - (* Declare each lambda param in the env so env_use can track it. - Annotated params get their declared quantity; unannotated params - get QOmega (unrestricted — no constraint to verify). *) - List.iter (fun (p : param) -> - let q = Option.value p.p_quantity ~default:QOmega in - env_declare env p.p_name.name q - ) lam.elam_params; - infer_usage_expr env lam.elam_body; - let after_snapshot = env_snapshot env in - (* Check annotated params against their declared quantities. - Only params with an explicit annotation are verified — unannotated - params default to QOmega which accepts any usage count. *) - List.iter (fun (p : param) -> - match p.p_quantity with - | None -> () - | Some declared_q -> - let actual = - Hashtbl.find_opt after_snapshot p.p_name.name - |> Option.value ~default:UZero - in - (match check_quantity p.p_name declared_q actual with - | Ok () -> () - | Error e -> env.errors := e :: !(env.errors)) - ) lam.elam_params; - (* Restore outer env, then re-apply QOmega-scaled deltas for captured - outer variables only (exclude lambda params). *) - env_restore env before_snapshot; - (* Restore quantity entries that lambda params may have shadowed. *) - List.iter (fun (name, old_q) -> - match old_q with - | Some q -> Hashtbl.replace env.quantities name q - | None -> Hashtbl.remove env.quantities name - ) old_quantities; - Hashtbl.iter (fun name after_u -> - if List.mem name param_names then () - else begin - let before_u = - Hashtbl.find_opt before_snapshot name |> Option.value ~default:UZero - in - let delta = - if equal_usage before_u after_u then UZero - else after_u - in - (* Any use inside a lambda body counts as potentially-unbounded. *) - let scaled = scale_usage QOmega delta in - let merged = add_usage before_u scaled in - Hashtbl.replace env.usages name merged - end - ) after_snapshot - - | ExprLet lb -> - (* ADR-002 / ADR-007: Let value context is scaled by the binder's - quantity. Concretely: - q·Γ₁ ⊢ e1 : A (Γ₂, x:^q A) ⊢ e2 : B - ───────────────────────────────────── - q·Γ₁ + Γ₂ ⊢ let x :^q = e1 in e2 : B - For the usage analysis we implement this as: snapshot the env, - walk e1 (which records usages into the live env), compute the - per-variable delta added by walking e1, scale each delta entry - by q, restore the snapshot, and re-apply the scaled deltas as - additions. Then walk e2 normally. - - Stage 11: if no explicit quantity annotation is given but the type - annotation is [Cmd _], infer QOne automatically (intrinsic linearity). *) - let q = match lb.el_quantity with - | Some q -> q - | None -> quantity_of_ty_annotation lb.el_ty - in - let before_value = env_snapshot env in - infer_usage_expr env lb.el_value; - let after_value = env_snapshot env in - env_restore env before_value; - (* Re-apply scaled deltas. *) - Hashtbl.iter (fun name after_u -> - let before_u = - Hashtbl.find_opt before_value name |> Option.value ~default:UZero - in - (* Compute the delta usage added by walking el_value. We model - delta as: how many additional uses occurred during the walk. - Since usage is a 3-point lattice, the simplest sound rule is - "if the walk increased the usage at all, the delta is the - after-value's contribution beyond the before-value." We - implement this by treating any rise from before to after as - the delta usage to scale. *) - let delta = - if equal_usage before_u after_u then UZero - else after_u - in - let scaled = scale_usage q delta in - let merged = add_usage before_u scaled in - Hashtbl.replace env.usages name merged - ) after_value; - Option.iter (infer_usage_expr env) lb.el_body - - | ExprIf ei -> - (* Condition is always evaluated *) - infer_usage_expr env ei.ei_cond; - (* Branches: take the upper-bound usage *) - let before_branches = env_snapshot env in - infer_usage_expr env ei.ei_then; - let then_usages = env_snapshot env in - env_restore env before_branches; - Option.iter (infer_usage_expr env) ei.ei_else; - (* Join: current state has else-branch usages (or pre-branch if no else) *) - env_join env then_usages - - | ExprMatch em -> - infer_usage_expr env em.em_scrutinee; - (* Each arm is a branch — join all of them *) - let before_arms = env_snapshot env in - let arm_snapshots = List.map (fun (arm : match_arm) -> - env_restore env before_arms; - Option.iter (infer_usage_expr env) arm.ma_guard; - infer_usage_expr env arm.ma_body; - env_snapshot env - ) em.em_arms in - (* Join all arm snapshots together *) - begin match arm_snapshots with - | [] -> env_restore env before_arms - | first :: rest -> - env_restore env first; - List.iter (env_join env) rest - end - - | ExprTuple exprs -> - List.iter (infer_usage_expr env) exprs - - | ExprArray exprs -> - List.iter (infer_usage_expr env) exprs - - | ExprRecord er -> - List.iter (fun (_id, e_opt) -> - Option.iter (infer_usage_expr env) e_opt - ) er.er_fields; - Option.iter (infer_usage_expr env) er.er_spread - - | ExprField (e, _) -> - infer_usage_expr env e - - | ExprTupleIndex (e, _) -> - infer_usage_expr env e - - | ExprIndex (arr, idx) -> - infer_usage_expr env arr; - infer_usage_expr env idx - - | ExprRowRestrict (e, _) -> - infer_usage_expr env e - - | ExprBlock blk -> - infer_usage_block env blk - - | ExprBinary (left, _, right) -> - infer_usage_expr env left; - infer_usage_expr env right - - | ExprUnary (_, e) -> - infer_usage_expr env e - - | ExprHandle eh -> - infer_usage_expr env eh.eh_body; - List.iter (fun arm -> - match arm with - | HandlerReturn (_pat, body) -> infer_usage_expr env body - | HandlerOp (_op, _pats, body) -> infer_usage_expr env body - ) eh.eh_handlers - - | ExprResume e_opt -> - Option.iter (infer_usage_expr env) e_opt - - | ExprReturn e_opt -> - Option.iter (infer_usage_expr env) e_opt - - | ExprTry et -> - infer_usage_block env et.et_body; - Option.iter (fun arms -> - List.iter (fun (arm : match_arm) -> - Option.iter (infer_usage_expr env) arm.ma_guard; - infer_usage_expr env arm.ma_body - ) arms - ) et.et_catch; - Option.iter (infer_usage_block env) et.et_finally - - | ExprUnsafe ops -> - List.iter (fun op -> - match op with - | UnsafeRead e -> infer_usage_expr env e - | UnsafeWrite (e1, e2) -> - infer_usage_expr env e1; - infer_usage_expr env e2 - | UnsafeOffset (e1, e2) -> - infer_usage_expr env e1; - infer_usage_expr env e2 - | UnsafeTransmute (_, _, e) -> infer_usage_expr env e - | UnsafeForget e -> infer_usage_expr env e - ) ops - - | ExprVariant _ -> () - - | ExprSpan (e, _) -> - infer_usage_expr env e - -(** Walk a block and record variable usages. *) -and infer_usage_block (env : env) (blk : block) : unit = - (* Collect local variables whose quantity must be checked at block end. - For each [StmtLet] whose type annotation infers QOne (i.e. [Cmd _] types), - we declare the bound variable in the env BEFORE processing the statement so - that subsequent uses within the block are tracked via [env_use]. *) - let local_linear_vars = ref [] in - List.iter (fun stmt -> - (match stmt with - | StmtLet sl -> - let q = match sl.sl_quantity with - | Some q -> q - | None -> quantity_of_ty_annotation sl.sl_ty - in - if q = QOne then - (match sl.sl_pat with - | PatVar id -> - env_declare env id.name QOne; - local_linear_vars := id :: !local_linear_vars - | _ -> ()) - | _ -> ()); - infer_usage_stmt env stmt - ) blk.blk_stmts; - Option.iter (infer_usage_expr env) blk.blk_expr; - (* Check that each local linear variable was used exactly once. - Errors are pushed to env.errors for the caller to drain. *) - List.iter (fun id -> - let actual = - Hashtbl.find_opt env.usages id.name |> Option.value ~default:UZero - in - match check_quantity id QOne actual with - | Ok () -> () - | Error e -> env.errors := e :: !(env.errors) - ) !local_linear_vars - -(** Walk a statement and record variable usages. *) -and infer_usage_stmt (env : env) (stmt : stmt) : unit = - match stmt with - | StmtLet sl -> - (* Same scaling treatment as ExprLet — see ADR-002 commentary - there. The statement form has no body to walk afterward; - subsequent statements in the enclosing block are walked - independently by infer_usage_block. - Stage 11: infer QOne from [Cmd _] type annotation when no explicit - quantity is given (same rule as ExprLet). *) - let q = match sl.sl_quantity with - | Some q -> q - | None -> quantity_of_ty_annotation sl.sl_ty - in - let before_value = env_snapshot env in - infer_usage_expr env sl.sl_value; - let after_value = env_snapshot env in - env_restore env before_value; - Hashtbl.iter (fun name after_u -> - let before_u = - Hashtbl.find_opt before_value name |> Option.value ~default:UZero - in - let delta = - if equal_usage before_u after_u then UZero - else after_u - in - let scaled = scale_usage q delta in - let merged = add_usage before_u scaled in - Hashtbl.replace env.usages name merged - ) after_value - | StmtExpr e -> - infer_usage_expr env e - | StmtAssign (lhs, _, rhs) -> - infer_usage_expr env lhs; - infer_usage_expr env rhs - | StmtWhile (cond, body) -> - (* Loop body may execute multiple times — usage inside is omega-scaled. - We analyse once, then add the usage to itself to model repetition. *) - let before_loop = env_snapshot env in - infer_usage_expr env cond; - infer_usage_block env body; - let after_loop = env_snapshot env in - (* Second pass: any variable used in the loop body is used >=2 times *) - env_restore env before_loop; - infer_usage_expr env cond; - infer_usage_block env body; - env_join env after_loop - | StmtFor (_pat, iter, body) -> - infer_usage_expr env iter; - (* Same loop treatment as while *) - let before_body = env_snapshot env in - infer_usage_block env body; - let after_body = env_snapshot env in - env_restore env before_body; - infer_usage_block env body; - env_join env after_body - -(* {1 check_function_quantities — top-level entry point} *) - -(** Check that all quantity-annotated parameters in a function declaration - are used the correct number of times. - - This is the primary integration point called after type checking. - It: - 1. Creates a fresh quantity environment. - 2. Declares each parameter with its quantity (defaulting to omega). - 3. Walks the function body to count usages. - 4. Checks each annotated parameter's usage against its declared quantity. - - Returns [Ok ()] if all quantities are satisfied, or - [Error (err, span)] for the first violation found. *) -let check_function_quantities (fd : fn_decl) : unit result = - let env = create_env () in - (* Step 1: declare parameter quantities *) - List.iter (fun (param : param) -> - let q = Option.value param.p_quantity ~default:QOmega in - env_declare env param.p_name.name q - ) fd.fd_params; - (* Step 2: walk function body to infer usages *) - begin match fd.fd_body with - | FnBlock blk -> infer_usage_block env blk - | FnExpr e -> infer_usage_expr env e - end; - (* Step 3: check each top-level parameter *) - let param_result = - List.fold_left (fun acc (param : param) -> - match acc with - | Error _ -> acc (* Stop at first error *) - | Ok () -> - let declared = Option.value param.p_quantity ~default:QOmega in - let actual = - Hashtbl.find_opt env.usages param.p_name.name - |> Option.value ~default:UZero - in - check_quantity param.p_name declared actual - ) (Ok ()) fd.fd_params - in - (* Step 4: drain errors accumulated by nested lambda param checks. - infer_usage_expr is unit, so nested violations are pushed to - env.errors during the body walk above. Report the first one. *) - match param_result with - | Error _ -> param_result - | Ok () -> - match !(env.errors) with - | [] -> Ok () - | (err, span) :: _ -> Error (err, span) - -(** Check quantities for all functions in a program. - - Iterates over all top-level function declarations and checks each one. - Returns [Ok ()] if all pass, or the first error found. *) -let check_program_quantities (program : program) : unit result = - List.fold_left (fun acc decl -> - match acc with - | Error _ -> acc - | Ok () -> - match decl with - | TopFn fd -> check_function_quantities fd - | _ -> Ok () - ) (Ok ()) program.prog_decls - -(* {1 Backward compatibility aliases} - - These maintain the old API surface while the codebase transitions - to the new function names. *) - -(** @deprecated Use {!check_function_quantities} instead. *) -let check_function (_symbols : Symbol.t) (fd : fn_decl) : unit result = - check_function_quantities fd - -(** @deprecated Use {!check_program_quantities} instead. *) -let check_program (_symbols : Symbol.t) (program : program) : unit result = - check_program_quantities program diff --git a/affinescript/lib/repl.ml b/affinescript/lib/repl.ml deleted file mode 100644 index 5f31369..0000000 --- a/affinescript/lib/repl.ml +++ /dev/null @@ -1,360 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Read-Eval-Print Loop for AffineScript. - - This module provides an interactive REPL for experimenting - with AffineScript expressions and declarations. -*) - -open Ast -open Value -open Interp -open Types - -(** REPL state *) -type state = { - env : env; (** Runtime environment *) - symbols : Symbol.t; (** Symbol table for name resolution *) - type_ctx : Typecheck.context; (** Type checking context *) -} - -(** Extract variable names from a pattern *) -let rec pattern_vars (pat : pattern) : string list = - match pat with - | PatWildcard _ -> [] - | PatVar id -> [id.name] - | PatLit _ -> [] - | PatTuple pats -> List.concat_map pattern_vars pats - | PatRecord (fields, _) -> - List.concat_map (fun (id, pat_opt) -> - match pat_opt with - | Some p -> pattern_vars p - | None -> [id.name] - ) fields - | PatCon (_, pats) -> List.concat_map pattern_vars pats - | PatOr (p1, p2) -> pattern_vars p1 @ pattern_vars p2 - | PatAs (id, pat) -> id.name :: pattern_vars pat - -(** Register symbols for top-level let bindings *) -let register_let_bindings (symbols : Symbol.t) (pat : pattern) : unit = - let vars = pattern_vars pat in - List.iter (fun name -> - let _ = Symbol.define symbols name Symbol.SKVariable Span.dummy Private in - () - ) vars - -(** Bind pattern variables to types in the type context *) -let rec bind_pattern_to_ctx (ctx : Typecheck.context) (pat : pattern) (scheme : Types.scheme) : unit = - match pat with - | PatVar id -> - (* Use Typecheck's bind_var_scheme to add the binding *) - begin match Symbol.lookup ctx.symbols id.name with - | Some sym -> - Hashtbl.replace ctx.var_types sym.sym_id scheme - | None -> () - end - | PatWildcard _ | PatLit _ -> () - | PatTuple pats -> - begin match scheme.sc_body with - | Types.TTuple tys when List.length pats = List.length tys -> - List.iter2 (fun pat ty -> - let sc = { scheme with sc_body = ty } in - bind_pattern_to_ctx ctx pat sc - ) pats tys - | _ -> () - end - | PatRecord (fields, _) -> - List.iter (fun (id, pat_opt) -> - match pat_opt with - | Some p -> bind_pattern_to_ctx ctx p scheme - | None -> - begin match Symbol.lookup ctx.symbols id.name with - | Some sym -> - Hashtbl.replace ctx.var_types sym.sym_id scheme - | None -> () - end - ) fields - | PatCon (_, pats) -> - List.iter (fun pat -> - bind_pattern_to_ctx ctx pat scheme - ) pats - | PatOr (p1, p2) -> - bind_pattern_to_ctx ctx p1 scheme; - bind_pattern_to_ctx ctx p2 scheme - | PatAs (id, pat) -> - begin match Symbol.lookup ctx.symbols id.name with - | Some sym -> - Hashtbl.replace ctx.var_types sym.sym_id scheme - | None -> () - end; - bind_pattern_to_ctx ctx pat scheme - -(** Create initial REPL state *) -let create_state () : state = - let symbols = Symbol.create () in - let type_ctx = Typecheck.create_context symbols in - - (* Register builtins in symbol table with types *) - let register_builtin name ty = - let sym = Symbol.define symbols name Symbol.SKFunction Span.dummy Public in - let scheme = { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ty } in - Hashtbl.replace type_ctx.var_types sym.sym_id scheme - in - - (* print : String -{ω}-> () / Pure *) - let print_ty = TArrow (ty_string, QOmega, ty_unit, EPure) in - register_builtin "print" print_ty; - - (* println : String -{ω}-> () / Pure *) - let println_ty = TArrow (ty_string, QOmega, ty_unit, EPure) in - register_builtin "println" println_ty; - - (* len : 'a -{ω}-> Int / Pure (polymorphic, works for arrays and strings) *) - let alpha_var = TVar (ref (Unbound (0, 0))) in - let len_ty = TArrow (alpha_var, QOmega, ty_int, EPure) in - register_builtin "len" len_ty; - - { - env = create_initial_env (); - symbols; - type_ctx; - } - -(** Process a single line of input *) -let process_line (state : state) (input : string) : (state * string) = - let resolve_ctx = { - Resolve.symbols = state.symbols; - current_module = []; - imports = []; - references = []; - } in - try - (* Try parsing as expression first *) - let expr = Parse_driver.parse_expr ~file:"" input in - (* Resolve names *) - match Resolve.resolve_expr resolve_ctx expr with - | Ok () -> - (* Type check *) - begin match Typecheck.synth state.type_ctx expr with - | Ok ty -> - (* Special handling for top-level let expressions *) - begin match expr with - | ExprLet lb when lb.el_body = None -> - (* Top-level let binding - first type-check the RHS *) - begin match Typecheck.synth state.type_ctx lb.el_value with - | Ok rhs_ty -> - (* Evaluate the RHS *) - begin match eval state.env lb.el_value with - | Ok rhs_val -> - begin match match_pattern lb.el_pat rhs_val with - | Ok bindings -> - (* Register symbols *) - register_let_bindings state.symbols lb.el_pat; - (* Add type bindings to context *) - let scheme = Typecheck.generalize state.type_ctx rhs_ty in - let _ = bind_pattern_to_ctx state.type_ctx lb.el_pat scheme in - (* Update environment *) - let state' = { state with env = extend_env_list bindings state.env } in - (state', Printf.sprintf "() : Unit") - | Error e -> - let msg = Printf.sprintf "Pattern match error: %s" - (show_eval_error e) in - (state, msg) - end - | Error e -> - let msg = Printf.sprintf "Runtime error: %s" - (show_eval_error e) in - (state, msg) - end - | Error e -> - let msg = Printf.sprintf "Type error: %s" - (Typecheck.show_type_error e) in - (state, msg) - end - | _ -> - (* Regular expression - evaluate and display result *) - begin match eval state.env expr with - | Ok value -> - let result = Printf.sprintf "%s : %s" - (Value.show_value value) - (ty_to_string ty) in - (state, result) - | Error e -> - let msg = Printf.sprintf "Runtime error: %s" - (show_eval_error e) in - (state, msg) - end - end - | Error e -> - let msg = Printf.sprintf "Type error: %s" - (Typecheck.show_type_error e) in - (state, msg) - end - | Error (e, _span) -> - let msg = Printf.sprintf "Resolution error: %s" - (Resolve.show_resolve_error e) in - (state, msg) - with - | Parse_driver.Parse_error _ | Lexer.Lexer_error _ -> - (* Try parsing as declaration *) - try - let prog = Parse_driver.parse_string ~file:"" input in - begin match prog.prog_decls with - | [decl] -> - (* Resolve names *) - begin match Resolve.resolve_decl resolve_ctx decl with - | Ok () -> - (* Type check *) - begin match Typecheck.check_decl state.type_ctx decl with - | Ok () -> - (* Evaluate *) - begin match eval_decl state.env decl with - | Ok env' -> - let state' = { state with env = env' } in - let name = match decl with - | TopFn fd -> fd.fd_name.name - | TopConst tc -> tc.tc_name.name - | TopType td -> td.td_name.name - | TopEffect ed -> ed.ed_name.name - | TopTrait td -> td.trd_name.name - | TopImpl _ -> "" - in - (state', Printf.sprintf "Defined %s" name) - | Error e -> - let msg = Printf.sprintf "Runtime error: %s" - (show_eval_error e) in - (state, msg) - end - | Error e -> - let msg = Printf.sprintf "Type error: %s" - (Typecheck.show_type_error e) in - (state, msg) - end - | Error (e, _span) -> - let msg = Printf.sprintf "Resolution error: %s" - (Resolve.show_resolve_error e) in - (state, msg) - end - | _ -> - (state, "Error: Expected single declaration") - end - with - | Parse_driver.Parse_error _ | Lexer.Lexer_error _ -> - (* Try parsing as declaration *) - try - let prog = Parse_driver.parse_string ~file:"" input in - begin match prog.prog_decls with - | [decl] -> - (* Resolve names *) - begin match Resolve.resolve_decl resolve_ctx decl with - | Ok () -> - (* Type check *) - begin match Typecheck.check_decl state.type_ctx decl with - | Ok () -> - (* Evaluate *) - begin match eval_decl state.env decl with - | Ok env' -> - let state' = { state with env = env' } in - let name = match decl with - | TopFn fd -> fd.fd_name.name - | TopConst tc -> tc.tc_name.name - | TopType td -> td.td_name.name - | TopEffect ed -> ed.ed_name.name - | TopTrait td -> td.trd_name.name - | TopImpl _ -> "" - in - (state', Printf.sprintf "Defined %s" name) - | Error e -> - let msg = Printf.sprintf "Runtime error: %s" - (show_eval_error e) in - (state, msg) - end - | Error e -> - let msg = Printf.sprintf "Type error: %s" - (Typecheck.show_type_error e) in - (state, msg) - end - | Error (e, _span) -> - let msg = Printf.sprintf "Resolution error: %s" - (Resolve.show_resolve_error e) in - (state, msg) - end - | _ -> - (state, "Error: Expected single declaration") - end - with - | Parse_driver.Parse_error (msg, _) -> - (state, Printf.sprintf "Parse error: %s" msg) - | Lexer.Lexer_error (msg, _) -> - (state, Printf.sprintf "Lexer error: %s" msg) - | exn -> - (state, Printf.sprintf "Unexpected error: %s" (Printexc.to_string exn)) - -(** Print the REPL prompt *) -let print_prompt () = - print_string ">>> "; - flush stdout - -(** Print the REPL banner *) -let print_banner () = - print_endline "AffineScript REPL v0.1.0"; - print_endline "Type :help for help, :quit to exit"; - print_endline "" - -(** Handle REPL command *) -let handle_command (state : state) (cmd : string) : (state * bool) = - match cmd with - | ":quit" | ":q" | ":exit" -> - print_endline "Goodbye!"; - (state, true) (* exit = true *) - - | ":help" | ":h" -> - print_endline "Available commands:"; - print_endline " :help, :h - Show this help message"; - print_endline " :quit, :q, :exit - Exit the REPL"; - print_endline " :env - Show current environment"; - print_endline " :clear - Clear environment"; - print_endline ""; - print_endline "Enter expressions or declarations to evaluate them."; - (state, false) - - | ":env" -> - print_endline "Current environment:"; - List.iter (fun (name, value) -> - Printf.printf " %s = %s\n" name (Value.show_value value) - ) state.env; - (state, false) - - | ":clear" -> - print_endline "Environment cleared."; - (create_state (), false) - - | _ -> - Printf.printf "Unknown command: %s\n" cmd; - print_endline "Type :help for available commands."; - (state, false) - -(** Run the REPL *) -let rec run_loop (state : state) : unit = - print_prompt (); - match read_line () with - | exception End_of_file -> - print_endline "\nGoodbye!"; - () - | "" -> - run_loop state - | line when String.length line > 0 && line.[0] = ':' -> - let (state', should_exit) = handle_command state line in - if should_exit then () - else run_loop state' - | line -> - let (state', result) = process_line state line in - print_endline result; - run_loop state' - -(** Start the REPL *) -let start () : unit = - print_banner (); - let state = create_state () in - run_loop state diff --git a/affinescript/lib/resolve.ml b/affinescript/lib/resolve.ml deleted file mode 100644 index 35f474e..0000000 --- a/affinescript/lib/resolve.ml +++ /dev/null @@ -1,724 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Name resolution pass. - - This module resolves all names in the AST to symbols in the symbol table. - It runs after parsing and before type checking. -*) - -open Ast - -(** Resolution errors *) -type resolve_error = - | UndefinedVariable of ident - | UndefinedType of ident - | UndefinedEffect of ident - | UndefinedModule of ident - | DuplicateDefinition of ident - | VisibilityError of ident * string - | ImportError of string -[@@deriving show] - -(** Resolution result *) -type 'a result = ('a, resolve_error * Span.t) Result.t - -(** A recorded reference: a use-site for a symbol. - Collected during resolution for the LSP's find-references feature. *) -type reference = { - ref_symbol_id : Symbol.symbol_id; - ref_span : Span.t; -} - -(** Resolution context *) -type context = { - symbols : Symbol.t; - current_module : string list; - imports : (string * Symbol.symbol) list; - mutable references : reference list; - (** Phase C: all use-site references collected during resolution. - Each entry records which symbol was referenced and where. *) -} - -(* Helper for Result bind *) -let ( let* ) = Result.bind - -(** Create a new resolution context *) -let create_context () : context = - let ctx = { - symbols = Symbol.create (); - current_module = []; - imports = []; - references = []; - } in - (* Register built-in functions — must match the interpreter's create_initial_env *) - let def name = let _ = Symbol.define ctx.symbols name SKFunction Span.dummy Public in () in - (* Console I/O *) - def "print"; def "println"; def "eprint"; def "eprintln"; - (* String / char builtins *) - def "len"; def "string_get"; def "string_sub"; def "string_find"; - def "char_to_int"; def "int_to_char"; def "show"; - def "to_lowercase"; def "to_uppercase"; def "trim"; - def "int_to_string"; def "float_to_string"; def "string_length"; - def "parse_int"; def "parse_float"; - (* Numeric coercions and math *) - def "int"; def "float"; - def "sqrt"; def "cbrt"; def "pow_float"; def "floor"; def "ceil"; def "round"; - def "abs"; def "max"; def "min"; - def "sin"; def "cos"; def "tan"; def "atan"; def "atan2"; - def "exp"; def "log"; def "log10"; def "log2"; - (* I/O — files, process, environment *) - def "read_line"; def "read_file"; def "write_file"; def "append_file"; - def "file_exists"; def "is_directory"; - def "getenv"; def "setenv"; def "getcwd"; def "chdir"; - def "list_dir"; def "create_dir"; def "remove_file"; def "remove_dir"; - def "panic"; def "exit"; - (* Time *) - def "time_now"; - (* TEA runtime — The Elm Architecture interpreter loop *) - def "tea_run"; - (* Cmd Msg — linear side-effect obligation builtins (Stage 11) *) - def "cmd_none"; - def "cmd_perform"; - ctx - -(** Record a use-site reference for a symbol (Phase C: find-references). *) -let record_reference (ctx : context) (sym : Symbol.symbol) (span : Span.t) : unit = - ctx.references <- { ref_symbol_id = sym.sym_id; ref_span = span } :: ctx.references - -(** Resolve an identifier *) -let resolve_ident (ctx : context) (id : ident) : Symbol.symbol result = - let name = id.name in - match Symbol.lookup ctx.symbols name with - | Some sym -> - record_reference ctx sym id.span; - Ok sym - | None -> Error (UndefinedVariable id, id.span) - -(** Resolve a type identifier *) -let resolve_type_ident (ctx : context) (id : ident) : Symbol.symbol result = - let name = id.name in - match Symbol.lookup ctx.symbols name with - | Some sym when sym.sym_kind = Symbol.SKType -> - record_reference ctx sym id.span; - Ok sym - | Some sym when sym.sym_kind = Symbol.SKTypeVar -> - record_reference ctx sym id.span; - Ok sym - | Some _ -> Error (UndefinedType id, id.span) - | None -> Error (UndefinedType id, id.span) - -(** Resolve an effect identifier *) -let resolve_effect_ident (ctx : context) (id : ident) : Symbol.symbol result = - let name = id.name in - match Symbol.lookup ctx.symbols name with - | Some sym when sym.sym_kind = Symbol.SKEffect -> - record_reference ctx sym id.span; - Ok sym - | Some _ -> Error (UndefinedEffect id, id.span) - | None -> Error (UndefinedEffect id, id.span) - -(** Resolve a pattern, binding variables *) -let rec resolve_pattern (ctx : context) (pat : pattern) : context result = - match pat with - | PatWildcard _ -> Ok ctx - | PatVar id -> - if Symbol.is_defined_locally ctx.symbols id.name then - Error (DuplicateDefinition id, id.span) - else begin - let _ = Symbol.define ctx.symbols id.name - Symbol.SKVariable id.span Private in - Ok ctx - end - | PatLit _ -> Ok ctx - | PatTuple pats -> - List.fold_left (fun acc pat -> - match acc with - | Error e -> Error e - | Ok ctx -> resolve_pattern ctx pat - ) (Ok ctx) pats - | PatRecord (fields, _has_rest) -> - List.fold_left (fun acc (_id, pat_opt) -> - match acc with - | Error e -> Error e - | Ok ctx -> - match pat_opt with - | Some pat -> resolve_pattern ctx pat - | None -> Ok ctx - ) (Ok ctx) fields - | PatCon (_con, pats) -> - List.fold_left (fun acc pat -> - match acc with - | Error e -> Error e - | Ok ctx -> resolve_pattern ctx pat - ) (Ok ctx) pats - | PatOr (p1, p2) -> - (* Both branches must bind the same variables *) - let* ctx1 = resolve_pattern ctx p1 in - let* _ctx2 = resolve_pattern ctx p2 in - Ok ctx1 - | PatAs (id, pat) -> - let* ctx = resolve_pattern ctx pat in - if Symbol.is_defined_locally ctx.symbols id.name then - Error (DuplicateDefinition id, id.span) - else begin - let _ = Symbol.define ctx.symbols id.name - Symbol.SKVariable id.span Private in - Ok ctx - end - -(** Resolve an expression *) -let rec resolve_expr (ctx : context) (expr : expr) : unit result = - match expr with - | ExprVar id -> - let* _ = resolve_ident ctx id in - Ok () - - | ExprLit _ -> Ok () - - | ExprApp (func, args) -> - let* () = resolve_expr ctx func in - List.fold_left (fun acc arg -> - match acc with - | Error e -> Error e - | Ok () -> resolve_expr ctx arg - ) (Ok ()) args - - | ExprLambda lam -> - Symbol.enter_scope ctx.symbols (Symbol.ScopeFunction "lambda"); - (* Bind parameters *) - List.iter (fun param -> - let _ = Symbol.define ctx.symbols param.p_name.name - Symbol.SKVariable param.p_name.span Private in - () - ) lam.elam_params; - let result = resolve_expr ctx lam.elam_body in - Symbol.exit_scope ctx.symbols; - result - - | ExprLet lb -> - let* () = resolve_expr ctx lb.el_value in - Symbol.enter_scope ctx.symbols Symbol.ScopeBlock; - let* _ = resolve_pattern ctx lb.el_pat in - let result = match lb.el_body with - | Some body -> resolve_expr ctx body - | None -> Ok () - in - Symbol.exit_scope ctx.symbols; - result - - | ExprIf ei -> - let* () = resolve_expr ctx ei.ei_cond in - let* () = resolve_expr ctx ei.ei_then in - (match ei.ei_else with - | Some e -> resolve_expr ctx e - | None -> Ok ()) - - | ExprMatch em -> - let* () = resolve_expr ctx em.em_scrutinee in - List.fold_left (fun acc arm -> - match acc with - | Error e -> Error e - | Ok () -> - Symbol.enter_scope ctx.symbols Symbol.ScopeMatch; - let result = - let* _ = resolve_pattern ctx arm.ma_pat in - let* () = match arm.ma_guard with - | Some g -> resolve_expr ctx g - | None -> Ok () - in - resolve_expr ctx arm.ma_body - in - Symbol.exit_scope ctx.symbols; - result - ) (Ok ()) em.em_arms - - | ExprTuple exprs -> - List.fold_left (fun acc e -> - match acc with - | Error e -> Error e - | Ok () -> resolve_expr ctx e - ) (Ok ()) exprs - - | ExprArray exprs -> - List.fold_left (fun acc e -> - match acc with - | Error e -> Error e - | Ok () -> resolve_expr ctx e - ) (Ok ()) exprs - - | ExprRecord er -> - let* () = List.fold_left (fun acc (_id, e_opt) -> - match acc with - | Error e -> Error e - | Ok () -> - match e_opt with - | Some e -> resolve_expr ctx e - | None -> Ok () - ) (Ok ()) er.er_fields in - (match er.er_spread with - | Some e -> resolve_expr ctx e - | None -> Ok ()) - - | ExprField (e, _field) -> - resolve_expr ctx e - - | ExprTupleIndex (e, _idx) -> - resolve_expr ctx e - - | ExprIndex (arr, idx) -> - let* () = resolve_expr ctx arr in - resolve_expr ctx idx - - | ExprBinary (left, _op, right) -> - let* () = resolve_expr ctx left in - resolve_expr ctx right - - | ExprUnary (_op, e) -> - resolve_expr ctx e - - | ExprBlock blk -> - resolve_block ctx blk - - | ExprReturn e_opt -> - (match e_opt with - | Some e -> resolve_expr ctx e - | None -> Ok ()) - - | ExprHandle eh -> - let* () = resolve_expr ctx eh.eh_body in - List.fold_left (fun acc arm -> - match acc with - | Error e -> Error e - | Ok () -> - Symbol.enter_scope ctx.symbols Symbol.ScopeHandler; - let result = match arm with - | HandlerReturn (pat, body) -> - let* _ = resolve_pattern ctx pat in - resolve_expr ctx body - | HandlerOp (_op, pats, body) -> - let* _ = List.fold_left (fun acc pat -> - match acc with - | Error e -> Error e - | Ok ctx -> resolve_pattern ctx pat - ) (Ok ctx) pats in - resolve_expr ctx body - in - Symbol.exit_scope ctx.symbols; - result - ) (Ok ()) eh.eh_handlers - - | ExprResume e_opt -> - (match e_opt with - | Some e -> resolve_expr ctx e - | None -> Ok ()) - - | ExprRowRestrict (e, _field) -> - resolve_expr ctx e - - | ExprTry et -> - let* () = resolve_block ctx et.et_body in - let* () = match et.et_catch with - | Some arms -> - List.fold_left (fun acc arm -> - match acc with - | Error e -> Error e - | Ok () -> - Symbol.enter_scope ctx.symbols Symbol.ScopeMatch; - let* _ = resolve_pattern ctx arm.ma_pat in - let result = resolve_expr ctx arm.ma_body in - Symbol.exit_scope ctx.symbols; - result - ) (Ok ()) arms - | None -> Ok () - in - (match et.et_finally with - | Some blk -> resolve_block ctx blk - | None -> Ok ()) - - | ExprUnsafe ops -> - (* Resolve expressions within unsafe operations *) - List.fold_left (fun acc op -> - let* () = acc in - match op with - | UnsafeRead e -> resolve_expr ctx e - | UnsafeWrite (e1, e2) -> - let* () = resolve_expr ctx e1 in - resolve_expr ctx e2 - | UnsafeOffset (e1, e2) -> - let* () = resolve_expr ctx e1 in - resolve_expr ctx e2 - | UnsafeTransmute (_, _, e) -> resolve_expr ctx e - | UnsafeForget e -> resolve_expr ctx e - ) (Ok ()) ops - - | ExprVariant (_ty, _variant) -> - Ok () - - | ExprSpan (e, _span) -> - resolve_expr ctx e - -and resolve_block (ctx : context) (blk : block) : unit result = - Symbol.enter_scope ctx.symbols Symbol.ScopeBlock; - let result = - let* () = List.fold_left (fun acc stmt -> - match acc with - | Error e -> Error e - | Ok () -> resolve_stmt ctx stmt - ) (Ok ()) blk.blk_stmts in - match blk.blk_expr with - | Some e -> resolve_expr ctx e - | None -> Ok () - in - Symbol.exit_scope ctx.symbols; - result - -and resolve_stmt (ctx : context) (stmt : stmt) : unit result = - match stmt with - | StmtLet sl -> - let* () = resolve_expr ctx sl.sl_value in - let* _ = resolve_pattern ctx sl.sl_pat in - Ok () - | StmtExpr e -> - resolve_expr ctx e - | StmtAssign (lhs, _op, rhs) -> - let* () = resolve_expr ctx lhs in - resolve_expr ctx rhs - | StmtWhile (cond, body) -> - let* () = resolve_expr ctx cond in - resolve_block ctx body - | StmtFor (pat, iter, body) -> - let* () = resolve_expr ctx iter in - Symbol.enter_scope ctx.symbols Symbol.ScopeBlock; - let* _ = resolve_pattern ctx pat in - let result = resolve_block ctx body in - Symbol.exit_scope ctx.symbols; - result - -(** Resolve a top-level declaration *) -let rec resolve_decl (ctx : context) (decl : top_level) : unit result = - match decl with - | TopFn fd -> - (* First, define the function itself for recursion *) - let _ = Symbol.define ctx.symbols fd.fd_name.name - Symbol.SKFunction fd.fd_name.span fd.fd_vis in - (* Then resolve the body *) - Symbol.enter_scope ctx.symbols (Symbol.ScopeFunction fd.fd_name.name); - (* Bind type parameters *) - List.iter (fun tp -> - let _ = Symbol.define ctx.symbols tp.tp_name.name - Symbol.SKTypeVar tp.tp_name.span Private in - () - ) fd.fd_type_params; - (* Bind parameters *) - List.iter (fun param -> - let _ = Symbol.define ctx.symbols param.p_name.name - Symbol.SKVariable param.p_name.span Private in - () - ) fd.fd_params; - let result = match fd.fd_body with - | FnBlock blk -> resolve_block ctx blk - | FnExpr e -> resolve_expr ctx e - in - Symbol.exit_scope ctx.symbols; - result - - | TopType td -> - let _ = Symbol.define ctx.symbols td.td_name.name - Symbol.SKType td.td_name.span td.td_vis in - (* Register enum variant constructors so they are reachable as expressions *) - (match td.td_body with - | TyEnum variants -> - List.iter (fun (vd : variant_decl) -> - let _ = Symbol.define ctx.symbols vd.vd_name.name - Symbol.SKConstructor vd.vd_name.span td.td_vis in - () - ) variants - | TyAlias _ | TyStruct _ -> ()); - Ok () - - | TopEffect ed -> - let _ = Symbol.define ctx.symbols ed.ed_name.name - Symbol.SKEffect ed.ed_name.span ed.ed_vis in - (* Define each operation *) - List.iter (fun op -> - let _ = Symbol.define ctx.symbols op.eod_name.name - Symbol.SKEffectOp op.eod_name.span ed.ed_vis in - () - ) ed.ed_ops; - Ok () - - | TopTrait td -> - let _ = Symbol.define ctx.symbols td.trd_name.name - Symbol.SKTrait td.trd_name.span td.trd_vis in - Ok () - - | TopImpl ib -> - (* Resolve impl blocks - check trait reference and methods *) - Symbol.enter_scope ctx.symbols (Symbol.ScopeBlock); - (* Bind type parameters *) - List.iter (fun tp -> - let _ = Symbol.define ctx.symbols tp.tp_name.name - Symbol.SKTypeVar tp.tp_name.span Private in - () - ) ib.ib_type_params; - (* Resolve each impl item *) - let result = List.fold_left (fun acc item -> - let* () = acc in - match item with - | ImplFn fd -> resolve_decl ctx (TopFn fd) - | ImplType _ -> Ok () - ) (Ok ()) ib.ib_items in - Symbol.exit_scope ctx.symbols; - result - - | TopConst tc -> - let _ = Symbol.define ctx.symbols tc.tc_name.name - Symbol.SKVariable tc.tc_name.span tc.tc_vis in - resolve_expr ctx tc.tc_value - -(** Resolve an entire program *) -let resolve_program (program : program) : (context, resolve_error * Span.t) Result.t = - let ctx = create_context () in - match List.fold_left (fun acc decl -> - match acc with - | Error e -> Error e - | Ok () -> resolve_decl ctx decl - ) (Ok ()) program.prog_decls with - | Ok () -> Ok ctx - | Error e -> Error e - -(** Resolve and type-check a loaded module's symbols *) -let resolve_and_typecheck_module (loaded_mod : Module_loader.loaded_module) - : (Symbol.t * Typecheck.context) result = - let prog = Module_loader.get_program loaded_mod in - let symbols = Symbol.create () in - let mod_ctx = { symbols; current_module = []; imports = []; references = [] } in - - (* Resolve all declarations in the module *) - let* () = List.fold_left (fun acc decl -> - match acc with - | Error e -> Error e - | Ok () -> resolve_decl mod_ctx decl - ) (Ok ()) prog.prog_decls in - - (* Type-check all declarations *) - let type_ctx = Typecheck.create_context symbols in - let type_result = List.fold_left (fun acc decl -> - match acc with - | Error e -> Error e - | Ok () -> Typecheck.check_decl type_ctx decl - ) (Ok ()) prog.prog_decls in - - match type_result with - | Ok () -> Ok (symbols, type_ctx) - | Error type_err -> - (* Convert type error to resolve error *) - let msg = Typecheck.show_type_error type_err in - Error (ImportError ("Type checking failed: " ^ msg), Span.dummy) - -(** Import symbols from a resolved module into the current context *) -let import_resolved_symbols - (dest_symbols : Symbol.t) - (dest_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) - (source_symbols : Symbol.t) - (source_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) - (_alias : string option) : unit = - - (* Get all public symbols from the source *) - Hashtbl.iter (fun _id sym -> - match sym.Symbol.sym_visibility with - | Public | PubCrate -> - (* Register symbol in destination *) - let _ = Symbol.register_import dest_symbols sym None in - (* Copy type information *) - begin match Hashtbl.find_opt source_types sym.Symbol.sym_id with - | Some scheme -> Hashtbl.replace dest_types sym.Symbol.sym_id scheme - | None -> () - end - | _ -> () (* Private symbols not imported *) - ) source_symbols.all_symbols - -(** Import specific items from resolved symbols *) -let import_specific_items - (dest_symbols : Symbol.t) - (dest_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) - (source_symbols : Symbol.t) - (source_types : (Symbol.symbol_id, Types.scheme) Hashtbl.t) - (items : import_item list) : unit result = - - List.fold_left (fun acc item -> - let* () = acc in - let item_name = item.ii_name.name in - match Symbol.lookup source_symbols item_name with - | Some sym -> - begin match sym.Symbol.sym_visibility with - | Public | PubCrate -> - let alias = Option.map (fun id -> id.name) item.ii_alias in - let _ = Symbol.register_import dest_symbols sym alias in - (* Copy type information *) - begin match Hashtbl.find_opt source_types sym.Symbol.sym_id with - | Some scheme -> Hashtbl.replace dest_types sym.Symbol.sym_id scheme - | None -> () - end; - Ok () - | _ -> - Error (VisibilityError (item.ii_name, "Symbol is not public"), item.ii_name.span) - end - | None -> - Error (UndefinedVariable item.ii_name, item.ii_name.span) - ) (Ok ()) items - -(** Resolve imports in a program using module loader *) -let resolve_imports_with_loader - (ctx : context) - (type_ctx : Typecheck.context) - (loader : Module_loader.t) - (imports : import_decl list) : unit result = - List.fold_left (fun acc import -> - let* () = acc in - match import with - | ImportSimple (path, alias) -> - (* use A.B or use A.B as C *) - let path_strs = List.map (fun id -> id.name) path in - begin match Module_loader.load_module loader path_strs with - | Ok loaded_mod -> - (* Resolve and type-check the module *) - begin match resolve_and_typecheck_module loaded_mod with - | Ok (mod_symbols, mod_type_ctx) -> - let alias_str = Option.map (fun id -> id.name) alias in - import_resolved_symbols ctx.symbols type_ctx.Typecheck.var_types - mod_symbols mod_type_ctx.Typecheck.var_types alias_str; - Ok () - | Error e -> Error e - end - | Error (Module_loader.ModuleNotFound _) -> - let id = List.hd (List.rev path) in - Error (UndefinedModule id, id.span) - | Error e -> - let id = List.hd (List.rev path) in - Error (ImportError (Module_loader.show_load_error e), id.span) - end - | ImportList (path, items) -> - (* use A.B::{x, y} *) - let path_strs = List.map (fun id -> id.name) path in - begin match Module_loader.load_module loader path_strs with - | Ok loaded_mod -> - (* Resolve and type-check the module *) - begin match resolve_and_typecheck_module loaded_mod with - | Ok (mod_symbols, mod_type_ctx) -> - import_specific_items ctx.symbols type_ctx.Typecheck.var_types - mod_symbols mod_type_ctx.Typecheck.var_types items - | Error e -> Error e - end - | Error (Module_loader.ModuleNotFound _) -> - let id = List.hd (List.rev path) in - Error (UndefinedModule id, id.span) - | Error e -> - let id = List.hd (List.rev path) in - Error (ImportError (Module_loader.show_load_error e), id.span) - end - | ImportGlob path -> - (* use A.B::* *) - let path_strs = List.map (fun id -> id.name) path in - begin match Module_loader.load_module loader path_strs with - | Ok loaded_mod -> - (* Resolve and type-check the module *) - begin match resolve_and_typecheck_module loaded_mod with - | Ok (mod_symbols, mod_type_ctx) -> - (* Import all public symbols *) - Hashtbl.iter (fun _id sym -> - match sym.Symbol.sym_visibility with - | Public | PubCrate -> - let _ = Symbol.register_import ctx.symbols sym None in - (* Copy type information *) - begin match Hashtbl.find_opt mod_type_ctx.Typecheck.var_types sym.Symbol.sym_id with - | Some scheme -> Hashtbl.replace type_ctx.Typecheck.var_types sym.Symbol.sym_id scheme - | None -> () - end - | _ -> () - ) mod_symbols.all_symbols; - Ok () - | Error e -> Error e - end - | Error (Module_loader.ModuleNotFound _) -> - let id = List.hd (List.rev path) in - Error (UndefinedModule id, id.span) - | Error e -> - let id = List.hd (List.rev path) in - Error (ImportError (Module_loader.show_load_error e), id.span) - end - ) (Ok ()) imports - -(** Resolve imports in a program (legacy, without module loader) *) -let resolve_imports (ctx : context) (imports : import_decl list) : unit result = - List.fold_left (fun acc import -> - let* () = acc in - match import with - | ImportSimple (path, alias) -> - (* use A.B or use A.B as C *) - let path_strs = List.map (fun id -> id.name) path in - begin match Symbol.lookup_qualified ctx.symbols path_strs with - | Some sym -> - let alias_str = Option.map (fun id -> id.name) alias in - let _ = Symbol.register_import ctx.symbols sym alias_str in - Ok () - | None -> - let id = List.hd (List.rev path) in - Error (UndefinedModule id, id.span) - end - | ImportList (path, items) -> - (* use A.B::{x, y} *) - let _path_strs = List.map (fun id -> id.name) path in - List.fold_left (fun acc item -> - let* () = acc in - match Symbol.lookup ctx.symbols item.ii_name.name with - | Some sym -> - let alias_str = Option.map (fun id -> id.name) item.ii_alias in - let _ = Symbol.register_import ctx.symbols sym alias_str in - Ok () - | None -> - Error (UndefinedVariable item.ii_name, item.ii_name.span) - ) (Ok ()) items - | ImportGlob path -> - (* use A.B::* - for now, just validate the path exists *) - let path_strs = List.map (fun id -> id.name) path in - begin match Symbol.lookup_qualified ctx.symbols path_strs with - | Some _ -> Ok () - | None -> - let id = List.hd (List.rev path) in - Error (UndefinedModule id, id.span) - end - ) (Ok ()) imports - -(** Resolve a complete program with imports *) -let resolve_program_with_imports (program : program) : (context, resolve_error * Span.t) Result.t = - let ctx = create_context () in - (* First resolve imports *) - let* () = resolve_imports ctx program.prog_imports in - (* Then resolve declarations *) - match resolve_program program with - | Ok resolved_ctx -> Ok resolved_ctx - | Error e -> Error e - -(** Resolve a complete program with module loader support *) -let resolve_program_with_loader - (program : program) - (loader : Module_loader.t) : (context * Typecheck.context) result = - let ctx = create_context () in - let type_ctx = Typecheck.create_context ctx.symbols in - (* First resolve imports using module loader *) - let* () = resolve_imports_with_loader ctx type_ctx loader program.prog_imports in - (* Then resolve declarations *) - let* () = List.fold_left (fun acc decl -> - match acc with - | Error e -> Error e - | Ok () -> resolve_decl ctx decl - ) (Ok ()) program.prog_decls in - Ok (ctx, type_ctx) - -(* Phase 1 complete. Future enhancements (Phase 2+): - - Full module system with nested namespaces (Phase 2) - - Forward reference resolution for mutual recursion (Phase 2) - - Type alias expansion during resolution (Phase 2) -*) diff --git a/affinescript/lib/sexpr_dump.ml b/affinescript/lib/sexpr_dump.ml deleted file mode 100644 index 76bc9cd..0000000 --- a/affinescript/lib/sexpr_dump.ml +++ /dev/null @@ -1,616 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** S-expression AST dump for AffineScript. - - Converts every AST node type defined in {!Ast} into a human-readable - S-expression string. Covers: - - - {!Ast.program}, {!Ast.top_level} - - {!Ast.fn_decl}, {!Ast.type_decl}, {!Ast.effect_decl}, {!Ast.trait_decl}, - {!Ast.impl_block} - - {!Ast.expr}, {!Ast.stmt}, {!Ast.block} - - {!Ast.pattern}, {!Ast.literal} - - {!Ast.type_expr}, {!Ast.effect_expr} - - {!Ast.quantity}, {!Ast.ownership}, {!Ast.visibility}, {!Ast.kind} -*) - -open Ast - -(* ====================================================================== - HELPERS - ====================================================================== *) - -(** Produce a string of [n] spaces for indentation. *) -let indent n = String.make n ' ' - -(** Wrap a tag and children into an S-expression with newlines. *) -let sexp_block tag d children = - let child_strs = List.map (fun c -> - Printf.sprintf "\n%s%s" (indent (d + 2)) c - ) children in - Printf.sprintf "(%s%s)" tag (String.concat "" child_strs) - -(* ====================================================================== - SIMPLE ENUMS - ====================================================================== *) - -(** Convert a quantity annotation to an S-expression tag. *) -let quantity_to_sexpr = function - | QZero -> "0" - | QOne -> "1" - | QOmega -> "ω" - -(** Convert an ownership modifier to an S-expression tag. *) -let ownership_to_sexpr = function - | Own -> "own" - | Ref -> "ref" - | Mut -> "mut" - -(** Convert a visibility modifier to an S-expression string. *) -let visibility_to_sexpr = function - | Private -> "private" - | Public -> "public" - | PubCrate -> "pub-crate" - | PubSuper -> "pub-super" - | PubIn ids -> Printf.sprintf "(pub-in %s)" - (String.concat "." (List.map (fun id -> id.name) ids)) - -(** Convert a kind to an S-expression string. *) -let rec kind_to_sexpr = function - | KType -> "Type" - | KRow -> "Row" - | KEffect -> "Effect" - | KArrow (a, b) -> Printf.sprintf "(-> %s %s)" (kind_to_sexpr a) (kind_to_sexpr b) - -(** Convert a binary operator to a string. *) -let binary_op_to_string = function - | OpAdd -> "+" | OpSub -> "-" | OpMul -> "*" | OpDiv -> "/" | OpMod -> "%" - | OpEq -> "==" | OpNe -> "!=" | OpLt -> "<" | OpLe -> "<=" | OpGt -> ">" | OpGe -> ">=" - | OpAnd -> "and" | OpOr -> "or" - | OpBitAnd -> "bit-and" | OpBitOr -> "bit-or" | OpBitXor -> "bit-xor" - | OpShl -> "shl" | OpShr -> "shr" - -(** Convert a unary operator to a string. *) -let unary_op_to_string = function - | OpNeg -> "neg" | OpNot -> "not" | OpBitNot -> "bit-not" - | OpRef -> "ref" | OpDeref -> "deref" - -(** Convert an assignment operator to a string. *) -let assign_op_to_string = function - | AssignEq -> "=" | AssignAdd -> "+=" | AssignSub -> "-=" - | AssignMul -> "*=" | AssignDiv -> "/=" - -(* ====================================================================== - TYPE EXPRESSIONS - ====================================================================== *) - -(** Convert a type argument to S-expression form. *) -let rec type_arg_to_sexpr = function - | TyArg ty -> type_expr_to_sexpr ty - -(** Convert a type expression to S-expression form. *) -and type_expr_to_sexpr = function - | TyVar id -> id.name - | TyCon id -> id.name - | TyApp (id, args) -> - Printf.sprintf "(%s %s)" id.name - (String.concat " " (List.map type_arg_to_sexpr args)) - | TyArrow (a, _q, b, eff) -> - let eff_str = match eff with - | None -> "" - | Some e -> Printf.sprintf " / %s" (effect_expr_to_sexpr e) - in - Printf.sprintf "(-> %s %s%s)" (type_expr_to_sexpr a) (type_expr_to_sexpr b) eff_str - | TyTuple tys -> - Printf.sprintf "(tuple %s)" (String.concat " " (List.map type_expr_to_sexpr tys)) - | TyRecord (fields, rest) -> - let fields_str = String.concat " " (List.map (fun rf -> - Printf.sprintf "(%s %s)" rf.rf_name.name (type_expr_to_sexpr rf.rf_ty) - ) fields) in - let rest_str = match rest with - | None -> "" - | Some id -> Printf.sprintf " ..%s" id.name - in - Printf.sprintf "(record %s%s)" fields_str rest_str - | TyOwn ty -> Printf.sprintf "(own %s)" (type_expr_to_sexpr ty) - | TyRef ty -> Printf.sprintf "(ref %s)" (type_expr_to_sexpr ty) - | TyMut ty -> Printf.sprintf "(mut %s)" (type_expr_to_sexpr ty) - | TyHole -> "_" - -(** Convert an effect expression to S-expression form. *) -and effect_expr_to_sexpr = function - | EffVar id -> id.name - | EffCon (id, args) -> - if args = [] then id.name - else Printf.sprintf "(%s %s)" id.name - (String.concat " " (List.map type_arg_to_sexpr args)) - | EffUnion (a, b) -> - Printf.sprintf "(+ %s %s)" (effect_expr_to_sexpr a) (effect_expr_to_sexpr b) - -(* ====================================================================== - PATTERNS - ====================================================================== *) - -(** Convert a pattern to S-expression form. *) -let rec pattern_to_sexpr = function - | PatWildcard _ -> "_" - | PatVar id -> id.name - | PatLit lit -> literal_to_sexpr lit - | PatCon (id, pats) -> - Printf.sprintf "(%s %s)" id.name - (String.concat " " (List.map pattern_to_sexpr pats)) - | PatTuple pats -> - Printf.sprintf "(tuple %s)" (String.concat " " (List.map pattern_to_sexpr pats)) - | PatRecord (fields, is_open) -> - let fields_str = String.concat " " (List.map (fun (id, pat) -> - match pat with - | None -> id.name - | Some p -> Printf.sprintf "(%s %s)" id.name (pattern_to_sexpr p) - ) fields) in - let open_str = if is_open then " .." else "" in - Printf.sprintf "(record %s%s)" fields_str open_str - | PatOr (a, b) -> Printf.sprintf "(or %s %s)" (pattern_to_sexpr a) (pattern_to_sexpr b) - | PatAs (id, p) -> Printf.sprintf "(as %s %s)" id.name (pattern_to_sexpr p) - -(** Convert a literal to S-expression form. *) -and literal_to_sexpr = function - | LitInt (n, _) -> string_of_int n - | LitFloat (f, _) -> string_of_float f - | LitBool (b, _) -> if b then "#t" else "#f" - | LitChar (c, _) -> Printf.sprintf "#\\%c" c - | LitString (s, _) -> Printf.sprintf "\"%s\"" (String.escaped s) - | LitUnit _ -> "()" - -(* ====================================================================== - EXPRESSIONS - ====================================================================== *) - -(** Convert an expression to S-expression form. - [d] is the current indentation depth. *) -let rec expr_to_sexpr d = function - | ExprLit lit -> literal_to_sexpr lit - | ExprVar id -> id.name - | ExprLet { el_mut; el_quantity; el_pat; el_ty; el_value; el_body } -> - let tag = if el_mut then "let-mut" else "let" in - let q_str = match el_quantity with - | None -> "" - | Some QZero -> " #@erased" - | Some QOne -> " #@linear" - | Some QOmega -> " #@unrestricted" - in - let ty_str = match el_ty with - | None -> "" - | Some t -> Printf.sprintf " : %s" (type_expr_to_sexpr t) - in - let body_str = match el_body with - | None -> "" - | Some b -> Printf.sprintf "\n%s%s" (indent (d + 2)) (expr_to_sexpr (d + 2) b) - in - Printf.sprintf "(%s%s %s%s %s%s)" tag q_str - (pattern_to_sexpr el_pat) ty_str (expr_to_sexpr (d + 2) el_value) body_str - | ExprIf { ei_cond; ei_then; ei_else } -> - let else_str = match ei_else with - | None -> "" - | Some e -> Printf.sprintf "\n%s%s" (indent (d + 2)) (expr_to_sexpr (d + 2) e) - in - Printf.sprintf "(if %s\n%s%s%s)" - (expr_to_sexpr (d + 2) ei_cond) - (indent (d + 2)) (expr_to_sexpr (d + 4) ei_then) else_str - | ExprMatch { em_scrutinee; em_arms } -> - let arms_str = String.concat "" (List.map (fun arm -> - let guard_str = match arm.ma_guard with - | None -> "" - | Some g -> Printf.sprintf " (guard %s)" (expr_to_sexpr (d + 6) g) - in - Printf.sprintf "\n%s(%s%s %s)" (indent (d + 2)) - (pattern_to_sexpr arm.ma_pat) guard_str (expr_to_sexpr (d + 4) arm.ma_body) - ) em_arms) in - Printf.sprintf "(match %s%s)" (expr_to_sexpr (d + 2) em_scrutinee) arms_str - | ExprLambda { elam_params; elam_ret_ty; elam_body } -> - let params_str = String.concat " " (List.map (param_to_sexpr) elam_params) in - let ret_str = match elam_ret_ty with - | None -> "" - | Some t -> Printf.sprintf " -> %s" (type_expr_to_sexpr t) - in - Printf.sprintf "(lambda (%s)%s\n%s%s)" params_str ret_str - (indent (d + 2)) (expr_to_sexpr (d + 2) elam_body) - | ExprApp (fn, args) -> - Printf.sprintf "(app %s %s)" (expr_to_sexpr (d + 2) fn) - (String.concat " " (List.map (expr_to_sexpr (d + 2)) args)) - | ExprField (e, id) -> - Printf.sprintf "(field %s \"%s\")" (expr_to_sexpr (d + 2) e) id.name - | ExprTupleIndex (e, i) -> - Printf.sprintf "(tuple-index %s %d)" (expr_to_sexpr (d + 2) e) i - | ExprIndex (e, idx) -> - Printf.sprintf "(index %s %s)" (expr_to_sexpr (d + 2) e) (expr_to_sexpr (d + 2) idx) - | ExprTuple exprs -> - Printf.sprintf "(tuple %s)" (String.concat " " (List.map (expr_to_sexpr (d + 2)) exprs)) - | ExprArray exprs -> - Printf.sprintf "(array %s)" (String.concat " " (List.map (expr_to_sexpr (d + 2)) exprs)) - | ExprRecord { er_fields; er_spread } -> - let fields_str = String.concat " " (List.map (fun (id, e) -> - match e with - | None -> id.name - | Some v -> Printf.sprintf "(%s %s)" id.name (expr_to_sexpr (d + 4) v) - ) er_fields) in - let spread_str = match er_spread with - | None -> "" - | Some e -> Printf.sprintf " (.. %s)" (expr_to_sexpr (d + 4) e) - in - Printf.sprintf "(record %s%s)" fields_str spread_str - | ExprRowRestrict (e, id) -> - Printf.sprintf "(row-restrict %s \"%s\")" (expr_to_sexpr (d + 2) e) id.name - | ExprBinary (l, op, r) -> - Printf.sprintf "(%s %s %s)" (binary_op_to_string op) - (expr_to_sexpr (d + 2) l) (expr_to_sexpr (d + 2) r) - | ExprUnary (op, e) -> - Printf.sprintf "(%s %s)" (unary_op_to_string op) (expr_to_sexpr (d + 2) e) - | ExprBlock blk -> block_to_sexpr d blk - | ExprReturn e -> - (match e with - | None -> "(return)" - | Some v -> Printf.sprintf "(return %s)" (expr_to_sexpr (d + 2) v)) - | ExprTry { et_body; et_catch; et_finally } -> - let catch_str = match et_catch with - | None -> "" - | Some arms -> - let arms_str = String.concat "" (List.map (fun arm -> - Printf.sprintf "\n%s(%s %s)" (indent (d + 4)) - (pattern_to_sexpr arm.ma_pat) (expr_to_sexpr (d + 6) arm.ma_body) - ) arms) in - Printf.sprintf "\n%s(catch%s)" (indent (d + 2)) arms_str - in - let finally_str = match et_finally with - | None -> "" - | Some blk -> Printf.sprintf "\n%s(finally %s)" (indent (d + 2)) (block_to_sexpr (d + 4) blk) - in - Printf.sprintf "(try %s%s%s)" (block_to_sexpr (d + 2) et_body) catch_str finally_str - | ExprHandle { eh_body; eh_handlers } -> - let handlers_str = String.concat "" (List.map (fun h -> - match h with - | HandlerReturn (pat, body) -> - Printf.sprintf "\n%s(return %s %s)" (indent (d + 2)) - (pattern_to_sexpr pat) (expr_to_sexpr (d + 4) body) - | HandlerOp (id, pats, body) -> - Printf.sprintf "\n%s(op %s (%s) %s)" (indent (d + 2)) id.name - (String.concat " " (List.map pattern_to_sexpr pats)) - (expr_to_sexpr (d + 4) body) - ) eh_handlers) in - Printf.sprintf "(handle %s%s)" (expr_to_sexpr (d + 2) eh_body) handlers_str - | ExprResume e -> - (match e with - | None -> "(resume)" - | Some v -> Printf.sprintf "(resume %s)" (expr_to_sexpr (d + 2) v)) - | ExprUnsafe ops -> - let ops_str = String.concat "" (List.map (fun op -> - Printf.sprintf "\n%s%s" (indent (d + 2)) (unsafe_op_to_sexpr (d + 2) op) - ) ops) in - Printf.sprintf "(unsafe%s)" ops_str - | ExprVariant (ty_id, var_id) -> - Printf.sprintf "(variant %s %s)" ty_id.name var_id.name - | ExprSpan (e, _) -> expr_to_sexpr d e - -(** Convert a block to S-expression form. *) -and block_to_sexpr d blk = - let stmts_str = String.concat "" (List.map (fun s -> - Printf.sprintf "\n%s%s" (indent (d + 2)) (stmt_to_sexpr (d + 2) s) - ) blk.blk_stmts) in - let final_str = match blk.blk_expr with - | None -> "" - | Some e -> Printf.sprintf "\n%s%s" (indent (d + 2)) (expr_to_sexpr (d + 2) e) - in - Printf.sprintf "(block%s%s)" stmts_str final_str - -(** Convert a statement to S-expression form. *) -and stmt_to_sexpr d = function - | StmtLet { sl_mut; sl_quantity; sl_pat; sl_ty; sl_value } -> - let tag = if sl_mut then "let-mut" else "let" in - let q_str = match sl_quantity with - | None -> "" - | Some QZero -> " #@erased" - | Some QOne -> " #@linear" - | Some QOmega -> " #@unrestricted" - in - let ty_str = match sl_ty with - | None -> "" - | Some t -> Printf.sprintf " : %s" (type_expr_to_sexpr t) - in - Printf.sprintf "(%s%s %s%s %s)" tag q_str - (pattern_to_sexpr sl_pat) ty_str (expr_to_sexpr (d + 2) sl_value) - | StmtExpr e -> expr_to_sexpr d e - | StmtAssign (lhs, op, rhs) -> - Printf.sprintf "(assign %s %s %s)" (assign_op_to_string op) - (expr_to_sexpr (d + 2) lhs) (expr_to_sexpr (d + 2) rhs) - | StmtWhile (cond, body) -> - Printf.sprintf "(while %s\n%s%s)" (expr_to_sexpr (d + 2) cond) - (indent (d + 2)) (block_to_sexpr (d + 2) body) - | StmtFor (pat, iter, body) -> - Printf.sprintf "(for %s %s\n%s%s)" (pattern_to_sexpr pat) - (expr_to_sexpr (d + 2) iter) (indent (d + 2)) (block_to_sexpr (d + 2) body) - -(** Convert an unsafe operation to S-expression form. *) -and unsafe_op_to_sexpr d = function - | UnsafeRead e -> - Printf.sprintf "(unsafe-read %s)" (expr_to_sexpr (d + 2) e) - | UnsafeWrite (dst, val_) -> - Printf.sprintf "(unsafe-write %s %s)" (expr_to_sexpr (d + 2) dst) (expr_to_sexpr (d + 2) val_) - | UnsafeOffset (base, off) -> - Printf.sprintf "(unsafe-offset %s %s)" (expr_to_sexpr (d + 2) base) (expr_to_sexpr (d + 2) off) - | UnsafeTransmute (from_ty, to_ty, e) -> - Printf.sprintf "(unsafe-transmute %s %s %s)" - (type_expr_to_sexpr from_ty) (type_expr_to_sexpr to_ty) (expr_to_sexpr (d + 2) e) - | UnsafeForget e -> - Printf.sprintf "(unsafe-forget %s)" (expr_to_sexpr (d + 2) e) - -(** Convert a parameter to S-expression form. *) -and param_to_sexpr p = - let q_str = match p.p_quantity with - | None -> "" - | Some q -> Printf.sprintf "%s " (quantity_to_sexpr q) - in - let own_str = match p.p_ownership with - | None -> "" - | Some o -> Printf.sprintf "%s " (ownership_to_sexpr o) - in - Printf.sprintf "(%s%s%s : %s)" q_str own_str p.p_name.name (type_expr_to_sexpr p.p_ty) - -(* ====================================================================== - TYPE PARAMETERS & CONSTRAINTS - ====================================================================== *) - -(** Convert a type parameter to S-expression form. *) -let type_param_to_sexpr tp = - let q_str = match tp.tp_quantity with - | None -> "" - | Some q -> Printf.sprintf "%s " (quantity_to_sexpr q) - in - let k_str = match tp.tp_kind with - | None -> "" - | Some k -> Printf.sprintf " : %s" (kind_to_sexpr k) - in - Printf.sprintf "(%s%s%s)" q_str tp.tp_name.name k_str - -(** Convert a trait bound to S-expression form. *) -let trait_bound_to_sexpr tb = - if tb.tb_args = [] then tb.tb_name.name - else Printf.sprintf "(%s %s)" tb.tb_name.name - (String.concat " " (List.map type_arg_to_sexpr tb.tb_args)) - -(** Convert a constraint to S-expression form. *) -let constraint_to_sexpr = function - | ConstraintTrait (id, bounds) -> - Printf.sprintf "(where %s : %s)" id.name - (String.concat " + " (List.map trait_bound_to_sexpr bounds)) - -(* ====================================================================== - TOP-LEVEL DECLARATIONS - ====================================================================== *) - -(** Convert a function signature to S-expression form. *) -let fn_sig_to_sexpr d fs = - let vis_str = visibility_to_sexpr fs.fs_vis in - let tparams_str = match fs.fs_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let params_str = String.concat " " (List.map param_to_sexpr fs.fs_params) in - let ret_str = match fs.fs_ret_ty with - | None -> "" - | Some t -> Printf.sprintf " -> %s" (type_expr_to_sexpr t) - in - let eff_str = match fs.fs_eff with - | None -> "" - | Some e -> Printf.sprintf " / %s" (effect_expr_to_sexpr e) - in - ignore d; - Printf.sprintf "(fn-sig %s \"%s\"%s (%s)%s%s)" - vis_str fs.fs_name.name tparams_str params_str ret_str eff_str - -(** Convert a function body to S-expression form. *) -let fn_body_to_sexpr d = function - | FnBlock blk -> block_to_sexpr d blk - | FnExpr e -> expr_to_sexpr d e - -(** Convert a function declaration to S-expression form. *) -let fn_decl_to_sexpr d fd = - let vis_str = visibility_to_sexpr fd.fd_vis in - let total_str = if fd.fd_total then " total" else "" in - let tparams_str = match fd.fd_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let params_str = String.concat " " (List.map param_to_sexpr fd.fd_params) in - let ret_str = match fd.fd_ret_ty with - | None -> "" - | Some t -> Printf.sprintf " -> %s" (type_expr_to_sexpr t) - in - let eff_str = match fd.fd_eff with - | None -> "" - | Some e -> Printf.sprintf " / %s" (effect_expr_to_sexpr e) - in - let where_str = match fd.fd_where with - | [] -> "" - | cs -> Printf.sprintf "\n%s%s" (indent (d + 2)) - (String.concat " " (List.map constraint_to_sexpr cs)) - in - Printf.sprintf "(fn %s%s \"%s\"%s (%s)%s%s%s\n%s%s)" - vis_str total_str fd.fd_name.name tparams_str params_str - ret_str eff_str where_str - (indent (d + 2)) (fn_body_to_sexpr (d + 2) fd.fd_body) - -(** Convert a type declaration to S-expression form. *) -let type_decl_to_sexpr d td = - let vis_str = visibility_to_sexpr td.td_vis in - let tparams_str = match td.td_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let body_str = match td.td_body with - | TyAlias ty -> Printf.sprintf "(alias %s)" (type_expr_to_sexpr ty) - | TyStruct fields -> - let fields_str = String.concat "" (List.map (fun sf -> - Printf.sprintf "\n%s(%s %s %s)" (indent (d + 4)) - (visibility_to_sexpr sf.sf_vis) sf.sf_name.name (type_expr_to_sexpr sf.sf_ty) - ) fields) in - Printf.sprintf "(struct%s)" fields_str - | TyEnum variants -> - let variants_str = String.concat "" (List.map (fun vd -> - let fields_str = match vd.vd_fields with - | [] -> "" - | fs -> Printf.sprintf " %s" (String.concat " " (List.map type_expr_to_sexpr fs)) - in - let ret_str = match vd.vd_ret_ty with - | None -> "" - | Some t -> Printf.sprintf " -> %s" (type_expr_to_sexpr t) - in - Printf.sprintf "\n%s(%s%s%s)" (indent (d + 4)) vd.vd_name.name fields_str ret_str - ) variants) in - Printf.sprintf "(enum%s)" variants_str - in - Printf.sprintf "(type %s \"%s\"%s\n%s%s)" - vis_str td.td_name.name tparams_str (indent (d + 2)) body_str - -(** Convert an effect declaration to S-expression form. *) -let effect_decl_to_sexpr d ed = - let vis_str = visibility_to_sexpr ed.ed_vis in - let tparams_str = match ed.ed_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let ops_str = String.concat "" (List.map (fun eod -> - let params_str = String.concat " " (List.map param_to_sexpr eod.eod_params) in - let ret_str = match eod.eod_ret_ty with - | None -> "" - | Some t -> Printf.sprintf " -> %s" (type_expr_to_sexpr t) - in - Printf.sprintf "\n%s(op \"%s\" (%s)%s)" (indent (d + 2)) - eod.eod_name.name params_str ret_str - ) ed.ed_ops) in - Printf.sprintf "(effect %s \"%s\"%s%s)" vis_str ed.ed_name.name tparams_str ops_str - -(** Convert a trait declaration to S-expression form. *) -let trait_decl_to_sexpr d trd = - let vis_str = visibility_to_sexpr trd.trd_vis in - let tparams_str = match trd.trd_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let super_str = match trd.trd_super with - | [] -> "" - | bs -> Printf.sprintf " : %s" (String.concat " + " (List.map trait_bound_to_sexpr bs)) - in - let items_str = String.concat "" (List.map (fun item -> - Printf.sprintf "\n%s%s" (indent (d + 2)) (match item with - | TraitFn fs -> fn_sig_to_sexpr (d + 2) fs - | TraitFnDefault fd -> fn_decl_to_sexpr (d + 2) fd - | TraitType { tt_name; tt_kind; tt_default } -> - let kind_str = match tt_kind with - | None -> "" - | Some k -> Printf.sprintf " : %s" (kind_to_sexpr k) - in - let default_str = match tt_default with - | None -> "" - | Some t -> Printf.sprintf " = %s" (type_expr_to_sexpr t) - in - Printf.sprintf "(assoc-type \"%s\"%s%s)" tt_name.name kind_str default_str) - ) trd.trd_items) in - Printf.sprintf "(trait %s \"%s\"%s%s%s)" vis_str trd.trd_name.name tparams_str super_str items_str - -(** Convert an impl block to S-expression form. *) -let impl_block_to_sexpr d ib = - let tparams_str = match ib.ib_type_params with - | [] -> "" - | tps -> Printf.sprintf " [%s]" (String.concat " " (List.map type_param_to_sexpr tps)) - in - let trait_str = match ib.ib_trait_ref with - | None -> "" - | Some tr -> - let args_str = match tr.tr_args with - | [] -> "" - | args -> Printf.sprintf " %s" (String.concat " " (List.map type_arg_to_sexpr args)) - in - Printf.sprintf " %s%s for" tr.tr_name.name args_str - in - let where_str = match ib.ib_where with - | [] -> "" - | cs -> Printf.sprintf "\n%s%s" (indent (d + 2)) - (String.concat " " (List.map constraint_to_sexpr cs)) - in - let items_str = String.concat "" (List.map (fun item -> - Printf.sprintf "\n%s%s" (indent (d + 2)) (match item with - | ImplFn fd -> fn_decl_to_sexpr (d + 2) fd - | ImplType (id, ty) -> - Printf.sprintf "(type \"%s\" %s)" id.name (type_expr_to_sexpr ty)) - ) ib.ib_items) in - Printf.sprintf "(impl%s%s %s%s%s)" tparams_str trait_str - (type_expr_to_sexpr ib.ib_self_ty) where_str items_str - -(** Convert a top-level declaration to S-expression form. *) -let top_level_to_sexpr d = function - | TopFn fd -> fn_decl_to_sexpr d fd - | TopType td -> type_decl_to_sexpr d td - | TopEffect ed -> effect_decl_to_sexpr d ed - | TopTrait trd -> trait_decl_to_sexpr d trd - | TopImpl ib -> impl_block_to_sexpr d ib - | TopConst { tc_vis; tc_name; tc_ty; tc_value } -> - Printf.sprintf "(const %s \"%s\" %s %s)" - (visibility_to_sexpr tc_vis) tc_name.name - (type_expr_to_sexpr tc_ty) (expr_to_sexpr (d + 2) tc_value) - -(* ====================================================================== - IMPORTS & MODULE PATH - ====================================================================== *) - -(** Convert a module path to a dotted string. *) -let module_path_to_string path = - String.concat "." (List.map (fun id -> id.name) path) - -(** Convert an import declaration to S-expression form. *) -let import_to_sexpr = function - | ImportSimple (path, alias) -> - let alias_str = match alias with - | None -> "" - | Some id -> Printf.sprintf " as %s" id.name - in - Printf.sprintf "(import \"%s\"%s)" (module_path_to_string path) alias_str - | ImportList (path, items) -> - let items_str = String.concat " " (List.map (fun ii -> - match ii.ii_alias with - | None -> ii.ii_name.name - | Some alias -> Printf.sprintf "(%s as %s)" ii.ii_name.name alias.name - ) items) in - Printf.sprintf "(import \"%s\" {%s})" (module_path_to_string path) items_str - | ImportGlob path -> - Printf.sprintf "(import \"%s\" *)" (module_path_to_string path) - -(* ====================================================================== - COMPLETE PROGRAM - ====================================================================== *) - -(** Convert a complete AffineScript program to S-expression form. - - This is the main entry point for the S-expression dump. The output - is a single S-expression string that faithfully represents every - node in the parse tree. *) -let program_to_sexpr prog = - let buf = Buffer.create 4096 in - Buffer.add_string buf "(program"; - (* Module declaration *) - (match prog.prog_module with - | None -> () - | Some path -> - Buffer.add_string buf - (Printf.sprintf "\n (module \"%s\")" (module_path_to_string path))); - (* Imports *) - List.iter (fun imp -> - Buffer.add_string buf "\n "; - Buffer.add_string buf (import_to_sexpr imp) - ) prog.prog_imports; - (* Declarations *) - List.iter (fun decl -> - Buffer.add_string buf "\n "; - Buffer.add_string buf (top_level_to_sexpr 2 decl) - ) prog.prog_decls; - Buffer.add_char buf ')'; - Buffer.contents buf diff --git a/affinescript/lib/span.ml b/affinescript/lib/span.ml deleted file mode 100644 index dbee822..0000000 --- a/affinescript/lib/span.ml +++ /dev/null @@ -1,35 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Source location tracking for error messages *) - -type pos = { - line : int; - col : int; - offset : int; -} -[@@deriving show, eq, ord] - -type t = { - start_pos : pos; - end_pos : pos; - file : string; -} -[@@deriving show, eq, ord] - -let dummy = { - start_pos = { line = 0; col = 0; offset = 0 }; - end_pos = { line = 0; col = 0; offset = 0 }; - file = ""; -} - -let make ~file ~start_pos ~end_pos = { start_pos; end_pos; file } - -let merge s1 s2 = - if s1.file <> s2.file then - invalid_arg "Span.merge: different files" - else - { start_pos = s1.start_pos; end_pos = s2.end_pos; file = s1.file } - -let pp_short fmt span = - Format.fprintf fmt "%s:%d:%d" span.file span.start_pos.line span.start_pos.col diff --git a/affinescript/lib/symbol.ml b/affinescript/lib/symbol.ml deleted file mode 100644 index 6bf7fc4..0000000 --- a/affinescript/lib/symbol.ml +++ /dev/null @@ -1,202 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Symbol table for name resolution. - - This module provides the symbol table infrastructure for tracking - bindings during name resolution and type checking. -*) - -open Ast - -(** Unique identifier for symbols *) -type symbol_id = int -[@@deriving show, eq, ord] - -(** Symbol kinds *) -type symbol_kind = - | SKVariable (** Local or global variable *) - | SKFunction (** Function definition *) - | SKType (** Type definition *) - | SKTypeVar (** Type variable *) - | SKEffect (** Effect definition *) - | SKEffectOp (** Effect operation *) - | SKTrait (** Trait definition *) - | SKModule (** Module *) - | SKConstructor (** Data constructor *) -[@@deriving show, eq] - -(** A symbol entry in the symbol table *) -type symbol = { - sym_id : symbol_id; - sym_name : string; - sym_kind : symbol_kind; - sym_span : Span.t; - sym_visibility : visibility; - sym_type : type_expr option; (** Filled during type checking *) - sym_quantity : quantity option; -} -[@@deriving show] - -(** Symbol table scope *) -type scope = { - scope_parent : scope option; - scope_symbols : (string, symbol) Hashtbl.t; - scope_kind : scope_kind; -} - -and scope_kind = - | ScopeGlobal - | ScopeModule of string - | ScopeFunction of string - | ScopeBlock - | ScopeMatch - | ScopeHandler - -(** The symbol table *) -type t = { - mutable current_scope : scope; - mutable next_id : symbol_id; - all_symbols : (symbol_id, symbol) Hashtbl.t; -} - -(** Create a new symbol table *) -let create () : t = - let global_scope = { - scope_parent = None; - scope_symbols = Hashtbl.create 64; - scope_kind = ScopeGlobal; - } in - { - current_scope = global_scope; - next_id = 0; - all_symbols = Hashtbl.create 256; - } - -(** Generate a fresh symbol ID *) -let fresh_id (table : t) : symbol_id = - let id = table.next_id in - table.next_id <- id + 1; - id - -(** Enter a new scope *) -let enter_scope (table : t) (kind : scope_kind) : unit = - let new_scope = { - scope_parent = Some table.current_scope; - scope_symbols = Hashtbl.create 16; - scope_kind = kind; - } in - table.current_scope <- new_scope - -(** Exit the current scope *) -let exit_scope (table : t) : unit = - match table.current_scope.scope_parent with - | Some parent -> table.current_scope <- parent - | None -> failwith "Cannot exit global scope" - -(** Define a new symbol in the current scope *) -let define (table : t) (name : string) (kind : symbol_kind) - (span : Span.t) (vis : visibility) : symbol = - let sym = { - sym_id = fresh_id table; - sym_name = name; - sym_kind = kind; - sym_span = span; - sym_visibility = vis; - sym_type = None; - sym_quantity = None; - } in - Hashtbl.replace table.current_scope.scope_symbols name sym; - Hashtbl.replace table.all_symbols sym.sym_id sym; - sym - -(** Look up a symbol in the current scope and parents *) -let rec lookup_in_scope (scope : scope) (name : string) : symbol option = - match Hashtbl.find_opt scope.scope_symbols name with - | Some sym -> Some sym - | None -> - match scope.scope_parent with - | Some parent -> lookup_in_scope parent name - | None -> None - -(** Look up a symbol by name *) -let lookup (table : t) (name : string) : symbol option = - lookup_in_scope table.current_scope name - -(** Look up a symbol by ID *) -let lookup_by_id (table : t) (id : symbol_id) : symbol option = - Hashtbl.find_opt table.all_symbols id - -(** Check if a name is defined in the current scope (not parents) *) -let is_defined_locally (table : t) (name : string) : bool = - Hashtbl.mem table.current_scope.scope_symbols name - -(** Update a symbol's type *) -let set_type (table : t) (id : symbol_id) (ty : type_expr) : unit = - match Hashtbl.find_opt table.all_symbols id with - | Some sym -> - let updated = { sym with sym_type = Some ty } in - Hashtbl.replace table.all_symbols id updated - | None -> () - -(** Update a symbol's quantity *) -let set_quantity (table : t) (id : symbol_id) (q : quantity) : unit = - match Hashtbl.find_opt table.all_symbols id with - | Some sym -> - let updated = { sym with sym_quantity = Some q } in - Hashtbl.replace table.all_symbols id updated - | None -> () - -(** Look up a qualified path (Foo.Bar.x) *) -let lookup_qualified (table : t) (path : string list) : symbol option = - match path with - | [] -> None - | [name] -> lookup table name - | _modules -> - (* For qualified paths, we need to traverse module scopes *) - (* Currently, we flatten to the final name since modules aren't fully implemented *) - let final_name = List.hd (List.rev path) in - lookup table final_name - -(** Check if a symbol is visible from the current scope *) -let is_visible (table : t) (sym : symbol) : bool = - match sym.sym_visibility with - | Private -> - (* Private symbols are only visible in the same scope *) - Hashtbl.mem table.current_scope.scope_symbols sym.sym_name - | Public -> true - | PubCrate -> true (* Within same crate, always visible *) - | PubSuper -> - (* Visible in parent module - check if we're in a child scope *) - begin match table.current_scope.scope_parent with - | Some _ -> true - | None -> false - end - | PubIn _path -> - (* Visible in specified path - for now, treat as public *) - true - -(** Register an import, making a symbol available under a new name *) -let register_import (table : t) (sym : symbol) (alias : string option) : symbol = - let name = match alias with - | Some n -> n - | None -> sym.sym_name - in - let imported = { sym with sym_name = name } in - Hashtbl.replace table.current_scope.scope_symbols name imported; - imported - -(** Look up an effect operation *) -let lookup_effect_op (table : t) (effect_name : string) (op_name : string) : symbol option = - (* First find the effect, then look for the operation *) - match lookup table effect_name with - | Some eff_sym when eff_sym.sym_kind = SKEffect -> - (* Effect found, now look for the operation *) - lookup table op_name - | _ -> None - -(* Phase 1 complete. Future enhancements (Phase 2+): - - Full module system with nested namespaces (Phase 2) - - Glob imports with filtering (Phase 2) - - Re-exports and visibility inheritance (Phase 2) -*) diff --git a/affinescript/lib/tea_bridge.ml b/affinescript/lib/tea_bridge.ml deleted file mode 100644 index 8d55ef5..0000000 --- a/affinescript/lib/tea_bridge.ml +++ /dev/null @@ -1,296 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** TEA Bridge Wasm Generator. - - Generates a valid WebAssembly 1.0 module that implements the - AffineScript TEA runtime ABI for the TitleScreen. - - The bridge module stores TitleModel in linear memory at a fixed - layout, and exports clean i32 functions that JS can call to drive - a PixiJS scene without needing a full AffineScript → Wasm compiler. - - {2 Memory layout} - - Model state is stored starting at byte offset 64 (after the WASM - binary header region) with the following field layout: - - {v - Offset Type Field Default - +0 i32 screen_w 1280 - +4 i32 screen_h 720 - +8 i32 bgm_playing 0 - +12 i32 selected_tag 0 - v} - - {2 selected_tag encoding} - - {v - 0 = none - 1 = new_game - 2 = load_game - 3 = settings - 4 = credits - v} - - {2 Msg tag encoding (input to affinescript_update)} - - {v - 0 = NewGame - 1 = LoadGame - 2 = Settings - 3 = Credits - v} - - The update function is branchless: [selected_tag := msg + 1], - which naturally maps {NewGame=0 → 1, …, Credits=3 → 4}. - - {2 Exported functions} - - {ul - {li [affinescript_init()]} - {li [affinescript_update(msg: i32)]} - {li [affinescript_get_screen_w() -> i32]} - {li [affinescript_get_screen_h() -> i32]} - {li [affinescript_get_bgm_playing() -> i32]} - {li [affinescript_get_selected() -> i32]} - {li [affinescript_set_screen(w: i32, h: i32)]} - {li [memory]} - } - - {2 Ownership annotations} - - The [affinescript.ownership] custom section marks [update]'s [msg] - parameter as Linear (kind byte 1) — consumed exactly once per TEA - update cycle — encoding the AffineScript linearity invariant for - typed-wasm Level 10 verification. - - A companion [affinescript.tea_layout] custom section encodes the - model field layout for tooling. -*) - -open Wasm - -(** Base address of the TitleModel in linear memory. *) -let model_base = 64 - -(** Field offsets relative to [model_base]. *) -let off_screen_w = 0 -let off_screen_h = 4 -let off_bgm_playing = 8 -let off_selected = 12 - -(** [load_field off] — Wasm instructions that load an i32 from - [(model_base + off)], leaving the value on the stack. *) -let load_field off : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Load (2, 0); -] - -(** [store_const off v] — Wasm instructions that store constant [v] - to [(model_base + off)]. *) -let store_const off v : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Const (Int32.of_int v); - I32Store (2, 0); -] - -(* ------------------------------------------------------------------------- - Type section - ------------------------------------------------------------------------- - Index Signature Used by - 0 () -> () fn_init - 1 (i32) -> () fn_update - 2 () -> i32 fn_get_screen_w/_h/_bgm/_selected - 3 (i32, i32) -> () fn_set_screen - ------------------------------------------------------------------------- *) - -let types : func_type list = [ - { ft_params = []; ft_results = [] }; - { ft_params = [I32]; ft_results = [] }; - { ft_params = []; ft_results = [I32] }; - { ft_params = [I32; I32]; ft_results = [] }; -] - -(* ------------------------------------------------------------------------- - Function bodies - ------------------------------------------------------------------------- *) - -(** fn 0: affinescript_init() — write default TitleModel to memory. *) -let fn_init : func = { - f_type = 0; - f_locals = []; - f_body = - store_const off_screen_w 1280 @ - store_const off_screen_h 720 @ - store_const off_bgm_playing 0 @ - store_const off_selected 0; -} - -(** fn 1: affinescript_update(msg: i32) — branchless update: - [selected_tag := msg + 1]. msg is Linear (consumed exactly once). *) -let fn_update : func = { - f_type = 1; - f_locals = []; - f_body = [ - (* address of selected_tag *) - I32Const (Int32.of_int (model_base + off_selected)); - (* compute msg + 1: maps NewGame=0→1, LoadGame=1→2, Settings=2→3, Credits=3→4 *) - LocalGet 0; - I32Const 1l; - I32Add; - I32Store (2, 0); - ]; -} - -(** fn 2: affinescript_get_screen_w() -> i32 *) -let fn_get_screen_w : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_screen_w; -} - -(** fn 3: affinescript_get_screen_h() -> i32 *) -let fn_get_screen_h : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_screen_h; -} - -(** fn 4: affinescript_get_bgm_playing() -> i32 *) -let fn_get_bgm_playing : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_bgm_playing; -} - -(** fn 5: affinescript_get_selected() -> i32 *) -let fn_get_selected : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_selected; -} - -(** fn 6: affinescript_set_screen(w: i32, h: i32) — store new dimensions. - Handles PixiJS resize events by updating the model. *) -let fn_set_screen : func = { - f_type = 3; - f_locals = []; - f_body = [ - I32Const (Int32.of_int (model_base + off_screen_w)); - LocalGet 0; - I32Store (2, 0); - I32Const (Int32.of_int (model_base + off_screen_h)); - LocalGet 1; - I32Store (2, 0); - ]; -} - -(* ------------------------------------------------------------------------- - Custom sections - ------------------------------------------------------------------------- *) - -(** Build the [affinescript.ownership] custom section payload. - - Encoding (all little-endian): - {v - u32 entry_count - per entry: - u32 func_index - u8 param_count - u8* param_kind (0=Unrestricted 1=Linear 2=SharedBorrow 3=ExclBorrow) - u8 return_kind - v} - - Only [fn_update] (index 1) carries a Linear param for msg. *) -let build_ownership_section () : bytes = - let buf = Buffer.create 64 in - let u32 n = - Buffer.add_char buf (Char.chr (n land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 16) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 24) land 0xff)) - in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - u32 7; (* 7 annotated functions *) - (* fn 0 init: () → (), no params, Unrestricted return *) - u32 0; u8 0; u8 0; - (* fn 1 update: (msg: Linear) → (), return Unrestricted *) - u32 1; u8 1; u8 1 (* Linear=1 *); u8 0; - (* fn 2-5 getters: () → i32, Unrestricted *) - u32 2; u8 0; u8 0; - u32 3; u8 0; u8 0; - u32 4; u8 0; u8 0; - u32 5; u8 0; u8 0; - (* fn 6 set_screen: (i32, i32) → (), both Unrestricted *) - u32 6; u8 2; u8 0; u8 0; u8 0; - Buffer.to_bytes buf - -(** Build the [affinescript.tea_layout] custom section. - - Compact binary descriptor for the TitleModel memory layout: - {v - u8 version = 1 - u8 base_addr = 64 - u8 field_count = 4 - per field: u8 name_len, name_bytes, u8 offset, u8 type_tag (0x49=i32) - v} *) -let build_tea_layout_section () : bytes = - let buf = Buffer.create 64 in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - let field name off = - u8 (String.length name); - Buffer.add_string buf name; - u8 off; - u8 0x49 (* i32 type tag *) - in - u8 1; (* version 1 *) - u8 model_base; (* base = 64 *) - u8 4; (* 4 fields *) - field "screen_w" off_screen_w; - field "screen_h" off_screen_h; - field "bgm_playing" off_bgm_playing; - field "selected" off_selected; - Buffer.to_bytes buf - -(* ------------------------------------------------------------------------- - Module assembly - ------------------------------------------------------------------------- *) - -(** Generate the complete TEA bridge Wasm module for the TitleScreen. - - The resulting module is suitable for use with AffineTEA.js in IDApTIK. - Write it with [Wasm_encode.write_module_to_file]. *) -let generate () : wasm_module = { - types; - funcs = [ - fn_init; - fn_update; - fn_get_screen_w; - fn_get_screen_h; - fn_get_bgm_playing; - fn_get_selected; - fn_set_screen; - ]; - tables = []; - mems = [{ mem_type = { lim_min = 1; lim_max = None } }]; - globals = []; - imports = []; - elems = []; - datas = []; - start = None; - exports = [ - { e_name = "affinescript_init"; e_desc = ExportFunc 0 }; - { e_name = "affinescript_update"; e_desc = ExportFunc 1 }; - { e_name = "affinescript_get_screen_w"; e_desc = ExportFunc 2 }; - { e_name = "affinescript_get_screen_h"; e_desc = ExportFunc 3 }; - { e_name = "affinescript_get_bgm_playing"; e_desc = ExportFunc 4 }; - { e_name = "affinescript_get_selected"; e_desc = ExportFunc 5 }; - { e_name = "affinescript_set_screen"; e_desc = ExportFunc 6 }; - { e_name = "memory"; e_desc = ExportMemory 0 }; - ]; - custom_sections = [ - ("affinescript.ownership", build_ownership_section ()); - ("affinescript.tea_layout", build_tea_layout_section ()); - ]; -} diff --git a/affinescript/lib/tea_cs_bridge.ml b/affinescript/lib/tea_cs_bridge.ml deleted file mode 100644 index b90b0f1..0000000 --- a/affinescript/lib/tea_cs_bridge.ml +++ /dev/null @@ -1,311 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** CharacterSelect TEA Bridge Wasm Generator. - - Generates a valid WebAssembly 1.0 module that implements the - AffineScript TEA runtime ABI for the CharacterSelectScreen. - - The bridge module stores CharacterSelectModel in linear memory at a fixed - layout, and exports clean i32 functions that JS can call to drive - a PixiJS scene without needing a full AffineScript → Wasm compiler. - - {2 Memory layout} - - Model state is stored starting at byte offset 64 (matching the TitleScreen - layout convention) with the following field layout: - - {v - Offset Type Field Default - +0 i32 screen_w 1280 - +4 i32 screen_h 720 - +8 i32 bgm_playing 0 (unused; kept for API compatibility) - +12 i32 selected_tag 0 - v} - - {2 selected_tag encoding} - - {v - 0 = none (no class chosen yet) - 1 = Assault - 2 = Recon - 3 = Engineer (Combat Engineer) - 4 = Signals - 5 = Medic - 6 = Logistics - 7 = confirmed (navigate to JessicaCustomise) - v} - - {2 Msg tag encoding (input to affinescript_update)} - - {v - 0 = SelectAssault - 1 = SelectRecon - 2 = SelectEngineer - 3 = SelectSignals - 4 = SelectMedic - 5 = SelectLogistics - 6 = Confirm - v} - - The update function is branchless: [selected_tag := msg + 1]. - This maps SelectAssault=0 → 1, …, Confirm=6 → 7 (navigate). - - {2 Exported functions} - - Identical API surface to the TitleScreen bridge so the same - [AffineTEA.js] / [AffineTEA.res] bindings work without modification: - - {ul - {li [affinescript_init()]} - {li [affinescript_update(msg: i32)]} - {li [affinescript_get_screen_w() -> i32]} - {li [affinescript_get_screen_h() -> i32]} - {li [affinescript_get_bgm_playing() -> i32]} - {li [affinescript_get_selected() -> i32]} - {li [affinescript_set_screen(w: i32, h: i32)]} - {li [memory]} - } - - {2 Ownership annotations} - - The [affinescript.ownership] custom section marks [update]'s [msg] - parameter as Linear (kind byte 1) — consumed exactly once per TEA - update cycle — encoding the AffineScript linearity invariant for - typed-wasm Level 10 verification. - - A companion [affinescript.tea_layout] custom section encodes the - model field layout for tooling. -*) - -open Wasm - -(** Base address of the CharacterSelectModel in linear memory. *) -let model_base = 64 - -(** Field offsets relative to [model_base]. *) -let off_screen_w = 0 -let off_screen_h = 4 -let off_bgm_playing = 8 -let off_selected = 12 - -(** [load_field off] — Wasm instructions that load an i32 from - [(model_base + off)], leaving the value on the stack. *) -let load_field off : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Load (2, 0); -] - -(** [store_const off v] — Wasm instructions that store constant [v] - to [(model_base + off)]. *) -let store_const off v : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Const (Int32.of_int v); - I32Store (2, 0); -] - -(* ------------------------------------------------------------------------- - Type section - ------------------------------------------------------------------------- - Index Signature Used by - 0 () -> () fn_init - 1 (i32) -> () fn_update - 2 () -> i32 fn_get_screen_w/_h/_bgm/_selected - 3 (i32, i32) -> () fn_set_screen - ------------------------------------------------------------------------- *) - -let types : func_type list = [ - { ft_params = []; ft_results = [] }; - { ft_params = [I32]; ft_results = [] }; - { ft_params = []; ft_results = [I32] }; - { ft_params = [I32; I32]; ft_results = [] }; -] - -(* ------------------------------------------------------------------------- - Function bodies - ------------------------------------------------------------------------- *) - -(** fn 0: affinescript_init() — write default CharacterSelectModel to memory. *) -let fn_init : func = { - f_type = 0; - f_locals = []; - f_body = - store_const off_screen_w 1280 @ - store_const off_screen_h 720 @ - store_const off_bgm_playing 0 @ - store_const off_selected 0; -} - -(** fn 1: affinescript_update(msg: i32) — branchless update: - [selected_tag := msg + 1]. msg is Linear (consumed exactly once). - - msg 0 → 1 (Assault selected) - msg 1 → 2 (Recon selected) - msg 2 → 3 (Engineer selected) - msg 3 → 4 (Signals selected) - msg 4 → 5 (Medic selected) - msg 5 → 6 (Logistics selected) - msg 6 → 7 (confirmed — navigate to JessicaCustomise) *) -let fn_update : func = { - f_type = 1; - f_locals = []; - f_body = [ - (* address of selected_tag *) - I32Const (Int32.of_int (model_base + off_selected)); - (* compute msg + 1 *) - LocalGet 0; - I32Const 1l; - I32Add; - I32Store (2, 0); - ]; -} - -(** fn 2: affinescript_get_screen_w() -> i32 *) -let fn_get_screen_w : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_screen_w; -} - -(** fn 3: affinescript_get_screen_h() -> i32 *) -let fn_get_screen_h : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_screen_h; -} - -(** fn 4: affinescript_get_bgm_playing() -> i32 *) -let fn_get_bgm_playing : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_bgm_playing; -} - -(** fn 5: affinescript_get_selected() -> i32 *) -let fn_get_selected : func = { - f_type = 2; - f_locals = []; - f_body = load_field off_selected; -} - -(** fn 6: affinescript_set_screen(w: i32, h: i32) — store new dimensions. - Handles PixiJS resize events by updating the model. *) -let fn_set_screen : func = { - f_type = 3; - f_locals = []; - f_body = [ - I32Const (Int32.of_int (model_base + off_screen_w)); - LocalGet 0; - I32Store (2, 0); - I32Const (Int32.of_int (model_base + off_screen_h)); - LocalGet 1; - I32Store (2, 0); - ]; -} - -(* ------------------------------------------------------------------------- - Custom sections - ------------------------------------------------------------------------- *) - -(** Build the [affinescript.ownership] custom section payload. - - Identical encoding to the TitleScreen bridge — only [fn_update]'s msg - param is Linear; all other params and returns are Unrestricted. *) -let build_ownership_section () : bytes = - let buf = Buffer.create 64 in - let u32 n = - Buffer.add_char buf (Char.chr (n land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 16) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 24) land 0xff)) - in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - u32 7; (* 7 annotated functions *) - (* fn 0 init: () → (), no params, Unrestricted return *) - u32 0; u8 0; u8 0; - (* fn 1 update: (msg: Linear) → (), return Unrestricted *) - u32 1; u8 1; u8 1 (* Linear=1 *); u8 0; - (* fn 2-5 getters: () → i32, Unrestricted *) - u32 2; u8 0; u8 0; - u32 3; u8 0; u8 0; - u32 4; u8 0; u8 0; - u32 5; u8 0; u8 0; - (* fn 6 set_screen: (i32, i32) → (), both Unrestricted *) - u32 6; u8 2; u8 0; u8 0; u8 0; - Buffer.to_bytes buf - -(** Build the [affinescript.tea_layout] custom section. - - Compact binary descriptor for the CharacterSelectModel memory layout: - {v - u8 version = 1 - u8 base_addr = 64 - u8 field_count = 4 - per field: u8 name_len, name_bytes, u8 offset, u8 type_tag (0x49=i32) - v} *) -let build_tea_layout_section () : bytes = - let buf = Buffer.create 64 in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - let field name off = - u8 (String.length name); - Buffer.add_string buf name; - u8 off; - u8 0x49 (* i32 type tag *) - in - u8 1; (* version 1 *) - u8 model_base; (* base = 64 *) - u8 4; (* 4 fields *) - field "screen_w" off_screen_w; - field "screen_h" off_screen_h; - field "bgm_playing" off_bgm_playing; - field "selected" off_selected; - Buffer.to_bytes buf - -(* ------------------------------------------------------------------------- - Module assembly - ------------------------------------------------------------------------- *) - -(** Generate the complete TEA bridge Wasm module for CharacterSelectScreen. - - The resulting module is suitable for use with AffineTEA.js in IDApTIK. - It shares the same exported function names and memory layout as the - TitleScreen bridge, so the same ReScript/JS bindings work unchanged. - - selected_tag semantics after update(msg): - - 1-6: one of the six operative backgrounds is highlighted - - 7: player confirmed — navigate to JessicaCustomiseScreen - - Write with [Wasm_encode.write_module_to_file]. *) -let generate () : wasm_module = { - types; - funcs = [ - fn_init; - fn_update; - fn_get_screen_w; - fn_get_screen_h; - fn_get_bgm_playing; - fn_get_selected; - fn_set_screen; - ]; - tables = []; - mems = [{ mem_type = { lim_min = 1; lim_max = None } }]; - globals = []; - imports = []; - elems = []; - datas = []; - start = None; - exports = [ - { e_name = "affinescript_init"; e_desc = ExportFunc 0 }; - { e_name = "affinescript_update"; e_desc = ExportFunc 1 }; - { e_name = "affinescript_get_screen_w"; e_desc = ExportFunc 2 }; - { e_name = "affinescript_get_screen_h"; e_desc = ExportFunc 3 }; - { e_name = "affinescript_get_bgm_playing"; e_desc = ExportFunc 4 }; - { e_name = "affinescript_get_selected"; e_desc = ExportFunc 5 }; - { e_name = "affinescript_set_screen"; e_desc = ExportFunc 6 }; - { e_name = "memory"; e_desc = ExportMemory 0 }; - ]; - custom_sections = [ - ("affinescript.ownership", build_ownership_section ()); - ("affinescript.tea_layout", build_tea_layout_section ()); - ]; -} diff --git a/affinescript/lib/tea_router.ml b/affinescript/lib/tea_router.ml deleted file mode 100644 index 9c23490..0000000 --- a/affinescript/lib/tea_router.ml +++ /dev/null @@ -1,467 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** TEA Router Wasm Generator. - - Generates a WebAssembly 1.0 module that implements the AffineScript - Cadre Router ABI for IDApTIK. - - The router holds IDApTIK's screen back-stack as an affine resource. - Pushing or popping a screen consumes the old stack linearly and produces - a new one — navigation history as a linear type, making the typed-wasm - paper claim real. - - {2 Memory layout} - - RouterModel at byte offset 64: - {v - Offset Type Field Default - +0 i32 screen_w 1280 - +4 i32 screen_h 720 - +8 i32 stack_len 0 - +12 i32 popup_tag -1 (none) - +16 i32[8] stack_data [0 × 8] - v} - - Maximum stack depth is 8, giving headroom above the 5 IDApTIK screen types. - Total footprint: 4 × 4 + 8 × 4 = 48 bytes (model_base 64, so up to byte 112). - - {2 Screen tag encoding} - - {v - 0 = Title - 1 = CharacterSelect - 2 = WorldMap - 3 = Load - 4 = Game - v} - - {2 Popup tag encoding} - - {v - -1 = none (no active popup) - 0 = Settings - 1 = Inventory - 2 = Hacking - v} - - {2 RouterMsg encoding (ownership section annotation)} - - Each exported function corresponds to one RouterMsg variant. The - payload parameters are annotated Linear (kind byte = 1) in the - [affinescript.ownership] custom section, encoding that each message is - consumed exactly once per TEA update cycle: - - {v - Push(screen_tag) fn 1 — screen_tag: Linear - Pop fn 2 — no payload - PresentPopup(popup) fn 3 — popup_tag: Linear - DismissPopup fn 4 — no payload - Resize(w, h) fn 5 — w: Linear, h: Linear - v} - - Getters (fn 6–10) have no payload parameters. - - {2 Exported functions} - - {ul - {li [affinescript_router_init()]} - {li [affinescript_router_push(screen_tag: i32)]} - {li [affinescript_router_pop()]} - {li [affinescript_router_present_popup(popup_tag: i32)]} - {li [affinescript_router_dismiss_popup()]} - {li [affinescript_router_resize(w: i32, h: i32)]} - {li [affinescript_router_get_screen_w() -> i32]} - {li [affinescript_router_get_screen_h() -> i32]} - {li [affinescript_router_get_stack_len() -> i32]} - {li [affinescript_router_get_stack_top() -> i32]} - {li [affinescript_router_get_popup_tag() -> i32]} - {li [memory]} - } -*) - -open Wasm - -(** Base address of the RouterModel in linear memory. *) -let model_base = 64 - -(** Field offsets relative to [model_base]. *) -let off_screen_w = 0 -let off_screen_h = 4 -let off_stack_len = 8 -let off_popup_tag = 12 -let off_stack_data = 16 (** 8 × i32 = 32 bytes *) - -(** Maximum number of screens on the back-stack. *) -let max_stack_depth = 8 - -(** [load_field off] — Wasm instructions that load an i32 from - [(model_base + off)], leaving the value on the stack. *) -let load_field off : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Load (2, 0); -] - -(** [store_const off v] — Wasm instructions that store constant [v] - to [(model_base + off)]. *) -let store_const off v : instr list = [ - I32Const (Int32.of_int (model_base + off)); - I32Const (Int32.of_int v); - I32Store (2, 0); -] - -(** [store_local off local_idx] — Wasm instructions that store - [local[local_idx]] to [(model_base + off)]. *) -let store_local off local_idx : instr list = [ - I32Const (Int32.of_int (model_base + off)); - LocalGet local_idx; - I32Store (2, 0); -] - -(* ------------------------------------------------------------------------- - Type section - ------------------------------------------------------------------------- - Index Signature Used by - 0 () -> () fn_init, fn_pop, fn_dismiss_popup - 1 (i32) -> () fn_push, fn_present_popup - 2 (i32, i32) -> () fn_resize - 3 () -> i32 fn_get_screen_w/_h/stack_len/stack_top/popup_tag - ------------------------------------------------------------------------- *) - -let types : func_type list = [ - { ft_params = []; ft_results = [] }; - { ft_params = [I32]; ft_results = [] }; - { ft_params = [I32; I32]; ft_results = [] }; - { ft_params = []; ft_results = [I32] }; -] - -(* ------------------------------------------------------------------------- - Function bodies - ------------------------------------------------------------------------- *) - -(** fn 0: affinescript_router_init() — write default RouterModel to memory. - - Initialises all four scalar fields and zeros the eight stack slots. *) -let fn_init : func = { - f_type = 0; - f_locals = []; - f_body = - store_const off_screen_w 1280 @ - store_const off_screen_h 720 @ - store_const off_stack_len 0 @ - store_const off_popup_tag (-1) @ - List.concat (List.init max_stack_depth (fun i -> - store_const (off_stack_data + i * 4) 0)); -} - -(** fn 1: affinescript_router_push(screen_tag: i32) — push [screen_tag] onto - the back-stack if the stack is not full. - - Encoding: [stack_data[stack_len] := screen_tag; stack_len += 1]. - [screen_tag] is Linear — consumed exactly once per TEA update cycle. - - When the stack is full the screen_tag is explicitly dropped via [Drop] - rather than left in an unused-param else-branch. This satisfies the - per-path linearity verifier: both branches consume param 0 exactly once - (store in the then-branch, explicit drop in the else-branch), giving - min_uses = 1, max_uses = 1 → OK. *) -let fn_push : func = { - f_type = 1; - f_locals = []; - f_body = [ - (* condition: stack_len < max_stack_depth *) - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const (Int32.of_int max_stack_depth); - I32LtS; - If (BtEmpty, - (* then: append screen_tag and bump len *) - [ - (* address = stack_data_base + (stack_len * 4) *) - I32Const (Int32.of_int (model_base + off_stack_data)); - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const 4l; - I32Mul; - I32Add; - (* value: screen_tag (param 0) *) - LocalGet 0; - I32Store (2, 0); - (* stack_len += 1 *) - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const 1l; - I32Add; - I32Store (2, 0); - ], - (* else: stack full — explicitly consume screen_tag (ownership discharged) *) - [ LocalGet 0; Drop ] - ); - ]; -} - -(** fn 2: affinescript_router_pop() — pop the top screen if non-empty. - - Encoding: [if stack_len > 0 then stack_len -= 1]. The popped slot is - not zeroed (it becomes dead data above the new top). *) -let fn_pop : func = { - f_type = 0; - f_locals = []; - f_body = [ - (* condition: stack_len > 0 *) - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const 0l; - I32GtS; - If (BtEmpty, - (* then: stack_len -= 1 *) - [ - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const 1l; - I32Sub; - I32Store (2, 0); - ], - [] - ); - ]; -} - -(** fn 3: affinescript_router_present_popup(popup_tag: i32) — activate popup. - - Stores [popup_tag] directly. Any previous popup is silently replaced - (caller should [dismiss_popup] first for clean semantics). - [popup_tag] is Linear — consumed exactly once. *) -let fn_present_popup : func = { - f_type = 1; - f_locals = []; - f_body = store_local off_popup_tag 0; -} - -(** fn 4: affinescript_router_dismiss_popup() — clear the active popup. - - Stores -1 to [popup_tag] (= no active popup). *) -let fn_dismiss_popup : func = { - f_type = 0; - f_locals = []; - f_body = store_const off_popup_tag (-1); -} - -(** fn 5: affinescript_router_resize(w: i32, h: i32) — update screen dimensions. - - Both parameters are Linear — the Resize message is consumed exactly once. *) -let fn_resize : func = { - f_type = 2; - f_locals = []; - f_body = [ - I32Const (Int32.of_int (model_base + off_screen_w)); - LocalGet 0; - I32Store (2, 0); - I32Const (Int32.of_int (model_base + off_screen_h)); - LocalGet 1; - I32Store (2, 0); - ]; -} - -(** fn 6: affinescript_router_get_screen_w() -> i32 *) -let fn_get_screen_w : func = { - f_type = 3; - f_locals = []; - f_body = load_field off_screen_w; -} - -(** fn 7: affinescript_router_get_screen_h() -> i32 *) -let fn_get_screen_h : func = { - f_type = 3; - f_locals = []; - f_body = load_field off_screen_h; -} - -(** fn 8: affinescript_router_get_stack_len() -> i32 *) -let fn_get_stack_len : func = { - f_type = 3; - f_locals = []; - f_body = load_field off_stack_len; -} - -(** fn 9: affinescript_router_get_stack_top() -> i32 - - Returns the screen tag at the top of the stack (the current screen), - or [-1] if the stack is empty. - - Encoding: - {v - if stack_len == 0: -1 - else: stack_data[stack_len - 1] - v} *) -let fn_get_stack_top : func = { - f_type = 3; - f_locals = []; - f_body = [ - (* load stack_len *) - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - (* branch on stack_len == 0 *) - I32Eqz; - If (BtType I32, - (* then: empty stack → -1 *) - [ I32Const (-1l) ], - (* else: load stack_data[stack_len - 1] *) - [ - I32Const (Int32.of_int (model_base + off_stack_data)); - (* compute index: (stack_len - 1) * 4 *) - I32Const (Int32.of_int (model_base + off_stack_len)); - I32Load (2, 0); - I32Const 1l; - I32Sub; - I32Const 4l; - I32Mul; - (* address = stack_data_base + index *) - I32Add; - I32Load (2, 0); - ] - ); - ]; -} - -(** fn 10: affinescript_router_get_popup_tag() -> i32 - - Returns the active popup tag, or [-1] if no popup is active. *) -let fn_get_popup_tag : func = { - f_type = 3; - f_locals = []; - f_body = load_field off_popup_tag; -} - -(* ------------------------------------------------------------------------- - Custom sections - ------------------------------------------------------------------------- *) - -(** Build the [affinescript.ownership] custom section payload. - - Encoding (all little-endian): - {v - u32 entry_count - per entry: - u32 func_index - u8 param_count - u8* param_kind (0=Unrestricted 1=Linear 2=SharedBorrow 3=ExclBorrow) - u8 return_kind - v} - - RouterMsg payload parameters that are Linear: - - fn 1 (push): screen_tag (1 param, Linear) - - fn 3 (present_popup): popup_tag (1 param, Linear) - - fn 5 (resize): w, h (2 params, both Linear) - All other functions carry no payload (0 params, Unrestricted return). *) -let build_ownership_section () : bytes = - let buf = Buffer.create 128 in - let u32 n = - Buffer.add_char buf (Char.chr (n land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 8) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 16) land 0xff)); - Buffer.add_char buf (Char.chr ((n lsr 24) land 0xff)) - in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - u32 11; (* 11 annotated functions *) - (* fn 0 init: () → (), no params *) - u32 0; u8 0; u8 0; - (* fn 1 push: (screen_tag: Linear) → () *) - u32 1; u8 1; u8 1; u8 0; - (* fn 2 pop: () → () *) - u32 2; u8 0; u8 0; - (* fn 3 present_popup: (popup_tag: Linear) → () *) - u32 3; u8 1; u8 1; u8 0; - (* fn 4 dismiss_popup: () → () *) - u32 4; u8 0; u8 0; - (* fn 5 resize: (w: Linear, h: Linear) → () *) - u32 5; u8 2; u8 1; u8 1; u8 0; - (* fn 6–10 getters: () → i32, no param annotations *) - u32 6; u8 0; u8 0; - u32 7; u8 0; u8 0; - u32 8; u8 0; u8 0; - u32 9; u8 0; u8 0; - u32 10; u8 0; u8 0; - Buffer.to_bytes buf - -(** Build the [affinescript.tea_layout] custom section. - - Compact binary descriptor for the RouterModel memory layout: - {v - u8 version = 1 - u8 base_addr = 64 - u8 field_count = 5 - per field: u8 name_len, name_bytes, u8 offset, u8 type_tag - 0x49 = i32 scalar - 0x41 = array of i32 - v} *) -let build_tea_layout_section () : bytes = - let buf = Buffer.create 64 in - let u8 n = Buffer.add_char buf (Char.chr (n land 0xff)) in - let field name off type_tag = - u8 (String.length name); - Buffer.add_string buf name; - u8 off; - u8 type_tag - in - u8 1; (* version 1 *) - u8 model_base; (* base = 64 *) - u8 5; (* 5 fields *) - field "screen_w" off_screen_w 0x49; (* i32 *) - field "screen_h" off_screen_h 0x49; - field "stack_len" off_stack_len 0x49; - field "popup_tag" off_popup_tag 0x49; - field "stack_data" off_stack_data 0x41; (* array of i32, 8 slots *) - Buffer.to_bytes buf - -(* ------------------------------------------------------------------------- - Module assembly - ------------------------------------------------------------------------- *) - -(** Generate the complete TEA Router Wasm module for IDApTIK's Cadre Router. - - The resulting module is suitable for use with AffineTEARouter.js. - Write it with [Wasm_encode.write_module_to_file]. *) -let generate () : wasm_module = { - types; - funcs = [ - fn_init; - fn_push; - fn_pop; - fn_present_popup; - fn_dismiss_popup; - fn_resize; - fn_get_screen_w; - fn_get_screen_h; - fn_get_stack_len; - fn_get_stack_top; - fn_get_popup_tag; - ]; - tables = []; - mems = [{ mem_type = { lim_min = 1; lim_max = None } }]; - globals = []; - imports = []; - elems = []; - datas = []; - start = None; - exports = [ - { e_name = "affinescript_router_init"; e_desc = ExportFunc 0 }; - { e_name = "affinescript_router_push"; e_desc = ExportFunc 1 }; - { e_name = "affinescript_router_pop"; e_desc = ExportFunc 2 }; - { e_name = "affinescript_router_present_popup"; e_desc = ExportFunc 3 }; - { e_name = "affinescript_router_dismiss_popup"; e_desc = ExportFunc 4 }; - { e_name = "affinescript_router_resize"; e_desc = ExportFunc 5 }; - { e_name = "affinescript_router_get_screen_w"; e_desc = ExportFunc 6 }; - { e_name = "affinescript_router_get_screen_h"; e_desc = ExportFunc 7 }; - { e_name = "affinescript_router_get_stack_len"; e_desc = ExportFunc 8 }; - { e_name = "affinescript_router_get_stack_top"; e_desc = ExportFunc 9 }; - { e_name = "affinescript_router_get_popup_tag"; e_desc = ExportFunc 10 }; - { e_name = "memory"; e_desc = ExportMemory 0 }; - ]; - custom_sections = [ - ("affinescript.ownership", build_ownership_section ()); - ("affinescript.tea_layout", build_tea_layout_section ()); - ]; -} diff --git a/affinescript/lib/token.ml b/affinescript/lib/token.ml deleted file mode 100644 index ade2600..0000000 --- a/affinescript/lib/token.ml +++ /dev/null @@ -1,231 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Token types for the AffineScript lexer *) - -type t = - (* Literals *) - | INT of int - | FLOAT of float - | CHAR of char - | STRING of string - | TRUE - | FALSE - - (* Identifiers *) - | LOWER_IDENT of string (** lowercase identifier *) - | UPPER_IDENT of string (** Uppercase identifier *) - | ROW_VAR of string (** row variable: ..name *) - - (* Keywords *) - | FN - | LET - | CONST - | MUT - | OWN - | REF - | TYPE - | STRUCT - | ENUM - | TRAIT - | IMPL - | EFFECT - | HANDLE - | RESUME - | MATCH - | IF - | ELSE - | WHILE - | FOR - | RETURN - | BREAK - | CONTINUE - | IN - | WHERE - | TOTAL - | MODULE - | USE - | PUB - | AS - | UNSAFE - | ASSUME - | SELF_KW (** self receiver keyword *) - | TRANSMUTE - | FORGET - | TRY - | CATCH - | FINALLY - - (* Built-in type names *) - | NAT - | INT_T - | BOOL - | FLOAT_T - | STRING_T - | CHAR_T - | TYPE_K - | ROW - | NEVER - - (* Punctuation *) - | LPAREN - | RPAREN - | LBRACE - | RBRACE - | LBRACKET - | RBRACKET - | COMMA - | SEMICOLON - | COLON - | COLONCOLON - | DOT - | DOTDOT - | ARROW (** -> *) - | FAT_ARROW (** => *) - | PIPE - | AT - | UNDERSCORE - | BACKSLASH (** \ for row restriction *) - | QUESTION (** ? for error propagation *) - - (* Quantity annotations *) - | ZERO (** 0 *) - | ONE (** 1 *) - | OMEGA (** ω or omega *) - - (* Operators *) - | PLUS - | PLUSPLUS - | MINUS - | STAR - | SLASH - | PERCENT - | EQ - | EQEQ - | NE - | LT - | LE - | GT - | GE - | AMPAMP - | PIPEPIPE - | BANG - | AMP - | CARET - | TILDE - | LTLT - | GTGT - | PLUSEQ - | MINUSEQ - | STAREQ - | SLASHEQ - - (* Special *) - | EOF -[@@deriving show, eq] - -(** Get string representation for error messages *) -let to_string = function - | INT n -> Printf.sprintf "integer %d" n - | FLOAT f -> Printf.sprintf "float %f" f - | CHAR c -> Printf.sprintf "char '%c'" c - | STRING s -> Printf.sprintf "string \"%s\"" s - | TRUE -> "true" - | FALSE -> "false" - | LOWER_IDENT s -> Printf.sprintf "identifier '%s'" s - | UPPER_IDENT s -> Printf.sprintf "type '%s'" s - | ROW_VAR s -> Printf.sprintf "row variable '..%s'" s - | FN -> "fn" - | LET -> "let" - | CONST -> "const" - | MUT -> "mut" - | OWN -> "own" - | REF -> "ref" - | TYPE -> "type" - | STRUCT -> "struct" - | ENUM -> "enum" - | TRAIT -> "trait" - | IMPL -> "impl" - | EFFECT -> "effect" - | HANDLE -> "handle" - | RESUME -> "resume" - | MATCH -> "match" - | IF -> "if" - | ELSE -> "else" - | WHILE -> "while" - | FOR -> "for" - | RETURN -> "return" - | BREAK -> "break" - | CONTINUE -> "continue" - | IN -> "in" - | WHERE -> "where" - | TOTAL -> "total" - | MODULE -> "module" - | USE -> "use" - | PUB -> "pub" - | AS -> "as" - | UNSAFE -> "unsafe" - | ASSUME -> "assume" - | SELF_KW -> "self" - | TRANSMUTE -> "transmute" - | FORGET -> "forget" - | TRY -> "try" - | CATCH -> "catch" - | FINALLY -> "finally" - | NAT -> "Nat" - | INT_T -> "Int" - | BOOL -> "Bool" - | FLOAT_T -> "Float" - | STRING_T -> "String" - | CHAR_T -> "Char" - | TYPE_K -> "Type" - | ROW -> "Row" - | NEVER -> "Never" - | LPAREN -> "(" - | RPAREN -> ")" - | LBRACE -> "{" - | RBRACE -> "}" - | LBRACKET -> "[" - | RBRACKET -> "]" - | COMMA -> "," - | SEMICOLON -> ";" - | COLON -> ":" - | COLONCOLON -> "::" - | DOT -> "." - | DOTDOT -> ".." - | ARROW -> "->" - | FAT_ARROW -> "=>" - | PIPE -> "|" - | AT -> "@" - | UNDERSCORE -> "_" - | BACKSLASH -> "\\" - | QUESTION -> "?" - | ZERO -> "0" - | ONE -> "1" - | OMEGA -> "ω" - | PLUS -> "+" - | PLUSPLUS -> "++" - | MINUS -> "-" - | STAR -> "*" - | SLASH -> "/" - | PERCENT -> "%" - | EQ -> "=" - | EQEQ -> "==" - | NE -> "!=" - | LT -> "<" - | LE -> "<=" - | GT -> ">" - | GE -> ">=" - | AMPAMP -> "&&" - | PIPEPIPE -> "||" - | BANG -> "!" - | AMP -> "&" - | CARET -> "^" - | TILDE -> "~" - | LTLT -> "<<" - | GTGT -> ">>" - | PLUSEQ -> "+=" - | MINUSEQ -> "-=" - | STAREQ -> "*=" - | SLASHEQ -> "/=" - | EOF -> "end of file" diff --git a/affinescript/lib/trait.ml b/affinescript/lib/trait.ml deleted file mode 100644 index 6a5512a..0000000 --- a/affinescript/lib/trait.ml +++ /dev/null @@ -1,553 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Trait resolution and method dispatch. - - This module implements: - - Trait registry (storing trait definitions) - - Impl registry (storing implementations) - - Trait resolution (checking impls match traits) - - Method resolution (finding correct impl for calls) - - Coherence checking (preventing overlapping impls) -*) - -open Ast -open Types - -(** Convert Ast.kind to Types.kind *) -let rec ast_kind_to_types_kind (k : Ast.kind) : Types.kind = - match k with - | Ast.KType -> Types.KType - | Ast.KRow -> Types.KRow - | Ast.KEffect -> Types.KEffect - | Ast.KArrow (k1, k2) -> Types.KArrow (ast_kind_to_types_kind k1, ast_kind_to_types_kind k2) - -(** Trait method signature *) -type trait_method = { - tm_name : string; - tm_type_params : type_param list; - tm_params : param list; - tm_ret_ty : ty option; - tm_has_default : bool; -} - -(** Trait definition *) -type trait_def = { - td_name : string; - td_type_params : type_param list; - td_super : trait_bound list; - td_methods : trait_method list; - td_assoc_types : (string * kind option) list; -} - -(** Implementation of a trait for a type *) -type trait_impl = { - ti_trait_name : string; - ti_trait_args : type_arg list; - ti_self_ty : ty; - ti_type_params : type_param list; - ti_methods : (string * fn_decl) list; - ti_assoc_types : (string * ty) list; - ti_where : constraint_ list; -} - -(** Trait registry - stores all trait definitions *) -type trait_registry = { - traits : (string, trait_def) Hashtbl.t; - impls : (string, trait_impl list) Hashtbl.t; (* Key: trait name *) -} - -(** Create empty trait registry *) -let create_registry () : trait_registry = { - traits = Hashtbl.create 64; - impls = Hashtbl.create 64; -} - -(** Register a trait definition. - - The [tm_ret_ty] field is populated from the AST return-type annotation - (if present) and later used by the type checker when verifying impl bodies. *) -let register_trait (registry : trait_registry) (trait_decl : trait_decl) : unit = - let methods = List.filter_map (fun item -> - match item with - | TraitFn fs -> - (* fs_ret_ty is the declared return-type annotation from the signature *) - let ret_ty = match fs.fs_ret_ty with - | None -> None - | Some te -> - (* Convert the AST type expression to an internal ty using a simple - structural walk. We do not have the full context here, so only - built-in primitive names are resolved; everything else becomes - TCon which the type checker resolves later during unification. *) - let rec lower_simple (te : type_expr) : ty = - match te with - | TyCon { name = "Int"; _ } -> TCon "Int" - | TyCon { name = "Float"; _ } -> TCon "Float" - | TyCon { name = "Bool"; _ } -> TCon "Bool" - | TyCon { name = "String"; _ } -> TCon "String" - | TyCon { name = "Char"; _ } -> TCon "Char" - | TyCon { name = "Unit"; _ } | TyTuple [] -> TCon "Unit" - | TyCon { name = "Never"; _ } -> TCon "Never" - | TyCon { name; _ } -> TCon name - | TyVar { name; _ } -> TCon name - | TyApp ({ name; _ }, args) -> - let arg_tys = List.filter_map (fun a -> - match a with TyArg te' -> Some (lower_simple te') - ) args in - TApp (TCon name, arg_tys) - | TyTuple tes -> TTuple (List.map lower_simple tes) - | TyArrow (a, _q, b, _eff) -> - TArrow (lower_simple a, QOmega, lower_simple b, EPure) - | TyOwn te' -> TOwn (lower_simple te') - | TyRef te' -> TRef (lower_simple te') - | TyMut te' -> TMut (lower_simple te') - | TyRecord (fields, _) -> - let row = List.fold_right (fun (rf : row_field) acc -> - RExtend (rf.rf_name.name, lower_simple rf.rf_ty, acc) - ) fields REmpty in - TRecord row - | TyHole -> - let id = !Types.next_tyvar in - Types.next_tyvar := id + 1; - TVar (ref (Unbound (id, 0))) - in - Some (lower_simple te) - in - Some { - tm_name = fs.fs_name.name; - tm_type_params = fs.fs_type_params; - tm_params = fs.fs_params; - tm_ret_ty = ret_ty; - tm_has_default = false; - } - | TraitFnDefault fd -> - let ret_ty = match fd.fd_ret_ty with - | None -> None - | Some te -> - let rec lower_simple (te : type_expr) : ty = - match te with - | TyCon { name = "Int"; _ } -> TCon "Int" - | TyCon { name = "Float"; _ } -> TCon "Float" - | TyCon { name = "Bool"; _ } -> TCon "Bool" - | TyCon { name = "String"; _ } -> TCon "String" - | TyCon { name = "Char"; _ } -> TCon "Char" - | TyCon { name = "Unit"; _ } | TyTuple [] -> TCon "Unit" - | TyCon { name = "Never"; _ } -> TCon "Never" - | TyCon { name; _ } -> TCon name - | TyVar { name; _ } -> TCon name - | TyApp ({ name; _ }, args) -> - let arg_tys = List.filter_map (fun a -> - match a with TyArg te' -> Some (lower_simple te') - ) args in - TApp (TCon name, arg_tys) - | TyTuple tes -> TTuple (List.map lower_simple tes) - | TyArrow (a, _q, b, _eff) -> - TArrow (lower_simple a, QOmega, lower_simple b, EPure) - | TyOwn te' -> TOwn (lower_simple te') - | TyRef te' -> TRef (lower_simple te') - | TyMut te' -> TMut (lower_simple te') - | TyRecord (fields, _) -> - let row = List.fold_right (fun (rf : row_field) acc -> - RExtend (rf.rf_name.name, lower_simple rf.rf_ty, acc) - ) fields REmpty in - TRecord row - | TyHole -> - let id = !Types.next_tyvar in - Types.next_tyvar := id + 1; - TVar (ref (Unbound (id, 0))) - in - Some (lower_simple te) - in - Some { - tm_name = fd.fd_name.name; - tm_type_params = fd.fd_type_params; - tm_params = fd.fd_params; - tm_ret_ty = ret_ty; - tm_has_default = true; - } - | TraitType _ -> None - ) trait_decl.trd_items in - - let assoc_types = List.filter_map (fun item -> - match item with - | TraitType { tt_name; tt_kind; _ } -> - let converted_kind = Option.map ast_kind_to_types_kind tt_kind in - Some (tt_name.name, converted_kind) - | _ -> None - ) trait_decl.trd_items in - - let trait_def = { - td_name = trait_decl.trd_name.name; - td_type_params = trait_decl.trd_type_params; - td_super = trait_decl.trd_super; - td_methods = methods; - td_assoc_types = assoc_types; - } in - - Hashtbl.replace registry.traits trait_decl.trd_name.name trait_def - -(** Register an implementation *) -let register_impl (registry : trait_registry) (impl_block : impl_block) (self_ty : ty) : unit = - match impl_block.ib_trait_ref with - | None -> () (* Inherent impl, not a trait impl *) - | Some trait_ref -> - let methods = List.filter_map (fun item -> - match item with - | ImplFn fd -> Some (fd.fd_name.name, fd) - | ImplType _ -> None - ) impl_block.ib_items in - - let assoc_types = List.filter_map (fun item -> - match item with - | ImplType (name, _ty_expr) -> - (* Convert ty_expr to ty - placeholder for now *) - (* TODO(#84): Need context to properly convert ty_expr to ty *) - let placeholder_var = ref (Unbound (0, 0)) in - Some (name.name, TVar placeholder_var) - | ImplFn _ -> None - ) impl_block.ib_items in - - let impl = { - ti_trait_name = trait_ref.tr_name.name; - ti_trait_args = trait_ref.tr_args; - ti_self_ty = self_ty; (* Use the provided self type *) - ti_type_params = impl_block.ib_type_params; - ti_methods = methods; - ti_assoc_types = assoc_types; - ti_where = impl_block.ib_where; - } in - - let existing = Hashtbl.find_opt registry.impls trait_ref.tr_name.name - |> Option.value ~default:[] in - Hashtbl.replace registry.impls trait_ref.tr_name.name (impl :: existing) - -(** Trait resolution error *) -type resolution_error = - | TraitNotFound of string - | MissingMethod of string * string (* trait_name, method_name *) - | MethodSignatureMismatch of string * string * string (* trait, method, reason *) - | MissingAssocType of string * string (* trait_name, type_name *) - | OverlappingImpl of string * ty (* trait_name, self_ty *) - | SupertraitNotSatisfied of string * string (* trait_name, supertrait_name *) - -let show_resolution_error = function - | TraitNotFound name -> Printf.sprintf "Trait '%s' not found" name - | MissingMethod (trait, method_name) -> - Printf.sprintf "Method '%s' required by trait '%s' is not implemented" method_name trait - | MethodSignatureMismatch (trait, method_name, reason) -> - Printf.sprintf "Method '%s' in trait '%s' has mismatched signature: %s" - method_name trait reason - | MissingAssocType (trait, type_name) -> - Printf.sprintf "Associated type '%s' required by trait '%s' is not provided" type_name trait - | OverlappingImpl (trait, _ty) -> - Printf.sprintf "Overlapping implementation for trait '%s'" trait - | SupertraitNotSatisfied (trait, supertrait) -> - Printf.sprintf "Supertrait '%s' is not satisfied for trait '%s'" supertrait trait - -type 'a result = ('a, resolution_error) Result.t - -let ( let* ) = Result.bind - -(** Check if an impl satisfies a trait *) -let check_impl_satisfies_trait (registry : trait_registry) (impl : trait_impl) : unit result = - (* Find trait definition *) - match Hashtbl.find_opt registry.traits impl.ti_trait_name with - | None -> Error (TraitNotFound impl.ti_trait_name) - | Some trait_def -> - (* Check all required methods are implemented *) - let* () = List.fold_left (fun acc method_def -> - let* () = acc in - if method_def.tm_has_default then - Ok () (* Method has default, not required *) - else - match List.assoc_opt method_def.tm_name impl.ti_methods with - | None -> Error (MissingMethod (trait_def.td_name, method_def.tm_name)) - | Some impl_method -> - (* TODO(#84): Check signature matches *) - (* For now, just check it exists *) - let impl_param_count = List.length impl_method.fd_params in - let trait_param_count = List.length method_def.tm_params in - if impl_param_count <> trait_param_count then - Error (MethodSignatureMismatch ( - trait_def.td_name, - method_def.tm_name, - Printf.sprintf "expected %d parameters, found %d" - trait_param_count impl_param_count - )) - else - Ok () - ) (Ok ()) trait_def.td_methods in - - (* Check all required associated types are provided *) - List.fold_left (fun acc (type_name, _kind) -> - let* () = acc in - match List.assoc_opt type_name impl.ti_assoc_types with - | None -> Error (MissingAssocType (trait_def.td_name, type_name)) - | Some _ -> Ok () - ) (Ok ()) trait_def.td_assoc_types - -(** Substitute type-param names with concrete types in a ty. - - [subst] maps type-parameter names to fresh unification variables. - We walk the type tree and replace [TCon name] with [Hashtbl.find subst name] - wherever a type parameter of that name exists. *) -let rec subst_ty (subst : (string, ty) Hashtbl.t) (ty : ty) : ty = - match Types.repr ty with - | TVar _ -> ty - | TCon name -> - begin match Hashtbl.find_opt subst name with - | Some replacement -> replacement - | None -> ty - end - | TApp (head, args) -> - TApp (subst_ty subst head, List.map (subst_ty subst) args) - | TArrow (a, q, b, eff) -> - TArrow (subst_ty subst a, q, subst_ty subst b, eff) - | TTuple tys -> - TTuple (List.map (subst_ty subst) tys) - | TRecord row -> - TRecord (subst_row subst row) - | TVariant row -> - TVariant (subst_row subst row) - | TForall (v, k, body) -> - TForall (v, k, subst_ty subst body) - | TExists (v, k, body) -> - TExists (v, k, subst_ty subst body) - | TRef t -> TRef (subst_ty subst t) - | TMut t -> TMut (subst_ty subst t) - | TOwn t -> TOwn (subst_ty subst t) - -and subst_row (subst : (string, ty) Hashtbl.t) (row : row) : row = - match Types.repr_row row with - | REmpty -> REmpty - | RExtend (l, ty, rest) -> - RExtend (l, subst_ty subst ty, subst_row subst rest) - | RVar _ -> row - -(** Create a fresh instantiation of an impl's self type. - - For each type parameter declared on the impl, we create a fresh - unification variable and substitute it for the parameter name in - the impl's self type. This allows unification-based matching - without permanently committing to any particular substitution. - - [fresh_var] should create a fresh [TVar (ref (Unbound (...)))] at - the caller's current unification level. *) -let fresh_impl_self_ty (impl : trait_impl) (fresh_var : unit -> ty) : ty = - let subst = Hashtbl.create 4 in - List.iter (fun (tp : type_param) -> - Hashtbl.replace subst tp.tp_name.name (fresh_var ()) - ) impl.ti_type_params; - if Hashtbl.length subst = 0 then - impl.ti_self_ty - else - subst_ty subst impl.ti_self_ty - -(** Find implementation of a trait for a given type using unification. - - For each candidate impl we: - 1. Instantiate its type parameters as fresh unification variables. - 2. Attempt [Unify.unify self_ty instantiated_self_ty]. - 3. If unification succeeds the substitution is captured in the mutable - type variables — we return that impl. - 4. If unification fails we move on to the next candidate. - - The [fresh_var] callback creates a new [TVar (Unbound _)] at the - appropriate level; callers typically pass a closure over [ctx.level]. *) -let find_impl_with_unify (registry : trait_registry) (trait_name : string) - (self_ty : ty) (fresh_var : unit -> ty) : trait_impl option = - match Hashtbl.find_opt registry.impls trait_name with - | None -> None - | Some impls -> - List.find_opt (fun impl -> - let candidate_self = fresh_impl_self_ty impl fresh_var in - match Unify.unify self_ty candidate_self with - | Ok () -> true - | Error _ -> false - ) impls - -(** Find implementation of a trait for a given type. - - Uses unification-based matching when fresh type variables are available - (via [~fresh_var]). Falls back to structural constructor-name matching - when no [fresh_var] callback is supplied (e.g. from legacy call sites). *) -let find_impl (registry : trait_registry) (trait_name : string) (self_ty : ty) : trait_impl option = - (* Use a simple level-0 fresh var for the fallback path *) - let fresh_var () = - let id = !Types.next_tyvar in - Types.next_tyvar := id + 1; - TVar (ref (Unbound (id, 0))) - in - find_impl_with_unify registry trait_name self_ty fresh_var - -(** Find all implementations for a given type across all traits. - - Uses the same unification-based matching as [find_impl]. Each candidate - self type is instantiated with fresh type variables so that impls with - generic parameters (e.g. [impl Display for Option[T]]) are handled - correctly by structural unification. *) -let find_impls_for_type (registry : trait_registry) (self_ty : ty) : trait_impl list = - let fresh_var () = - let id = !Types.next_tyvar in - Types.next_tyvar := id + 1; - TVar (ref (Unbound (id, 0))) - in - Hashtbl.fold (fun _trait_name impls acc -> - let matching = List.filter (fun impl -> - let candidate_self = fresh_impl_self_ty impl fresh_var in - match Unify.unify self_ty candidate_self with - | Ok () -> true - | Error _ -> false - ) impls in - matching @ acc - ) registry.impls [] - -(** Find method in a trait impl *) -let find_method (impl : trait_impl) (method_name : string) : fn_decl option = - List.assoc_opt method_name impl.ti_methods - -(** Find method in any trait impl for a type *) -let find_method_for_type (registry : trait_registry) (self_ty : ty) (method_name : string) - : (trait_impl * fn_decl) option = - let impls = find_impls_for_type registry self_ty in - let rec search_impls = function - | [] -> None - | impl :: rest -> - begin match find_method impl method_name with - | Some method_decl -> Some (impl, method_decl) - | None -> search_impls rest - end - in - search_impls impls - -(** Check for overlapping implementations *) -let check_coherence (registry : trait_registry) (trait_name : string) : unit result = - match Hashtbl.find_opt registry.impls trait_name with - | None -> Ok () - | Some impls -> - (* TODO(#84): Check for overlapping impls *) - (* For now, just ensure no duplicate self types *) - let rec check_pairs = function - | [] -> Ok () - | _impl :: rest -> - (* Check if any impl in rest has same self_ty *) - (* TODO(#84): Proper unification check *) - check_pairs rest - in - check_pairs impls - -(** Standard library traits - automatically registered *) -let register_stdlib_traits (registry : trait_registry) : unit = - (* Eq trait *) - let self_ty = Ast.TyCon { Ast.name = "Self"; span = Span.dummy } in - let eq_self_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "self"; span = Span.dummy }; - p_ty = self_ty; - } in - let eq_other_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "other"; span = Span.dummy }; - p_ty = self_ty; - } in - let eq_trait = { - td_name = "Eq"; - td_type_params = []; - td_super = []; - td_methods = [{ - tm_name = "eq"; - tm_type_params = []; - tm_params = [eq_self_param; eq_other_param]; - tm_ret_ty = Some ty_bool; - tm_has_default = false; - }]; - td_assoc_types = []; - } in - Hashtbl.replace registry.traits "Eq" eq_trait; - - (* Ord trait (requires Eq) *) - let ord_self_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "self"; span = Span.dummy }; - p_ty = self_ty; - } in - let ord_other_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "other"; span = Span.dummy }; - p_ty = self_ty; - } in - let ord_trait = { - td_name = "Ord"; - td_type_params = []; - td_super = [{ - tb_name = { name = "Eq"; span = Span.dummy }; - tb_args = []; - }]; - td_methods = [{ - tm_name = "cmp"; - tm_type_params = []; - tm_params = [ord_self_param; ord_other_param]; - tm_ret_ty = None; (* Returns Ordering enum *) - tm_has_default = false; - }]; - td_assoc_types = []; - } in - Hashtbl.replace registry.traits "Ord" ord_trait; - - (* Hash trait *) - let hash_self_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "self"; span = Span.dummy }; - p_ty = self_ty; - } in - let hash_trait = { - td_name = "Hash"; - td_type_params = []; - td_super = []; - td_methods = [{ - tm_name = "hash"; - tm_type_params = []; - tm_params = [hash_self_param]; - tm_ret_ty = Some ty_int; - tm_has_default = false; - }]; - td_assoc_types = []; - } in - Hashtbl.replace registry.traits "Hash" hash_trait; - - (* Display trait *) - let display_self_param = { - Ast.p_quantity = None; - p_ownership = Some Ast.Ref; - p_name = { Ast.name = "self"; span = Span.dummy }; - p_ty = self_ty; - } in - let display_trait = { - td_name = "Display"; - td_type_params = []; - td_super = []; - td_methods = [{ - tm_name = "to_string"; - tm_type_params = []; - tm_params = [display_self_param]; - tm_ret_ty = None; (* Returns String *) - tm_has_default = false; - }]; - td_assoc_types = []; - } in - Hashtbl.replace registry.traits "Display" display_trait - -(** Validate all registered implementations *) -let validate_all_impls (registry : trait_registry) : unit result = - Hashtbl.fold (fun _trait_name impls acc -> - let* () = acc in - List.fold_left (fun acc2 impl -> - let* () = acc2 in - check_impl_satisfies_trait registry impl - ) (Ok ()) impls - ) registry.impls (Ok ()) diff --git a/affinescript/lib/tw_interface.ml b/affinescript/lib/tw_interface.ml deleted file mode 100644 index 258bc76..0000000 --- a/affinescript/lib/tw_interface.ml +++ /dev/null @@ -1,276 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** typed-wasm multi-module boundary verifier — Stage 10. - - Implements two capabilities: - - 1. {b Interface extraction}: given any Wasm module that contains an - [affinescript.ownership] custom section, extract the - ownership-annotated signatures of all exported functions. This is - the machine-readable boundary contract that callers must honour. - Printed by [affinescript interface FILE]. - - 2. {b Cross-module call verification}: given a callee module (with - ownership annotations) and a caller module (which imports functions - from the callee), verify that the caller's function bodies invoke - each Linear-param import with consistent per-path call counts: - - [max_calls > 1] on any path → [LinearImportCalledMultiple] - (the Linear argument may be duplicated) - - [min_calls = 0, max_calls ≥ 1] → [LinearImportDroppedOnSomePath] - (the argument is dropped without transfer on the zero-call path) - - The intra-module verifier ([Tw_verify]) checks function bodies against - their own params. This module checks call sites against the callee's - exported ownership contract — closing the full-stack guarantee loop from - AffineScript source through to the multi-module Wasm boundary. - - @see typed-wasm LEVEL-STATUS.md — Level 7 and Level 10 sections. -*) - -open Codegen - -(* ============================================================================ - Interface types - ============================================================================ *) - -(** Ownership-annotated signature for one exported function. *) -type func_interface = { - fi_name : string; (** export name *) - fi_func_idx : int; (** global function index in the callee *) - fi_param_kinds : ownership_kind list; - fi_ret_kind : ownership_kind; -} - -(** A cross-module ownership violation found in a caller's function body. *) -type cross_error = - | LinearImportCalledMultiple of { - caller_func_idx : int; (** local function index in the caller module *) - import_func_idx : int; (** global import slot index in the caller *) - import_name : string; - count : int; (** max calls on any single execution path *) - } - (** Level 10: A Linear-param import is invoked [count] times on some - execution path. Each call consumes the Linear argument — calling - twice duplicates it, violating exclusive ownership. *) - | LinearImportDroppedOnSomePath of { - caller_func_idx : int; - import_func_idx : int; - import_name : string; - } - (** Level 10: A Linear-param import is invoked on some execution paths - but not others (min_calls = 0, max_calls ≥ 1). The Linear argument - is silently dropped on the zero-call paths. *) - -(* ============================================================================ - Ownership index extraction - ============================================================================ *) - -(** Build a [(func_idx → (param_kinds, ret_kind))] lookup table from the - [affinescript.ownership] custom section of [wasm_mod]. - Returns an empty table if the section is absent. *) -let ownership_index_of_module (wasm_mod : Wasm.wasm_module) - : (int, ownership_kind list * ownership_kind) Hashtbl.t = - let tbl = Hashtbl.create 16 in - (match List.assoc_opt "affinescript.ownership" wasm_mod.Wasm.custom_sections with - | None -> () - | Some payload -> - List.iter (fun (idx, param_kinds, ret_kind) -> - Hashtbl.replace tbl idx (param_kinds, ret_kind) - ) (Tw_verify.parse_ownership_section_payload payload)); - tbl - -(* ============================================================================ - Interface extraction - ============================================================================ *) - -(** Extract the ownership-annotated export interface of [wasm_mod]. - - Returns one [func_interface] per exported function (non-function exports - such as tables, memories, and globals are excluded). Functions without - an entry in the ownership section are treated as fully [Unrestricted]. *) -let extract_exports (wasm_mod : Wasm.wasm_module) : func_interface list = - let idx = ownership_index_of_module wasm_mod in - List.filter_map (fun export -> - match export.Wasm.e_desc with - | Wasm.ExportFunc func_idx -> - let (param_kinds, ret_kind) = - match Hashtbl.find_opt idx func_idx with - | Some ann -> ann - | None -> ([], Unrestricted) - in - Some { - fi_name = export.Wasm.e_name; - fi_func_idx = func_idx; - fi_param_kinds = param_kinds; - fi_ret_kind = ret_kind; - } - | _ -> None - ) wasm_mod.Wasm.exports - -(* ============================================================================ - Per-path call-count analysis - ============================================================================ *) - -(** Count how many times [Call import_idx] appears across all execution paths - in [instrs], returning [(min_calls, max_calls)]. - - Sequential instructions sum their ranges. For [If] nodes only one branch - executes: take [min] of minimums and [max] of maximums. Nested [Block] - and [Loop] bodies are descended into. - - Example: [If { Call 0 } { Call 0 }] → [(1, 1)] — called exactly once. - Example: [If { Call 0 } { [] }] → [(0, 1)] — dropped on else path. - Example: [Call 0; Call 0] → [(2, 2)] — always duplicated. *) -let rec count_calls_range (import_idx : int) (instrs : Wasm.instr list) : int * int = - List.fold_left (fun (a_min, a_max) instr -> - let (i_min, i_max) = calls_range_in_instr import_idx instr in - (a_min + i_min, a_max + i_max) - ) (0, 0) instrs - -and calls_range_in_instr (import_idx : int) (instr : Wasm.instr) : int * int = - match instr with - | Wasm.Call n -> - if n = import_idx then (1, 1) else (0, 0) - | Wasm.Block (_, body) | Wasm.Loop (_, body) -> - count_calls_range import_idx body - | Wasm.If (_, then_, else_) -> - let (t_min, t_max) = count_calls_range import_idx then_ in - let (e_min, e_max) = count_calls_range import_idx else_ in - (min t_min e_min, max t_max e_max) - | _ -> (0, 0) - -(* ============================================================================ - Cross-module verification - ============================================================================ *) - -(** [has_linear_param param_kinds] — true if any param in [param_kinds] is [Linear]. *) -let has_linear_param (kinds : ownership_kind list) : bool = - List.mem Linear kinds - -(** Verify that [caller_mod]'s local function bodies respect the ownership - annotations of the imports they call. - - [callee_iface] is the ownership-annotated export interface of the callee - module whose functions are imported by [caller_mod]. Only imports whose - export names appear in [callee_iface] with at least one [Linear] parameter - are checked — other imports are not constrained by this pass. - - For each such Linear-annotated import, every local function in [caller_mod] - is inspected. If a function calls the import at all (max_calls ≥ 1): - - [max_calls > 1]: [LinearImportCalledMultiple] - - [min_calls = 0]: [LinearImportDroppedOnSomePath] - - Functions that never call the import on any path are not flagged — there - is no obligation for every function to invoke every import. *) -let verify_cross_module - (callee_iface : func_interface list) - (caller_mod : Wasm.wasm_module) - : (unit, cross_error list) Result.t = - (* Index callee exports by their name for O(n) lookup *) - let iface_by_name = - List.fold_left (fun acc fi -> (fi.fi_name, fi) :: acc) [] callee_iface - in - let import_count = List.length caller_mod.Wasm.imports in - (* Identify imports in caller that correspond to Linear-param exports in callee *) - let linear_imports = - List.filter_map (fun (import_slot, import) -> - match List.assoc_opt import.Wasm.i_name iface_by_name with - | Some fi when has_linear_param fi.fi_param_kinds -> - Some (import_slot, import.Wasm.i_name) - | _ -> None - ) (List.mapi (fun i imp -> (i, imp)) caller_mod.Wasm.imports) - in - if linear_imports = [] then Ok () - else begin - let errors = - List.concat_map (fun (import_slot, import_name) -> - List.concat_map (fun (local_idx, func) -> - let caller_func_idx = local_idx + import_count in - let (min_calls, max_calls) = - count_calls_range import_slot func.Wasm.f_body - in - if max_calls = 0 then - [] (* never calls this import — not a violation *) - else - let drop_errors = - if min_calls = 0 then - [LinearImportDroppedOnSomePath { - caller_func_idx; - import_func_idx = import_slot; - import_name }] - else [] - in - let dup_errors = - if max_calls > 1 then - [LinearImportCalledMultiple { - caller_func_idx; - import_func_idx = import_slot; - import_name; - count = max_calls }] - else [] - in - drop_errors @ dup_errors - ) (List.mapi (fun i f -> (i, f)) caller_mod.Wasm.funcs) - ) linear_imports - in - if errors = [] then Ok () else Error errors - end - -(* ============================================================================ - Pretty-printing - ============================================================================ *) - -let pp_kind (fmt : Format.formatter) (k : ownership_kind) : unit = - Format.pp_print_string fmt (match k with - | Linear -> "own" - | SharedBorrow -> "ref" - | ExclBorrow -> "mut" - | Unrestricted -> "val") - -(** Format the ownership-annotated export interface for human-readable output. *) -let pp_interface (fmt : Format.formatter) (iface : func_interface list) : unit = - match iface with - | [] -> - Format.fprintf fmt "No exported functions with ownership annotations.@." - | _ -> - Format.fprintf fmt "Ownership-annotated export interface:@."; - List.iter (fun fi -> - let param_strs = - List.mapi (fun i k -> - Format.asprintf "p%d:%a" i pp_kind k - ) fi.fi_param_kinds - in - Format.fprintf fmt " [fn %d] %s(%s) -> %a@." - fi.fi_func_idx - fi.fi_name - (String.concat ", " param_strs) - pp_kind fi.fi_ret_kind - ) iface - -(** Format a single cross-module error. *) -let pp_cross_error (fmt : Format.formatter) (err : cross_error) : unit = - match err with - | LinearImportCalledMultiple { caller_func_idx; import_name; count; _ } -> - Format.fprintf fmt - "Level 10 boundary violation: caller fn %d calls import '%s' \ - %d time(s) on some path (Linear param; must be called at most once)" - caller_func_idx import_name count - | LinearImportDroppedOnSomePath { caller_func_idx; import_name; _ } -> - Format.fprintf fmt - "Level 10 boundary violation: caller fn %d calls import '%s' on \ - some paths but not others (Linear param dropped on zero-call path)" - caller_func_idx import_name - -(** Format a full cross-module verification report. *) -let pp_cross_report (fmt : Format.formatter) (errs : cross_error list) : unit = - match errs with - | [] -> - Format.fprintf fmt "typed-wasm cross-module boundary verification: OK@." - | _ -> - Format.fprintf fmt - "typed-wasm cross-module boundary verification: %d violation(s)@." - (List.length errs); - List.iter (fun e -> - Format.fprintf fmt " %a@." pp_cross_error e - ) errs diff --git a/affinescript/lib/tw_verify.ml b/affinescript/lib/tw_verify.ml deleted file mode 100644 index 2a8fc64..0000000 --- a/affinescript/lib/tw_verify.ml +++ /dev/null @@ -1,280 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 hyperpolymath *) - -(** typed-wasm ownership verifier — Stage 7 (per-path analysis added Stage 9). - - Statically verifies typed-wasm Level 7 (aliasing safety) and Level 10 - (linearity) constraints on AffineScript's Wasm IR. - - The verifier runs after codegen on the in-memory [Wasm.wasm_module] - together with the ownership annotations collected during codegen. - It is a second line of defence: the QTT quantity checker (Stage 1) already - enforces these rules at the AffineScript source level; this module - re-checks them on the emitted Wasm IR to catch any codegen bugs. - - Level 10 — Linearity: a parameter annotated [Linear] (TyOwn) must be - loaded exactly once on every execution path. Zero uses = dropped; - more than one use = possible duplication; non-zero use on some paths - but zero on others = partial drop (dropped on the zero-count path). - - Level 7 — Aliasing safety: a parameter annotated [ExclBorrow] (TyMut) - must be loaded at most once on any execution path. - - [SharedBorrow] (TyRef) and [Unrestricted] params are not checked. - - Branch semantics (per-path min/max): for [If] instructions we compute - [(min_then, max_then)] and [(min_else, max_else)] independently and - combine as [(min_then, max_then, min_else, max_else) → - (min min_then min_else, max max_then max_else)]. A Linear param used - exactly once in BOTH branches gives (1, 1) — OK. A param used in the - then-branch only gives (min=0, max=1) — [LinearDroppedOnSomePath]. - - @see typed-wasm LEVEL-STATUS.md — Level 7 and Level 10 sections. -*) - -open Codegen - -(* ============================================================================ - Error types - ============================================================================ *) - -(** An ownership violation found in a Wasm function body. *) -type ownership_error = - | LinearNotUsed of { func_idx : int; param_idx : int } - (** Level 10: Linear parameter was never loaded on any path — dropped without - consumption. The owned resource leaks unconditionally. *) - | LinearDroppedOnSomePath of { func_idx : int; param_idx : int } - (** Level 10: Linear parameter is consumed on some execution paths but - silently dropped on others (per-path min_uses = 0, max_uses >= 1). - The caller's ownership guarantee is satisfied only conditionally — - the drop path leaks the resource. *) - | LinearUsedMultiple of { func_idx : int; param_idx : int; count : int } - (** Level 10: Linear parameter was loaded [count] times on some path — - potential duplication. Only one consumer is permitted. *) - | ExclBorrowAliased of { func_idx : int; param_idx : int; count : int } - (** Level 7: ExclBorrow parameter was loaded [count] times — aliasing violation. - An exclusive borrow must not have multiple simultaneous references. *) - -(* ============================================================================ - Per-path use-range analysis - ============================================================================ *) - -(** Compute [(min_uses, max_uses)] — the minimum and maximum number of times - [local_idx] is loaded across all execution paths within [instrs]. - - Sequential instructions sum their ranges: if instr A uses the param - (1, 1) times and instr B uses it (0, 1) times, the sequence is (1, 2). - - For [If] nodes only one branch executes, so we take the component-wise - min/max of the two branch ranges: - - [min = min(then_min, else_min)]: may be zero if one branch doesn't use it - - [max = max(then_max, else_max)]: the worst-case duplication count - - Example: [If { LocalGet 0 } { LocalGet 0 }] → [(1, 1)] OK for Linear. - Example: [If { LocalGet 0 } { [] }] → [(0, 1)] LinearDroppedOnSomePath. - Example: [If { LocalGet 0; LocalGet 0 } { LocalGet 0 }] → [(1, 2)] - → LinearUsedMultiple (max = 2). - - Nested [Block] and [Loop] bodies are descended into (no new paths). *) -let rec count_uses_range (local_idx : int) (instrs : Wasm.instr list) : int * int = - List.fold_left (fun (a_min, a_max) instr -> - let (i_min, i_max) = uses_range_in_instr local_idx instr in - (a_min + i_min, a_max + i_max) - ) (0, 0) instrs - -and uses_range_in_instr (local_idx : int) (instr : Wasm.instr) : int * int = - match instr with - | Wasm.LocalGet n -> if n = local_idx then (1, 1) else (0, 0) - | Wasm.Block (_, body) | Wasm.Loop (_, body) -> - count_uses_range local_idx body - | Wasm.If (_, then_, else_) -> - let (t_min, t_max) = count_uses_range local_idx then_ in - let (e_min, e_max) = count_uses_range local_idx else_ in - (min t_min e_min, max t_max e_max) - | _ -> (0, 0) - -(* ============================================================================ - Per-function verification - ============================================================================ *) - -(** Verify ownership constraints for one function. - - [func] is the Wasm function body. - [param_kinds] are the ownership annotations for each parameter (in order). - [func_idx] is the global function index (for error reporting). - Returns all violations found (empty list = clean). - - Uses per-path min/max analysis: - - [LinearNotUsed]: max_uses = 0 (param dropped on every path) - - [LinearDroppedOnSomePath]: min_uses = 0, max_uses >= 1 (dropped conditionally) - - [LinearUsedMultiple]: max_uses > 1 (duplicated on some path) - Multiple violations can be reported for a single param (e.g. both - [LinearDroppedOnSomePath] and [LinearUsedMultiple] if min=0, max>1). *) -let verify_function - (func : Wasm.func) - (param_kinds : ownership_kind list) - (func_idx : int) - : ownership_error list = - List.concat_map (fun (param_idx, kind) -> - let (min_uses, max_uses) = count_uses_range param_idx func.Wasm.f_body in - match kind with - | Linear -> - (* Exactly once on every path: zero everywhere = dropped; zero on some - path = partial drop; more than one on some path = may duplicate. *) - let drop_errors = - if max_uses = 0 then [LinearNotUsed { func_idx; param_idx }] - else if min_uses = 0 then [LinearDroppedOnSomePath { func_idx; param_idx }] - else [] - in - let dup_errors = - if max_uses > 1 then [LinearUsedMultiple { func_idx; param_idx; count = max_uses }] - else [] - in - drop_errors @ dup_errors - | ExclBorrow -> - (* At most once on any path: max > 1 creates simultaneous aliases. *) - if max_uses > 1 then - [ExclBorrowAliased { func_idx; param_idx; count = max_uses }] - else - [] - | Unrestricted | SharedBorrow -> - (* No constraints on these ownership kinds. *) - [] - ) (List.mapi (fun i k -> (i, k)) param_kinds) - -(* ============================================================================ - Module-level verification - ============================================================================ *) - -(** Verify ownership constraints across an entire Wasm module. - - [wasm_mod] is the compiled module. - [annots] is the list of [(func_idx, param_kinds, ret_kind)] annotations - collected by codegen and stored in the [affinescript.ownership] custom - section (but here provided in structured form directly from [Codegen]). - - Imported functions have no body and are skipped. - Returns all violations found. *) -let verify_module - (wasm_mod : Wasm.wasm_module) - (annots : (int * ownership_kind list * ownership_kind) list) - : ownership_error list = - let import_count = List.length wasm_mod.Wasm.imports in - List.concat_map (fun (func_idx, param_kinds, _ret_kind) -> - let local_idx = func_idx - import_count in - if local_idx < 0 || local_idx >= List.length wasm_mod.Wasm.funcs then - [] (* Imported function: no IR to inspect *) - else - let func = List.nth wasm_mod.Wasm.funcs local_idx in - verify_function func param_kinds func_idx - ) annots - -(* ============================================================================ - Pipeline integration — parse ownership section from the module - ============================================================================ *) - -(** Parse the [affinescript.ownership] custom section payload into structured - [(func_idx, param_kinds, ret_kind)] annotations. - - Binary encoding (from [Codegen.build_ownership_section]): - u32le count - for each entry: - u32le func_idx - u8 n_params - u8[n] param_kinds (0=Unrestricted, 1=Linear, 2=SharedBorrow, 3=ExclBorrow) - u8 ret_kind *) -let parse_ownership_section_payload - (payload : bytes) - : (int * ownership_kind list * ownership_kind) list = - let kind_of_byte = function - | 1 -> Linear - | 2 -> SharedBorrow - | 3 -> ExclBorrow - | _ -> Unrestricted - in - let pos = ref 0 in - let len = Bytes.length payload in - let read_u32_le () = - if !pos + 4 > len then 0 - else begin - let b0 = Char.code (Bytes.get payload !pos) in - let b1 = Char.code (Bytes.get payload (!pos + 1)) in - let b2 = Char.code (Bytes.get payload (!pos + 2)) in - let b3 = Char.code (Bytes.get payload (!pos + 3)) in - pos := !pos + 4; - b0 lor (b1 lsl 8) lor (b2 lsl 16) lor (b3 lsl 24) - end - in - let read_u8 () = - if !pos >= len then 0 - else begin - let b = Char.code (Bytes.get payload !pos) in - pos := !pos + 1; - b - end - in - let count = read_u32_le () in - List.init count (fun _ -> - let func_idx = read_u32_le () in - let n_params = read_u8 () in - let param_kinds = List.init n_params (fun _ -> kind_of_byte (read_u8 ())) in - let ret_kind = kind_of_byte (read_u8 ()) in - (func_idx, param_kinds, ret_kind) - ) - -(** Verify a Wasm module using the embedded [affinescript.ownership] custom - section. This is the primary entry point for the pipeline and the CLI. - - Returns [Ok ()] if no violations are found, [Error errs] otherwise. *) -let verify_from_module - (wasm_mod : Wasm.wasm_module) - : (unit, ownership_error list) Result.t = - match List.assoc_opt "affinescript.ownership" wasm_mod.Wasm.custom_sections with - | None -> - (* No ownership section: nothing to verify. This is not an error — - modules compiled without ownership qualifiers have no constraints. *) - Ok () - | Some payload -> - let annots = parse_ownership_section_payload payload in - let errors = verify_module wasm_mod annots in - if errors = [] then Ok () else Error errors - -(* ============================================================================ - Pretty-printing - ============================================================================ *) - -(** Format a single ownership error for human-readable output. *) -let pp_error (fmt : Format.formatter) (err : ownership_error) : unit = - match err with - | LinearNotUsed { func_idx; param_idx } -> - Format.fprintf fmt - "Level 10 violation: function %d, param %d — Linear (own) param dropped \ - on all paths (must be consumed exactly once)" - func_idx param_idx - | LinearDroppedOnSomePath { func_idx; param_idx } -> - Format.fprintf fmt - "Level 10 violation: function %d, param %d — Linear (own) param dropped \ - on some paths (per-path min uses = 0; must be consumed on every path)" - func_idx param_idx - | LinearUsedMultiple { func_idx; param_idx; count } -> - Format.fprintf fmt - "Level 10 violation: function %d, param %d — Linear (own) param loaded \ - %d times on some path (exactly 1 required; possible duplication)" - func_idx param_idx count - | ExclBorrowAliased { func_idx; param_idx; count } -> - Format.fprintf fmt - "Level 7 violation: function %d, param %d — ExclBorrow (mut) param \ - aliased (%d simultaneous references; at most 1 permitted)" - func_idx param_idx count - -(** Format a full verification report. Prints "OK" if no errors. *) -let pp_report (fmt : Format.formatter) (errs : ownership_error list) : unit = - match errs with - | [] -> - Format.fprintf fmt "typed-wasm ownership verification: OK@." - | _ -> - Format.fprintf fmt "typed-wasm ownership verification: %d violation(s)@." - (List.length errs); - List.iter (fun e -> - Format.fprintf fmt " %a@." pp_error e - ) errs diff --git a/affinescript/lib/typecheck.ml b/affinescript/lib/typecheck.ml deleted file mode 100644 index 30846c6..0000000 --- a/affinescript/lib/typecheck.ml +++ /dev/null @@ -1,1399 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** Bidirectional type checker for AffineScript. - - Implements a Hindley-Milner-style type checker with bidirectional - mode switching (synth/check), let-polymorphism via levels, and - integration with the unification engine ({!Unify}). - - Design: - - [synth]: infer a type for an expression (mode ⇒) - - [check]: verify an expression against an expected type (mode ⇐) - - At application sites we synth the function and check the argument - - At let-bindings we generalize to produce polymorphic schemes - - Effect annotations are unified alongside types - - Limitations (Phase 1): - - Quantity checking is a separate pass (see {!Quantity}) - - Refinement types are checked structurally, not via SMT - - Trait resolution uses name matching (see {!Trait}) - - Effect inference is basic: we unify declared effects -*) - -open Types -open Ast - -let string_of_binary_op = function - | OpAdd -> "+" - | OpSub -> "-" - | OpMul -> "*" - | OpDiv -> "/" - | OpMod -> "%" - | OpEq -> "==" - | OpNe -> "!=" - | OpLt -> "<" - | OpLe -> "<=" - | OpGt -> ">" - | OpGe -> ">=" - | OpAnd -> "&&" - | OpOr -> "||" - | OpBitAnd -> "&" - | OpBitOr -> "|" - | OpBitXor -> "^" - | OpShl -> "<<" - | OpShr -> ">>" - | OpConcat -> "++" - -let rec expr_summary (expr : expr) : string = - match expr with - | ExprVar id -> id.name - | ExprLit (LitBool (b, _)) -> string_of_bool b - | ExprLit (LitInt (i, _)) -> string_of_int i - | ExprLit (LitFloat (f, _)) -> string_of_float f - | ExprLit (LitChar (c, _)) -> Printf.sprintf "%c" c - | ExprLit (LitString (s, _)) -> Printf.sprintf "\"%s\"" s - | ExprLit (LitUnit _) -> "()" - | ExprBinary (l, op, r) -> - Printf.sprintf "(%s %s %s)" (expr_summary l) (string_of_binary_op op) (expr_summary r) - | ExprUnary (_, inner) -> expr_summary inner - | ExprApp (f, args) -> - let fdesc = expr_summary f in - let argsdesc = match args with - | [] -> "" - | hd :: _ -> expr_summary hd - in - Printf.sprintf "%s(%s...)" fdesc argsdesc - | ExprField (e, id) -> - Printf.sprintf "%s.%s" (expr_summary e) id.name - | ExprTuple _ -> "" - | ExprRecord _ -> "" - | ExprBlock _ -> "{...}" - | ExprIf _ -> "if(...)" - | ExprLet _ -> "let(...)" - | ExprMatch _ -> "match(...)" - | ExprRowRestrict _ -> "row_restrict" - | ExprTupleIndex _ -> "tuple_index" - | ExprIndex _ -> "index" - | ExprArray _ -> "array" - | ExprReturn _ -> "return" - | ExprTry _ -> "try" - | ExprHandle _ -> "handle" - | ExprResume _ -> "resume" - | ExprUnsafe _ -> "unsafe" - | ExprVariant (id, _) -> id.name - | ExprSpan (inner, _) -> expr_summary inner - | _ -> "" - -(** {1 Errors} *) - -(** Type checking error *) -type type_error = - | UnboundVariable of string - | TypeMismatch of { expected : ty; got : ty } - | OccursCheck of string * ty - | NotImplemented of string - | ArityMismatch of { name : string; expected : int; got : int } - | NotAFunction of ty - | FieldNotFound of { field : string; record_ty : ty } - | TupleIndexOutOfBounds of { index : int; length : int } - | DuplicateField of string - | UnificationError of Unify.unify_error - | PatternTypeMismatch of string - | BranchTypeMismatch of { then_ty : ty; else_ty : ty } - | QuantityError of Quantity.quantity_error * Span.t - (** QTT quantity violation detected after type checking. *) - -(** Format a type error for human consumption. *) -let show_type_error = function - | UnboundVariable v -> "Unbound variable: " ^ v - | TypeMismatch { expected; got } -> - Printf.sprintf "Type mismatch: expected %s, got %s" - (ty_to_string expected) (ty_to_string got) - | OccursCheck (v, ty) -> - Printf.sprintf "Occurs check: %s in %s" v (ty_to_string ty) - | NotImplemented msg -> "Not implemented: " ^ msg - | ArityMismatch { name; expected; got } -> - Printf.sprintf "Function %s expects %d arguments, got %d" name expected got - | NotAFunction ty -> - Printf.sprintf "Expected a function type, got %s" (ty_to_string ty) - | FieldNotFound { field; record_ty } -> - Printf.sprintf "Field '%s' not found in type %s" field (ty_to_string record_ty) - | TupleIndexOutOfBounds { index; length } -> - Printf.sprintf "Tuple index %d out of bounds (tuple has %d elements)" index length - | DuplicateField f -> - Printf.sprintf "Duplicate field: %s" f - | UnificationError ue -> - Printf.sprintf "Unification error: %s" (Unify.show_unify_error ue) - | PatternTypeMismatch msg -> - Printf.sprintf "Pattern type mismatch: %s" msg - | BranchTypeMismatch { then_ty; else_ty } -> - Printf.sprintf "Branch type mismatch: then-branch has type %s, else-branch has type %s" - (ty_to_string then_ty) (ty_to_string else_ty) - | QuantityError (qerr, _span) -> - Printf.sprintf "Quantity error: %s" (Quantity.format_quantity_error qerr) - -let format_type_error = show_type_error - -(** {1 Context} *) - -(** Type checking context. - - [level] tracks the current let-nesting depth for generalization. - Variables bound at a deeper level than the current one can be - generalized when we exit back to a shallower level. *) -type context = { - var_types : (Symbol.symbol_id, scheme) Hashtbl.t; - (** Symbol-ID-keyed type map — used by resolve.ml for imports *) - name_types : (string, scheme) Hashtbl.t; - (** Name-keyed type map — used by the type checker for lookups *) - type_env : (string, ty) Hashtbl.t; - (** Named type constructors → their definition types *) - constructor_env : (string, ty) Hashtbl.t; - (** Data constructors (enum variants) → their function types *) - symbols : Symbol.t; - mutable level : int; - mutable current_eff : eff; - (** The current effect context — unified with declared effects *) - trait_registry : Trait.trait_registry; - (** Trait registry — stores trait definitions and impls for dispatch *) -} - -type 'a result = ('a, type_error) Result.t - -let ( let* ) = Result.bind - -(** Lift a unification result into a type-checking result. *) -let unify_or_err (t1 : ty) (t2 : ty) : unit result = - match Unify.unify t1 t2 with - | Ok () -> Ok () - | Error ue -> Error (UnificationError ue) - -let unify_eff_or_err (e1 : eff) (e2 : eff) : unit result = - match Unify.unify_eff e1 e2 with - | Ok () -> Ok () - | Error ue -> Error (UnificationError ue) - -(** {1 Context management} *) - -let create_context (symbols : Symbol.t) : context = - { - var_types = Hashtbl.create 128; - name_types = Hashtbl.create 128; - type_env = Hashtbl.create 64; - constructor_env = Hashtbl.create 64; - symbols; - level = 0; - current_eff = fresh_effvar 0; - trait_registry = Trait.create_registry (); - } - -(** Enter a deeper let-level. *) -let enter_level (ctx : context) : unit = - ctx.level <- ctx.level + 1 - -(** Exit a let-level. *) -let exit_level (ctx : context) : unit = - ctx.level <- ctx.level - 1 - -(** Bind a variable to a monomorphic type (no generalization). *) -let bind_var (ctx : context) (name : string) (ty : ty) : unit = - let sc = { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ty } in - Hashtbl.replace ctx.name_types name sc - -(** Bind a variable to a polymorphic scheme. *) -let bind_scheme (ctx : context) (name : string) (sc : scheme) : unit = - Hashtbl.replace ctx.name_types name sc - -(** {1 Instantiation and generalization} *) - -(** Instantiate a polymorphic scheme by replacing bound variables - with fresh unification variables at the current level. *) -let instantiate (level : int) (sc : scheme) : ty = - let subst = Hashtbl.create 8 in - (* Create fresh type variables for each quantified tyvar *) - List.iter (fun (tv, _kind) -> - let fresh = fresh_tyvar level in - Hashtbl.replace subst tv fresh - ) sc.sc_tyvars; - (* Apply substitution to the body *) - let rec apply_subst ty = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v, _) -> - begin match Hashtbl.find_opt subst v with - | Some fresh -> fresh - | None -> ty - end - | Link t -> apply_subst t - end - | TCon _ -> ty - | TApp (t, args) -> - TApp (apply_subst t, List.map apply_subst args) - | TArrow (a, q, b, e) -> - TArrow (apply_subst a, q, apply_subst b, e) - | TTuple tys -> - TTuple (List.map apply_subst tys) - | TRecord row -> - TRecord (apply_subst_row row) - | TVariant row -> - TVariant (apply_subst_row row) - | TForall (v, k, body) -> - TForall (v, k, apply_subst body) - | TExists (v, k, body) -> - TExists (v, k, apply_subst body) - | TRef t -> TRef (apply_subst t) - | TMut t -> TMut (apply_subst t) - | TOwn t -> TOwn (apply_subst t) - and apply_subst_row row = - match repr_row row with - | REmpty -> REmpty - | RExtend (l, ty, rest) -> - RExtend (l, apply_subst ty, apply_subst_row rest) - | RVar _ -> row - in - apply_subst sc.sc_body - -(** Generalize a type by quantifying over all type variables - that were introduced at a level deeper than the context's - current level. *) -let generalize (ctx : context) (ty : ty) : scheme = - let tyvars = ref [] in - let rec collect ty = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v, lev) when lev > ctx.level -> - if not (List.exists (fun (v', _) -> v' = v) !tyvars) then - tyvars := (v, Types.KType) :: !tyvars - | Unbound _ -> () - | Link t -> collect t - end - | TCon _ -> () - | TApp (t, args) -> - collect t; List.iter collect args - | TArrow (a, _, b, _) -> - collect a; collect b - | TTuple tys -> - List.iter collect tys - | TRecord row | TVariant row -> - collect_row row - | TForall (_, _, body) | TExists (_, _, body) -> - collect body - | TRef t | TMut t | TOwn t -> - collect t - and collect_row row = - match repr_row row with - | REmpty -> () - | RExtend (_, ty, rest) -> - collect ty; collect_row rest - | RVar _ -> () - in - collect ty; - { sc_tyvars = !tyvars; sc_effvars = []; sc_rowvars = []; - sc_body = ty } - -(** Look up a variable, returning a fresh instantiation of its scheme. *) -let lookup_var (ctx : context) (name : string) : ty result = - match Hashtbl.find_opt ctx.name_types name with - | Some sc -> Ok (instantiate ctx.level sc) - | None -> Error (UnboundVariable name) - -(** {1 Kind checking} *) - -let rec infer_kind (ctx : context) (ty : ty) : kind result = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (_, _) -> Ok KType - | Link t -> infer_kind ctx t - end - | TCon name -> - begin match name with - | "Array" | "Option" | "List" | "Vec" | "Cmd" -> Ok (KArrow (KType, KType)) - | "Result" -> Ok (KArrow (KType, KArrow (KType, KType))) - | _ -> Ok KType - end - | TApp (head, args) -> - let* k = infer_kind ctx head in - check_kind_app ctx k args - | TArrow (_, _, _, _) | TTuple _ | TRecord _ | TVariant _ -> - Ok KType - | TForall (_, _, body) | TExists (_, _, body) -> - let* _ = infer_kind ctx body in - Ok KType - | TRef t | TMut t | TOwn t -> - infer_kind ctx t - -and check_kind (ctx : context) (ty : ty) (expected : kind) : unit result = - let* got = infer_kind ctx ty in - if got = expected then Ok () - else Error (NotImplemented (Printf.sprintf "Kind mismatch: expected %s, got %s" (show_kind expected) (show_kind got))) - -and check_kind_app (ctx : context) (k : kind) (args : ty list) : kind result = - match args with - | [] -> Ok k - | arg :: rest -> - begin match k with - | KArrow (param_k, ret_k) -> - let* () = check_kind ctx arg param_k in - check_kind_app ctx ret_k rest - | _ -> Error (NotImplemented "Too many arguments for kind") - end - -(** {1 AST type_expr → internal ty conversion} *) - -let lower_quantity (q : Ast.quantity) : Types.quantity = - match q with - | QZero -> QZero - | QOne -> QOne - | QOmega -> QOmega - -(** Convert an AST [type_expr] to an internal [ty]. - - Type variables are looked up in [type_env]; unknown names become - fresh unification variables so that inference can resolve them. *) -let rec lower_type_expr (ctx : context) (te : type_expr) : ty = - match te with - | TyVar { name; _ } -> - begin match Hashtbl.find_opt ctx.type_env name with - | Some ty -> ty - | None -> - (* Unresolved type variable: create a fresh unification var *) - let tv = fresh_tyvar ctx.level in - Hashtbl.replace ctx.type_env name tv; - tv - end - | TyCon { name; _ } -> - begin match name with - | "Int" -> ty_int - | "Float" -> ty_float - | "Bool" -> ty_bool - | "String" -> ty_string - | "Char" -> ty_char - | "Unit" | "()" -> ty_unit - | "Never" -> ty_never - | _ -> - begin match Hashtbl.find_opt ctx.type_env name with - | Some ty -> ty - | None -> TCon name - end - end - | TyApp ({ name; _ }, args) -> - let head = match Hashtbl.find_opt ctx.type_env name with - | Some ty -> ty - | None -> TCon name - in - let arg_tys = List.map (fun arg -> - match arg with - | TyArg te -> lower_type_expr ctx te - ) args in - TApp (head, arg_tys) - | TyArrow (a, q_opt, b, eff_opt) -> - let a' = lower_type_expr ctx a in - let b' = lower_type_expr ctx b in - let q = match q_opt with - | Some q -> lower_quantity q - | None -> QOmega (* Default to unrestricted *) - in - let eff = match eff_opt with - | Some e -> lower_effect_expr ctx e - | None -> EPure - in - TArrow (a', q, b', eff) - | TyTuple [] -> - ty_unit - | TyTuple tes -> - TTuple (List.map (lower_type_expr ctx) tes) - | TyRecord (fields, _row_var) -> - let row = List.fold_right (fun (rf : row_field) acc -> - RExtend (rf.rf_name.name, lower_type_expr ctx rf.rf_ty, acc) - ) fields REmpty in - TRecord row - | TyOwn te -> TOwn (lower_type_expr ctx te) - | TyRef te -> TRef (lower_type_expr ctx te) - | TyMut te -> TMut (lower_type_expr ctx te) - | TyHole -> - fresh_tyvar ctx.level - -and lower_effect_expr (ctx : context) (ee : effect_expr) : eff = - match ee with - | EffVar { name; _ } -> - begin match name with - | "Pure" -> EPure - | _ -> ESingleton name - end - | EffCon ({ name; _ }, _args) -> - ESingleton name - | EffUnion (e1, e2) -> - EUnion [lower_effect_expr ctx e1; lower_effect_expr ctx e2] - -(** {1 Binary/unary operator typing} *) - -(** Return the type of a binary operator given its operand types. *) -let type_of_binop (op : binary_op) : ty * ty * ty = - match op with - (* Arithmetic: Int -> Int -> Int *) - | OpAdd | OpSub | OpMul | OpDiv | OpMod -> - (ty_int, ty_int, ty_int) - (* Comparison: Int -> Int -> Bool *) - | OpLt | OpLe | OpGt | OpGe -> - (ty_int, ty_int, ty_bool) - (* Equality: polymorphic, but we approximate as 'a -> 'a -> Bool *) - | OpEq | OpNe -> - let tv = fresh_tyvar 0 in - (tv, tv, ty_bool) - (* Logical: Bool -> Bool -> Bool *) - | OpAnd | OpOr -> - (ty_bool, ty_bool, ty_bool) - (* Bitwise: Int -> Int -> Int *) - | OpBitAnd | OpBitOr | OpBitXor | OpShl | OpShr -> - (ty_int, ty_int, ty_int) - | OpConcat -> - (ty_string, ty_string, ty_string) - -let type_of_unop (op : unary_op) : ty * ty = - match op with - | OpNeg -> (ty_int, ty_int) - | OpNot -> (ty_bool, ty_bool) - | OpBitNot -> (ty_int, ty_int) - | OpRef -> - let tv = fresh_tyvar 0 in - (tv, TRef tv) - | OpDeref -> - let tv = fresh_tyvar 0 in - (TRef tv, tv) - -(** {1 Pattern typing} *) - -(** Type-check a pattern and return the list of bindings it introduces. - Each binding is [(name, ty)]. The pattern is checked against [expected_ty] - when in checking mode. *) -let rec check_pattern (ctx : context) (pat : pattern) (expected : ty) - : ((string * ty) list) result = - match pat with - | PatWildcard _ -> - Ok [] - | PatVar { name; _ } -> - Ok [(name, expected)] - | PatLit lit -> - let lit_ty = type_of_literal lit in - let* () = unify_or_err expected lit_ty in - Ok [] - | PatTuple pats -> - let n = List.length pats in - let elem_tys = List.init n (fun _ -> fresh_tyvar ctx.level) in - let* () = unify_or_err expected (TTuple elem_tys) in - let* bindings_list = check_patterns ctx pats elem_tys in - Ok (List.concat bindings_list) - | PatCon ({ name; _ }, sub_pats) -> - (* Look up the constructor type *) - begin match Hashtbl.find_opt ctx.constructor_env name with - | Some ctor_ty -> - let ctor_ty' = instantiate ctx.level - { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ctor_ty } in - (* Constructor type should be T1 -> T2 -> ... -> ResultType *) - let rec peel_arrows ty pats acc = - match pats with - | [] -> - let* () = unify_or_err expected ty in - Ok (List.rev acc) - | p :: rest -> - begin match repr ty with - | TArrow (param_ty, _, ret_ty, _) -> - let* binds = check_pattern ctx p param_ty in - peel_arrows ret_ty rest (binds :: acc) - | _ -> - Error (ArityMismatch { name; expected = List.length sub_pats; - got = List.length pats - List.length rest }) - end - in - let* bindings_list = peel_arrows ctor_ty' sub_pats [] in - Ok (List.concat bindings_list) - | None -> - (* Unknown constructor — if it has no arguments, treat as a variant tag *) - if sub_pats = [] then begin - (* Unify expected with a variant containing this tag *) - Ok [] - end else - Error (UnboundVariable name) - end - | PatRecord (fields, _has_rest) -> - let bindings = ref [] in - List.iter (fun (({ name; _ } : ident), pat_opt) -> - let field_ty = fresh_tyvar ctx.level in - begin match pat_opt with - | Some sub_pat -> - begin match check_pattern ctx sub_pat field_ty with - | Ok bs -> bindings := bs @ !bindings - | Error _ -> () - end - | None -> - bindings := (name, field_ty) :: !bindings - end - ) fields; - Ok !bindings - | PatOr (p1, _p2) -> - (* Both branches must produce the same bindings; we check the first *) - check_pattern ctx p1 expected - | PatAs ({ name; _ }, sub_pat) -> - let* binds = check_pattern ctx sub_pat expected in - Ok ((name, expected) :: binds) - -and check_patterns (ctx : context) (pats : pattern list) (tys : ty list) - : ((string * ty) list list) result = - match pats, tys with - | [], [] -> Ok [] - | p :: ps, t :: ts -> - let* binds = check_pattern ctx p t in - let* rest = check_patterns ctx ps ts in - Ok (binds :: rest) - | _ -> Error (PatternTypeMismatch "pattern/type list length mismatch") - -(** {1 Literal typing} *) - -and type_of_literal (lit : literal) : ty = - match lit with - | LitInt _ -> ty_int - | LitFloat _ -> ty_float - | LitBool _ -> ty_bool - | LitChar _ -> ty_char - | LitString _ -> ty_string - | LitUnit _ -> ty_unit - -(** {1 Expression synthesis (mode ⇒)} *) - -(** Synthesize a type for an expression. *) -let rec synth (ctx : context) (expr : expr) : ty result = - match expr with - (* Literals *) - | ExprLit lit -> - Ok (type_of_literal lit) - - (* Variables — instantiate their scheme *) - | ExprVar { name; _ } -> - lookup_var ctx name - - (* Let bindings: let pat = e1 in e2 *) - | ExprLet { el_pat; el_ty; el_value; el_body; el_mut = _; el_quantity = _ } -> - (* Synthesize or check the value *) - enter_level ctx; - let* val_ty = begin match el_ty with - | Some te -> - let ann_ty = lower_type_expr ctx te in - let* () = check ctx el_value ann_ty in - Ok ann_ty - | None -> - synth ctx el_value - end in - exit_level ctx; - (* Generalize the value type *) - let sc = generalize ctx val_ty in - (* Bind pattern variables *) - let* bindings = check_pattern ctx el_pat val_ty in - let old_bindings = List.map (fun (name, _) -> - (name, Hashtbl.find_opt ctx.name_types name) - ) bindings in - List.iter (fun (name, _ty) -> - bind_scheme ctx name sc - ) bindings; - (* Type-check the body if present, else return Unit *) - let result = begin match el_body with - | Some body -> synth ctx body - | None -> Ok ty_unit - end in - (* Restore old bindings *) - List.iter (fun (name, old) -> - match old with - | Some sc' -> Hashtbl.replace ctx.name_types name sc' - | None -> Hashtbl.remove ctx.name_types name - ) old_bindings; - result - - (* If-then-else *) - | ExprIf { ei_cond; ei_then; ei_else } -> - let* () = check ctx ei_cond ty_bool in - let* then_ty = synth ctx ei_then in - begin match ei_else with - | Some else_expr -> - let* else_ty = synth ctx else_expr in - let* () = unify_or_err then_ty else_ty in - Ok then_ty - | None -> - let () = - if ty_to_string then_ty <> ty_to_string ty_unit then - Format.eprintf "If without else returns %s; then=%s cond=%s\n%!" - (ty_to_string then_ty) (expr_summary ei_then) (expr_summary ei_cond) - else - () - in - (* No else branch: result is Unit *) - let* () = unify_or_err then_ty ty_unit in - Ok ty_unit - end - - (* Match expressions *) - | ExprMatch { em_scrutinee; em_arms } -> - let* scrut_ty = synth ctx em_scrutinee in - let result_ty = fresh_tyvar ctx.level in - let* () = List.fold_left (fun acc (arm : match_arm) -> - let* () = acc in - let* bindings = check_pattern ctx arm.ma_pat scrut_ty in - (* Save and bind pattern variables *) - let old = List.map (fun (n, _) -> - (n, Hashtbl.find_opt ctx.name_types n) - ) bindings in - List.iter (fun (n, t) -> bind_var ctx n t) bindings; - (* Check guard if present *) - let* () = match arm.ma_guard with - | Some guard -> check ctx guard ty_bool - | None -> Ok () - in - (* Body must unify with result type *) - let* arm_ty = synth ctx arm.ma_body in - let* () = unify_or_err result_ty arm_ty in - (* Restore *) - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - Ok () - ) (Ok ()) em_arms in - Ok result_ty - - (* Lambda *) - | ExprLambda { elam_params; elam_ret_ty; elam_body } -> - let param_tys = List.map (fun (p : param) -> - lower_type_expr ctx p.p_ty - ) elam_params in - (* Save old bindings, bind params *) - let old = List.map2 (fun (p : param) ty -> - let old = Hashtbl.find_opt ctx.name_types p.p_name.name in - bind_var ctx p.p_name.name ty; - (p.p_name.name, old) - ) elam_params param_tys in - (* Synthesize or check body *) - let* body_ty = begin match elam_ret_ty with - | Some te -> - let ret_ty = lower_type_expr ctx te in - let* () = check ctx elam_body ret_ty in - Ok ret_ty - | None -> - synth ctx elam_body - end in - (* Restore *) - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - (* Build the arrow type: curried for multi-param. - Each arrow carries the declared quantity of the corresponding parameter. - If the parameter has no explicit annotation, we default to QOmega - (unrestricted), matching the convention used by check_fn_decl. *) - let eff = fresh_effvar ctx.level in - let param_qty_pairs = List.map2 (fun (p : param) param_ty -> - let q = match p.p_quantity with - | Some q -> lower_quantity q - | None -> QOmega - in - (q, param_ty) - ) elam_params param_tys in - let ty = List.fold_right (fun (q, param_ty) acc -> - TArrow (param_ty, q, acc, eff) - ) param_qty_pairs body_ty in - Ok ty - - (* Function application *) - | ExprApp (fn_expr, args) -> - let* fn_ty = synth ctx fn_expr in - apply_args ctx fn_ty args - - (* Tuple *) - | ExprTuple exprs -> - let* tys = synth_list ctx exprs in - Ok (TTuple tys) - - (* Array *) - | ExprArray exprs -> - begin match exprs with - | [] -> - let elem_ty = fresh_tyvar ctx.level in - Ok (TApp (TCon "Array", [elem_ty])) - | first :: rest -> - let* first_ty = synth ctx first in - let* () = List.fold_left (fun acc e -> - let* () = acc in - check ctx e first_ty - ) (Ok ()) rest in - Ok (TApp (TCon "Array", [first_ty])) - end - - (* Record literal *) - | ExprRecord { er_fields; er_spread = _ } -> - let* field_tys = List.fold_left (fun acc (({ name; _ } : ident), expr_opt) -> - let* fields = acc in - let* ty = begin match expr_opt with - | Some e -> synth ctx e - | None -> lookup_var ctx name - end in - Ok ((name, ty) :: fields) - ) (Ok []) er_fields in - let row = List.fold_right (fun (name, ty) acc -> - RExtend (name, ty, acc) - ) field_tys REmpty in - Ok (TRecord row) - - (* Field access — first try record-field projection, then trait method lookup *) - | ExprField (obj, { name = field; _ }) -> - let* obj_ty = synth ctx obj in - let field_ty = fresh_tyvar ctx.level in - let rest_row = fresh_rowvar ctx.level in - let expected_record = TRecord (RExtend (field, field_ty, rest_row)) in - begin match Unify.unify (repr obj_ty) expected_record with - | Ok () -> Ok field_ty - | Error _ -> - (* Record projection failed — try trait method dispatch. - We search all registered impls for a method named [field] - whose self type unifies with [obj_ty]. *) - begin match Trait.find_method_for_type ctx.trait_registry obj_ty field with - | Some (_impl, method_decl) -> - (* Build the method's monomorphic type from the fn_decl. - Parameters: each p_ty is lowered to an internal ty. - Return type defaults to a fresh type variable when omitted. - Effects are left as a fresh effect variable (unannotated). *) - let param_tys = List.map (fun (p : Ast.param) -> - lower_type_expr ctx p.p_ty - ) method_decl.Ast.fd_params in - let ret_ty = match method_decl.Ast.fd_ret_ty with - | Some te -> lower_type_expr ctx te - | None -> fresh_tyvar ctx.level - in - let eff = fresh_effvar ctx.level in - (* Fold params into a curried arrow, right-to-left *) - let method_ty = List.fold_right (fun (param_and_ty) acc -> - let (p, pt) = (param_and_ty : Ast.param * ty) in - let q = match p.Ast.p_quantity with - | Some q -> lower_quantity q - | None -> QOmega - in - TArrow (pt, q, acc, eff) - ) (List.combine method_decl.Ast.fd_params param_tys) ret_ty in - Ok method_ty - | None -> - (* Neither record field nor trait method — report a field-not-found error *) - Error (FieldNotFound { field; record_ty = obj_ty }) - end - end - - (* Tuple indexing *) - | ExprTupleIndex (tup, idx) -> - let* tup_ty = synth ctx tup in - begin match repr tup_ty with - | TTuple tys -> - if idx >= 0 && idx < List.length tys then - Ok (List.nth tys idx) - else - Error (TupleIndexOutOfBounds { index = idx; length = List.length tys }) - | _ -> - (* Create a tuple type with enough slots *) - let n = idx + 1 in - let elem_tys = List.init n (fun _ -> fresh_tyvar ctx.level) in - let* () = unify_or_err tup_ty (TTuple elem_tys) in - Ok (List.nth elem_tys idx) - end - - (* Array indexing *) - | ExprIndex (arr, idx_expr) -> - let* arr_ty = synth ctx arr in - let* () = check ctx idx_expr ty_int in - let elem_ty = fresh_tyvar ctx.level in - let* () = unify_or_err arr_ty (TApp (TCon "Array", [elem_ty])) in - Ok elem_ty - - (* Binary operators *) - | ExprBinary (lhs, op, rhs) -> - let (lhs_ty, rhs_ty, result_ty) = type_of_binop op in - let* () = check ctx lhs lhs_ty in - let* () = check ctx rhs rhs_ty in - Ok result_ty - - (* Unary operators *) - | ExprUnary (op, operand) -> - let (operand_ty, result_ty) = type_of_unop op in - let* () = check ctx operand operand_ty in - Ok result_ty - - (* Block *) - | ExprBlock blk -> - synth_block ctx blk - - (* Return — return type is Never (it doesn't produce a value locally) *) - | ExprReturn expr_opt -> - begin match expr_opt with - | Some e -> - let* _ty = synth ctx e in - Ok ty_never - | None -> - Ok ty_never - end - - (* Variant constructor: Type::Variant *) - | ExprVariant ({ name = _type_name; _ }, { name = variant_name; _ }) -> - begin match Hashtbl.find_opt ctx.constructor_env variant_name with - | Some ctor_ty -> Ok ctor_ty - | None -> - (* Unknown variant — return a fresh variable *) - Ok (fresh_tyvar ctx.level) - end - - (* Row restriction *) - | ExprRowRestrict (obj, { name = field; _ }) -> - let* obj_ty = synth ctx obj in - let field_ty = fresh_tyvar ctx.level in - let rest_row = fresh_rowvar ctx.level in - let* () = unify_or_err obj_ty (TRecord (RExtend (field, field_ty, rest_row))) in - Ok (TRecord rest_row) - - (* Span wrapper — unwrap and recurse *) - | ExprSpan (inner, _span) -> - synth ctx inner - - (* Effect handling *) - | ExprHandle { eh_body; _ } -> - synth ctx eh_body - - (* Resume *) - | ExprResume expr_opt -> - begin match expr_opt with - | Some e -> synth ctx e - | None -> Ok ty_unit - end - - (* Try-catch-finally. - Body type is synthesised first and becomes the overall result type. - Each catch arm is type-checked against a fresh error-type variable - (the effect system will constrain this once effect inference is - complete) and its body type must unify with the result type. - The finally block (if present) is checked for unit — its value is - discarded at runtime; a non-unit finally type is a type error. *) - | ExprTry { et_body; et_catch; et_finally } -> - let* body_ty = synth_block ctx et_body in - let result_ty = fresh_tyvar ctx.level in - let* () = unify_or_err result_ty body_ty in - let* () = match et_catch with - | None -> Ok () - | Some arms -> - (* All catch arms match against a single opaque error type. *) - let err_ty = fresh_tyvar ctx.level in - List.fold_left (fun acc (arm : match_arm) -> - let* () = acc in - let* bindings = check_pattern ctx arm.ma_pat err_ty in - (* Save bindings that will be shadowed, then install new ones. *) - let old = List.map (fun (n, _) -> - (n, Hashtbl.find_opt ctx.name_types n) - ) bindings in - List.iter (fun (n, t) -> bind_var ctx n t) bindings; - let* arm_ty = synth ctx arm.ma_body in - let* () = unify_or_err result_ty arm_ty in - (* Restore previous bindings. *) - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - Ok () - ) (Ok ()) arms - in - let* () = match et_finally with - | None -> Ok () - | Some blk -> - (* Finally must be unit — its value is always discarded. *) - let* fin_ty = synth_block ctx blk in - unify_or_err fin_ty ty_unit - in - Ok result_ty - - (* Unsafe *) - | ExprUnsafe _ -> - Ok (fresh_tyvar ctx.level) - -(** Apply a function type to a list of arguments. *) -and apply_args (ctx : context) (fn_ty : ty) (args : expr list) : ty result = - match args with - | [] -> Ok fn_ty - | arg :: rest -> - let fn_ty' = repr fn_ty in - match fn_ty' with - | TArrow (param_ty, _q, ret_ty, _eff) -> - let* () = check ctx arg param_ty in - apply_args ctx ret_ty rest - | TVar _ -> - (* Unknown function type: create fresh arrow *) - let param_ty = fresh_tyvar ctx.level in - let ret_ty = fresh_tyvar ctx.level in - let eff = fresh_effvar ctx.level in - let q = lower_quantity QOmega in (* Default for unknown *) - let* () = unify_or_err fn_ty' (TArrow (param_ty, q, ret_ty, eff)) in - let* () = check ctx arg param_ty in - apply_args ctx ret_ty rest - | _ -> - Error (NotAFunction fn_ty') -and synth_list (ctx : context) (exprs : expr list) : (ty list) result = - List.fold_right (fun e acc -> - let* tys = acc in - let* ty = synth ctx e in - Ok (ty :: tys) - ) exprs (Ok []) - -(** {1 Block and statement typing} *) - -(** Returns true if an expression always diverges — i.e., it never produces - a value in normal control flow. Used to give blocks a Never type when - all paths exit via return. *) -and always_diverges (e : expr) : bool = - match e with - | ExprReturn _ -> true - | ExprBlock blk -> block_always_diverges blk - | ExprIf { ei_cond = _; ei_then; ei_else = Some else_e } -> - always_diverges ei_then && always_diverges else_e - | _ -> false - -(** Returns true if a block always diverges (all paths return). *) -and block_always_diverges (blk : block) : bool = - match blk.blk_expr with - | Some e -> always_diverges e - | None -> - begin match List.rev blk.blk_stmts with - | StmtExpr e :: _ -> always_diverges e - | _ -> false - end - -and synth_block (ctx : context) (blk : block) : ty result = - (* Type-check each statement for side effects *) - let* () = List.fold_left (fun acc stmt -> - let* () = acc in - check_stmt ctx stmt - ) (Ok ()) blk.blk_stmts in - (* The block's type is the type of the final expression, or Unit *) - match blk.blk_expr with - | Some e -> synth ctx e - | None -> - (* When all paths through the block diverge (every code path ends with - return/break/etc.), the block has type Never rather than Unit. This - allows functions declared as returning T to have bodies that exclusively - use `return expr;` rather than a final expression. *) - if block_always_diverges blk - then Ok ty_never - else Ok ty_unit - -and check_stmt (ctx : context) (stmt : stmt) : unit result = - match stmt with - | StmtLet { sl_pat; sl_ty; sl_value; sl_mut = _; sl_quantity = _ } -> - enter_level ctx; - let* val_ty = begin match sl_ty with - | Some te -> - let ann_ty = lower_type_expr ctx te in - let* () = check ctx sl_value ann_ty in - Ok ann_ty - | None -> - synth ctx sl_value - end in - exit_level ctx; - let sc = generalize ctx val_ty in - let* bindings = check_pattern ctx sl_pat val_ty in - List.iter (fun (name, _ty) -> - bind_scheme ctx name sc - ) bindings; - Ok () - | StmtExpr e -> - let* _ty = synth ctx e in - Ok () - | StmtAssign (lhs, _op, rhs) -> - let* lhs_ty = synth ctx lhs in - check ctx rhs lhs_ty - | StmtWhile (cond, body) -> - let* () = check ctx cond ty_bool in - let* _ty = synth_block ctx body in - Ok () - | StmtFor (pat, iter_expr, body) -> - let* iter_ty = synth ctx iter_expr in - let elem_ty = fresh_tyvar ctx.level in - let* () = unify_or_err iter_ty (TApp (TCon "Array", [elem_ty])) in - let* bindings = check_pattern ctx pat elem_ty in - let old = List.map (fun (n, _) -> - (n, Hashtbl.find_opt ctx.name_types n) - ) bindings in - List.iter (fun (n, t) -> bind_var ctx n t) bindings; - let* _ty = synth_block ctx body in - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - Ok () - -(** {1 Checking mode (mode ⇐)} *) - -(** Check that an expression has the expected type. *) -and check (ctx : context) (expr : expr) (expected : ty) : unit result = - match expr with - (* Lambda against arrow type: check mode is more precise. - We peel the expected arrow type one param at a time. For each param: - - If the param has an explicit quantity annotation, we verify it is - consistent with the arrow quantity from the expected type. - - The arrow quantity from the expected type is used as the definitive - quantity for binding (so an unannotated lambda param correctly inherits - the quantity from its context, e.g. a @linear annotation on the let). *) - | ExprLambda { elam_params; elam_body; elam_ret_ty = _ } - when (match repr expected with TArrow _ -> true | _ -> false) -> - let rec peel_arrows ty params = - match params, repr ty with - | [], _ -> Ok () - | p :: rest, TArrow (param_ty, q, ret_ty, _eff) -> - (* Validate explicit quantity annotation against the expected arrow. *) - let* () = match p.p_quantity with - | Some pq -> - let pq' = lower_quantity pq in - if pq' = q then Ok () - else Error (TypeMismatch { - expected = TArrow (param_ty, q, ret_ty, EPure); - got = TArrow (param_ty, pq', ret_ty, EPure); - }) - | None -> Ok () - in - bind_var ctx p.p_name.name param_ty; - peel_arrows ret_ty rest - | _ -> synth_and_unify ctx expr expected - in - let old = List.map (fun (p : param) -> - (p.p_name.name, Hashtbl.find_opt ctx.name_types p.p_name.name) - ) elam_params in - let* () = peel_arrows expected elam_params in - (* Now check the body against the final return type *) - let final_ret = List.fold_left (fun ty _ -> - match repr ty with - | TArrow (_, _, ret, _) -> ret - | _ -> ty - ) expected elam_params in - let* () = check ctx elam_body final_ret in - (* Restore *) - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - Ok () - - (* If without else against Unit *) - | ExprIf { ei_cond; ei_then; ei_else = None } -> - let* () = check ctx ei_cond ty_bool in - let* () = check ctx ei_then ty_unit in - unify_or_err expected ty_unit - - (* Default: synth and unify *) - | _ -> - synth_and_unify ctx expr expected - -and synth_and_unify (ctx : context) (expr : expr) (expected : ty) : unit result = - let* got = synth ctx expr in - unify_or_err expected got - -(** {1 Declaration checking} *) - -(** Register built-in types and functions. *) -let register_builtins (ctx : context) : unit = - (* Arithmetic builtins *) - let int_binop = TArrow (ty_int, QOmega, TArrow (ty_int, QOmega, ty_int, EPure), EPure) in - let float_binop = TArrow (ty_float, QOmega, TArrow (ty_float, QOmega, ty_float, EPure), EPure) in - bind_var ctx "print" (TArrow (ty_string, QOmega, ty_unit, ESingleton "IO")); - bind_var ctx "println" (TArrow (ty_string, QOmega, ty_unit, ESingleton "IO")); - bind_var ctx "read_line" (TArrow (ty_unit, QOmega, ty_string, ESingleton "IO")); - bind_var ctx "int_to_string" (TArrow (ty_int, QOmega, ty_string, EPure)); - bind_var ctx "int" (TArrow (ty_float, QOmega, ty_int, EPure)); - bind_var ctx "float" (TArrow (ty_int, QOmega, ty_float, EPure)); - bind_var ctx "float_to_string" (TArrow (ty_float, QOmega, ty_string, EPure)); - bind_var ctx "string_length" (TArrow (ty_string, QOmega, ty_int, EPure)); - bind_var ctx "sqrt" (TArrow (ty_float, QOmega, ty_float, EPure)); - bind_var ctx "abs" int_binop; - bind_var ctx "max" int_binop; - bind_var ctx "min" int_binop; - bind_var ctx "pow_float" float_binop; - bind_var ctx "len" (let tv = fresh_tyvar 0 in - TArrow (TApp (TCon "Array", [tv]), QOmega, ty_int, EPure)); - bind_var ctx "panic" (TArrow (ty_string, QOmega, ty_never, EPure)); - bind_var ctx "exit" (TArrow (ty_int, QOmega, ty_never, ESingleton "IO")); - (* TEA runtime — accepts any record, returns unit with IO effect *) - let tea_tv = fresh_tyvar 0 in - bind_var ctx "tea_run" (TArrow (tea_tv, QOmega, ty_unit, ESingleton "IO")); - (* Cmd Msg — linear side-effect obligation type (Stage 11). - Cmd has kind * → *. - cmd_none : Cmd 'a — the no-op command; a linear value obligating zero IO. - cmd_perform : (unit ->{IO} unit) -> Cmd 'a — wraps an IO action as a Cmd. - Cmd values bound with a [Cmd _] type annotation are automatically - treated as QOne (linear) by the quantity checker. *) - let cmd_tv = fresh_tyvar 0 in - let cmd_tv2 = fresh_tyvar 0 in - bind_var ctx "cmd_none" - (TApp (TCon "Cmd", [cmd_tv])); - bind_var ctx "cmd_perform" - (TArrow (TArrow (ty_unit, QOmega, ty_unit, ESingleton "IO"), - QOmega, - TApp (TCon "Cmd", [cmd_tv2]), - EPure)) - -(** Check a top-level function declaration. *) -let check_fn_decl (ctx : context) (fd : fn_decl) : unit result = - (* Lower parameter types *) - let* param_tys = List.fold_left (fun acc (p : param) -> - let* tys = acc in - let ty = lower_type_expr ctx p.p_ty in - let* () = check_kind ctx ty KType in - Ok (ty :: tys) - ) (Ok []) fd.fd_params in - let param_tys = List.rev param_tys in - (* Lower return type *) - let* ret_ty = match fd.fd_ret_ty with - | Some te -> - let ty = lower_type_expr ctx te in - let* () = check_kind ctx ty KType in - Ok ty - | None -> Ok (fresh_tyvar ctx.level) - in - (* Lower effect *) - (* Build the function type *) - let fn_eff = match fd.fd_eff with - | Some ee -> lower_effect_expr ctx ee - | None -> fresh_effvar ctx.level - in - let fn_ty = List.fold_right2 (fun param_ty (p : param) acc -> - let q = match p.p_quantity with - | Some q -> lower_quantity q - | None -> QOmega - in - TArrow (param_ty, q, acc, fn_eff) - ) param_tys fd.fd_params ret_ty in - (* Bind the function name (allows recursion) *) - bind_var ctx fd.fd_name.name fn_ty; - (* Bind parameters *) - let old = List.map2 (fun (p : param) ty -> - let old = Hashtbl.find_opt ctx.name_types p.p_name.name in - bind_var ctx p.p_name.name ty; - (p.p_name.name, old) - ) fd.fd_params param_tys in - (* Check the body against the return type *) - let* () = begin match fd.fd_body with - | FnBlock blk -> - let* body_ty = synth_block ctx blk in - unify_or_err ret_ty body_ty - | FnExpr e -> - check ctx e ret_ty - end in - (* Restore parameter bindings *) - List.iter (fun (n, old_sc) -> - match old_sc with - | Some sc -> Hashtbl.replace ctx.name_types n sc - | None -> Hashtbl.remove ctx.name_types n - ) old; - (* Generalize and rebind the function with its polymorphic type *) - let sc = generalize ctx fn_ty in - bind_scheme ctx fd.fd_name.name sc; - Ok () - -(** Register a type declaration in the context. *) -let register_type_decl (ctx : context) (td : type_decl) : unit result = - let* ty = match td.td_body with - | TyAlias te -> - let ty = lower_type_expr ctx te in - let* () = check_kind ctx ty KType in - Ok ty - | TyStruct fields -> - (* Register struct as a record type constructor *) - let row = List.fold_right (fun (sf : struct_field) acc -> - RExtend (sf.sf_name.name, lower_type_expr ctx sf.sf_ty, acc) - ) fields REmpty in - let ty = TRecord row in - let* () = check_kind ctx ty KType in - Ok ty - | TyEnum variants -> - (* Register each variant as a constructor *) - let result_ty = match td.td_type_params with - | [] -> TCon td.td_name.name - | params -> - let tparams = List.map (fun tp -> - match tp.tp_name.name with - | _ -> - let tv = fresh_tyvar 0 in - (* Note: In a full impl we'd map param name to tv in a local env *) - tv - ) params in - TApp (TCon td.td_name.name, tparams) - in - List.iter (fun (vd : variant_decl) -> - let ctor_ty = List.fold_right (fun field_te acc -> - TArrow (lower_type_expr ctx field_te, QOmega, acc, EPure) - ) vd.vd_fields result_ty in - (* If it has type params, we should really bind a TForall scheme *) - let sc = match td.td_type_params with - | [] -> { sc_tyvars = []; sc_effvars = []; sc_rowvars = []; sc_body = ctor_ty } - | _ -> generalize ctx ctor_ty - in - Hashtbl.replace ctx.constructor_env vd.vd_name.name ctor_ty; - (* Also bind as a variable for ExprVar references *) - if vd.vd_fields = [] then - bind_scheme ctx vd.vd_name.name sc - else - bind_scheme ctx vd.vd_name.name sc - ) variants; - Ok (TCon td.td_name.name) - in - Hashtbl.replace ctx.type_env td.td_name.name ty; - Ok () - -(** Register an effect declaration. *) -let register_effect_decl (ctx : context) (ed : effect_decl) : unit result = - List.iter (fun (op : effect_op_decl) -> - let param_tys = List.map (fun (p : param) -> - lower_type_expr ctx p.p_ty - ) op.eod_params in - let ret_ty = match op.eod_ret_ty with - | Some te -> lower_type_expr ctx te - | None -> ty_unit - in - let eff = ESingleton ed.ed_name.name in - let op_ty = List.fold_right (fun pty acc -> - TArrow (pty, QOmega, acc, eff) - ) param_tys ret_ty in - bind_var ctx op.eod_name.name op_ty - ) ed.ed_ops; - Ok () - -(** Check a single top-level declaration. *) -let check_decl (ctx : context) (decl : top_level) : (unit, type_error) Result.t = - match decl with - | TopFn fd -> - check_fn_decl ctx fd - | TopType td -> - register_type_decl ctx td - | TopEffect ed -> - register_effect_decl ctx ed - | TopTrait _td -> - (* Trait declarations are registered in the forward pass; nothing to - re-check here since there are no bodies to type-check in Phase 1. *) - Ok () - | TopImpl ib -> - (* Type-check each method body in the impl against the trait's declared - method signatures (where those exist). Methods whose trait signature - cannot be found are still checked for internal consistency. - - We also verify that the impl satisfies its trait (all required methods - are present) using [Trait.check_impl_satisfies_trait]. *) - let self_ty = lower_type_expr ctx ib.ib_self_ty in - (* Make Self available as a type alias inside method bodies *) - let old_self = Hashtbl.find_opt ctx.type_env "Self" in - Hashtbl.replace ctx.type_env "Self" self_ty; - (* Build a synthetic trait_impl record for the satisfaction check *) - let synth_impl = { - Trait.ti_trait_name = (match ib.ib_trait_ref with - | Some tr -> tr.Ast.tr_name.name - | None -> ""); - Trait.ti_trait_args = (match ib.ib_trait_ref with - | Some tr -> tr.Ast.tr_args - | None -> []); - Trait.ti_self_ty = self_ty; - Trait.ti_type_params = ib.ib_type_params; - Trait.ti_methods = List.filter_map (fun item -> - match item with - | Ast.ImplFn fd -> Some (fd.Ast.fd_name.name, fd) - | Ast.ImplType _ -> None - ) ib.ib_items; - Trait.ti_assoc_types = []; - Trait.ti_where = ib.ib_where; - } in - (* Verify method presence (trait satisfaction) — convert trait errors to - type errors so they surface through the standard pipeline. *) - let* () = match ib.ib_trait_ref with - | None -> Ok () (* Inherent impl — no trait to satisfy *) - | Some _ -> - begin match Trait.check_impl_satisfies_trait ctx.trait_registry synth_impl with - | Ok () -> Ok () - | Error re -> - Error (NotImplemented (Trait.show_resolution_error re)) - end - in - (* Type-check each method body *) - let result = List.fold_left (fun acc item -> - let* () = acc in - match item with - | Ast.ImplFn fd -> - (* Type-check the method body using the standard function checker *) - check_fn_decl ctx fd - | Ast.ImplType _ -> - (* Associated type definitions carry no body to check *) - Ok () - ) (Ok ()) ib.ib_items in - (* Restore the previous Self binding *) - begin match old_self with - | Some ty -> Hashtbl.replace ctx.type_env "Self" ty - | None -> Hashtbl.remove ctx.type_env "Self" - end; - result - | TopConst { tc_name; tc_ty; tc_value; _ } -> - let expected = lower_type_expr ctx tc_ty in - let* () = check ctx tc_value expected in - bind_var ctx tc_name.name expected; - Ok () - -(** {1 Program-level entry point} *) - -(** Type-check an entire program. - - First registers all type and effect declarations (forward pass), - then checks all function declarations and constants. *) -let check_program (symbols : Symbol.t) (prog : Ast.program) - : (context, type_error) Result.t = - let ctx = create_context symbols in - register_builtins ctx; - (* Forward pass: register all types, effects, traits, impls, and - function signatures so that mutually recursive declarations resolve. *) - let* () = List.fold_left (fun acc decl -> - let* () = acc in - match decl with - | TopType td -> register_type_decl ctx td - | TopEffect ed -> register_effect_decl ctx ed - | TopFn fd -> - (* Pre-register function with a fresh type for mutual recursion *) - let fn_ty = fresh_tyvar ctx.level in - bind_var ctx fd.fd_name.name fn_ty; - Ok () - | TopTrait td -> - (* Register trait definition in the trait registry so that - find_impl / find_method_for_type can locate it. *) - Trait.register_trait ctx.trait_registry td; - Ok () - | TopImpl ib -> - (* Lower the self type and register the impl in the trait registry. - This makes impl methods visible to ExprField trait fallback. *) - let self_ty = lower_type_expr ctx ib.ib_self_ty in - Trait.register_impl ctx.trait_registry ib self_ty; - Ok () - | _ -> Ok () - ) (Ok ()) prog.prog_decls in - (* Check pass: verify all declarations *) - let result = List.fold_left (fun acc decl -> - let* () = acc in - check_decl ctx decl - ) (Ok ()) prog.prog_decls in - match result with - | Ok () -> - (* Quantity checking pass: verify QTT quantity annotations. - This runs after type checking succeeds so that we report type - errors first (they are more fundamental). *) - begin match Quantity.check_program_quantities prog with - | Ok () -> Ok ctx - | Error (qerr, span) -> - Error (QuantityError (qerr, span)) - end - | Error e -> Error e diff --git a/affinescript/lib/types.ml b/affinescript/lib/types.ml deleted file mode 100644 index ef21b41..0000000 --- a/affinescript/lib/types.ml +++ /dev/null @@ -1,325 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Internal type representation for type checking. - - This module defines the internal type representation used during - type checking, separate from the AST types. It includes type variables - with levels for let-generalization. -*) - -(** Type variable identifier *) -type tyvar = int -[@@deriving show, eq, ord] - -(** Row variable identifier *) -type rowvar = int -[@@deriving show, eq, ord] - -(** Effect variable identifier *) -type effvar = int -[@@deriving show, eq, ord] - -(** Quantity (for QTT) *) -type quantity = - | QZero (** 0 - erased at runtime *) - | QOne (** 1 - used exactly once *) - | QOmega (** ω - used arbitrarily *) - | QVar of int (** Quantity variable *) -[@@deriving show, eq] - -(** Kind *) -type kind = - | KType (** Type kind *) - | KRow (** Row kind *) - | KEffect (** Effect kind *) - | KArrow of kind * kind (** Higher-order kind *) -[@@deriving show, eq] - -(** Type representation *) -type ty = - | TVar of tyvar_state ref (** Type variable (mutable for unification) *) - | TCon of string (** Type constructor (Int, Bool, etc.) *) - | TApp of ty * ty list (** Type application *) - | TArrow of ty * quantity * ty * eff (** Function type with quantity and effect *) - | TTuple of ty list (** Tuple type *) - | TRecord of row (** Record type *) - | TVariant of row (** Variant type *) - | TForall of tyvar * kind * ty (** Universal quantification *) - | TExists of tyvar * kind * ty (** Existential quantification *) - | TRef of ty (** Immutable reference *) - | TMut of ty (** Mutable reference *) - | TOwn of ty (** Owned type *) -[@@deriving show] - -(** Type variable state (for unification) *) -and tyvar_state = - | Unbound of tyvar * int (** Unbound with level *) - | Link of ty (** Linked to another type *) -[@@deriving show] - -(** Row type *) -and row = - | REmpty (** Empty row *) - | RExtend of string * ty * row (** Row extension *) - | RVar of rowvar_state ref (** Row variable *) -[@@deriving show] - -and rowvar_state = - | RUnbound of rowvar * int - | RLink of row -[@@deriving show] - -(** Effect type *) -and eff = - | EPure (** No effects *) - | EVar of effvar_state ref (** Effect variable *) - | ESingleton of string (** Single effect *) - | EUnion of eff list (** Union of effects *) -[@@deriving show] - -and effvar_state = - | EUnbound of effvar * int - | ELink of eff -[@@deriving show] - -(** Type scheme (polymorphic type) *) -type scheme = { - sc_tyvars : (tyvar * kind) list; - sc_effvars : effvar list; - sc_rowvars : rowvar list; - sc_body : ty; -} -[@@deriving show] - -(** Fresh variable generation *) -let next_tyvar = ref 0 -let next_rowvar = ref 0 -let next_effvar = ref 0 - -let fresh_tyvar (level : int) : ty = - let id = !next_tyvar in - next_tyvar := id + 1; - TVar (ref (Unbound (id, level))) - -let fresh_rowvar (level : int) : row = - let id = !next_rowvar in - next_rowvar := id + 1; - RVar (ref (RUnbound (id, level))) - -let fresh_effvar (level : int) : eff = - let id = !next_effvar in - next_effvar := id + 1; - EVar (ref (EUnbound (id, level))) - -(** Reset all counters (for testing) *) -let reset () = - next_tyvar := 0; - next_rowvar := 0; - next_effvar := 0 - -(** Primitive types *) -let ty_unit = TCon "Unit" -let ty_bool = TCon "Bool" -let ty_int = TCon "Int" -let ty_float = TCon "Float" -let ty_char = TCon "Char" -let ty_string = TCon "String" -let ty_never = TCon "Never" - -(** Construct an arrow type *) -let arrow ?(q = QOmega) ?(eff = EPure) (a : ty) (b : ty) : ty = - TArrow (a, q, b, eff) - -(** Construct a tuple type *) -let tuple (tys : ty list) : ty = - TTuple tys - -(** Follow links in a type variable *) -let rec repr (ty : ty) : ty = - match ty with - | TVar r -> - begin match !r with - | Link ty' -> - let ty'' = repr ty' in - r := Link ty''; (* Path compression *) - ty'' - | Unbound _ -> ty - end - | _ -> ty - -(** Follow links in a row *) -let rec repr_row (row : row) : row = - match row with - | RVar r -> - begin match !r with - | RLink row' -> - let row'' = repr_row row' in - r := RLink row''; - row'' - | RUnbound _ -> row - end - | _ -> row - -(** Follow links in an effect *) -let rec repr_eff (e : eff) : eff = - match e with - | EVar r -> - begin match !r with - | ELink e' -> - let e'' = repr_eff e' in - r := ELink e''; - e'' - | EUnbound _ -> e - end - | _ -> e - -(** Pretty printing for types *) - -let rec pp_ty (fmt : Format.formatter) (ty : ty) : unit = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v, _) -> Format.fprintf fmt "'t%d" v - | Link t -> pp_ty fmt t - end - | TCon c -> Format.fprintf fmt "%s" c - | TApp (t, args) -> - Format.fprintf fmt "%a[%a]" pp_ty t pp_ty_list args - | TArrow (a, q, b, EPure) -> - Format.fprintf fmt "(%a -{%a}-> %a)" pp_ty a pp_quantity q pp_ty b - | TArrow (a, q, b, eff) -> - Format.fprintf fmt "(%a -{%a}-> %a / %a)" pp_ty a pp_quantity q pp_ty b pp_eff eff - | TTuple tys -> - Format.fprintf fmt "(%a)" pp_ty_tuple tys - | TRecord row -> - Format.fprintf fmt "{%a}" pp_row row - | TVariant row -> - Format.fprintf fmt "[%a]" pp_row row - | TForall (v, k, body) -> - Format.fprintf fmt "(forall 't%d: %a. %a)" v pp_kind k pp_ty body - | TExists (v, k, body) -> - Format.fprintf fmt "(exists 't%d: %a. %a)" v pp_kind k pp_ty body - | TRef t -> Format.fprintf fmt "ref %a" pp_ty t - | TMut t -> Format.fprintf fmt "mut %a" pp_ty t - | TOwn t -> Format.fprintf fmt "own %a" pp_ty t - -and pp_ty_list (fmt : Format.formatter) (tys : ty list) : unit = - Format.pp_print_list ~pp_sep:(fun f () -> Format.fprintf f ", ") - pp_ty fmt tys - -and pp_ty_tuple (fmt : Format.formatter) (tys : ty list) : unit = - Format.pp_print_list ~pp_sep:(fun f () -> Format.fprintf f ", ") - pp_ty fmt tys - -and pp_row (fmt : Format.formatter) (row : row) : unit = - match repr_row row with - | REmpty -> () - | RExtend (l, ty, REmpty) -> - Format.fprintf fmt "%s: %a" l pp_ty ty - | RExtend (l, ty, rest) -> - Format.fprintf fmt "%s: %a, %a" l pp_ty ty pp_row rest - | RVar r -> - begin match !r with - | RUnbound (v, _) -> Format.fprintf fmt "..r%d" v - | RLink row' -> pp_row fmt row' - end - -and pp_eff (fmt : Format.formatter) (e : eff) : unit = - match repr_eff e with - | EPure -> Format.fprintf fmt "Pure" - | EVar r -> - begin match !r with - | EUnbound (v, _) -> Format.fprintf fmt "e%d" v - | ELink e' -> pp_eff fmt e' - end - | ESingleton name -> Format.fprintf fmt "%s" name - | EUnion effs -> - Format.pp_print_list ~pp_sep:(fun f () -> Format.fprintf f " + ") - pp_eff fmt effs - -and pp_kind (fmt : Format.formatter) (k : kind) : unit = - match k with - | KType -> Format.fprintf fmt "Type" - | KRow -> Format.fprintf fmt "Row" - | KEffect -> Format.fprintf fmt "Effect" - | KArrow (k1, k2) -> Format.fprintf fmt "(%a -> %a)" pp_kind k1 pp_kind k2 - -let ty_to_string (ty : ty) : string = - Format.asprintf "%a" pp_ty ty - -(** Type substitution: substitute type variable v with replacement in ty *) -let rec subst_ty (v : tyvar) (replacement : ty) (ty : ty) : ty = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v', _) when v' = v -> replacement - | Unbound _ -> ty - | Link t -> subst_ty v replacement t - end - | TCon _ -> ty - | TApp (t, args) -> - TApp (subst_ty v replacement t, List.map (subst_ty v replacement) args) - | TArrow (a, q, b, eff) -> - TArrow (subst_ty v replacement a, q, subst_ty v replacement b, eff) - | TTuple tys -> - TTuple (List.map (subst_ty v replacement) tys) - | TRecord row -> - TRecord (subst_row v replacement row) - | TVariant row -> - TVariant (subst_row v replacement row) - | TForall (v', _, _) when v' = v -> - ty (* Variable is shadowed *) - | TForall (v', k, body) -> - TForall (v', k, subst_ty v replacement body) - | TExists (v', _, _) when v' = v -> - ty (* Variable is shadowed *) - | TExists (v', k, body) -> - TExists (v', k, subst_ty v replacement body) - | TRef t -> TRef (subst_ty v replacement t) - | TMut t -> TMut (subst_ty v replacement t) - | TOwn t -> TOwn (subst_ty v replacement t) - -and subst_row (v : tyvar) (replacement : ty) (row : row) : row = - match repr_row row with - | REmpty -> REmpty - | RExtend (l, ty, rest) -> - RExtend (l, subst_ty v replacement ty, subst_row v replacement rest) - | RVar _ -> row - -(** Free type variable collection *) -module TyVarSet = Set.Make(Int) - -let rec free_tyvars (ty : ty) : TyVarSet.t = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v, _) -> TyVarSet.singleton v - | Link t -> free_tyvars t - end - | TCon _ -> TyVarSet.empty - | TApp (t, args) -> - List.fold_left TyVarSet.union (free_tyvars t) - (List.map free_tyvars args) - | TArrow (a, _, b, _) -> - TyVarSet.union (free_tyvars a) (free_tyvars b) - | TTuple tys -> - List.fold_left TyVarSet.union TyVarSet.empty (List.map free_tyvars tys) - | TRecord row | TVariant row -> - free_tyvars_row row - | TForall (v, _, body) | TExists (v, _, body) -> - TyVarSet.remove v (free_tyvars body) - | TRef t | TMut t | TOwn t -> - free_tyvars t - -and free_tyvars_row (row : row) : TyVarSet.t = - match repr_row row with - | REmpty -> TyVarSet.empty - | RExtend (_, ty, rest) -> - TyVarSet.union (free_tyvars ty) (free_tyvars_row rest) - | RVar _ -> TyVarSet.empty - -(** Check if a type variable occurs in a type (for occurs check) *) -let occurs (v : tyvar) (ty : ty) : bool = - TyVarSet.mem v (free_tyvars ty) - diff --git a/affinescript/lib/unify.ml b/affinescript/lib/unify.ml deleted file mode 100644 index 19d66ac..0000000 --- a/affinescript/lib/unify.ml +++ /dev/null @@ -1,317 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Type unification. - - This module implements unification for types, rows, and effects. - It uses mutable references for efficient union-find style unification. -*) - -open Types - -(** Unification errors *) -type unify_error = - | TypeMismatch of ty * ty - | OccursCheck of tyvar * ty - | RowMismatch of row * row - | RowOccursCheck of rowvar * row - | EffectMismatch of eff * eff - | EffectOccursCheck of effvar * eff - | KindMismatch of kind * kind - | LabelNotFound of string * row -[@@deriving show] - -type 'a result = ('a, unify_error) Result.t - -(* Result bind operator *) -let ( let* ) = Result.bind - -(** Check if a type variable occurs in a type (occurs check) *) -let rec occurs_in_ty (var : tyvar) (ty : ty) : bool = - match repr ty with - | TVar r -> - begin match !r with - | Unbound (v, _) -> v = var - | Link _ -> failwith "occurs_in_ty: unexpected Link after repr" - end - | TCon _ -> false - | TApp (t, args) -> - occurs_in_ty var t || List.exists (occurs_in_ty var) args - | TArrow (a, _, b, eff) -> - occurs_in_ty var a || occurs_in_ty var b || occurs_in_eff var eff - | TTuple tys -> - List.exists (occurs_in_ty var) tys - | TRecord row | TVariant row -> - occurs_in_row var row - | TForall (_, _, body) | TExists (_, _, body) -> - occurs_in_ty var body - | TRef t | TMut t | TOwn t -> - occurs_in_ty var t - -and occurs_in_row (var : tyvar) (row : row) : bool = - match repr_row row with - | REmpty -> false - | RExtend (_, ty, rest) -> - occurs_in_ty var ty || occurs_in_row var rest - | RVar _ -> false - -and occurs_in_eff (var : tyvar) (e : eff) : bool = - match repr_eff e with - | EPure -> false - | EVar _ -> false - | ESingleton _ -> false - | EUnion effs -> List.exists (occurs_in_eff var) effs - -(** Check if a row variable occurs in a row *) -let rec rowvar_occurs_in_row (var : rowvar) (row : row) : bool = - match repr_row row with - | REmpty -> false - | RExtend (_, _, rest) -> rowvar_occurs_in_row var rest - | RVar r -> - begin match !r with - | RUnbound (v, _) -> v = var - | RLink _ -> failwith "rowvar_occurs_in_row: unexpected Link" - end - -(** Check if an effect variable occurs in an effect *) -let rec effvar_occurs_in_eff (var : effvar) (e : eff) : bool = - match repr_eff e with - | EPure -> false - | ESingleton _ -> false - | EVar r -> - begin match !r with - | EUnbound (v, _) -> v = var - | ELink _ -> failwith "effvar_occurs_in_eff: unexpected Link" - end - | EUnion effs -> List.exists (effvar_occurs_in_eff var) effs - -(** Unify two types *) -let rec unify (t1 : ty) (t2 : ty) : unit result = - let t1 = repr t1 in - let t2 = repr t2 in - match (t1, t2) with - (* Same variable *) - | (TVar r1, TVar r2) when r1 == r2 -> - Ok () - - (* Variable on left *) - | (TVar r, t) -> - begin match !r with - | Unbound (var, _level) -> - if occurs_in_ty var t then - Error (OccursCheck (var, t)) - else begin - r := Link t; - Ok () - end - | Link _ -> failwith "unify: unexpected Link after repr" - end - - (* Variable on right *) - | (t, TVar r) -> - begin match !r with - | Unbound (var, _level) -> - if occurs_in_ty var t then - Error (OccursCheck (var, t)) - else begin - r := Link t; - Ok () - end - | Link _ -> failwith "unify: unexpected Link after repr" - end - - (* Same constructor *) - | (TCon c1, TCon c2) when c1 = c2 -> - Ok () - - (* Type application *) - | (TApp (t1, args1), TApp (t2, args2)) - when List.length args1 = List.length args2 -> - let* () = unify t1 t2 in - unify_list args1 args2 - - (* Arrow types *) - | (TArrow (a1, q1, b1, e1), TArrow (a2, q2, b2, e2)) -> - if q1 <> q2 then - Error (TypeMismatch (t1, t2)) - else - let* () = unify a1 a2 in - let* () = unify b1 b2 in - unify_eff e1 e2 - - (* Tuple types *) - | (TTuple ts1, TTuple ts2) when List.length ts1 = List.length ts2 -> - unify_list ts1 ts2 - - (* Record types *) - | (TRecord r1, TRecord r2) -> - unify_row r1 r2 - - (* Variant types *) - | (TVariant r1, TVariant r2) -> - unify_row r1 r2 - - (* Forall types - alpha-equivalence: bound variables are equivalent *) - | (TForall (v1, k1, body1), TForall (v2, k2, body2)) -> - if k1 <> k2 then - Error (KindMismatch (k1, k2)) - else if v1 = v2 then - (* Same variable name, directly unify bodies *) - unify body1 body2 - else - (* Different names: substitute v2 with v1 in body2 for alpha-equivalence *) - let body2' = subst_ty v2 (TVar (ref (Unbound (v1, 0)))) body2 in - unify body1 body2' - - (* Reference types *) - | (TRef t1, TRef t2) -> unify t1 t2 - | (TMut t1, TMut t2) -> unify t1 t2 - | (TOwn t1, TOwn t2) -> unify t1 t2 - - (* Never (bottom type) unifies with anything — diverging paths are compatible with all types *) - | (TCon "Never", _) | (_, TCon "Never") -> Ok () - - (* Mismatch *) - | _ -> - Error (TypeMismatch (t1, t2)) - -and unify_list (ts1 : ty list) (ts2 : ty list) : unit result = - match (ts1, ts2) with - | ([], []) -> Ok () - | (t1 :: rest1, t2 :: rest2) -> - let* () = unify t1 t2 in - unify_list rest1 rest2 - | _ -> failwith "unify_list: length mismatch" - -(** Unify two rows *) -and unify_row (r1 : row) (r2 : row) : unit result = - let r1 = repr_row r1 in - let r2 = repr_row r2 in - match (r1, r2) with - (* Both empty *) - | (REmpty, REmpty) -> Ok () - - (* Same variable *) - | (RVar rv1, RVar rv2) when rv1 == rv2 -> Ok () - - (* Variable on left *) - | (RVar r, row) -> - begin match !r with - | RUnbound (var, _level) -> - if rowvar_occurs_in_row var row then - Error (RowOccursCheck (var, row)) - else begin - r := RLink row; - Ok () - end - | RLink _ -> failwith "unify_row: unexpected RLink" - end - - (* Variable on right *) - | (row, RVar r) -> - begin match !r with - | RUnbound (var, _level) -> - if rowvar_occurs_in_row var row then - Error (RowOccursCheck (var, row)) - else begin - r := RLink row; - Ok () - end - | RLink _ -> failwith "unify_row: unexpected RLink" - end - - (* Both extend with same label *) - | (RExtend (l1, t1, rest1), RExtend (l2, t2, rest2)) when l1 = l2 -> - let* () = unify t1 t2 in - unify_row rest1 rest2 - - (* Extend with different labels - row rewriting *) - | (RExtend (l1, t1, rest1), RExtend (l2, t2, rest2)) -> - (* l1 ≠ l2, so we need to find l1 in r2 and l2 in r1 *) - (* Level 0 is appropriate here as unification creates monomorphic types *) - let new_rest = fresh_rowvar 0 in - let* () = unify_row rest1 (RExtend (l2, t2, new_rest)) in - unify_row rest2 (RExtend (l1, t1, new_rest)) - - (* Empty vs extend - error *) - | (REmpty, RExtend (l, _, _)) -> - Error (LabelNotFound (l, r1)) - | (RExtend (l, _, _), REmpty) -> - Error (LabelNotFound (l, r2)) - -(** Unify two effects *) -and unify_eff (e1 : eff) (e2 : eff) : unit result = - let e1 = repr_eff e1 in - let e2 = repr_eff e2 in - match (e1, e2) with - (* Both pure *) - | (EPure, EPure) -> Ok () - - (* Same variable *) - | (EVar r1, EVar r2) when r1 == r2 -> Ok () - - (* Variable on left *) - | (EVar r, eff) -> - begin match !r with - | EUnbound (var, _level) -> - if effvar_occurs_in_eff var eff then - Error (EffectOccursCheck (var, eff)) - else begin - r := ELink eff; - Ok () - end - | ELink _ -> failwith "unify_eff: unexpected ELink" - end - - (* Variable on right *) - | (eff, EVar r) -> - begin match !r with - | EUnbound (var, _level) -> - if effvar_occurs_in_eff var eff then - Error (EffectOccursCheck (var, eff)) - else begin - r := ELink eff; - Ok () - end - | ELink _ -> failwith "unify_eff: unexpected ELink" - end - - (* Same singleton *) - | (ESingleton e1, ESingleton e2) when e1 = e2 -> - Ok () - - (* Union vs union - set-based unification *) - | (EUnion es1, EUnion es2) -> - (* Effects are sets, so order doesn't matter *) - (* Each effect in es1 must have a corresponding effect in es2 *) - let rec find_and_unify (e : eff) (candidates : eff list) : (eff list, unify_error) Result.t = - match candidates with - | [] -> Error (EffectMismatch (e, EUnion es2)) - | c :: rest -> - match unify_eff e c with - | Ok () -> Ok rest - | Error _ -> - match find_and_unify e rest with - | Ok remaining -> Ok (c :: remaining) - | Error err -> Error err - in - let* remaining = List.fold_left (fun acc e -> - let* candidates = acc in - find_and_unify e candidates - ) (Ok es2) es1 in - (* All effects in es2 should be matched *) - if remaining = [] then Ok () - else Error (EffectMismatch (e1, e2)) - - (* Mismatch *) - | _ -> - Error (EffectMismatch (e1, e2)) - -(* Result bind operator *) -let ( let* ) = Result.bind - -(* Phase 1 complete. Future enhancements (Phase 2+): - - SMT-based predicate unification (Phase 3) - - Better error messages with source locations (Phase 2) - - Higher-order unification for type families (Phase 3) -*) diff --git a/affinescript/lib/value.ml b/affinescript/lib/value.ml deleted file mode 100644 index 2bf654e..0000000 --- a/affinescript/lib/value.ml +++ /dev/null @@ -1,287 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Runtime values for the AffineScript interpreter. - - This module defines the value representation used during - interpretation, including closures, records, and effect handlers. -*) - -open Ast - -(** Runtime value *) -type value = - | VUnit - | VBool of bool - | VInt of int - | VFloat of float - | VChar of char - | VString of string - | VTuple of value list - | VArray of value array - | VRecord of (string * value) list - | VVariant of string * value option - | VClosure of closure - | VBuiltin of string * (value list -> value result) - | VRef of value ref - | VMut of value ref - | VOwn of value ref * bool ref (** Value + moved flag *) - -(** Closure: captures environment *) -and closure = { - cl_params : param list; - cl_body : expr; - cl_env : env; -} - -(** Environment: maps names to values *) -and env = (string * value) list - -(** Evaluation errors *) -and eval_error = - | UnboundVariable of string - | TypeMismatch of string - | DivisionByZero - | IndexOutOfBounds of int * int - | FieldNotFound of string - | PatternMatchFailure - | AffineViolation of string (** Double use of affine value *) - | RuntimeError of string - | PerformEffect of string * value list (** Effect operation name and arguments *) - -and 'a result = ('a, eval_error) Result.t - -(** Show function for evaluation errors *) -let show_eval_error (e : eval_error) : string = - match e with - | UnboundVariable name -> "Unbound variable: " ^ name - | TypeMismatch msg -> "Type mismatch: " ^ msg - | DivisionByZero -> "Division by zero" - | IndexOutOfBounds (idx, len) -> - Printf.sprintf "Index out of bounds: %d (length: %d)" idx len - | FieldNotFound field -> "Field not found: " ^ field - | PatternMatchFailure -> "Pattern match failure" - | AffineViolation msg -> "Affine violation: " ^ msg - | RuntimeError msg -> "Runtime error: " ^ msg - | PerformEffect (name, _) -> "Unhandled effect: " ^ name - -let pp_eval_error fmt e = Format.pp_print_string fmt (show_eval_error e) - -(** Empty environment *) -let empty_env : env = [] - -(** Extend environment with a binding *) -let extend_env (name : string) (value : value) (env : env) : env = - (name, value) :: env - -(** Extend environment with multiple bindings *) -let extend_env_list (bindings : (string * value) list) (env : env) : env = - bindings @ env - -(** Look up a variable in the environment *) -let rec lookup_env (name : string) (env : env) : value result = - match env with - | [] -> Error (UnboundVariable name) - | (n, v) :: rest -> - if n = name then Ok v - else lookup_env name rest - -(** Check if a value is truthy *) -let is_truthy (v : value) : bool = - match v with - | VBool false -> false - | VUnit -> false - | _ -> true - -(** Convert value to string for display *) -let rec show_value (v : value) : string = - match v with - | VUnit -> "()" - | VBool true -> "true" - | VBool false -> "false" - | VInt n -> string_of_int n - | VFloat f -> string_of_float f - | VChar c -> "'" ^ Char.escaped c ^ "'" - | VString s -> "\"" ^ String.escaped s ^ "\"" - | VTuple vs -> - "(" ^ String.concat ", " (List.map show_value vs) ^ ")" - | VArray arr -> - "[" ^ String.concat ", " (Array.to_list (Array.map show_value arr)) ^ "]" - | VRecord fields -> - "{" ^ String.concat ", " (List.map (fun (n, v) -> - n ^ ": " ^ show_value v - ) fields) ^ "}" - | VVariant (tag, None) -> tag - | VVariant (tag, Some v) -> tag ^ "(" ^ show_value v ^ ")" - | VClosure _ -> "" - | VBuiltin (name, _) -> "" - | VRef r -> "ref(" ^ show_value !r ^ ")" - | VMut r -> "mut(" ^ show_value !r ^ ")" - | VOwn (r, moved) -> - if !moved then "" - else "own(" ^ show_value !r ^ ")" - -(** Pretty printer for values *) -let pp_value fmt v = Format.pp_print_string fmt (show_value v) - -(** Check affine ownership - mark as moved *) -let check_and_move_own (v : value) : value result = - match v with - | VOwn (r, moved_flag) -> - if !moved_flag then - Error (AffineViolation "Value already moved") - else begin - moved_flag := true; - Ok !r - end - | _ -> Ok v - -(** Dereference a reference *) -let deref (v : value) : value result = - match v with - | VRef r | VMut r -> Ok !r - | VOwn (r, moved_flag) -> - if !moved_flag then - Error (AffineViolation "Cannot dereference moved value") - else - Ok !r - | _ -> Error (TypeMismatch "Expected reference type") - -(** Assign to a mutable reference *) -let assign (v : value) (new_val : value) : unit result = - match v with - | VMut r -> - r := new_val; - Ok () - | VRef _ -> Error (TypeMismatch "Cannot assign to immutable reference") - | _ -> Error (TypeMismatch "Expected mutable reference") - -(** Binary operation on integers *) -let binop_int (op : binary_op) (a : int) (b : int) : value result = - match op with - | OpAdd -> Ok (VInt (a + b)) - | OpSub -> Ok (VInt (a - b)) - | OpMul -> Ok (VInt (a * b)) - | OpDiv -> - if b = 0 then Error DivisionByZero - else Ok (VInt (a / b)) - | OpMod -> - if b = 0 then Error DivisionByZero - else Ok (VInt (a mod b)) - | OpEq -> Ok (VBool (a = b)) - | OpNe -> Ok (VBool (a <> b)) - | OpLt -> Ok (VBool (a < b)) - | OpLe -> Ok (VBool (a <= b)) - | OpGt -> Ok (VBool (a > b)) - | OpGe -> Ok (VBool (a >= b)) - | OpBitAnd -> Ok (VInt (a land b)) - | OpBitOr -> Ok (VInt (a lor b)) - | OpBitXor -> Ok (VInt (a lxor b)) - | OpShl -> Ok (VInt (a lsl b)) - | OpShr -> Ok (VInt (a lsr b)) - | OpConcat -> Error (TypeMismatch "Concatenation only supported for strings/arrays") - | OpAnd | OpOr -> Error (TypeMismatch "Logical operators require booleans") - -(** Binary operation on floats *) -let binop_float (op : binary_op) (a : float) (b : float) : value result = - match op with - | OpAdd -> Ok (VFloat (a +. b)) - | OpSub -> Ok (VFloat (a -. b)) - | OpMul -> Ok (VFloat (a *. b)) - | OpDiv -> Ok (VFloat (a /. b)) - | OpEq -> Ok (VBool (a = b)) - | OpNe -> Ok (VBool (a <> b)) - | OpLt -> Ok (VBool (a < b)) - | OpLe -> Ok (VBool (a <= b)) - | OpGt -> Ok (VBool (a > b)) - | OpGe -> Ok (VBool (a >= b)) - | _ -> Error (TypeMismatch "Operation not supported on floats") - -(** Binary operation on strings *) -let binop_string (op : binary_op) (a : string) (b : string) : value result = - match op with - | OpAdd | OpConcat -> Ok (VString (a ^ b)) (* both + and ++ concat strings at runtime *) - | OpEq -> Ok (VBool (a = b)) - | OpNe -> Ok (VBool (a <> b)) - | OpLt -> Ok (VBool (String.compare a b < 0)) - | OpLe -> Ok (VBool (String.compare a b <= 0)) - | OpGt -> Ok (VBool (String.compare a b > 0)) - | OpGe -> Ok (VBool (String.compare a b >= 0)) - | _ -> Error (TypeMismatch "Operation not supported on strings") - -(** Binary operation on booleans *) -let binop_bool (op : binary_op) (a : bool) (b : bool) : value result = - match op with - | OpAnd -> Ok (VBool (a && b)) - | OpOr -> Ok (VBool (a || b)) - | OpEq -> Ok (VBool (a = b)) - | OpNe -> Ok (VBool (a <> b)) - | _ -> Error (TypeMismatch "Operation not supported on booleans") - -(** Unary operation *) -let unary_op (op : unary_op) (v : value) : value result = - match (op, v) with - | (OpNeg, VInt n) -> Ok (VInt (-n)) - | (OpNeg, VFloat f) -> Ok (VFloat (-.f)) - | (OpNot, VBool b) -> Ok (VBool (not b)) - | (OpBitNot, VInt n) -> Ok (VInt (lnot n)) - | (OpRef, v) -> Ok (VRef (ref v)) - | (OpDeref, _) -> deref v - | _ -> Error (TypeMismatch "Invalid unary operation") - -(** Get field from record *) -let get_field (name : string) (fields : (string * value) list) : value result = - match List.assoc_opt name fields with - | Some v -> Ok v - | None -> Error (FieldNotFound name) - -(** Get element from tuple *) -let get_tuple_elem (idx : int) (elems : value list) : value result = - if idx >= 0 && idx < List.length elems then - Ok (List.nth elems idx) - else - Error (IndexOutOfBounds (idx, List.length elems)) - -(** Get element from array *) -let get_array_elem (idx : int) (arr : value array) : value result = - if idx >= 0 && idx < Array.length arr then - Ok (Array.get arr idx) - else - Error (IndexOutOfBounds (idx, Array.length arr)) - -(** Set element in array *) -let set_array_elem (idx : int) (arr : value array) (v : value) : unit result = - if idx >= 0 && idx < Array.length arr then begin - Array.set arr idx v; - Ok () - end else - Error (IndexOutOfBounds (idx, Array.length arr)) - -(** Value equality (structural) *) -let rec value_eq (v1 : value) (v2 : value) : bool = - match (v1, v2) with - | (VUnit, VUnit) -> true - | (VBool a, VBool b) -> a = b - | (VInt a, VInt b) -> a = b - | (VFloat a, VFloat b) -> a = b - | (VChar a, VChar b) -> a = b - | (VString a, VString b) -> a = b - | (VTuple vs1, VTuple vs2) -> - List.length vs1 = List.length vs2 && - List.for_all2 value_eq vs1 vs2 - | (VArray arr1, VArray arr2) -> - Array.length arr1 = Array.length arr2 && - Array.for_all2 value_eq arr1 arr2 - | (VRecord fs1, VRecord fs2) -> - List.length fs1 = List.length fs2 && - List.for_all (fun (n1, v1) -> - match List.assoc_opt n1 fs2 with - | Some v2 -> value_eq v1 v2 - | None -> false - ) fs1 - | (VVariant (t1, v1), VVariant (t2, v2)) -> - t1 = t2 && (match (v1, v2) with - | (None, None) -> true - | (Some a, Some b) -> value_eq a b - | _ -> false) - | _ -> false diff --git a/affinescript/lib/wasi_runtime.ml b/affinescript/lib/wasi_runtime.ml deleted file mode 100644 index 6f1c5d3..0000000 --- a/affinescript/lib/wasi_runtime.ml +++ /dev/null @@ -1,290 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** WASI runtime support - I/O bindings for WebAssembly System Interface. - - This module provides helper functions to generate WASM code that calls - WASI system calls for I/O operations. -*) - -open Wasm - -(** WASI file descriptor constants *) -let fd_stdout = 1l -let fd_stderr = 2l - -(** Create WASI fd_write import - - fd_write signature: (fd: i32, iovs: i32, iovs_len: i32, nwritten: i32) -> i32 - - Returns (import, func_type_index) -*) -let create_fd_write_import () : import * func_type = - let func_type = { - ft_params = [I32; I32; I32; I32]; (* fd, iovs_ptr, iovs_len, nwritten_ptr *) - ft_results = [I32]; (* error code *) - } in - let import = { - i_module = "wasi_snapshot_preview1"; - i_name = "fd_write"; - i_desc = ImportFunc 0; (* Will be adjusted when added to module *) - } in - (import, func_type) - -(** Generate code to print an integer to stdout - - This function: - 1. Converts int to string in memory (digit by digit) - 2. Creates iovec structure pointing to string - 3. Calls WASI fd_write - - Algorithm: - - Handle negative sign separately - - Extract digits using division and modulo - - Write digits in reverse order (most significant first) - - Memory layout: - - [0..15]: digit buffer (max 11 chars: "-2147483648") - - [16..23]: iovec struct (buf_ptr, buf_len) - - [24..27]: nwritten result - - Returns: code that leaves 0 on stack for success -*) -let gen_print_int (heap_ptr_global : int) (fd_write_idx : int) (num_local : int) - : instr list = - (* We need additional locals for the conversion: - - buf_ptr: pointer to start of allocated buffer - - write_ptr: current position while writing digits (counts backwards) - - n: the number being converted (copy of num_local) - - digit: current digit being extracted - - is_negative: 1 if negative, 0 if positive - - len: final string length - *) - - (* Allocate 28 bytes total *) - let total_size = 28 in - let buf_local = num_local + 1 in (* reuse locals after num_local *) - let write_ptr_local = num_local + 2 in - let n_local = num_local + 3 in - let digit_local = num_local + 4 in - let is_neg_local = num_local + 5 in - let len_local = num_local + 6 in - - [ - (* Allocate memory *) - GlobalGet heap_ptr_global; - I32Const (Int32.of_int total_size); - I32Add; - GlobalSet heap_ptr_global; - - (* buf_ptr = heap_ptr - 28 *) - GlobalGet heap_ptr_global; - I32Const (Int32.of_int total_size); - I32Sub; - LocalTee buf_local; - - (* Copy number to n_local, check if negative *) - LocalGet num_local; - LocalTee n_local; - I32Const 0l; - I32LtS; (* n < 0 ? *) - LocalTee is_neg_local; - - (* If negative, negate n to make it positive *) - If (BtEmpty, [ - I32Const 0l; - LocalGet n_local; - I32Sub; (* n = -n *) - LocalSet n_local; - ], []); - - (* write_ptr starts at buf + 15 (end of buffer, write backwards) *) - LocalGet buf_local; - I32Const 15l; - I32Add; - LocalSet write_ptr_local; - - (* Extract digits loop - write from right to left *) - Block (BtEmpty, [ - Loop (BtEmpty, [ - (* digit = n % 10 *) - LocalGet n_local; - I32Const 10l; - I32RemU; - LocalTee digit_local; - - (* *write_ptr = '0' + digit *) - I32Const 48l; (* ASCII '0' *) - I32Add; - LocalGet write_ptr_local; - I32Store (0, 0); - - (* write_ptr-- *) - LocalGet write_ptr_local; - I32Const 1l; - I32Sub; - LocalSet write_ptr_local; - - (* n = n / 10 *) - LocalGet n_local; - I32Const 10l; - I32DivU; - LocalTee n_local; - - (* Continue if n > 0 *) - I32Const 0l; - I32GtU; - BrIf 0; - ]) - ]); - - (* If negative, add '-' sign *) - LocalGet is_neg_local; - If (BtEmpty, [ - I32Const 45l; (* ASCII '-' *) - LocalGet write_ptr_local; - I32Store (0, 0); - LocalGet write_ptr_local; - I32Const 1l; - I32Sub; - LocalSet write_ptr_local; - ], []); - - (* Calculate length: (buf + 15) - write_ptr *) - LocalGet buf_local; - I32Const 15l; - I32Add; - LocalGet write_ptr_local; - I32Sub; - LocalSet len_local; - - (* Actual string starts at write_ptr + 1 *) - LocalGet write_ptr_local; - I32Const 1l; - I32Add; - LocalSet write_ptr_local; - - (* Create iovec structure at buf + 16 *) - (* iovec.buf_ptr = write_ptr (where string actually starts) *) - LocalGet buf_local; - I32Const 16l; - I32Add; - LocalGet write_ptr_local; - I32Store (2, 0); - - (* iovec.buf_len = len *) - LocalGet buf_local; - I32Const 16l; - I32Add; - LocalGet len_local; - I32Store (2, 4); - - (* Call fd_write(stdout, iovec_ptr, 1, nwritten_ptr) *) - I32Const fd_stdout; - LocalGet buf_local; - I32Const 16l; - I32Add; (* iovs = buf + 16 *) - I32Const 1l; (* iovs_len = 1 *) - LocalGet buf_local; - I32Const 24l; - I32Add; (* nwritten = buf + 24 *) - Call fd_write_idx; - - (* Drop error code, return success *) - Drop; - I32Const 0l; - ] - -(** Generate code to print a newline *) -let gen_println (heap_ptr_global : int) (fd_write_idx : int) (temp_local : int) - : instr list = - let newline_byte = 10l in (* ASCII '\n' *) - [ - (* Allocate space for 1 byte + iovec + nwritten = 13 bytes *) - GlobalGet heap_ptr_global; - I32Const 13l; - I32Add; - GlobalSet heap_ptr_global; - - GlobalGet heap_ptr_global; - I32Const 13l; - I32Sub; - LocalSet temp_local; - - (* Store newline character *) - LocalGet temp_local; - I32Const newline_byte; - I32Store (0, 0); - - (* Create iovec *) - LocalGet temp_local; - I32Const 1l; - I32Add; - LocalGet temp_local; - I32Store (2, 0); - - LocalGet temp_local; - I32Const 1l; - I32Add; - I32Const 1l; - I32Store (2, 4); - - (* Call fd_write *) - I32Const fd_stdout; - LocalGet temp_local; - I32Const 1l; - I32Add; - I32Const 1l; - LocalGet temp_local; - I32Const 9l; - I32Add; - Call fd_write_idx; - - Drop; - I32Const 0l; - ] - -(** Generate code to print a string (length-prefixed in memory). - String layout: [len: i32][bytes...] - Returns: code that leaves 0 on stack for success *) -let gen_print_str (heap_ptr_global : int) (str_ptr_local : int) (fd_write_idx : int) (temp_local : int) - : instr list = - [ - (* Allocate 12 bytes for iovec + nwritten *) - GlobalGet heap_ptr_global; - I32Const 12l; - I32Add; - GlobalSet heap_ptr_global; - - GlobalGet heap_ptr_global; - I32Const 12l; - I32Sub; - LocalSet temp_local; - - (* iovec.buf_ptr = str_ptr + 4 *) - LocalGet temp_local; - LocalGet str_ptr_local; - I32Const 4l; - I32Add; - I32Store (2, 0); - - (* iovec.buf_len = *str_ptr (length) *) - LocalGet temp_local; - I32Const 4l; - I32Add; - LocalGet str_ptr_local; - I32Load (2, 0); - I32Store (2, 0); - - (* Call fd_write(stdout, iovec_ptr, 1, nwritten_ptr) *) - I32Const fd_stdout; - LocalGet temp_local; (* iovs *) - I32Const 1l; (* iovs_len *) - LocalGet temp_local; - I32Const 8l; - I32Add; (* nwritten *) - Call fd_write_idx; - - Drop; - I32Const 0l; - ] diff --git a/affinescript/lib/wasm.ml b/affinescript/lib/wasm.ml deleted file mode 100644 index d15903c..0000000 --- a/affinescript/lib/wasm.ml +++ /dev/null @@ -1,266 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** WebAssembly intermediate representation. - - This module defines a simplified WASM AST for code generation from AffineScript. - Based on the WebAssembly 1.0 specification. -*) - -(** WebAssembly value types *) -type value_type = - | I32 (** 32-bit integer *) - | I64 (** 64-bit integer *) - | F32 (** 32-bit float *) - | F64 (** 64-bit float *) -[@@deriving show, eq] - -(** WebAssembly block types *) -type block_type = - | BtEmpty - | BtType of value_type -[@@deriving show, eq] - -(** WebAssembly instructions *) -type instr = - (* Control flow *) - | Unreachable - | Nop - | Block of block_type * instr list - | Loop of block_type * instr list - | If of block_type * instr list * instr list (** then, else *) - | Br of int (** branch to label *) - | BrIf of int (** conditional branch *) - | Return - | Call of int (** function index *) - | CallIndirect of int (** type index *) - - (* Parametric instructions *) - | Drop - | Select - - (* Variable access *) - | LocalGet of int (** local variable index *) - | LocalSet of int - | LocalTee of int (** set and keep value on stack *) - | GlobalGet of int - | GlobalSet of int - - (* Memory instructions *) - | I32Load of int * int (** align, offset *) - | I64Load of int * int - | F32Load of int * int - | F64Load of int * int - | I32Store of int * int - | I64Store of int * int - | F32Store of int * int - | F64Store of int * int - | MemorySize - | MemoryGrow - - (* Numeric constants *) - | I32Const of int32 - | I64Const of int64 - | F32Const of float - | F64Const of float - - (* I32 operations *) - | I32Eqz - | I32Eq | I32Ne - | I32LtS | I32LtU - | I32GtS | I32GtU - | I32LeS | I32LeU - | I32GeS | I32GeU - - | I32Clz | I32Ctz | I32Popcnt - | I32Add | I32Sub | I32Mul - | I32DivS | I32DivU - | I32RemS | I32RemU - | I32And | I32Or | I32Xor - | I32Shl | I32ShrS | I32ShrU - | I32Rotl | I32Rotr - - (* I64 operations *) - | I64Eqz - | I64Eq | I64Ne - | I64LtS | I64LtU - | I64GtS | I64GtU - | I64LeS | I64LeU - | I64GeS | I64GeU - - | I64Clz | I64Ctz | I64Popcnt - | I64Add | I64Sub | I64Mul - | I64DivS | I64DivU - | I64RemS | I64RemU - | I64And | I64Or | I64Xor - | I64Shl | I64ShrS | I64ShrU - | I64Rotl | I64Rotr - - (* F32 operations *) - | F32Eq | F32Ne - | F32Lt | F32Gt - | F32Le | F32Ge - - | F32Abs | F32Neg | F32Ceil | F32Floor - | F32Trunc | F32Nearest | F32Sqrt - | F32Add | F32Sub | F32Mul | F32Div - | F32Min | F32Max | F32Copysign - - (* F64 operations *) - | F64Eq | F64Ne - | F64Lt | F64Gt - | F64Le | F64Ge - - | F64Abs | F64Neg | F64Ceil | F64Floor - | F64Trunc | F64Nearest | F64Sqrt - | F64Add | F64Sub | F64Mul | F64Div - | F64Min | F64Max | F64Copysign - - (* Conversions *) - | I32WrapI64 - | I64ExtendI32S | I64ExtendI32U - | I32TruncF32S | I32TruncF32U - | I32TruncF64S | I32TruncF64U - | I64TruncF32S | I64TruncF32U - | I64TruncF64S | I64TruncF64U - | F32ConvertI32S | F32ConvertI32U - | F32ConvertI64S | F32ConvertI64U - | F32DemoteF64 - | F64ConvertI32S | F64ConvertI32U - | F64ConvertI64S | F64ConvertI64U - | F64PromoteF32 - | I32ReinterpretF32 - | I64ReinterpretF64 - | F32ReinterpretI32 - | F64ReinterpretI64 -[@@deriving show, eq] - -(** Function type *) -type func_type = { - ft_params : value_type list; - ft_results : value_type list; -} -[@@deriving show, eq] - -(** Function locals *) -type local = { - l_count : int; - l_type : value_type; -} -[@@deriving show, eq] - -(** Function definition *) -type func = { - f_type : int; (** index into type section *) - f_locals : local list; - f_body : instr list; -} -[@@deriving show, eq] - -(** Export descriptor *) -type export_desc = - | ExportFunc of int - | ExportTable of int - | ExportMemory of int - | ExportGlobal of int -[@@deriving show, eq] - -(** Export *) -type export = { - e_name : string; - e_desc : export_desc; -} -[@@deriving show, eq] - -(** Import descriptor *) -type import_desc = - | ImportFunc of int (** type index *) - | ImportTable - | ImportMemory - | ImportGlobal of value_type -[@@deriving show, eq] - -(** Import *) -type import = { - i_module : string; - i_name : string; - i_desc : import_desc; -} -[@@deriving show, eq] - -(** Global definition *) -type global = { - g_type : value_type; - g_mutable : bool; - g_init : instr list; -} -[@@deriving show, eq] - -(** Memory limits *) -type limits = { - lim_min : int; - lim_max : int option; -} -[@@deriving show, eq] - -(** Memory definition *) -type memory = { - mem_type : limits; -} -[@@deriving show, eq] - -(** Table definition *) -type table = { - tab_type : limits; -} -[@@deriving show, eq] - -(** Element segment (for initializing tables) *) -type elem = { - e_table : int; (** table index *) - e_offset : int; (** offset in table *) - e_funcs : int list; (** function indices *) -} -[@@deriving show, eq] - -(** Data segment (for initializing memory) *) -type data = { - d_data : bytes; (** data bytes *) - d_offset : int; (** offset in memory *) -} -[@@deriving show, eq] - -(** WebAssembly module *) -type wasm_module = { - types : func_type list; - funcs : func list; - tables : table list; - mems : memory list; - globals : global list; - exports : export list; - imports : import list; - elems : elem list; (** element segments for table initialization *) - datas : data list; (** data segments for memory initialization *) - start : int option; (** optional start function index *) - custom_sections : (string * bytes) list; - (** Named custom sections (Wasm section ID 0). - Used for [affinescript.ownership] — carries ownership annotations - (TyOwn/TyRef/TyMut) that survive to the binary for typed-wasm - Level 7/10 verification. *) -} -[@@deriving show, eq] - -(** Create an empty WASM module *) -let empty_module () : wasm_module = { - types = []; - funcs = []; - tables = []; - mems = []; - globals = []; - exports = []; - imports = []; - elems = []; - datas = []; - start = None; - custom_sections = []; -} diff --git a/affinescript/lib/wasm_encode.ml b/affinescript/lib/wasm_encode.ml deleted file mode 100644 index 64dd0e5..0000000 --- a/affinescript/lib/wasm_encode.ml +++ /dev/null @@ -1,373 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* Minimal WASM Binary Encoder *) - -open Wasm - -let add_u8 buf n = - Buffer.add_char buf (Char.chr (n land 0xFF)) - -let add_u32_leb buf n = - let rec loop v = - let byte = v land 0x7F in - let v' = v lsr 7 in - if v' = 0 then - add_u8 buf byte - else ( - add_u8 buf (byte lor 0x80); - loop v' - ) - in - loop n - -let add_sleb32 buf n = - let rec loop v = - let byte = Int32.to_int (Int32.logand v 0x7Fl) in - let sign_bit = byte land 0x40 in - let v' = Int32.shift_right v 7 in - let is_done = (v' = 0l && sign_bit = 0) || (v' = -1l && sign_bit <> 0) in - if is_done then - add_u8 buf byte - else ( - add_u8 buf (byte lor 0x80); - loop v' - ) - in - loop n - -let add_sleb64 buf n = - let rec loop v = - let byte = Int64.to_int (Int64.logand v 0x7FL) in - let sign_bit = byte land 0x40 in - let v' = Int64.shift_right v 7 in - let is_done = (v' = 0L && sign_bit = 0) || (v' = -1L && sign_bit <> 0) in - if is_done then - add_u8 buf byte - else ( - add_u8 buf (byte lor 0x80); - loop v' - ) - in - loop n - -let add_bytes buf b = - Buffer.add_bytes buf b - -let add_string buf s = - let b = Bytes.of_string s in - add_u32_leb buf (Bytes.length b); - add_bytes buf b - -let add_vec buf items f = - add_u32_leb buf (List.length items); - List.iter (f buf) items - -let add_valtype buf = function - | I32 -> add_u8 buf 0x7F - | I64 -> add_u8 buf 0x7E - | F32 -> add_u8 buf 0x7D - | F64 -> add_u8 buf 0x7C - -let add_blocktype buf = function - | BtEmpty -> add_u8 buf 0x40 - | BtType t -> add_valtype buf t - -let add_limits buf lim = - match lim.lim_max with - | None -> - add_u8 buf 0x00; - add_u32_leb buf lim.lim_min - | Some max -> - add_u8 buf 0x01; - add_u32_leb buf lim.lim_min; - add_u32_leb buf max - -let add_memarg buf align offset = - add_u32_leb buf align; - add_u32_leb buf offset - -let rec add_instr buf = function - | Unreachable -> add_u8 buf 0x00 - | Nop -> add_u8 buf 0x01 - | Block (bt, instrs) -> - add_u8 buf 0x02; - add_blocktype buf bt; - List.iter (add_instr buf) instrs; - add_u8 buf 0x0B - | Loop (bt, instrs) -> - add_u8 buf 0x03; - add_blocktype buf bt; - List.iter (add_instr buf) instrs; - add_u8 buf 0x0B - | If (bt, then_instrs, else_instrs) -> - add_u8 buf 0x04; - add_blocktype buf bt; - List.iter (add_instr buf) then_instrs; - if else_instrs <> [] then ( - add_u8 buf 0x05; - List.iter (add_instr buf) else_instrs - ); - add_u8 buf 0x0B - | Br idx -> add_u8 buf 0x0C; add_u32_leb buf idx - | BrIf idx -> add_u8 buf 0x0D; add_u32_leb buf idx - | Return -> add_u8 buf 0x0F - | Call idx -> add_u8 buf 0x10; add_u32_leb buf idx - | CallIndirect idx -> add_u8 buf 0x11; add_u32_leb buf idx; add_u8 buf 0x00 - | Drop -> add_u8 buf 0x1A - | Select -> add_u8 buf 0x1B - | LocalGet idx -> add_u8 buf 0x20; add_u32_leb buf idx - | LocalSet idx -> add_u8 buf 0x21; add_u32_leb buf idx - | LocalTee idx -> add_u8 buf 0x22; add_u32_leb buf idx - | GlobalGet idx -> add_u8 buf 0x23; add_u32_leb buf idx - | GlobalSet idx -> add_u8 buf 0x24; add_u32_leb buf idx - | I32Load (align, offset) -> add_u8 buf 0x28; add_memarg buf align offset - | I64Load (align, offset) -> add_u8 buf 0x29; add_memarg buf align offset - | F32Load (align, offset) -> add_u8 buf 0x2A; add_memarg buf align offset - | F64Load (align, offset) -> add_u8 buf 0x2B; add_memarg buf align offset - | I32Store (align, offset) -> add_u8 buf 0x36; add_memarg buf align offset - | I64Store (align, offset) -> add_u8 buf 0x37; add_memarg buf align offset - | F32Store (align, offset) -> add_u8 buf 0x38; add_memarg buf align offset - | F64Store (align, offset) -> add_u8 buf 0x39; add_memarg buf align offset - | MemorySize -> add_u8 buf 0x3F; add_u8 buf 0x00 - | MemoryGrow -> add_u8 buf 0x40; add_u8 buf 0x00 - | I32Const v -> add_u8 buf 0x41; add_sleb32 buf v - | I64Const v -> add_u8 buf 0x42; add_sleb64 buf v - | F32Const v -> - add_u8 buf 0x43; - let bits = Int32.bits_of_float v in - let b = Bytes.create 4 in - Bytes.set_int32_le b 0 bits; - add_bytes buf b - | F64Const v -> - add_u8 buf 0x44; - let bits = Int64.bits_of_float v in - let b = Bytes.create 8 in - Bytes.set_int64_le b 0 bits; - add_bytes buf b - | I32Eqz -> add_u8 buf 0x45 - | I32Eq -> add_u8 buf 0x46 - | I32Ne -> add_u8 buf 0x47 - | I32LtS -> add_u8 buf 0x48 - | I32LtU -> add_u8 buf 0x49 - | I32GtS -> add_u8 buf 0x4A - | I32GtU -> add_u8 buf 0x4B - | I32LeS -> add_u8 buf 0x4C - | I32LeU -> add_u8 buf 0x4D - | I32GeS -> add_u8 buf 0x4E - | I32GeU -> add_u8 buf 0x4F - | I64Eqz -> add_u8 buf 0x50 - | I64Eq -> add_u8 buf 0x51 - | I64Ne -> add_u8 buf 0x52 - | I64LtS -> add_u8 buf 0x53 - | I64LtU -> add_u8 buf 0x54 - | I64GtS -> add_u8 buf 0x55 - | I64GtU -> add_u8 buf 0x56 - | I64LeS -> add_u8 buf 0x57 - | I64LeU -> add_u8 buf 0x58 - | I64GeS -> add_u8 buf 0x59 - | I64GeU -> add_u8 buf 0x5A - | F32Eq -> add_u8 buf 0x5B - | F32Ne -> add_u8 buf 0x5C - | F32Lt -> add_u8 buf 0x5D - | F32Gt -> add_u8 buf 0x5E - | F32Le -> add_u8 buf 0x5F - | F32Ge -> add_u8 buf 0x60 - | F64Eq -> add_u8 buf 0x61 - | F64Ne -> add_u8 buf 0x62 - | F64Lt -> add_u8 buf 0x63 - | F64Gt -> add_u8 buf 0x64 - | F64Le -> add_u8 buf 0x65 - | F64Ge -> add_u8 buf 0x66 - | I32Clz -> add_u8 buf 0x67 - | I32Ctz -> add_u8 buf 0x68 - | I32Popcnt -> add_u8 buf 0x69 - | I32Add -> add_u8 buf 0x6A - | I32Sub -> add_u8 buf 0x6B - | I32Mul -> add_u8 buf 0x6C - | I32DivS -> add_u8 buf 0x6D - | I32DivU -> add_u8 buf 0x6E - | I32RemS -> add_u8 buf 0x6F - | I32RemU -> add_u8 buf 0x70 - | I32And -> add_u8 buf 0x71 - | I32Or -> add_u8 buf 0x72 - | I32Xor -> add_u8 buf 0x73 - | I32Shl -> add_u8 buf 0x74 - | I32ShrS -> add_u8 buf 0x75 - | I32ShrU -> add_u8 buf 0x76 - | I32Rotl -> add_u8 buf 0x77 - | I32Rotr -> add_u8 buf 0x78 - | I64Clz -> add_u8 buf 0x79 - | I64Ctz -> add_u8 buf 0x7A - | I64Popcnt -> add_u8 buf 0x7B - | I64Add -> add_u8 buf 0x7C - | I64Sub -> add_u8 buf 0x7D - | I64Mul -> add_u8 buf 0x7E - | I64DivS -> add_u8 buf 0x7F - | I64DivU -> add_u8 buf 0x80 - | I64RemS -> add_u8 buf 0x81 - | I64RemU -> add_u8 buf 0x82 - | I64And -> add_u8 buf 0x83 - | I64Or -> add_u8 buf 0x84 - | I64Xor -> add_u8 buf 0x85 - | I64Shl -> add_u8 buf 0x86 - | I64ShrS -> add_u8 buf 0x87 - | I64ShrU -> add_u8 buf 0x88 - | I64Rotl -> add_u8 buf 0x89 - | I64Rotr -> add_u8 buf 0x8A - | F32Abs -> add_u8 buf 0x8B - | F32Neg -> add_u8 buf 0x8C - | F32Ceil -> add_u8 buf 0x8D - | F32Floor -> add_u8 buf 0x8E - | F32Trunc -> add_u8 buf 0x8F - | F32Nearest -> add_u8 buf 0x90 - | F32Sqrt -> add_u8 buf 0x91 - | F32Add -> add_u8 buf 0x92 - | F32Sub -> add_u8 buf 0x93 - | F32Mul -> add_u8 buf 0x94 - | F32Div -> add_u8 buf 0x95 - | F32Min -> add_u8 buf 0x96 - | F32Max -> add_u8 buf 0x97 - | F32Copysign -> add_u8 buf 0x98 - | F64Abs -> add_u8 buf 0x99 - | F64Neg -> add_u8 buf 0x9A - | F64Ceil -> add_u8 buf 0x9B - | F64Floor -> add_u8 buf 0x9C - | F64Trunc -> add_u8 buf 0x9D - | F64Nearest -> add_u8 buf 0x9E - | F64Sqrt -> add_u8 buf 0x9F - | F64Add -> add_u8 buf 0xA0 - | F64Sub -> add_u8 buf 0xA1 - | F64Mul -> add_u8 buf 0xA2 - | F64Div -> add_u8 buf 0xA3 - | F64Min -> add_u8 buf 0xA4 - | F64Max -> add_u8 buf 0xA5 - | F64Copysign -> add_u8 buf 0xA6 - | I32WrapI64 -> add_u8 buf 0xA7 - | I32TruncF32S -> add_u8 buf 0xA8 - | I32TruncF32U -> add_u8 buf 0xA9 - | I32TruncF64S -> add_u8 buf 0xAA - | I32TruncF64U -> add_u8 buf 0xAB - | I64ExtendI32S -> add_u8 buf 0xAC - | I64ExtendI32U -> add_u8 buf 0xAD - | I64TruncF32S -> add_u8 buf 0xAE - | I64TruncF32U -> add_u8 buf 0xAF - | I64TruncF64S -> add_u8 buf 0xB0 - | I64TruncF64U -> add_u8 buf 0xB1 - | F32ConvertI32S -> add_u8 buf 0xB2 - | F32ConvertI32U -> add_u8 buf 0xB3 - | F32ConvertI64S -> add_u8 buf 0xB4 - | F32ConvertI64U -> add_u8 buf 0xB5 - | F32DemoteF64 -> add_u8 buf 0xB6 - | F64ConvertI32S -> add_u8 buf 0xB7 - | F64ConvertI32U -> add_u8 buf 0xB8 - | F64ConvertI64S -> add_u8 buf 0xB9 - | F64ConvertI64U -> add_u8 buf 0xBA - | F64PromoteF32 -> add_u8 buf 0xBB - | I32ReinterpretF32 -> add_u8 buf 0xBC - | I64ReinterpretF64 -> add_u8 buf 0xBD - | F32ReinterpretI32 -> add_u8 buf 0xBE - | F64ReinterpretI64 -> add_u8 buf 0xBF - -let add_instrs buf instrs = - List.iter (add_instr buf) instrs - -let add_func_type buf ft = - add_u8 buf 0x60; - add_vec buf ft.ft_params add_valtype; - add_vec buf ft.ft_results add_valtype - -let add_import buf imp = - add_string buf imp.i_module; - add_string buf imp.i_name; - match imp.i_desc with - | ImportFunc type_idx -> - add_u8 buf 0x00; - add_u32_leb buf type_idx - | ImportTable -> - add_u8 buf 0x01; - add_u8 buf 0x70; - add_limits buf { lim_min = 0; lim_max = None } - | ImportMemory -> - add_u8 buf 0x02; - add_limits buf { lim_min = 1; lim_max = None } - | ImportGlobal vt -> - add_u8 buf 0x03; - add_valtype buf vt; - add_u8 buf 0x00 - -let add_export buf exp = - add_string buf exp.e_name; - match exp.e_desc with - | ExportFunc idx -> add_u8 buf 0x00; add_u32_leb buf idx - | ExportTable idx -> add_u8 buf 0x01; add_u32_leb buf idx - | ExportMemory idx -> add_u8 buf 0x02; add_u32_leb buf idx - | ExportGlobal idx -> add_u8 buf 0x03; add_u32_leb buf idx - -let add_global buf g = - add_valtype buf g.g_type; - add_u8 buf (if g.g_mutable then 0x01 else 0x00); - add_instrs buf g.g_init; - add_u8 buf 0x0B - -let add_code buf f = - let body = Buffer.create 256 in - add_vec body f.f_locals (fun b l -> add_u32_leb b l.l_count; add_valtype b l.l_type); - add_instrs body f.f_body; - add_u8 body 0x0B; - add_u32_leb buf (Buffer.length body); - Buffer.add_buffer buf body - -let add_elem buf e = - if e.e_table <> 0 then failwith "Only table 0 supported"; - add_u8 buf 0x00; - add_u8 buf 0x41; add_sleb32 buf (Int32.of_int e.e_offset); add_u8 buf 0x0B; - add_vec buf e.e_funcs add_u32_leb - -let add_data buf d = - add_u8 buf 0x00; - add_u8 buf 0x41; add_sleb32 buf (Int32.of_int d.d_offset); add_u8 buf 0x0B; - add_u32_leb buf (Bytes.length d.d_data); - add_bytes buf d.d_data - -let add_section buf id builder = - let sec = Buffer.create 256 in - builder sec; - if Buffer.length sec > 0 then ( - add_u8 buf id; - add_u32_leb buf (Buffer.length sec); - Buffer.add_buffer buf sec - ) - -let write_module_to_file path (m : wasm_module) : unit = - let buf = Buffer.create 4096 in - Buffer.add_string buf "asm"; - Buffer.add_string buf ""; - - add_section buf 1 (fun b -> add_vec b m.types add_func_type); - add_section buf 2 (fun b -> add_vec b m.imports add_import); - add_section buf 3 (fun b -> add_vec b m.funcs (fun b f -> add_u32_leb b f.f_type)); - add_section buf 4 (fun b -> - add_vec b m.tables (fun b t -> add_u8 b 0x70; add_limits b t.tab_type) - ); - add_section buf 5 (fun b -> - add_vec b m.mems (fun b mem -> add_limits b mem.mem_type) - ); - add_section buf 6 (fun b -> add_vec b m.globals add_global); - add_section buf 7 (fun b -> add_vec b m.exports add_export); - (match m.start with - | None -> () - | Some idx -> add_section buf 8 (fun b -> add_u32_leb b idx)); - add_section buf 9 (fun b -> add_vec b m.elems add_elem); - add_section buf 10 (fun b -> add_vec b m.funcs add_code); - add_section buf 11 (fun b -> add_vec b m.datas add_data); - - (* Emit custom sections (Wasm section ID 0) — includes [affinescript.ownership] typed-wasm schema *) - List.iter (fun (name, payload) -> - add_section buf 0 (fun b -> - add_string b name; - add_bytes b payload - ) - ) m.custom_sections; - - let oc = open_out_bin path in - output_bytes oc (Bytes.of_string (Buffer.contents buf)); - close_out oc diff --git a/affinescript/lib/wasm_gc.ml b/affinescript/lib/wasm_gc.ml deleted file mode 100644 index 74d78e0..0000000 --- a/affinescript/lib/wasm_gc.ml +++ /dev/null @@ -1,316 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** WebAssembly GC proposal intermediate representation. - - Extends the WASM 1.0 IR ({!Wasm}) with types and instructions defined - by the WebAssembly GC proposal (phase 4, merged into spec 2023). - - AffineScript types map to GC types as follows: - - Int / Bool / Char / Nat → [GcPrim I32] in struct fields - - Float → [GcPrim F64] in struct fields - - String → [GcRef (HtConcrete string_array_idx)] - - Array[T] → [GcRef (HtConcrete array_type_idx)] - - Record / Struct → [GcRef (HtConcrete struct_type_idx)] - - Tuple → [GcRef (HtConcrete tuple_type_idx)] - - Variant → [GcRef (HtConcrete variant_type_idx)] - - Binary encoding: {!Wasm_gc_encode} - Code generation: {!Codegen_gc} - Spec reference: https://webassembly.github.io/gc/core/binary/ -*) - -(** {1 Primitive value types} *) - -(** The four primitive WASM value types (same as in {!Wasm}). *) -type prim_valtype = - | I32 (** 32-bit integer *) - | I64 (** 64-bit integer *) - | F32 (** 32-bit float *) - | F64 (** 64-bit float *) -[@@deriving show, eq] - -(** {1 Reference / heap types} *) - -(** Heap types classify what a GC reference points to. - Abstract heap types sit at the top of the type hierarchy; - concrete heap types are indices into the type section. *) -type heap_type = - | HtAny (** any — root of the reference type hierarchy *) - | HtEq (** eq — types supporting structural equality *) - | HtI31 (** i31 — unboxed 31-bit integers *) - | HtStruct (** struct — any struct (abstract supertype) *) - | HtArray (** array — any array (abstract supertype) *) - | HtFunc (** func — any function reference *) - | HtNone (** none — bottom of non-func/non-extern hierarchy *) - | HtNoFunc (** nofunc — bottom of the func hierarchy *) - | HtNoExtern (** noextern — bottom of the extern hierarchy *) - | HtConcrete of int (** concrete type index (non-negative) *) -[@@deriving show, eq] - -(** GC-aware value types — extends primitive types with reference types. *) -type gc_valtype = - | GcPrim of prim_valtype (** i32 / i64 / f32 / f64 *) - | GcRef of heap_type (** (ref ht) — non-null reference *) - | GcRefNull of heap_type (** (ref null ht) — nullable reference *) - | GcAnyref (** anyref = (ref null any) shorthand *) - | GcEqref (** eqref = (ref null eq) shorthand *) - | GcI31ref (** i31ref = (ref null i31) shorthand *) - | GcStructref (** structref = (ref null struct) shorthand *) - | GcArrayref (** arrayref = (ref null array) shorthand *) -[@@deriving show, eq] - -(** {1 Type definitions} *) - -(** Storage type for struct / array fields. - Can be a full value type or a packed integer type (i8 / i16). *) -type storage_type = - | StVal of gc_valtype (** full value type *) - | StI8 (** packed i8 — sign-extended on struct.get_s *) - | StI16 (** packed i16 — sign-extended on struct.get_s *) -[@@deriving show, eq] - -(** Field type: storage type + mutability flag. *) -type field_type = { - ft_storage : storage_type; - ft_mutable : bool; (** true = mutable (var); false = immutable (const) *) -} -[@@deriving show, eq] - -(** Struct type: an ordered sequence of fields. - Field names are tracked separately in {!Codegen_gc} — the binary - format only carries field indices. *) -type struct_type = field_type list -[@@deriving show, eq] - -(** Array element type (a single field_type). *) -type array_type = field_type -[@@deriving show, eq] - -(** Function type using GC value types. - Used in the type section for GC-aware function signatures. *) -type gc_func_type = { - gft_params : gc_valtype list; - gft_results : gc_valtype list; -} -[@@deriving show, eq] - -(** A type definition entry in the GC type section. *) -type gc_type_def = - | GcStructType of struct_type (** struct { field* } *) - | GcArrayType of array_type (** array { field } *) - | GcFuncType of gc_func_type (** func (params) (results) *) -[@@deriving show, eq] - -(** {1 Instructions} *) - -(** Block result type for GC-aware blocks. - [GcBtRef ht] covers blocks that return a reference. *) -type gc_blocktype = - | GcBtEmpty (** void block — no result *) - | GcBtPrim of prim_valtype (** block returning a primitive *) - | GcBtRef of heap_type (** block returning a (ref null ht) *) -[@@deriving show, eq] - -(** GC instructions, extending the WASM 1.0 instruction set. - - [Std instr] wraps any standard WASM 1.0 instruction for use in GC - function bodies. All GC-specific instructions have dedicated constructors - and are encoded with a [0xFB] prefix opcode. - - Spec: https://webassembly.github.io/gc/core/binary/instructions.html *) -type gc_instr = - - (* ── Standard WASM 1.0 instructions (wrapped for use in GC bodies) ──── *) - - | Std of Wasm.instr - (** Any WASM 1.0 instruction: LocalGet, I32Add, Call, Return, etc. - Encoded identically to the WASM 1.0 binary format. *) - - (* ── Struct instructions (0xFB prefix) ─────────────────────────────── *) - - | StructNew of int - (** [struct.new typeidx] — allocate struct; all fields from stack - (TOS = last declared field). Consumes N values; leaves a ref. *) - - | StructNewDefault of int - (** [struct.new_default typeidx] — allocate struct with zero / null defaults. *) - - | StructGet of int * int - (** [struct.get typeidx fieldidx] — read field from struct ref. - Consumes (ref struct); leaves the field value. *) - - | StructGetS of int * int - (** [struct.get_s typeidx fieldidx] — read packed field, sign-extended. *) - - | StructGetU of int * int - (** [struct.get_u typeidx fieldidx] — read packed field, zero-extended. *) - - | StructSet of int * int - (** [struct.set typeidx fieldidx] — write to a mutable struct field. - Consumes (ref struct, value); leaves nothing. *) - - (* ── Array instructions (0xFB prefix) ──────────────────────────────── *) - - | ArrayNew of int - (** [array.new typeidx] — allocate array of given length, all elements - initialised to a provided value. Consumes (init_val, length). *) - - | ArrayNewDefault of int - (** [array.new_default typeidx] — allocate zero / null-initialized array. - Consumes (length: i32). *) - - | ArrayNewFixed of int * int - (** [array.new_fixed typeidx n] — allocate array from N stack values. - Values must be on the stack TOS = element[n-1]. *) - - | ArrayGet of int - (** [array.get typeidx] — load element at index. - Consumes (ref array, index: i32); leaves element value. *) - - | ArrayGetS of int - (** [array.get_s typeidx] — load packed element, sign-extended. *) - - | ArrayGetU of int - (** [array.get_u typeidx] — load packed element, zero-extended. *) - - | ArraySet of int - (** [array.set typeidx] — store element at index. - Consumes (ref array, index: i32, value); leaves nothing. *) - - | ArrayLen - (** [array.len] — get array length. - Consumes (ref array); leaves i32 length. *) - - | ArrayCopy of int * int - (** [array.copy dst_typeidx src_typeidx] — bulk element copy - between two arrays. *) - - (* ── Reference instructions ─────────────────────────────────────────── *) - - | RefNull of heap_type - (** [ref.null ht] — push a null reference of given heap type. *) - - | RefIsNull - (** [ref.is_null] — test if reference is null: ref → i32. *) - - | RefTest of heap_type - (** [ref.test (ref ht)] — non-null cast test: ref → i32 (1=match). *) - - | RefTestNull of heap_type - (** [ref.test (ref null ht)] — nullable cast test: ref → i32. *) - - | RefCast of heap_type - (** [ref.cast (ref ht)] — non-null downcast; traps on failure. *) - - | RefCastNull of heap_type - (** [ref.cast (ref null ht)] — nullable downcast; traps on non-null mismatch. *) - - | RefI31 - (** [ref.i31] — box i32 into i31ref. Consumes i32; leaves i31ref. *) - - | I31GetS - (** [i31.get_s] — unbox i31ref with sign extension: i31ref → i32. *) - - | I31GetU - (** [i31.get_u] — unbox i31ref with zero extension: i31ref → i32. *) - - (* ── GC-aware control flow wrappers ─────────────────────────────────── *) - - | GcBlock of gc_blocktype * gc_instr list - (** Block whose result type may be a reference. *) - - | GcLoop of gc_blocktype * gc_instr list - (** Loop with a GC block type. *) - - | GcIf of gc_blocktype * gc_instr list * gc_instr list - (** If/else with a GC block type. *) - -[@@deriving show, eq] - -(** {1 Module-level types} *) - -(** A GC-aware function definition. *) -type gc_func = { - gf_type : int; (** index into type section — must reference GcFuncType *) - gf_locals : (int * gc_valtype) list; (** (count, type) pairs for additional locals beyond params *) - gf_body : gc_instr list; -} -[@@deriving show, eq] - -(** Import descriptor for a GC module. *) -type gc_import_desc = - | GcImportFunc of int (** function type index *) - | GcImportMemory (** linear memory (optional in GC modules) *) -[@@deriving show, eq] - -(** A GC module import. *) -type gc_import = { - gi_module : string; - gi_name : string; - gi_desc : gc_import_desc; -} -[@@deriving show, eq] - -(** Export descriptor for a GC module. *) -type gc_export_desc = - | GcExportFunc of int (** function index *) - | GcExportMemory of int (** memory index *) -[@@deriving show, eq] - -(** A GC module export. *) -type gc_export = { - ge_name : string; - ge_desc : gc_export_desc; -} -[@@deriving show, eq] - -(** A complete WasmGC module. - - Sections emitted by {!Wasm_gc_encode.write_gc_module_to_file}: - - Type section (1): GC type definitions - - Import section (2): imports - - Function section (3): type indices - - Export section (7): named exports - - Code section (10): function bodies *) -type gc_module = { - gc_types : gc_type_def list; (** ordered type section entries *) - gc_imports : gc_import list; (** imports (functions, memories) *) - gc_funcs : gc_func list; (** function definitions *) - gc_exports : gc_export list; (** exports *) -} -[@@deriving show, eq] - -(** {1 Convenience constructors} *) - -(** Empty GC module. *) -let empty_gc_module () : gc_module = { - gc_types = []; - gc_imports = []; - gc_funcs = []; - gc_exports = []; -} - -(** Immutable i32 struct field. *) -let field_i32 : field_type = { - ft_storage = StVal (GcPrim I32); - ft_mutable = false; -} - -(** Immutable f64 struct field. *) -let field_f64 : field_type = { - ft_storage = StVal (GcPrim F64); - ft_mutable = false; -} - -(** Mutable anyref struct field (stores any GC value). *) -let field_anyref_mut : field_type = { - ft_storage = StVal GcAnyref; - ft_mutable = true; -} - -(** Immutable anyref struct field. *) -let field_anyref : field_type = { - ft_storage = StVal GcAnyref; - ft_mutable = false; -} diff --git a/affinescript/lib/wasm_gc_encode.ml b/affinescript/lib/wasm_gc_encode.ml deleted file mode 100644 index 285e71e..0000000 --- a/affinescript/lib/wasm_gc_encode.ml +++ /dev/null @@ -1,368 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) *) - -(** Binary encoder for WasmGC modules. - - Serialises a {!Wasm_gc.gc_module} to the WASM GC binary format. - - Key differences from the WASM 1.0 format ({!Wasm_encode}): - - Type section entries may be struct types (0x5F) or array types (0x5E), - in addition to function types (0x60). - - Value types include reference types (0x63 / 0x64 + heap_type). - - GC instructions use a 0xFB sub-opcode prefix. - - Standard WASM 1.0 instructions are encoded by delegating to - {!Wasm_encode.add_instr}. - - Spec: https://webassembly.github.io/gc/core/binary/ -*) - -open Wasm_gc - -(* ── Low-level byte writers ─────────────────────────────────────────────── *) - -let add_u8 buf n = - Buffer.add_char buf (Char.chr (n land 0xFF)) - -(** Unsigned LEB128 encoding. Used for type indices, counts, etc. *) -let add_u32_leb buf n = - let rec loop v = - let byte = v land 0x7F in - let v' = v lsr 7 in - if v' = 0 then add_u8 buf byte - else (add_u8 buf (byte lor 0x80); loop v') - in - loop n - -(** UTF-8 / binary string with LEB128 length prefix. *) -let add_string buf s = - let b = Bytes.of_string s in - add_u32_leb buf (Bytes.length b); - Buffer.add_bytes buf b - -(** Encode a WASM vector: LEB128 count followed by elements. *) -let add_vec buf items f = - add_u32_leb buf (List.length items); - List.iter (f buf) items - -(* ── Value type encodings ────────────────────────────────────────────────── *) - -(** Encode a primitive value type (same byte assignments as WASM 1.0). *) -let add_prim_valtype buf = function - | I32 -> add_u8 buf 0x7F - | I64 -> add_u8 buf 0x7E - | F32 -> add_u8 buf 0x7D - | F64 -> add_u8 buf 0x7C - -(** Encode a heap type. - - Abstract heap types use the fixed byte values specified in the GC proposal. - Concrete type indices (non-negative integers) encode identically to - unsigned LEB128 — the s33 format is equivalent for non-negative values. *) -let add_heap_type buf = function - | HtAny -> add_u8 buf 0x6E (* any *) - | HtEq -> add_u8 buf 0x6D (* eq *) - | HtI31 -> add_u8 buf 0x6C (* i31 *) - | HtStruct -> add_u8 buf 0x6B (* struct *) - | HtArray -> add_u8 buf 0x6A (* array *) - | HtFunc -> add_u8 buf 0x70 (* func *) - | HtNone -> add_u8 buf 0x71 (* none *) - | HtNoFunc -> add_u8 buf 0x73 (* nofunc *) - | HtNoExtern -> add_u8 buf 0x72 (* noextern *) - | HtConcrete idx -> add_u32_leb buf idx (* s33: positive = type index *) - -(** Encode a GC-aware value type. *) -let add_gc_valtype buf = function - | GcPrim p -> add_prim_valtype buf p - | GcRef ht -> - (* (ref ht) — non-null reference: 0x63 heaptype *) - add_u8 buf 0x63; add_heap_type buf ht - | GcRefNull ht -> - (* (ref null ht) — nullable reference: 0x64 heaptype *) - add_u8 buf 0x64; add_heap_type buf ht - | GcAnyref -> add_u8 buf 0x6E (* anyref shorthand *) - | GcEqref -> add_u8 buf 0x6D (* eqref shorthand *) - | GcI31ref -> add_u8 buf 0x6C (* i31ref shorthand *) - | GcStructref -> add_u8 buf 0x6B (* structref shorthand *) - | GcArrayref -> add_u8 buf 0x6A (* arrayref shorthand *) - -(* ── Type section encodings ─────────────────────────────────────────────── *) - -(** Encode the storage type of a struct / array field. *) -let add_storage_type buf = function - | StVal vt -> add_gc_valtype buf vt - | StI8 -> add_u8 buf 0x78 (* packed i8 *) - | StI16 -> add_u8 buf 0x77 (* packed i16 *) - -(** Encode a field type: storage type followed by mutability byte. *) -let add_field_type buf ft = - add_storage_type buf ft.ft_storage; - add_u8 buf (if ft.ft_mutable then 0x01 else 0x00) - -(** Encode a struct type definition: 0x5F + vec(fieldtype). *) -let add_struct_type buf (fields : struct_type) = - add_u8 buf 0x5F; - add_vec buf fields add_field_type - -(** Encode an array type definition: 0x5E + fieldtype. *) -let add_array_type buf (elem : array_type) = - add_u8 buf 0x5E; - add_field_type buf elem - -(** Encode a function type definition using GC value types: 0x60 + params + results. *) -let add_gc_func_type buf (ft : gc_func_type) = - add_u8 buf 0x60; - add_vec buf ft.gft_params add_gc_valtype; - add_vec buf ft.gft_results add_gc_valtype - -(** Encode one type definition in the type section. - Each entry is implicitly a final type with no declared supertypes, - which is the simplest valid encoding (backward-compatible with the - pre-GC type section for function types). *) -let add_gc_type_def buf = function - | GcStructType fields -> add_struct_type buf fields - | GcArrayType elem -> add_array_type buf elem - | GcFuncType ft -> add_gc_func_type buf ft - -(* ── GC block type encoding ─────────────────────────────────────────────── *) - -(** Encode a block result type for GC-aware control flow instructions. *) -let add_gc_blocktype buf = function - | GcBtEmpty -> add_u8 buf 0x40 (* void *) - | GcBtPrim p -> add_prim_valtype buf p (* primitive result *) - | GcBtRef ht -> - (* Emit as (ref null ht) — nullable covers both null and non-null *) - add_u8 buf 0x64; add_heap_type buf ht - -(* ── Instruction encoding ───────────────────────────────────────────────── *) - -(** Recursively encode a GC instruction. - - Standard WASM 1.0 instructions are delegated to {!Wasm_encode.add_instr}. - GC instructions use the 0xFB sub-opcode prefix, followed by the specific - opcode and operands. *) -let rec add_gc_instr buf = function - - (* ── Wrapped standard WASM 1.0 instructions ──────────────────────── *) - | Std instr -> - Wasm_encode.add_instr buf instr - - (* ── Struct instructions ─────────────────────────────────────────── *) - - | StructNew typeidx -> - (* struct.new: 0xFB 0x00 typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x00; add_u32_leb buf typeidx - - | StructNewDefault typeidx -> - (* struct.new_default: 0xFB 0x01 typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x01; add_u32_leb buf typeidx - - | StructGet (typeidx, fieldidx) -> - (* struct.get: 0xFB 0x02 typeidx fieldidx *) - add_u8 buf 0xFB; add_u8 buf 0x02; - add_u32_leb buf typeidx; add_u32_leb buf fieldidx - - | StructGetS (typeidx, fieldidx) -> - (* struct.get_s: 0xFB 0x03 typeidx fieldidx *) - add_u8 buf 0xFB; add_u8 buf 0x03; - add_u32_leb buf typeidx; add_u32_leb buf fieldidx - - | StructGetU (typeidx, fieldidx) -> - (* struct.get_u: 0xFB 0x04 typeidx fieldidx *) - add_u8 buf 0xFB; add_u8 buf 0x04; - add_u32_leb buf typeidx; add_u32_leb buf fieldidx - - | StructSet (typeidx, fieldidx) -> - (* struct.set: 0xFB 0x05 typeidx fieldidx *) - add_u8 buf 0xFB; add_u8 buf 0x05; - add_u32_leb buf typeidx; add_u32_leb buf fieldidx - - (* ── Array instructions ──────────────────────────────────────────── *) - - | ArrayNew typeidx -> - (* array.new: 0xFB 0x06 typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x06; add_u32_leb buf typeidx - - | ArrayNewDefault typeidx -> - (* array.new_default: 0xFB 0x07 typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x07; add_u32_leb buf typeidx - - | ArrayNewFixed (typeidx, n) -> - (* array.new_fixed: 0xFB 0x08 typeidx n *) - add_u8 buf 0xFB; add_u8 buf 0x08; - add_u32_leb buf typeidx; add_u32_leb buf n - - | ArrayGet typeidx -> - (* array.get: 0xFB 0x0B typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x0B; add_u32_leb buf typeidx - - | ArrayGetS typeidx -> - (* array.get_s: 0xFB 0x0C typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x0C; add_u32_leb buf typeidx - - | ArrayGetU typeidx -> - (* array.get_u: 0xFB 0x0D typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x0D; add_u32_leb buf typeidx - - | ArraySet typeidx -> - (* array.set: 0xFB 0x0E typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x0E; add_u32_leb buf typeidx - - | ArrayLen -> - (* array.len: 0xFB 0x0F *) - add_u8 buf 0xFB; add_u8 buf 0x0F - - | ArrayCopy (dst, src) -> - (* array.copy: 0xFB 0x11 dst_typeidx src_typeidx *) - add_u8 buf 0xFB; add_u8 buf 0x11; - add_u32_leb buf dst; add_u32_leb buf src - - (* ── Reference instructions ──────────────────────────────────────── *) - - | RefNull ht -> - (* ref.null: 0xD0 heaptype *) - add_u8 buf 0xD0; add_heap_type buf ht - - | RefIsNull -> - (* ref.is_null: 0xD1 *) - add_u8 buf 0xD1 - - | RefTest ht -> - (* ref.test (ref ht) non-null: 0xFB 0x14 heaptype *) - add_u8 buf 0xFB; add_u8 buf 0x14; add_heap_type buf ht - - | RefTestNull ht -> - (* ref.test (ref null ht) nullable: 0xFB 0x15 heaptype *) - add_u8 buf 0xFB; add_u8 buf 0x15; add_heap_type buf ht - - | RefCast ht -> - (* ref.cast (ref ht) non-null: 0xFB 0x16 heaptype *) - add_u8 buf 0xFB; add_u8 buf 0x16; add_heap_type buf ht - - | RefCastNull ht -> - (* ref.cast (ref null ht) nullable: 0xFB 0x17 heaptype *) - add_u8 buf 0xFB; add_u8 buf 0x17; add_heap_type buf ht - - | RefI31 -> - (* ref.i31: 0xFB 0x1C *) - add_u8 buf 0xFB; add_u8 buf 0x1C - - | I31GetS -> - (* i31.get_s: 0xFB 0x1D *) - add_u8 buf 0xFB; add_u8 buf 0x1D - - | I31GetU -> - (* i31.get_u: 0xFB 0x1E *) - add_u8 buf 0xFB; add_u8 buf 0x1E - - (* ── GC-aware control flow ────────────────────────────────────────── *) - - | GcBlock (bt, instrs) -> - add_u8 buf 0x02; (* block opcode *) - add_gc_blocktype buf bt; - List.iter (add_gc_instr buf) instrs; - add_u8 buf 0x0B (* end *) - - | GcLoop (bt, instrs) -> - add_u8 buf 0x03; (* loop opcode *) - add_gc_blocktype buf bt; - List.iter (add_gc_instr buf) instrs; - add_u8 buf 0x0B (* end *) - - | GcIf (bt, then_instrs, else_instrs) -> - add_u8 buf 0x04; (* if opcode *) - add_gc_blocktype buf bt; - List.iter (add_gc_instr buf) then_instrs; - if else_instrs <> [] then ( - add_u8 buf 0x05; (* else *) - List.iter (add_gc_instr buf) else_instrs - ); - add_u8 buf 0x0B (* end *) - -(* ── Section helpers ────────────────────────────────────────────────────── *) - -(** Encode a function code entry: locals vector + body + end marker. *) -let add_gc_code buf (gf : gc_func) = - let body = Buffer.create 256 in - (* Locals: vec of (count, gc_valtype) pairs *) - add_vec body gf.gf_locals (fun b (count, vt) -> - add_u32_leb b count; - add_gc_valtype b vt - ); - List.iter (add_gc_instr body) gf.gf_body; - add_u8 body 0x0B; (* end *) - add_u32_leb buf (Buffer.length body); - Buffer.add_buffer buf body - -let add_gc_import buf (gi : gc_import) = - add_string buf gi.gi_module; - add_string buf gi.gi_name; - match gi.gi_desc with - | GcImportFunc type_idx -> - add_u8 buf 0x00; add_u32_leb buf type_idx - | GcImportMemory -> - add_u8 buf 0x02; - add_u8 buf 0x00; (* limits: no max *) - add_u32_leb buf 1 (* minimum 1 page *) - -let add_gc_export buf (ge : gc_export) = - add_string buf ge.ge_name; - match ge.ge_desc with - | GcExportFunc idx -> add_u8 buf 0x00; add_u32_leb buf idx - | GcExportMemory idx -> add_u8 buf 0x02; add_u32_leb buf idx - -(** Write a section if it has content. - section_id: WASM section identifier byte. - builder: fills a temporary buffer whose length-prefixed content is appended. *) -let add_section buf section_id builder = - let sec = Buffer.create 256 in - builder sec; - if Buffer.length sec > 0 then ( - add_u8 buf section_id; - add_u32_leb buf (Buffer.length sec); - Buffer.add_buffer buf sec - ) - -(* ── Top-level writer ───────────────────────────────────────────────────── *) - -(** Serialise a {!Wasm_gc.gc_module} to a WASM GC binary file. - - Sections emitted in the required order: - {ol - {- Type (1) — GC type definitions} - {- Import (2) — imports} - {- Function (3) — function type index list} - {- Export (7) — named exports} - {- Code (10) — function bodies} - } - - Browsers and runtimes that support the GC proposal (V8 ≥ Chrome 119, - SpiderMonkey ≥ Firefox 120, Wasmtime with --wasm-features gc) accept - this format. *) -let write_gc_module_to_file (path : string) (m : gc_module) : unit = - let buf = Buffer.create 4096 in - - (* WASM magic number: \0asm *) - Buffer.add_string buf "\x00\x61\x73\x6D"; - (* WASM version: 1 (little-endian u32) *) - Buffer.add_string buf "\x01\x00\x00\x00"; - - (* Section 1: Types *) - add_section buf 1 (fun b -> add_vec b m.gc_types add_gc_type_def); - - (* Section 2: Imports *) - add_section buf 2 (fun b -> add_vec b m.gc_imports add_gc_import); - - (* Section 3: Functions (type index for each function) *) - add_section buf 3 (fun b -> - add_vec b m.gc_funcs (fun b gf -> add_u32_leb b gf.gf_type) - ); - - (* Section 7: Exports *) - add_section buf 7 (fun b -> add_vec b m.gc_exports add_gc_export); - - (* Section 10: Code (function bodies) *) - add_section buf 10 (fun b -> add_vec b m.gc_funcs add_gc_code); - - let oc = open_out_bin path in - output_bytes oc (Bytes.of_string (Buffer.contents buf)); - close_out oc diff --git a/affinescript/manifest.scm b/affinescript/manifest.scm deleted file mode 100644 index dd942c7..0000000 --- a/affinescript/manifest.scm +++ /dev/null @@ -1,24 +0,0 @@ -;;; SPDX-License-Identifier: MPL-2.0 -;;; manifest.scm — Generic Guix manifest for RSR-compliant projects -;;; -;;; Usage: -;;; guix shell -m manifest.scm -;;; - -(specifications->manifest - '(;; Core development tools - "git" - "just" - "nickel" - "curl" - "bash" - "coreutils" - - ;; Documentation - "asciidoctor" - "pandoc" - - ;; Common build dependencies - "openssl" - "zlib" - "pkg-config")) diff --git a/affinescript/opsm.toml b/affinescript/opsm.toml deleted file mode 100644 index 9d3c672..0000000 --- a/affinescript/opsm.toml +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# affinescript package manifest — OPSM native. -# This file makes affinescript a first-class OPSM/HFR package. - -[package] -name = "affinescript" -description = "AffineScript compiler and standard library — affine-typed, WASM-first functional language" -license = "MPL-2.0" -authors = ["Jonathan D.A. Jewell "] -keywords = ["affinescript", "hyperpolymath", "nextgen-languages", "compiler"] -homepage = "https://github.com/hyperpolymath/nextgen-languages" -repository = "https://github.com/hyperpolymath/nextgen-languages" -forth = "affinescript" - -[opsm] -trust_level = "hyperpolymath" -registry = "hf" - -[dependencies] -proven = { git = "https://github.com/hyperpolymath/proven", registry = "hf" } -groove = { git = "https://github.com/hyperpolymath/groove", registry = "hf" } diff --git a/affinescript/packages/affine-js/README.adoc b/affinescript/packages/affine-js/README.adoc deleted file mode 100644 index c53cb09..0000000 --- a/affinescript/packages/affine-js/README.adoc +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= affine-js -:toc: -:icons: font - -JavaScript interop layer for AffineScript WASM modules. - -Part of the https://github.com/hyperpolymath/affinescript[AffineScript] project. - -== Installation - -[source,json] ----- -// deno.json -{ - "imports": { - "@hyperpolymath/affine-js": "jsr:@hyperpolymath/affine-js@0.1.0" - } -} ----- - -== Quick start - -[source,javascript] ----- -import { AffineModule, run, int } from "@hyperpolymath/affine-js"; - -// Run a compiled AffineScript program end-to-end: -await run("./hello.wasm"); - -// Load a module and call individual exports: -const mod = await AffineModule.fromFile("./math.wasm"); -const result = mod.call("add", {}, int(3), int(4)); -console.log(result); // { kind: "int", value: 7 } ----- - -== Compiling AffineScript to WASM - -[source,bash] ----- -# Compile an AffineScript program to WASM: -affinescript compile hello.affine --output hello.wasm - -# Compile with WasmGC target (for modern runtimes): -affinescript compile-gc hello.affine --output hello.wasm ----- - -== Effect handlers - -AffineScript programs declare algebraic effects using `effect` declarations. -Every effect operation becomes a WASM import that the host (your JavaScript) must satisfy. - -Default handlers are provided for the `IO` effect (print, println, read_line). -Override them via the `imports` option: - -[source,javascript] ----- -import { AffineModule } from "@hyperpolymath/affine-js"; - -const mod = await AffineModule.fromFile("./app.wasm", { - imports: { - // Override IO.println to send output to a DOM element instead: - affine_io_println(strPtr) { - const text = readStringFromMemory(mod.memory, strPtr); - document.getElementById("output").textContent += text + "\n"; - }, - - // Provide a handler for a custom effect: - // effect Log { fn warn(msg: String); } - affine_log_warn(strPtr) { - console.warn("[AffineScript]", readStringFromMemory(mod.memory, strPtr)); - }, - }, -}); - -await mod.runMain(); ----- - -Effect import names follow the pattern `affine__` in snake_case. - -== AffineValue types - -All JS ↔ WASM value exchanges use the `AffineValue` tagged-union type: - -[cols="1,2,2"] -|=== -| Kind | Construction | Description - -| `int` -| `int(42)` -| 32-bit integer - -| `float` -| `float(3.14)` -| 64-bit float - -| `bool` -| `bool(true)` -| Boolean - -| `unit` -| `unit()` -| Unit / void - -| `string` -| `string("hello")` -| UTF-8 string (heap-allocated in WASM memory) - -| `some` -| `some(int(5))` -| `Option[T]::Some(v)` - -| `none` -| `none()` -| `Option[T]::None` - -| `ok` -| `ok(string("result"))` -| `Result[T,E]::Ok(v)` - -| `err` -| `err(string("oops"))` -| `Result[T,E]::Err(e)` - -| `array` -| `array([int(1), int(2)])` -| Homogeneous array - -| `record` -| `record({ x: int(1), y: int(2) })` -| Structural record -|=== - -== TypeScript support - -`affine-js` ships `types.d.ts` declarations. TypeScript projects can use the -package without any additional steps — the type definitions are automatically -picked up by `tsc` and editors. - -For TypeScript projects, the companion `@hyperpolymath/affine-ts` package -provides enhanced declarations with stricter generics. - -== Return type hints - -Because WASM erases type information at the binary level, you need to tell the -unmarshaler how to interpret return values for complex types: - -[source,javascript] ----- -// Simple types — no hint needed (defaults to "int"): -mod.call("get_count"); // → { kind: "int", value: 42 } - -// Hint for strings: -mod.call("get_name", { returnType: "string" }); - -// Hint for Option: -mod.call("maybe_find", { returnType: "option" }); - -// Hint for Result: -mod.call("try_parse", { returnType: "result" }); ----- - -== Memory layout - -The AffineScript WASM runtime uses the following in-memory representation: - -- **Int** → `i32` WASM value -- **Float** → `f64` WASM value -- **Bool** → `i32` (0 = false, 1 = true) -- **Unit** → `i32` sentinel (0) -- **String** → pointer to `[length: i32][...utf-8 bytes]` -- **Option::None** → null pointer (0) -- **Option::Some(v)** → pointer to `[TAG_SOME: i32][...payload]` -- **Result::Ok(v)** → pointer to `[TAG_OK: i32][...payload]` -- **Result::Err(e)** → pointer to `[TAG_ERR: i32][...payload]` -- **Array[T]** → pointer to `[length: i32][...element pointers (i32)]` -- **Record** → pointer to consecutive field values (in declaration order) - -Tag constants are exported from `@hyperpolymath/affine-js/runtime` as `AFFINE_TAG`. - -== License - -MPL-2.0. -See link:../../LICENSE[LICENSE] at the repository root. diff --git a/affinescript/packages/affine-js/marshal.js b/affinescript/packages/affine-js/marshal.js deleted file mode 100644 index 9b33eb4..0000000 --- a/affinescript/packages/affine-js/marshal.js +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-js: Value marshaling between JavaScript and AffineScript WASM -// -// AffineScript values have a typed representation on both sides of the WASM -// boundary. This module provides: -// -// marshal(value, memory) — AffineValue → raw WASM operand (i32 / f64) -// unmarshal(raw, tag, mem) — raw WASM operand → AffineValue -// constructors — type-safe AffineValue builders -// -// Simple types (Int, Float, Bool, Unit) are passed as WASM value-type -// operands directly. Complex types (String, Option, Result, Array, Record) -// are heap-allocated in linear memory and passed as i32 pointers. -// -// ── Memory protocol ────────────────────────────────────────────────────────── -// -// The AffineScript WASM runtime allocates from a bump allocator starting at -// the low end of linear memory (above the stack). Functions that return -// complex values write to an out-pointer supplied by the caller. The JS -// host uses the same protocol when marshaling arguments into memory. -// -// The exported function `affine_alloc(bytes: i32) -> i32` provides a simple -// allocator interface. The JS host calls this to reserve space before -// writing strings or records. The corresponding `affine_dealloc` is a no-op -// in the current bump allocator but exists for forward-compatibility. - -import { AFFINE_SIZE, AFFINE_TAG } from "./runtime.js"; - -// ── AffineValue constructors ────────────────────────────────────────────────── - -/** - * @typedef { - * | { kind: "int"; value: number } - * | { kind: "float"; value: number } - * | { kind: "bool"; value: boolean } - * | { kind: "unit" } - * | { kind: "string"; value: string } - * | { kind: "some"; value: AffineValue } - * | { kind: "none" } - * | { kind: "ok"; value: AffineValue } - * | { kind: "err"; value: AffineValue } - * | { kind: "array"; elements: AffineValue[] } - * | { kind: "record"; fields: Record } - * } AffineValue - */ - -/** @returns {AffineValue} */ -export const int = (value) => ({ kind: "int", value }); -/** @returns {AffineValue} */ -export const float = (value) => ({ kind: "float", value }); -/** @returns {AffineValue} */ -export const bool = (value) => ({ kind: "bool", value }); -/** @returns {AffineValue} */ -export const unit = () => ({ kind: "unit" }); -/** @returns {AffineValue} */ -export const string = (value) => ({ kind: "string", value }); -/** @returns {AffineValue} */ -export const some = (value) => ({ kind: "some", value }); -/** @returns {AffineValue} */ -export const none = () => ({ kind: "none" }); -/** @returns {AffineValue} */ -export const ok = (value) => ({ kind: "ok", value }); -/** @returns {AffineValue} */ -export const err = (value) => ({ kind: "err", value }); -/** @returns {AffineValue} */ -export const array = (elements) => ({ kind: "array", elements }); -/** @returns {AffineValue} */ -export const record = (fields) => ({ kind: "record", fields }); - -// ── Marshal: AffineValue → WASM ─────────────────────────────────────────────── - -/** - * Marshal an `AffineValue` into the form expected by a WASM import/export. - * - * For simple types this returns a JS number directly. For heap types this - * allocates in linear memory (via `affine_alloc`) and returns the i32 - * pointer. - * - * @param {AffineValue} value - * @param {WebAssembly.Memory} memory - * @param {(n: number) => number} alloc - `affine_alloc` export from the module - * @returns {number} Raw WASM operand (i32 or f64 as JS number) - */ -export function marshal(value, memory, alloc) { - switch (value.kind) { - case "int": - return value.value | 0; // coerce to i32 - - case "float": - return value.value; // f64, passed as JS number - - case "bool": - return value.value ? 1 : 0; // i32 0/1 - - case "unit": - return 0; // sentinel i32 - - case "string": { - const encoded = new TextEncoder().encode(value.value); - const ptr = alloc(AFFINE_SIZE.LEN + encoded.byteLength); - const view = new DataView(memory.buffer); - view.setInt32(ptr, encoded.byteLength, true); - new Uint8Array(memory.buffer).set(encoded, ptr + AFFINE_SIZE.LEN); - return ptr; - } - - case "none": - return 0; // null pointer = None - - case "some": { - // [TAG_SOME: i32][...marshaled payload] - // For payload we need its size; for simplicity primitives inline here. - const payloadSize = sizeOf(value.value); - const ptr = alloc(AFFINE_SIZE.TAG + payloadSize); - const view = new DataView(memory.buffer); - view.setInt32(ptr, AFFINE_TAG.SOME, true); - writePrimitive(value.value, memory, ptr + AFFINE_SIZE.TAG, alloc); - return ptr; - } - - case "ok": { - const payloadSize = sizeOf(value.value); - const ptr = alloc(AFFINE_SIZE.TAG + payloadSize); - const view = new DataView(memory.buffer); - view.setInt32(ptr, AFFINE_TAG.OK, true); - writePrimitive(value.value, memory, ptr + AFFINE_SIZE.TAG, alloc); - return ptr; - } - - case "err": { - const payloadSize = sizeOf(value.value); - const ptr = alloc(AFFINE_SIZE.TAG + payloadSize); - const view = new DataView(memory.buffer); - view.setInt32(ptr, AFFINE_TAG.ERR, true); - writePrimitive(value.value, memory, ptr + AFFINE_SIZE.TAG, alloc); - return ptr; - } - - case "array": { - const ptr = alloc(AFFINE_SIZE.LEN + value.elements.length * AFFINE_SIZE.PTR); - const view = new DataView(memory.buffer); - view.setInt32(ptr, value.elements.length, true); - let off = ptr + AFFINE_SIZE.LEN; - for (const elem of value.elements) { - const elemPtr = marshal(elem, memory, alloc); - view.setInt32(off, elemPtr, true); - off += AFFINE_SIZE.PTR; - } - return ptr; - } - - case "record": { - const entries = Object.entries(value.fields); - const ptr = alloc(entries.length * AFFINE_SIZE.PTR); - const view = new DataView(memory.buffer); - let off = ptr; - for (const [, fieldVal] of entries) { - const fieldPtr = marshal(fieldVal, memory, alloc); - view.setInt32(off, fieldPtr, true); - off += AFFINE_SIZE.PTR; - } - return ptr; - } - - default: - throw new Error(`affine-js: unknown value kind '${value.kind}'`); - } -} - -// ── Unmarshal: WASM → AffineValue ──────────────────────────────────────────── - -/** - * Unmarshal a raw WASM return value into an `AffineValue`. - * - * The `valueType` hint tells the unmarshaler how to interpret the raw number - * (because WASM is untyped from the JS host perspective). - * - * @param {number} raw - Raw WASM operand - * @param {"int"|"float"|"bool"|"unit"|"string"|"option"|"result"|"array"|"record"} valueType - * @param {WebAssembly.Memory} memory - * @param {string[]} [fieldNames] - For "record" kind, in declaration order - * @returns {AffineValue} - */ -export function unmarshal(raw, valueType, memory) { - const view = new DataView(memory.buffer); - switch (valueType) { - case "int": - return int(raw | 0); - - case "float": - return float(raw); - - case "bool": - return bool(raw !== 0); - - case "unit": - return unit(); - - case "string": { - if (raw === 0) return string(""); // null pointer = empty string - const len = view.getInt32(raw, true); - const bytes = new Uint8Array(memory.buffer, raw + AFFINE_SIZE.LEN, len); - return string(new TextDecoder().decode(bytes)); - } - - case "option": { - if (raw === 0) return none(); - const tag = view.getInt32(raw, true); - if (tag !== AFFINE_TAG.SOME) { - throw new Error(`affine-js: expected Some tag (1), got ${tag}`); - } - // Payload is an opaque i32 pointer; caller must further unmarshal - const payloadPtr = view.getInt32(raw + AFFINE_SIZE.TAG, true); - return some(int(payloadPtr)); // opaque: caller can re-unmarshal - } - - case "result": { - if (raw === 0) throw new Error("affine-js: null Result pointer"); - const tag = view.getInt32(raw, true); - const payloadPtr = view.getInt32(raw + AFFINE_SIZE.TAG, true); - if (tag === AFFINE_TAG.OK) return ok(int(payloadPtr)); - if (tag === AFFINE_TAG.ERR) return err(int(payloadPtr)); - throw new Error(`affine-js: unknown Result tag ${tag}`); - } - - case "array": { - if (raw === 0) return array([]); - const len = view.getInt32(raw, true); - const elements = []; - let off = raw + AFFINE_SIZE.LEN; - for (let i = 0; i < len; i++) { - elements.push(int(view.getInt32(off, true))); - off += AFFINE_SIZE.PTR; - } - return array(elements); - } - - default: - // Fallback: treat as opaque int pointer - return int(raw); - } -} - -// ── Internal helpers ────────────────────────────────────────────────────────── - -/** - * Return the byte size needed to store a marshaled value in-place. - * For heap types (string, option, result) the caller is responsible for - * allocating space for the pointee separately; this returns PTR size. - * @param {AffineValue} value - * @returns {number} - */ -function sizeOf(value) { - switch (value.kind) { - case "float": return AFFINE_SIZE.FLOAT; - case "int": - case "bool": - case "unit": - case "string": - case "some": - case "none": - case "ok": - case "err": - case "array": - case "record": - return AFFINE_SIZE.PTR; - default: - return AFFINE_SIZE.PTR; - } -} - -/** - * Write a marshaled value at a given offset in memory, in-place. - * Primitive values are written directly; heap values are marshaled recursively. - * @param {AffineValue} value - * @param {WebAssembly.Memory} memory - * @param {number} offset - * @param {(n: number) => number} alloc - */ -function writePrimitive(value, memory, offset, alloc) { - const view = new DataView(memory.buffer); - switch (value.kind) { - case "int": - case "bool": - case "unit": - view.setInt32(offset, value.kind === "bool" ? (value.value ? 1 : 0) : (value.value ?? 0), true); - break; - case "float": - view.setFloat64(offset, value.value, true); - break; - default: { - const ptr = marshal(value, memory, alloc); - view.setInt32(offset, ptr, true); - } - } -} diff --git a/affinescript/packages/affine-js/mod.js b/affinescript/packages/affine-js/mod.js deleted file mode 100644 index f918449..0000000 --- a/affinescript/packages/affine-js/mod.js +++ /dev/null @@ -1,252 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-js: JavaScript interop layer for AffineScript WASM modules -// -// AffineScript programs compile to standard WebAssembly (WASM) modules. -// This package provides ergonomic JavaScript bindings for loading and calling -// those modules from Deno or any WASM-capable JS host. -// -// Quick start: -// -// import { AffineModule, run } from "@hyperpolymath/affine-js"; -// -// // Run a compiled AffineScript program: -// const result = await run("./hello.wasm"); -// -// // Or load and call individual exports: -// const mod = await AffineModule.fromFile("./math.wasm"); -// const sum = mod.call("add", { kind: "int", value: 3 }, { kind: "int", value: 4 }); -// console.log(sum); // { kind: "int", value: 7 } -// -// Effect handlers: -// -// AffineScript programs may declare algebraic effects (IO, State, Exn, etc.). -// The host must supply a handler for every effect the program may invoke at -// runtime. Pass `{ imports: { affine_io_println: myLogger } }` to override -// the default IO implementation. -// -// See ./runtime.js for the full list of default handlers and the naming -// convention used by the codegen. - -import { makeRuntimeImports } from "./runtime.js"; -import { marshal, unmarshal } from "./marshal.js"; - -/** - * An instantiated AffineScript WASM module. - * - * Wraps a `WebAssembly.Instance` and provides type-safe access to exported - * functions via `AffineValue` objects. - */ -export class AffineModule { - /** @type {WebAssembly.Instance} */ - #instance; - - /** @type {WebAssembly.Memory} */ - #memory; - - /** @type {(n: number) => number} */ - #alloc; - - /** - * @param {WebAssembly.Instance} instance - * @param {WebAssembly.Memory} memory - * @param {(n: number) => number} alloc - */ - constructor(instance, memory, alloc) { - this.#instance = instance; - this.#memory = memory; - this.#alloc = alloc; - } - - // ── Factory methods ──────────────────────────────────────────────────────── - - /** - * Load a compiled AffineScript `.wasm` file from the local filesystem. - * - * @param {string | URL} path - Path to the `.wasm` binary - * @param {LoadOptions} [options] - * @returns {Promise} - * - * @example - * const mod = await AffineModule.fromFile("./target/program.wasm"); - */ - static async fromFile(path, options = {}) { - const resolved = path instanceof URL ? path : new URL(path, import.meta.url); - const bytes = await Deno.readFile(resolved.pathname); - return AffineModule.fromBytes(bytes, options); - } - - /** - * Load a compiled AffineScript module from raw WASM bytes. - * - * Use this when you already have the bytes (e.g. fetched from a server, - * generated in-memory, or embedded as a Uint8Array literal). - * - * @param {Uint8Array | ArrayBuffer} bytes - Raw WASM binary - * @param {LoadOptions} [options] - * @returns {Promise} - * - * @example - * const res = await fetch("/api/compiled"); - * const mod = await AffineModule.fromBytes(await res.arrayBuffer()); - */ - static async fromBytes(bytes, options = {}) { - const runtimeImports = makeRuntimeImports(); - - const imports = { - env: { - ...runtimeImports, - ...(options.imports ?? {}), - }, - }; - - const wasmBytes = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes); - const { instance } = await WebAssembly.instantiate(wasmBytes, imports); - - const memory = instance.exports.memory; - if (!(memory instanceof WebAssembly.Memory)) { - throw new Error( - "affine-js: AffineScript WASM module must export 'memory'; " + - "ensure the module was compiled by the AffineScript compiler", - ); - } - - // Wire memory into the runtime imports so string I/O works. - runtimeImports.setMemory(memory); - - // affine_alloc: bump-allocator exposed by the runtime. - const affineAlloc = instance.exports["affine_alloc"]; - const alloc = typeof affineAlloc === "function" - ? (n) => affineAlloc(n) - : (_n) => { - throw new Error( - "affine-js: module does not export 'affine_alloc'; " + - "cannot marshal heap-allocated arguments (strings, arrays, records). " + - "Simple types (int, float, bool, unit) still work.", - ); - }; - - return new AffineModule(instance, memory, alloc); - } - - // ── Calling exports ──────────────────────────────────────────────────────── - - /** - * Call a named export, marshaling AffineValues to WASM and back. - * - * The `returnType` hint is needed because WebAssembly does not carry - * high-level type information at runtime. For simple programs that only - * use `Int`, `Float`, `Bool`, or `Unit` you can omit it and inspect the - * raw number. - * - * @param {string} name - Export name (as it appears in the AffineScript source) - * @param {CallOptions} [options] - * @param {...import("./marshal.js").AffineValue} args - Arguments - * @returns {import("./marshal.js").AffineValue} - * - * @example - * const sum = mod.call("add", {}, { kind: "int", value: 3 }, { kind: "int", value: 4 }); - */ - call(name, options = {}, ...args) { - const fn = this.#instance.exports[name]; - if (typeof fn !== "function") { - const available = this.functionExports.join(", "); - throw new Error( - `affine-js: no function export named '${name}'. ` + - `Available: [${available}]`, - ); - } - - const rawArgs = args.map((v) => marshal(v, this.#memory, this.#alloc)); - const rawResult = fn(...rawArgs); - - const returnType = options.returnType ?? "int"; - return unmarshal(rawResult, returnType, this.#memory); - } - - /** - * Invoke the top-level `main` function of the module. - * - * AffineScript programs compile their top-level `fn main() -> T / E { ... }` - * to a WASM export named `"main"`. - * - * @param {CallOptions} [options] - * @returns {import("./marshal.js").AffineValue} - */ - runMain(options = {}) { - return this.call("main", { returnType: "unit", ...options }); - } - - // ── Introspection ────────────────────────────────────────────────────────── - - /** - * Names of all exported functions. - * @returns {string[]} - */ - get functionExports() { - return Object.keys(this.#instance.exports).filter( - (k) => typeof this.#instance.exports[k] === "function", - ); - } - - /** - * Direct access to the underlying WASM memory buffer. - * Useful for advanced / low-level interop that bypasses the marshal layer. - * @type {WebAssembly.Memory} - */ - get memory() { - return this.#memory; - } - - /** - * Direct access to the underlying `WebAssembly.Instance`. - * @type {WebAssembly.Instance} - */ - get instance() { - return this.#instance; - } -} - -// ── Convenience helpers ─────────────────────────────────────────────────────── - -/** - * Load and run a compiled AffineScript program in one call. - * - * Equivalent to `AffineModule.fromFile(path, options).then(m => m.runMain())`. - * - * @param {string | URL} path - Path to the `.wasm` binary - * @param {LoadOptions} [options] - * @returns {Promise} - * - * @example - * import { run } from "@hyperpolymath/affine-js"; - * await run("./hello.wasm"); - */ -export async function run(path, options = {}) { - const mod = await AffineModule.fromFile(path, options); - return mod.runMain(options); -} - -// ── Re-exports for convenience ──────────────────────────────────────────────── - -export { int, float, bool, unit, string, some, none, ok, err, array, record } - from "./marshal.js"; -export { marshal, unmarshal } from "./marshal.js"; -export { AFFINE_TAG, AFFINE_SIZE, makeRuntimeImports } from "./runtime.js"; - -// ── JSDoc typedefs ──────────────────────────────────────────────────────────── - -/** - * @typedef {Object} LoadOptions - * @property {Record} [imports] - * Additional WASM imports to merge with the default runtime. - * Keys must match the import names generated by the AffineScript codegen - * for your declared effects. Example: `{ affine_io_println: myLog }`. - */ - -/** - * @typedef {Object} CallOptions - * @property {"int"|"float"|"bool"|"unit"|"string"|"option"|"result"|"array"|"record"} [returnType="int"] - * Hint for the unmarshaler so it knows how to interpret the raw WASM return value. - */ diff --git a/affinescript/packages/affine-js/runtime.js b/affinescript/packages/affine-js/runtime.js deleted file mode 100644 index 53d7f74..0000000 --- a/affinescript/packages/affine-js/runtime.js +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-js: AffineScript WASM runtime support -// -// Provides the host-side imports that AffineScript WASM modules expect. -// AffineScript programs declare algebraic effects (e.g. `effect IO { ... }`) -// and compile to WASM modules that import the handlers from the host. The -// host is responsible for providing implementations of every declared effect -// operation. -// -// The defaults below implement `IO` (print / println / read_line) using the -// Deno standard streams. Pass a custom `imports` object to `AffineModule` -// to override handlers or to implement user-defined effects. -// -// Memory layout constants (AffineScript 0.1.x WASM codegen): -// -// TAG_NONE = 0x00 (Option::None sentinel, also Unit sentinel) -// TAG_SOME = 0x01 (Option::Some(v) — followed by payload) -// TAG_OK = 0x02 (Result::Ok(v) — followed by payload) -// TAG_ERR = 0x03 (Result::Err(e) — followed by payload) -// -// String layout in linear memory: -// [length: i32 LE][...utf-8 bytes] -// -// Array layout in linear memory: -// [length: i32 LE][...elements (element-size bytes each)] -// -// Record layout: field values stored consecutively in declaration order, -// each aligned to its natural size (4B for i32, 8B for f64). -// -// These constants should be kept in sync with lib/codegen.ml and -// lib/codegen_gc.ml as the compiler evolves. - -/** Tag byte values used in AffineScript's WASM memory encoding. */ -export const AFFINE_TAG = Object.freeze({ - /** Unit / Option::None sentinel */ - NONE: 0x00, - /** Option::Some payload header */ - SOME: 0x01, - /** Result::Ok payload header */ - OK: 0x02, - /** Result::Err payload header */ - ERR: 0x03, -}); - -/** Byte size of each WASM value category. */ -export const AFFINE_SIZE = Object.freeze({ - INT: 4, // i32 - FLOAT: 8, // f64 - PTR: 4, // pointer into linear memory (i32) - TAG: 4, // tag word prefix (i32) - LEN: 4, // length prefix (i32) -}); - -/** - * Build the default host import object for AffineScript WASM modules. - * - * AffineScript's `effect IO` compiles to WASM imports under the `env` - * namespace. This function returns a set of implementations that write to - * Deno's stdout/stderr and read from stdin. - * - * @param {WebAssembly.Memory} [memory] - Provided lazily because the memory - * object is not available until after instantiation. The host should call - * `setMemory(mem)` on the returned object after calling - * `WebAssembly.instantiate`. Alternatively, use `AffineModule.fromBytes` - * which handles this automatically. - * @returns {AffineRuntimeImports} - */ -export function makeRuntimeImports(memory = null) { - let _memory = memory; - - /** Read a UTF-8 string from WASM linear memory given a pointer. */ - function readString(ptr) { - if (!_memory) throw new Error("affine-js: memory not yet set"); - const view = new DataView(_memory.buffer); - const len = view.getInt32(ptr, /* littleEndian = */ true); - const bytes = new Uint8Array(_memory.buffer, ptr + AFFINE_SIZE.LEN, len); - return new TextDecoder().decode(bytes); - } - - /** - * Write a JS string into WASM linear memory starting at `ptr`. - * Caller must ensure there is sufficient space. - * @returns {number} Number of bytes written (including length prefix). - */ - function writeString(ptr, str) { - if (!_memory) throw new Error("affine-js: memory not yet set"); - const encoded = new TextEncoder().encode(str); - const view = new DataView(_memory.buffer); - view.setInt32(ptr, encoded.byteLength, true); - new Uint8Array(_memory.buffer).set(encoded, ptr + AFFINE_SIZE.LEN); - return AFFINE_SIZE.LEN + encoded.byteLength; - } - - const imports = { - /** - * Accept the instantiated memory from the caller. - * @param {WebAssembly.Memory} mem - */ - setMemory(mem) { - _memory = mem; - }, - - // ── IO effect operations ────────────────────────────────────────────── - - /** `IO.print(s: String)` — write to stdout without newline */ - affine_io_print(strPtr) { - Deno.stdout.writeSync(new TextEncoder().encode(readString(strPtr))); - }, - - /** `IO.println(s: String)` — write to stdout with trailing newline */ - affine_io_println(strPtr) { - Deno.stdout.writeSync( - new TextEncoder().encode(readString(strPtr) + "\n"), - ); - }, - - /** `IO.read_line() -> String` — read one line from stdin, return WASM pointer */ - affine_io_read_line(outPtr) { - const buf = new Uint8Array(4096); - const n = Deno.stdin.readSync(buf); - const line = new TextDecoder().decode(buf.subarray(0, n ?? 0)).trimEnd(); - writeString(outPtr, line); - return outPtr; - }, - - // ── Panic / abort ──────────────────────────────────────────────────── - - /** Called by the runtime when an unreachable code path is reached. */ - affine_panic(msgPtr) { - const msg = _memory ? readString(msgPtr) : "(memory not available)"; - throw new Error(`AffineScript panic: ${msg}`); - }, - - /** Called by the runtime when an unhandled effect is invoked. */ - affine_unhandled_effect(namePtr) { - const name = _memory ? readString(namePtr) : "(memory not available)"; - throw new Error( - `AffineScript unhandled effect: '${name}' — provide a handler in the imports object`, - ); - }, - }; - - return imports; -} - -/** - * @typedef {ReturnType} AffineRuntimeImports - */ diff --git a/affinescript/packages/affine-js/types.d.affine b/affinescript/packages/affine-js/types.d.affine deleted file mode 100644 index cac779b..0000000 --- a/affinescript/packages/affine-js/types.d.affine +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Ported via Harvard Engine mechanical processor - -module types.d; - -// TODO(#84): Complete semantic implementation - -/* === ORIGINAL TYPESCRIPT CONTEXT === -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-js: TypeScript declarations -// -// These are ambient type declarations for the affine-js package. -// They allow TypeScript projects to consume affine-js with full type safety -// without requiring TypeScript source code in this package. -// -// Usage in a TypeScript/tsconfig project: -// import { AffineModule, run, int, string } from "@hyperpolymath/affine-js"; - -// ── AffineValue discriminated union ────────────────────────────────────────── - -export type AffineInt = { kind: "int"; value: number }; -export type AffineFloat = { kind: "float"; value: number }; -export type AffineBool = { kind: "bool"; value: boolean }; -export type AffineUnit = { kind: "unit" }; -export type AffineString = { kind: "string"; value: string }; -export type AffineSome = { kind: "some"; value: AffineValue }; -export type AffineNone = { kind: "none" }; -export type AffineOk = { kind: "ok"; value: AffineValue }; -export type AffineErr = { kind: "err"; value: AffineValue }; -export type AffineArray = { kind: "array"; elements: AffineValue[] }; -export type AffineRecord = { kind: "record"; fields: Record }; - -export type AffineValue = - | AffineInt - | AffineFloat - | AffineBool - | AffineUnit - | AffineString - | AffineSome - | AffineNone - | AffineOk - | AffineErr - | AffineArray - | AffineRecord; - -// ── Return type hint ────────────────────────────────────────────────────────── - -export type AffineValueType = - | "int" - | "float" - | "bool" - | "unit" - | "string" - | "option" - | "result" - | "array" - | "record"; - -// ── LoadOptions ─────────────────────────────────────────────────────────────── - -export interface LoadOptions { - /** - * Extra host imports merged with the default AffineScript runtime. - * - * Keys must match the import names the AffineScript codegen emits for your - * declared effects. The naming convention is `affine__`, e.g. - * `affine_io_println` for the `println` operation of the `IO` effect. - * - * @example - * { imports: { affine_io_println: (ptr: number) => console.log(readString(ptr)) } } - */ - imports?: Record number | void>; -} - -// ── CallOptions ─────────────────────────────────────────────────────────────── - -export interface CallOptions extends LoadOptions { - /** - * Type hint for the return value unmarshaler. - * - * Because WASM erases high-level type information at the binary level, - * the host must be told how to interpret raw return values. - * @default "int" - */ - returnType?: AffineValueType; -} - -// ── AffineModule ────────────────────────────────────────────────────────────── - -export declare class AffineModule { - /** - * Load a compiled AffineScript `.wasm` file from the local filesystem. - */ - static fromFile(path: string | URL, options?: LoadOptions): Promise; - - /** - * Load a compiled AffineScript module from raw WASM bytes. - */ - static fromBytes(bytes: Uint8Array | ArrayBuffer, options?: LoadOptions): Promise; - - /** - * Call a named export by name. - */ - call(name: string, options?: CallOptions, ...args: AffineValue[]): AffineValue; - - /** - * Invoke the top-level `main` function. - */ - runMain(options?: CallOptions): AffineValue; - - /** Names of all exported functions. */ - readonly functionExports: string[]; - - /** Underlying WASM linear memory. */ - readonly memory: WebAssembly.Memory; - - /** Underlying `WebAssembly.Instance`. */ - readonly instance: WebAssembly.Instance; -} - -// ── Top-level functions ─────────────────────────────────────────────────────── - -/** Load and run a compiled AffineScript program in one call. */ -export declare function run(path: string | URL, options?: LoadOptions): Promise; - -/** Marshal an AffineValue to a raw WASM operand. */ -export declare function marshal( - value: AffineValue, - memory: WebAssembly.Memory, - alloc: (n: number) => number, -): number; - -/** Unmarshal a raw WASM operand to an AffineValue. */ -export declare function unmarshal( - raw: number, - valueType: AffineValueType, - memory: WebAssembly.Memory, -): AffineValue; - -// ── AffineValue constructors ────────────────────────────────────────────────── - -export declare function int(value: number): AffineInt; -export declare function float(value: number): AffineFloat; -export declare function bool(value: boolean): AffineBool; -export declare function unit(): AffineUnit; -export declare function string(value: string): AffineString; -export declare function some(value: AffineValue): AffineSome; -export declare function none(): AffineNone; -export declare function ok(value: AffineValue): AffineOk; -export declare function err(value: AffineValue): AffineErr; -export declare function array(elements: AffineValue[]): AffineArray; -export declare function record(fields: Record): AffineRecord; - -// ── Runtime constants ───────────────────────────────────────────────────────── - -export declare const AFFINE_TAG: Readonly<{ NONE: 0; SOME: 1; OK: 2; ERR: 3 }>; -export declare const AFFINE_SIZE: Readonly<{ INT: 4; FLOAT: 8; PTR: 4; TAG: 4; LEN: 4 }>; - -==================================== */ diff --git a/affinescript/packages/affine-res/README.adoc b/affinescript/packages/affine-res/README.adoc deleted file mode 100644 index 35b7104..0000000 --- a/affinescript/packages/affine-res/README.adoc +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= affine-res -:toc: -:icons: font - -ReScript bindings for AffineScript WASM modules. - -Wraps `@hyperpolymath/affine-js` with idiomatic ReScript types, exhaustive -pattern matching on `AffineScriptValue.t`, and typed call shortcuts that -extract native ReScript values without manual type hints. - -== Installation - -[source,json] ----- -// rescript.json — add to bs-dependencies -{ - "bs-dependencies": ["affine-res"] -} ----- - -== Quick start - -[source,rescript] ----- -open AffineRes - -let main = async () => { - // Load a compiled AffineScript program: - let mod = await AffineScript.fromFile("./calculator.wasm") - - // Typed call — returns a ReScript int directly: - let sum = mod->AffineScript.callInt("add", [Value.Int(3), Value.Int(4)]) - Console.log(sum) // 7 - - // Generic call with pattern matching: - let result = mod->AffineScript.call("safe_divide", ~returnType="result", - [Value.Int(10), Value.Int(0)]) - switch result { - | Ok(Int(n)) => Console.log(`Result: ${Int.toString(n)}`) - | Err(String(msg)) => Console.error(`Error: ${msg}`) - | _ => Console.error("Unexpected shape") - } -} ----- - -== AffineScriptValue.t - -All values crossing the WASM boundary use the `AffineScriptValue.t` variant: - -[source,rescript] ----- -type t = - | Int(int) - | Float(float) - | Bool(bool) - | Unit - | String(string) - | Some(t) - | None - | Ok(t) - | Err(t) - | Array(array) - | Record(Js.Dict.t) ----- - -Exhaustive pattern matching is enforced by the ReScript compiler — you can -never forget to handle an `Err` case. - -== Typed shortcuts - -[cols="2,2"] -|=== -| Function | Returns - -| `callInt(mod, name, args)` -| `int` - -| `callFloat(mod, name, args)` -| `float` - -| `callBool(mod, name, args)` -| `bool` - -| `callString(mod, name, args)` -| `string` - -| `callOption(mod, name, args)` -| `option` - -| `callOk(mod, name, args)` -| `AffineScriptValue.t` (raises on Err) -|=== - -== License - -MPL-2.0. -See link:../../LICENSE[LICENSE] at the repository root. diff --git a/affinescript/packages/affine-res/src/AffineScript.affine b/affinescript/packages/affine-res/src/AffineScript.affine deleted file mode 100644 index 74db16a..0000000 --- a/affinescript/packages/affine-res/src/AffineScript.affine +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// Ported via Harvard Engine bulk-processor - -module AffineScript; - -// TODO(#84): Complete semantic implementation diff --git a/affinescript/packages/affine-res/src/AffineScript.resi b/affinescript/packages/affine-res/src/AffineScript.resi deleted file mode 100644 index 2bd3b08..0000000 --- a/affinescript/packages/affine-res/src/AffineScript.resi +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// AffineScript.resi: Public interface for the AffineScript ReScript bindings - -open AffineScriptValue - -/** An instantiated AffineScript WASM module. */ -type t - -/** Load a compiled `.wasm` file. */ -let fromFile: string => promise - -/** Load a compiled module from raw bytes. */ -let fromBytes: Js.TypedArray2.Uint8Array.t => promise - -/** Call a named export, returning a generic AffineScriptValue. */ -let call: (t, string, ~returnType: string=?, array) => t - -/** Run the top-level `main` export. */ -let runMain: t => t - -/** Names of all exported functions. */ -let functionExports: t => array - -/** Call and extract result as int. Raises on wrong type. */ -let callInt: (t, string, array) => int - -/** Call and extract result as float. Raises on wrong type. */ -let callFloat: (t, string, array) => float - -/** Call and extract result as bool. Raises on wrong type. */ -let callBool: (t, string, array) => bool - -/** Call and extract result as string. Raises on wrong type. */ -let callString: (t, string, array) => string - -/** Call and return as Belt option. Raises if not Option type. */ -let callOption: (t, string, array) => option - -/** Call and unwrap Ok. Raises on Err with error message. */ -let callOk: (t, string, array) => AffineScriptValue.t diff --git a/affinescript/packages/affine-res/src/AffineScriptValue.affine b/affinescript/packages/affine-res/src/AffineScriptValue.affine deleted file mode 100644 index 9ce23e4..0000000 --- a/affinescript/packages/affine-res/src/AffineScriptValue.affine +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell -// Ported via Harvard Engine bulk-processor - -module AffineScriptValue; - -// TODO(#84): Complete semantic implementation diff --git a/affinescript/packages/affine-ts/README.adoc b/affinescript/packages/affine-ts/README.adoc deleted file mode 100644 index d838df1..0000000 --- a/affinescript/packages/affine-ts/README.adoc +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= affine-ts -:toc: -:icons: font - -TypeScript-ergonomic wrapper around `@hyperpolymath/affine-js`. - -Adds typed call helpers (`callInt`, `callString`, `callResult`, …) and -narrowing predicates (`isOk`, `isSome`, `expectOk`, …) so TypeScript -call sites read naturally without manual `returnType` hints. - -NOTE: This package is implemented in JavaScript + JSDoc with `.d.ts` -declarations. No TypeScript source files are used. TypeScript type safety -comes from the declaration files, not from a TypeScript compiler step. - -== Installation - -[source,json] ----- -// deno.json -{ - "imports": { - "@hyperpolymath/affine-ts": "jsr:@hyperpolymath/affine-ts@0.1.0" - } -} ----- - -== Quick start - -[source,javascript] ----- -import { AffineModule, callInt, callString, callResult, expectOk, int } - from "@hyperpolymath/affine-ts"; - -const mod = await AffineModule.fromFile("./calculator.wasm"); - -// Typed helpers — no returnType hint needed: -const sum = callInt(mod, "add", int(3), int(4)); // { kind: "int", value: 7 } -const label = callString(mod, "format_result", sum); // { kind: "string", value: "7" } -const res = callResult(mod, "safe_divide", int(10), int(0)); - -// Narrowing: -if (isErr(res)) { - console.error("Division failed:", res.value); -} else { - console.log("Result:", expectOk(res)); -} ----- - -== API - -All exports from `@hyperpolymath/affine-js` are re-exported verbatim. - -Additional exports: - -[cols="1,2"] -|=== -| Export | Description - -| `callInt(mod, name, ...args)` -| Call and coerce return to Int - -| `callFloat(mod, name, ...args)` -| Call and coerce return to Float - -| `callBool(mod, name, ...args)` -| Call and coerce return to Bool - -| `callString(mod, name, ...args)` -| Call and coerce return to String - -| `callOption(mod, name, ...args)` -| Call and coerce return to Option - -| `callResult(mod, name, ...args)` -| Call and coerce return to Result - -| `expectSome(value)` -| Assert Some, extract payload; throws on None - -| `expectOk(value)` -| Assert Ok, extract payload; throws on Err with message - -| `isNone(value)` -| Type-narrowing predicate - -| `isSome(value)` -| Type-narrowing predicate - -| `isOk(value)` -| Type-narrowing predicate - -| `isErr(value)` -| Type-narrowing predicate -|=== - -== License - -MPL-2.0. -See link:../../LICENSE[LICENSE] at the repository root. diff --git a/affinescript/packages/affine-ts/mod.js b/affinescript/packages/affine-ts/mod.js deleted file mode 100644 index ef5c937..0000000 --- a/affinescript/packages/affine-ts/mod.js +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-ts: TypeScript-oriented wrapper for affine-js -// -// This package re-exports everything from @hyperpolymath/affine-js and adds -// TypeScript-ergonomic helpers that make it easy to write typed call sites -// without manual `returnType` hints. -// -// Note on TypeScript: this package is implemented in JavaScript with JSDoc -// annotations and ambient `.d.ts` declarations. No TypeScript source files -// are used. TypeScript projects consume the package via the declaration -// files; the runtime is plain JavaScript. - -export * from "@hyperpolymath/affine-js"; -import { - AffineModule as _AffineModule, - run as _run, - unmarshal, - marshal, - int, - float, - bool, - unit, - string, - some, - none, - ok, - err, - array, - record, -} from "@hyperpolymath/affine-js"; - -// ── Typed call helpers ──────────────────────────────────────────────────────── -// -// These helpers wrap AffineModule.call with a fixed `returnType` hint so -// call sites read as typed calls rather than configuration objects. - -/** - * Call an exported function and interpret the result as an Int. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {{ kind: "int"; value: number }} - */ -export function callInt(mod, name, ...args) { - return /** @type {{ kind: "int"; value: number }} */ ( - mod.call(name, { returnType: "int" }, ...args) - ); -} - -/** - * Call an exported function and interpret the result as a Float. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {{ kind: "float"; value: number }} - */ -export function callFloat(mod, name, ...args) { - return /** @type {{ kind: "float"; value: number }} */ ( - mod.call(name, { returnType: "float" }, ...args) - ); -} - -/** - * Call an exported function and interpret the result as a Bool. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {{ kind: "bool"; value: boolean }} - */ -export function callBool(mod, name, ...args) { - return /** @type {{ kind: "bool"; value: boolean }} */ ( - mod.call(name, { returnType: "bool" }, ...args) - ); -} - -/** - * Call an exported function and interpret the result as a String. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {{ kind: "string"; value: string }} - */ -export function callString(mod, name, ...args) { - return /** @type {{ kind: "string"; value: string }} */ ( - mod.call(name, { returnType: "string" }, ...args) - ); -} - -/** - * Call an exported function and interpret the result as an Option. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {import("@hyperpolymath/affine-js").AffineSome | import("@hyperpolymath/affine-js").AffineNone} - */ -export function callOption(mod, name, ...args) { - return mod.call(name, { returnType: "option" }, ...args); -} - -/** - * Call an exported function and interpret the result as a Result. - * - * @param {_AffineModule} mod - * @param {string} name - * @param {...import("@hyperpolymath/affine-js").AffineValue} args - * @returns {import("@hyperpolymath/affine-js").AffineOk | import("@hyperpolymath/affine-js").AffineErr} - */ -export function callResult(mod, name, ...args) { - return mod.call(name, { returnType: "result" }, ...args); -} - -// ── Narrowing helpers ───────────────────────────────────────────────────────── - -/** - * Assert that an AffineValue is Some and extract the payload. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {import("@hyperpolymath/affine-js").AffineValue} - */ -export function expectSome(value) { - if (value.kind !== "some") { - throw new Error(`affine-ts: expected Some, got ${value.kind}`); - } - return value.value; -} - -/** - * Assert that an AffineValue is Ok and extract the payload. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {import("@hyperpolymath/affine-js").AffineValue} - */ -export function expectOk(value) { - if (value.kind !== "ok") { - const msg = value.kind === "err" && value.value.kind === "string" - ? value.value.value - : value.kind; - throw new Error(`affine-ts: expected Ok, got Err(${msg})`); - } - return value.value; -} - -/** - * Check if an AffineValue is None. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {boolean} - */ -export function isNone(value) { - return value.kind === "none"; -} - -/** - * Check if an AffineValue is Some. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {value is import("@hyperpolymath/affine-js").AffineSome} - */ -export function isSome(value) { - return value.kind === "some"; -} - -/** - * Check if an AffineValue is Ok. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {value is import("@hyperpolymath/affine-js").AffineOk} - */ -export function isOk(value) { - return value.kind === "ok"; -} - -/** - * Check if an AffineValue is Err. - * - * @param {import("@hyperpolymath/affine-js").AffineValue} value - * @returns {value is import("@hyperpolymath/affine-js").AffineErr} - */ -export function isErr(value) { - return value.kind === "err"; -} diff --git a/affinescript/packages/affine-ts/types.d.affine b/affinescript/packages/affine-ts/types.d.affine deleted file mode 100644 index 65b43d5..0000000 --- a/affinescript/packages/affine-ts/types.d.affine +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Ported via Harvard Engine mechanical processor - -module types.d; - -// TODO(#84): Complete semantic implementation - -/* === ORIGINAL TYPESCRIPT CONTEXT === -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// -// affine-ts: Enhanced TypeScript declarations -// -// Re-exports everything from affine-js and adds affine-ts typed helpers. - -export * from "@hyperpolymath/affine-js"; - -import type { - AffineModule, - AffineValue, - AffineSome, - AffineNone, - AffineOk, - AffineErr, - AffineInt, - AffineFloat, - AffineBool, - AffineString, -} from "@hyperpolymath/affine-js"; - -// ── Typed call helpers ──────────────────────────────────────────────────────── - -/** Call an export and coerce the return value to Int. */ -export declare function callInt(mod: AffineModule, name: string, ...args: AffineValue[]): AffineInt; - -/** Call an export and coerce the return value to Float. */ -export declare function callFloat(mod: AffineModule, name: string, ...args: AffineValue[]): AffineFloat; - -/** Call an export and coerce the return value to Bool. */ -export declare function callBool(mod: AffineModule, name: string, ...args: AffineValue[]): AffineBool; - -/** Call an export and coerce the return value to String. */ -export declare function callString(mod: AffineModule, name: string, ...args: AffineValue[]): AffineString; - -/** Call an export and coerce the return value to Option. */ -export declare function callOption(mod: AffineModule, name: string, ...args: AffineValue[]): AffineSome | AffineNone; - -/** Call an export and coerce the return value to Result. */ -export declare function callResult(mod: AffineModule, name: string, ...args: AffineValue[]): AffineOk | AffineErr; - -// ── Narrowing helpers ───────────────────────────────────────────────────────── - -/** Assert Some and extract payload; throws on None. */ -export declare function expectSome(value: AffineValue): AffineValue; - -/** Assert Ok and extract payload; throws on Err with message. */ -export declare function expectOk(value: AffineValue): AffineValue; - -/** Type-narrowing predicate: is this value None? */ -export declare function isNone(value: AffineValue): value is AffineNone; - -/** Type-narrowing predicate: is this value Some? */ -export declare function isSome(value: AffineValue): value is AffineSome; - -/** Type-narrowing predicate: is this value Ok? */ -export declare function isOk(value: AffineValue): value is AffineOk; - -/** Type-narrowing predicate: is this value Err? */ -export declare function isErr(value: AffineValue): value is AffineErr; - -==================================== */ diff --git a/affinescript/panic-attack.toml b/affinescript/panic-attack.toml deleted file mode 100644 index a19eda2..0000000 --- a/affinescript/panic-attack.toml +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# panic-attack.toml — AffineScript security scan configuration - -[project] -name = "affinescript" -language = "ocaml" - -[scan] -# Directories to scan -include = ["lib", "bin", "test"] -# Directories to skip -exclude = [ - "_build", - ".git", - "distributions/rattlescript/target", - "docs", -] - -[rules] -# OCaml-specific dangerous patterns -dangerous_patterns = [ - { name = "no-obj-magic", pattern = "Obj\\.magic", severity = "critical" }, - { name = "no-unsafe-string", pattern = "Bytes\\.unsafe_from_string|String\\.unsafe_get", severity = "high" }, - { name = "no-todo-panic", pattern = "failwith.*TODO|assert false", severity = "medium" }, - { name = "no-raw-ignore", pattern = "ignore \\(", severity = "low" }, -] - -[false-positives] -# Known acceptable patterns that panic-attack may flag -# unsafe_blocks: getUnsafe in test fixtures is legitimate (bounds-checked by surrounding logic) diff --git a/affinescript/run_wasm.js b/affinescript/run_wasm.js deleted file mode 100644 index da99dde..0000000 --- a/affinescript/run_wasm.js +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// Simple WASM test runner for Node.js -const fs = require('fs'); -const path = require('path'); - -const wasmFile = process.argv[2] || 'out.wasm'; -const functionName = process.argv[3] || 'main'; - -const wasmBuffer = fs.readFileSync(wasmFile); - -WebAssembly.instantiate(wasmBuffer).then(result => { - const exports = result.instance.exports; - - if (typeof exports[functionName] === 'function') { - const returnValue = exports[functionName](); - console.log(`${functionName}() returned: ${returnValue}`); - process.exit(0); // Success regardless of return value - } else { - console.error(`Function ${functionName} not found in exports`); - process.exit(1); - } -}).catch(err => { - console.error('Error:', err); - process.exit(1); -}); diff --git a/affinescript/runtime/Cargo.toml b/affinescript/runtime/Cargo.toml deleted file mode 100644 index a6cc4ad..0000000 --- a/affinescript/runtime/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2024-2025 hyperpolymath -# Copyright 2024-2025 AffineScript Contributors - -[package] -name = "affinescript-runtime" -version = "0.1.0" -edition = "2021" -authors = ["AffineScript Contributors"] -description = "Runtime library for AffineScript, compiled to WebAssembly" -license = "MIT OR AGPL-3.0-or-later" -repository = "https://github.com/hyperpolymath/affinescript" -keywords = ["affinescript", "runtime", "wasm"] -categories = ["wasm", "no-std"] - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = ["std"] -std = [] -# Enable small garbage collector for cyclic ω-quantity data -gc = [] -# Enable WASI support -wasi = ["dep:wasi"] - -[dependencies] -# Small allocator for WASM (talc replaces unmaintained wee_alloc) -talc = { version = "4", optional = true, default-features = false } - -# WASI bindings (optional) -wasi = { version = "0.11", optional = true } - -[dev-dependencies] -# Testing -wasm-bindgen-test = "0.3" - -[profile.release] -# Optimize for size in WASM -opt-level = "s" -lto = true -codegen-units = 1 -panic = "abort" - -[profile.release-speed] -inherits = "release" -opt-level = 3 diff --git a/affinescript/runtime/src/alloc.rs b/affinescript/runtime/src/alloc.rs deleted file mode 100644 index 5622eec..0000000 --- a/affinescript/runtime/src/alloc.rs +++ /dev/null @@ -1,167 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! Memory Allocator for AffineScript Runtime -//! -//! This module provides memory allocation optimized for linear/affine values. -//! Since most values are used exactly once, we can use a simple bump allocator -//! with explicit deallocation. -//! -//! # Architecture -//! -//! ```text -//! ┌─────────────────────────────────────────────────────────────┐ -//! │ WASM Linear Memory │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Stack │ Heap (bump) │ Free List │ Reserved │ -//! └─────────────────────────────────────────────────────────────┘ -//! ``` -//! -//! # Features -//! -//! - **Bump allocation**: Fast O(1) allocation for short-lived values -//! - **Free list**: Reuse deallocated blocks for longer-lived values -//! - **Size classes**: Segregated free lists for common sizes -//! - **Linear optimization**: Skip reference counting for linear values - -#[cfg(not(feature = "std"))] -use core::{alloc::Layout, ptr::NonNull}; - -#[cfg(feature = "std")] -use std::{alloc::Layout, ptr::NonNull}; - -/// Memory block header -#[repr(C)] -struct BlockHeader { - /// Size of the block (excluding header) - size: usize, - /// Flags (in_use, is_linear, etc.) - flags: u32, -} - -/// Size classes for segregated free lists -const SIZE_CLASSES: [usize; 8] = [16, 32, 64, 128, 256, 512, 1024, 2048]; - -/// Global allocator state -struct AllocatorState { - /// Start of heap - heap_start: usize, - /// Current bump pointer - bump_ptr: usize, - /// End of available heap - heap_end: usize, - /// Free lists by size class - free_lists: [Option>; 8], - /// Total allocated bytes - allocated: usize, - /// Total freed bytes - freed: usize, -} - -static mut ALLOCATOR: AllocatorState = AllocatorState { - heap_start: 0, - bump_ptr: 0, - heap_end: 0, - free_lists: [None; 8], - allocated: 0, - freed: 0, -}; - -/// Initialize the allocator -/// -/// Called once at program startup. -pub fn init() { - // TODO(#84): Phase 6 implementation - // - [ ] Query WASM memory size - // - [ ] Set up heap region - // - [ ] Initialize free lists - // - [ ] Set up guard pages (if available) -} - -/// Allocate memory -/// -/// # Arguments -/// -/// * `size` - Number of bytes to allocate -/// * `align` - Required alignment (must be power of 2) -/// -/// # Returns -/// -/// Pointer to allocated memory, or null on failure -#[no_mangle] -pub extern "C" fn allocate(size: usize, align: usize) -> *mut u8 { - // TODO(#84): Phase 6 implementation - // - [ ] Check free list for matching size class - // - [ ] Fall back to bump allocation - // - [ ] Handle out-of-memory (grow memory or fail) - // - [ ] Track allocation statistics - - core::ptr::null_mut() -} - -/// Deallocate memory -/// -/// # Arguments -/// -/// * `ptr` - Pointer previously returned by `allocate` -/// * `size` - Size of the allocation -/// * `align` - Alignment of the allocation -/// -/// # Safety -/// -/// The pointer must have been allocated by this allocator and not yet freed. -#[no_mangle] -pub unsafe extern "C" fn deallocate(ptr: *mut u8, size: usize, align: usize) { - // TODO(#84): Phase 6 implementation - // - [ ] Validate pointer is in heap range - // - [ ] Add to appropriate free list - // - [ ] Coalesce adjacent free blocks (optional) - // - [ ] Track deallocation statistics -} - -/// Reallocate memory -/// -/// # Arguments -/// -/// * `ptr` - Pointer previously returned by `allocate` -/// * `old_size` - Current size of the allocation -/// * `new_size` - Desired new size -/// * `align` - Alignment requirement -/// -/// # Returns -/// -/// Pointer to reallocated memory (may be different from input) -#[no_mangle] -pub unsafe extern "C" fn reallocate( - ptr: *mut u8, - old_size: usize, - new_size: usize, - align: usize, -) -> *mut u8 { - // TODO(#84): Phase 6 implementation - // - [ ] If shrinking, just update size - // - [ ] If growing and space available, extend in place - // - [ ] Otherwise allocate new block and copy - - core::ptr::null_mut() -} - -/// Get allocation statistics -#[no_mangle] -pub extern "C" fn alloc_stats() -> (usize, usize, usize) { - unsafe { - ( - ALLOCATOR.allocated, - ALLOCATOR.freed, - ALLOCATOR.allocated - ALLOCATOR.freed, - ) - } -} - -// TODO(#84): Phase 6 implementation -// - [ ] Implement size class selection -// - [ ] Implement free list management -// - [ ] Add memory growth support -// - [ ] Add allocation tracking for debugging -// - [ ] Optimize for common patterns (small allocations, LIFO) diff --git a/affinescript/runtime/src/effects.rs b/affinescript/runtime/src/effects.rs deleted file mode 100644 index 27c5381..0000000 --- a/affinescript/runtime/src/effects.rs +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! Effect Handling Runtime for AffineScript -//! -//! This module implements the runtime support for algebraic effects, -//! using evidence-passing compilation (Koka-style). -//! -//! # Architecture -//! -//! ```text -//! ┌─────────────────────────────────────────────────────────────┐ -//! │ Handler Stack │ -//! ├─────────────────────────────────────────────────────────────┤ -//! │ Handler N │ Handler N-1 │ ... │ Handler 1 │ Base │ -//! └─────────────────────────────────────────────────────────────┘ -//! │ │ │ -//! ▼ ▼ ▼ -//! ┌──────────┐ ┌──────────┐ ┌──────────┐ -//! │ Evidence │ │ Evidence │ │ Evidence │ -//! │ (ops) │ │ (ops) │ │ (ops) │ -//! └──────────┘ └──────────┘ └──────────┘ -//! ``` -//! -//! # Evidence Passing -//! -//! Each effect operation receives an "evidence" parameter that contains: -//! - Pointer to the handler function for that operation -//! - Captured environment from the handler -//! - Continuation management data - -#[cfg(not(feature = "std"))] -use core::ptr::NonNull; - -#[cfg(feature = "std")] -use std::ptr::NonNull; - -/// Evidence for an effect operation -/// -/// This is passed to effectful functions and contains the handler -/// to invoke when an operation is performed. -#[repr(C)] -pub struct Evidence { - /// Pointer to the handler function - pub handler: *const (), - /// Captured environment - pub env: *mut (), - /// Parent evidence (for nested handlers) - pub parent: *mut Evidence, - /// Handler frame pointer - pub frame: *mut HandlerFrame, -} - -impl Evidence { - /// Create new evidence - pub fn new(handler: *const (), env: *mut (), parent: *mut Evidence) -> Self { - Evidence { - handler, - env, - parent, - frame: core::ptr::null_mut(), - } - } -} - -/// Handler frame on the stack -/// -/// Tracks the state needed to resume a continuation. -#[repr(C)] -pub struct HandlerFrame { - /// Saved stack pointer - pub sp: *mut u8, - /// Saved frame pointer - pub fp: *mut u8, - /// Return address - pub ra: *const (), - /// Effect signature being handled - pub effect_id: u32, - /// Whether this is a one-shot or multi-shot handler - pub is_linear: bool, - /// Parent frame - pub parent: *mut HandlerFrame, -} - -/// Continuation representation -/// -/// Captures the state needed to resume execution after an effect operation. -#[repr(C)] -pub struct Continuation { - /// Saved execution state - pub frame: HandlerFrame, - /// Captured stack segment (for multi-shot) - pub stack: Option>, - /// Stack segment size - pub stack_size: usize, - /// Whether this continuation has been used - pub used: bool, -} - -/// Handler for an effect -/// -/// Contains the implementation of each operation. -#[repr(C)] -pub struct Handler { - /// Operation implementations (function pointers) - pub operations: *const *const (), - /// Number of operations - pub num_ops: usize, - /// Return clause - pub return_fn: *const (), - /// Captured environment - pub env: *mut (), -} - -/// Global handler stack -struct HandlerStack { - /// Top of stack - top: *mut HandlerFrame, - /// Stack of installed handlers - handlers: [*mut Handler; 64], - /// Number of installed handlers - count: usize, -} - -static mut HANDLER_STACK: HandlerStack = HandlerStack { - top: core::ptr::null_mut(), - handlers: [core::ptr::null_mut(); 64], - count: 0, -}; - -/// Initialize the effect system -pub fn init() { - // TODO(#84): Phase 6 implementation - // - [ ] Set up initial handler frame - // - [ ] Install default handlers for built-in effects - // - [ ] Initialize continuation pool -} - -/// Install a handler -/// -/// # Arguments -/// -/// * `handler` - The handler to install -/// * `evidence` - Evidence to populate -/// -/// # Returns -/// -/// Opaque handle for uninstalling -#[no_mangle] -pub extern "C" fn install_handler(handler: *mut Handler, evidence: *mut Evidence) -> u32 { - // TODO(#84): Phase 6 implementation - // - [ ] Push handler onto stack - // - [ ] Set up evidence - // - [ ] Return handle - - 0 -} - -/// Uninstall a handler -/// -/// # Arguments -/// -/// * `handle` - Handle returned by `install_handler` -#[no_mangle] -pub extern "C" fn uninstall_handler(handle: u32) { - // TODO(#84): Phase 6 implementation - // - [ ] Pop handler from stack - // - [ ] Restore previous evidence -} - -/// Perform an effect operation -/// -/// # Arguments -/// -/// * `evidence` - Evidence for the effect -/// * `op_index` - Index of the operation to perform -/// * `arg` - Argument to the operation -/// -/// # Returns -/// -/// Result of the operation -#[no_mangle] -pub extern "C" fn perform(evidence: *mut Evidence, op_index: u32, arg: *mut ()) -> *mut () { - // TODO(#84): Phase 6 implementation - // - [ ] Look up handler in evidence - // - [ ] Create continuation - // - [ ] Call handler with continuation - - core::ptr::null_mut() -} - -/// Resume a continuation -/// -/// # Arguments -/// -/// * `k` - The continuation to resume -/// * `value` - Value to pass to the continuation -/// -/// # Returns -/// -/// Result of resuming -#[no_mangle] -pub extern "C" fn resume(k: *mut Continuation, value: *mut ()) -> *mut () { - // TODO(#84): Phase 6 implementation - // - [ ] Check if continuation is linear and already used - // - [ ] Restore execution state - // - [ ] Jump to continuation point - - core::ptr::null_mut() -} - -/// Abort an effect (for one-shot handlers) -/// -/// # Arguments -/// -/// * `evidence` - Evidence for the effect -/// * `value` - Value to return -#[no_mangle] -pub extern "C" fn abort_effect(evidence: *mut Evidence, value: *mut ()) -> ! { - // TODO(#84): Phase 6 implementation - // - [ ] Unwind to handler frame - // - [ ] Call return clause - - loop {} -} - -// TODO(#84): Phase 6 implementation -// - [ ] Implement evidence passing transform in codegen -// - [ ] Implement handler frame management -// - [ ] Implement one-shot continuation optimization -// - [ ] Implement multi-shot continuation copying -// - [ ] Add effect row tracking at runtime (for debugging) -// - [ ] Implement tail-resumptive optimization diff --git a/affinescript/runtime/src/ffi.rs b/affinescript/runtime/src/ffi.rs deleted file mode 100644 index a10e4f6..0000000 --- a/affinescript/runtime/src/ffi.rs +++ /dev/null @@ -1,278 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! Foreign Function Interface for AffineScript Runtime -//! -//! This module provides the FFI layer between AffineScript and host environments. -//! It handles: -//! - Type conversions between AffineScript and host types -//! - String interop -//! - Callback management -//! - Host function imports -//! -//! # Supported Hosts -//! -//! - **JavaScript**: Via wasm-bindgen or direct imports -//! - **WASI**: Standard WASI interfaces -//! - **Native**: For testing and tooling - -#[cfg(not(feature = "std"))] -use core::slice; - -#[cfg(feature = "std")] -use std::slice; - -/// String representation for FFI -/// -/// AffineScript strings are UTF-8, length-prefixed. -#[repr(C)] -pub struct FfiString { - /// Pointer to string data - pub ptr: *const u8, - /// Length in bytes - pub len: usize, -} - -impl FfiString { - /// Create from raw parts - pub const fn new(ptr: *const u8, len: usize) -> Self { - FfiString { ptr, len } - } - - /// Get as byte slice - pub unsafe fn as_bytes(&self) -> &[u8] { - slice::from_raw_parts(self.ptr, self.len) - } - - /// Get as str (unchecked) - pub unsafe fn as_str(&self) -> &str { - core::str::from_utf8_unchecked(self.as_bytes()) - } -} - -/// Array representation for FFI -#[repr(C)] -pub struct FfiArray { - /// Pointer to array data - pub ptr: *const T, - /// Number of elements - pub len: usize, -} - -/// Result type for FFI operations -#[repr(C)] -pub struct FfiResult { - /// Success value (if is_ok is true) - pub value: T, - /// Error message (if is_ok is false) - pub error: FfiString, - /// Whether operation succeeded - pub is_ok: bool, -} - -/// Callback type for host functions -pub type HostCallback = extern "C" fn(*const (), *mut ()) -> *mut (); - -/// Host function registry -struct HostRegistry { - /// Registered callbacks - callbacks: [Option; 256], - /// Number of registered callbacks - count: usize, -} - -static mut HOST_REGISTRY: HostRegistry = HostRegistry { - callbacks: [None; 256], - count: 0, -}; - -/// Initialize FFI layer -pub fn init() { - // TODO(#84): Phase 6 implementation - // - [ ] Set up host function table - // - [ ] Initialize string interop - // - [ ] Register built-in imports -} - -/// Register a host callback -/// -/// # Arguments -/// -/// * `id` - Unique identifier for the callback -/// * `callback` - The callback function -/// -/// # Returns -/// -/// True if registration succeeded -#[no_mangle] -pub extern "C" fn register_host_callback(id: u32, callback: HostCallback) -> bool { - unsafe { - if (id as usize) < HOST_REGISTRY.callbacks.len() { - HOST_REGISTRY.callbacks[id as usize] = Some(callback); - HOST_REGISTRY.count += 1; - true - } else { - false - } - } -} - -/// Call a host function -/// -/// # Arguments -/// -/// * `id` - Callback identifier -/// * `arg` - Argument to pass -/// -/// # Returns -/// -/// Result from host function -#[no_mangle] -pub extern "C" fn call_host(id: u32, arg: *const ()) -> *mut () { - unsafe { - if let Some(callback) = HOST_REGISTRY.callbacks.get(id as usize).and_then(|c| *c) { - callback(arg, core::ptr::null_mut()) - } else { - core::ptr::null_mut() - } - } -} - -// ============================================================================ -// String operations -// ============================================================================ - -/// Allocate a string buffer -#[no_mangle] -pub extern "C" fn string_alloc(len: usize) -> *mut u8 { - crate::alloc::allocate(len, 1) -} - -/// Free a string buffer -#[no_mangle] -pub unsafe extern "C" fn string_free(ptr: *mut u8, len: usize) { - crate::alloc::deallocate(ptr, len, 1) -} - -/// Copy string to host -#[no_mangle] -pub extern "C" fn string_to_host(s: FfiString) -> *const u8 { - s.ptr -} - -// ============================================================================ -// JavaScript interop (wasm-bindgen compatible) -// ============================================================================ - -#[cfg(target_arch = "wasm32")] -mod js { - use super::*; - - extern "C" { - // These would be provided by wasm-bindgen or manual JS glue - - /// Log to console - #[link_name = "__affinescript_console_log"] - pub fn console_log(msg: *const u8, len: usize); - - /// Get current time in milliseconds - #[link_name = "__affinescript_now"] - pub fn now() -> f64; - - /// Call JavaScript function - #[link_name = "__affinescript_js_call"] - pub fn js_call(func_id: u32, arg: *const (), arg_len: usize) -> *mut (); - } - - /// Print to console - #[no_mangle] - pub extern "C" fn print(s: FfiString) { - unsafe { - console_log(s.ptr, s.len); - } - } -} - -// ============================================================================ -// WASI support -// ============================================================================ - -#[cfg(feature = "wasi")] -mod wasi_ffi { - use super::*; - - /// Write to stdout - #[no_mangle] - pub extern "C" fn wasi_print(s: FfiString) { - // TODO(#84): Phase 6 implementation - // - [ ] Use fd_write to stdout - } - - /// Read from stdin - #[no_mangle] - pub extern "C" fn wasi_read_line() -> FfiString { - // TODO(#84): Phase 6 implementation - // - [ ] Use fd_read from stdin - FfiString::new(core::ptr::null(), 0) - } - - /// Get environment variable - #[no_mangle] - pub extern "C" fn wasi_getenv(name: FfiString) -> FfiString { - // TODO(#84): Phase 6 implementation - // - [ ] Use environ_get - FfiString::new(core::ptr::null(), 0) - } - - /// Get command line arguments - #[no_mangle] - pub extern "C" fn wasi_args() -> FfiArray { - // TODO(#84): Phase 6 implementation - // - [ ] Use args_get - FfiArray { - ptr: core::ptr::null(), - len: 0, - } - } -} - -// ============================================================================ -// Type conversions -// ============================================================================ - -/// Convert AffineScript Int to host i64 -#[no_mangle] -pub extern "C" fn int_to_i64(value: *const ()) -> i64 { - // TODO(#84): Phase 6 implementation - // AffineScript Ints may be arbitrary precision - 0 -} - -/// Convert host i64 to AffineScript Int -#[no_mangle] -pub extern "C" fn i64_to_int(value: i64) -> *mut () { - // TODO(#84): Phase 6 implementation - core::ptr::null_mut() -} - -/// Convert AffineScript Float to host f64 -#[no_mangle] -pub extern "C" fn float_to_f64(value: *const ()) -> f64 { - // TODO(#84): Phase 6 implementation - 0.0 -} - -/// Convert host f64 to AffineScript Float -#[no_mangle] -pub extern "C" fn f64_to_float(value: f64) -> *mut () { - // TODO(#84): Phase 6 implementation - core::ptr::null_mut() -} - -// TODO(#84): Phase 6 implementation -// - [ ] Implement wasm-bindgen integration -// - [ ] Add JSON serialization for complex types -// - [ ] Implement async callback support -// - [ ] Add TypeScript type generation -// - [ ] Implement Component Model interface types (future) diff --git a/affinescript/runtime/src/gc.rs b/affinescript/runtime/src/gc.rs deleted file mode 100644 index 8a9a048..0000000 --- a/affinescript/runtime/src/gc.rs +++ /dev/null @@ -1,283 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! Optional Garbage Collector for AffineScript Runtime -//! -//! This module provides a simple mark-sweep garbage collector for -//! cyclic data structures with ω (unrestricted) quantity. -//! -//! # When GC is Needed -//! -//! Most AffineScript data is linear or affine and doesn't need GC. -//! However, ω-quantity data (unrestricted/shareable) can form cycles: -//! -//! ```affinescript -//! type Node = { -//! value: Int, -//! ω next: Option[Node] // Can be shared, may form cycles -//! } -//! ``` -//! -//! # Algorithm -//! -//! Simple mark-sweep: -//! 1. Mark: Trace from roots, mark reachable objects -//! 2. Sweep: Free unmarked objects -//! -//! # Performance -//! -//! - Collection is triggered when: -//! - Allocation fails and memory is low -//! - Explicit `gc::collect()` call -//! - Threshold of ω allocations reached -//! -//! - This GC is intentionally simple; most memory is managed -//! via ownership and doesn't need GC. - -#[cfg(not(feature = "std"))] -use core::ptr::NonNull; - -#[cfg(feature = "std")] -use std::ptr::NonNull; - -/// GC object header -/// -/// Prepended to all GC-managed allocations. -#[repr(C)] -pub struct GcHeader { - /// Mark bit (set during marking phase) - pub marked: bool, - /// Object size (excluding header) - pub size: u32, - /// Type tag (for tracing) - pub type_tag: u32, - /// Next object in allocation list - pub next: *mut GcHeader, -} - -/// GC-managed pointer -/// -/// Smart pointer for garbage-collected values. -#[repr(transparent)] -pub struct Gc { - ptr: NonNull, - _marker: core::marker::PhantomData, -} - -impl Gc { - /// Allocate a new GC-managed value - pub fn new(value: T) -> Self { - // TODO(#84): Phase 6 implementation - // - [ ] Allocate space for header + value - // - [ ] Initialize header - // - [ ] Register in allocation list - // - [ ] Store value - - unimplemented!("GC allocation not yet implemented") - } -} - -/// Mutable GC cell (interior mutability) -pub struct GcCell { - inner: Gc>, -} - -/// GC statistics -#[derive(Default)] -pub struct GcStats { - /// Total collections performed - pub collections: usize, - /// Objects currently alive - pub live_objects: usize, - /// Bytes currently allocated - pub live_bytes: usize, - /// Objects freed in last collection - pub last_freed: usize, -} - -/// Global GC state -struct GcState { - /// Head of allocation list - alloc_list: *mut GcHeader, - /// Number of allocations since last GC - alloc_count: usize, - /// Threshold to trigger collection - threshold: usize, - /// Statistics - stats: GcStats, - /// Root set - roots: [*mut GcHeader; 256], - /// Number of roots - root_count: usize, -} - -static mut GC_STATE: GcState = GcState { - alloc_list: core::ptr::null_mut(), - alloc_count: 0, - threshold: 1000, - stats: GcStats { - collections: 0, - live_objects: 0, - live_bytes: 0, - last_freed: 0, - }, - roots: [core::ptr::null_mut(); 256], - root_count: 0, -}; - -/// Initialize the garbage collector -pub fn init() { - // TODO(#84): Phase 6 implementation - // - [ ] Set up allocation list - // - [ ] Initialize root set - // - [ ] Set threshold based on available memory -} - -/// Perform garbage collection -#[no_mangle] -pub extern "C" fn collect() { - unsafe { - GC_STATE.stats.collections += 1; - - // Mark phase - mark_from_roots(); - - // Sweep phase - sweep(); - - // Reset allocation counter - GC_STATE.alloc_count = 0; - } -} - -/// Mark phase: trace from roots -unsafe fn mark_from_roots() { - // TODO(#84): Phase 6 implementation - // - [ ] Mark all roots - // - [ ] Recursively mark reachable objects - // - [ ] Handle cycles (already marked = skip) -} - -/// Sweep phase: free unmarked objects -unsafe fn sweep() { - // TODO(#84): Phase 6 implementation - // - [ ] Walk allocation list - // - [ ] Free unmarked objects - // - [ ] Clear marks on surviving objects - // - [ ] Update statistics -} - -/// Register a root -/// -/// Roots are objects that should not be collected even if -/// not reachable from other GC objects. -#[no_mangle] -pub extern "C" fn gc_add_root(obj: *mut GcHeader) { - unsafe { - if GC_STATE.root_count < GC_STATE.roots.len() { - GC_STATE.roots[GC_STATE.root_count] = obj; - GC_STATE.root_count += 1; - } - } -} - -/// Unregister a root -#[no_mangle] -pub extern "C" fn gc_remove_root(obj: *mut GcHeader) { - unsafe { - for i in 0..GC_STATE.root_count { - if GC_STATE.roots[i] == obj { - // Swap with last and decrease count - GC_STATE.roots[i] = GC_STATE.roots[GC_STATE.root_count - 1]; - GC_STATE.root_count -= 1; - break; - } - } - } -} - -/// Allocate GC-managed memory -#[no_mangle] -pub extern "C" fn gc_alloc(size: usize, type_tag: u32) -> *mut GcHeader { - // Check if we should collect first - unsafe { - if GC_STATE.alloc_count >= GC_STATE.threshold { - collect(); - } - } - - // Allocate header + data (with overflow check) - let total_size = match size.checked_add(core::mem::size_of::()) { - Some(s) => s, - None => return core::ptr::null_mut(), // Size overflow - }; - let ptr = crate::alloc::allocate(total_size, core::mem::align_of::()); - - if ptr.is_null() { - // Try collecting and retry - collect(); - let ptr = crate::alloc::allocate(total_size, core::mem::align_of::()); - if ptr.is_null() { - return core::ptr::null_mut(); - } - } - - // Initialize header - let header = ptr as *mut GcHeader; - unsafe { - (*header).marked = false; - (*header).size = size as u32; - (*header).type_tag = type_tag; - - // Add to allocation list - (*header).next = GC_STATE.alloc_list; - GC_STATE.alloc_list = header; - GC_STATE.alloc_count += 1; - GC_STATE.stats.live_objects += 1; - GC_STATE.stats.live_bytes += total_size; - } - - header -} - -/// Get data pointer from header -#[no_mangle] -pub extern "C" fn gc_data(header: *mut GcHeader) -> *mut u8 { - if header.is_null() { - return core::ptr::null_mut(); - } - unsafe { header.add(1) as *mut u8 } -} - -/// Get GC statistics -#[no_mangle] -pub extern "C" fn gc_stats() -> GcStats { - unsafe { GC_STATE.stats } -} - -/// Force a collection if above threshold -#[no_mangle] -pub extern "C" fn gc_maybe_collect() { - unsafe { - if GC_STATE.alloc_count >= GC_STATE.threshold { - collect(); - } - } -} - -/// Set collection threshold -#[no_mangle] -pub extern "C" fn gc_set_threshold(threshold: usize) { - unsafe { - GC_STATE.threshold = threshold; - } -} - -// TODO(#84): Phase 6 implementation -// - [ ] Implement proper mark phase with type-based tracing -// - [ ] Implement sweep with proper memory deallocation -// - [ ] Add write barrier for generational GC (optional) -// - [ ] Add incremental collection (optional) -// - [ ] Add finalization support -// - [ ] Integrate with effect system for GC-safe points diff --git a/affinescript/runtime/src/lib.rs b/affinescript/runtime/src/lib.rs deleted file mode 100644 index d740ac7..0000000 --- a/affinescript/runtime/src/lib.rs +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! AffineScript Runtime Library -//! -//! This crate provides the runtime support for AffineScript programs -//! compiled to WebAssembly. -//! -//! # Features -//! -//! - `std`: Enable standard library support (default) -//! - `gc`: Enable garbage collector for cyclic data -//! - `wasi`: Enable WASI support for CLI programs -//! -//! # Modules -//! -//! - [`alloc`]: Memory allocation -//! - [`effects`]: Effect handling runtime -//! - [`panic`]: Panic handling -//! - [`ffi`]: Foreign function interface - -#![cfg_attr(not(feature = "std"), no_std)] -#![warn(missing_docs)] - -#[cfg(not(feature = "std"))] -extern crate alloc as std_alloc; - -pub mod alloc; -pub mod effects; -pub mod ffi; -pub mod panic; - -#[cfg(feature = "gc")] -pub mod gc; - -/// Re-exports for generated code -pub mod prelude { - pub use crate::alloc::{allocate, deallocate}; - pub use crate::effects::{resume, Evidence, Handler}; - - #[cfg(feature = "gc")] - pub use crate::gc::{Gc, GcCell}; -} - -/// Runtime initialization -/// -/// Called at the start of every AffineScript program. -#[no_mangle] -pub extern "C" fn __affinescript_init() { - // Initialize allocator - alloc::init(); - - // Initialize panic handler - panic::init(); - - // Initialize effect system - effects::init(); -} - -/// Runtime cleanup -/// -/// Called at the end of every AffineScript program. -#[no_mangle] -pub extern "C" fn __affinescript_cleanup() { - #[cfg(feature = "gc")] - gc::collect(); -} - -// TODO(#84): Phase 6 implementation -// - [ ] Memory allocator optimized for linear values -// - [ ] Effect evidence passing runtime -// - [ ] Handler frame management -// - [ ] Continuation allocation/deallocation -// - [ ] WASI integration -// - [ ] JavaScript interop via wasm-bindgen diff --git a/affinescript/runtime/src/panic.rs b/affinescript/runtime/src/panic.rs deleted file mode 100644 index 1957a9a..0000000 --- a/affinescript/runtime/src/panic.rs +++ /dev/null @@ -1,243 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -//! Panic Handling for AffineScript Runtime -//! -//! This module provides panic and error handling infrastructure. -//! Since AffineScript compiles to WASM, panics need special handling. -//! -//! # Panic Strategy -//! -//! 1. **Abort**: Immediately terminate execution (default for WASM) -//! 2. **Trap**: Trigger WASM trap instruction -//! 3. **Unwind**: Stack unwinding (requires exception handling proposal) -//! -//! # Error Reporting -//! -//! Errors are reported to the host via: -//! - WASI stderr (if available) -//! - Host-provided callback -//! - WASM trap with error code - -#[cfg(not(feature = "std"))] -use core::fmt::{self, Write}; - -#[cfg(feature = "std")] -use std::fmt::{self, Write}; - -/// Panic information -#[repr(C)] -pub struct PanicInfo { - /// Error message - pub message: *const u8, - /// Message length - pub message_len: usize, - /// Source file - pub file: *const u8, - /// File name length - pub file_len: usize, - /// Line number - pub line: u32, - /// Column number - pub column: u32, -} - -/// Panic hook type -pub type PanicHook = extern "C" fn(*const PanicInfo); - -/// Global panic hook -static mut PANIC_HOOK: Option = None; - -/// Error codes for WASM traps -#[repr(u32)] -pub enum ErrorCode { - /// General panic - Panic = 1, - /// Out of memory - OutOfMemory = 2, - /// Stack overflow - StackOverflow = 3, - /// Integer overflow - IntegerOverflow = 4, - /// Division by zero - DivisionByZero = 5, - /// Array index out of bounds - IndexOutOfBounds = 6, - /// Use after move (linear type violation) - UseAfterMove = 7, - /// Borrow checker violation - BorrowViolation = 8, - /// Unhandled effect - UnhandledEffect = 9, - /// Assertion failure - AssertionFailed = 10, - /// Unreachable code - Unreachable = 11, -} - -/// Initialize panic handling -pub fn init() { - // TODO(#84): Phase 6 implementation - // - [ ] Set up default panic hook - // - [ ] Register with host for error reporting - // - [ ] Initialize stack canaries (if enabled) -} - -/// Set custom panic hook -/// -/// # Arguments -/// -/// * `hook` - Function to call on panic -#[no_mangle] -pub extern "C" fn set_panic_hook(hook: PanicHook) { - unsafe { - PANIC_HOOK = Some(hook); - } -} - -/// Panic with message -/// -/// # Arguments -/// -/// * `message` - Error message -/// * `file` - Source file name -/// * `line` - Line number -/// * `column` - Column number -#[no_mangle] -pub extern "C" fn panic( - message: *const u8, - message_len: usize, - file: *const u8, - file_len: usize, - line: u32, - column: u32, -) -> ! { - let info = PanicInfo { - message, - message_len, - file, - file_len, - line, - column, - }; - - unsafe { - if let Some(hook) = PANIC_HOOK { - hook(&info); - } - } - - // Abort execution - abort() -} - -/// Panic with error code -/// -/// # Arguments -/// -/// * `code` - Error code indicating the type of error -#[no_mangle] -pub extern "C" fn panic_code(code: ErrorCode) -> ! { - // TODO(#84): Phase 6 implementation - // - [ ] Convert code to message - // - [ ] Call panic hook - // - [ ] Trap with code - - abort() -} - -/// Abort execution -#[no_mangle] -pub extern "C" fn abort() -> ! { - #[cfg(target_arch = "wasm32")] - { - core::arch::wasm32::unreachable() - } - - #[cfg(not(target_arch = "wasm32"))] - { - // For non-WASM targets (testing) - #[cfg(feature = "std")] - std::process::abort(); - - #[cfg(not(feature = "std"))] - loop {} - } -} - -/// Assert condition -/// -/// # Arguments -/// -/// * `condition` - Condition to check -/// * `message` - Error message if condition is false -#[no_mangle] -pub extern "C" fn assert( - condition: bool, - message: *const u8, - message_len: usize, - file: *const u8, - file_len: usize, - line: u32, - column: u32, -) { - if !condition { - panic(message, message_len, file, file_len, line, column) - } -} - -/// Debug assertion (only in debug builds) -#[no_mangle] -pub extern "C" fn debug_assert( - condition: bool, - message: *const u8, - message_len: usize, - file: *const u8, - file_len: usize, - line: u32, - column: u32, -) { - #[cfg(debug_assertions)] - if !condition { - panic(message, message_len, file, file_len, line, column) - } -} - -/// Report use-after-move error -#[no_mangle] -pub extern "C" fn use_after_move( - var_name: *const u8, - var_name_len: usize, - file: *const u8, - file_len: usize, - line: u32, - column: u32, -) -> ! { - // TODO(#84): Phase 6 implementation - // - [ ] Format error message - // - [ ] Include variable name - // - [ ] Call panic - - panic_code(ErrorCode::UseAfterMove) -} - -/// Report borrow violation error -#[no_mangle] -pub extern "C" fn borrow_violation( - message: *const u8, - message_len: usize, - file: *const u8, - file_len: usize, - line: u32, - column: u32, -) -> ! { - panic(message, message_len, file, file_len, line, column) -} - -// TODO(#84): Phase 6 implementation -// - [ ] Implement WASI error output -// - [ ] Add stack trace collection (if debug info available) -// - [ ] Add error code documentation -// - [ ] Implement exception handling proposal support (optional) -// - [ ] Add runtime assertions for invariants diff --git a/affinescript/site/index.adoc b/affinescript/site/index.adoc deleted file mode 100644 index 456b361..0000000 --- a/affinescript/site/index.adoc +++ /dev/null @@ -1,27 +0,0 @@ -== AffineScript - -The public web home for this project is -https://affinescript.dev[affinescript.dev]. - -The language where protocol correctness is free. - -AffineScript is the only language that combines affine types, -quantitative type theory, row polymorphism, and algebraic effects in a -single practical systems language. This intersection makes it possible -to write code where the compiler proves your protocol states, resource -lifecycles, and effect boundaries are correct – with syntax that doesn’t -feel like a theorem prover. - -=== Project Links - -* Website: https://affinescript.dev[affinescript.dev] -* Source: https://github.com/hyperpolymath/affinescript -* README: -https://github.com/hyperpolymath/affinescript/blob/main/README.adoc[project -overview] -* Docs: -https://github.com/hyperpolymath/affinescript/tree/main/docs[documentation -directory] - -This page is a lightweight landing point for the repository and will -grow with the project. diff --git a/affinescript/stdlib/Core.affine b/affinescript/stdlib/Core.affine deleted file mode 100644 index 40d7a11..0000000 --- a/affinescript/stdlib/Core.affine +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Core - Basic utilities and operations - -module Core; - -// Identity function -pub fn id[T](x: T) -> T { - return x; -} - -// Constant function -pub fn const[A, B](x: A, own y: B) -> A { - return x; -} - -/// Function composition: (f >> g)(x) = g(f(x)) -pub fn compose[A, B, C](f: A -> B, g: B -> C) -> (A -> C) { - return fn(x: A) -> C { g(f(x)) }; -} - -/// Flip argument order: flip(f)(a, b) = f(b, a) -pub fn flip[A, B, C](f: (A, B) -> C) -> ((B, A) -> C) { - return fn(b: B, a: A) -> C { f(a, b) }; -} - -/// Apply a function to a value (pipe operator) -pub fn pipe[A, B](x: A, f: A -> B) -> B { - return f(x); -} - -// Comparison operators - -pub fn min(a: Int, b: Int) -> Int { - return if a < b { return a; } else { return b; }; -} - -pub fn max(a: Int, b: Int) -> Int { - return if a > b { return a; } else { return b; }; -} - -pub fn clamp(x: Int, low: Int, high: Int) -> Int { - return max(low, min(x, high)); -} - -// Absolute value -pub fn abs(x: Int) -> Int { - return if x < 0 { return 0 - x; } else { return x; }; -} - -// Sign function -pub fn sign(x: Int) -> Int { - return if x < 0 { - return 0 - 1; - } else { - return if x > 0 { return 1; } else { return 0; }; - }; -} - -// Boolean operations - -pub fn not(x: Bool) -> Bool { - return if x { return false; } else { return true; }; -} - -pub fn and(a: Bool, b: Bool) -> Bool { - return if a { return b; } else { return false; }; -} - -pub fn or(a: Bool, b: Bool) -> Bool { - return if a { return true; } else { return b; }; -} - -pub fn xor(a: Bool, b: Bool) -> Bool { - return if a { return not(b); } else { return b; }; -} diff --git a/affinescript/stdlib/Math.affine b/affinescript/stdlib/Math.affine deleted file mode 100644 index 833e36c..0000000 --- a/affinescript/stdlib/Math.affine +++ /dev/null @@ -1,111 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Math - Mathematical functions - -module Math; - -// Constants (as functions since const declarations not yet supported) -pub fn pi() -> Float { - return 3.141592653589793; -} - -pub fn e() -> Float { - return 2.718281828459045; -} - -pub fn tau() -> Float { - return 6.283185307179586; -} - -// Integer operations - -pub fn abs(x: Int) -> Int { - return if x < 0 { return 0 - x; } else { return x; }; -} - -pub fn min(a: Int, b: Int) -> Int { - return if a < b { return a; } else { return b; }; -} - -pub fn max(a: Int, b: Int) -> Int { - return if a > b { return a; } else { return b; }; -} - -pub fn clamp(x: Int, low: Int, high: Int) -> Int { - return max(low, min(x, high)); -} - -// Power (integer exponentiation) -pub fn pow(base: Int, exp: Int) -> Int { - return if exp == 0 { - return 1; - } else { - return if exp == 1 { - return base; - } else { - let half = pow(base, exp / 2); - return if exp % 2 == 0 { - return half * half; - } else { - return base * half * half; - }; - }; - }; -} - -// GCD using Euclid's algorithm -pub fn gcd(a: Int, b: Int) -> Int { - let a_abs = abs(a); - let b_abs = abs(b); - return if b_abs == 0 { - return a_abs; - } else { - return gcd(b_abs, a_abs % b_abs); - }; -} - -// LCM -pub fn lcm(a: Int, b: Int) -> Int { - return if a == 0 { - return 0; - } else { - return if b == 0 { - return 0; - } else { - return abs(a * b) / gcd(a, b); - }; - }; -} - -// Factorial -pub fn factorial(n: Int) -> Int { - return if n <= 1 { - return 1; - } else { - return n * factorial(n - 1); - }; -} - -// Fibonacci -pub fn fib(n: Int) -> Int { - return if n <= 1 { - return n; - } else { - return fib(n - 1) + fib(n - 2); - }; -} - -// Check if number is even -pub fn is_even(n: Int) -> Bool { - return n % 2 == 0; -} - -// Check if number is odd -pub fn is_odd(n: Int) -> Bool { - return n % 2 != 0; -} - -// Note: Float operations and transcendental functions (sin, cos, sqrt, etc.) -// would require FFI or builtin implementation -// Currently disabled due to type checker limitations with Float comparisons diff --git a/affinescript/stdlib/Option.affine b/affinescript/stdlib/Option.affine deleted file mode 100644 index 5c4e36e..0000000 --- a/affinescript/stdlib/Option.affine +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Option - Optional value utilities - -module Option; - -// Option type is built-in as: -// type Option[T] = Some(T) | None; - -// Check if option has a value -pub fn is_some[T](opt: Option[T]) -> Bool { - return match opt { - Some(_) => { return true; }, - None => { return false; } - }; -} - -// Check if option is empty -pub fn is_none[T](opt: Option[T]) -> Bool { - return match opt { - Some(_) => { return false; }, - None => { return true; } - }; -} - -// Get value or panic -pub fn unwrap[T](own opt: Option[T]) -> T { - return match opt { - Some(v) => { return v; }, - None => { - // TODO(#84): panic - return 0; // Placeholder - } - }; -} - -// Get value or default -pub fn unwrap_or[T](own opt: Option[T], default: T) -> T { - return match opt { - Some(v) => { return v; }, - None => { return default; } - }; -} - -// Get value or compute default -pub fn unwrap_or_else[T](own opt: Option[T], f: fn() -> T) -> T { - return match opt { - Some(v) => { return v; }, - None => { return f(); } - }; -} - -// Map over value -pub fn map[T, U](own opt: Option[T], f: fn(T) -> U) -> Option[U] { - return match opt { - Some(v) => { return Some(f(v)); }, - None => { return None; } - }; -} - -// Map or return default -pub fn map_or[T, U](own opt: Option[T], default: U, f: fn(T) -> U) -> U { - return match opt { - Some(v) => { return f(v); }, - None => { return default; } - }; -} - -// Chain operations (flatMap/bind) -pub fn and_then[T, U](own opt: Option[T], f: fn(T) -> Option[U]) -> Option[U] { - return match opt { - Some(v) => { return f(v); }, - None => { return None; } - }; -} - -// Return opt if it has a value, otherwise return other -pub fn or[T](own opt: Option[T], own other: Option[T]) -> Option[T] { - return match opt { - Some(_) => { return opt; }, - None => { return other; } - }; -} - -// Return opt if it has a value, otherwise compute alternative -pub fn or_else[T](own opt: Option[T], f: fn() -> Option[T]) -> Option[T] { - return match opt { - Some(_) => { return opt; }, - None => { return f(); } - }; -} - -// Filter value by predicate -pub fn filter[T](own opt: Option[T], pred: fn(ref T) -> Bool) -> Option[T] { - return match opt { - Some(v) => { - return if pred(v) { return Some(v); } else { return None; }; - }, - None => { return None; } - }; -} - -// Convert Option to Result -pub fn ok_or[T, E](own opt: Option[T], err: E) -> Result[T, E] { - return match opt { - Some(v) => { return Ok(v); }, - None => { return Err(err); } - }; -} - -pub fn ok_or_else[T, E](own opt: Option[T], f: fn() -> E) -> Result[T, E] { - return match opt { - Some(v) => { return Ok(v); }, - None => { return Err(f()); } - }; -} diff --git a/affinescript/stdlib/README.adoc b/affinescript/stdlib/README.adoc deleted file mode 100644 index 95ff58e..0000000 --- a/affinescript/stdlib/README.adoc +++ /dev/null @@ -1,174 +0,0 @@ -== AffineScript Standard Library - -The AffineScript standard library provides essential utilities and data -structures. - -=== Modules - -==== Core - -Basic utilities and operations. - -*Functions:* - `+id[T](x: T) -> T+` - Identity function - -`+const[A, B](x: A, _y: B) -> A+` - Constant function - -`+compose[A, B, C](f, g)+` - Function composition - `+flip[A, B, C](f)+` -- Flip function arguments - `+min(a, b)+`, `+max(a, b)+`, -`+clamp(x, low, high)+` - Numeric operations - `+abs(x)+`, `+sign(x)+` - -Absolute value and sign - `+not(x)+`, `+and(a, b)+`, `+or(a, b)+`, -`+xor(a, b)+` - Boolean operations - -*Example:* - -[source,affinescript] ----- -use Core::{min, max, abs}; - -let smallest = min(10, 20); // 10 -let largest = max(10, 20); // 20 -let absolute = abs(-42); // 42 ----- - -==== Result - -Error handling utilities for `+Result[T, E]+` type. - -*Functions:* - `+is_ok(r)+`, `+is_err(r)+` - Check result status - -`+unwrap(r)+`, `+unwrap_or(r, default)+` - Extract value - -`+unwrap_err(r)+` - Extract error - `+map(r, f)+`, `+map_err(r, f)+` - -Transform value or error - `+and_then(r, f)+` - Chain operations -(flatMap) - `+ok(r)+`, `+err(r)+` - Convert to Option - -*Example:* - -[source,affinescript] ----- -use Result::{map, unwrap_or}; - -fn divide(a: Int, b: Int) -> Result[Int, String] { - return if b == 0 { - Err("division by zero") - } else { - Ok(a / b) - }; -} - -let result = divide(10, 2); -let doubled = map(result, |x| { return x * 2; }); -let value = unwrap_or(doubled, 0); // 10 ----- - -==== Option - -Optional value utilities for `+Option[T]+` type. - -*Functions:* - `+is_some(opt)+`, `+is_none(opt)+` - Check if value -exists - `+unwrap(opt)+`, `+unwrap_or(opt, default)+` - Extract value - -`+unwrap_or_else(opt, f)+` - Extract or compute default - -`+map(opt, f)+`, `+map_or(opt, default, f)+` - Transform value - -`+and_then(opt, f)+` - Chain operations (flatMap) - `+or(opt, other)+`, -`+or_else(opt, f)+` - Alternative values - `+filter(opt, pred)+` - -Filter by predicate - `+ok_or(opt, err)+`, `+ok_or_else(opt, f)+` - -Convert to Result - -*Example:* - -[source,affinescript] ----- -use Option::{map, unwrap_or}; - -fn find_positive(x: Int) -> Option[Int] { - return if x > 0 { Some(x) } else { None }; -} - -let opt = find_positive(42); -let doubled = map(opt, |x| { return x * 2; }); -let value = unwrap_or(doubled, 0); // 84 ----- - -==== Math - -Mathematical functions and constants. - -*Constants:* - `+PI+` = 3.14159… - `+E+` = 2.71828… - `+TAU+` = 6.28318… -(2π) - -*Integer Functions:* - `+abs(x)+`, `+min(a, b)+`, `+max(a, b)+`, -`+clamp(x, low, high)+` - `+pow(base, exp)+` - Integer exponentiation - -`+gcd(a, b)+`, `+lcm(a, b)+` - Greatest common divisor and least common -multiple - `+factorial(n)+` - Factorial - `+fib(n)+` - Fibonacci number -- `+is_even(n)+`, `+is_odd(n)+` - Parity checks - -*Float Functions:* - `+abs_f(x)+`, `+min_f(a, b)+`, `+max_f(a, b)+`, -`+clamp_f(x, low, high)+` - -*Example:* - -[source,affinescript] ----- -use Math::{pow, gcd, factorial}; - -let squared = pow(5, 2); // 25 -let divisor = gcd(48, 18); // 6 -let perm = factorial(5); // 120 ----- - -=== Usage - -Import modules using the `+use+` statement: - -[source,affinescript] ----- -// Import entire module -use Core; -let result = Core.abs(-10); - -// Import specific functions -use Core::{min, max}; -let smaller = min(5, 10); - -// Import with alias -use Math as M; -let circle_area = M.PI * radius * radius; ----- - -=== Built-in Types - -The standard library uses these built-in types: - -* `+Result[T, E]+` - Success (Ok) or failure (Err) -* `+Option[T]+` - Present value (Some) or absent (None) -* `+Int+` - Integer numbers -* `+Float+` - Floating-point numbers -* `+Bool+` - Boolean values (true/false) -* `+String+` - Text strings - -=== Status - -*Implemented:* - ✅ Core utilities - ✅ Result error handling - ✅ -Option optional values - ✅ Math basic functions - -*TODO:* - String manipulation functions - Array/List operations - I/O -functions (requires FFI) - Transcendental math functions (sin, cos, -sqrt, etc.) - Date/Time utilities - File system operations - -=== Contributing - -To add new stdlib functions: - -[arabic] -. Add function to appropriate module file -. Document with examples -. Update this README -. Add tests in `+tests/stdlib/+` - -=== Testing - -Test standard library functions: - -[source,bash] ----- -affinescript eval tests/stdlib/test_core.affine -affinescript eval tests/stdlib/test_result.affine -affinescript eval tests/stdlib/test_option.affine -affinescript eval tests/stdlib/test_math.affine ----- diff --git a/affinescript/stdlib/Result.affine b/affinescript/stdlib/Result.affine deleted file mode 100644 index 442797e..0000000 --- a/affinescript/stdlib/Result.affine +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Result - Error handling utilities - -module Result; - -// Result type is built-in as: -// type Result[T, E] = Ok(T) | Err(E); - -// Check if result is Ok -pub fn is_ok[T, E](r: Result[T, E]) -> Bool { - return match r { - Ok(_) => { return true; }, - Err(_) => { return false; } - }; -} - -// Check if result is Err -pub fn is_err[T, E](r: Result[T, E]) -> Bool { - return match r { - Ok(_) => { return false; }, - Err(_) => { return true; } - }; -} - -// Get value or panic -pub fn unwrap[T, E](own r: Result[T, E]) -> T { - return match r { - Ok(v) => { return v; }, - Err(_) => { - // TODO(#84): panic with error message - return 0; // Placeholder - } - }; -} - -// Get value or default -pub fn unwrap_or[T, E](own r: Result[T, E], default: T) -> T { - return match r { - Ok(v) => { return v; }, - Err(_) => { return default; } - }; -} - -// Get error or panic -pub fn unwrap_err[T, E](own r: Result[T, E]) -> E { - return match r { - Ok(_) => { - // TODO(#84): panic - return 0; // Placeholder - }, - Err(e) => { return e; } - }; -} - -// Map over Ok value -pub fn map[T, U, E](own r: Result[T, E], f: fn(T) -> U) -> Result[U, E] { - return match r { - Ok(v) => { return Ok(f(v)); }, - Err(e) => { return Err(e); } - }; -} - -// Map over Err value -pub fn map_err[T, E, F](own r: Result[T, E], f: fn(E) -> F) -> Result[T, F] { - return match r { - Ok(v) => { return Ok(v); }, - Err(e) => { return Err(f(e)); } - }; -} - -// Chain operations (flatMap/bind) -pub fn and_then[T, U, E](own r: Result[T, E], f: fn(T) -> Result[U, E]) -> Result[U, E] { - return match r { - Ok(v) => { return f(v); }, - Err(e) => { return Err(e); } - }; -} - -// Convert Result to Option -pub fn ok[T, E](own r: Result[T, E]) -> Option[T] { - return match r { - Ok(v) => { return Some(v); }, - Err(_) => { return None; } - }; -} - -pub fn err[T, E](own r: Result[T, E]) -> Option[E] { - return match r { - Ok(_) => { return None; }, - Err(e) => { return Some(e); } - }; -} diff --git a/affinescript/stdlib/collections.affine b/affinescript/stdlib/collections.affine deleted file mode 100644 index bca0cc7..0000000 --- a/affinescript/stdlib/collections.affine +++ /dev/null @@ -1,243 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Collections - Advanced list, array, and data structure operations - -// ============================================================================ -// List Operations -// ============================================================================ - -/// Reverse a list -fn reverse(list: [T]) -> [T] { - let result = []; - for x in list { - result = [x] ++ result; - } - result -} - -/// Take first n elements from list -fn take(n: Int, list: [T]) -> [T] { - if n <= 0 || len(list) == 0 { - [] - } else { - [list[0]] ++ take(n - 1, list[1:]) - } -} - -/// Drop first n elements from list -fn drop(n: Int, list: [T]) -> [T] { - if n <= 0 { - list - } else if len(list) == 0 { - [] - } else { - drop(n - 1, list[1:]) - } -} - -/// Zip two lists together -fn zip(as: [A], bs: [B]) -> [(A, B)] { - if len(as) == 0 || len(bs) == 0 { - [] - } else { - [(as[0], bs[0])] ++ zip(as[1:], bs[1:]) - } -} - -/// Unzip a list of pairs -fn unzip(pairs: [(A, B)]) -> ([A], [B]) { - let as = []; - let bs = []; - for (a, b) in pairs { - as = as ++ [a]; - bs = bs ++ [b]; - } - (as, bs) -} - -/// Find first element matching predicate -fn find(pred: T -> Bool, list: [T]) -> Option { - for x in list { - if pred(x) { - return Some(x); - } - } - None -} - -/// Check if any element matches predicate -fn any(pred: T -> Bool, list: [T]) -> Bool { - for x in list { - if pred(x) { - return true; - } - } - false -} - -/// Check if all elements match predicate -fn all(pred: T -> Bool, list: [T]) -> Bool { - for x in list { - if !pred(x) { - return false; - } - } - true -} - -/// Partition list into two lists based on predicate -fn partition(pred: T -> Bool, list: [T]) -> ([T], [T]) { - let trues = []; - let falses = []; - for x in list { - if pred(x) { - trues = trues ++ [x]; - } else { - falses = falses ++ [x]; - } - } - (trues, falses) -} - -/// Group consecutive equal elements -fn group(list: [T]) -> [[T]] { - if len(list) == 0 { - [] - } else { - let first = list[0]; - let same = filter(fn(x) => x == first, list); - let different = filter(fn(x) => x != first, list); - [same] ++ group(different) - } -} - -/// Remove duplicate elements (requires Eq) -fn unique(list: [T]) -> [T] { - if len(list) == 0 { - [] - } else { - let first = list[0]; - let rest = list[1:]; - let filtered = filter(fn(x) => x != first, rest); - [first] ++ unique(filtered) - } -} - -/// Intersperse element between all elements of list -fn intersperse(sep: T, list: [T]) -> [T] { - if len(list) <= 1 { - list - } else { - [list[0], sep] ++ intersperse(sep, list[1:]) - } -} - -/// Concatenate list of lists -fn concat(lists: [[T]]) -> [T] { - let result = []; - for list in lists { - result = result ++ list; - } - result -} - -/// Flat map (map then concat) -fn flat_map(f: A -> [B], list: [A]) -> [B] { - concat(map(f, list)) -} - -// ============================================================================ -// Array Operations -// ============================================================================ - -/// Fill array with value -fn array_fill(size: Int, value: T) -> [T] { - let arr = []; - let mut i = 0; - while i < size { - arr = arr ++ [value]; - i = i + 1; - } - arr -} - -/// Array from range -fn range(start: Int, end: Int) -> [Int] { - if start >= end { - [] - } else { - [start] ++ range(start + 1, end) - } -} - -/// Array from range with step -fn range_step(start: Int, end: Int, step: Int) -> [Int] { - if step <= 0 || start >= end { - [] - } else { - [start] ++ range_step(start + step, end, step) - } -} - -// ============================================================================ -// Sorting and Searching -// ============================================================================ - -/// Sort list (requires Ord) -fn sort(list: [T]) -> [T] { - if len(list) <= 1 { - list - } else { - let pivot = list[0]; - let rest = list[1:]; - let smaller = filter(fn(x) => x < pivot, rest); - let greater = filter(fn(x) => x >= pivot, rest); - sort(smaller) ++ [pivot] ++ sort(greater) - } -} - -/// Binary search in sorted array -fn binary_search(target: T, arr: [T]) -> Option { - binary_search_helper(target, arr, 0, len(arr)) -} - -fn binary_search_helper(target: T, arr: [T], low: Int, high: Int) -> Option { - if low >= high { - None - } else { - let mid = (low + high) / 2; - let mid_val = arr[mid]; - if mid_val == target { - Some(mid) - } else if mid_val < target { - binary_search_helper(target, arr, mid + 1, high) - } else { - binary_search_helper(target, arr, low, mid) - } - } -} - -// ============================================================================ -// Set Operations (using lists) -// ============================================================================ - -/// Union of two sets -fn set_union(a: [T], b: [T]) -> [T] { - unique(a ++ b) -} - -/// Intersection of two sets -fn set_intersection(a: [T], b: [T]) -> [T] { - filter(fn(x) => any(fn(y) => x == y, b), a) -} - -/// Difference of two sets -fn set_difference(a: [T], b: [T]) -> [T] { - filter(fn(x) => !any(fn(y) => x == y, b), a) -} - -/// Check if element is in set -fn set_member(x: T, set: [T]) -> Bool { - any(fn(y) => x == y, set) -} diff --git a/affinescript/stdlib/effects.affine b/affinescript/stdlib/effects.affine deleted file mode 100644 index 835b247..0000000 --- a/affinescript/stdlib/effects.affine +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// AffineScript Standard Library - Effect Declarations - -// Core IO effect - file and console operations -effect io; - -// State effect - mutable references -effect state; - -// Exception effect - panics and error handling -effect exn; - -// Built-in IO operations -extern fn print(s: String) -> Unit / io; -extern fn println(s: String) -> Unit / io; -extern fn read_line() -> String / io; -extern fn read_file(path: String) -> String / io; -extern fn write_file(path: String, content: String) -> Unit / io; - -// Built-in State operations -extern fn ref(x: T) -> Ref / state; -extern fn get(r: Ref) -> T / state; -extern fn set(r: Ref, x: T) -> Unit / state; - -// Built-in Exception operations -extern fn panic(msg: String) -> Never / exn; -extern fn error(msg: String) -> T / exn; - -// Pure operations (no effects) -extern fn int_to_string(n: Int) -> String; -extern fn string_to_int(s: String) -> Option; -extern fn string_length(s: String) -> Int; -extern fn string_concat(s1: String, s2: String) -> String; diff --git a/affinescript/stdlib/io.affine b/affinescript/stdlib/io.affine deleted file mode 100644 index bfaaac4..0000000 --- a/affinescript/stdlib/io.affine +++ /dev/null @@ -1,272 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// AffineScript Standard Library - Input/Output -// -// Builtin functions (implemented in interpreter runtime): -// print(args...) -> () (print to stdout) -// println(args...) -> () (print with newline to stdout) -// eprint(args...) -> () (print to stderr) -// eprintln(args...) -> () (print with newline to stderr) -// read_file(path) -> Result -// write_file(path, data) -> Result<(), String> -// append_file(path, data) -> Result<(), String> -// file_exists(path) -> Bool -// is_directory(path) -> Bool -// getenv(name) -> Option -// getcwd() -> Result -// read_line() -> Result -// exit(code) -> Never -// show(value) -> String -// time_now() -> Float (CPU time in seconds) - -// ============================================================================ -// Console Output -// ============================================================================ - -// print, println, eprint, eprintln are builtins — see module header - -/// Print formatted string (simple placeholder substitution) -/// -/// Format placeholders: -/// {} — insert next argument via show() -/// -/// Example: -/// printf("Hello, {}! You are {} years old.", ["Alice", 30]) -fn printf(format: String, args: [Any]) -> () { - let flen = len(format); - let arg_idx = 0; - let i = 0; - - while i < flen { - if i + 1 < flen && string_sub(format, i, 2) == "{}" { - if arg_idx < len(args) { - print(show(args[arg_idx])); - arg_idx = arg_idx + 1; - } else { - print("{}"); - } - i = i + 2; - } else { - print(string_sub(format, i, 1)); - i = i + 1; - } - } -} - -/// Print formatted string with trailing newline -fn println_fmt(format: String, args: [Any]) -> () { - printf(format, args); - println(""); -} - -/// Print debug representation of any value -fn debug(value: T) -> () { - eprintln("DEBUG: " ++ show(value)); -} - -/// Print error with newline to stderr (convenience wrapper) -fn error(msg: String) -> () { - eprintln("[ERROR] " ++ msg); -} - -/// Print warning with newline to stderr -fn warn(msg: String) -> () { - eprintln("[WARN] " ++ msg); -} - -// ============================================================================ -// File Operations (builtins) -// ============================================================================ - -// read_file, write_file, append_file, file_exists are builtins — see module header - -/// Read file as a list of lines -fn read_lines(path: String) -> Result<[String], String> { - match read_file(path) { - Ok(content) => Ok(split(content, "\n")), - Err(msg) => Err(msg) - } -} - -/// Get file size by reading and measuring content length -/// -/// Note: this reads the entire file; a more efficient builtin would be -/// preferable for large files once the runtime supports stat(). -fn file_size(path: String) -> Result { - match read_file(path) { - Ok(content) => Ok(len(content)), - Err(msg) => Err(msg) - } -} - -// ============================================================================ -// Directory Operations -// ============================================================================ - -// is_directory is a builtin — see module header - -/// List directory contents (builtin — returns sorted entries, excluding . and ..) -extern fn list_dir(path: String) -> Result<[String], String>; - -/// Create directory with permissions 0o755 -extern fn create_dir(path: String) -> Result<(), String>; - -/// Remove an empty directory -extern fn remove_dir(path: String) -> Result<(), String>; - -// ============================================================================ -// Path Operations -// ============================================================================ - -/// Join path components with the system separator (/) -fn path_join(components: [String]) -> String { - let result = ""; - let mut first = true; - for component in components { - if first { - result = component; - first = false; - } else { - result = result ++ "/" ++ component; - } - } - result -} - -/// Extract the file extension from a path (without the leading dot) -/// -/// Returns None if no extension is found. -/// Example: path_extension("file.txt") => Some("txt") -fn path_extension(path: String) -> Option { - let plen = len(path); - let i = plen - 1; - while i >= 0 { - let ch = string_get(path, i); - if ch == '.' { - if i == plen - 1 { - // Trailing dot, no extension - return None; - } - return Some(string_sub(path, i + 1, plen - i - 1)); - } - if ch == '/' { - // Hit a directory separator before finding a dot - return None; - } - i = i - 1; - } - None -} - -/// Get the filename component from a path -/// -/// Example: path_filename("/home/user/file.txt") => "file.txt" -fn path_filename(path: String) -> String { - let plen = len(path); - if plen == 0 { - return ""; - } - let i = plen - 1; - while i >= 0 { - if string_get(path, i) == '/' { - return string_sub(path, i + 1, plen - i - 1); - } - i = i - 1; - } - // No separator found; entire path is the filename - path -} - -/// Get the directory component from a path -/// -/// Example: path_dirname("/home/user/file.txt") => "/home/user" -fn path_dirname(path: String) -> String { - let plen = len(path); - if plen == 0 { - return "."; - } - let i = plen - 1; - while i >= 0 { - if string_get(path, i) == '/' { - if i == 0 { - return "/"; - } - return string_sub(path, 0, i); - } - i = i - 1; - } - // No separator found; directory is the current directory - "." -} - -/// Get the filename without its extension (stem) -/// -/// Example: path_stem("archive.tar.gz") => "archive.tar" -fn path_stem(path: String) -> String { - let filename = path_filename(path); - let flen = len(filename); - let i = flen - 1; - while i > 0 { - if string_get(filename, i) == '.' { - return string_sub(filename, 0, i); - } - i = i - 1; - } - filename -} - -// ============================================================================ -// Process Operations -// ============================================================================ - -// getenv, getcwd, exit are builtins — see module header - -/// Set environment variable -extern fn setenv(name: String, value: String) -> Result<(), String>; - -/// Change current working directory -extern fn chdir(path: String) -> Result<(), String>; - -// ============================================================================ -// Input Operations -// ============================================================================ - -// read_line is a builtin — see module header - -/// Read all input from stdin until EOF -fn read_stdin() -> Result { - let parts = []; - let done = false; - while !done { - match read_line() { - Ok(line) => { - parts = parts ++ [line]; - }, - Err(_) => { - done = true; - } - } - } - Ok(join(parts, "\n")) -} - -/// Prompt user for input and return their response -fn prompt(message: String) -> Result { - print(message); - read_line() -} - -// ============================================================================ -// Timing -// ============================================================================ - -// time_now is a builtin — see module header - -/// Measure the wall-clock time of a function call (in seconds) -fn timed(f: () -> T) -> (T, Float) { - let start = time_now(); - let result = f(); - let elapsed = time_now() - start; - (result, elapsed) -} diff --git a/affinescript/stdlib/math.affine b/affinescript/stdlib/math.affine deleted file mode 100644 index ab636a0..0000000 --- a/affinescript/stdlib/math.affine +++ /dev/null @@ -1,368 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// AffineScript Standard Library - Mathematics -// -// Builtin functions (implemented in interpreter runtime): -// sqrt(x: Float) -> Float -// cbrt(x: Float) -> Float -// pow_float(base: Float, exp: Float) -> Float -// floor(x: Float) -> Int -// ceil(x: Float) -> Int -// round(x: Float) -> Int -// trunc(x: Float) -> Int -// sin(x: Float) -> Float -// cos(x: Float) -> Float -// tan(x: Float) -> Float -// asin(x: Float) -> Float -// acos(x: Float) -> Float -// atan(x: Float) -> Float -// atan2(y: Float, x: Float) -> Float -// exp(x: Float) -> Float -// log(x: Float) -> Float (natural logarithm) -// log10(x: Float) -> Float -// log2(x: Float) -> Float - -// ============================================================================ -// Constants -// ============================================================================ - -/// Ratio of a circle's circumference to its diameter -const PI: Float = 3.141592653589793; - -/// Euler's number, base of the natural logarithm -const E: Float = 2.718281828459045; - -/// Full-turn constant (2 * PI) -const TAU: Float = 6.283185307179586; - -/// Positive infinity sentinel (largest representable float) -const INFINITY: Float = 1.0 / 0.0; - -/// Negative infinity sentinel -const NEG_INFINITY: Float = -1.0 / 0.0; - -// ============================================================================ -// Basic arithmetic -// ============================================================================ - -/// Absolute value of an integer -fn abs(x: Int) -> Int { - if x < 0 { -x } else { x } -} - -/// Absolute value of a float -fn abs_float(x: Float) -> Float { - if x < 0.0 { -x } else { x } -} - -/// Sign of an integer: -1, 0, or 1 -fn sign(x: Int) -> Int { - if x > 0 { 1 } else if x < 0 { -1 } else { 0 } -} - -/// Sign of a float: -1, 0, or 1 -fn sign_float(x: Float) -> Int { - if x > 0.0 { 1 } else if x < 0.0 { -1 } else { 0 } -} - -// ============================================================================ -// Power and roots -// ============================================================================ - -/// Integer exponentiation via repeated squaring -fn pow(base: Int, exp: Int) -> Int { - if exp == 0 { - return 1; - } - if exp == 1 { - return base; - } - let half = pow(base, exp / 2); - if exp % 2 == 0 { - half * half - } else { - base * half * half - } -} - -/// Square of an integer -fn square(x: Int) -> Int { - x * x -} - -/// Cube of an integer -fn cube(x: Int) -> Int { - x * x * x -} - -// sqrt, cbrt, pow_float are builtins — see module header - -// ============================================================================ -// Rounding and truncation (builtins) -// ============================================================================ - -// floor, ceil, round, trunc are builtins — see module header - -/// Convert an integer to a float -fn to_float(n: Int) -> Float { - n + 0.0 -} - -/// Fractional part of a float (x - trunc(x)) -fn fract(x: Float) -> Float { - x - to_float(trunc(x)) -} - -// ============================================================================ -// Trigonometry (builtins) -// ============================================================================ - -// sin, cos, tan, asin, acos, atan, atan2 are builtins — see module header - -/// Convert degrees to radians -fn deg_to_rad(degrees: Float) -> Float { - degrees * PI / 180.0 -} - -/// Convert radians to degrees -fn rad_to_deg(radians: Float) -> Float { - radians * 180.0 / PI -} - -/// Hyperbolic sine -fn sinh(x: Float) -> Float { - (exp(x) - exp(-x)) / 2.0 -} - -/// Hyperbolic cosine -fn cosh(x: Float) -> Float { - (exp(x) + exp(-x)) / 2.0 -} - -/// Hyperbolic tangent -fn tanh(x: Float) -> Float { - sinh(x) / cosh(x) -} - -// ============================================================================ -// Logarithms and exponentials (builtins) -// ============================================================================ - -// exp, log, log10, log2 are builtins — see module header - -/// Logarithm with arbitrary base -fn log_base(base: Float, x: Float) -> Float { - log(x) / log(base) -} - -// ============================================================================ -// Comparison -// ============================================================================ - -/// Minimum of two integers -fn min_int(a: Int, b: Int) -> Int { - if a < b { a } else { b } -} - -/// Maximum of two integers -fn max_int(a: Int, b: Int) -> Int { - if a > b { a } else { b } -} - -/// Minimum of two floats -fn min_float(a: Float, b: Float) -> Float { - if a < b { a } else { b } -} - -/// Maximum of two floats -fn max_float(a: Float, b: Float) -> Float { - if a > b { a } else { b } -} - -/// Clamp an integer between min_val and max_val (inclusive) -fn clamp_int(value: Int, min_val: Int, max_val: Int) -> Int { - if value < min_val { - min_val - } else if value > max_val { - max_val - } else { - value - } -} - -/// Clamp a float between min_val and max_val (inclusive) -fn clamp_float(value: Float, min_val: Float, max_val: Float) -> Float { - if value < min_val { - min_val - } else if value > max_val { - max_val - } else { - value - } -} - -/// Linear interpolation between a and b by factor t (0.0 to 1.0) -fn lerp(a: Float, b: Float, t: Float) -> Float { - a + (b - a) * t -} - -// ============================================================================ -// Number theory -// ============================================================================ - -/// Greatest common divisor via Euclid's algorithm -fn gcd(a: Int, b: Int) -> Int { - let x = abs(a); - let y = abs(b); - - while y != 0 { - let temp = y; - y = x % y; - x = temp; - } - - x -} - -/// Least common multiple -fn lcm(a: Int, b: Int) -> Int { - if a == 0 || b == 0 { - return 0; - } - abs(a * b) / gcd(a, b) -} - -/// Check if n is even -fn is_even(n: Int) -> Bool { - n % 2 == 0 -} - -/// Check if n is odd -fn is_odd(n: Int) -> Bool { - n % 2 != 0 -} - -/// Check if n is a prime number (trial division) -fn is_prime(n: Int) -> Bool { - if n < 2 { - return false; - } - if n < 4 { - return true; - } - if n % 2 == 0 || n % 3 == 0 { - return false; - } - let i = 5; - while i * i <= n { - if n % i == 0 || n % (i + 2) == 0 { - return false; - } - i = i + 6; - } - true -} - -/// Integer division rounding towards negative infinity (floor division) -fn div_floor(a: Int, b: Int) -> Int { - let q = a / b; - if (a % b != 0) && ((a < 0) != (b < 0)) { - q - 1 - } else { - q - } -} - -/// Modulo that always returns a non-negative result -fn mod_positive(a: Int, b: Int) -> Int { - let r = a % b; - if r < 0 { - r + abs(b) - } else { - r - } -} - -// ============================================================================ -// Sequences -// ============================================================================ - -/// Factorial of n (n!) -fn factorial(n: Int) -> Int { - if n <= 1 { - 1 - } else { - n * factorial(n - 1) - } -} - -/// n-th Fibonacci number (iterative) -fn fibonacci(n: Int) -> Int { - if n <= 1 { - n - } else { - let a = 0; - let b = 1; - let i = 2; - while i <= n { - let temp = a + b; - a = b; - b = temp; - i = i + 1; - } - b - } -} - -/// Sum of first n natural numbers: 1 + 2 + ... + n -fn sum_naturals(n: Int) -> Int { - n * (n + 1) / 2 -} - -/// Sum of first n squares: 1^2 + 2^2 + ... + n^2 -fn sum_squares(n: Int) -> Int { - n * (n + 1) * (2 * n + 1) / 6 -} - -/// Binomial coefficient C(n, k) = n! / (k! * (n-k)!) -fn binomial(n: Int, k: Int) -> Int { - if k < 0 || k > n { - return 0; - } - // Use the smaller of k and n-k for efficiency - let k_eff = if k > n - k { n - k } else { k }; - let result = 1; - let i = 0; - while i < k_eff { - result = result * (n - i) / (i + 1); - i = i + 1; - } - result -} - -// ============================================================================ -// Statistics helpers -// ============================================================================ - -/// Arithmetic mean of a list of floats -fn mean(values: [Float]) -> Float { - let n = len(values); - if n == 0 { - return 0.0; - } - let total = 0.0; - for v in values { - total = total + v; - } - total / to_float(n) -} - -/// Sum of a list of floats -fn sum_float(values: [Float]) -> Float { - let total = 0.0; - for v in values { - total = total + v; - } - total -} diff --git a/affinescript/stdlib/option.affine b/affinescript/stdlib/option.affine deleted file mode 100644 index 5726281..0000000 --- a/affinescript/stdlib/option.affine +++ /dev/null @@ -1,328 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Option - Utilities for Option type - -// Option type is defined in prelude, but here are utilities - -// ============================================================================ -// Combinators -// ============================================================================ - -/// Map over Some value -fn map(f: T -> U, opt: Option) -> Option { - match opt { - Some(value) => Some(f(value)), - None => None - } -} - -/// Apply function in Option to value in Option -fn apply(f_opt: Option U>, value_opt: Option) -> Option { - match (f_opt, value_opt) { - (Some(f), Some(value)) => Some(f(value)), - _ => None - } -} - -/// Flat map (bind) over Option -fn flat_map(f: T -> Option, opt: Option) -> Option { - match opt { - Some(value) => f(value), - None => None - } -} - -/// Also known as 'and_then' -fn and_then(opt: Option, f: T -> Option) -> Option { - flat_map(f, opt) -} - -/// Or else - use alternative Option if None -fn or_else(opt: Option, alternative: Option) -> Option { - match opt { - Some(_) => opt, - None => alternative - } -} - -/// Or else computed - compute alternative only if None -fn or_else_with(opt: Option, f: () -> Option) -> Option { - match opt { - Some(_) => opt, - None => f() - } -} - -// ============================================================================ -// Queries -// ============================================================================ - -/// Check if Option is Some -fn is_some(opt: Option) -> Bool { - match opt { - Some(_) => true, - None => false - } -} - -/// Check if Option is None -fn is_none(opt: Option) -> Bool { - match opt { - Some(_) => false, - None => true - } -} - -/// Check if Option contains specific value -fn contains(opt: Option, value: T) -> Bool { - match opt { - Some(x) => x == value, - None => false - } -} - -// ============================================================================ -// Extractors -// ============================================================================ - -/// Unwrap Some value or panic -fn unwrap(opt: Option) -> T { - match opt { - Some(value) => value, - None => panic("Called unwrap on None") - } -} - -/// Unwrap with custom error message -fn expect(opt: Option, msg: String) -> T { - match opt { - Some(value) => value, - None => panic(msg) - } -} - -/// Unwrap or provide default value -fn unwrap_or(opt: Option, default: T) -> T { - match opt { - Some(value) => value, - None => default - } -} - -/// Unwrap or compute default value -fn unwrap_or_else(opt: Option, f: () -> T) -> T { - match opt { - Some(value) => value, - None => f() - } -} - -/// Get value or return from function with default -fn ok_or(opt: Option, err: E) -> Result { - match opt { - Some(value) => Ok(value), - None => Err(err) - } -} - -/// Get value or compute error -fn ok_or_else(opt: Option, f: () -> E) -> Result { - match opt { - Some(value) => Ok(value), - None => Err(f()) - } -} - -// ============================================================================ -// Filtering and Zipping -// ============================================================================ - -/// Filter Option based on predicate -fn filter(pred: T -> Bool, opt: Option) -> Option { - match opt { - Some(value) => if pred(value) { Some(value) } else { None }, - None => None - } -} - -/// Zip two Options together -fn zip(a: Option, b: Option) -> Option<(A, B)> { - match (a, b) { - (Some(x), Some(y)) => Some((x, y)), - _ => None - } -} - -/// Zip with function -fn zip_with(f: (A, B) -> C, a: Option, b: Option) -> Option { - match (a, b) { - (Some(x), Some(y)) => Some(f(x, y)), - _ => None - } -} - -/// Unzip Option of pair -fn unzip(opt: Option<(A, B)>) -> (Option, Option) { - match opt { - Some((a, b)) => (Some(a), Some(b)), - None => (None, None) - } -} - -// ============================================================================ -// Collections -// ============================================================================ - -/// Transpose Option of list to list of Option -fn transpose(opt: Option<[T]>) -> [Option] { - match opt { - Some(list) => map(fn(x) => Some(x), list), - None => [] - } -} - -/// Collect list of Options into Option of list (None on any None) -fn collect(opts: [Option]) -> Option<[T]> { - let values = []; - for opt in opts { - match opt { - Some(value) => values = values ++ [value], - None => return None - } - } - Some(values) -} - -/// Filter out Nones from list -fn cat_options(opts: [Option]) -> [T] { - let values = []; - for opt in opts { - match opt { - Some(value) => values = values ++ [value], - None => {} - } - } - values -} - -/// Map list with function returning Option, filtering Nones -fn map_filter(f: T -> Option, list: [T]) -> [U] { - let results = map(f, list); - cat_options(results) -} - -/// Find first Some in list of Options -fn first_some(opts: [Option]) -> Option { - for opt in opts { - match opt { - Some(value) => return Some(value), - None => {} - } - } - None -} - -/// Get head of list as Option -fn head(list: [T]) -> Option { - if len(list) > 0 { - Some(list[0]) - } else { - None - } -} - -/// Get tail of list as Option -fn tail(list: [T]) -> Option<[T]> { - if len(list) > 0 { - Some(list[1:]) - } else { - None - } -} - -/// Get last element of list as Option -fn last(list: [T]) -> Option { - let n = len(list); - if n > 0 { - Some(list[n - 1]) - } else { - None - } -} - -/// Get element at index as Option -fn get(list: [T], index: Int) -> Option { - if index >= 0 && index < len(list) { - Some(list[index]) - } else { - None - } -} - -// ============================================================================ -// Boolean Options -// ============================================================================ - -/// Convert bool to Option -fn bool_to_option(b: Bool) -> Option<()> { - if b { - Some(()) - } else { - None - } -} - -/// AND operation on Options (both must be Some) -fn option_and(a: Option, b: Option) -> Option<(T, U)> { - zip(a, b) -} - -/// OR operation on Options (first Some wins) -fn option_or(a: Option, b: Option) -> Option { - or_else(a, b) -} - -/// XOR operation on Options (exactly one must be Some) -fn option_xor(a: Option, b: Option) -> Option { - match (a, b) { - (Some(x), None) => Some(x), - (None, Some(y)) => Some(y), - _ => None - } -} - -// ============================================================================ -// Utility Functions -// ============================================================================ - -/// Flatten nested Option -fn flatten(opt: Option>) -> Option { - match opt { - Some(inner) => inner, - None => None - } -} - -/// Replace None with provided Option -fn replace_none(opt: Option, replacement: Option) -> Option { - or_else(opt, replacement) -} - -/// Take value from Option, leaving None -fn take(opt: &mut Option) -> Option { - let result = opt; - opt = None; - result -} - -/// Insert value into Option if None -fn get_or_insert(opt: &mut Option, value: T) -> T { - match opt { - Some(x) => x, - None => { - opt = Some(value); - value - } - } -} diff --git a/affinescript/stdlib/prelude.affine b/affinescript/stdlib/prelude.affine deleted file mode 100644 index bd0706c..0000000 --- a/affinescript/stdlib/prelude.affine +++ /dev/null @@ -1,197 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// AffineScript Standard Library - Prelude -// Common functions and utilities automatically available - -// ============================================================================ -// Option type - represents optional values -// ============================================================================ - -type Option = Some(T) | None - -fn is_some(opt: Option) -> Bool { - match opt { - Some(_) => true, - None => false - } -} - -fn is_none(opt: Option) -> Bool { - match opt { - Some(_) => false, - None => true - } -} - -fn unwrap(opt: Option) -> T { - match opt { - Some(value) => value, - None => { - println("Called unwrap on None"); - // TODO(#84): panic!() once implemented - } - } -} - -fn unwrap_or(opt: Option, default: T) -> T { - match opt { - Some(value) => value, - None => default - } -} - -// ============================================================================ -// Result type - represents success or failure -// ============================================================================ - -type Result = Ok(T) | Err(E) - -fn is_ok(res: Result) -> Bool { - match res { - Ok(_) => true, - Err(_) => false - } -} - -fn is_err(res: Result) -> Bool { - match res { - Ok(_) => false, - Err(_) => true - } -} - -fn unwrap_result(res: Result) -> T { - match res { - Ok(value) => value, - Err(_) => { - println("Called unwrap on Err"); - // TODO(#84): panic!() once implemented - } - } -} - -fn unwrap_or_result(res: Result, default: T) -> T { - match res { - Ok(value) => value, - Err(_) => default - } -} - -// ============================================================================ -// List utilities -// ============================================================================ - -fn map(arr: [T], f: T -> U) -> [U] { - let result = []; - for x in arr { - result = result ++ [f(x)]; - } - result -} - -fn filter(arr: [T], predicate: T -> Bool) -> [T] { - let result = []; - for x in arr { - if predicate(x) { - result = result ++ [x]; - } - } - result -} - -fn fold(arr: [T], init: U, f: (U, T) -> U) -> U { - let acc = init; - for x in arr { - acc = f(acc, x); - } - acc -} - -/// Conforms to aLib collection/contains spec v1.0 -fn contains(arr: [T], element: T) -> Bool { - for x in arr { - if x == element { - return true; - } - } - false -} - -fn sum(arr: [Int]) -> Int { - fold(arr, 0, |acc, x| acc + x) -} - -fn product(arr: [Int]) -> Int { - fold(arr, 1, |acc, x| acc * x) -} - -// ============================================================================ -// Comparison and ordering -// ============================================================================ - -fn min(a: Int, b: Int) -> Int { - if a < b { a } else { b } -} - -fn max(a: Int, b: Int) -> Int { - if a > b { a } else { b } -} - -fn clamp(value: Int, min_val: Int, max_val: Int) -> Int { - if value < min_val { - min_val - } else if value > max_val { - max_val - } else { - value - } -} - -// ============================================================================ -// Boolean utilities -// ============================================================================ - -fn not(b: Bool) -> Bool { - if b { false } else { true } -} - -fn all(arr: [Bool]) -> Bool { - for b in arr { - if not(b) { - return false; - } - } - true -} - -fn any(arr: [Bool]) -> Bool { - for b in arr { - if b { - return true; - } - } - false -} - -// ============================================================================ -// Range and iteration utilities -// ============================================================================ - -fn range(start: Int, end: Int) -> [Int] { - let result = []; - let i = start; - while i < end { - result = result ++ [i]; - i = i + 1; - } - result -} - -fn repeat(value: T, n: Int) -> [T] { - let result = []; - let i = 0; - while i < n { - result = result ++ [value]; - i = i + 1; - } - result -} diff --git a/affinescript/stdlib/result.affine b/affinescript/stdlib/result.affine deleted file mode 100644 index 6944ae8..0000000 --- a/affinescript/stdlib/result.affine +++ /dev/null @@ -1,244 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Result - Error handling with Result type - -// Result type is defined in prelude, but here are utilities - -// ============================================================================ -// Constructors and Conversions -// ============================================================================ - -/// Convert Option to Result -fn option_to_result(opt: Option, err: E) -> Result { - match opt { - Some(value) => Ok(value), - None => Err(err) - } -} - -/// Convert Result to Option (discarding error) -fn result_to_option(result: Result) -> Option { - match result { - Ok(value) => Some(value), - Err(_) => None - } -} - -// ============================================================================ -// Combinators -// ============================================================================ - -/// Map over Ok value -fn map_ok(f: T -> U, result: Result) -> Result { - match result { - Ok(value) => Ok(f(value)), - Err(e) => Err(e) - } -} - -/// Map over Err value -fn map_err(f: E -> F, result: Result) -> Result { - match result { - Ok(value) => Ok(value), - Err(e) => Err(f(e)) - } -} - -/// Apply function in Result to value in Result -fn apply(f_result: Result U, E>, value_result: Result) -> Result { - match (f_result, value_result) { - (Ok(f), Ok(value)) => Ok(f(value)), - (Err(e), _) => Err(e), - (_, Err(e)) => Err(e) - } -} - -/// Flat map (bind) over Result -fn flat_map(f: T -> Result, result: Result) -> Result { - match result { - Ok(value) => f(value), - Err(e) => Err(e) - } -} - -/// Also known as 'and_then' -fn and_then(result: Result, f: T -> Result) -> Result { - flat_map(f, result) -} - -/// Or else - use alternative Result on error -fn or_else(result: Result, f: E -> Result) -> Result { - match result { - Ok(value) => Ok(value), - Err(e) => f(e) - } -} - -// ============================================================================ -// Queries -// ============================================================================ - -/// Check if Result is Ok -fn is_ok(result: Result) -> Bool { - match result { - Ok(_) => true, - Err(_) => false - } -} - -/// Check if Result is Err -fn is_err(result: Result) -> Bool { - match result { - Ok(_) => false, - Err(_) => true - } -} - -// ============================================================================ -// Extractors -// ============================================================================ - -/// Unwrap Ok value or panic -fn unwrap(result: Result) -> T { - match result { - Ok(value) => value, - Err(_) => panic("Called unwrap on Err value") - } -} - -/// Unwrap Err value or panic -fn unwrap_err(result: Result) -> E { - match result { - Ok(_) => panic("Called unwrap_err on Ok value"), - Err(e) => e - } -} - -/// Unwrap with custom error message -fn expect(result: Result, msg: String) -> T { - match result { - Ok(value) => value, - Err(_) => panic(msg) - } -} - -/// Unwrap or provide default value -fn unwrap_or(result: Result, default: T) -> T { - match result { - Ok(value) => value, - Err(_) => default - } -} - -/// Unwrap or compute default value -fn unwrap_or_else(result: Result, f: E -> T) -> T { - match result { - Ok(value) => value, - Err(e) => f(e) - } -} - -// ============================================================================ -// Collections -// ============================================================================ - -/// Transpose Option of Result to Result of Option -fn transpose_opt(opt: Option>) -> Result, E> { - match opt { - Some(Ok(value)) => Ok(Some(value)), - Some(Err(e)) => Err(e), - None => Ok(None) - } -} - -/// Collect list of Results into Result of list (fails on first error) -fn collect(results: [Result]) -> Result<[T], E> { - let values = []; - for result in results { - match result { - Ok(value) => values = values ++ [value], - Err(e) => return Err(e) - } - } - Ok(values) -} - -/// Partition list of Results into successes and failures -fn partition_results(results: [Result]) -> ([T], [E]) { - let oks = []; - let errs = []; - for result in results { - match result { - Ok(value) => oks = oks ++ [value], - Err(e) => errs = errs ++ [e] - } - } - (oks, errs) -} - -/// Try to apply function to all elements, collecting errors -fn try_map(f: T -> Result, list: [T]) -> Result<[U], E> { - let results = map(f, list); - collect(results) -} - -// ============================================================================ -// Boolean Results -// ============================================================================ - -/// Convert bool to Result -fn bool_to_result(b: Bool, err: E) -> Result<(), E> { - if b { - Ok(()) - } else { - Err(err) - } -} - -/// AND operation on Results (both must be Ok) -fn result_and(a: Result, b: Result) -> Result<(T, U), E> { - match (a, b) { - (Ok(x), Ok(y)) => Ok((x, y)), - (Err(e), _) => Err(e), - (_, Err(e)) => Err(e) - } -} - -/// OR operation on Results (first Ok wins) -fn result_or(a: Result, b: Result) -> Result { - match a { - Ok(value) => Ok(value), - Err(_) => b - } -} - -// ============================================================================ -// Error Handling Patterns -// ============================================================================ - -/// Try block emulation - execute function and catch panics as Err -fn try(f: () -> T) -> Result { - // TODO(#84): Requires exception handling support - try { - Ok(f()) - } catch { - RuntimeError(msg) => Err(msg), - _ => Err("Unknown error") - } -} - -/// Retry operation n times on failure -fn retry(n: Int, f: () -> Result) -> Result { - let result = f(); - match result { - Ok(_) => result, - Err(e) => { - if n <= 1 { - Err(e) - } else { - retry(n - 1, f) - } - } - } -} diff --git a/affinescript/stdlib/string.affine b/affinescript/stdlib/string.affine deleted file mode 100644 index 58e15eb..0000000 --- a/affinescript/stdlib/string.affine +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// AffineScript Standard Library - String utilities -// -// String operations backed by interpreter builtins: -// string_get(s, idx) -> Char (character at index) -// string_sub(s, start, length) -> String (substring extraction) -// string_find(s, needle) -> Int (-1 if not found) -// to_lowercase(s) -> String (ASCII lowercase) -// to_uppercase(s) -> String (ASCII uppercase) -// trim(s) -> String (strip leading/trailing whitespace) -// int_to_string(n) -> String (integer to decimal string) -// float_to_string(f) -> String (float to string) -// parse_int(s) -> Option (decimal string to integer) -// parse_float(s) -> Option (string to float) -// char_to_int(c) -> Int (character to ASCII code point) -// int_to_char(n) -> Char (ASCII code point to character) -// show(v) -> String (any value to debug string) - -// ============================================================================ -// String inspection -// ============================================================================ - -/// Check if string is empty -fn is_empty(s: String) -> Bool { - len(s) == 0 -} - -/// Get character at index, returning None for out-of-bounds access -fn char_at(s: String, idx: Int) -> Option { - if idx >= 0 && idx < len(s) { - Some(string_get(s, idx)) - } else { - None - } -} - -/// Get the length of a string (alias for len) -/// Conforms to aLib string/length spec v1.0 -fn length(s: String) -> Int { - len(s) -} - -// ============================================================================ -// Case conversion (delegated to builtins) -// ============================================================================ - -// to_lowercase(s: String) -> String — builtin -// to_uppercase(s: String) -> String — builtin -// trim(s: String) -> String — builtin - -// ============================================================================ -// String searching -// ============================================================================ - -/// Check if string starts with the given prefix -fn starts_with(s: String, prefix: String) -> Bool { - let plen = len(prefix); - if plen > len(s) { - false - } else { - string_sub(s, 0, plen) == prefix - } -} - -/// Check if string ends with the given suffix -fn ends_with(s: String, suffix: String) -> Bool { - let slen = len(s); - let sfxlen = len(suffix); - if sfxlen > slen { - false - } else { - string_sub(s, slen - sfxlen, sfxlen) == suffix - } -} - -/// Check if string contains a substring -fn contains(s: String, substr: String) -> Bool { - string_find(s, substr) >= 0 -} - -/// Find the first index of a substring, or -1 if not found -fn index_of(s: String, substr: String) -> Int { - string_find(s, substr) -} - -// ============================================================================ -// String manipulation -// ============================================================================ - -/// Concatenate two strings -/// Conforms to aLib string/concat spec v1.0 -fn concat(a: String, b: String) -> String { - a ++ b -} - -/// Extract substring from start (inclusive) to end (exclusive) -/// Conforms to aLib string/substring spec v1.0 -fn substring(s: String, start: Int, end: Int) -> String { - let slen = len(s); - let clamped_start = if start < 0 { 0 } else if start > slen { slen } else { start }; - let clamped_end = if end < clamped_start { clamped_start } else if end > slen { slen } else { end }; - string_sub(s, clamped_start, clamped_end - clamped_start) -} - -/// Repeat a string n times -fn repeat(s: String, n: Int) -> String { - let result = ""; - let i = 0; - while i < n { - result = concat(result, s); - i = i + 1; - } - result -} - -/// Split string by a delimiter -fn split(s: String, delimiter: String) -> [String] { - let slen = len(s); - let dlen = len(delimiter); - - if dlen == 0 { - // Split into individual characters - let result = []; - let i = 0; - while i < slen { - result = result ++ [string_sub(s, i, 1)]; - i = i + 1; - } - return result; - } - - let result = []; - let current_start = 0; - let i = 0; - - while i <= slen - dlen { - if string_sub(s, i, dlen) == delimiter { - result = result ++ [string_sub(s, current_start, i - current_start)]; - current_start = i + dlen; - i = i + dlen; - } else { - i = i + 1; - } - } - - // Append the remaining tail - result = result ++ [string_sub(s, current_start, slen - current_start)]; - result -} - -/// Join an array of strings with a separator -fn join(arr: [String], separator: String) -> String { - if len(arr) == 0 { - return ""; - } - - let result = arr[0]; - let i = 1; - while i < len(arr) { - result = concat(result, concat(separator, arr[i])); - i = i + 1; - } - result -} - -/// Replace all occurrences of `from` with `to_str` in a string -fn replace(s: String, from: String, to_str: String) -> String { - join(split(s, from), to_str) -} - -/// Reverse a string -fn reverse_string(s: String) -> String { - let slen = len(s); - let result = ""; - let i = slen - 1; - while i >= 0 { - result = concat(result, string_sub(s, i, 1)); - i = i - 1; - } - result -} - -/// Pad a string on the left to reach a target length -fn pad_left(s: String, target_len: Int, pad_char: String) -> String { - let slen = len(s); - if slen >= target_len { - s - } else { - concat(repeat(pad_char, target_len - slen), s) - } -} - -/// Pad a string on the right to reach a target length -fn pad_right(s: String, target_len: Int, pad_char: String) -> String { - let slen = len(s); - if slen >= target_len { - s - } else { - concat(s, repeat(pad_char, target_len - slen)) - } -} - -// ============================================================================ -// String conversion (delegated to builtins) -// ============================================================================ - -// int_to_string(n: Int) -> String — builtin -// float_to_string(f: Float) -> String — builtin -// parse_int(s: String) -> Option — builtin -// parse_float(s: String) -> Option — builtin - -// ============================================================================ -// Character classification -// ============================================================================ - -/// Check if character is an ASCII digit (0-9) -fn is_digit(c: Char) -> Bool { - let code = char_to_int(c); - code >= 48 && code <= 57 -} - -/// Check if character is an ASCII letter (a-z, A-Z) -fn is_alpha(c: Char) -> Bool { - let code = char_to_int(c); - (code >= 65 && code <= 90) || (code >= 97 && code <= 122) -} - -/// Check if character is alphanumeric -fn is_alphanumeric(c: Char) -> Bool { - is_digit(c) || is_alpha(c) -} - -/// Check if character is ASCII whitespace (space, tab, newline, carriage return) -fn is_whitespace(c: Char) -> Bool { - let code = char_to_int(c); - code == 32 || code == 9 || code == 10 || code == 13 -} diff --git a/affinescript/stdlib/testing.affine b/affinescript/stdlib/testing.affine deleted file mode 100644 index 690e6f7..0000000 --- a/affinescript/stdlib/testing.affine +++ /dev/null @@ -1,321 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// AffineScript Standard Library - Testing -// -// Provides assertion functions, test organisation, property-based helpers, -// and lightweight benchmarking. -// -// Depends on builtins: show, panic, time_now - -// ============================================================================ -// Assertions -// ============================================================================ - -/// Assert that a condition is true; panic with message on failure -fn assert(condition: Bool, message: String) -> () { - if !condition { - panic("Assertion failed: " ++ message); - } -} - -/// Assert two values are equal -fn assert_eq(actual: T, expected: T, message: String) -> () { - if actual != expected { - panic("Assertion failed: " ++ message ++ "\n Expected: " ++ show(expected) ++ "\n Actual: " ++ show(actual)); - } -} - -/// Assert two values are not equal -fn assert_ne(actual: T, expected: T, message: String) -> () { - if actual == expected { - panic("Assertion failed: values should not be equal: " ++ message ++ "\n Both: " ++ show(actual)); - } -} - -/// Assert a Result is Ok and return the inner value -fn assert_ok(result: Result, message: String) -> T { - match result { - Ok(value) => value, - Err(e) => panic("Assertion failed: expected Ok, got Err(" ++ show(e) ++ "): " ++ message) - } -} - -/// Assert a Result is Err and return the error value -fn assert_err(result: Result, message: String) -> E { - match result { - Ok(v) => panic("Assertion failed: expected Err, got Ok(" ++ show(v) ++ "): " ++ message), - Err(e) => e - } -} - -/// Assert an Option is Some and return the inner value -fn assert_some(opt: Option, message: String) -> T { - match opt { - Some(value) => value, - None => panic("Assertion failed: expected Some, got None: " ++ message) - } -} - -/// Assert an Option is None -fn assert_none(opt: Option, message: String) -> () { - match opt { - Some(v) => panic("Assertion failed: expected None, got Some(" ++ show(v) ++ "): " ++ message), - None => {} - } -} - -// ============================================================================ -// Numeric Assertions -// ============================================================================ - -/// Assert actual < bound -fn assert_lt(actual: T, bound: T, message: String) -> () { - if !(actual < bound) { - panic("Assertion failed: " ++ show(actual) ++ " should be < " ++ show(bound) ++ ": " ++ message); - } -} - -/// Assert actual <= bound -fn assert_le(actual: T, bound: T, message: String) -> () { - if !(actual <= bound) { - panic("Assertion failed: " ++ show(actual) ++ " should be <= " ++ show(bound) ++ ": " ++ message); - } -} - -/// Assert actual > bound -fn assert_gt(actual: T, bound: T, message: String) -> () { - if !(actual > bound) { - panic("Assertion failed: " ++ show(actual) ++ " should be > " ++ show(bound) ++ ": " ++ message); - } -} - -/// Assert actual >= bound -fn assert_ge(actual: T, bound: T, message: String) -> () { - if !(actual >= bound) { - panic("Assertion failed: " ++ show(actual) ++ " should be >= " ++ show(bound) ++ ": " ++ message); - } -} - -/// Assert two floats are equal within an epsilon tolerance -fn assert_float_eq(actual: Float, expected: Float, epsilon: Float, message: String) -> () { - let diff = if actual > expected { actual - expected } else { expected - actual }; - if diff > epsilon { - panic("Assertion failed: " ++ show(actual) ++ " != " ++ show(expected) ++ " (epsilon " ++ show(epsilon) ++ "): " ++ message); - } -} - -// ============================================================================ -// Collection Assertions -// ============================================================================ - -/// Assert a list is empty -fn assert_empty(list: [T], message: String) -> () { - if len(list) != 0 { - panic("Assertion failed: list should be empty (length " ++ show(len(list)) ++ "): " ++ message); - } -} - -/// Assert a list is not empty -fn assert_not_empty(list: [T], message: String) -> () { - if len(list) == 0 { - panic("Assertion failed: list should not be empty: " ++ message); - } -} - -/// Assert list has expected length -fn assert_length(list: [T], expected_len: Int, message: String) -> () { - let actual_len = len(list); - if actual_len != expected_len { - panic("Assertion failed: expected length " ++ show(expected_len) ++ ", got " ++ show(actual_len) ++ ": " ++ message); - } -} - -/// Assert list contains a specific element -fn assert_contains(list: [T], element: T, message: String) -> () { - let found = false; - for x in list { - if x == element { - found = true; - } - } - if !found { - panic("Assertion failed: list should contain " ++ show(element) ++ ": " ++ message); - } -} - -/// Assert a string contains a substring -fn assert_str_contains(haystack: String, needle: String, message: String) -> () { - if string_find(haystack, needle) < 0 { - panic("Assertion failed: \"" ++ haystack ++ "\" should contain \"" ++ needle ++ "\": " ++ message); - } -} - -// ============================================================================ -// Test Organisation -// ============================================================================ - -/// Result of a single test execution -type TestResult = Pass | Fail(String) - -/// A named test case -type TestCase = { - name: String, - test: () -> TestResult -} - -/// A named collection of test cases -type TestSuite = { - name: String, - tests: [TestCase] -} - -/// Run a single test case, catching panics -fn run_test(test: TestCase) -> TestResult { - println(" Running: " ++ test.name); - try { - let result = test.test(); - match result { - Pass => { - println(" PASS"); - Pass - }, - Fail(msg) => { - println(" FAIL: " ++ msg); - Fail(msg) - } - } - } catch { - RuntimeError(msg) => { - println(" ERROR: " ++ msg); - Fail(msg) - }, - _ => { - println(" PANIC"); - Fail("Test panicked") - } - } -} - -/// Run all tests in a suite and return (passed, failed) counts -fn run_suite(suite: TestSuite) -> (Int, Int) { - println("Running suite: " ++ suite.name); - let mut passed = 0; - let mut failed = 0; - - for test in suite.tests { - match run_test(test) { - Pass => passed = passed + 1, - Fail(_) => failed = failed + 1 - } - } - - println("\nResults: " ++ show(passed) ++ " passed, " ++ show(failed) ++ " failed"); - (passed, failed) -} - -/// Run multiple test suites and return aggregate (passed, failed) counts -fn run_suites(suites: [TestSuite]) -> (Int, Int) { - let mut total_passed = 0; - let mut total_failed = 0; - - for suite in suites { - let (passed, failed) = run_suite(suite); - total_passed = total_passed + passed; - total_failed = total_failed + failed; - println(""); - } - - println("========================================"); - println("Total: " ++ show(total_passed) ++ " passed, " ++ show(total_failed) ++ " failed"); - (total_passed, total_failed) -} - -// ============================================================================ -// Property-Based Testing Utilities -// ============================================================================ - -/// Check that a property holds for all elements in a list -fn for_all(values: [T], prop: T -> Bool) -> TestResult { - for value in values { - if !prop(value) { - return Fail("Property failed for value: " ++ show(value)); - } - } - Pass -} - -/// Check that a property holds for at least one element -fn exists(values: [T], prop: T -> Bool) -> TestResult { - for value in values { - if prop(value) { - return Pass; - } - } - Fail("Property failed for all values") -} - -/// Check that a property is an involution (applying twice yields the original) -fn assert_involution(f: T -> T, values: [T], message: String) -> () { - for v in values { - let round_tripped = f(f(v)); - if round_tripped != v { - panic("Involution failed for " ++ show(v) ++ ": f(f(x)) = " ++ show(round_tripped) ++ ": " ++ message); - } - } -} - -/// Check that a binary operation is commutative over given pairs -fn assert_commutative(op: (T, T) -> R, pairs: [(T, T)], message: String) -> () { - for (a, b) in pairs { - let lhs = op(a, b); - let rhs = op(b, a); - if lhs != rhs { - panic("Commutativity failed: op(" ++ show(a) ++ ", " ++ show(b) ++ ") = " ++ show(lhs) ++ - " but op(" ++ show(b) ++ ", " ++ show(a) ++ ") = " ++ show(rhs) ++ ": " ++ message); - } - } -} - -// ============================================================================ -// Benchmarking -// ============================================================================ - -/// Result of a benchmark run -type BenchResult = { - iterations: Int, - total_time: Float, - avg_time: Float -} - -/// Benchmark a function by running it for the given number of iterations. -/// Returns timing statistics using the time_now() builtin. -fn bench(f: () -> (), iterations: Int) -> BenchResult { - let start = time_now(); - - let mut i = 0; - while i < iterations { - f(); - i = i + 1; - } - - let total = time_now() - start; - { - iterations: iterations, - total_time: total, - avg_time: total / (iterations + 0.0) - } -} - -/// Compare performance of two functions side-by-side -fn bench_compare(name1: String, f1: () -> (), name2: String, f2: () -> (), iterations: Int) -> (BenchResult, BenchResult) { - println("Benchmarking " ++ name1 ++ " vs " ++ name2 ++ " (" ++ show(iterations) ++ " iterations)"); - - let r1 = bench(f1, iterations); - let r2 = bench(f2, iterations); - - println(" " ++ name1 ++ ": " ++ show(r1.total_time) ++ "s total, " ++ show(r1.avg_time) ++ "s/iter"); - println(" " ++ name2 ++ ": " ++ show(r2.total_time) ++ "s total, " ++ show(r2.avg_time) ++ "s/iter"); - - (r1, r2) -} diff --git a/affinescript/stdlib/traits.affine b/affinescript/stdlib/traits.affine deleted file mode 100644 index 6e12882..0000000 --- a/affinescript/stdlib/traits.affine +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2024-2025 hyperpolymath - -// Core traits for AffineScript standard library - -/// Trait for types that can be compared for equality -pub trait Eq { - /// Check if two values are equal - pub fn eq(ref self, ref other: Self) -> Bool; - - /// Check if two values are not equal (has default implementation) - pub fn ne(ref self, ref other: Self) -> Bool { - return !self.eq(other); - } -} - -/// Ordering result for comparisons -pub enum Ordering { - Less, - Equal, - Greater -} - -/// Trait for types that can be ordered (requires Eq) -pub trait Ord: Eq { - /// Compare two values and return ordering - pub fn cmp(ref self, ref other: Self) -> Ordering; - - /// Check if self < other - pub fn lt(ref self, ref other: Self) -> Bool { - match self.cmp(other) { - Ordering::Less => return true, - _ => return false - } - } - - /// Check if self <= other - pub fn le(ref self, ref other: Self) -> Bool { - match self.cmp(other) { - Ordering::Less => return true, - Ordering::Equal => return true, - _ => return false - } - } - - /// Check if self > other - pub fn gt(ref self, ref other: Self) -> Bool { - match self.cmp(other) { - Ordering::Greater => return true, - _ => return false - } - } - - /// Check if self >= other - pub fn ge(ref self, ref other: Self) -> Bool { - match self.cmp(other) { - Ordering::Greater => return true, - Ordering::Equal => return true, - _ => return false - } - } -} - -/// Trait for types that can be hashed -pub trait Hash { - /// Compute hash value for this type - pub fn hash(ref self) -> Int; -} - -/// Trait for types that can be converted to string -pub trait Display { - /// Convert value to string representation - pub fn to_string(ref self) -> String; -} - -/// Trait for types that can be debugged (detailed output) -pub trait Debug { - /// Convert value to debug string representation - pub fn debug(ref self) -> String; -} - -/// Trait for types that can be cloned -pub trait Clone { - /// Create a copy of this value - pub fn clone(ref self) -> Self; -} - -/// Trait for types that can be converted to another type -pub trait Into[T] { - /// Convert self into target type - pub fn into(own self) -> T; -} - -/// Trait for types that can be created from another type -pub trait From[T] { - /// Create self from source type - pub fn from(own value: T) -> Self; -} - -/// Trait for types that have a default value -pub trait Default { - /// Create default value - pub fn default() -> Self; -} - -/// Trait for iterators -pub trait Iterator { - type Item; - - /// Get next item from iterator - pub fn next(mut self) -> Option[Item]; - - /// Count remaining items - pub fn count(mut self) -> Int { - let mut n = 0; - while let Some(_) = self.next() { - n = n + 1; - } - return n; - } - - /// Collect into Vec - pub fn collect(mut self) -> Vec[Item] { - let mut result = Vec::new(); - while let Some(item) = self.next() { - result.push(item); - } - return result; - } -} - -// Implementations for built-in types - -impl Eq for Int { - pub fn eq(ref self, ref other: Int) -> Bool { - return *self == *other; - } -} - -impl Ord for Int { - pub fn cmp(ref self, ref other: Int) -> Ordering { - if *self < *other { - return Ordering::Less; - } else if *self > *other { - return Ordering::Greater; - } else { - return Ordering::Equal; - } - } -} - -impl Hash for Int { - pub fn hash(ref self) -> Int { - return *self; - } -} - -impl Display for Int { - pub fn to_string(ref self) -> String { - return int_to_string(*self); - } -} - -impl Eq for Bool { - pub fn eq(ref self, ref other: Bool) -> Bool { - return *self == *other; - } -} - -impl Display for Bool { - pub fn to_string(ref self) -> Bool { - if *self { - return "true"; - } else { - return "false"; - } - } -} diff --git a/affinescript/test/dune b/affinescript/test/dune deleted file mode 100644 index acff5da..0000000 --- a/affinescript/test/dune +++ /dev/null @@ -1,7 +0,0 @@ -(test - (name test_main) - (libraries affinescript alcotest str unix) - (deps - (source_tree golden) - (source_tree e2e) - (source_tree ../examples))) diff --git a/affinescript/test/e2e/fixtures/affine_basic.affine b/affinescript/test/e2e/fixtures/affine_basic.affine deleted file mode 100644 index 900cd1c..0000000 --- a/affinescript/test/e2e/fixtures/affine_basic.affine +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: basic functions for quantity checking -// Tests: functions that can be quantity-checked after parsing -// Note: quantity annotations (0/1/omega) are not yet in the lexer, -// so we test the quantity checker at the OCaml API level separately. - -fn use_once(x: Int) -> Int = x + 1; - -fn use_many(x: Int) -> Int = x + x; - -fn no_use(x: Int) -> Int = 42; - -fn combine(x: Int, y: Int) -> Int = x + y; diff --git a/affinescript/test/e2e/fixtures/affine_let_valid.affine b/affinescript/test/e2e/fixtures/affine_let_valid.affine deleted file mode 100644 index 25d9908..0000000 --- a/affinescript/test/e2e/fixtures/affine_let_valid.affine +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// ADR-002 / ADR-007 valid case (Option C primary form). -// -// `r` is @linear (must be used exactly once). It is bound under a -// @linear let, so the value context scales by 1 (identity). The let- -// bound alias `s` is then used exactly once in the body. This must -// pass the quantity checker. - -fn forward(@linear r: Int) -> Int { - @linear let s = r; - s -} diff --git a/affinescript/test/e2e/fixtures/affine_let_valid_sugar.affine b/affinescript/test/e2e/fixtures/affine_let_valid_sugar.affine deleted file mode 100644 index 1870a4d..0000000 --- a/affinescript/test/e2e/fixtures/affine_let_valid_sugar.affine +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// ADR-002 / ADR-007 valid case — Option B sugar form. -// Identical semantics to affine_let_valid.affine, written with :1 -// numeric quantity sugar in place of @linear. - -fn forward(@linear r: Int) -> Int { - let s :1 = r; - s -} diff --git a/affinescript/test/e2e/fixtures/affine_violation.affine b/affinescript/test/e2e/fixtures/affine_violation.affine deleted file mode 100644 index b9167bf..0000000 --- a/affinescript/test/e2e/fixtures/affine_violation.affine +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: function with double variable use -// The quantity checker should flag this for linear-annotated vars. -// Since quantity annotations aren't in the lexer, we test the quantity -// checker programmatically. - -fn double_use(x: Int) -> Int = x + x; diff --git a/affinescript/test/e2e/fixtures/arithmetic.affine b/affinescript/test/e2e/fixtures/arithmetic.affine deleted file mode 100644 index 4c5fbd7..0000000 --- a/affinescript/test/e2e/fixtures/arithmetic.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: arithmetic expressions -// Tests: parsing, constant folding, WASM codegen, Julia codegen - -fn add(a: Int, b: Int) -> Int = a + b; - -fn mul(x: Int, y: Int) -> Int = x * y; - -fn constant_fold() -> Int = 2 + 3 * 4; - -fn nested_arith(a: Int, b: Int, c: Int) -> Int = (a + b) * c - a / b; - -fn boolean_logic(x: Bool, y: Bool) -> Bool = x && y || !x; - -fn comparisons(a: Int, b: Int) -> Bool = a < b && b >= a; diff --git a/affinescript/test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine b/affinescript/test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine deleted file mode 100644 index 3740402..0000000 --- a/affinescript/test/e2e/fixtures/bug_001_omega_let_smuggles_linear.affine +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// BUG-001 regression: ω-let must NOT smuggle a linear value through. -// -// `consume` declares its parameter as @linear, so within consume's body -// the parameter `r` is a linear binding. The body binds `r` under an -// @unrestricted let — per the QTT-orthodox scaled Let rule (ADR-002), -// the value context for `e1` is scaled by ω, which promotes any linear -// usage in `e1` to UMany. The quantity checker must then reject this -// program because `r` is now considered "used multiple times" inside a -// @linear binding. -// -// This fixture uses Option C surface syntax (@unrestricted, @linear). -// The dual sugar-form fixture is bug_001_sugar_form.affine. - -fn consume(@linear r: Int) -> Int { - @unrestricted let alias = r; - alias + alias -} diff --git a/affinescript/test/e2e/fixtures/bug_001_sugar_form.affine b/affinescript/test/e2e/fixtures/bug_001_sugar_form.affine deleted file mode 100644 index 9a744d3..0000000 --- a/affinescript/test/e2e/fixtures/bug_001_sugar_form.affine +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// BUG-001 regression — Option B sugar form. Same program as -// bug_001_omega_let_smuggles_linear.affine but written with the -// :1 / :ω numeric quantity sugar instead of @linear / @unrestricted. -// Both surface forms must produce identical enforcement: the quantity -// checker must reject this for the same reason. - -fn consume(@linear r: Int) -> Int { - let alias :ω = r; - alias + alias -} diff --git a/affinescript/test/e2e/fixtures/cmd_dropped.affine b/affinescript/test/e2e/fixtures/cmd_dropped.affine deleted file mode 100644 index a3ab549..0000000 --- a/affinescript/test/e2e/fixtures/cmd_dropped.affine +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) -// -// Stage 11 — Cmd linearity: violation (Cmd dropped). -// -// The [Cmd ClickMsg] type annotation makes [cmd] linear (QOne). -// The body returns only [count + 1] — [cmd] is dropped without use. -// Expected outcome: quantity error LinearVariableUnused for [cmd]. -// -// This is the compile-time guarantee: a forgotten side-effect is a -// type error, not a silent behavioural bug. - -enum ClickMsg { Click, Hover } - -fn update_drop(msg: ClickMsg, count: Int) -> Int { - let cmd : Cmd[ClickMsg] = cmd_none; - count + 1 -} diff --git a/affinescript/test/e2e/fixtures/cmd_linear.affine b/affinescript/test/e2e/fixtures/cmd_linear.affine deleted file mode 100644 index de74927..0000000 --- a/affinescript/test/e2e/fixtures/cmd_linear.affine +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 Jonathan D.A. Jewell (hyperpolymath) -// -// Stage 11 — Cmd linearity: correct usage. -// -// Demonstrates that a [Cmd Msg] binding annotated with the [Cmd _] type -// is automatically treated as QOne (linear) by the quantity checker. -// The Cmd is returned in the tuple — consumed exactly once — so the -// quantity check passes. -// -// The AffineScript TEA guarantee: update must handle its Cmd obligation. -// Forgetting to include the Cmd in the return value is a compile error. - -enum ClickMsg { Click, Hover } - -// update : ClickMsg -> Int -> (Int, Cmd ClickMsg) -// Returns the new model paired with a no-op Cmd. -// The Cmd ClickMsg binding is linear: it must appear in the return value. - -fn update(msg: ClickMsg, count: Int) -> (Int, Cmd[ClickMsg]) { - let cmd : Cmd[ClickMsg] = cmd_none; - (count + 1, cmd) -} diff --git a/affinescript/test/e2e/fixtures/counter.affine b/affinescript/test/e2e/fixtures/counter.affine deleted file mode 100644 index 21dac0f..0000000 --- a/affinescript/test/e2e/fixtures/counter.affine +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 hyperpolymath -// -// AffineScript TEA counter — Stage 3c dogfood -// -// Demonstrates The Elm Architecture in AffineScript: -// - Model is an owned value: each update consumes the old model, produces a new one -// - Msg is linear: consumed by update, not reusable -// - Cmd carries obligations: None means no side-effects, Log carries a message to handle -// -// Run: affinescript eval examples/counter.afs -// At the stdin prompt, type one of: Increment Decrement Reset -// Ctrl-D (EOF) exits cleanly. - -// ── Msg ───────────────────────────────────────────────────────────────────── -// Linear: each message is consumed exactly once by update. - -enum CounterMsg { - Increment, - Decrement, - Reset -} - -// ── Cmd ───────────────────────────────────────────────────────────────────── -// Linear obligation. None → no effect. Log(String) → something to handle. -// Dropping a non-None Cmd at compile time is a quantity violation (Stage 1). - -enum CounterCmd { - None, - Log(String) -} - -// ── init ───────────────────────────────────────────────────────────────────── -// Produces the initial model. No commands needed at start-up. - -fn counter_init() -> Int { - 0 -} - -// ── update ─────────────────────────────────────────────────────────────────── -// Consumes Msg and old Model; produces new Model plus any commands. -// The old model is gone after this call — AffineScript enforces this via QTT. - -fn counter_update(msg: CounterMsg, model: Int) -> Int { - match msg { - Increment => model + 1, - Decrement => model - 1, - Reset => 0 - } -} - -// ── view ───────────────────────────────────────────────────────────────────── -// Borrows the model (read-only), renders a string. -// Rebuilding the view does not consume the model. - -fn counter_view(model: Int) -> String { - "Count: " ++ int_to_string(model) -} - -// ── subscriptions ──────────────────────────────────────────────────────────── -// No real-time subscriptions at the interpreter level. - -fn counter_subs(model: Int) -> String { - "None" -} - -// ── main ───────────────────────────────────────────────────────────────────── -// Wire up the TEA runtime and hand off to the interpreter loop. - -fn main() -> () { - tea_run({ - init: counter_init, - update: counter_update, - view: counter_view, - subscriptions: counter_subs - }) -} diff --git a/affinescript/test/e2e/fixtures/dependent_types.affine b/affinescript/test/e2e/fixtures/dependent_types.affine deleted file mode 100644 index c7a0bfb..0000000 --- a/affinescript/test/e2e/fixtures/dependent_types.affine +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: dependent-type surface syntax (parse-only) -// Tests: type aliases and generic functions with where-clause constraints -// Note: refinement predicates are parsed but not yet reduced/checked. - -type nat = Int; - -fn succ(n: nat) -> nat = n + 1; - -fn add_nats(a: nat, b: nat) -> nat = a + b; - -fn zero() -> nat = 0; diff --git a/affinescript/test/e2e/fixtures/effects.affine b/affinescript/test/e2e/fixtures/effects.affine deleted file mode 100644 index 120d6b0..0000000 --- a/affinescript/test/e2e/fixtures/effects.affine +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: effect system -// Tests: effect declarations, effect annotations - -effect IO { - fn print(s: String) -> (); - fn read() -> String; -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} - -effect Exn[E] { - fn throw(err: E) -> Never; -} - -fn hello() -> () = (); diff --git a/affinescript/test/e2e/fixtures/erased_violation.affine b/affinescript/test/e2e/fixtures/erased_violation.affine deleted file mode 100644 index 715687e..0000000 --- a/affinescript/test/e2e/fixtures/erased_violation.affine +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: function using variable (for erased check) -// Quantity checker should flag this if variable is erased (QZero). -// Since quantity annotations aren't in the lexer, we test the quantity -// checker programmatically. - -fn use_var(x: Int) -> Int = x + 1; diff --git a/affinescript/test/e2e/fixtures/full_pipeline.affine b/affinescript/test/e2e/fixtures/full_pipeline.affine deleted file mode 100644 index 6cf1ea7..0000000 --- a/affinescript/test/e2e/fixtures/full_pipeline.affine +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: full pipeline integration -// Tests: a non-trivial program through all compiler stages - -struct Vec2 { - x: Int, - y: Int -} - -enum Shape { - Circle(Int), - Rect(Vec2) -} - -fn vec2_add(a: Vec2, b: Vec2) -> Vec2 { - { x: a.x + b.x, y: a.y + b.y } -} - -fn area(s: Shape) -> Int { - match s { - Circle(r) => r * r * 3, - Rect(size) => size.x * size.y - } -} - -fn max(a: Int, b: Int) -> Int { - if a > b { a } else { b } -} - -fn larger_area(s1: Shape, s2: Shape) -> Int = max(area(s1), area(s2)); - -fn main() -> () { - let circle = Circle(5); - let rect = Rect({ x: 3, y: 4 }); - let result = larger_area(circle, rect); - (); -} diff --git a/affinescript/test/e2e/fixtures/interp_simple.affine b/affinescript/test/e2e/fixtures/interp_simple.affine deleted file mode 100644 index 02e10a3..0000000 --- a/affinescript/test/e2e/fixtures/interp_simple.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: interpreter evaluation -// Tests: simple expressions that the tree-walking interpreter can evaluate - -fn add(a: Int, b: Int) -> Int = a + b; - -fn identity(x: Int) -> Int = x; - -fn constant() -> Int = 42; - -fn nested() -> Int { - let x = 10; - let y = 20; - x + y -} diff --git a/affinescript/test/e2e/fixtures/julia_simple.affine b/affinescript/test/e2e/fixtures/julia_simple.affine deleted file mode 100644 index 46a98ef..0000000 --- a/affinescript/test/e2e/fixtures/julia_simple.affine +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: Julia backend codegen -// Tests: simple functions that should fully compile to Julia - -fn add(a: Int, b: Int) -> Int = a + b; - -fn fibonacci(n: Int) -> Int { - if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) } -} - -fn greet(name: String) -> String = "Hello"; diff --git a/affinescript/test/e2e/fixtures/lambda.affine b/affinescript/test/e2e/fixtures/lambda.affine deleted file mode 100644 index 5253c5f..0000000 --- a/affinescript/test/e2e/fixtures/lambda.affine +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: lambda expressions and higher-order functions -// Tests: closures, lambda syntax, function application - -fn apply(f: (Int) -> Int, x: Int) -> Int = f(x); - -fn test_lambda() -> Int { - let f = |x| x + 1; - f(41) -} - -fn test_typed_lambda() -> Int { - let double = |x: Int| x * 2; - double(21) -} - -fn test_hof() -> Int { - apply(|x| x * x, 5) -} diff --git a/affinescript/test/e2e/fixtures/linear_arrow.affine b/affinescript/test/e2e/fixtures/linear_arrow.affine deleted file mode 100644 index ed39dfe..0000000 --- a/affinescript/test/e2e/fixtures/linear_arrow.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: linear arrow quantity enforcement in lambda expressions -// Tests: -// - Lambda with @linear param produces T -[1]-> U type (not T -[ω]-> U) -// - Lambda with @linear param used once passes quantity check -// - Named function with @linear param passes quantity check - -fn consume_once(@linear x: Int) -> Int = x + 1; - -fn wrap_linear() -> Int { - let f = |@linear x: Int| x + 1; - f(41) -} - -fn hof_linear(g: (Int) -> Int, n: Int) -> Int = g(n); diff --git a/affinescript/test/e2e/fixtures/linear_arrow_violation.affine b/affinescript/test/e2e/fixtures/linear_arrow_violation.affine deleted file mode 100644 index 6954a85..0000000 --- a/affinescript/test/e2e/fixtures/linear_arrow_violation.affine +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Regression: @linear param in a lambda must not be used twice inside the body. -// The quantity checker must reject this program because `x` is used -// two times inside the lambda body, violating the @linear (must use once) -// contract declared on the lambda parameter. - -fn trigger_double_use() -> Int { - let f = |@linear x: Int| x + x; - f(21) -} diff --git a/affinescript/test/e2e/fixtures/ownership.affine b/affinescript/test/e2e/fixtures/ownership.affine deleted file mode 100644 index 7434460..0000000 --- a/affinescript/test/e2e/fixtures/ownership.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: ownership system -// Tests: own/ref/mut modifiers, borrowing - -fn takes_owned(x: own String) -> () = (); - -fn takes_ref(x: ref Int) -> Int = 42; - -fn takes_mut(x: mut Bool) -> () = (); - -struct File { - fd: own Int -} - -fn read_file(f: ref File) -> Int = 42; diff --git a/affinescript/test/e2e/fixtures/ownership_codegen.affine b/affinescript/test/e2e/fixtures/ownership_codegen.affine deleted file mode 100644 index 594a8ab..0000000 --- a/affinescript/test/e2e/fixtures/ownership_codegen.affine +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Stage 2 round-trip test fixture. -// Ownership annotations must survive from AffineScript source to the Wasm custom section. -// Bodies return literals to avoid type unification between own/ref/mut and the plain return type. - -fn consume_owned(x: own Int) -> Int { - 0 -} - -fn borrow_ref(y: ref Int) -> Int { - 0 -} - -fn borrow_mut(z: mut Int) -> Int { - 0 -} - -fn plain(n: Int) -> Int { - 0 -} diff --git a/affinescript/test/e2e/fixtures/pattern_match.affine b/affinescript/test/e2e/fixtures/pattern_match.affine deleted file mode 100644 index 0ef0ec2..0000000 --- a/affinescript/test/e2e/fixtures/pattern_match.affine +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: pattern matching -// Tests: literal patterns, constructor patterns, tuple destructuring - -enum Option[T] { - None, - Some(T) -} - -fn unwrap_or(opt: Option[Int], default: Int) -> Int { - match opt { - Some(x) => x, - None => default - }; -} - -fn fst(a: Int, b: Int) -> Int = a; - -fn classify(n: Int) -> Int { - match n { - 0 => 1, - 1 => 2, - _ => 3 - }; -} diff --git a/affinescript/test/e2e/fixtures/python_face_basic.pyaff b/affinescript/test/e2e/fixtures/python_face_basic.pyaff deleted file mode 100644 index acd9a50..0000000 --- a/affinescript/test/e2e/fixtures/python_face_basic.pyaff +++ /dev/null @@ -1,20 +0,0 @@ -# Python-face fixture: basic arithmetic and control flow. -# SPDX-License-Identifier: MPL-2.0 - -def add(x: Int, y: Int) -> Int: - let result = x + y - result - -def classify(n: Int) -> String: - if n > 0: - "positive" - elif n < 0: - "negative" - else: - "zero" - -def factorial(n: Int) -> Int: - if n == 0: - 1 - else: - n * factorial(n - 1) diff --git a/affinescript/test/e2e/fixtures/refinement_types.affine b/affinescript/test/e2e/fixtures/refinement_types.affine deleted file mode 100644 index 51849bc..0000000 --- a/affinescript/test/e2e/fixtures/refinement_types.affine +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: type constraints via where clauses -// Tests: parsing of where-clause constraints on generic functions - -fn identity[T](x: T) -> T = x; - -fn apply[T, U](f: (T) -> U, x: T) -> U = f(x); diff --git a/affinescript/test/e2e/fixtures/row_polymorphism.affine b/affinescript/test/e2e/fixtures/row_polymorphism.affine deleted file mode 100644 index 24a8337..0000000 --- a/affinescript/test/e2e/fixtures/row_polymorphism.affine +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: record types (row polymorphism building blocks) -// Tests: record type syntax, struct declarations - -struct Point2D { - x: Int, - y: Int -} - -struct Point3D { - x: Int, - y: Int, - z: Int -} - -fn getX(p: Point2D) -> Int = p.x; - -fn mk_point(x: Int, y: Int) -> Point2D { - { x: x, y: y } -} diff --git a/affinescript/test/e2e/fixtures/titlescreen.affine b/affinescript/test/e2e/fixtures/titlescreen.affine deleted file mode 100644 index 9046325..0000000 --- a/affinescript/test/e2e/fixtures/titlescreen.affine +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025-2026 hyperpolymath -// -// AffineScript TEA TitleScreen — Stage 3d dogfood -// -// IDApTIK title screen in AffineScript TEA. -// This is the bridge target for Stage 4: when the Wasm/JS bridge is done, -// this exact file compiles to a .wasm that AffineTEA.js drives via PixiJS. -// -// Model: screen state (dimensions, music playing, selected option) -// Msg: navigation events from the title menu -// Cmd: side-effects (BGM, SFX, screen navigation) -// -// Run: affinescript eval examples/titlescreen.afs -// Type: NewGame LoadGame Settings Credits Ctrl-D to exit - -// ── Msg ────────────────────────────────────────────────────────────────────── - -enum TitleMsg { - NewGame, - LoadGame, - Settings, - Credits -} - -// ── Cmd ────────────────────────────────────────────────────────────────────── - -enum TitleCmd { - None, - PlayBGM(String), - PlaySFX(String), - Navigate(String) -} - -// ── Model ──────────────────────────────────────────────────────────────────── - -struct TitleModel { - screen_w: Int, - screen_h: Int, - bgm_playing: Int, - selected: String -} - -// ── init ───────────────────────────────────────────────────────────────────── - -fn title_init() -> TitleModel { - { - screen_w: 1280, - screen_h: 720, - bgm_playing: 0, - selected: "none" - } -} - -// ── update ──────────────────────────────────────────────────────────────────── -// Consumes msg and model; returns updated model. -// In Stage 4, this also emits Cmd(Navigate(...)) which the JS bridge handles. - -fn title_update(msg: TitleMsg, model: TitleModel) -> TitleModel { - match msg { - NewGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "new_game" }, - LoadGame => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "load_game" }, - Settings => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "settings" }, - Credits => { screen_w: model.screen_w, screen_h: model.screen_h, bgm_playing: model.bgm_playing, selected: "credits" } - } -} - -// ── view ───────────────────────────────────────────────────────────────────── -// Borrows model, renders the title screen state as a string. -// In Stage 4, this returns Html that the JS bridge applies to the Pixi scene. - -fn title_view(model: TitleModel) -> String { - "=== IDApTIK ===" ++ "\n" ++ - "Screen: " ++ int_to_string(model.screen_w) ++ "x" ++ int_to_string(model.screen_h) ++ "\n" ++ - "Selected: " ++ model.selected -} - -// ── subscriptions ───────────────────────────────────────────────────────────── - -fn title_subs(model: TitleModel) -> String { - "None" -} - -// ── main ────────────────────────────────────────────────────────────────────── - -fn main() -> () { - tea_run({ - init: title_init, - update: title_update, - view: title_view, - subscriptions: title_subs - }) -} diff --git a/affinescript/test/e2e/fixtures/trait_impl_missing_method.affine b/affinescript/test/e2e/fixtures/trait_impl_missing_method.affine deleted file mode 100644 index e187226..0000000 --- a/affinescript/test/e2e/fixtures/trait_impl_missing_method.affine +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: trait impl with missing required method -// Verifies that an impl block that fails to provide a non-default -// trait method is rejected by the type checker. - -trait Describe { - fn describe(x: Int) -> String; - fn summary(x: Int) -> String; -} - -struct Widget { - id: Int -} - -impl Describe for Widget { - fn describe(x: Int) -> String = "widget"; - // summary is intentionally omitted — should be rejected -} - -fn make_widget() -> Int = 1; diff --git a/affinescript/test/e2e/fixtures/trait_impl_valid.affine b/affinescript/test/e2e/fixtures/trait_impl_valid.affine deleted file mode 100644 index 8c2a3f8..0000000 --- a/affinescript/test/e2e/fixtures/trait_impl_valid.affine +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: valid trait impl acceptance -// Verifies that TopTrait and TopImpl declarations are registered and their -// method bodies are type-checked without error. - -trait Greet { - fn greet(name: String) -> String; -} - -struct Person { - age: Int -} - -impl Greet for Person { - fn greet(name: String) -> String = name; -} - -fn make_person() -> Int = 42; diff --git a/affinescript/test/e2e/fixtures/traits.affine b/affinescript/test/e2e/fixtures/traits.affine deleted file mode 100644 index 32bc875..0000000 --- a/affinescript/test/e2e/fixtures/traits.affine +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: trait system -// Tests: trait declarations, supertraits, impl blocks, method signatures - -trait Eq { - fn eq(a: Int, b: Int) -> Bool; -} - -trait Ord: Eq { - fn cmp(a: Int, b: Int) -> Int; -} - -trait Show { - fn show(x: Int) -> String; -} - -impl Eq for Int { - fn eq(a: Int, b: Int) -> Bool = true; -} - -impl Show for Int { - fn show(x: Int) -> String = "int"; -} - -struct Point { - x: Int, - y: Int -} - -impl Eq for Point { - fn eq(a: Int, b: Int) -> Bool = true; -} - -fn generic_id[T](v: T) -> T = v; diff --git a/affinescript/test/e2e/fixtures/try_body_only.affine b/affinescript/test/e2e/fixtures/try_body_only.affine deleted file mode 100644 index 2e1c601..0000000 --- a/affinescript/test/e2e/fixtures/try_body_only.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// E2E fixture: try block with body only (no catch, no finally). -// The result is the body value — simplest try form. - -fn safe_add(x: Int, y: Int) -> Int { - try { - x + y - } -} - -fn main() -> Int { - safe_add(10, 32) -} diff --git a/affinescript/test/e2e/fixtures/try_catch_finally.affine b/affinescript/test/e2e/fixtures/try_catch_finally.affine deleted file mode 100644 index 01e8002..0000000 --- a/affinescript/test/e2e/fixtures/try_catch_finally.affine +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// E2E fixture: try/catch/finally — full three-clause form. -// Verifies that all three clauses type-check together correctly. - -fn full_try(x: Int) -> Int { - try { - x * 3 - } catch { - e => 0 - } finally { - () - } -} - -fn main() -> Int { - full_try(7) -} diff --git a/affinescript/test/e2e/fixtures/try_catch_var.affine b/affinescript/test/e2e/fixtures/try_catch_var.affine deleted file mode 100644 index 1958b0c..0000000 --- a/affinescript/test/e2e/fixtures/try_catch_var.affine +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// E2E fixture: try/catch with a named variable pattern. -// The catch arm binds the exception to a variable and returns a fallback. - -fn guarded(x: Int) -> Int { - try { - x + 1 - } catch { - err => -1 - } -} - -fn main() -> Int { - guarded(99) -} diff --git a/affinescript/test/e2e/fixtures/try_catch_wildcard.affine b/affinescript/test/e2e/fixtures/try_catch_wildcard.affine deleted file mode 100644 index cc69aec..0000000 --- a/affinescript/test/e2e/fixtures/try_catch_wildcard.affine +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// E2E fixture: try/catch with a wildcard pattern. -// The catch arm discards the exception and returns a fallback value. - -fn safe_div(x: Int, y: Int) -> Int { - try { - x - } catch { - _ => 0 - } -} - -fn main() -> Int { - safe_div(42, 1) -} diff --git a/affinescript/test/e2e/fixtures/try_finally.affine b/affinescript/test/e2e/fixtures/try_finally.affine deleted file mode 100644 index f12701e..0000000 --- a/affinescript/test/e2e/fixtures/try_finally.affine +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) -// -// E2E fixture: try/finally with no catch. -// The finally block runs for side-effects; the body value is returned. - -fn compute(x: Int) -> Int { - try { - x * 2 - } finally { - () - } -} - -fn main() -> Int { - compute(21) -} diff --git a/affinescript/test/e2e/fixtures/type_decls.affine b/affinescript/test/e2e/fixtures/type_decls.affine deleted file mode 100644 index 74ac52c..0000000 --- a/affinescript/test/e2e/fixtures/type_decls.affine +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: type declarations -// Tests: aliases, structs, enums, parameterised types - -type Alias = Int; - -struct Point { - x: Int, - y: Int -} - -enum Color { - Red, - Green, - Blue, - Custom(Int, Int, Int) -} - -enum Option[T] { - None, - Some(T) -} - -enum Result[T, E] { - Ok(T), - Err(E) -} - -fn make_point(x: Int, y: Int) -> Point { - { x: x, y: y } -} - -fn origin() -> Point { - { x: 0, y: 0 } -} diff --git a/affinescript/test/e2e/fixtures/verify_ownership_clean.affine b/affinescript/test/e2e/fixtures/verify_ownership_clean.affine deleted file mode 100644 index 5e284e2..0000000 --- a/affinescript/test/e2e/fixtures/verify_ownership_clean.affine +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Stage 7 verify integration fixture — clean case. -// -// This fixture compiles cleanly and the verifier must report OK. -// All functions have only Unrestricted (plain Int) params so that no -// LinearNotUsed or ExclBorrowAliased violations can fire. -// The ownership section will be present with all-zero (Unrestricted) entries. - -fn identity(n: Int) -> Int { - n -} - -fn add(a: Int, b: Int) -> Int { - a + b -} - -fn double(x: Int) -> Int { - x + x -} - -fn const_zero(n: Int) -> Int { - 0 -} diff --git a/affinescript/test/e2e/fixtures/wasm_simple.affine b/affinescript/test/e2e/fixtures/wasm_simple.affine deleted file mode 100644 index 9cc8c2c..0000000 --- a/affinescript/test/e2e/fixtures/wasm_simple.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// End-to-end test: WASM backend codegen -// Tests: simple functions that should fully compile to WASM binary - -fn add(a: Int, b: Int) -> Int = a + b; - -fn factorial(n: Int) -> Int { - if n <= 1 { 1 } else { n * factorial(n - 1) } -} - -fn main() -> () { - let result = add(3, 4); - let fact5 = factorial(5); - (); -} diff --git a/affinescript/test/golden/binary_ops.affine b/affinescript/test/golden/binary_ops.affine deleted file mode 100644 index ef6a0b9..0000000 --- a/affinescript/test/golden/binary_ops.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test binary operators and precedence -fn test_ops() -> Int { - 1 + 2 * 3 -} - -fn test_compare() -> Bool { - x == y && a < b -} diff --git a/affinescript/test/golden/binary_ops.expected b/affinescript/test/golden/binary_ops.expected deleted file mode 100644 index 61b9f08..0000000 --- a/affinescript/test/golden/binary_ops.expected +++ /dev/null @@ -1,131 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_ops"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 40 }; - end_pos = { Span.line = 2; col = 4; offset = 43 }; - file = "test/golden/binary_ops.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 15; offset = 54 }; - end_pos = { Span.line = 2; col = 18; offset = 57 }; - file = "test/golden/binary_ops.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprBinary ( - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 2; col = 22; offset = 61 }; - end_pos = - { Span.line = 3; col = 3; offset = 65 }; - file = "test/golden/binary_ops.affine" } - ))), - Ast.OpAdd, - (Ast.ExprBinary ( - (Ast.ExprLit - (Ast.LitInt (2, - { Span.start_pos = - { Span.line = 3; col = 5; offset = 67 }; - end_pos = - { Span.line = 3; col = 7; offset = 69 }; - file = "test/golden/binary_ops.affine" } - ))), - Ast.OpMul, - (Ast.ExprLit - (Ast.LitInt (3, - { Span.start_pos = - { Span.line = 3; col = 9; offset = 71 }; - end_pos = - { Span.line = 3; col = 11; offset = 73 }; - file = "test/golden/binary_ops.affine" } - ))) - )) - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_compare"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 78 }; - end_pos = { Span.line = 6; col = 4; offset = 81 }; - file = "test/golden/binary_ops.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Bool"; - span = - { Span.start_pos = - { Span.line = 6; col = 19; offset = 96 }; - end_pos = { Span.line = 6; col = 22; offset = 99 }; - file = "test/golden/binary_ops.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprBinary ( - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 6; col = 27; offset = 104 }; - end_pos = - { Span.line = 7; col = 3; offset = 108 }; - file = "test/golden/binary_ops.affine" } - }), - Ast.OpEq, - (Ast.ExprVar - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 7; col = 5; offset = 110 }; - end_pos = - { Span.line = 7; col = 8; offset = 113 }; - file = "test/golden/binary_ops.affine" } - }) - )), - Ast.OpAnd, - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "a"; - span = - { Span.start_pos = - { Span.line = 7; col = 10; offset = 115 }; - end_pos = - { Span.line = 7; col = 13; offset = 118 }; - file = "test/golden/binary_ops.affine" } - }), - Ast.OpLt, - (Ast.ExprVar - { Ast.name = "b"; - span = - { Span.start_pos = - { Span.line = 7; col = 15; offset = 120 }; - end_pos = - { Span.line = 7; col = 17; offset = 122 }; - file = "test/golden/binary_ops.affine" } - }) - )) - ))) - }) - }) - ] - } diff --git a/affinescript/test/golden/effects.affine b/affinescript/test/golden/effects.affine deleted file mode 100644 index e16836d..0000000 --- a/affinescript/test/golden/effects.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test effect declarations -effect IO { - fn print(s: String) -> (); - fn read() -> String; -} - -effect State[S] { - fn get() -> S; - fn put(s: S) -> (); -} diff --git a/affinescript/test/golden/effects.expected b/affinescript/test/golden/effects.expected deleted file mode 100644 index d409e83..0000000 --- a/affinescript/test/golden/effects.expected +++ /dev/null @@ -1,132 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "IO"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 28 }; - end_pos = { Span.line = 2; col = 8; offset = 35 }; - file = "test/golden/effects.affine" } - }; - ed_type_params = []; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "print"; - span = - { Span.start_pos = { Span.line = 3; col = 3; offset = 42 }; - end_pos = { Span.line = 3; col = 6; offset = 45 }; - file = "test/golden/effects.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "s"; - span = - { Span.start_pos = { Span.line = 3; col = 11; offset = 50 }; - end_pos = { Span.line = 3; col = 12; offset = 51 }; - file = "test/golden/effects.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 3; col = 13; offset = 52 }; - end_pos = { Span.line = 3; col = 15; offset = 54 }; - file = "test/golden/effects.affine" } - }) - } - ]; - eod_ret_ty = (Some (Ast.TyTuple [])) }; - { Ast.eod_name = - { Ast.name = "read"; - span = - { Span.start_pos = { Span.line = 4; col = 3; offset = 71 }; - end_pos = { Span.line = 4; col = 6; offset = 74 }; - file = "test/golden/effects.affine" } - }; - eod_params = []; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 4; col = 13; offset = 81 }; - end_pos = { Span.line = 4; col = 16; offset = 84 }; - file = "test/golden/effects.affine" } - })) - } - ] - }); - (Ast.TopEffect - { Ast.ed_vis = Ast.Private; - ed_name = - { Ast.name = "State"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 95 }; - end_pos = { Span.line = 7; col = 8; offset = 102 }; - file = "test/golden/effects.affine" } - }; - ed_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "S"; - span = - { Span.start_pos = { Span.line = 7; col = 13; offset = 107 }; - end_pos = { Span.line = 7; col = 14; offset = 108 }; - file = "test/golden/effects.affine" } - }; - tp_kind = None } - ]; - ed_ops = - [{ Ast.eod_name = - { Ast.name = "get"; - span = - { Span.start_pos = { Span.line = 8; col = 3; offset = 115 }; - end_pos = { Span.line = 8; col = 6; offset = 118 }; - file = "test/golden/effects.affine" } - }; - eod_params = []; - eod_ret_ty = - (Some (Ast.TyCon - { Ast.name = "S"; - span = - { Span.start_pos = - { Span.line = 8; col = 12; offset = 124 }; - end_pos = { Span.line = 8; col = 15; offset = 127 }; - file = "test/golden/effects.affine" } - })) - }; - { Ast.eod_name = - { Ast.name = "put"; - span = - { Span.start_pos = { Span.line = 9; col = 3; offset = 132 }; - end_pos = { Span.line = 9; col = 6; offset = 135 }; - file = "test/golden/effects.affine" } - }; - eod_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "s"; - span = - { Span.start_pos = { Span.line = 9; col = 9; offset = 138 }; - end_pos = { Span.line = 9; col = 10; offset = 139 }; - file = "test/golden/effects.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "S"; - span = - { Span.start_pos = - { Span.line = 9; col = 11; offset = 140 }; - end_pos = { Span.line = 9; col = 13; offset = 142 }; - file = "test/golden/effects.affine" } - }) - } - ]; - eod_ret_ty = (Some (Ast.TyTuple [])) } - ] - }) - ] - } diff --git a/affinescript/test/golden/functions.affine b/affinescript/test/golden/functions.affine deleted file mode 100644 index 25e7a40..0000000 --- a/affinescript/test/golden/functions.affine +++ /dev/null @@ -1,16 +0,0 @@ -// Test function declarations -fn simple(x: Int) -> Int { - x + 1 -} - -total fn safe(n: Nat) -> Nat { - n -} - -fn generic[T](x: T) -> T { - x -} - -pub fn visible() -> () { - () -} diff --git a/affinescript/test/golden/functions.expected b/affinescript/test/golden/functions.expected deleted file mode 100644 index 7fdf28c..0000000 --- a/affinescript/test/golden/functions.expected +++ /dev/null @@ -1,211 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "simple"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 30 }; - end_pos = { Span.line = 2; col = 4; offset = 33 }; - file = "test/golden/functions.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 2; col = 10; offset = 39 }; - end_pos = { Span.line = 2; col = 11; offset = 40 }; - file = "test/golden/functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = { Span.line = 2; col = 12; offset = 41 }; - end_pos = { Span.line = 2; col = 14; offset = 43 }; - file = "test/golden/functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 19; offset = 48 }; - end_pos = { Span.line = 2; col = 22; offset = 51 }; - file = "test/golden/functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 2; col = 26; offset = 55 }; - end_pos = - { Span.line = 3; col = 3; offset = 59 }; - file = "test/golden/functions.affine" } - }), - Ast.OpAdd, - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 3; col = 5; offset = 61 }; - end_pos = - { Span.line = 3; col = 7; offset = 63 }; - file = "test/golden/functions.affine" } - ))) - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = true; - fd_name = - { Ast.name = "safe"; - span = - { Span.start_pos = { Span.line = 6; col = 7; offset = 74 }; - end_pos = { Span.line = 6; col = 10; offset = 77 }; - file = "test/golden/functions.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "n"; - span = - { Span.start_pos = { Span.line = 6; col = 14; offset = 81 }; - end_pos = { Span.line = 6; col = 15; offset = 82 }; - file = "test/golden/functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Nat"; - span = - { Span.start_pos = { Span.line = 6; col = 16; offset = 83 }; - end_pos = { Span.line = 6; col = 18; offset = 85 }; - file = "test/golden/functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Nat"; - span = - { Span.start_pos = - { Span.line = 6; col = 23; offset = 90 }; - end_pos = { Span.line = 6; col = 26; offset = 93 }; - file = "test/golden/functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprVar - { Ast.name = "n"; - span = - { Span.start_pos = - { Span.line = 6; col = 30; offset = 97 }; - end_pos = { Span.line = 7; col = 3; offset = 101 }; - file = "test/golden/functions.affine" } - })) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "generic"; - span = - { Span.start_pos = { Span.line = 10; col = 1; offset = 106 }; - end_pos = { Span.line = 10; col = 4; offset = 109 }; - file = "test/golden/functions.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 10; col = 11; offset = 116 }; - end_pos = { Span.line = 10; col = 12; offset = 117 }; - file = "test/golden/functions.affine" } - }; - tp_kind = None } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 10; col = 14; offset = 119 }; - end_pos = { Span.line = 10; col = 15; offset = 120 }; - file = "test/golden/functions.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 10; col = 16; offset = 121 }; - end_pos = { Span.line = 10; col = 18; offset = 123 }; - file = "test/golden/functions.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 10; col = 21; offset = 126 }; - end_pos = { Span.line = 10; col = 24; offset = 129 }; - file = "test/golden/functions.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 10; col = 26; offset = 131 }; - end_pos = - { Span.line = 11; col = 3; offset = 135 }; - file = "test/golden/functions.affine" } - })) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Public; fd_total = false; - fd_name = - { Ast.name = "visible"; - span = - { Span.start_pos = { Span.line = 14; col = 5; offset = 144 }; - end_pos = { Span.line = 14; col = 8; offset = 147 }; - file = "test/golden/functions.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 14; col = 24; offset = 163 }; - end_pos = - { Span.line = 15; col = 4; offset = 168 }; - file = "test/golden/functions.affine" }))) - }) - }) - ] - } diff --git a/affinescript/test/golden/hello.affine b/affinescript/test/golden/hello.affine deleted file mode 100644 index 400c13f..0000000 --- a/affinescript/test/golden/hello.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Simple hello world -fn main() -> () { - () -} diff --git a/affinescript/test/golden/hello.expected b/affinescript/test/golden/hello.expected deleted file mode 100644 index 572310e..0000000 --- a/affinescript/test/golden/hello.expected +++ /dev/null @@ -1,27 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "main"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 22 }; - end_pos = { Span.line = 2; col = 4; offset = 25 }; - file = "test/golden/hello.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 2; col = 17; offset = 38 }; - end_pos = { Span.line = 3; col = 4; offset = 43 }; - file = "test/golden/hello.affine" }))) - }) - }) - ] - } diff --git a/affinescript/test/golden/imports.affine b/affinescript/test/golden/imports.affine deleted file mode 100644 index ce3b919..0000000 --- a/affinescript/test/golden/imports.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test module imports -use std.io; -use std.collections.Vec as Vector; -use std.prelude::{Option, Result}; - -fn main() -> () { - () -} diff --git a/affinescript/test/golden/imports.expected b/affinescript/test/golden/imports.expected deleted file mode 100644 index a79e643..0000000 --- a/affinescript/test/golden/imports.expected +++ /dev/null @@ -1,103 +0,0 @@ -{ Ast.prog_module = None; - prog_imports = - [(Ast.ImportSimple ( - [{ Ast.name = "std"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 23 }; - end_pos = { Span.line = 2; col = 5; offset = 27 }; - file = "test/golden/imports.affine" } - }; - { Ast.name = "io"; - span = - { Span.start_pos = { Span.line = 2; col = 8; offset = 30 }; - end_pos = { Span.line = 2; col = 9; offset = 31 }; - file = "test/golden/imports.affine" } - } - ], - None)); - (Ast.ImportSimple ( - [{ Ast.name = "std"; - span = - { Span.start_pos = { Span.line = 3; col = 1; offset = 35 }; - end_pos = { Span.line = 3; col = 5; offset = 39 }; - file = "test/golden/imports.affine" } - }; - { Ast.name = "collections"; - span = - { Span.start_pos = { Span.line = 3; col = 8; offset = 42 }; - end_pos = { Span.line = 3; col = 9; offset = 43 }; - file = "test/golden/imports.affine" } - }; - { Ast.name = "Vec"; - span = - { Span.start_pos = { Span.line = 3; col = 20; offset = 54 }; - end_pos = { Span.line = 3; col = 21; offset = 55 }; - file = "test/golden/imports.affine" } - } - ], - (Some { Ast.name = "Vector"; - span = - { Span.start_pos = { Span.line = 3; col = 25; offset = 59 }; - end_pos = { Span.line = 3; col = 28; offset = 62 }; - file = "test/golden/imports.affine" } - }) - )); - (Ast.ImportList ( - [{ Ast.name = "std"; - span = - { Span.start_pos = { Span.line = 4; col = 1; offset = 70 }; - end_pos = { Span.line = 4; col = 5; offset = 74 }; - file = "test/golden/imports.affine" } - }; - { Ast.name = "prelude"; - span = - { Span.start_pos = { Span.line = 4; col = 8; offset = 77 }; - end_pos = { Span.line = 4; col = 9; offset = 78 }; - file = "test/golden/imports.affine" } - } - ], - [{ Ast.ii_name = - { Ast.name = "Option"; - span = - { Span.start_pos = { Span.line = 4; col = 18; offset = 87 }; - end_pos = { Span.line = 4; col = 19; offset = 88 }; - file = "test/golden/imports.affine" } - }; - ii_alias = None }; - { Ast.ii_name = - { Ast.name = "Result"; - span = - { Span.start_pos = { Span.line = 4; col = 25; offset = 94 }; - end_pos = { Span.line = 4; col = 27; offset = 96 }; - file = "test/golden/imports.affine" } - }; - ii_alias = None } - ] - )) - ]; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "main"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 106 }; - end_pos = { Span.line = 6; col = 4; offset = 109 }; - file = "test/golden/imports.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 6; col = 17; offset = 122 }; - end_pos = { Span.line = 7; col = 4; offset = 127 }; - file = "test/golden/imports.affine" }))) - }) - }) - ] - } diff --git a/affinescript/test/golden/lambda.affine b/affinescript/test/golden/lambda.affine deleted file mode 100644 index 7fd853a..0000000 --- a/affinescript/test/golden/lambda.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test lambda expressions -fn test_lambda() -> Int { - let f = |x| x + 1; - f(42) -} - -fn test_lambda_typed() -> Int { - let f = |x: Int| x * 2; - f(21) -} diff --git a/affinescript/test/golden/lambda.expected b/affinescript/test/golden/lambda.expected deleted file mode 100644 index b9551e2..0000000 --- a/affinescript/test/golden/lambda.expected +++ /dev/null @@ -1,202 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_lambda"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 27 }; - end_pos = { Span.line = 2; col = 4; offset = 30 }; - file = "test/golden/lambda.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 18; offset = 44 }; - end_pos = { Span.line = 2; col = 21; offset = 47 }; - file = "test/golden/lambda.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = - [Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 3; col = 3; offset = 55 }; - end_pos = { Span.line = 3; col = 7; offset = 59 }; - file = "test/golden/lambda.affine" } - }); - sl_ty = None; - sl_value = - Ast.ExprLambda { - elam_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 11; offset = 63 }; - end_pos = { Span.line = 3; col = 12; offset = 64 }; - file = "test/golden/lambda.affine" } - }; - p_ty = Ast.TyHole } - ]; - elam_ret_ty = None; - elam_body = - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 13; offset = 65 }; - end_pos = - { Span.line = 3; col = 15; offset = 67 }; - file = "test/golden/lambda.affine" } - }), - Ast.OpAdd, - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 3; col = 17; offset = 69 }; - end_pos = - { Span.line = 3; col = 19; offset = 71 }; - file = "test/golden/lambda.affine" } - ))) - ))}} - ]; - blk_expr = - (Some (Ast.ExprApp ( - (Ast.ExprVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 3; col = 20; offset = 72 }; - end_pos = - { Span.line = 4; col = 3; offset = 76 }; - file = "test/golden/lambda.affine" } - }), - [(Ast.ExprLit - (Ast.LitInt (42, - { Span.start_pos = - { Span.line = 4; col = 4; offset = 77 }; - end_pos = - { Span.line = 4; col = 5; offset = 78 }; - file = "test/golden/lambda.affine" } - ))) - ] - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_lambda_typed"; - span = - { Span.start_pos = { Span.line = 7; col = 1; offset = 85 }; - end_pos = { Span.line = 7; col = 4; offset = 88 }; - file = "test/golden/lambda.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 7; col = 24; offset = 108 }; - end_pos = { Span.line = 7; col = 27; offset = 111 }; - file = "test/golden/lambda.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = - [Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 8; col = 3; offset = 119 }; - end_pos = { Span.line = 8; col = 7; offset = 123 }; - file = "test/golden/lambda.affine" } - }); - sl_ty = None; - sl_value = - Ast.ExprLambda { - elam_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 8; col = 11; offset = 127 }; - end_pos = { Span.line = 8; col = 12; offset = 128 }; - file = "test/golden/lambda.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 8; col = 13; offset = 129 }; - end_pos = - { Span.line = 8; col = 15; offset = 131 }; - file = "test/golden/lambda.affine" } - }) - } - ]; - elam_ret_ty = None; - elam_body = - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 8; col = 18; offset = 134 }; - end_pos = - { Span.line = 8; col = 20; offset = 136 }; - file = "test/golden/lambda.affine" } - }), - Ast.OpMul, - (Ast.ExprLit - (Ast.LitInt (2, - { Span.start_pos = - { Span.line = 8; col = 22; offset = 138 }; - end_pos = - { Span.line = 8; col = 24; offset = 140 }; - file = "test/golden/lambda.affine" } - ))) - ))}} - ]; - blk_expr = - (Some (Ast.ExprApp ( - (Ast.ExprVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 8; col = 25; offset = 141 }; - end_pos = - { Span.line = 9; col = 3; offset = 145 }; - file = "test/golden/lambda.affine" } - }), - [(Ast.ExprLit - (Ast.LitInt (21, - { Span.start_pos = - { Span.line = 9; col = 4; offset = 146 }; - end_pos = - { Span.line = 9; col = 5; offset = 147 }; - file = "test/golden/lambda.affine" } - ))) - ] - ))) - }) - }) - ] - } diff --git a/affinescript/test/golden/literals.affine b/affinescript/test/golden/literals.affine deleted file mode 100644 index bba50e7..0000000 --- a/affinescript/test/golden/literals.affine +++ /dev/null @@ -1,9 +0,0 @@ -// Test all literal types -fn test_literals() -> () { - let i = 42; - let f = 3.14; - let b = true; - let s = "hello"; - let c = 'x'; - let u = (); -} diff --git a/affinescript/test/golden/literals.expected b/affinescript/test/golden/literals.expected deleted file mode 100644 index 3fa75ec..0000000 --- a/affinescript/test/golden/literals.expected +++ /dev/null @@ -1,134 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_literals"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 26 }; - end_pos = { Span.line = 2; col = 4; offset = 29 }; - file = "test/golden/literals.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = - [Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "i"; - span = - { Span.start_pos = - { Span.line = 3; col = 3; offset = 55 }; - end_pos = { Span.line = 3; col = 7; offset = 59 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitInt (42, - { Span.start_pos = - { Span.line = 3; col = 9; offset = 61 }; - end_pos = { Span.line = 3; col = 11; offset = 63 }; - file = "test/golden/literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "f"; - span = - { Span.start_pos = - { Span.line = 4; col = 3; offset = 69 }; - end_pos = { Span.line = 4; col = 7; offset = 73 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitFloat (3.14, - { Span.start_pos = - { Span.line = 4; col = 9; offset = 75 }; - end_pos = { Span.line = 4; col = 11; offset = 77 }; - file = "test/golden/literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "b"; - span = - { Span.start_pos = - { Span.line = 5; col = 3; offset = 85 }; - end_pos = { Span.line = 5; col = 7; offset = 89 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitBool (true, - { Span.start_pos = - { Span.line = 5; col = 9; offset = 91 }; - end_pos = { Span.line = 5; col = 11; offset = 93 }; - file = "test/golden/literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "s"; - span = - { Span.start_pos = - { Span.line = 6; col = 3; offset = 101 }; - end_pos = { Span.line = 6; col = 7; offset = 105 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitString ("hello", - { Span.start_pos = - { Span.line = 6; col = 9; offset = 107 }; - end_pos = { Span.line = 6; col = 17; offset = 115 }; - file = "test/golden/literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "c"; - span = - { Span.start_pos = - { Span.line = 7; col = 3; offset = 120 }; - end_pos = { Span.line = 7; col = 7; offset = 124 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitChar ('x', - { Span.start_pos = - { Span.line = 7; col = 9; offset = 126 }; - end_pos = { Span.line = 7; col = 12; offset = 129 }; - file = "test/golden/literals.affine" } - )))}; - Ast.StmtLet {sl_mut = false; sl_quantity = None; - sl_pat = - (Ast.PatVar - { Ast.name = "u"; - span = - { Span.start_pos = - { Span.line = 8; col = 3; offset = 135 }; - end_pos = { Span.line = 8; col = 7; offset = 139 }; - file = "test/golden/literals.affine" } - }); - sl_ty = None; - sl_value = - (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 8; col = 9; offset = 141 }; - end_pos = { Span.line = 8; col = 12; offset = 144 }; - file = "test/golden/literals.affine" }))} - ]; - blk_expr = None }) - }) - ] - } diff --git a/affinescript/test/golden/ownership.affine b/affinescript/test/golden/ownership.affine deleted file mode 100644 index e0527c0..0000000 --- a/affinescript/test/golden/ownership.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test ownership modifiers -fn test_ownership(x: own String, y: ref Int, z: mut Bool) -> () { - () -} - -struct File { - fd: own Int -} diff --git a/affinescript/test/golden/ownership.expected b/affinescript/test/golden/ownership.expected deleted file mode 100644 index e14de0b..0000000 --- a/affinescript/test/golden/ownership.expected +++ /dev/null @@ -1,119 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_ownership"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 28 }; - end_pos = { Span.line = 2; col = 4; offset = 31 }; - file = "test/golden/ownership.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 2; col = 18; offset = 45 }; - end_pos = { Span.line = 2; col = 19; offset = 46 }; - file = "test/golden/ownership.affine" } - }; - p_ty = - (Ast.TyOwn - (Ast.TyCon - { Ast.name = "String"; - span = - { Span.start_pos = - { Span.line = 2; col = 22; offset = 49 }; - end_pos = { Span.line = 2; col = 26; offset = 53 }; - file = "test/golden/ownership.affine" } - })) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "y"; - span = - { Span.start_pos = { Span.line = 2; col = 32; offset = 59 }; - end_pos = { Span.line = 2; col = 34; offset = 61 }; - file = "test/golden/ownership.affine" } - }; - p_ty = - (Ast.TyRef - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 37; offset = 64 }; - end_pos = { Span.line = 2; col = 41; offset = 68 }; - file = "test/golden/ownership.affine" } - })) - }; - { Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "z"; - span = - { Span.start_pos = { Span.line = 2; col = 44; offset = 71 }; - end_pos = { Span.line = 2; col = 46; offset = 73 }; - file = "test/golden/ownership.affine" } - }; - p_ty = - (Ast.TyMut - (Ast.TyCon - { Ast.name = "Bool"; - span = - { Span.start_pos = - { Span.line = 2; col = 49; offset = 76 }; - end_pos = { Span.line = 2; col = 53; offset = 80 }; - file = "test/golden/ownership.affine" } - })) - } - ]; - fd_ret_ty = (Some (Ast.TyTuple [])); fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitUnit - { Span.start_pos = - { Span.line = 2; col = 65; offset = 92 }; - end_pos = { Span.line = 3; col = 4; offset = 97 }; - file = "test/golden/ownership.affine" }))) - }) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "File"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 102 }; - end_pos = { Span.line = 6; col = 8; offset = 109 }; - file = "test/golden/ownership.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyStruct - [{ Ast.sf_vis = Ast.Private; - sf_name = - { Ast.name = "fd"; - span = - { Span.start_pos = { Span.line = 6; col = 13; offset = 114 }; - end_pos = { Span.line = 7; col = 3; offset = 118 }; - file = "test/golden/ownership.affine" } - }; - sf_ty = - (Ast.TyOwn - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 7; col = 7; offset = 122 }; - end_pos = { Span.line = 7; col = 11; offset = 126 }; - file = "test/golden/ownership.affine" } - })) - } - ]) - }) - ] - } diff --git a/affinescript/test/golden/patterns.affine b/affinescript/test/golden/patterns.affine deleted file mode 100644 index fee417c..0000000 --- a/affinescript/test/golden/patterns.affine +++ /dev/null @@ -1,20 +0,0 @@ -// Test pattern matching -fn test_match() -> Int { - match x { - 0 => 1, - n => n + 1 - } -} - -fn test_destructure() -> Int { - match pair { - (a, b) => a + b - } -} - -fn test_constructor() -> Int { - match opt { - Some(x) => x, - None => 0 - } -} diff --git a/affinescript/test/golden/patterns.expected b/affinescript/test/golden/patterns.expected deleted file mode 100644 index 37e2fe3..0000000 --- a/affinescript/test/golden/patterns.expected +++ /dev/null @@ -1,276 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_match"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 25 }; - end_pos = { Span.line = 2; col = 4; offset = 28 }; - file = "test/golden/patterns.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 17; offset = 41 }; - end_pos = { Span.line = 2; col = 20; offset = 44 }; - file = "test/golden/patterns.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 3; offset = 52 }; - end_pos = { Span.line = 3; col = 9; offset = 58 }; - file = "test/golden/patterns.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatLit - (Ast.LitInt (0, - { Span.start_pos = - { Span.line = 3; col = 11; offset = 60 }; - end_pos = - { Span.line = 4; col = 5; offset = 66 }; - file = "test/golden/patterns.affine" } - ))); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 4; col = 7; offset = 68 }; - end_pos = - { Span.line = 4; col = 10; offset = 71 }; - file = "test/golden/patterns.affine" } - ))) - }; - { Ast.ma_pat = - (Ast.PatVar - { Ast.name = "n"; - span = - { Span.start_pos = - { Span.line = 4; col = 11; offset = 72 }; - end_pos = - { Span.line = 5; col = 5; offset = 78 }; - file = "test/golden/patterns.affine" } - }); - ma_guard = None; - ma_body = - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "n"; - span = - { Span.start_pos = - { Span.line = 5; col = 7; offset = 80 }; - end_pos = - { Span.line = 5; col = 10; offset = 83 }; - file = "test/golden/patterns.affine" } - }), - Ast.OpAdd, - (Ast.ExprLit - (Ast.LitInt (1, - { Span.start_pos = - { Span.line = 5; col = 12; offset = 85 }; - end_pos = - { Span.line = 5; col = 14; offset = 87 }; - file = "test/golden/patterns.affine" } - ))) - )) - } - ]}) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_destructure"; - span = - { Span.start_pos = { Span.line = 9; col = 1; offset = 96 }; - end_pos = { Span.line = 9; col = 4; offset = 99 }; - file = "test/golden/patterns.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 9; col = 23; offset = 118 }; - end_pos = { Span.line = 9; col = 26; offset = 121 }; - file = "test/golden/patterns.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "pair"; - span = - { Span.start_pos = - { Span.line = 10; col = 3; offset = 129 }; - end_pos = - { Span.line = 10; col = 9; offset = 135 }; - file = "test/golden/patterns.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatTuple - [(Ast.PatVar - { Ast.name = "a"; - span = - { Span.start_pos = - { Span.line = 11; col = 5; offset = 146 }; - end_pos = - { Span.line = 11; col = 6; offset = 147 }; - file = "test/golden/patterns.affine" } - }); - (Ast.PatVar - { Ast.name = "b"; - span = - { Span.start_pos = - { Span.line = 11; col = 7; offset = 148 - }; - end_pos = - { Span.line = 11; col = 9; offset = 150 - }; - file = "test/golden/patterns.affine" } - }) - ]); - ma_guard = None; - ma_body = - (Ast.ExprBinary ( - (Ast.ExprVar - { Ast.name = "a"; - span = - { Span.start_pos = - { Span.line = 11; col = 12; offset = 153 }; - end_pos = - { Span.line = 11; col = 15; offset = 156 }; - file = "test/golden/patterns.affine" } - }), - Ast.OpAdd, - (Ast.ExprVar - { Ast.name = "b"; - span = - { Span.start_pos = - { Span.line = 11; col = 17; offset = 158 }; - end_pos = - { Span.line = 11; col = 19; offset = 160 }; - file = "test/golden/patterns.affine" } - }) - )) - } - ]}) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "test_constructor"; - span = - { Span.start_pos = { Span.line = 15; col = 1; offset = 169 }; - end_pos = { Span.line = 15; col = 4; offset = 172 }; - file = "test/golden/patterns.affine" } - }; - fd_type_params = []; fd_params = []; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 15; col = 23; offset = 191 }; - end_pos = { Span.line = 15; col = 26; offset = 194 }; - file = "test/golden/patterns.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprMatch { - em_scrutinee = - (Ast.ExprVar - { Ast.name = "opt"; - span = - { Span.start_pos = - { Span.line = 16; col = 3; offset = 202 }; - end_pos = - { Span.line = 16; col = 9; offset = 208 }; - file = "test/golden/patterns.affine" } - }); - em_arms = - [{ Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "Some"; - span = - { Span.start_pos = - { Span.line = 16; col = 13; offset = 212 }; - end_pos = - { Span.line = 17; col = 5; offset = 218 }; - file = "test/golden/patterns.affine" } - }, - [(Ast.PatVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 17; col = 9; offset = 222 }; - end_pos = - { Span.line = 17; col = 10; offset = 223 - }; - file = "test/golden/patterns.affine" } - }) - ] - )); - ma_guard = None; - ma_body = - (Ast.ExprVar - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 17; col = 13; offset = 226 }; - end_pos = - { Span.line = 17; col = 16; offset = 229 }; - file = "test/golden/patterns.affine" } - }) - }; - { Ast.ma_pat = - (Ast.PatCon ( - { Ast.name = "None"; - span = - { Span.start_pos = - { Span.line = 17; col = 17; offset = 230 }; - end_pos = - { Span.line = 18; col = 5; offset = 236 }; - file = "test/golden/patterns.affine" } - }, - [])); - ma_guard = None; - ma_body = - (Ast.ExprLit - (Ast.LitInt (0, - { Span.start_pos = - { Span.line = 18; col = 10; offset = 241 }; - end_pos = - { Span.line = 18; col = 13; offset = 244 }; - file = "test/golden/patterns.affine" } - ))) - } - ]}) - }) - }) - ] - } diff --git a/affinescript/test/golden/rows.affine b/affinescript/test/golden/rows.affine deleted file mode 100644 index bacbcab..0000000 --- a/affinescript/test/golden/rows.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test row polymorphism -fn getX[..r](p: {x: Int, ..r}) -> Int { - p.x -} - -fn addY[..r](p: {..r}) -> {y: Int, ..r} { - {y: 0, ..p} -} diff --git a/affinescript/test/golden/rows.expected b/affinescript/test/golden/rows.expected deleted file mode 100644 index 3011684..0000000 --- a/affinescript/test/golden/rows.expected +++ /dev/null @@ -1,208 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "getX"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 25 }; - end_pos = { Span.line = 2; col = 4; offset = 28 }; - file = "test/golden/rows.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 2; col = 8; offset = 32 }; - end_pos = { Span.line = 2; col = 9; offset = 33 }; - file = "test/golden/rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "p"; - span = - { Span.start_pos = { Span.line = 2; col = 13; offset = 37 }; - end_pos = { Span.line = 2; col = 14; offset = 38 }; - file = "test/golden/rows.affine" } - }; - p_ty = - (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 2; col = 17; offset = 41 }; - end_pos = { Span.line = 2; col = 18; offset = 42 }; - file = "test/golden/rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 19; offset = 43 }; - end_pos = { Span.line = 2; col = 21; offset = 45 }; - file = "test/golden/rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 2; col = 24; offset = 48 }; - end_pos = { Span.line = 2; col = 26; offset = 50 }; - file = "test/golden/rows.affine" } - }) - )) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 2; col = 32; offset = 56 }; - end_pos = { Span.line = 2; col = 35; offset = 59 }; - file = "test/golden/rows.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprField ( - (Ast.ExprVar - { Ast.name = "p"; - span = - { Span.start_pos = - { Span.line = 2; col = 39; offset = 63 }; - end_pos = - { Span.line = 3; col = 3; offset = 67 }; - file = "test/golden/rows.affine" } - }), - { Ast.name = "x"; - span = - { Span.start_pos = - { Span.line = 3; col = 4; offset = 68 }; - end_pos = { Span.line = 3; col = 5; offset = 69 }; - file = "test/golden/rows.affine" } - } - ))) - }) - }); - (Ast.TopFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "addY"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 74 }; - end_pos = { Span.line = 6; col = 4; offset = 77 }; - file = "test/golden/rows.affine" } - }; - fd_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "r"; - span = - { Span.start_pos = { Span.line = 6; col = 8; offset = 81 }; - end_pos = { Span.line = 6; col = 9; offset = 82 }; - file = "test/golden/rows.affine" } - }; - tp_kind = (Some Ast.KRow) } - ]; - fd_params = - [{ Ast.p_quantity = None; p_ownership = None; - p_name = - { Ast.name = "p"; - span = - { Span.start_pos = { Span.line = 6; col = 13; offset = 86 }; - end_pos = { Span.line = 6; col = 14; offset = 87 }; - file = "test/golden/rows.affine" } - }; - p_ty = - (Ast.TyRecord ([], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 6; col = 17; offset = 90 }; - end_pos = { Span.line = 6; col = 18; offset = 91 }; - file = "test/golden/rows.affine" } - }) - )) - } - ]; - fd_ret_ty = - (Some (Ast.TyRecord ( - [{ Ast.rf_name = - { Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 6; col = 27; offset = 100 }; - end_pos = { Span.line = 6; col = 28; offset = 101 }; - file = "test/golden/rows.affine" } - }; - rf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 6; col = 29; offset = 102 }; - end_pos = - { Span.line = 6; col = 31; offset = 104 }; - file = "test/golden/rows.affine" } - }) - } - ], - (Some { Ast.name = "r"; - span = - { Span.start_pos = - { Span.line = 6; col = 34; offset = 107 }; - end_pos = - { Span.line = 6; col = 36; offset = 109 }; - file = "test/golden/rows.affine" } - }) - ))); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some Ast.ExprRecord { - er_fields = - [({ Ast.name = "y"; - span = - { Span.start_pos = - { Span.line = 7; col = 3; offset = 118 }; - end_pos = - { Span.line = 7; col = 4; offset = 119 }; - file = "test/golden/rows.affine" } - }, - (Some (Ast.ExprLit - (Ast.LitInt (0, - { Span.start_pos = - { Span.line = 7; col = 5; offset = 120 - }; - end_pos = - { Span.line = 7; col = 7; offset = 122 - }; - file = "test/golden/rows.affine" } - ))))) - ]; - er_spread = - (Some (Ast.ExprVar - { Ast.name = "p"; - span = - { Span.start_pos = - { Span.line = 7; col = 8; offset = 123 }; - end_pos = - { Span.line = 7; col = 10; offset = 125 }; - file = "test/golden/rows.affine" } - }))}) - }) - }) - ] - } diff --git a/affinescript/test/golden/traits.affine b/affinescript/test/golden/traits.affine deleted file mode 100644 index 3d1eb68..0000000 --- a/affinescript/test/golden/traits.affine +++ /dev/null @@ -1,14 +0,0 @@ -// Test trait declarations -trait Eq { - fn eq(ref self, ref other: Self) -> Bool; -} - -trait Ord: Eq { - fn cmp(ref self, ref other: Self) -> Ordering; -} - -impl Eq for Int { - fn eq(ref self, ref other: Int) -> Bool { - true - } -} diff --git a/affinescript/test/golden/traits.expected b/affinescript/test/golden/traits.expected deleted file mode 100644 index d6260e5..0000000 --- a/affinescript/test/golden/traits.expected +++ /dev/null @@ -1,260 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopTrait - { Ast.trd_vis = Ast.Private; - trd_name = - { Ast.name = "Eq"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 27 }; - end_pos = { Span.line = 2; col = 7; offset = 33 }; - file = "test/golden/traits.affine" } - }; - trd_type_params = []; trd_super = []; - trd_items = - [(Ast.TraitFn - { Ast.fs_vis = Ast.Private; - fs_name = - { Ast.name = "eq"; - span = - { Span.start_pos = { Span.line = 3; col = 3; offset = 40 }; - end_pos = { Span.line = 3; col = 6; offset = 43 }; - file = "test/golden/traits.affine" } - }; - fs_type_params = []; - fs_params = - [{ Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "self"; - span = - { Span.start_pos = { Span.line = 3; col = 8; offset = 45 }; - end_pos = { Span.line = 3; col = 13; offset = 50 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Self"; - span = - { Span.start_pos = - { Span.line = 3; col = 8; offset = 45 }; - end_pos = { Span.line = 3; col = 13; offset = 50 }; - file = "test/golden/traits.affine" } - }) - }; - { Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "other"; - span = - { Span.start_pos = - { Span.line = 3; col = 19; offset = 56 }; - end_pos = { Span.line = 3; col = 23; offset = 60 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Self"; - span = - { Span.start_pos = - { Span.line = 3; col = 28; offset = 65 }; - end_pos = { Span.line = 3; col = 30; offset = 67 }; - file = "test/golden/traits.affine" } - }) - } - ]; - fs_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Bool"; - span = - { Span.start_pos = - { Span.line = 3; col = 36; offset = 73 }; - end_pos = { Span.line = 3; col = 39; offset = 76 }; - file = "test/golden/traits.affine" } - })); - fs_eff = None }) - ] - }); - (Ast.TopTrait - { Ast.trd_vis = Ast.Private; - trd_name = - { Ast.name = "Ord"; - span = - { Span.start_pos = { Span.line = 6; col = 1; offset = 85 }; - end_pos = { Span.line = 6; col = 7; offset = 91 }; - file = "test/golden/traits.affine" } - }; - trd_type_params = []; - trd_super = - [{ Ast.tb_name = - { Ast.name = "Eq"; - span = - { Span.start_pos = { Span.line = 6; col = 10; offset = 94 }; - end_pos = { Span.line = 6; col = 12; offset = 96 }; - file = "test/golden/traits.affine" } - }; - tb_args = [] } - ]; - trd_items = - [(Ast.TraitFn - { Ast.fs_vis = Ast.Private; - fs_name = - { Ast.name = "cmp"; - span = - { Span.start_pos = { Span.line = 7; col = 3; offset = 103 }; - end_pos = { Span.line = 7; col = 6; offset = 106 }; - file = "test/golden/traits.affine" } - }; - fs_type_params = []; - fs_params = - [{ Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "self"; - span = - { Span.start_pos = - { Span.line = 7; col = 9; offset = 109 }; - end_pos = { Span.line = 7; col = 14; offset = 114 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Self"; - span = - { Span.start_pos = - { Span.line = 7; col = 9; offset = 109 }; - end_pos = { Span.line = 7; col = 14; offset = 114 }; - file = "test/golden/traits.affine" } - }) - }; - { Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "other"; - span = - { Span.start_pos = - { Span.line = 7; col = 20; offset = 120 }; - end_pos = { Span.line = 7; col = 24; offset = 124 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Self"; - span = - { Span.start_pos = - { Span.line = 7; col = 29; offset = 129 }; - end_pos = { Span.line = 7; col = 31; offset = 131 }; - file = "test/golden/traits.affine" } - }) - } - ]; - fs_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Ordering"; - span = - { Span.start_pos = - { Span.line = 7; col = 37; offset = 137 }; - end_pos = - { Span.line = 7; col = 40; offset = 140 }; - file = "test/golden/traits.affine" } - })); - fs_eff = None }) - ] - }); - (Ast.TopImpl - { Ast.ib_type_params = []; - ib_trait_ref = - (Some { Ast.tr_name = - { Ast.name = "Eq"; - span = - { Span.start_pos = - { Span.line = 10; col = 1; offset = 153 }; - end_pos = { Span.line = 10; col = 6; offset = 158 }; - file = "test/golden/traits.affine" } - }; - tr_args = [] }); - ib_self_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = { Span.line = 10; col = 9; offset = 161 }; - end_pos = { Span.line = 10; col = 13; offset = 165 }; - file = "test/golden/traits.affine" } - }); - ib_where = []; - ib_items = - [(Ast.ImplFn - { Ast.fd_vis = Ast.Private; fd_total = false; - fd_name = - { Ast.name = "eq"; - span = - { Span.start_pos = { Span.line = 11; col = 3; offset = 173 }; - end_pos = { Span.line = 11; col = 6; offset = 176 }; - file = "test/golden/traits.affine" } - }; - fd_type_params = []; - fd_params = - [{ Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "self"; - span = - { Span.start_pos = - { Span.line = 11; col = 8; offset = 178 }; - end_pos = { Span.line = 11; col = 13; offset = 183 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Self"; - span = - { Span.start_pos = - { Span.line = 11; col = 8; offset = 178 }; - end_pos = { Span.line = 11; col = 13; offset = 183 }; - file = "test/golden/traits.affine" } - }) - }; - { Ast.p_quantity = None; p_ownership = (Some Ast.Ref); - p_name = - { Ast.name = "other"; - span = - { Span.start_pos = - { Span.line = 11; col = 19; offset = 189 }; - end_pos = { Span.line = 11; col = 23; offset = 193 }; - file = "test/golden/traits.affine" } - }; - p_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 11; col = 28; offset = 198 }; - end_pos = - { Span.line = 11; col = 30; offset = 200 }; - file = "test/golden/traits.affine" } - }) - } - ]; - fd_ret_ty = - (Some (Ast.TyCon - { Ast.name = "Bool"; - span = - { Span.start_pos = - { Span.line = 11; col = 35; offset = 205 }; - end_pos = - { Span.line = 11; col = 38; offset = 208 }; - file = "test/golden/traits.affine" } - })); - fd_eff = None; fd_where = []; - fd_body = - (Ast.FnBlock - { Ast.blk_stmts = []; - blk_expr = - (Some (Ast.ExprLit - (Ast.LitBool (true, - { Span.start_pos = - { Span.line = 11; col = 43; offset = 213 }; - end_pos = - { Span.line = 12; col = 5; offset = 219 }; - file = "test/golden/traits.affine" } - )))) - }) - }) - ] - }) - ] - } diff --git a/affinescript/test/golden/types.affine b/affinescript/test/golden/types.affine deleted file mode 100644 index efb439e..0000000 --- a/affinescript/test/golden/types.affine +++ /dev/null @@ -1,12 +0,0 @@ -// Test type declarations -type Alias = Int; - -struct Point { - x: Int, - y: Int -} - -enum Option[T] { - None, - Some(T) -} diff --git a/affinescript/test/golden/types.expected b/affinescript/test/golden/types.expected deleted file mode 100644 index 72980a3..0000000 --- a/affinescript/test/golden/types.expected +++ /dev/null @@ -1,125 +0,0 @@ -{ Ast.prog_module = None; prog_imports = []; - prog_decls = - [(Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Alias"; - span = - { Span.start_pos = { Span.line = 2; col = 1; offset = 26 }; - end_pos = { Span.line = 2; col = 6; offset = 31 }; - file = "test/golden/types.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyAlias - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = { Span.line = 2; col = 12; offset = 37 }; - end_pos = { Span.line = 2; col = 14; offset = 39 }; - file = "test/golden/types.affine" } - })) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Point"; - span = - { Span.start_pos = { Span.line = 4; col = 1; offset = 45 }; - end_pos = { Span.line = 4; col = 8; offset = 52 }; - file = "test/golden/types.affine" } - }; - td_type_params = []; - td_body = - (Ast.TyStruct - [{ Ast.sf_vis = Ast.Private; - sf_name = - { Ast.name = "x"; - span = - { Span.start_pos = { Span.line = 4; col = 14; offset = 58 }; - end_pos = { Span.line = 5; col = 3; offset = 62 }; - file = "test/golden/types.affine" } - }; - sf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 5; col = 4; offset = 63 }; - end_pos = { Span.line = 5; col = 6; offset = 65 }; - file = "test/golden/types.affine" } - }) - }; - { Ast.sf_vis = Ast.Private; - sf_name = - { Ast.name = "y"; - span = - { Span.start_pos = { Span.line = 5; col = 9; offset = 68 }; - end_pos = { Span.line = 6; col = 3; offset = 72 }; - file = "test/golden/types.affine" } - }; - sf_ty = - (Ast.TyCon - { Ast.name = "Int"; - span = - { Span.start_pos = - { Span.line = 6; col = 4; offset = 73 }; - end_pos = { Span.line = 6; col = 6; offset = 75 }; - file = "test/golden/types.affine" } - }) - } - ]) - }); - (Ast.TopType - { Ast.td_vis = Ast.Private; - td_name = - { Ast.name = "Option"; - span = - { Span.start_pos = { Span.line = 9; col = 1; offset = 82 }; - end_pos = { Span.line = 9; col = 6; offset = 87 }; - file = "test/golden/types.affine" } - }; - td_type_params = - [{ Ast.tp_quantity = None; - tp_name = - { Ast.name = "T"; - span = - { Span.start_pos = { Span.line = 9; col = 12; offset = 93 }; - end_pos = { Span.line = 9; col = 13; offset = 94 }; - file = "test/golden/types.affine" } - }; - tp_kind = None } - ]; - td_body = - (Ast.TyEnum - [{ Ast.vd_name = - { Ast.name = "None"; - span = - { Span.start_pos = { Span.line = 9; col = 16; offset = 97 }; - end_pos = { Span.line = 10; col = 3; offset = 101 }; - file = "test/golden/types.affine" } - }; - vd_fields = []; vd_ret_ty = None }; - { Ast.vd_name = - { Ast.name = "Some"; - span = - { Span.start_pos = - { Span.line = 10; col = 7; offset = 105 }; - end_pos = { Span.line = 11; col = 3; offset = 109 }; - file = "test/golden/types.affine" } - }; - vd_fields = - [(Ast.TyCon - { Ast.name = "T"; - span = - { Span.start_pos = - { Span.line = 11; col = 7; offset = 113 }; - end_pos = { Span.line = 11; col = 8; offset = 114 }; - file = "test/golden/types.affine" } - }) - ]; - vd_ret_ty = None } - ]) - }) - ] - } diff --git a/affinescript/test/test_e2e.ml b/affinescript/test/test_e2e.ml deleted file mode 100644 index 86f1da2..0000000 --- a/affinescript/test/test_e2e.ml +++ /dev/null @@ -1,2574 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* Copyright (c) 2026 Jonathan D.A. Jewell *) - -(** End-to-end integration tests for the AffineScript compiler pipeline. - - These tests validate the full source-to-output pipeline: - source -> lex -> parse -> resolve -> typecheck -> quantitycheck - -> wasm codegen - -> julia codegen - -> interpreter - - Each test fixture exercises a specific language feature and verifies - that every applicable compiler stage handles it correctly. -*) - -open Affinescript - -(* ============================================================================ - Test Utilities - ============================================================================ *) - -(* Fixture directory. When running via [dune test], the CWD is - [_build/default/test/]; when running via [dune exec], it is the - project root. We probe both locations. *) -let fixture_dir = - if Sys.file_exists "e2e/fixtures" then "e2e/fixtures" - else "test/e2e/fixtures" - -(** Read file contents *) -let read_file path = - let chan = open_in path in - let content = really_input_string chan (in_channel_length chan) in - close_in chan; - content - -(** Temporary file for WASM output *) -let with_temp_file suffix f = - let tmp = Filename.temp_file "affinescript_e2e" suffix in - Fun.protect ~finally:(fun () -> - if Sys.file_exists tmp then Sys.remove tmp - ) (fun () -> f tmp) - -(** Fixture path helper *) -let fixture path = Filename.concat fixture_dir path - -(* ============================================================================ - Pipeline Stage Runners - ============================================================================ *) - -(** Stage 1: Parse a fixture file and return the AST *) -let parse_fixture path = - try - Ok (Parse_driver.parse_file path) - with - | Parse_driver.Parse_error (msg, span) -> - Error (Printf.sprintf "Parse error at %s: %s" (Span.show span) msg) - | Lexer.Lexer_error (msg, pos) -> - Error (Printf.sprintf "Lexer error at %d:%d: %s" pos.line pos.col msg) - | e -> - Error (Printf.sprintf "Unexpected error: %s" (Printexc.to_string e)) - -(** Stage 2: Resolve names in the parsed AST *) -let resolve_program prog = - let loader_config = Module_loader.default_config () in - let loader = Module_loader.create loader_config in - match Resolve.resolve_program_with_loader prog loader with - | Ok (resolve_ctx, type_ctx) -> Ok (resolve_ctx, type_ctx) - | Error (e, _span) -> - Error (Printf.sprintf "Resolution error: %s" - (Resolve.show_resolve_error e)) - -(** Stage 3: Type-check the resolved program *) -let typecheck_program symbols prog = - match Typecheck.check_program symbols prog with - | Ok ctx -> Ok ctx - | Error e -> - Error (Printf.sprintf "Type error: %s" - (Typecheck.format_type_error e)) - -(** Stage 4: Quantity-check the program (affine/linear enforcement) *) -let quantity_check_program symbols prog = - match Quantity.check_program symbols prog with - | Ok () -> Ok () - | Error (e, _span) -> - Error (Printf.sprintf "Quantity error: %s" - (Quantity.format_quantity_error e)) - -(** Stage 5a: Generate WASM output *) -let wasm_codegen prog = - let optimized = Opt.fold_constants_program prog in - match Codegen.generate_module optimized with - | Ok wasm_module -> Ok wasm_module - | Error e -> - Error (Printf.sprintf "WASM codegen error: %s" - (Codegen.show_codegen_error e)) - -(** Stage 5b: Generate Julia output *) -let julia_codegen prog symbols = - match Julia_codegen.codegen_julia prog symbols with - | Ok code -> Ok code - | Error e -> - Error (Printf.sprintf "Julia codegen error: %s" e) - -(** Stage 5c: Interpret the program *) -let interpret_program prog = - match Interp.eval_program prog with - | Ok env -> Ok env - | Error e -> - Error (Printf.sprintf "Interpreter error: %s" - (Value.show_eval_error e)) - -(* ============================================================================ - Full Pipeline Runners - ============================================================================ *) - -(** Run through parse -> resolve -> typecheck *) -let run_frontend path = - let open Result in - let ( let* ) = bind in - let* prog = parse_fixture path in - let* (resolve_ctx, _type_ctx) = resolve_program prog in - let* _tc_ctx = typecheck_program resolve_ctx.symbols prog in - Ok (prog, resolve_ctx) - -(** Run full pipeline through WASM codegen *) -let run_wasm_pipeline path = - let open Result in - let ( let* ) = bind in - let* (prog, _resolve_ctx) = run_frontend path in - let* wasm_module = wasm_codegen prog in - Ok wasm_module - -(** Run full pipeline through Julia codegen *) -let run_julia_pipeline path = - let open Result in - let ( let* ) = bind in - let* (prog, resolve_ctx) = run_frontend path in - let* julia_code = julia_codegen prog resolve_ctx.symbols in - Ok julia_code - -(** Run full pipeline through interpreter *) -let run_interp_pipeline path = - let open Result in - let ( let* ) = bind in - let* (prog, _resolve_ctx) = run_frontend path in - let* env = interpret_program prog in - Ok env - -(* ============================================================================ - Section 1: Parsing Tests - ============================================================================ - - These tests verify that fixture files parse without errors and produce - non-trivial ASTs with the expected number of declarations. -*) - -let test_parse_arithmetic () = - match parse_fixture (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check int) "declaration count" 6 (List.length prog.prog_decls) - -let test_parse_affine_basic () = - match parse_fixture (fixture "affine_basic.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check int) "declaration count" 4 (List.length prog.prog_decls) - -let test_parse_dependent_types () = - match parse_fixture (fixture "dependent_types.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - (* 1 type decl + 3 functions *) - Alcotest.(check int) "declaration count" 4 (List.length prog.prog_decls) - -let test_parse_refinement_types () = - match parse_fixture (fixture "refinement_types.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check bool) "has declarations" true - (List.length prog.prog_decls > 0) - -let test_parse_traits () = - match parse_fixture (fixture "traits.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - (* Count trait decls, impls, struct, enum, and functions *) - let has_trait = List.exists (fun d -> - match d with Ast.TopTrait _ -> true | _ -> false - ) prog.prog_decls in - let has_impl = List.exists (fun d -> - match d with Ast.TopImpl _ -> true | _ -> false - ) prog.prog_decls in - Alcotest.(check bool) "has traits" true has_trait; - Alcotest.(check bool) "has impls" true has_impl - -let test_parse_effects () = - match parse_fixture (fixture "effects.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - let has_effect = List.exists (fun d -> - match d with Ast.TopEffect _ -> true | _ -> false - ) prog.prog_decls in - Alcotest.(check bool) "has effects" true has_effect - -let test_parse_ownership () = - match parse_fixture (fixture "ownership.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check bool) "has declarations" true - (List.length prog.prog_decls > 0) - -let test_parse_row_polymorphism () = - match parse_fixture (fixture "row_polymorphism.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check int) "declaration count" 4 (List.length prog.prog_decls) - -let test_parse_pattern_match () = - match parse_fixture (fixture "pattern_match.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check bool) "has declarations" true - (List.length prog.prog_decls > 0) - -let test_parse_type_decls () = - match parse_fixture (fixture "type_decls.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - let has_type = List.exists (fun d -> - match d with Ast.TopType _ -> true | _ -> false - ) prog.prog_decls in - Alcotest.(check bool) "has type decls" true has_type - -let test_parse_lambda () = - match parse_fixture (fixture "lambda.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - Alcotest.(check bool) "has declarations" true - (List.length prog.prog_decls > 0) - -let test_parse_full_pipeline () = - match parse_fixture (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - (* struct Vec2, enum Shape, 4 functions + main *) - Alcotest.(check bool) "has many declarations" true - (List.length prog.prog_decls >= 6) - -let parse_tests = [ - Alcotest.test_case "arithmetic" `Quick test_parse_arithmetic; - Alcotest.test_case "affine_basic" `Quick test_parse_affine_basic; - Alcotest.test_case "dependent_types" `Quick test_parse_dependent_types; - Alcotest.test_case "refinement_types" `Quick test_parse_refinement_types; - Alcotest.test_case "traits" `Quick test_parse_traits; - Alcotest.test_case "effects" `Quick test_parse_effects; - Alcotest.test_case "ownership" `Quick test_parse_ownership; - Alcotest.test_case "row_polymorphism" `Quick test_parse_row_polymorphism; - Alcotest.test_case "pattern_match" `Quick test_parse_pattern_match; - Alcotest.test_case "type_decls" `Quick test_parse_type_decls; - Alcotest.test_case "lambda" `Quick test_parse_lambda; - Alcotest.test_case "full_pipeline" `Quick test_parse_full_pipeline; -] - -(* ============================================================================ - Section 2: Name Resolution Tests - ============================================================================ - - These tests verify that parsed programs pass name resolution, - populating the symbol table correctly. -*) - -let test_resolve_arithmetic () = - match parse_fixture (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - (* All function names should be resolved *) - Alcotest.(check bool) "symbols populated" true - (Symbol.lookup ctx.symbols "add" <> None) - -let test_resolve_traits () = - match parse_fixture (fixture "traits.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (_ctx, _) -> - (* Resolution should succeed without errors *) - () - -let test_resolve_effects () = - match parse_fixture (fixture "effects.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (_ctx, _) -> () - -let test_resolve_ownership () = - match parse_fixture (fixture "ownership.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (_ctx, _) -> () - -let test_resolve_type_decls () = - match parse_fixture (fixture "type_decls.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - (* Type names should be resolved *) - Alcotest.(check bool) "Point resolved" true - (Symbol.lookup ctx.symbols "Point" <> None) - -let test_resolve_full_pipeline () = - match parse_fixture (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - Alcotest.(check bool) "main resolved" true - (Symbol.lookup ctx.symbols "main" <> None); - Alcotest.(check bool) "area resolved" true - (Symbol.lookup ctx.symbols "area" <> None) - -let resolve_tests = [ - Alcotest.test_case "arithmetic" `Quick test_resolve_arithmetic; - Alcotest.test_case "traits" `Quick test_resolve_traits; - Alcotest.test_case "effects" `Quick test_resolve_effects; - Alcotest.test_case "ownership" `Quick test_resolve_ownership; - Alcotest.test_case "type_decls" `Quick test_resolve_type_decls; - Alcotest.test_case "full_pipeline" `Quick test_resolve_full_pipeline; -] - -(* ============================================================================ - Section 3: Type Checking Tests - ============================================================================ - - These tests verify that programs pass through the type checker. - Note: typecheck.ml is currently a stub that accepts everything, so - these tests validate the pipeline wiring rather than type correctness. - When the type checker is fully implemented, these become true tests. -*) - -let test_typecheck_arithmetic () = - match run_frontend (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_typecheck_traits () = - match run_frontend (fixture "traits.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_typecheck_effects () = - match run_frontend (fixture "effects.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_typecheck_ownership () = - match run_frontend (fixture "ownership.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_typecheck_full_pipeline () = - match run_frontend (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let typecheck_tests = [ - Alcotest.test_case "arithmetic" `Quick test_typecheck_arithmetic; - Alcotest.test_case "traits" `Quick test_typecheck_traits; - Alcotest.test_case "effects" `Quick test_typecheck_effects; - Alcotest.test_case "ownership" `Quick test_typecheck_ownership; - Alcotest.test_case "full_pipeline" `Quick test_typecheck_full_pipeline; -] - -(* ============================================================================ - Section 4: Quantity (Affine Type) Checking Tests - ============================================================================ - - These tests validate the quantitative type theory enforcement: - - QOne (linear/affine): variable must be used at most once - - QZero (erased): variable must not be used at runtime - - QOmega (unrestricted): no restriction -*) - -let test_quantity_affine_valid () = - match parse_fixture (fixture "affine_basic.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match quantity_check_program ctx.symbols prog with - | Ok () -> () - | Error msg -> Alcotest.fail msg - -let test_quantity_affine_violation () = - match parse_fixture (fixture "affine_violation.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match quantity_check_program ctx.symbols prog with - | Ok () -> - (* If the quantity checker does not yet catch this, skip gracefully *) - () - | Error msg -> - (* Expected: double use of linear variable should be an error *) - Alcotest.(check bool) "error mentions linear" - true (String.length msg > 0) - -let test_quantity_erased_violation () = - match parse_fixture (fixture "erased_violation.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match quantity_check_program ctx.symbols prog with - | Ok () -> - (* If the quantity checker does not yet catch this, skip gracefully *) - () - | Error msg -> - (* Expected: erased variable used at runtime should be an error *) - Alcotest.(check bool) "error mentions erased" - true (String.length msg > 0) - -(* ──── BUG-001 / ADR-007 regression cases ────────────────────────────────── - The four fixtures cover the cross product of {must-reject, must-accept} - × {Option C @linear primary form, Option B :1 sugar form}. Both surface - forms must produce identical enforcement, which proves the hybrid - syntax is wired through the same code path. *) - -let test_bug_001_smuggles_linear_attr_form () = - match parse_fixture (fixture "bug_001_omega_let_smuggles_linear.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> - Alcotest.fail "BUG-001 (attr form): expected quantity rejection of \ - @unrestricted let smuggling a @linear value, but the \ - checker accepted the program" - | Error e -> - let msg = Typecheck.format_type_error e in - Alcotest.(check bool) "error mentions @linear vocabulary" true - (try let _ = Str.search_forward (Str.regexp "@linear") msg 0 in true - with Not_found -> false) - -let test_bug_001_smuggles_linear_sugar_form () = - match parse_fixture (fixture "bug_001_sugar_form.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> - Alcotest.fail "BUG-001 (sugar form): expected quantity rejection of \ - :ω let smuggling a @linear value, but the checker \ - accepted the program" - | Error e -> - let msg = Typecheck.format_type_error e in - Alcotest.(check bool) "error mentions @linear vocabulary" true - (try let _ = Str.search_forward (Str.regexp "@linear") msg 0 in true - with Not_found -> false) - -let test_affine_let_valid_attr_form () = - match parse_fixture (fixture "affine_let_valid.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> () - | Error e -> - Alcotest.fail (Printf.sprintf - "valid @linear let case rejected: %s" - (Typecheck.format_type_error e)) - -let test_affine_let_valid_sugar_form () = - match parse_fixture (fixture "affine_let_valid_sugar.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> () - | Error e -> - Alcotest.fail (Printf.sprintf - "valid :1 let case rejected: %s" - (Typecheck.format_type_error e)) - -let quantity_tests = [ - Alcotest.test_case "valid affine usage" `Quick test_quantity_affine_valid; - Alcotest.test_case "affine double use" `Quick test_quantity_affine_violation; - Alcotest.test_case "erased usage" `Quick test_quantity_erased_violation; - Alcotest.test_case "BUG-001 attr form rejects ω-let smuggling @linear" - `Quick test_bug_001_smuggles_linear_attr_form; - Alcotest.test_case "BUG-001 sugar form rejects :ω let smuggling @linear" - `Quick test_bug_001_smuggles_linear_sugar_form; - Alcotest.test_case "valid @linear let accepts" `Quick test_affine_let_valid_attr_form; - Alcotest.test_case "valid :1 let accepts" `Quick test_affine_let_valid_sugar_form; -] - -(* ============================================================================ - Section 4b: Linear Arrow Tests - - These tests verify that quantity annotations on lambda parameters are - enforced correctly: - - - Lambda synth: |@linear x: T| body now produces T -[1]-> U, not T -[ω]-> U - - Lambda body: @linear param double-use inside a lambda body is rejected - - Valid single-use passes without error - - Regression coverage for the linear arrow enforcement PR. -*) - -(** Valid: a lambda with a @linear param used exactly once passes the - quantity checker. *) -let test_linear_arrow_valid () = - match parse_fixture (fixture "linear_arrow.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> () - | Error e -> - Alcotest.fail (Printf.sprintf - "valid @linear lambda param rejected: %s" - (Typecheck.format_type_error e)) - -(** Violation: a lambda with a @linear param used twice must be rejected. - Verifies that the lambda param quantity checker (added alongside the - linear arrow synth fix) correctly catches body-level violations. *) -let test_linear_arrow_lambda_double_use () = - match parse_fixture (fixture "linear_arrow_violation.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> - Alcotest.fail - "expected rejection: @linear lambda param used twice should be \ - a quantity error, but the checker accepted it" - | Error e -> - let msg = Typecheck.format_type_error e in - Alcotest.(check bool) "error mentions @linear" true - (try let _ = Str.search_forward (Str.regexp "@linear") msg 0 in true - with Not_found -> false) - -let linear_arrow_tests = [ - Alcotest.test_case "valid @linear lambda param accepted" - `Quick test_linear_arrow_valid; - Alcotest.test_case "@linear lambda param double-use rejected" - `Quick test_linear_arrow_lambda_double_use; -] - -(* ============================================================================ - Section 5: WASM Backend Tests - ============================================================================ - - These tests validate WebAssembly code generation from parsed programs. - The test verifies that: - 1. The WASM module is generated without errors - 2. The generated binary can be written to a file - 3. The binary starts with a valid WASM magic number -*) - -let test_wasm_arithmetic () = - match run_wasm_pipeline (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok _wasm_mod -> () - -let test_wasm_simple () = - match run_wasm_pipeline (fixture "wasm_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - (* Verify the module has functions *) - Alcotest.(check bool) "has functions" true - (List.length wasm_mod.Wasm.funcs > 0); - (* Verify the module has exports *) - Alcotest.(check bool) "has exports" true - (List.length wasm_mod.Wasm.exports > 0) - -let test_wasm_write_binary () = - match run_wasm_pipeline (fixture "wasm_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - with_temp_file ".wasm" (fun tmp_path -> - (* Write the WASM binary *) - Wasm_encode.write_module_to_file tmp_path wasm_mod; - (* Verify the file exists and has content *) - let stat = Unix.stat tmp_path in - Alcotest.(check bool) "file has content" true - (stat.Unix.st_size > 0); - (* Read and check WASM magic number *) - let ic = open_in_bin tmp_path in - let magic = really_input_string ic 4 in - close_in ic; - (* WASM magic is \x00asm but our encoder writes " asm" *) - Alcotest.(check int) "magic byte length" 4 - (String.length magic) - ) - -let test_wasm_full_pipeline () = - match run_wasm_pipeline (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - Alcotest.(check bool) "has functions" true - (List.length wasm_mod.Wasm.funcs > 0); - with_temp_file ".wasm" (fun tmp_path -> - Wasm_encode.write_module_to_file tmp_path wasm_mod; - let stat = Unix.stat tmp_path in - Alcotest.(check bool) "non-empty binary" true - (stat.Unix.st_size > 0) - ) - -let test_wasm_lambda () = - match run_wasm_pipeline (fixture "lambda.affine") with - | Error msg -> Alcotest.fail msg - | Ok _wasm_mod -> () - -let wasm_tests = [ - Alcotest.test_case "arithmetic codegen" `Quick test_wasm_arithmetic; - Alcotest.test_case "simple program" `Quick test_wasm_simple; - Alcotest.test_case "write binary" `Quick test_wasm_write_binary; - Alcotest.test_case "full pipeline" `Quick test_wasm_full_pipeline; - Alcotest.test_case "lambda codegen" `Quick test_wasm_lambda; -] - -(* ============================================================================ - Section 6: Julia Backend Tests - ============================================================================ - - These tests validate Julia code generation: - 1. Julia code is produced without errors - 2. The output contains expected Julia constructs - 3. Function signatures map correctly -*) - -let test_julia_arithmetic () = - match run_julia_pipeline (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - (* Julia code should contain function definitions *) - Alcotest.(check bool) "contains function keyword" true - (String.length code > 0) - -let test_julia_simple () = - match run_julia_pipeline (fixture "julia_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "non-empty output" true - (String.length code > 0); - (* Check for Julia-style function definitions *) - let has_function = try - let _ = Str.search_forward (Str.regexp "function") code 0 in true - with Not_found -> false in - Alcotest.(check bool) "has function keyword" true has_function - -let test_julia_type_mapping () = - match run_julia_pipeline (fixture "julia_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - (* Check type mapping: Int -> Int64 *) - let has_int64 = try - let _ = Str.search_forward (Str.regexp "Int64") code 0 in true - with Not_found -> false in - Alcotest.(check bool) "maps Int to Int64" true has_int64 - -let test_julia_write_output () = - match run_julia_pipeline (fixture "julia_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - with_temp_file ".jl" (fun tmp_path -> - let oc = open_out tmp_path in - output_string oc code; - close_out oc; - let stat = Unix.stat tmp_path in - Alcotest.(check bool) "file has content" true - (stat.Unix.st_size > 0) - ) - -let test_julia_full_pipeline () = - match run_julia_pipeline (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "non-empty output" true - (String.length code > 0) - -let julia_tests = [ - Alcotest.test_case "arithmetic codegen" `Quick test_julia_arithmetic; - Alcotest.test_case "simple program" `Quick test_julia_simple; - Alcotest.test_case "type mapping" `Quick test_julia_type_mapping; - Alcotest.test_case "write output" `Quick test_julia_write_output; - Alcotest.test_case "full pipeline" `Quick test_julia_full_pipeline; -] - -(* ============================================================================ - Section 7: Interpreter Tests - ============================================================================ - - These tests validate the tree-walking interpreter: - 1. Simple programs evaluate successfully - 2. The environment contains expected bindings -*) - -let test_interp_simple () = - match run_interp_pipeline (fixture "interp_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok _env -> () - -let test_interp_arithmetic () = - match run_interp_pipeline (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok _env -> () - -let test_interp_lambda () = - match run_interp_pipeline (fixture "lambda.affine") with - | Error msg -> Alcotest.fail msg - | Ok _env -> () - -let test_interp_full_pipeline () = - match run_interp_pipeline (fixture "full_pipeline.affine") with - | Error msg -> Alcotest.fail msg - | Ok _env -> () - -let interp_tests = [ - Alcotest.test_case "simple evaluation" `Quick test_interp_simple; - Alcotest.test_case "arithmetic" `Quick test_interp_arithmetic; - Alcotest.test_case "lambda" `Quick test_interp_lambda; - Alcotest.test_case "full pipeline" `Quick test_interp_full_pipeline; -] - -(* ============================================================================ - Section 8: Optimizer Tests - ============================================================================ - - These tests validate the optimization passes: - 1. Constant folding reduces known expressions - 2. Optimization preserves semantics (same AST shape for non-constant exprs) -*) - -let test_opt_constant_folding () = - match parse_fixture (fixture "arithmetic.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - let optimized = Opt.fold_constants_program prog in - (* The optimized program should still have the same number of declarations *) - Alcotest.(check int) "same decl count" - (List.length prog.prog_decls) - (List.length optimized.prog_decls) - -let test_opt_preserves_semantics () = - match parse_fixture (fixture "interp_simple.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - let optimized = Opt.fold_constants_program prog in - (* Both should interpret successfully *) - (match Interp.eval_program prog, Interp.eval_program optimized with - | Ok _, Ok _ -> () - | Error e, _ -> - Alcotest.fail (Printf.sprintf "Original failed: %s" - (Value.show_eval_error e)) - | _, Error e -> - Alcotest.fail (Printf.sprintf "Optimized failed: %s" - (Value.show_eval_error e))) - -let optimizer_tests = [ - Alcotest.test_case "constant folding" `Quick test_opt_constant_folding; - Alcotest.test_case "preserves semantics" `Quick test_opt_preserves_semantics; -] - -(* ============================================================================ - Section 9: Full Pipeline Integration Tests - ============================================================================ - - These tests run the complete pipeline from source to all backends - and verify consistency across outputs. -*) - -let test_full_pipeline_all_stages () = - let path = fixture "full_pipeline.affine" in - - (* Stage 1: Parse *) - let prog = match parse_fixture path with - | Error msg -> Alcotest.fail (Printf.sprintf "Parse: %s" msg) - | Ok p -> p - in - Alcotest.(check bool) "parsed" true (List.length prog.prog_decls > 0); - - (* Stage 2: Resolve *) - let (resolve_ctx, _type_ctx) = match resolve_program prog with - | Error msg -> Alcotest.fail (Printf.sprintf "Resolve: %s" msg) - | Ok r -> r - in - - (* Stage 3: Typecheck *) - (match typecheck_program resolve_ctx.symbols prog with - | Error msg -> Alcotest.fail (Printf.sprintf "Typecheck: %s" msg) - | Ok _ -> ()); - - (* Stage 4: Optimize *) - let optimized = Opt.fold_constants_program prog in - Alcotest.(check int) "optimization preserves decls" - (List.length prog.prog_decls) - (List.length optimized.prog_decls); - - (* Stage 5a: WASM codegen *) - (match Codegen.generate_module optimized with - | Error e -> - Alcotest.fail (Printf.sprintf "WASM codegen: %s" - (Codegen.show_codegen_error e)) - | Ok wasm_mod -> - Alcotest.(check bool) "WASM has functions" true - (List.length wasm_mod.Wasm.funcs > 0); - (* Write to temp file to verify binary encoding *) - with_temp_file ".wasm" (fun tmp -> - Wasm_encode.write_module_to_file tmp wasm_mod; - let stat = Unix.stat tmp in - Alcotest.(check bool) "WASM binary non-empty" true - (stat.Unix.st_size > 0))); - - (* Stage 5b: Julia codegen *) - (match Julia_codegen.codegen_julia prog resolve_ctx.symbols with - | Error e -> - Alcotest.fail (Printf.sprintf "Julia codegen: %s" e) - | Ok code -> - Alcotest.(check bool) "Julia output non-empty" true - (String.length code > 0)); - - (* Stage 5c: Interpreter *) - (match Interp.eval_program prog with - | Error e -> - Alcotest.fail (Printf.sprintf "Interpreter: %s" - (Value.show_eval_error e)) - | Ok _env -> ()) - -let test_full_pipeline_wasm_roundtrip () = - let path = fixture "wasm_simple.affine" in - match run_wasm_pipeline path with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - with_temp_file ".wasm" (fun tmp -> - (* Write *) - Wasm_encode.write_module_to_file tmp wasm_mod; - (* Verify file properties *) - let stat = Unix.stat tmp in - let size = stat.Unix.st_size in - Alcotest.(check bool) "reasonable size" true - (size > 8 && size < 1_000_000)) - -let test_full_pipeline_julia_roundtrip () = - let path = fixture "julia_simple.affine" in - match run_julia_pipeline path with - | Error msg -> Alcotest.fail msg - | Ok code -> - with_temp_file ".jl" (fun tmp -> - let oc = open_out tmp in - output_string oc code; - close_out oc; - (* Re-read and verify *) - let content = read_file tmp in - Alcotest.(check int) "roundtrip size matches" - (String.length code) (String.length content)) - -let full_pipeline_tests = [ - Alcotest.test_case "all stages" `Quick test_full_pipeline_all_stages; - Alcotest.test_case "wasm roundtrip" `Quick test_full_pipeline_wasm_roundtrip; - Alcotest.test_case "julia roundtrip" `Quick test_full_pipeline_julia_roundtrip; -] - -(* ============================================================================ - Section 10: Error Path Tests - ============================================================================ - - These tests verify that the compiler correctly rejects malformed input. -*) - -let test_error_parse_bad_syntax () = - let source = "fn (" in - match Parse_driver.parse_string ~file:"" source with - | exception Parse_driver.Parse_error _ -> () - | exception Lexer.Lexer_error _ -> () - | _ -> Alcotest.fail "Expected parse error for bad syntax" - -let test_error_parse_unclosed_brace () = - let source = "fn foo() -> Int { 42" in - match Parse_driver.parse_string ~file:"" source with - | exception Parse_driver.Parse_error _ -> () - | exception Lexer.Lexer_error _ -> () - | _ -> Alcotest.fail "Expected parse error for unclosed brace" - -let test_error_parse_missing_arrow () = - let source = "fn foo() Int { 42 }" in - match Parse_driver.parse_string ~file:"" source with - | exception Parse_driver.Parse_error _ -> () - | exception Lexer.Lexer_error _ -> () - | _ -> Alcotest.fail "Expected parse error for missing arrow" - -let error_tests = [ - Alcotest.test_case "bad syntax" `Quick test_error_parse_bad_syntax; - Alcotest.test_case "unclosed brace" `Quick test_error_parse_unclosed_brace; - Alcotest.test_case "missing arrow" `Quick test_error_parse_missing_arrow; -] - -(* ============================================================================ - Section 11: Python-Face Parser Tests - ============================================================================ - - These tests verify that the Python-face transformer correctly maps - Python-style surface syntax to canonical AffineScript and that the - resulting AST is structurally equivalent to the equivalent canonical - source. -*) - -(** Parse Python-face source and return the program or a failure message. *) -let parse_python src = - try Ok (Python_face.parse_string_python ~file:"" src) - with - | Parse_driver.Parse_error (msg, span) -> - Error (Printf.sprintf "parse error at %s: %s" (Span.show span) msg) - | Lexer.Lexer_error (msg, pos) -> - Error (Printf.sprintf "lexer error at %d:%d: %s" pos.line pos.col msg) - -(** Verify [pyface_src] produces the same number of top-level declarations as - [canonical_src]. *) -let check_same_decl_count ~name pyface_src canonical_src = - let py_prog = match parse_python pyface_src with - | Ok p -> p - | Error e -> Alcotest.fail (Printf.sprintf "%s (python-face): %s" name e) - in - let can_prog = match Parse_driver.parse_string ~file:"" canonical_src with - | p -> p - | exception Parse_driver.Parse_error (msg, _) -> - Alcotest.fail (Printf.sprintf "%s (canonical): parse error: %s" name msg) - in - Alcotest.(check int) (name ^ " decl count") - (List.length can_prog.prog_decls) - (List.length py_prog.prog_decls) - -let test_python_face_def_to_fn () = - (* `def` maps to `fn` — function declaration parses to one TopFn *) - let src = "def add(x: Int, y: Int) -> Int:\n x + y\n" in - match parse_python src with - | Error e -> Alcotest.fail e - | Ok prog -> - Alcotest.(check int) "one top-level fn" 1 (List.length prog.prog_decls) - -let test_python_face_if_else () = - (* if/elif/else chain produces one function with an if-else expression *) - check_same_decl_count - ~name:"if-elif-else" - {| -def classify(n: Int) -> String: - if n > 0: - "positive" - elif n < 0: - "negative" - else: - "zero" -|} - {| -fn classify(n: Int) -> String { - if n > 0 { "positive" } - else if n < 0 { "negative" } - else { "zero" } -} -|} - -let test_python_face_keywords () = - (* True/False/None/and/or/not/pass all transform correctly *) - let src = {| -def check(a: Bool, b: Bool) -> Bool: - a and not b -|} in - match parse_python src with - | Error e -> Alcotest.fail e - | Ok prog -> - Alcotest.(check int) "one fn" 1 (List.length prog.prog_decls) - -let test_python_face_fixture () = - (* The full basic fixture file parses without error *) - let path = fixture "python_face_basic.pyaff" in - match Python_face.parse_file_python path with - | exception Parse_driver.Parse_error (msg, span) -> - Alcotest.fail (Printf.sprintf "parse error at %s: %s" (Span.show span) msg) - | exception Lexer.Lexer_error (msg, pos) -> - Alcotest.fail (Printf.sprintf "lexer error at %d:%d: %s" pos.line pos.col msg) - | prog -> - Alcotest.(check int) "three top-level fns" 3 (List.length prog.prog_decls) - -let test_python_face_transform_preview () = - (* The text transform produces the expected canonical structure. *) - let src = "def foo(x: Int) -> Int:\n x + 1\n" in - let canonical = Python_face.preview_transform src in - (* Should contain `fn` (not `def`) and `{` *) - Alcotest.(check bool) "contains fn" true - (let len = String.length canonical in - let rec find i = - if i >= len - 2 then false - else if canonical.[i] = 'f' && canonical.[i+1] = 'n' && canonical.[i+2] = ' ' - then true - else find (i + 1) - in find 0); - Alcotest.(check bool) "contains brace" true - (String.contains canonical '{') - -let python_face_tests = [ - Alcotest.test_case "def → fn" `Quick test_python_face_def_to_fn; - Alcotest.test_case "if/elif/else chain" `Quick test_python_face_if_else; - Alcotest.test_case "keyword substitution" `Quick test_python_face_keywords; - Alcotest.test_case "fixture parses (3 fns)" `Quick test_python_face_fixture; - Alcotest.test_case "transform preview" `Quick test_python_face_transform_preview; -] - -(* ============================================================================ - Section N: Stage 2 — Ownership Schema Round-Trip Tests - ============================================================================ - - Verify that AffineScript ownership qualifiers (own/ref/mut) survive codegen - and appear in the [affinescript.ownership] Wasm custom section. - - Kind encoding (matches Codegen.ownership_kind): - 0 = Unrestricted (plain value) - 1 = Linear (own / TyOwn — typed-wasm Level 10) - 2 = SharedBorrow (ref / TyRef — typed-wasm Level 7) - 3 = ExclBorrow (mut / TyMut — typed-wasm Level 7) -*) - -(** Find the [affinescript.ownership] custom section payload, if present *) -let find_ownership_section (wasm_mod : Wasm.wasm_module) : bytes option = - List.assoc_opt "affinescript.ownership" wasm_mod.Wasm.custom_sections - -(** Parse the ownership section payload into structured entries. - Returns a list of (func_index, param_kinds, return_kind) tuples. *) -let parse_ownership_section (payload : bytes) : (int * int list * int) list = - let pos = ref 0 in - let read_u32_le () = - let b0 = Char.code (Bytes.get payload !pos) in - let b1 = Char.code (Bytes.get payload (!pos + 1)) in - let b2 = Char.code (Bytes.get payload (!pos + 2)) in - let b3 = Char.code (Bytes.get payload (!pos + 3)) in - pos := !pos + 4; - b0 lor (b1 lsl 8) lor (b2 lsl 16) lor (b3 lsl 24) - in - let read_u8 () = - let b = Char.code (Bytes.get payload !pos) in - pos := !pos + 1; - b - in - let count = read_u32_le () in - List.init count (fun _ -> - let func_idx = read_u32_le () in - let n_params = read_u8 () in - let param_kinds = List.init n_params (fun _ -> read_u8 ()) in - let ret_kind = read_u8 () in - (func_idx, param_kinds, ret_kind) - ) - -let test_ownership_section_present () = - match run_wasm_pipeline (fixture "ownership_codegen.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - match find_ownership_section wasm_mod with - | None -> - Alcotest.fail "Expected [affinescript.ownership] custom section, none found" - | Some payload -> - Alcotest.(check bool) "section payload is non-empty" true - (Bytes.length payload > 0) - -let test_ownership_roundtrip () = - (* Fixture: consume_owned(x: own Int), borrow_ref(y: ref Int), - borrow_mut(z: mut Int), plain(n: Int) — four functions. - After codegen, the ownership section must record: - consume_owned → param_kinds = [1] (Linear) - borrow_ref → param_kinds = [2] (SharedBorrow) - borrow_mut → param_kinds = [3] (ExclBorrow) - plain → param_kinds = [0] (Unrestricted) *) - match run_wasm_pipeline (fixture "ownership_codegen.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - match find_ownership_section wasm_mod with - | None -> Alcotest.fail "No [affinescript.ownership] section in compiled output" - | Some payload -> - let entries = parse_ownership_section payload in - (* At least one function must have a Linear (1) param — TyOwn survived *) - let has_linear = List.exists (fun (_, param_kinds, _) -> - List.mem 1 param_kinds - ) entries in - Alcotest.(check bool) "TyOwn survived as Linear (kind=1)" true has_linear; - (* At least one SharedBorrow (2) — TyRef survived *) - let has_shared = List.exists (fun (_, param_kinds, _) -> - List.mem 2 param_kinds - ) entries in - Alcotest.(check bool) "TyRef survived as SharedBorrow (kind=2)" true has_shared; - (* At least one ExclBorrow (3) — TyMut survived *) - let has_excl = List.exists (fun (_, param_kinds, _) -> - List.mem 3 param_kinds - ) entries in - Alcotest.(check bool) "TyMut survived as ExclBorrow (kind=3)" true has_excl - -let test_ownership_entry_count () = - (* ownership_codegen.affine defines 4 functions; all 4 should be recorded *) - match run_wasm_pipeline (fixture "ownership_codegen.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - match find_ownership_section wasm_mod with - | None -> Alcotest.fail "No ownership section" - | Some payload -> - let entries = parse_ownership_section payload in - Alcotest.(check bool) "at least 4 entries (one per function)" true - (List.length entries >= 4) - -let ownership_schema_tests = [ - Alcotest.test_case "section present" `Quick test_ownership_section_present; - Alcotest.test_case "round-trip kinds" `Quick test_ownership_roundtrip; - Alcotest.test_case "entry count" `Quick test_ownership_entry_count; -] - -(* ============================================================================ - Section 11b: E2E TEA Bridge — Stage 4 Wasm Bridge Generator - ============================================================================ - - Tests for [Tea_bridge.generate()], which emits a valid Wasm 1.0 module - implementing the TitleScreen TEA state machine with clean i32 exports. - - Covered: - 1. Module structure: correct function, memory, and export counts. - 2. update() msg parameter marked Linear in the ownership section. - 3. tea_layout custom section present with the expected field count. - 4. Wasm binary round-trip: encoded bytes start with the correct magic. - 5. update branchless: selected = msg + 1 after affinescript_update(n). -*) - -(** Parse the [affinescript.ownership] custom section from a Wasm binary - and return (func_idx, param_kinds, return_kind) entries, or [] on failure. *) -let parse_ownership_from_bytes (raw : bytes) : (int * int list * int) list = - (* Scan for the custom section with name "affinescript.ownership" *) - let target = "affinescript.ownership" in - let rlen = Bytes.length raw in - let found = ref [] in - let i = ref 8 in (* skip magic + version *) - while !i < rlen - 4 do - let section_id = Char.code (Bytes.get raw !i) in - (* read LEB128 section size *) - let read_leb pos = - let v = ref 0 and shift = ref 0 and p = ref pos in - (try while !p < rlen do - let b = Char.code (Bytes.get raw !p) in - incr p; - v := !v lor ((b land 0x7f) lsl !shift); - shift := !shift + 7; - if b land 0x80 = 0 then raise Exit - done with Exit -> ()); - (!v, !p) - in - let (sec_size, after_size) = read_leb (!i + 1) in - let sec_end = after_size + sec_size in - if section_id = 0 && sec_end <= rlen then begin - (* read custom section name *) - let (name_len, after_nlen) = read_leb after_size in - if after_nlen + name_len <= sec_end then begin - let name = Bytes.sub_string raw after_nlen name_len in - if name = target then begin - (* found: parse entries *) - let p = ref (after_nlen + name_len) in - (* read u32 LE entry count *) - let read_u32_le pos = - let b0 = Char.code (Bytes.get raw pos) in - let b1 = Char.code (Bytes.get raw (pos + 1)) in - let b2 = Char.code (Bytes.get raw (pos + 2)) in - let b3 = Char.code (Bytes.get raw (pos + 3)) in - b0 lor (b1 lsl 8) lor (b2 lsl 16) lor (b3 lsl 24) - in - let count = read_u32_le !p in p := !p + 4; - for _ = 1 to count do - let fidx = read_u32_le !p in p := !p + 4; - let pcnt = Char.code (Bytes.get raw !p) in incr p; - let params = List.init pcnt (fun _ -> - let k = Char.code (Bytes.get raw !p) in incr p; k - ) in - let ret = Char.code (Bytes.get raw !p) in incr p; - found := (fidx, params, ret) :: !found - done - end - end - end; - i := max sec_end (!i + 1) - done; - List.rev !found - -(** Encode the bridge module to bytes via a temp file. *) -let encode_bridge_to_bytes () : bytes = - let m = Tea_bridge.generate () in - let path = Filename.temp_file "tea_bridge" ".wasm" in - Wasm_encode.write_module_to_file path m; - let ic = open_in_bin path in - let n = in_channel_length ic in - let raw = Bytes.create n in - really_input ic raw 0 n; - close_in ic; - (try Sys.remove path with _ -> ()); - raw - -(** Structure: 7 functions, 1 memory, 8 exports. *) -let test_bridge_structure () = - let m = Tea_bridge.generate () in - Alcotest.(check int) "7 functions" 7 (List.length m.funcs); - Alcotest.(check int) "1 memory" 1 (List.length m.mems); - Alcotest.(check int) "8 exports" 8 (List.length m.exports) - -(** All 7 expected exports are present by name. *) -let test_bridge_export_names () = - let m = Tea_bridge.generate () in - let names = List.map (fun e -> e.Wasm.e_name) m.exports in - let expected = [ - "affinescript_init"; "affinescript_update"; - "affinescript_get_screen_w"; "affinescript_get_screen_h"; - "affinescript_get_bgm_playing"; "affinescript_get_selected"; - "affinescript_set_screen"; "memory"; - ] in - List.iter (fun ex -> - Alcotest.(check bool) - (Printf.sprintf "export '%s' present" ex) - true (List.mem ex names) - ) expected - -(** Two custom sections present: ownership + tea_layout. *) -let test_bridge_custom_sections () = - let m = Tea_bridge.generate () in - Alcotest.(check int) "2 custom sections" 2 (List.length m.custom_sections); - let names = List.map fst m.custom_sections in - Alcotest.(check bool) "ownership section" true - (List.mem "affinescript.ownership" names); - Alcotest.(check bool) "tea_layout section" true - (List.mem "affinescript.tea_layout" names) - -(** Wasm binary starts with correct magic \x00asm + version \x01\x00\x00\x00. *) -let test_bridge_wasm_magic () = - let raw = encode_bridge_to_bytes () in - let magic = Bytes.sub raw 0 4 in - let version = Bytes.sub raw 4 4 in - Alcotest.(check bytes) "Wasm magic" (Bytes.of_string "\x00asm") magic; - Alcotest.(check bytes) "Wasm version" (Bytes.of_string "\x01\x00\x00\x00") version - -(** update's msg parameter (func 1, param 0) is Linear (kind byte = 1). *) -let test_bridge_update_msg_linear () = - let raw = encode_bridge_to_bytes () in - let entries = parse_ownership_from_bytes raw in - (* find entry for func 1 *) - match List.assoc_opt 1 (List.map (fun (f, p, r) -> (f, (p, r))) entries) with - | None -> - Alcotest.fail "no ownership entry for func 1 (update)" - | Some (params, _ret) -> - (match params with - | [kind] -> - Alcotest.(check int) "update msg param is Linear (1)" 1 kind - | _ -> - Alcotest.fail - (Printf.sprintf "expected 1 param for update, got %d" (List.length params))) - -(** tea_layout section is non-empty and starts with version byte 1. *) -let test_bridge_tea_layout_section () = - let m = Tea_bridge.generate () in - match List.assoc_opt "affinescript.tea_layout" m.custom_sections with - | None -> - Alcotest.fail "affinescript.tea_layout section missing" - | Some payload -> - Alcotest.(check bool) "payload non-empty" true (Bytes.length payload > 0); - let version_byte = Char.code (Bytes.get payload 0) in - Alcotest.(check int) "layout version byte = 1" 1 version_byte - -(** Stage 8: TEA bridge module passes typed-wasm Level 7/10 verification. - [fn_update] uses [LocalGet 0] (msg) exactly once — per-path analysis - gives min=1, max=1 → OK. *) -let test_bridge_ownership_verify () = - let m = Tea_bridge.generate () in - match Tw_verify.verify_from_module m with - | Ok () -> () (* expected *) - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_verify.pp_error e) errs) in - Alcotest.fail (Printf.sprintf "TEA bridge failed ownership verification: %s" msg) - -let tea_bridge_tests = [ - Alcotest.test_case "structure (7 funcs, 1 mem, 8 exports)" `Quick test_bridge_structure; - Alcotest.test_case "export names all present" `Quick test_bridge_export_names; - Alcotest.test_case "two custom sections" `Quick test_bridge_custom_sections; - Alcotest.test_case "Wasm binary magic + version" `Quick test_bridge_wasm_magic; - Alcotest.test_case "update msg param is Linear" `Quick test_bridge_update_msg_linear; - Alcotest.test_case "tea_layout section present + versioned" `Quick test_bridge_tea_layout_section; - Alcotest.test_case "ownership verify: clean" `Quick test_bridge_ownership_verify; -] - -(* ============================================================================ - Section 11b: E2E TEA Router — Cadre Router Wasm Module - ============================================================================ - - Verifies the Tea_router.generate () output satisfies all structural, - ownership, layout, and round-trip invariants needed for Stage 6. - - Tests cover: - 1. Module structure (11 funcs, 1 mem, 12 exports) - 2. All 12 export names present - 3. Two custom sections (ownership + tea_layout) - 4. Wasm binary magic + version - 5. push param is Linear (kind=1 in ownership section) - 6. present_popup param is Linear - 7. resize params are both Linear - 8. Push / pop round-trip: push Title(0), pop → stack_len returns to 0 -*) - -(** Encode the router module to bytes via a temp file (mirrors encode_bridge_to_bytes). *) -let encode_router_to_bytes () : bytes = - let m = Tea_router.generate () in - let path = Filename.temp_file "tea_router" ".wasm" in - Wasm_encode.write_module_to_file path m; - let ic = open_in_bin path in - let n = in_channel_length ic in - let raw = Bytes.create n in - really_input ic raw 0 n; - close_in ic; - (try Sys.remove path with _ -> ()); - raw - -(** Structure: 11 functions, 1 memory, 12 exports. *) -let test_router_structure () = - let m = Tea_router.generate () in - Alcotest.(check int) "11 functions" 11 (List.length m.funcs); - Alcotest.(check int) "1 memory" 1 (List.length m.mems); - Alcotest.(check int) "12 exports" 12 (List.length m.exports) - -(** All 12 expected exports are present by name. *) -let test_router_export_names () = - let m = Tea_router.generate () in - let names = List.map (fun e -> e.Wasm.e_name) m.exports in - let expected = [ - "affinescript_router_init"; - "affinescript_router_push"; - "affinescript_router_pop"; - "affinescript_router_present_popup"; - "affinescript_router_dismiss_popup"; - "affinescript_router_resize"; - "affinescript_router_get_screen_w"; - "affinescript_router_get_screen_h"; - "affinescript_router_get_stack_len"; - "affinescript_router_get_stack_top"; - "affinescript_router_get_popup_tag"; - "memory"; - ] in - List.iter (fun ex -> - Alcotest.(check bool) - (Printf.sprintf "export '%s' present" ex) - true (List.mem ex names) - ) expected - -(** Two custom sections: ownership + tea_layout. *) -let test_router_custom_sections () = - let m = Tea_router.generate () in - Alcotest.(check int) "2 custom sections" 2 (List.length m.custom_sections); - let names = List.map fst m.custom_sections in - Alcotest.(check bool) "ownership section" true - (List.mem "affinescript.ownership" names); - Alcotest.(check bool) "tea_layout section" true - (List.mem "affinescript.tea_layout" names) - -(** Wasm binary starts with correct magic \x00asm + version \x01\x00\x00\x00. *) -let test_router_wasm_magic () = - let raw = encode_router_to_bytes () in - let magic = Bytes.sub raw 0 4 in - let version = Bytes.sub raw 4 4 in - Alcotest.(check bytes) "Wasm magic" (Bytes.of_string "\x00asm") magic; - Alcotest.(check bytes) "Wasm version" (Bytes.of_string "\x01\x00\x00\x00") version - -(** fn_push (func 1): param 0 (screen_tag) must be Linear (kind byte = 1). *) -let test_router_push_param_linear () = - let raw = encode_router_to_bytes () in - let entries = parse_ownership_from_bytes raw in - match List.assoc_opt 1 (List.map (fun (f, p, r) -> (f, (p, r))) entries) with - | None -> - Alcotest.fail "no ownership entry for func 1 (push)" - | Some (params, _ret) -> - (match params with - | [kind] -> - Alcotest.(check int) "push screen_tag is Linear (1)" 1 kind - | _ -> - Alcotest.fail - (Printf.sprintf "expected 1 param for push, got %d" (List.length params))) - -(** fn_present_popup (func 3): param 0 (popup_tag) must be Linear. *) -let test_router_present_popup_param_linear () = - let raw = encode_router_to_bytes () in - let entries = parse_ownership_from_bytes raw in - match List.assoc_opt 3 (List.map (fun (f, p, r) -> (f, (p, r))) entries) with - | None -> - Alcotest.fail "no ownership entry for func 3 (present_popup)" - | Some (params, _ret) -> - (match params with - | [kind] -> - Alcotest.(check int) "present_popup popup_tag is Linear (1)" 1 kind - | _ -> - Alcotest.fail - (Printf.sprintf "expected 1 param for present_popup, got %d" (List.length params))) - -(** fn_resize (func 5): both params (w, h) must be Linear. *) -let test_router_resize_params_linear () = - let raw = encode_router_to_bytes () in - let entries = parse_ownership_from_bytes raw in - match List.assoc_opt 5 (List.map (fun (f, p, r) -> (f, (p, r))) entries) with - | None -> - Alcotest.fail "no ownership entry for func 5 (resize)" - | Some (params, _ret) -> - (match params with - | [k0; k1] -> - Alcotest.(check int) "resize w is Linear (1)" 1 k0; - Alcotest.(check int) "resize h is Linear (1)" 1 k1 - | _ -> - Alcotest.fail - (Printf.sprintf "expected 2 params for resize, got %d" (List.length params))) - -(** tea_layout section is non-empty and starts with version byte 1. *) -let test_router_tea_layout_section () = - let m = Tea_router.generate () in - match List.assoc_opt "affinescript.tea_layout" m.custom_sections with - | None -> - Alcotest.fail "affinescript.tea_layout section missing" - | Some payload -> - Alcotest.(check bool) "payload non-empty" true (Bytes.length payload > 0); - let version_byte = Char.code (Bytes.get payload 0) in - Alcotest.(check int) "layout version byte = 1" 1 version_byte - -(** Stage 9: Router bridge module passes typed-wasm Level 7/10 verification. - fn_push: then-branch stores LocalGet 0, else-branch explicitly [LocalGet 0; Drop]. - Per-path analysis: min(1,1)=1, max(1,1)=1 → OK. *) -let test_router_ownership_verify () = - let m = Tea_router.generate () in - match Tw_verify.verify_from_module m with - | Ok () -> () (* expected *) - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_verify.pp_error e) errs) in - Alcotest.fail (Printf.sprintf "Router bridge failed ownership verification: %s" msg) - -let tea_router_tests = [ - Alcotest.test_case "structure (11 funcs, 1 mem, 12 exports)" `Quick test_router_structure; - Alcotest.test_case "export names all present" `Quick test_router_export_names; - Alcotest.test_case "two custom sections" `Quick test_router_custom_sections; - Alcotest.test_case "Wasm binary magic + version" `Quick test_router_wasm_magic; - Alcotest.test_case "push screen_tag param is Linear" `Quick test_router_push_param_linear; - Alcotest.test_case "present_popup param is Linear" `Quick test_router_present_popup_param_linear; - Alcotest.test_case "resize w+h params are both Linear" `Quick test_router_resize_params_linear; - Alcotest.test_case "tea_layout section present + versioned" `Quick test_router_tea_layout_section; - Alcotest.test_case "ownership verify: clean" `Quick test_router_ownership_verify; -] - -(* ============================================================================ - Section 12: E2E Traits — Registry, Method Dispatch, Body Checking - ============================================================================ - - These tests verify the trait resolution pipeline wired in the type checker: - - 1. A valid impl (all required methods provided) passes type checking. - 2. An impl that omits a required method is rejected with a descriptive - error that names the missing method. - - Regression coverage for the trait-registry wiring PR. -*) - -(** Valid impl: an impl block that satisfies its trait in full must be - accepted by the type checker without error. *) -let test_trait_impl_valid () = - match parse_fixture (fixture "trait_impl_valid.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> () (* Expected: impl accepted *) - | Error e -> - Alcotest.fail (Printf.sprintf - "valid trait impl unexpectedly rejected: %s" - (Typecheck.format_type_error e)) - -(** Missing method: an impl block that omits a non-default required method - must be rejected by the type checker. The error must mention the - missing method name so the user knows what to fix. *) -let test_trait_impl_missing_method () = - match parse_fixture (fixture "trait_impl_missing_method.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - match Typecheck.check_program ctx.symbols prog with - | Ok _ -> - Alcotest.fail - "expected rejection: impl omitting a required trait method should \ - be a type error, but the checker accepted the program" - | Error e -> - (* The error must mention the omitted method name *) - let msg = Typecheck.format_type_error e in - Alcotest.(check bool) "error mentions 'summary'" true - (try let _ = Str.search_forward (Str.regexp "summary") msg 0 in true - with Not_found -> false) - -let trait_impl_tests = [ - Alcotest.test_case "valid impl accepted" - `Quick test_trait_impl_valid; - Alcotest.test_case "missing method rejected" - `Quick test_trait_impl_missing_method; -] - -(* ============================================================================ - Section N+1: Stage 3 — TEA stdlib (The Elm Architecture) tests - ============================================================================ - - Verify: - 3a — Cmd/Sub/Html enum types parse and type-check - 3b — enum constructors are bound at runtime (nullary + payload) - 3c — counter.afs: init→0, update(Increment)→1, update(Decrement)→0 - 3d — titlescreen.afs compiles without errors (interpreter-level) -*) - -(** Run eval pipeline through to the interpreter env, then call main() with - stdin from a string. Returns the (stdout, exit code) pair. *) -let run_tea_program_with_input fixture_name input_lines = - (* Write the test input to a temp file and redirect stdin *) - let input = String.concat "\n" input_lines ^ "\n" in - let tmp_in = Filename.temp_file "affinescript_tea_in" "" in - let oc = open_out tmp_in in - output_string oc input; - close_out oc; - let output_buf = Buffer.create 64 in - let saved_stdout = Unix.dup Unix.stdout in - let tmp_out = Filename.temp_file "affinescript_tea_out" "" in - let fd_out = Unix.openfile tmp_out [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC] 0o600 in - Unix.dup2 fd_out Unix.stdout; - Unix.close fd_out; - let saved_stdin = Unix.dup Unix.stdin in - let fd_in = Unix.openfile tmp_in [Unix.O_RDONLY] 0o600 in - Unix.dup2 fd_in Unix.stdin; - Unix.close fd_in; - let result = (match run_wasm_pipeline (fixture fixture_name) with - | Error _ -> `Failed (* type/compile error before running *) - | Ok _ -> - match run_frontend (fixture fixture_name) with - | Error _ -> `Failed - | Ok (prog, _) -> - (match Interp.eval_program prog with - | Error _ -> `Failed - | Ok env -> - (match Value.lookup_env "main" env with - | Error _ -> `NoMain - | Ok main_fn -> - (match Interp.apply_function main_fn [] with - | Ok _ -> `Ok - | Error _ -> `RuntimeError)))) in - Unix.dup2 saved_stdout Unix.stdout; - Unix.close saved_stdout; - Unix.dup2 saved_stdin Unix.stdin; - Unix.close saved_stdin; - let ic = open_in tmp_out in - (try while true do - Buffer.add_string output_buf (input_line ic); Buffer.add_char output_buf '\n' - done with End_of_file -> ()); - close_in ic; - Sys.remove tmp_in; Sys.remove tmp_out; - (Buffer.contents output_buf, result) - -(** Simpler helper: just check the program compiles and evals without error *) -let test_tea_counter_compiles () = - (* counter.affine should parse, type-check, and run without errors *) - match run_frontend (fixture "counter.affine") with - | Error msg -> Alcotest.fail ("Frontend error: " ^ msg) - | Ok (prog, _) -> - match Interp.eval_program prog with - | Error e -> Alcotest.fail ("Eval error: " ^ Value.show_eval_error e) - | Ok _ -> () (* definitions loaded into env — success *) - -let test_tea_counter_init () = - (* counter_init() should return 0 *) - match run_frontend (fixture "counter.affine") with - | Error msg -> Alcotest.fail msg - | Ok (prog, _) -> - match Interp.eval_program prog with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok env -> - match Value.lookup_env "counter_init" env with - | Error _ -> Alcotest.fail "counter_init not found in env" - | Ok init_fn -> - match Interp.apply_function init_fn [] with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok v -> - Alcotest.(check int) "counter starts at 0" 0 - (match v with Value.VInt n -> n | _ -> -1) - -let test_tea_counter_update () = - (* counter_update(Increment, 0) should return 1; - counter_update(Decrement, 1) should return 0 *) - match run_frontend (fixture "counter.affine") with - | Error msg -> Alcotest.fail msg - | Ok (prog, _) -> - match Interp.eval_program prog with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok env -> - match Value.lookup_env "counter_update" env with - | Error _ -> Alcotest.fail "counter_update not found" - | Ok update_fn -> - let incr = Value.VVariant ("Increment", None) in - let decr = Value.VVariant ("Decrement", None) in - let model0 = Value.VInt 0 in - (match Interp.apply_function update_fn [incr; model0] with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok v1 -> - Alcotest.(check int) "Increment 0 → 1" 1 - (match v1 with Value.VInt n -> n | _ -> -1); - match Interp.apply_function update_fn [decr; v1] with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok v2 -> - Alcotest.(check int) "Decrement 1 → 0" 0 - (match v2 with Value.VInt n -> n | _ -> -1)) - -let test_tea_titlescreen_compiles () = - (* titlescreen.affine should parse, type-check, and eval without errors *) - match run_frontend (fixture "titlescreen.affine") with - | Error msg -> Alcotest.fail ("Frontend error: " ^ msg) - | Ok (prog, _) -> - match Interp.eval_program prog with - | Error e -> Alcotest.fail ("Eval error: " ^ Value.show_eval_error e) - | Ok _ -> () - -let test_tea_titlescreen_update () = - (* title_update(NewGame, init_model) should set selected = "new_game" *) - match run_frontend (fixture "titlescreen.affine") with - | Error msg -> Alcotest.fail msg - | Ok (prog, _) -> - match Interp.eval_program prog with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok env -> - let get n = match Value.lookup_env n env with - | Ok v -> v | Error _ -> Alcotest.fail (n ^ " not found") in - let init_fn = get "title_init" in - let update_fn = get "title_update" in - match Interp.apply_function init_fn [] with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok model -> - let new_game_msg = Value.VVariant ("NewGame", None) in - (match Interp.apply_function update_fn [new_game_msg; model] with - | Error e -> Alcotest.fail (Value.show_eval_error e) - | Ok new_model -> - (* selected field should be "new_game" *) - let selected = match new_model with - | Value.VRecord fields -> - (match List.assoc_opt "selected" fields with - | Some (Value.VString s) -> s - | _ -> "?") - | _ -> "?" - in - Alcotest.(check string) "NewGame sets selected=new_game" - "new_game" selected) - -let tea_tests = [ - Alcotest.test_case "counter compiles" `Quick test_tea_counter_compiles; - Alcotest.test_case "counter init=0" `Quick test_tea_counter_init; - Alcotest.test_case "counter update transitions" `Quick test_tea_counter_update; - Alcotest.test_case "titlescreen compiles" `Quick test_tea_titlescreen_compiles; - Alcotest.test_case "titlescreen NewGame→new_game" `Quick test_tea_titlescreen_update; -] - -(* ============================================================================ - Section 13: E2E LSP Phase B — Hover and Goto-Definition - - These tests verify the hover and goto-def pipeline entry points that - power LSP features. They run entirely through the library API (no - subprocess), calling the same helpers used by the CLI commands. - - Key properties verified: - - find_symbol_at locates a symbol by definition span - - find_symbol_at resolves a use-site reference back to its definition - - Not-found returns None (no crash) - - JSON serialisation is duplicate-key-free -*) - -(** Run parse→resolve→typecheck on a fixture, collect the symbol table - and reference list for subsequent hover/goto-def queries. *) -let pipeline_for_hover path = - match parse_fixture path with - | Error msg -> failwith msg - | Ok prog -> - let loader_config = Module_loader.default_config () in - let loader = Module_loader.create loader_config in - match Resolve.resolve_program_with_loader prog loader with - | Error (e, _) -> failwith (Resolve.show_resolve_error e) - | Ok (resolve_ctx, _) -> - (* Type-check to populate sym_type; ignore errors (same as CLI). *) - let _tc = Typecheck.check_program resolve_ctx.symbols prog in - let refs = - List.rev resolve_ctx.references - |> List.map (fun (r : Resolve.reference) -> - Json_output.{ ref_symbol_id = r.ref_symbol_id; - ref_span = r.ref_span }) - in - (resolve_ctx.symbols, refs) - -(** Helper: run the query, fail the test if the symbol isn't found. *) -let require_symbol symbols refs line col = - match Json_output.find_symbol_at symbols refs line col with - | None -> - Alcotest.failf "hover: expected symbol at (%d,%d) but got None" line col - | Some sym -> sym - -(** hover — definition span resolves to its own name. *) -let test_hover_def_span () = - let (symbols, refs) = pipeline_for_hover (fixture "arithmetic.affine") in - (* `fn add(…)` — name span starts at line 5, col 1. *) - let sym = require_symbol symbols refs 5 1 in - Alcotest.(check string) "hovered name is 'add'" "add" sym.Symbol.sym_name; - Alcotest.(check string) "kind is function" - "function" (Json_output.symbol_kind_to_string sym.sym_kind) - -(** hover — use-site reference resolves to the defining symbol. *) -let test_hover_use_site () = - let (symbols, refs) = pipeline_for_hover (fixture "full_pipeline.affine") in - (* `Circle` is used at line 33, col 14 (verified above). *) - let sym = require_symbol symbols refs 33 14 in - Alcotest.(check string) "use-site resolves to 'Circle'" "Circle" sym.Symbol.sym_name - -(** hover — off-document position returns None without crashing. *) -let test_hover_not_found () = - let (symbols, refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let result = Json_output.find_symbol_at symbols refs 9999 9999 in - Alcotest.(check bool) "none at phantom position" true (result = None) - -(** goto-def — JSON output is well-formed (has "found" and "file"). *) -let test_goto_def_json () = - let (symbols, refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let sym_opt = Json_output.find_symbol_at symbols refs 5 3 in - let json = match sym_opt with - | Some sym -> Json_output.goto_def_to_json sym - | None -> Json_output.not_found_json - in - (match json with - | `Assoc fields -> - Alcotest.(check bool) "has 'found'" true (List.mem_assoc "found" fields); - Alcotest.(check bool) "has 'file'" true (List.mem_assoc "file" fields) - | _ -> Alcotest.fail "goto_def_to_json must return a JSON object") - -let lsp_phase_b_tests = [ - Alcotest.test_case "hover def span" `Quick test_hover_def_span; - Alcotest.test_case "hover use-site" `Quick test_hover_use_site; - Alcotest.test_case "hover not found" `Quick test_hover_not_found; - Alcotest.test_case "goto-def JSON fields" `Quick test_goto_def_json; -] - -(* ============================================================================ - Section N: LSP Phase C — Completion candidates - ============================================================================ - - These tests validate [Json_output.extract_prefix_at] and - [Json_output.collect_completions] — the two functions powering the - [complete FILE LINE COL] subcommand. -*) - -(** Cursor placed right after "add(" on line 5 of arithmetic.affine: - col 7 puts end_idx at 5 (0-based), scanning back collects 'd','d','a' - → prefix "add". *) -let test_complete_prefix_extracted () = - let path = fixture "arithmetic.affine" in - let source = read_file path in - (* Line 5: "fn add(a: Int, b: Int) -> Int = a + b;" - Cols: 1234567 — col 7 is '(' so prefix ends at col 6 *) - let (prefix, dot_ctx) = Json_output.extract_prefix_at source 5 7 in - Alcotest.(check string) "prefix is 'add'" "add" prefix; - Alcotest.(check bool) "not dot context" false dot_ctx - -(** Prefix "add" matches the [add] symbol in the arithmetic fixture. *) -let test_complete_prefix_match () = - let (symbols, _refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let items = Json_output.collect_completions symbols "add" false in - let names = - List.map (fun (i : Json_output.completion_item) -> i.Json_output.comp_name) items - in - Alcotest.(check bool) "add is a candidate" true (List.mem "add" names) - -(** Empty prefix returns all symbols + keywords — at least the 6 functions - defined in the arithmetic fixture are present. *) -let test_complete_empty_prefix () = - let (symbols, _refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let items = Json_output.collect_completions symbols "" false in - Alcotest.(check bool) "non-empty for empty prefix" - true (List.length items > 0) - -(** An unrecognised prefix produces an empty candidate list. *) -let test_complete_no_match () = - let (symbols, _refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let items = Json_output.collect_completions symbols "zzznotfound" false in - Alcotest.(check int) "zero candidates for unknown prefix" 0 (List.length items) - -(** Keyword "fn" appears in completions when the prefix matches and we are - not in a dot-access context. *) -let test_complete_keyword_included () = - let (symbols, _refs) = pipeline_for_hover (fixture "arithmetic.affine") in - let items = Json_output.collect_completions symbols "fn" false in - let kinds = - List.map (fun (i : Json_output.completion_item) -> i.Json_output.comp_kind) items - in - Alcotest.(check bool) "keyword item present" true (List.mem "keyword" kinds) - -(** In a dot-access context, keyword candidates are suppressed. *) -let test_complete_dot_suppresses_keywords () = - let (symbols, _refs) = pipeline_for_hover (fixture "arithmetic.affine") in - (* dot_ctx = true → no keywords, even for empty prefix *) - let items = Json_output.collect_completions symbols "" true in - let kinds = - List.map (fun (i : Json_output.completion_item) -> i.Json_output.comp_kind) items - in - Alcotest.(check bool) "no keyword items in dot context" - false (List.mem "keyword" kinds) - -let lsp_phase_c_tests = [ - Alcotest.test_case "prefix extracted correctly" `Quick test_complete_prefix_extracted; - Alcotest.test_case "prefix match returns symbol" `Quick test_complete_prefix_match; - Alcotest.test_case "empty prefix returns candidates" `Quick test_complete_empty_prefix; - Alcotest.test_case "unknown prefix returns empty" `Quick test_complete_no_match; - Alcotest.test_case "keyword included when prefix ok" `Quick test_complete_keyword_included; - Alcotest.test_case "dot ctx suppresses keywords" `Quick test_complete_dot_suppresses_keywords; -] - -(* ============================================================================ - Section N+1: LSP Phase D — JSON-RPC server helpers - ============================================================================ - - The server loop itself requires a live stdin/stdout pair, so we test - the stateless helper functions that underpin every LSP handler: - uri_to_path, lsp_range (0-based position conversion), and the in-process - pipeline runner. -*) - -(** [file:///abs/path] → [/abs/path]. *) -let test_lsp_uri_to_path () = - let path = Lsp_server.uri_to_path "file:///var/mnt/eclipse/repos/foo.affine" in - Alcotest.(check string) "uri_to_path strips prefix" - "/var/mnt/eclipse/repos/foo.affine" path - -(** Compiler spans are 1-based; LSP ranges must be 0-based. *) -let test_lsp_position_conversion () = - let span = Span.make - ~file:"test.affine" - ~start_pos:{ Span.line = 5; col = 3; offset = 0 } - ~end_pos: { Span.line = 5; col = 6; offset = 0 } - in - let range = Lsp_server.lsp_range span in - (match range with - | `Assoc rf -> - (match List.assoc_opt "start" rf with - | Some (`Assoc sp) -> - let ln = (match List.assoc_opt "line" sp with Some (`Int n) -> n | _ -> -1) in - let ch = (match List.assoc_opt "character" sp with Some (`Int n) -> n | _ -> -1) in - Alcotest.(check int) "start.line is 0-based" 4 ln; - Alcotest.(check int) "start.character is 0-based" 2 ch - | _ -> Alcotest.fail "start is not an object") - | _ -> Alcotest.fail "lsp_range must return an Assoc") - -(** Valid source → empty diagnostics + symbol table present. *) -let test_lsp_pipeline_valid () = - let source = "fn add(a: Int, b: Int) -> Int = a + b;" in - let (diags, symbols_opt, _refs) = - Lsp_server.run_pipeline "/tmp/affinescript_lsp_test.affine" source - in - Alcotest.(check int) "no diagnostics for valid source" 0 (List.length diags); - Alcotest.(check bool) "symbol table present" true (symbols_opt <> None) - -(** Broken source → at least one diagnostic, no crash. *) -let test_lsp_pipeline_invalid () = - let source = "fn broken( " in - let (diags, _symbols_opt, _refs) = - Lsp_server.run_pipeline "/tmp/affinescript_lsp_test_bad.affine" source - in - Alcotest.(check bool) "at least one diagnostic for broken source" - true (List.length diags > 0) - -let lsp_phase_d_tests = [ - Alcotest.test_case "uri_to_path strips file://" `Quick test_lsp_uri_to_path; - Alcotest.test_case "lsp_range converts to 0-based" `Quick test_lsp_position_conversion; - Alcotest.test_case "pipeline: valid source → no diags" `Quick test_lsp_pipeline_valid; - Alcotest.test_case "pipeline: broken source → diag" `Quick test_lsp_pipeline_invalid; -] - -(* ============================================================================ - Section 21: Try / Catch / Finally Tests - ============================================================================ - - These tests verify that the try/catch/finally construct type-checks and - survives the full pipeline through both the Julia and interpreter backends. - - WASM 1.0 tests only verify that the pipeline raises a clean - UnsupportedFeature error when catch arms are present; body-only and - finally-only variants must succeed. -*) - -let test_try_typecheck_body_only () = - match run_frontend (fixture "try_body_only.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_try_typecheck_finally () = - match run_frontend (fixture "try_finally.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_try_typecheck_catch_wildcard () = - match run_frontend (fixture "try_catch_wildcard.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_try_typecheck_catch_var () = - match run_frontend (fixture "try_catch_var.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_try_typecheck_full () = - match run_frontend (fixture "try_catch_finally.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -(** Body-only and finally-only variants must compile to WASM without error. *) -let test_try_wasm_body_only () = - match run_wasm_pipeline (fixture "try_body_only.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -let test_try_wasm_finally () = - match run_wasm_pipeline (fixture "try_finally.affine") with - | Error msg -> Alcotest.fail msg - | Ok _ -> () - -(** Catch arms must produce a clean UnsupportedFeature error in WASM 1.0. *) -let test_try_wasm_catch_unsupported () = - match run_wasm_pipeline (fixture "try_catch_wildcard.affine") with - | Ok _ -> - (* Acceptable if the WASM backend happens to support this in future. *) - () - | Error msg -> - Alcotest.(check bool) "UnsupportedFeature error for catch in WASM" - true (String.length msg > 0) - -(** All five fixtures must produce Julia code without errors. *) -let test_try_julia_body_only () = - match run_julia_pipeline (fixture "try_body_only.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "non-empty Julia output" true - (String.length code > 0) - -let test_try_julia_finally () = - match run_julia_pipeline (fixture "try_finally.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "contains try keyword" true - (try let _ = Str.search_forward (Str.regexp "try") code 0 in true - with Not_found -> false) - -let test_try_julia_catch_wildcard () = - match run_julia_pipeline (fixture "try_catch_wildcard.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "contains catch keyword" true - (try let _ = Str.search_forward (Str.regexp "catch") code 0 in true - with Not_found -> false) - -let test_try_julia_catch_var () = - match run_julia_pipeline (fixture "try_catch_var.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - Alcotest.(check bool) "contains catch keyword" true - (try let _ = Str.search_forward (Str.regexp "catch") code 0 in true - with Not_found -> false) - -let test_try_julia_full () = - match run_julia_pipeline (fixture "try_catch_finally.affine") with - | Error msg -> Alcotest.fail msg - | Ok code -> - let has_try = try let _ = Str.search_forward (Str.regexp "try") code 0 in true with Not_found -> false in - let has_catch = try let _ = Str.search_forward (Str.regexp "catch") code 0 in true with Not_found -> false in - let has_finally = try let _ = Str.search_forward (Str.regexp "finally") code 0 in true with Not_found -> false in - Alcotest.(check bool) "has try" true has_try; - Alcotest.(check bool) "has catch" true has_catch; - Alcotest.(check bool) "has finally" true has_finally - -let try_catch_tests = [ - Alcotest.test_case "typecheck: body-only" `Quick test_try_typecheck_body_only; - Alcotest.test_case "typecheck: finally" `Quick test_try_typecheck_finally; - Alcotest.test_case "typecheck: catch wildcard" `Quick test_try_typecheck_catch_wildcard; - Alcotest.test_case "typecheck: catch var" `Quick test_try_typecheck_catch_var; - Alcotest.test_case "typecheck: full form" `Quick test_try_typecheck_full; - Alcotest.test_case "wasm: body-only compiles" `Quick test_try_wasm_body_only; - Alcotest.test_case "wasm: finally compiles" `Quick test_try_wasm_finally; - Alcotest.test_case "wasm: catch → unsupported" `Quick test_try_wasm_catch_unsupported; - Alcotest.test_case "julia: body-only" `Quick test_try_julia_body_only; - Alcotest.test_case "julia: finally" `Quick test_try_julia_finally; - Alcotest.test_case "julia: catch wildcard" `Quick test_try_julia_catch_wildcard; - Alcotest.test_case "julia: catch var" `Quick test_try_julia_catch_var; - Alcotest.test_case "julia: full form" `Quick test_try_julia_full; -] - -(* ============================================================================ - Section: Stage 7 — typed-wasm Ownership Verifier (Tw_verify) - ============================================================================ - - Verify that [Tw_verify.verify_module] and [Tw_verify.verify_from_module] - correctly enforce: - - Level 10 — Linearity: Linear (own) params must be loaded exactly once. - * Zero loads → LinearNotUsed violation (param dropped). - * Two or more loads → LinearUsedMultiple violation (param duplicated). - * Exactly one load → OK. - - Level 7 — Aliasing safety: ExclBorrow (mut) params may be loaded at most once. - * Two or more loads → ExclBorrowAliased violation. - * One load → OK. - - SharedBorrow (ref) and Unrestricted params are unconstrained — any number - of loads is allowed. - - Tests 1-7 build synthetic Wasm modules directly (no compilation). - Tests 8-9 run the full pipeline on fixture files. -*) - -(** Build a minimal Wasm module with a single function body. *) -let mk_single_func_module (body : Wasm.instr list) : Wasm.wasm_module = - let func = Wasm.{ f_type = 0; f_locals = []; f_body = body } in - { (Wasm.empty_module ()) with Wasm.funcs = [func] } - -(** Shorthand: annotate func 0 with given param kinds and Unrestricted return. *) -let single_annot (param_kinds : Codegen.ownership_kind list) - : (int * Codegen.ownership_kind list * Codegen.ownership_kind) list = - [(0, param_kinds, Codegen.Unrestricted)] - -(* ---- Test 1: Linear param used exactly once — OK ---- *) - -let test_verify_linear_ok () = - (* Body: LocalGet 0; Return — param 0 loaded once. *) - let m = mk_single_func_module [Wasm.LocalGet 0; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "linear used once → OK" true (errs = []) - -(* ---- Test 2: Linear param dropped (never loaded) — violation ---- *) - -let test_verify_linear_dropped () = - (* Body: I32Const 0; Return — param 0 never loaded. *) - let m = mk_single_func_module [Wasm.I32Const 0l; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "linear dropped → violation" true - (List.exists (function - | Tw_verify.LinearNotUsed { param_idx = 0; _ } -> true - | _ -> false) errs) - -(* ---- Test 3: Linear param loaded twice — violation ---- *) - -let test_verify_linear_dup () = - (* Body: LocalGet 0; LocalGet 0; I32Add; Return — param 0 loaded twice. *) - let m = mk_single_func_module - [Wasm.LocalGet 0; Wasm.LocalGet 0; Wasm.I32Add; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "linear duplicated → violation" true - (List.exists (function - | Tw_verify.LinearUsedMultiple { param_idx = 0; _ } -> true - | _ -> false) errs) - -(* ---- Test 4: ExclBorrow used once — OK ---- *) - -let test_verify_excl_ok () = - let m = mk_single_func_module [Wasm.LocalGet 0; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.ExclBorrow]) in - Alcotest.(check bool) "excl borrow once → OK" true (errs = []) - -(* ---- Test 5: ExclBorrow aliased (loaded twice) — violation ---- *) - -let test_verify_excl_aliased () = - let m = mk_single_func_module - [Wasm.LocalGet 0; Wasm.LocalGet 0; Wasm.I32Add; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.ExclBorrow]) in - Alcotest.(check bool) "excl borrow aliased → violation" true - (List.exists (function - | Tw_verify.ExclBorrowAliased { param_idx = 0; _ } -> true - | _ -> false) errs) - -(* ---- Test 6: Unrestricted param — any number of loads is OK ---- *) - -let test_verify_unrestricted_ok () = - (* Unrestricted params carry no ownership constraints: loading N times is fine. *) - let m = mk_single_func_module - [Wasm.LocalGet 0; Wasm.LocalGet 0; Wasm.I32Add; Wasm.Return] in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Unrestricted]) in - Alcotest.(check bool) "unrestricted multi-load → OK" true (errs = []) - -(* ---- Test 7: If branch — Linear used once in each arm → per-path (1,1) → OK ---- *) - -let test_verify_if_branch_ok () = - (* if (1) { LocalGet 0 } else { LocalGet 0 } - Per-path analysis: min(1,1)=1, max(1,1)=1 → OK. *) - let body = [ - Wasm.I32Const 1l; - Wasm.If (Wasm.BtType Wasm.I32, - [Wasm.LocalGet 0], - [Wasm.LocalGet 0]); - ] in - let m = mk_single_func_module body in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "if/else each use once → per-path OK" true (errs = []) - -(* ---- Test 10: Linear dropped in one branch only → LinearDroppedOnSomePath ---- *) - -let test_verify_if_partial_drop () = - (* if (1) { LocalGet 0 } else { [] } - Per-path analysis: then=(1,1), else=(0,0) → combined (min=0, max=1). - min_uses=0, max_uses=1 → LinearDroppedOnSomePath violation. *) - let body = [ - Wasm.I32Const 1l; - Wasm.If (Wasm.BtEmpty, - [Wasm.LocalGet 0; Wasm.Drop], - []); - ] in - let m = mk_single_func_module body in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "linear dropped in one branch → LinearDroppedOnSomePath" true - (List.exists (function - | Tw_verify.LinearDroppedOnSomePath { param_idx = 0; _ } -> true - | _ -> false) errs) - -(* ---- Test 11: Linear consumed in then, explicitly dropped in else → OK ---- *) -(* - This mirrors fn_push in tea_router.ml after Stage 9 fix. - then: LocalGet 0; I32Store (uses param once) - else: LocalGet 0; Drop (explicitly drops param) - Per-path: min(1,1)=1, max(1,1)=1 → OK. *) - -let test_verify_if_explicit_drop_ok () = - let body = [ - Wasm.I32Const 1l; - Wasm.If (Wasm.BtEmpty, - (* then: use the value *) - [Wasm.LocalGet 0; Wasm.Drop], - (* else: explicitly discharge ownership *) - [Wasm.LocalGet 0; Wasm.Drop]); - ] in - let m = mk_single_func_module body in - let errs = Tw_verify.verify_module m (single_annot [Codegen.Linear]) in - Alcotest.(check bool) "explicit drop in else → per-path OK" true (errs = []) - -(* ---- Test 8: Pipeline — ownership_codegen.affine → LinearNotUsed expected ---- *) -(* - ownership_codegen.affine has bodies that return 0 without using their - ownership params. The verifier should find LinearNotUsed for the - [consume_owned] function (kind=1, never loaded). *) - -let test_verify_pipeline_violations () = - match run_wasm_pipeline (fixture "ownership_codegen.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - (match Tw_verify.verify_from_module wasm_mod with - | Ok () -> - Alcotest.fail "Expected violations for dropped linear params, got OK" - | Error errs -> - let has_linear_not_used = List.exists (function - | Tw_verify.LinearNotUsed _ -> true - | _ -> false) errs in - Alcotest.(check bool) "LinearNotUsed violation detected" true has_linear_not_used) - -(* ---- Test 9: Pipeline — verify_ownership_clean.affine → OK ---- *) -(* - verify_ownership_clean.affine uses all params in their bodies. - The verifier must report clean. *) - -let test_verify_pipeline_clean () = - match run_wasm_pipeline (fixture "verify_ownership_clean.affine") with - | Error msg -> Alcotest.fail msg - | Ok wasm_mod -> - (match Tw_verify.verify_from_module wasm_mod with - | Ok () -> () (* expected *) - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_verify.pp_error e) errs) in - Alcotest.fail (Printf.sprintf "Unexpected violations: %s" msg)) - -let tw_verify_tests = [ - Alcotest.test_case "linear used once → OK" `Quick test_verify_linear_ok; - Alcotest.test_case "linear dropped → violation" `Quick test_verify_linear_dropped; - Alcotest.test_case "linear duplicated → violation" `Quick test_verify_linear_dup; - Alcotest.test_case "excl borrow once → OK" `Quick test_verify_excl_ok; - Alcotest.test_case "excl borrow aliased → violation" `Quick test_verify_excl_aliased; - Alcotest.test_case "unrestricted multi-load → OK" `Quick test_verify_unrestricted_ok; - Alcotest.test_case "if/else per-path linear → OK" `Quick test_verify_if_branch_ok; - Alcotest.test_case "pipeline: dropped linear → violation" `Quick test_verify_pipeline_violations; - Alcotest.test_case "pipeline: clean fixture → OK" `Quick test_verify_pipeline_clean; - Alcotest.test_case "if one-arm drop → LinearDroppedOnSomePath" `Quick test_verify_if_partial_drop; - Alcotest.test_case "if explicit else-drop → per-path OK" `Quick test_verify_if_explicit_drop_ok; -] - -(* ============================================================================ - Section: Stage 10 — Tw_interface (boundary verifier) - ============================================================================ - - Verify that [Tw_interface.extract_exports] and - [Tw_interface.verify_cross_module] correctly: - - 1. Extract ownership-annotated export interfaces from generated modules. - 2. Accept well-formed callers (Linear-param import called once per path). - 3. Reject callers that duplicate or conditionally drop Linear-param calls. -*) - -(* ---- Test 1: tea-bridge export interface has update with Linear param ---- *) - -let test_iface_bridge_update_linear () = - let m = Tea_bridge.generate () in - let iface = Tw_interface.extract_exports m in - let update_fi = - List.find_opt (fun fi -> fi.Tw_interface.fi_name = "affinescript_update") iface - in - match update_fi with - | None -> Alcotest.fail "affinescript_update not found in interface" - | Some fi -> - let has_own = List.mem Codegen.Linear fi.Tw_interface.fi_param_kinds in - Alcotest.(check bool) "affinescript_update has own (Linear) param" true has_own - -(* ---- Test 2: router export interface has push with Linear param ---- *) - -let test_iface_router_push_linear () = - let m = Tea_router.generate () in - let iface = Tw_interface.extract_exports m in - let push_fi = - List.find_opt - (fun fi -> fi.Tw_interface.fi_name = "affinescript_router_push") iface - in - match push_fi with - | None -> Alcotest.fail "affinescript_router_push not found in interface" - | Some fi -> - let has_own = List.mem Codegen.Linear fi.Tw_interface.fi_param_kinds in - Alcotest.(check bool) "affinescript_router_push has own (Linear) param" true has_own - -(* ---- Test 3: router resize has two Linear params ---- *) - -let test_iface_router_resize_two_linear () = - let m = Tea_router.generate () in - let iface = Tw_interface.extract_exports m in - let resize_fi = - List.find_opt - (fun fi -> fi.Tw_interface.fi_name = "affinescript_router_resize") iface - in - match resize_fi with - | None -> Alcotest.fail "affinescript_router_resize not found in interface" - | Some fi -> - let n_linear = - List.length (List.filter (( = ) Codegen.Linear) fi.Tw_interface.fi_param_kinds) - in - Alcotest.(check int) "resize has 2 Linear params" 2 n_linear - -(* ---- Test 4: cross-module — caller calls Linear import once → OK ---- *) - -let test_cross_call_once_ok () = - (* Callee: a module with a single Linear-param export named "consume". *) - let callee = - let m = mk_single_func_module [Wasm.LocalGet 0; Wasm.Drop] in - let export = Wasm.{ e_name = "consume"; e_desc = ExportFunc 0 } in - let annots = Codegen.build_ownership_section [(0, [Codegen.Linear], Codegen.Unrestricted)] in - { m with - Wasm.exports = [export]; - Wasm.custom_sections = [("affinescript.ownership", annots)]; - } - in - let iface = Tw_interface.extract_exports callee in - (* Caller: imports "consume" at slot 0, calls it once. *) - let caller = - let import = Wasm.{ i_module = "test"; i_name = "consume"; i_desc = ImportFunc 0 } in - let fn = Wasm.{ f_type = 0; f_locals = []; f_body = [Wasm.I32Const 0l; Wasm.Call 0] } in - { (Wasm.empty_module ()) with - Wasm.types = [{ Wasm.ft_params = [Wasm.I32]; ft_results = [] }]; - Wasm.imports = [import]; - Wasm.funcs = [fn]; - } - in - (match Tw_interface.verify_cross_module iface caller with - | Ok () -> () - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_interface.pp_cross_error e) errs) in - Alcotest.fail ("Expected OK, got violations: " ^ msg)) - -(* ---- Test 5: cross-module — caller calls Linear import twice → violation ---- *) - -let test_cross_call_twice_violation () = - let callee = - let m = mk_single_func_module [Wasm.LocalGet 0; Wasm.Drop] in - let export = Wasm.{ e_name = "consume"; e_desc = ExportFunc 0 } in - let annots = Codegen.build_ownership_section [(0, [Codegen.Linear], Codegen.Unrestricted)] in - { m with - Wasm.exports = [export]; - Wasm.custom_sections = [("affinescript.ownership", annots)]; - } - in - let iface = Tw_interface.extract_exports callee in - (* Caller: calls "consume" twice → LinearImportCalledMultiple. *) - let caller = - let import = Wasm.{ i_module = "test"; i_name = "consume"; i_desc = ImportFunc 0 } in - let fn = Wasm.{ - f_type = 0; f_locals = []; - f_body = [Wasm.I32Const 0l; Wasm.Call 0; Wasm.I32Const 1l; Wasm.Call 0]; - } in - { (Wasm.empty_module ()) with - Wasm.types = [{ Wasm.ft_params = [Wasm.I32]; ft_results = [] }]; - Wasm.imports = [import]; - Wasm.funcs = [fn]; - } - in - (match Tw_interface.verify_cross_module iface caller with - | Ok () -> - Alcotest.fail "Expected LinearImportCalledMultiple, got OK" - | Error errs -> - let has_dup = List.exists (function - | Tw_interface.LinearImportCalledMultiple _ -> true - | _ -> false) errs in - Alcotest.(check bool) "duplicate import call → LinearImportCalledMultiple" true has_dup) - -(* ---- Test 6: cross-module — caller calls Linear import in one branch only → violation ---- *) - -let test_cross_call_partial_violation () = - let callee = - let m = mk_single_func_module [Wasm.LocalGet 0; Wasm.Drop] in - let export = Wasm.{ e_name = "consume"; e_desc = ExportFunc 0 } in - let annots = Codegen.build_ownership_section [(0, [Codegen.Linear], Codegen.Unrestricted)] in - { m with - Wasm.exports = [export]; - Wasm.custom_sections = [("affinescript.ownership", annots)]; - } - in - let iface = Tw_interface.extract_exports callee in - (* Caller: If { Call 0 } { [] } → dropped on else path. *) - let caller = - let import = Wasm.{ i_module = "test"; i_name = "consume"; i_desc = ImportFunc 0 } in - let fn = Wasm.{ - f_type = 0; f_locals = []; - f_body = [ - Wasm.I32Const 1l; - Wasm.If (Wasm.BtEmpty, - [Wasm.I32Const 0l; Wasm.Call 0], - []); - ]; - } in - { (Wasm.empty_module ()) with - Wasm.types = [{ Wasm.ft_params = [Wasm.I32]; ft_results = [] }]; - Wasm.imports = [import]; - Wasm.funcs = [fn]; - } - in - (match Tw_interface.verify_cross_module iface caller with - | Ok () -> - Alcotest.fail "Expected LinearImportDroppedOnSomePath, got OK" - | Error errs -> - let has_partial = List.exists (function - | Tw_interface.LinearImportDroppedOnSomePath _ -> true - | _ -> false) errs in - Alcotest.(check bool) "partial-path call → LinearImportDroppedOnSomePath" true has_partial) - -(* ---- Test 7: generated bridge modules verify clean at boundary ---- *) - -let test_bridge_boundary_clean () = - (* tea-bridge: affinescript_update has a Linear msg param. - Synthetic caller calls it once → clean. *) - let callee_iface = Tw_interface.extract_exports (Tea_bridge.generate ()) in - let caller = - let import = Wasm.{ - i_module = "env"; - i_name = "affinescript_update"; - i_desc = ImportFunc 0; - } in - let fn = Wasm.{ f_type = 0; f_locals = []; f_body = [Wasm.I32Const 0l; Wasm.Call 0] } in - { (Wasm.empty_module ()) with - Wasm.types = [{ Wasm.ft_params = [Wasm.I32]; ft_results = [] }]; - Wasm.imports = [import]; - Wasm.funcs = [fn]; - } - in - (match Tw_interface.verify_cross_module callee_iface caller with - | Ok () -> () - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_interface.pp_cross_error e) errs) in - Alcotest.fail ("Bridge boundary check failed: " ^ msg)) - -let test_router_boundary_clean () = - (* router: affinescript_router_push has a Linear screen_tag param. - Synthetic caller calls it once → clean. *) - let callee_iface = Tw_interface.extract_exports (Tea_router.generate ()) in - let caller = - let import = Wasm.{ - i_module = "router"; - i_name = "affinescript_router_push"; - i_desc = ImportFunc 0; - } in - let fn = Wasm.{ f_type = 0; f_locals = []; f_body = [Wasm.I32Const 1l; Wasm.Call 0] } in - { (Wasm.empty_module ()) with - Wasm.types = [{ Wasm.ft_params = [Wasm.I32]; ft_results = [] }]; - Wasm.imports = [import]; - Wasm.funcs = [fn]; - } - in - (match Tw_interface.verify_cross_module callee_iface caller with - | Ok () -> () - | Error errs -> - let msg = String.concat "; " (List.map (fun e -> - Format.asprintf "%a" Tw_interface.pp_cross_error e) errs) in - Alcotest.fail ("Router boundary check failed: " ^ msg)) - -(* ============================================================================ - Section: Stage 11 — Cmd linearity (source-level QTT enforcement) - ============================================================================ - - Verify that: - 1. [Cmd _] type annotations automatically confer QOne on let-bindings. - 2. A Cmd returned in its tuple → QTT satisfied (no error). - 3. A Cmd dropped (not returned) → LinearVariableUnused error. - 4. [cmd_none] and [cmd_perform] are recognised as built-in values. -*) - -(** Full pipeline up to quantity checking (resolves, typechecks, then QTT). *) -let run_pipeline_to_quantity path = - match parse_fixture path with - | Error msg -> Error msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Error msg - | Ok (resolve_ctx, _) -> - match Typecheck.check_program resolve_ctx.symbols prog with - | Error e -> Error (Typecheck.format_type_error e) - | Ok _tc -> - match Quantity.check_program resolve_ctx.symbols prog with - | Ok () -> Ok () - | Error (e, _span) -> - Error (Printf.sprintf "Quantity error: %s" (Quantity.format_quantity_error e)) - -(* ---- Test 1: cmd_none recognised — Cmd type resolves ---- *) - -let test_cmd_type_resolves () = - (* cmd_linear.affine uses Cmd ClickMsg — should typecheck cleanly. *) - match parse_fixture (fixture "cmd_linear.affine") with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - (match Typecheck.check_program ctx.symbols prog with - | Ok _ -> () - | Error e -> - Alcotest.fail ("Cmd type failed to typecheck: " ^ Typecheck.format_type_error e)) - -(* ---- Test 2: Cmd returned in tuple — quantity check passes ---- *) - -let test_cmd_returned_passes () = - match run_pipeline_to_quantity (fixture "cmd_linear.affine") with - | Ok () -> () - | Error msg -> Alcotest.fail ("Expected OK, got: " ^ msg) - -(* ---- Test 3: Cmd dropped — LinearVariableUnused ---- *) - -let test_cmd_dropped_violation () = - match run_pipeline_to_quantity (fixture "cmd_dropped.affine") with - | Error msg -> - (* Should mention linear/unused *) - Alcotest.(check bool) "error mentions cmd or linear" - true (String.length msg > 0) - | Ok () -> - Alcotest.fail "Expected LinearVariableUnused for dropped Cmd, got OK" - -(* ---- Test 4: in-memory — Cmd binding without annotation stays QOmega ---- *) - -let test_cmd_no_annotation_qomega () = - (* Without a [Cmd _] type annotation, the binding gets QOmega — no enforcement. - This verifies backwards-compatibility: existing code that uses cmd_none - without annotation is not broken by Stage 11. *) - let src = {| - enum M { Click } - fn f(msg: M, n: Int) -> Int { - let cmd = cmd_none; - n - } - |} in - let prog_result = try - Ok (Parse_driver.parse_string ~file:"" src) - with - | Parse_driver.Parse_error (msg, _) -> Error ("Parse: " ^ msg) - | Lexer.Lexer_error (msg, _) -> Error ("Lex: " ^ msg) - in - match prog_result with - | Error msg -> Alcotest.fail msg - | Ok prog -> - match resolve_program prog with - | Error msg -> Alcotest.fail msg - | Ok (ctx, _) -> - (match Quantity.check_program ctx.symbols prog with - | Ok () -> () (* QOmega — no enforcement, OK to drop *) - | Error (e, _) -> - Alcotest.fail ("Unexpected quantity error (no annotation → QOmega): " - ^ Quantity.format_quantity_error e)) - -let cmd_linear_tests = [ - Alcotest.test_case "Cmd type resolves in typecheck" `Quick test_cmd_type_resolves; - Alcotest.test_case "Cmd returned in tuple → QTT OK" `Quick test_cmd_returned_passes; - Alcotest.test_case "Cmd dropped → LinearVariableUnused" `Quick test_cmd_dropped_violation; - Alcotest.test_case "No annotation → QOmega (backwards compat)" `Quick test_cmd_no_annotation_qomega; -] - -let tw_interface_tests = [ - Alcotest.test_case "bridge: update export has own param" `Quick test_iface_bridge_update_linear; - Alcotest.test_case "router: push export has own param" `Quick test_iface_router_push_linear; - Alcotest.test_case "router: resize export has 2 own params" `Quick test_iface_router_resize_two_linear; - Alcotest.test_case "cross: call once → OK" `Quick test_cross_call_once_ok; - Alcotest.test_case "cross: call twice → LinearImportCalledMultiple" `Quick test_cross_call_twice_violation; - Alcotest.test_case "cross: call one-arm → LinearImportDroppedOnSomePath" `Quick test_cross_call_partial_violation; - Alcotest.test_case "bridge boundary: clean caller → OK" `Quick test_bridge_boundary_clean; - Alcotest.test_case "router boundary: clean caller → OK" `Quick test_router_boundary_clean; -] - -(* ============================================================================ - Test Suite Export - ============================================================================ *) - -let tests = - [ - ("E2E Parse", parse_tests); - ("E2E Resolve", resolve_tests); - ("E2E Typecheck", typecheck_tests); - ("E2E Quantity", quantity_tests); - ("E2E Linear Arrows", linear_arrow_tests); - ("E2E WASM", wasm_tests); - ("E2E Ownership Schema", ownership_schema_tests); - ("E2E Julia", julia_tests); - ("E2E Interp", interp_tests); - ("E2E Optimizer", optimizer_tests); - ("E2E Full Pipeline", full_pipeline_tests); - ("E2E Errors", error_tests); - ("E2E Python-Face", python_face_tests); - ("E2E Traits", trait_impl_tests); - ("E2E TEA", tea_tests); - ("E2E TEA Bridge", tea_bridge_tests); - ("E2E TEA Router", tea_router_tests); - ("E2E LSP Phase B", lsp_phase_b_tests); - ("E2E LSP Phase C", lsp_phase_c_tests); - ("E2E LSP Phase D", lsp_phase_d_tests); - ("E2E Try/Catch/Finally", try_catch_tests); - ("E2E Ownership Verify", tw_verify_tests); - ("E2E Cmd Linearity", cmd_linear_tests); - ("E2E Boundary Verify", tw_interface_tests); - ] diff --git a/affinescript/test/test_golden.ml b/affinescript/test/test_golden.ml deleted file mode 100644 index e7fdfcc..0000000 --- a/affinescript/test/test_golden.ml +++ /dev/null @@ -1,132 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Golden tests for parser/AST output *) - -open Affinescript - -(** Directory containing golden test files. - When run via [dune test], the CWD is [_build/default/test/] where Dune - copies the source tree, so [golden/] is directly accessible. - When run from the project root (e.g. during development), fall back to - [test/golden]. *) -let golden_dir = - if Sys.file_exists "golden" then "golden" - else "test/golden" - -(** Read file contents *) -let read_file path = - let chan = open_in path in - let content = really_input_string chan (in_channel_length chan) in - close_in chan; - content - -(** Check if file exists *) -let file_exists path = - try - let _ = Unix.stat path in - true - with Unix.Unix_error (Unix.ENOENT, _, _) -> false - -(** Normalize AST output for comparison (remove span details) *) -let normalize_ast ast_str = - (* Normalize whitespace first so span records are on a single logical line *) - let re_ws = Str.regexp "[ \t\n\r]+" in - let flat = Str.global_replace re_ws " " ast_str in - (* Remove span information. Current field order (ppx_deriving.show): - { Span.start_pos = { Span.line = N; col = N; offset = N }; - end_pos = { Span.line = N; col = N; offset = N }; - file = "path" } - After whitespace normalization this becomes a single-line record. - We also handle the legacy { Span.file = ...; start_pos = ...; end_pos = ... } - form so that pre-existing -placeholder expected files still match. *) - let re_span = Str.regexp - {|{ Span\.start_pos = { [^}]* }; end_pos = { [^}]* }; file = "[^"]*" }|} - in - String.trim (Str.global_replace re_span "" flat) - -(** Parse a file and return the AST as a string *) -let parse_to_string path = - try - let ast = Parse_driver.parse_file path in - let ast_str = Ast.show_program ast in - Ok ast_str - with - | Parse_driver.Parse_error (msg, span) -> - Error (Printf.sprintf "Parse error at %s: %s" (Span.show span) msg) - | e -> - Error (Printf.sprintf "Error: %s" (Printexc.to_string e)) - -(** Run a single golden test *) -let run_golden_test ~source_path ~expected_path () = - match parse_to_string source_path with - | Error msg -> - Alcotest.fail (Printf.sprintf "Failed to parse %s: %s" source_path msg) - | Ok actual -> - if not (file_exists expected_path) then - Alcotest.fail (Printf.sprintf "Expected file not found: %s\nActual output:\n%s" expected_path actual) - else - let expected = read_file expected_path in - let norm_actual = normalize_ast actual in - let norm_expected = normalize_ast expected in - if norm_actual <> norm_expected then begin - Printf.eprintf "\n=== EXPECTED ===\n%s\n" expected; - Printf.eprintf "\n=== ACTUAL ===\n%s\n" actual; - Alcotest.fail "AST output does not match expected" - end - -(** Create a test case from a .affine file *) -let test_case_of_file filename = - let base = Filename.chop_extension filename in - let source_path = Filename.concat golden_dir filename in - let expected_path = Filename.concat golden_dir (base ^ ".expected") in - Alcotest.test_case base `Quick (run_golden_test ~source_path ~expected_path) - -(** Discover all .affine files in golden directory *) -let discover_tests () = - if not (Sys.file_exists golden_dir) then - [] - else - let files = Sys.readdir golden_dir in - files - |> Array.to_list - |> List.filter (fun f -> Filename.check_suffix f ".affine") - |> List.sort String.compare - |> List.map test_case_of_file - -(** Run parser on examples directory and check they all parse. - The examples dir lives one level above the test source tree; - Dune's [(source_tree ../examples)] dep makes it available as - [../examples] from the test CWD. *) -let examples_dir = - if Sys.file_exists "../examples" then "../examples" - else "examples" - -let run_example_parse_test filename () = - let path = Filename.concat examples_dir filename in - match parse_to_string path with - | Error msg -> - Alcotest.fail (Printf.sprintf "Failed to parse %s: %s" path msg) - | Ok _ast -> - (* Just check it parses successfully *) - () - -(** Discover all examples and create parse tests *) -let discover_example_tests () = - if not (Sys.file_exists examples_dir) then - [] - else - let files = Sys.readdir examples_dir in - files - |> Array.to_list - |> List.filter (fun f -> Filename.check_suffix f ".affine") - |> List.sort String.compare - |> List.map (fun f -> - let base = Filename.chop_extension f in - Alcotest.test_case ("parse " ^ base) `Quick (run_example_parse_test f)) - -(** All golden tests *) -let tests = discover_tests () - -(** All example parse tests *) -let example_tests = discover_example_tests () diff --git a/affinescript/test/test_lexer.ml b/affinescript/test/test_lexer.ml deleted file mode 100644 index 4b9a3a1..0000000 --- a/affinescript/test/test_lexer.ml +++ /dev/null @@ -1,147 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* SPDX-FileCopyrightText: 2024-2025 hyperpolymath *) - -(** Lexer tests *) - -open Affinescript - -let token_testable = Alcotest.testable Token.pp Token.equal - -let lex_all source = - let lexer = Lexer.from_string ~file:"" source in - let rec loop acc = - let (tok, _) = lexer () in - if tok = Token.EOF then List.rev (tok :: acc) - else loop (tok :: acc) - in - loop [] - -let test_keywords () = - let tokens = lex_all "fn let mut own ref type" in - Alcotest.(check (list token_testable)) "keywords" - [Token.FN; Token.LET; Token.MUT; Token.OWN; Token.REF; Token.TYPE; Token.EOF] - tokens - -let test_identifiers () = - let tokens = lex_all "foo Bar _test test123" in - Alcotest.(check (list token_testable)) "identifiers" - [Token.LOWER_IDENT "foo"; Token.UPPER_IDENT "Bar"; - Token.UNDERSCORE; Token.LOWER_IDENT "test"; - Token.LOWER_IDENT "test123"; Token.EOF] - tokens - -let test_literals () = - let tokens = lex_all "42 3.14 true false" in - Alcotest.(check (list token_testable)) "literals" - [Token.INT 42; Token.FLOAT 3.14; Token.TRUE; Token.FALSE; Token.EOF] - tokens - -let test_string_literal () = - let tokens = lex_all {|"hello world"|} in - Alcotest.(check (list token_testable)) "string literal" - [Token.STRING "hello world"; Token.EOF] - tokens - -let test_string_escapes () = - let tokens = lex_all {|"hello\nworld\t!"|} in - Alcotest.(check (list token_testable)) "string escapes" - [Token.STRING "hello\nworld\t!"; Token.EOF] - tokens - -let test_operators () = - let tokens = lex_all "+ - * / == != < > <= >= -> =>" in - Alcotest.(check (list token_testable)) "operators" - [Token.PLUS; Token.MINUS; Token.STAR; Token.SLASH; - Token.EQEQ; Token.NE; Token.LT; Token.GT; Token.LE; Token.GE; - Token.ARROW; Token.FAT_ARROW; Token.EOF] - tokens - -let test_punctuation () = - let tokens = lex_all "( ) { } [ ] , ; : :: . .." in - Alcotest.(check (list token_testable)) "punctuation" - [Token.LPAREN; Token.RPAREN; Token.LBRACE; Token.RBRACE; - Token.LBRACKET; Token.RBRACKET; Token.COMMA; Token.SEMICOLON; - Token.COLON; Token.COLONCOLON; Token.DOT; Token.DOTDOT; Token.EOF] - tokens - -let test_row_variable () = - let tokens = lex_all "..rest" in - Alcotest.(check (list token_testable)) "row variable" - [Token.ROW_VAR "rest"; Token.EOF] - tokens - -let test_comments () = - let tokens = lex_all "foo // comment\nbar" in - Alcotest.(check (list token_testable)) "line comment" - [Token.LOWER_IDENT "foo"; Token.LOWER_IDENT "bar"; Token.EOF] - tokens - -let test_block_comments () = - let tokens = lex_all "foo /* block */ bar" in - Alcotest.(check (list token_testable)) "block comment" - [Token.LOWER_IDENT "foo"; Token.LOWER_IDENT "bar"; Token.EOF] - tokens - -let test_nested_comments () = - let tokens = lex_all "foo /* outer /* inner */ still outer */ bar" in - Alcotest.(check (list token_testable)) "nested comments" - [Token.LOWER_IDENT "foo"; Token.LOWER_IDENT "bar"; Token.EOF] - tokens - -let test_hex_literal () = - let tokens = lex_all "0xFF 0x10" in - Alcotest.(check (list token_testable)) "hex literals" - [Token.INT 255; Token.INT 16; Token.EOF] - tokens - -let test_binary_literal () = - let tokens = lex_all "0b1010 0b11" in - Alcotest.(check (list token_testable)) "binary literals" - [Token.INT 10; Token.INT 3; Token.EOF] - tokens - -let test_function_decl () = - let tokens = lex_all "fn add(a: Int, b: Int) -> Int" in - Alcotest.(check (list token_testable)) "function declaration" - [Token.FN; Token.LOWER_IDENT "add"; Token.LPAREN; - Token.LOWER_IDENT "a"; Token.COLON; Token.INT_T; Token.COMMA; - Token.LOWER_IDENT "b"; Token.COLON; Token.INT_T; Token.RPAREN; - Token.ARROW; Token.INT_T; Token.EOF] - tokens - -let test_total_function () = - let tokens = lex_all "total fn safe() -> Nat" in - Alcotest.(check (list token_testable)) "total function" - [Token.TOTAL; Token.FN; Token.LOWER_IDENT "safe"; Token.LPAREN; - Token.RPAREN; Token.ARROW; Token.NAT; Token.EOF] - tokens - -let test_type_decl () = - let tokens = lex_all "type Option[T] = None | Some(T)" in - Alcotest.(check (list token_testable)) "type declaration" - [Token.TYPE; Token.UPPER_IDENT "Option"; Token.LBRACKET; - Token.UPPER_IDENT "T"; Token.RBRACKET; Token.EQ; - Token.UPPER_IDENT "None"; Token.PIPE; - Token.UPPER_IDENT "Some"; Token.LPAREN; - Token.UPPER_IDENT "T"; Token.RPAREN; Token.EOF] - tokens - -let tests = - [ - Alcotest.test_case "keywords" `Quick test_keywords; - Alcotest.test_case "identifiers" `Quick test_identifiers; - Alcotest.test_case "literals" `Quick test_literals; - Alcotest.test_case "string literal" `Quick test_string_literal; - Alcotest.test_case "string escapes" `Quick test_string_escapes; - Alcotest.test_case "operators" `Quick test_operators; - Alcotest.test_case "punctuation" `Quick test_punctuation; - Alcotest.test_case "row variable" `Quick test_row_variable; - Alcotest.test_case "line comment" `Quick test_comments; - Alcotest.test_case "block comment" `Quick test_block_comments; - Alcotest.test_case "nested comments" `Quick test_nested_comments; - Alcotest.test_case "hex literal" `Quick test_hex_literal; - Alcotest.test_case "binary literal" `Quick test_binary_literal; - Alcotest.test_case "function decl" `Quick test_function_decl; - Alcotest.test_case "total function" `Quick test_total_function; - Alcotest.test_case "type decl" `Quick test_type_decl; - ] diff --git a/affinescript/test/test_main.ml b/affinescript/test/test_main.ml deleted file mode 100644 index 75f4bb4..0000000 --- a/affinescript/test/test_main.ml +++ /dev/null @@ -1,13 +0,0 @@ -(* SPDX-License-Identifier: MPL-2.0 *) -(* Copyright (c) 2024-2026 Jonathan D.A. Jewell *) - -(** Main test runner for AffineScript *) - -let () = - Alcotest.run "AffineScript" - ([ - ("Lexer", Test_lexer.tests); - (* ("Parser", Test_parser.tests); *) (* TODO(#84): Re-enable when test_parser is implemented *) - ("Golden", Test_golden.tests); - ("Examples", Test_golden.example_tests); - ] @ Test_e2e.tests) diff --git a/affinescript/test_if.affine b/affinescript/test_if.affine deleted file mode 100644 index 34e1b39..0000000 --- a/affinescript/test_if.affine +++ /dev/null @@ -1,4 +0,0 @@ -fn test() -> Int { - let x = if true { 1 } else { 2 }; - return x; -} diff --git a/affinescript/test_min.affine b/affinescript/test_min.affine deleted file mode 100644 index 1e52f09..0000000 --- a/affinescript/test_min.affine +++ /dev/null @@ -1,7 +0,0 @@ -fn min(a: Int, b: Int) -> Int { - if a < b { - a - } else { - b - } -} diff --git a/affinescript/test_simple.affine b/affinescript/test_simple.affine deleted file mode 100644 index 4fe7198..0000000 --- a/affinescript/test_simple.affine +++ /dev/null @@ -1,7 +0,0 @@ -fn test() -> Int { - if true { - 42 - } else { - 0 - } -} diff --git a/affinescript/test_spread.affine b/affinescript/test_spread.affine deleted file mode 100644 index ecb8078..0000000 --- a/affinescript/test_spread.affine +++ /dev/null @@ -1,4 +0,0 @@ -fn test() -> Int { - let x = { a: 1, ..{ b: 2 } }; - return x.a; -} diff --git a/affinescript/test_spread2.affine b/affinescript/test_spread2.affine deleted file mode 100644 index ecb8078..0000000 --- a/affinescript/test_spread2.affine +++ /dev/null @@ -1,4 +0,0 @@ -fn test() -> Int { - let x = { a: 1, ..{ b: 2 } }; - return x.a; -} diff --git a/affinescript/test_spread3.affine b/affinescript/test_spread3.affine deleted file mode 100644 index dd8ab32..0000000 --- a/affinescript/test_spread3.affine +++ /dev/null @@ -1,5 +0,0 @@ -fn test() -> Int { - let base = { b: 2 }; - let x = { a: 1 ..base }; - return x.a; -} diff --git a/affinescript/tests/borrow/minimal.affine b/affinescript/tests/borrow/minimal.affine deleted file mode 100644 index 50673f6..0000000 --- a/affinescript/tests/borrow/minimal.affine +++ /dev/null @@ -1 +0,0 @@ -fn test(own x: Int) -> Int = x; diff --git a/affinescript/tests/borrow/move_test.affine b/affinescript/tests/borrow/move_test.affine deleted file mode 100644 index 2537aa6..0000000 --- a/affinescript/tests/borrow/move_test.affine +++ /dev/null @@ -1,2 +0,0 @@ -fn consume(own x: Int) -> Int = x; -fn test() -> Int = consume(42); diff --git a/affinescript/tests/borrow/simple.affine b/affinescript/tests/borrow/simple.affine deleted file mode 100644 index ce51e19..0000000 --- a/affinescript/tests/borrow/simple.affine +++ /dev/null @@ -1,3 +0,0 @@ -fn test() -> () { - () -} diff --git a/affinescript/tests/borrow/simple_move_test.affine b/affinescript/tests/borrow/simple_move_test.affine deleted file mode 100644 index 14f2b22..0000000 --- a/affinescript/tests/borrow/simple_move_test.affine +++ /dev/null @@ -1,3 +0,0 @@ -fn consume(own x: Int) -> Int = x; -fn use_val(ref y: Int) -> Int = y; -fn test() -> Int = let x = 42 in let _ = consume(x) in use_val(x); diff --git a/affinescript/tests/borrow/simple_own.affine b/affinescript/tests/borrow/simple_own.affine deleted file mode 100644 index 46abeba..0000000 --- a/affinescript/tests/borrow/simple_own.affine +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Simple test of own parameter - -fn test(own x: Int) -> Int { - x -} diff --git a/affinescript/tests/borrow/test_immutable_assign_error.affine b/affinescript/tests/borrow/test_immutable_assign_error.affine deleted file mode 100644 index 058bfac..0000000 --- a/affinescript/tests/borrow/test_immutable_assign_error.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test assignment to immutable variable (should error) - -fn main() -> Int { - let x = 10; - x = 20; // ERROR: cannot assign to immutable variable - return x; -} diff --git a/affinescript/tests/borrow/test_move.affine b/affinescript/tests/borrow/test_move.affine deleted file mode 100644 index 8389aab..0000000 --- a/affinescript/tests/borrow/test_move.affine +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/add -// Source: aggregate-library/specs/arithmetic/add.md -// -// Conforms to aLib arithmetic/add spec v1.0 - -/// Test: Basic addition of positive integers -fn test_add_positive() -> TestResult { - let result = 2 + 3; - assert_eq(result, 5, "Basic addition of positive integers"); - Pass -} - -/// Test: Addition with negative number -fn test_add_negative() -> TestResult { - let result = -5 + 3; - assert_eq(result, -2, "Addition with negative number"); - Pass -} - -/// Test: Addition of zeros -fn test_add_zeros() -> TestResult { - let result = 0 + 0; - assert_eq(result, 0, "Addition of zeros"); - Pass -} - -/// Test: Addition of decimal numbers -fn test_add_decimal() -> TestResult { - let result = 1.5 + 2.5; - assert_float_eq(result, 4.0, 0.0001, "Addition of decimal numbers"); - Pass -} - -/// Test: Addition of two negative numbers -fn test_add_two_negatives() -> TestResult { - let result = -10 + -20; - assert_eq(result, -30, "Addition of two negative numbers"); - Pass -} - -/// Run all add conformance tests -fn test_alib_arithmetic_add() -> TestResult { - let suite = { - name: "aLib arithmetic/add conformance", - tests: [ - { name: "add_positive", test: test_add_positive }, - { name: "add_negative", test: test_add_negative }, - { name: "add_zeros", test: test_add_zeros }, - { name: "add_decimal", test: test_add_decimal }, - { name: "add_two_negatives", test: test_add_two_negatives } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some add conformance tests failed") - } -} diff --git a/affinescript/tests/borrow/test_move_after_use.affine b/affinescript/tests/borrow/test_move_after_use.affine deleted file mode 100644 index 020e926..0000000 --- a/affinescript/tests/borrow/test_move_after_use.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test use-after-move detection - -fn consume(own x: Int) -> Int { - return x; -} - -fn main() -> Int { - let x = 42; - let y = consume(x); // x is moved here - return x; // ERROR: use after move -} diff --git a/affinescript/tests/borrow/test_mutable_borrow.affine b/affinescript/tests/borrow/test_mutable_borrow.affine deleted file mode 100644 index fd16b7b..0000000 --- a/affinescript/tests/borrow/test_mutable_borrow.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test mutable borrow checking - -fn main() -> Int { - let mut x = 10; - x += 5; // OK: x is mutable - return x; // Returns 15 -} diff --git a/affinescript/tests/borrow/test_ref_borrow.affine b/affinescript/tests/borrow/test_ref_borrow.affine deleted file mode 100644 index f5a692e..0000000 --- a/affinescript/tests/borrow/test_ref_borrow.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test reference creation and borrowing - -fn main() -> Int { - let x = 42; - let ptr = &x; // Creates a shared borrow of x - let y = *ptr; // Dereference is OK - return y; // Returns 42 -} diff --git a/affinescript/tests/borrow/use_after_move.affine b/affinescript/tests/borrow/use_after_move.affine deleted file mode 100644 index 4f69e86..0000000 --- a/affinescript/tests/borrow/use_after_move.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Borrow checker test: Use after move should fail - -fn consume(own x: Int) -> Int { - return x; -} - -fn test_use_after_move() -> Int { - let value = 42; - let result = consume(value); - let invalid = value; - return result; -} diff --git a/affinescript/tests/borrow/valid_move.affine b/affinescript/tests/borrow/valid_move.affine deleted file mode 100644 index d44a8d6..0000000 --- a/affinescript/tests/borrow/valid_move.affine +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Borrow checker test: Valid move should pass - -fn consume(own x: Int) -> Int { - return x; -} - -fn test_valid_move() -> Int { - let value = 42; - let result = consume(value); - return result; -} diff --git a/affinescript/tests/codegen/README.adoc b/affinescript/tests/codegen/README.adoc deleted file mode 100644 index 4d11782..0000000 --- a/affinescript/tests/codegen/README.adoc +++ /dev/null @@ -1,20 +0,0 @@ -== Codegen WASM tests - -This directory contains AffineScript codegen tests and their compiled -WASM artifacts. - -=== Run locally - -From the repo root: - -.... -./tools/run_codegen_wasm_tests.sh -.... - -The script: - compiles every `+tests/codegen/*.as+` file to -`+tests/codegen/*.wasm+` - runs any `+tests/codegen/*.mjs+` harnesses - -=== Notes - -* WASM artifacts under `+tests/codegen/+` are generated by CI and should -not be committed. diff --git a/affinescript/tests/codegen/simple_arithmetic.affine b/affinescript/tests/codegen/simple_arithmetic.affine deleted file mode 100644 index 037087a..0000000 --- a/affinescript/tests/codegen/simple_arithmetic.affine +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Simple arithmetic test for WASM code generation - -fn main() -> Int { - let a = 10; - let b = 32; - let c = a + b; - return c; -} diff --git a/affinescript/tests/codegen/test_array_bounds.affine b/affinescript/tests/codegen/test_array_bounds.affine deleted file mode 100644 index eb031fd..0000000 --- a/affinescript/tests/codegen/test_array_bounds.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test array bounds - first and last elements - -fn main() -> Int { - let arr = [100, 200, 300, 400, 500]; - - let first = arr[0]; // 100 - let last = arr[4]; // 500 - - return first + last; // Should return 600 -} diff --git a/affinescript/tests/codegen/test_array_empty.affine b/affinescript/tests/codegen/test_array_empty.affine deleted file mode 100644 index fead85a..0000000 --- a/affinescript/tests/codegen/test_array_empty.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test empty array allocation - -fn main() -> Int { - let arr = []; - // Can't index empty array, just return success - return 999; // Should return 999 -} diff --git a/affinescript/tests/codegen/test_array_simple.affine b/affinescript/tests/codegen/test_array_simple.affine deleted file mode 100644 index e8c1927..0000000 --- a/affinescript/tests/codegen/test_array_simple.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test simple array creation and indexing - -fn main() -> Int { - let arr = [10, 20, 30]; - return arr[1]; // Should return 20 -} diff --git a/affinescript/tests/codegen/test_array_single.affine b/affinescript/tests/codegen/test_array_single.affine deleted file mode 100644 index 29fd9df..0000000 --- a/affinescript/tests/codegen/test_array_single.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test single-element array - -fn main() -> Int { - let arr = [42]; - return arr[0]; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_array_sum.affine b/affinescript/tests/codegen/test_array_sum.affine deleted file mode 100644 index 7865ca1..0000000 --- a/affinescript/tests/codegen/test_array_sum.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test array with sum of elements - -fn main() -> Int { - let arr = [5, 10, 15, 20, 25]; - - // Manually sum elements (no loops yet in codegen) - let sum = arr[0] + arr[1] + arr[2] + arr[3] + arr[4]; - - return sum; // Should return 75 -} diff --git a/affinescript/tests/codegen/test_assign_compound.affine b/affinescript/tests/codegen/test_assign_compound.affine deleted file mode 100644 index 463b071..0000000 --- a/affinescript/tests/codegen/test_assign_compound.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test compound assignment operators - -fn main() -> Int { - let mut x = 10; - x += 5; // x = 15 - x *= 2; // x = 30 - x -= 10; // x = 20 - x /= 2; // x = 10 - return x; // Should return 10 -} diff --git a/affinescript/tests/codegen/test_assign_simple.affine b/affinescript/tests/codegen/test_assign_simple.affine deleted file mode 100644 index 4ecc07e..0000000 --- a/affinescript/tests/codegen/test_assign_simple.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test simple variable assignment - -fn main() -> Int { - let mut x = 10; - x = 42; - return x; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_closure_capture.affine b/affinescript/tests/codegen/test_closure_capture.affine deleted file mode 100644 index c2ccb5f..0000000 --- a/affinescript/tests/codegen/test_closure_capture.affine +++ /dev/null @@ -1,9 +0,0 @@ -// Test closure capturing and using a variable - -fn main() -> Int { - let x = 5; - let f = |y: Int| -> Int { - return x + y; - }; - return f(10); // Should return 15 -} diff --git a/affinescript/tests/codegen/test_closure_debug.affine b/affinescript/tests/codegen/test_closure_debug.affine deleted file mode 100644 index 94cf602..0000000 --- a/affinescript/tests/codegen/test_closure_debug.affine +++ /dev/null @@ -1,9 +0,0 @@ -// Minimal closure test for debugging - -fn main() -> Int { - let x = 42; - let f = |y: Int| -> Int { - return x; // Should return 42, currently returns 0 - }; - return f(999); -} diff --git a/affinescript/tests/codegen/test_closure_multi_capture.affine b/affinescript/tests/codegen/test_closure_multi_capture.affine deleted file mode 100644 index 58daf90..0000000 --- a/affinescript/tests/codegen/test_closure_multi_capture.affine +++ /dev/null @@ -1,13 +0,0 @@ -// Test closure capturing multiple variables - -fn main() -> Int { - let x = 10; - let y = 20; - let z = 30; - - let f = |a: Int| -> Int { - return x + y + z + a; - }; - - return f(5); // Should return 65 (10+20+30+5) -} diff --git a/affinescript/tests/codegen/test_enum_pattern.affine b/affinescript/tests/codegen/test_enum_pattern.affine deleted file mode 100644 index 8740563..0000000 --- a/affinescript/tests/codegen/test_enum_pattern.affine +++ /dev/null @@ -1,21 +0,0 @@ -// Test enum-like pattern matching with integer tags -// This simulates how enums will work when parser support is added - -fn classify_status(code: Int) -> Int { - return match code { - 0 => 100, // Success - 1 => 200, // Warning - 2 => 300, // Error - _ => 999 // Unknown - }; -} - -fn main() -> Int { - let success = classify_status(0); // 100 - let warning = classify_status(1); // 200 - let error = classify_status(2); // 300 - let unknown = classify_status(99); // 999 - - // Return sum: 100 + 200 + 300 + 999 = 1599 - return success + warning + error + unknown; -} diff --git a/affinescript/tests/codegen/test_for_loop.affine b/affinescript/tests/codegen/test_for_loop.affine deleted file mode 100644 index a04919f..0000000 --- a/affinescript/tests/codegen/test_for_loop.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test for loop over array - -fn main() -> Int { - let arr = [1, 2, 3, 4, 5]; - let mut sum = 0; - for x in arr { - sum += x; - } - return sum; // Should return 15 (1+2+3+4+5) -} diff --git a/affinescript/tests/codegen/test_function_call.affine b/affinescript/tests/codegen/test_function_call.affine deleted file mode 100644 index 8dd2d49..0000000 --- a/affinescript/tests/codegen/test_function_call.affine +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test basic function calls in WASM codegen - -fn helper(x: Int) -> Int { - return x + 1; -} - -fn main() -> Int { - return helper(41); // Should return 42 -} diff --git a/affinescript/tests/codegen/test_function_call.mjs b/affinescript/tests/codegen/test_function_call.mjs deleted file mode 100644 index c1c98fa..0000000 --- a/affinescript/tests/codegen/test_function_call.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 hyperpolymath - -import { readFile } from 'fs/promises'; - -const wasmBuffer = await readFile('./tests/codegen/test_function_call.wasm'); -const imports = { - wasi_snapshot_preview1: { - fd_write: () => 0, - }, -}; -const wasmModule = await WebAssembly.instantiate(wasmBuffer, imports); -const result = wasmModule.instance.exports.main(); - -console.log(`Result: ${result}`); -console.log(`Expected: 42`); -console.log(`Test ${result === 42 ? 'PASSED ✓' : 'FAILED ✗'}`); - -process.exit(result === 42 ? 0 : 1); diff --git a/affinescript/tests/codegen/test_lambda_call.affine b/affinescript/tests/codegen/test_lambda_call.affine deleted file mode 100644 index f47c74a..0000000 --- a/affinescript/tests/codegen/test_lambda_call.affine +++ /dev/null @@ -1,12 +0,0 @@ -// Test lambda function call - -fn main() -> Int { - // Simple lambda that adds 10 - let add_ten = |x: Int| -> Int { - return x + 10; - }; - - // Call the lambda - let result = add_ten(5); - return result; // Should return 15 -} diff --git a/affinescript/tests/codegen/test_lambda_multi_param.affine b/affinescript/tests/codegen/test_lambda_multi_param.affine deleted file mode 100644 index 618afbc..0000000 --- a/affinescript/tests/codegen/test_lambda_multi_param.affine +++ /dev/null @@ -1,19 +0,0 @@ -// Test lambda with multiple parameters - -fn main() -> Int { - // Lambda that adds two numbers - let add = |x: Int, y: Int| -> Int { - return x + y; - }; - - // Lambda that computes (x * y) + z - let compute = |x: Int, y: Int, z: Int| -> Int { - let product = x * y; - return product + z; - }; - - let result1 = add(10, 20); // 30 - let result2 = compute(5, 6, 10); // 5*6 + 10 = 40 - - return result1 + result2; // 30 + 40 = 70 -} diff --git a/affinescript/tests/codegen/test_match_literal.affine b/affinescript/tests/codegen/test_match_literal.affine deleted file mode 100644 index a72f771..0000000 --- a/affinescript/tests/codegen/test_match_literal.affine +++ /dev/null @@ -1,13 +0,0 @@ -// Test pattern matching with literal patterns - -fn main() -> Int { - let x = 10; - - let result = match x { - 5 => 100, - 10 => 200, - _ => 300 - }; - - return result; // Should return 200 -} diff --git a/affinescript/tests/codegen/test_match_mixed.affine b/affinescript/tests/codegen/test_match_mixed.affine deleted file mode 100644 index 3af20a3..0000000 --- a/affinescript/tests/codegen/test_match_mixed.affine +++ /dev/null @@ -1,17 +0,0 @@ -// Test pattern matching with mixed patterns - -fn classify(x: Int) -> Int { - return match x { - 0 => 1, - 1 => 2, - n => n * 10 - }; -} - -fn main() -> Int { - let a = classify(0); // 1 - let b = classify(1); // 2 - let c = classify(5); // 50 - - return a + b + c; // 1 + 2 + 50 = 53 -} diff --git a/affinescript/tests/codegen/test_match_simple.affine b/affinescript/tests/codegen/test_match_simple.affine deleted file mode 100644 index d024ca0..0000000 --- a/affinescript/tests/codegen/test_match_simple.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test simple pattern matching with variables - -fn main() -> Int { - let x = 42; - - let result = match x { - y => y + 10 - }; - - return result; // Should return 52 -} diff --git a/affinescript/tests/codegen/test_multiple_calls.affine b/affinescript/tests/codegen/test_multiple_calls.affine deleted file mode 100644 index 7129d2c..0000000 --- a/affinescript/tests/codegen/test_multiple_calls.affine +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test multiple function calls and composition - -fn add(a: Int, b: Int) -> Int { - return a + b; -} - -fn multiply(a: Int, b: Int) -> Int { - return a * b; -} - -fn square(x: Int) -> Int { - return multiply(x, x); -} - -fn main() -> Int { - let a = add(10, 5); // 15 - let b = square(3); // 9 - let c = multiply(a, b); // 135 - return c; -} diff --git a/affinescript/tests/codegen/test_multiple_calls.mjs b/affinescript/tests/codegen/test_multiple_calls.mjs deleted file mode 100644 index 6d1c9a4..0000000 --- a/affinescript/tests/codegen/test_multiple_calls.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 hyperpolymath - -import { readFile } from 'fs/promises'; - -const wasmBuffer = await readFile('./tests/codegen/test_multiple_calls.wasm'); -const imports = { - wasi_snapshot_preview1: { - fd_write: () => 0, - }, -}; -const wasmModule = await WebAssembly.instantiate(wasmBuffer, imports); -const result = wasmModule.instance.exports.main(); - -console.log(`Result: ${result}`); -console.log(`Expected: 135`); -console.log(`Test ${result === 135 ? 'PASSED ✓' : 'FAILED ✗'}`); - -process.exit(result === 135 ? 0 : 1); diff --git a/affinescript/tests/codegen/test_multiple_lambdas.affine b/affinescript/tests/codegen/test_multiple_lambdas.affine deleted file mode 100644 index 5c7653a..0000000 --- a/affinescript/tests/codegen/test_multiple_lambdas.affine +++ /dev/null @@ -1,25 +0,0 @@ -// Test multiple lambda functions - -fn main() -> Int { - // First lambda - adds 10 - let add_ten = |x: Int| -> Int { - return x + 10; - }; - - // Second lambda - multiplies by 2 - let double = |x: Int| -> Int { - return x * 2; - }; - - // Third lambda - subtracts 5 - let sub_five = |x: Int| -> Int { - return x - 5; - }; - - // Use all three lambdas - let a = add_ten(5); // 15 - let b = double(a); // 30 - let c = sub_five(b); // 25 - - return c; // Should return 25 -} diff --git a/affinescript/tests/codegen/test_opt_constant_fold.affine b/affinescript/tests/codegen/test_opt_constant_fold.affine deleted file mode 100644 index fa40889..0000000 --- a/affinescript/tests/codegen/test_opt_constant_fold.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test constant folding optimization - -fn main() -> Int { - let a = 2 + 3; // Should fold to 5 - let b = 10 * 4; // Should fold to 40 - let c = 20 - 5; // Should fold to 15 - return a + b + c; // 5 + 40 + 15 = 60 -} diff --git a/affinescript/tests/codegen/test_opt_if_fold.affine b/affinescript/tests/codegen/test_opt_if_fold.affine deleted file mode 100644 index 8bdd62f..0000000 --- a/affinescript/tests/codegen/test_opt_if_fold.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test if-expression constant folding - -fn main() -> Int { - let x = if true { 42; } else { 0; }; // Should fold to 42 - let y = if false { 99; } else { 10; }; // Should fold to 10 - return x + y; // 42 + 10 = 52 -} diff --git a/affinescript/tests/codegen/test_pattern_as.affine b/affinescript/tests/codegen/test_pattern_as.affine deleted file mode 100644 index 7fb16e8..0000000 --- a/affinescript/tests/codegen/test_pattern_as.affine +++ /dev/null @@ -1,12 +0,0 @@ -// Test as pattern (x @ pattern) - -fn main() -> Int { - let x = 42; - - let result = match x { - y @ 42 => y + 1, - _ => 0 - }; - - return result; // Should return 43 -} diff --git a/affinescript/tests/codegen/test_pattern_or.affine b/affinescript/tests/codegen/test_pattern_or.affine deleted file mode 100644 index 33e14ec..0000000 --- a/affinescript/tests/codegen/test_pattern_or.affine +++ /dev/null @@ -1,13 +0,0 @@ -// Test or pattern (p1 | p2) - -fn main() -> Int { - let x = 42; - - let result = match x { - 10 | 20 => 1, - 42 => 2, - _ => 0 - }; - - return result; // Should return 2 -} diff --git a/affinescript/tests/codegen/test_pattern_record.affine b/affinescript/tests/codegen/test_pattern_record.affine deleted file mode 100644 index 8d95219..0000000 --- a/affinescript/tests/codegen/test_pattern_record.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test record pattern matching - -fn main() -> Int { - let r = {x: 10, y: 20}; - - let result = match r { - {x: a, y: b} => a + b - }; - - return result; // Should return 30 -} diff --git a/affinescript/tests/codegen/test_pattern_tuple.affine b/affinescript/tests/codegen/test_pattern_tuple.affine deleted file mode 100644 index 241663a..0000000 --- a/affinescript/tests/codegen/test_pattern_tuple.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test tuple pattern matching - -fn main() -> Int { - let t = (10, 20, 30); - - let result = match t { - (a, b, c) => a + b + c - }; - - return result; // Should return 60 -} diff --git a/affinescript/tests/codegen/test_record_multi_field.affine b/affinescript/tests/codegen/test_record_multi_field.affine deleted file mode 100644 index ce45104..0000000 --- a/affinescript/tests/codegen/test_record_multi_field.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test record with multiple fields - -fn main() -> Int { - let r = {x: 10, y: 20, z: 30}; - return r.x + r.y + r.z; // Should return 60 -} diff --git a/affinescript/tests/codegen/test_record_multiple.affine b/affinescript/tests/codegen/test_record_multiple.affine deleted file mode 100644 index 4b9ec5e..0000000 --- a/affinescript/tests/codegen/test_record_multiple.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test multiple distinct records - -fn main() -> Int { - let r1 = {a: 5, b: 10}; - let r2 = {x: 20, y: 30, z: 40}; - - return r1.a + r1.b + r2.x + r2.y + r2.z; // 5 + 10 + 20 + 30 + 40 = 105 -} diff --git a/affinescript/tests/codegen/test_record_simple.affine b/affinescript/tests/codegen/test_record_simple.affine deleted file mode 100644 index 072353f..0000000 --- a/affinescript/tests/codegen/test_record_simple.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test simple record construction and field access - -fn main() -> Int { - let r = {x: 42}; - return r.x; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_recursive_call.affine b/affinescript/tests/codegen/test_recursive_call.affine deleted file mode 100644 index 83bdaa4..0000000 --- a/affinescript/tests/codegen/test_recursive_call.affine +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test recursive function calls - -fn factorial(n: Int) -> Int { - return if n <= 1 { - return 1; - } else { - return n * factorial(n - 1); - }; -} - -fn main() -> Int { - return factorial(5); // Should return 120 -} diff --git a/affinescript/tests/codegen/test_ref_deref_chain.affine b/affinescript/tests/codegen/test_ref_deref_chain.affine deleted file mode 100644 index 16a7d42..0000000 --- a/affinescript/tests/codegen/test_ref_deref_chain.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test chained references and dereferences - -fn main() -> Int { - let x = 42; - let p1 = &x; - let p2 = &p1; // Pointer to pointer - let v1 = *p2; // Get p1 - let v2 = *v1; // Get x - return v2; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_ref_deref_simple.affine b/affinescript/tests/codegen/test_ref_deref_simple.affine deleted file mode 100644 index 806edfc..0000000 --- a/affinescript/tests/codegen/test_ref_deref_simple.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test reference and dereference operators - -fn main() -> Int { - let x = 42; - let ptr = &x; // Take reference to x - let y = *ptr; // Dereference to get value - return y; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_simple_lambda.affine b/affinescript/tests/codegen/test_simple_lambda.affine deleted file mode 100644 index 70340c5..0000000 --- a/affinescript/tests/codegen/test_simple_lambda.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test simple lambda expression compilation - -fn main() -> Int { - // Simple lambda that adds 1 - let f = |x: Int| -> Int { - return x + 1; - }; - - return 42; -} diff --git a/affinescript/tests/codegen/test_string_diff.affine b/affinescript/tests/codegen/test_string_diff.affine deleted file mode 100644 index d7ac638..0000000 --- a/affinescript/tests/codegen/test_string_diff.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test different strings get different pointers - -fn main() -> Int { - let s1 = "Hello"; - let s2 = "World"; - - // Different strings should have different pointers - // s1 at 2048, s2 at 2048 + 4 + 5 = 2057 - // Difference should be 9 - return s2 - s1; -} diff --git a/affinescript/tests/codegen/test_string_eq.affine b/affinescript/tests/codegen/test_string_eq.affine deleted file mode 100644 index 0ad6100..0000000 --- a/affinescript/tests/codegen/test_string_eq.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test string equality via pointer comparison - -fn main() -> Int { - let s1 = "Hello"; - let s2 = "Hello"; - - // If deduplication works, s1 and s2 have the same pointer value - // Compare them: if equal, both point to 2048 - return s1 - s2; // Should return 0 if equal -} diff --git a/affinescript/tests/codegen/test_string_multi.affine b/affinescript/tests/codegen/test_string_multi.affine deleted file mode 100644 index b390533..0000000 --- a/affinescript/tests/codegen/test_string_multi.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test multiple strings - -fn main() -> Int { - let s1 = "Hello"; - let s2 = "World"; - let s3 = "Hello"; // Should reuse same data as s1 - return s1; // Return pointer to first string -} diff --git a/affinescript/tests/codegen/test_string_ptr.affine b/affinescript/tests/codegen/test_string_ptr.affine deleted file mode 100644 index f405728..0000000 --- a/affinescript/tests/codegen/test_string_ptr.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test string pointer value - -fn main() -> Int { - let s = "Hello"; - return s; // Return the pointer to the string -} diff --git a/affinescript/tests/codegen/test_string_simple.affine b/affinescript/tests/codegen/test_string_simple.affine deleted file mode 100644 index 6ed85d9..0000000 --- a/affinescript/tests/codegen/test_string_simple.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test simple string literal - -fn main() -> Int { - let s = "Hello"; - return 42; // Just return a value, string stored but not used yet -} diff --git a/affinescript/tests/codegen/test_tuple_five.affine b/affinescript/tests/codegen/test_tuple_five.affine deleted file mode 100644 index 4a1df6d..0000000 --- a/affinescript/tests/codegen/test_tuple_five.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test tuple with 5 elements - -fn main() -> Int { - let t = (1, 2, 3, 4, 5); - - // Sum all elements - let sum = t.0 + t.1 + t.2 + t.3 + t.4; - - return sum; // Should return 15 -} diff --git a/affinescript/tests/codegen/test_tuple_nested.affine b/affinescript/tests/codegen/test_tuple_nested.affine deleted file mode 100644 index e92b1ac..0000000 --- a/affinescript/tests/codegen/test_tuple_nested.affine +++ /dev/null @@ -1,8 +0,0 @@ -// Test tuple with nested expressions - -fn main() -> Int { - let arr = [100, 200]; - let t = (arr[0], arr[1], 300); - - return t.0 + t.1 + t.2; // Should return 600 -} diff --git a/affinescript/tests/codegen/test_tuple_pair.affine b/affinescript/tests/codegen/test_tuple_pair.affine deleted file mode 100644 index 6cbc93e..0000000 --- a/affinescript/tests/codegen/test_tuple_pair.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test tuple pair (2-element tuple) - -fn main() -> Int { - let pair = (100, 200); - return pair.0 + pair.1; // Should return 300 -} diff --git a/affinescript/tests/codegen/test_tuple_record_array.affine b/affinescript/tests/codegen/test_tuple_record_array.affine deleted file mode 100644 index d0f597d..0000000 --- a/affinescript/tests/codegen/test_tuple_record_array.affine +++ /dev/null @@ -1,10 +0,0 @@ -// Test tuple/record/array construction together - -type Point = { x: Int, y: Int, z: Int }; - -fn main() -> Int { - let p = { x: 10, y: 20, z: 30 }; - let t = (1, 2, 3, 4); - let a = [5, 6]; - return p.x + p.y + p.z + t.0 + t.1 + t.2 + t.3 + a[0] + a[1]; -} diff --git a/affinescript/tests/codegen/test_tuple_record_array.mjs b/affinescript/tests/codegen/test_tuple_record_array.mjs deleted file mode 100644 index 8bcd851..0000000 --- a/affinescript/tests/codegen/test_tuple_record_array.mjs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2025 hyperpolymath - -import { readFile } from 'fs/promises'; - -const wasmBuffer = await readFile('./tests/codegen/test_tuple_record_array.wasm'); -const imports = { - wasi_snapshot_preview1: { - fd_write: () => 0, - }, -}; -const wasmModule = await WebAssembly.instantiate(wasmBuffer, imports); -const result = wasmModule.instance.exports.main(); - -console.log(`Result: ${result}`); -console.log('Expected: 81'); -console.log(`Test ${result === 81 ? 'PASSED ✓' : 'FAILED ✗'}`); - -process.exit(result === 81 ? 0 : 1); diff --git a/affinescript/tests/codegen/test_tuple_simple.affine b/affinescript/tests/codegen/test_tuple_simple.affine deleted file mode 100644 index b472c25..0000000 --- a/affinescript/tests/codegen/test_tuple_simple.affine +++ /dev/null @@ -1,6 +0,0 @@ -// Test simple tuple creation and indexing - -fn main() -> Int { - let t = (10, 20, 30); - return t.1; // Should return 20 -} diff --git a/affinescript/tests/codegen/test_variant_as_int.affine b/affinescript/tests/codegen/test_variant_as_int.affine deleted file mode 100644 index 58bcc2f..0000000 --- a/affinescript/tests/codegen/test_variant_as_int.affine +++ /dev/null @@ -1,12 +0,0 @@ -// Test variant-like behavior using integers as tags - -fn main() -> Int { - let color = 0; // Representing "Red" as tag 0 - - return match color { - 0 => 10, // Red - 1 => 20, // Green - 2 => 30, // Blue - _ => 999 - }; // Should return 10 -} diff --git a/affinescript/tests/codegen/test_variant_match_arg.affine b/affinescript/tests/codegen/test_variant_match_arg.affine deleted file mode 100644 index e56e572..0000000 --- a/affinescript/tests/codegen/test_variant_match_arg.affine +++ /dev/null @@ -1,17 +0,0 @@ -// Test pattern matching on variant with argument - -enum Option { - None, - Some(Int) -} - -fn main() -> Int { - let x = Option::Some(42); - - let result = match x { - Some(y) => y, - None => 0 - }; - - return result; // Should return 42 -} diff --git a/affinescript/tests/codegen/test_variant_match_none.affine b/affinescript/tests/codegen/test_variant_match_none.affine deleted file mode 100644 index 2eb63df..0000000 --- a/affinescript/tests/codegen/test_variant_match_none.affine +++ /dev/null @@ -1,17 +0,0 @@ -// Test pattern matching on None variant - -enum Option { - None, - Some(Int) -} - -fn main() -> Int { - let x = Option::None; - - let result = match x { - Some(y) => y, - None => 99 - }; - - return result; // Should return 99 -} diff --git a/affinescript/tests/codegen/test_variant_multi_arg.affine b/affinescript/tests/codegen/test_variant_multi_arg.affine deleted file mode 100644 index bc5eeb8..0000000 --- a/affinescript/tests/codegen/test_variant_multi_arg.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test variant constructor with multiple arguments - -enum Result { - Ok(Int, Int), - Err(Int) -} - -fn main() -> Int { - let r = Result::Ok(10, 20); - return r; // Return pointer to variant -} diff --git a/affinescript/tests/codegen/test_variant_with_arg.affine b/affinescript/tests/codegen/test_variant_with_arg.affine deleted file mode 100644 index ec27707..0000000 --- a/affinescript/tests/codegen/test_variant_with_arg.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test variant constructor with single argument - -enum Option { - None, - Some(Int) -} - -fn main() -> Int { - let x = Option::Some(42); - return x; // Return pointer to variant -} diff --git a/affinescript/tests/codegen/test_while_loop.affine b/affinescript/tests/codegen/test_while_loop.affine deleted file mode 100644 index 836fe8b..0000000 --- a/affinescript/tests/codegen/test_while_loop.affine +++ /dev/null @@ -1,11 +0,0 @@ -// Test while loop - -fn main() -> Int { - let mut sum = 0; - let mut i = 1; - while i <= 10 { - sum += i; - i += 1; - } - return sum; // Should return 55 (1+2+3+...+10) -} diff --git a/affinescript/tests/conformance/README.adoc b/affinescript/tests/conformance/README.adoc deleted file mode 100644 index f0e4c2b..0000000 --- a/affinescript/tests/conformance/README.adoc +++ /dev/null @@ -1,174 +0,0 @@ -== aLib Conformance Tests - -This directory contains conformance tests for the *aggregate-library -(aLib)* specification. - -=== Purpose - -These tests verify that AffineScript’s standard library operations -conform to the language-agnostic specifications defined in the -https://github.com/hyperpolymath/aggregate-library[aggregate-library] -project. - -=== What is aLib? - -aggregate-library (aLib) is a *methodology repository* that provides: - -Language-agnostic operation specifications - Behavioral semantics and -properties - Executable test vectors in YAML format - -aLib is NOT a code library - it’s a way to specify minimal overlap -between diverse programming ecosystems. - -=== Test Structure - -.... -tests/conformance/ -├── arithmetic/ # Arithmetic operation tests -│ └── add.affine -├── collection/ # Collection operation tests -│ ├── map.affine -│ ├── filter.affine -│ ├── fold.affine -│ └── contains.affine -├── run_all.affine # Master test runner -└── README.md # This file -.... - -=== Running Tests - -==== Run all conformance tests: - -[source,bash] ----- -affinescript tests/conformance/run_all.affine ----- - -==== Run specific category: - -[source,bash] ----- -affinescript tests/conformance/collection/map.affine ----- - -==== Expected Output: - -.... -================================================================================ -aLib Conformance Report -================================================================================ - -✓ PASS collection/map: 5/5 tests -✓ PASS collection/filter: 5/5 tests -✓ PASS collection/fold: 6/6 tests -✓ PASS collection/contains: 6/6 tests -✓ PASS arithmetic/add: 5/5 tests - -================================================================================ -Summary -================================================================================ -Total operations tested: 5 -Conformant operations: 5/5 -Total test cases: 27 -Tests passed: 27 -Tests failed: 0 -Conformance rate: 100% - -✓ Excellent aLib conformance (≥95%) -================================================================================ -.... - -=== Test Vector Sources - -Each test file includes a reference to its source aLib spec: - -[source,affinescript] ----- -// Source: aggregate-library/specs/collection/map.md ----- - -=== Conformance Criteria - -* *100% conformance*: All test vectors pass -* *≥95% conformance*: Excellent (production-ready) -* *≥80% conformance*: Good (acceptable with documented gaps) -* *<80% conformance*: Needs improvement - -=== AffineScript-Specific Semantics - -AffineScript’s conformance tests respect affine type constraints: - -==== map - -* Source collection *moved* (not copied) -* Elements consumed *exactly once* -* Result owned by caller - -==== filter - -* Predicate *borrows* (`+&T -> Bool+`) -* Source collection *moved* -* Filtered elements automatically *dropped* - -==== fold - -* Accumulator ownership tracked -* Source collection *moved* -* Left-associative evaluation - -==== contains - -* Requires `+Eq+` trait on element type -* Short-circuit on first match -* Source collection *borrowed* (not moved) - -=== Adding New Conformance Tests - -[arabic] -. Read the aLib spec from `+aggregate-library/specs/+` -. Extract test vectors from YAML section -. Create `+tests/conformance//.affine+` -. Translate aLib function expressions to AffineScript syntax -. Add test to `+run_all.affine+` - -Example: - -[source,affinescript] ----- -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Source: aggregate-library/specs/collection/map.md - -fn test_map_double() -> TestResult { - let input = [1, 2, 3]; - let result = map(input, fn(x) => x * 2); - assert_eq(result, [2, 4, 6], "Double each number"); - Pass -} ----- - -=== Integration Strategy - -See link:../../docs/ALIB-INTEGRATION.md[docs/ALIB-INTEGRATION.md] for -the complete aLib integration roadmap. - -=== Status - -*Phase 1: Conformance* ✅ *COMPLETE* - [x] Collection conformance tests -(4/4 specs) ✓ - [x] Arithmetic conformance tests (5/5 specs) ✓ - [x] -Comparison conformance tests (6/6 specs) ✓ - [x] Logical conformance -tests (3/3 specs) ✓ - [x] String conformance tests (3/3 specs) ✓ - [x] -Conditional conformance tests (1/1 specs) ✓ - -*🏆 Total Progress: 22/22 specs (100% - PERFECT CONFORMANCE)* - -AffineScript now validates against all core aLib operations. Phase 1 -complete! - -=== Contributing - -When contributing affine-specific notes to aLib upstream: 1. Document -ownership semantics 2. Explain move vs borrow decisions 3. Show safety -guarantees 4. Provide affine-specific test vectors - -=== License - -MPL-2.0 (following AffineScript project license) diff --git a/affinescript/tests/conformance/arithmetic/add.affine b/affinescript/tests/conformance/arithmetic/add.affine deleted file mode 100644 index 8389aab..0000000 --- a/affinescript/tests/conformance/arithmetic/add.affine +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/add -// Source: aggregate-library/specs/arithmetic/add.md -// -// Conforms to aLib arithmetic/add spec v1.0 - -/// Test: Basic addition of positive integers -fn test_add_positive() -> TestResult { - let result = 2 + 3; - assert_eq(result, 5, "Basic addition of positive integers"); - Pass -} - -/// Test: Addition with negative number -fn test_add_negative() -> TestResult { - let result = -5 + 3; - assert_eq(result, -2, "Addition with negative number"); - Pass -} - -/// Test: Addition of zeros -fn test_add_zeros() -> TestResult { - let result = 0 + 0; - assert_eq(result, 0, "Addition of zeros"); - Pass -} - -/// Test: Addition of decimal numbers -fn test_add_decimal() -> TestResult { - let result = 1.5 + 2.5; - assert_float_eq(result, 4.0, 0.0001, "Addition of decimal numbers"); - Pass -} - -/// Test: Addition of two negative numbers -fn test_add_two_negatives() -> TestResult { - let result = -10 + -20; - assert_eq(result, -30, "Addition of two negative numbers"); - Pass -} - -/// Run all add conformance tests -fn test_alib_arithmetic_add() -> TestResult { - let suite = { - name: "aLib arithmetic/add conformance", - tests: [ - { name: "add_positive", test: test_add_positive }, - { name: "add_negative", test: test_add_negative }, - { name: "add_zeros", test: test_add_zeros }, - { name: "add_decimal", test: test_add_decimal }, - { name: "add_two_negatives", test: test_add_two_negatives } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some add conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/arithmetic/divide.affine b/affinescript/tests/conformance/arithmetic/divide.affine deleted file mode 100644 index 5fbe76a..0000000 --- a/affinescript/tests/conformance/arithmetic/divide.affine +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/divide -// Source: aggregate-library/specs/arithmetic/divide.md -// -// Conforms to aLib arithmetic/divide spec v1.0 - -/// Test: Basic division with exact result -fn test_divide_exact() -> TestResult { - let result = 6 / 2; - assert_eq(result, 3, "Basic division with exact result"); - Pass -} - -/// Test: Division with fractional result -fn test_divide_fractional() -> TestResult { - let result = 7.0 / 2.0; - assert_float_eq(result, 3.5, 0.0001, "Division with fractional result"); - Pass -} - -/// Test: Zero divided by non-zero -fn test_divide_zero() -> TestResult { - let result = 0 / 5; - assert_eq(result, 0, "Zero divided by non-zero"); - Pass -} - -/// Test: Division with negative dividend -fn test_divide_negative_dividend() -> TestResult { - let result = -10 / 2; - assert_eq(result, -5, "Division with negative dividend"); - Pass -} - -/// Test: Division with negative divisor -fn test_divide_negative_divisor() -> TestResult { - let result = 10 / -2; - assert_eq(result, -5, "Division with negative divisor"); - Pass -} - -/// Test: Division of two negative numbers -fn test_divide_two_negatives() -> TestResult { - let result = -12 / -3; - assert_eq(result, 4, "Division of two negative numbers"); - Pass -} - -/// Run all divide conformance tests -fn test_alib_arithmetic_divide() -> TestResult { - let suite = { - name: "aLib arithmetic/divide conformance", - tests: [ - { name: "divide_exact", test: test_divide_exact }, - { name: "divide_fractional", test: test_divide_fractional }, - { name: "divide_zero", test: test_divide_zero }, - { name: "divide_negative_dividend", test: test_divide_negative_dividend }, - { name: "divide_negative_divisor", test: test_divide_negative_divisor }, - { name: "divide_two_negatives", test: test_divide_two_negatives } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some divide conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/arithmetic/modulo.affine b/affinescript/tests/conformance/arithmetic/modulo.affine deleted file mode 100644 index ae94dfa..0000000 --- a/affinescript/tests/conformance/arithmetic/modulo.affine +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/modulo -// Source: aggregate-library/specs/arithmetic/modulo.md -// -// Conforms to aLib arithmetic/modulo spec v1.0 - -/// Test: Basic modulo with positive integers -fn test_modulo_basic() -> TestResult { - let result = 7 % 3; - assert_eq(result, 1, "Basic modulo with positive integers"); - Pass -} - -/// Test: Modulo with no remainder -fn test_modulo_no_remainder() -> TestResult { - let result = 10 % 5; - assert_eq(result, 0, "Modulo with no remainder"); - Pass -} - -/// Test: Modulo returning non-zero remainder -fn test_modulo_nonzero() -> TestResult { - let result = 15 % 4; - assert_eq(result, 3, "Modulo returning non-zero remainder"); - Pass -} - -/// Test: Zero modulo non-zero -fn test_modulo_zero() -> TestResult { - let result = 0 % 5; - assert_eq(result, 0, "Zero modulo non-zero"); - Pass -} - -/// Test: Modulo with larger numbers -fn test_modulo_large() -> TestResult { - let result = 100 % 7; - assert_eq(result, 2, "Modulo with larger numbers"); - Pass -} - -/// Run all modulo conformance tests -fn test_alib_arithmetic_modulo() -> TestResult { - let suite = { - name: "aLib arithmetic/modulo conformance", - tests: [ - { name: "modulo_basic", test: test_modulo_basic }, - { name: "modulo_no_remainder", test: test_modulo_no_remainder }, - { name: "modulo_nonzero", test: test_modulo_nonzero }, - { name: "modulo_zero", test: test_modulo_zero }, - { name: "modulo_large", test: test_modulo_large } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some modulo conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/arithmetic/multiply.affine b/affinescript/tests/conformance/arithmetic/multiply.affine deleted file mode 100644 index eabd618..0000000 --- a/affinescript/tests/conformance/arithmetic/multiply.affine +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/multiply -// Source: aggregate-library/specs/arithmetic/multiply.md -// -// Conforms to aLib arithmetic/multiply spec v1.0 - -/// Test: Basic multiplication of positive integers -fn test_multiply_positive() -> TestResult { - let result = 2 * 3; - assert_eq(result, 6, "Basic multiplication of positive integers"); - Pass -} - -/// Test: Multiplication with negative number -fn test_multiply_negative() -> TestResult { - let result = -5 * 3; - assert_eq(result, -15, "Multiplication with negative number"); - Pass -} - -/// Test: Multiplication by zero -fn test_multiply_zero() -> TestResult { - let result = 0 * 42; - assert_eq(result, 0, "Multiplication by zero"); - Pass -} - -/// Test: Multiplication by one (identity) -fn test_multiply_identity() -> TestResult { - let result = 7 * 1; - assert_eq(result, 7, "Multiplication by one (identity)"); - Pass -} - -/// Test: Multiplication with decimal number -fn test_multiply_decimal() -> TestResult { - let result = 2.5 * 4.0; - assert_float_eq(result, 10.0, 0.0001, "Multiplication with decimal number"); - Pass -} - -/// Test: Multiplication of two negative numbers -fn test_multiply_two_negatives() -> TestResult { - let result = -3 * -4; - assert_eq(result, 12, "Multiplication of two negative numbers"); - Pass -} - -/// Run all multiply conformance tests -fn test_alib_arithmetic_multiply() -> TestResult { - let suite = { - name: "aLib arithmetic/multiply conformance", - tests: [ - { name: "multiply_positive", test: test_multiply_positive }, - { name: "multiply_negative", test: test_multiply_negative }, - { name: "multiply_zero", test: test_multiply_zero }, - { name: "multiply_identity", test: test_multiply_identity }, - { name: "multiply_decimal", test: test_multiply_decimal }, - { name: "multiply_two_negatives", test: test_multiply_two_negatives } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some multiply conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/arithmetic/subtract.affine b/affinescript/tests/conformance/arithmetic/subtract.affine deleted file mode 100644 index bdb8ae9..0000000 --- a/affinescript/tests/conformance/arithmetic/subtract.affine +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: arithmetic/subtract -// Source: aggregate-library/specs/arithmetic/subtract.md -// -// Conforms to aLib arithmetic/subtract spec v1.0 - -/// Test: Basic subtraction of positive integers -fn test_subtract_positive() -> TestResult { - let result = 5 - 3; - assert_eq(result, 2, "Basic subtraction of positive integers"); - Pass -} - -/// Test: Subtraction resulting in negative number -fn test_subtract_negative_result() -> TestResult { - let result = 3 - 5; - assert_eq(result, -2, "Subtraction resulting in negative number"); - Pass -} - -/// Test: Subtraction of zeros -fn test_subtract_zeros() -> TestResult { - let result = 0 - 0; - assert_eq(result, 0, "Subtraction of zeros"); - Pass -} - -/// Test: Subtracting positive from negative -fn test_subtract_from_negative() -> TestResult { - let result = -5 - 3; - assert_eq(result, -8, "Subtracting positive from negative"); - Pass -} - -/// Test: Subtraction of decimal numbers -fn test_subtract_decimal() -> TestResult { - let result = 10.5 - 2.5; - assert_float_eq(result, 8.0, 0.0001, "Subtraction of decimal numbers"); - Pass -} - -/// Test: Subtracting negative from negative -fn test_subtract_negatives() -> TestResult { - let result = -10 - -20; - assert_eq(result, 10, "Subtracting negative from negative"); - Pass -} - -/// Run all subtract conformance tests -fn test_alib_arithmetic_subtract() -> TestResult { - let suite = { - name: "aLib arithmetic/subtract conformance", - tests: [ - { name: "subtract_positive", test: test_subtract_positive }, - { name: "subtract_negative_result", test: test_subtract_negative_result }, - { name: "subtract_zeros", test: test_subtract_zeros }, - { name: "subtract_from_negative", test: test_subtract_from_negative }, - { name: "subtract_decimal", test: test_subtract_decimal }, - { name: "subtract_negatives", test: test_subtract_negatives } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some subtract conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/collection/contains.affine b/affinescript/tests/conformance/collection/contains.affine deleted file mode 100644 index f23116b..0000000 --- a/affinescript/tests/conformance/collection/contains.affine +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: collection/contains -// Source: aggregate-library/specs/collection/contains.md -// -// Conforms to aLib collection/contains spec v1.0 - -/// Test: Element is present in collection -fn test_contains_present() -> TestResult { - let input = [1, 2, 3, 4, 5]; - let result = contains(input, 3); - assert(result, "Element is present in collection"); - Pass -} - -/// Test: Element is not present in collection -fn test_contains_not_present() -> TestResult { - let input = [1, 2, 3, 4, 5]; - let result = contains(input, 6); - assert(!result, "Element is not present in collection"); - Pass -} - -/// Test: Empty collection contains no elements -fn test_contains_empty() -> TestResult { - let input = []; - let result = contains(input, 1); - assert(!result, "Empty collection contains no elements"); - Pass -} - -/// Test: Single element collection contains that element -fn test_contains_single() -> TestResult { - let input = [1]; - let result = contains(input, 1); - assert(result, "Single element collection contains that element"); - Pass -} - -/// Test: String element is present -fn test_contains_string() -> TestResult { - let input = ["a", "b", "c"]; - let result = contains(input, "b"); - assert(result, "String element is present"); - Pass -} - -/// Test: Element appears multiple times (still returns true) -fn test_contains_multiple() -> TestResult { - let input = [1, 2, 1, 3, 1]; - let result = contains(input, 1); - assert(result, "Element appears multiple times (still returns true)"); - Pass -} - -/// Run all contains conformance tests -fn test_alib_collection_contains() -> TestResult { - let suite = { - name: "aLib collection/contains conformance", - tests: [ - { name: "contains_present", test: test_contains_present }, - { name: "contains_not_present", test: test_contains_not_present }, - { name: "contains_empty", test: test_contains_empty }, - { name: "contains_single", test: test_contains_single }, - { name: "contains_string", test: test_contains_string }, - { name: "contains_multiple", test: test_contains_multiple } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some contains conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/collection/filter.affine b/affinescript/tests/conformance/collection/filter.affine deleted file mode 100644 index 4ecce62..0000000 --- a/affinescript/tests/conformance/collection/filter.affine +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: collection/filter -// Source: aggregate-library/specs/collection/filter.md -// -// Conforms to aLib collection/filter spec v1.0 - -/// Test: Filter numbers greater than 2 -fn test_filter_greater_than() -> TestResult { - let input = [1, 2, 3, 4, 5]; - let result = filter(input, fn(x) => x > 2); - assert_eq(result, [3, 4, 5], "Filter numbers greater than 2"); - Pass -} - -/// Test: Filter even numbers -fn test_filter_even() -> TestResult { - let input = [1, 2, 3, 4, 5]; - let result = filter(input, fn(x) => x % 2 == 0); - assert_eq(result, [2, 4], "Filter even numbers"); - Pass -} - -/// Test: Filter with always-true predicate returns all elements -fn test_filter_always_true() -> TestResult { - let input = [1, 2, 3]; - let result = filter(input, fn(x) => true); - assert_eq(result, [1, 2, 3], "Filter with always-true predicate returns all elements"); - Pass -} - -/// Test: Filter with always-false predicate returns empty collection -fn test_filter_always_false() -> TestResult { - let input = [1, 2, 3]; - let result = filter(input, fn(x) => false); - assert_eq(result, [], "Filter with always-false predicate returns empty collection"); - Pass -} - -/// Test: Filter empty collection returns empty collection -fn test_filter_empty() -> TestResult { - let input = []; - let result = filter(input, fn(x) => true); - assert_eq(result, [], "Filter empty collection returns empty collection"); - Pass -} - -/// Run all filter conformance tests -fn test_alib_collection_filter() -> TestResult { - let suite = { - name: "aLib collection/filter conformance", - tests: [ - { name: "filter_greater_than", test: test_filter_greater_than }, - { name: "filter_even", test: test_filter_even }, - { name: "filter_always_true", test: test_filter_always_true }, - { name: "filter_always_false", test: test_filter_always_false }, - { name: "filter_empty", test: test_filter_empty } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some filter conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/collection/fold.affine b/affinescript/tests/conformance/collection/fold.affine deleted file mode 100644 index d146da1..0000000 --- a/affinescript/tests/conformance/collection/fold.affine +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: collection/fold -// Source: aggregate-library/specs/collection/fold.md -// -// Conforms to aLib collection/fold spec v1.0 - -/// Test: Sum all numbers in collection -fn test_fold_sum() -> TestResult { - let input = [1, 2, 3, 4]; - let result = fold(input, 0, fn(acc, x) => acc + x); - assert_eq(result, 10, "Sum all numbers in collection"); - Pass -} - -/// Test: Product of all numbers in collection -fn test_fold_product() -> TestResult { - let input = [1, 2, 3, 4]; - let result = fold(input, 1, fn(acc, x) => acc * x); - assert_eq(result, 24, "Product of all numbers in collection"); - Pass -} - -/// Test: Folding empty collection returns initial value -fn test_fold_empty() -> TestResult { - let input = []; - let result = fold(input, 42, fn(acc, x) => acc + x); - assert_eq(result, 42, "Folding empty collection returns initial value"); - Pass -} - -/// Test: Folding single element -fn test_fold_single() -> TestResult { - let input = [5]; - let result = fold(input, 10, fn(acc, x) => acc + x); - assert_eq(result, 15, "Folding single element"); - Pass -} - -/// Test: Concatenate strings using fold -fn test_fold_concat() -> TestResult { - let input = ["a", "b", "c"]; - let result = fold(input, "", fn(acc, s) => acc ++ s); - assert_eq(result, "abc", "Concatenate strings using fold"); - Pass -} - -/// Test: Count elements using fold -fn test_fold_count() -> TestResult { - let input = [1, 2, 3]; - let result = fold(input, 0, fn(acc, x) => acc + 1); - assert_eq(result, 3, "Count elements using fold"); - Pass -} - -/// Run all fold conformance tests -fn test_alib_collection_fold() -> TestResult { - let suite = { - name: "aLib collection/fold conformance", - tests: [ - { name: "fold_sum", test: test_fold_sum }, - { name: "fold_product", test: test_fold_product }, - { name: "fold_empty", test: test_fold_empty }, - { name: "fold_single", test: test_fold_single }, - { name: "fold_concat", test: test_fold_concat }, - { name: "fold_count", test: test_fold_count } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some fold conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/collection/map.affine b/affinescript/tests/conformance/collection/map.affine deleted file mode 100644 index eb4b828..0000000 --- a/affinescript/tests/conformance/collection/map.affine +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: collection/map -// Source: aggregate-library/specs/collection/map.md -// -// Conforms to aLib collection/map spec v1.0 - -/// Test: Double each number in collection -fn test_map_double() -> TestResult { - let input = [1, 2, 3]; - let result = map(input, fn(x) => x * 2); - assert_eq(result, [2, 4, 6], "Double each number in collection"); - Pass -} - -/// Test: Add 10 to each number -fn test_map_add_ten() -> TestResult { - let input = [1, 2, 3]; - let result = map(input, fn(x) => x + 10); - assert_eq(result, [11, 12, 13], "Add 10 to each number"); - Pass -} - -/// Test: Mapping over empty collection returns empty collection -fn test_map_empty() -> TestResult { - let input = []; - let result = map(input, fn(x) => x * 2); - assert_eq(result, [], "Mapping over empty collection returns empty collection"); - Pass -} - -/// Test: Identity function returns same element -fn test_map_identity() -> TestResult { - let input = [5]; - let result = map(input, fn(x) => x); - assert_eq(result, [5], "Identity function returns same element"); - Pass -} - -/// Test: Map over string collection -fn test_map_string() -> TestResult { - let input = ["a", "b", "c"]; - let result = map(input, fn(s) => s ++ s); - assert_eq(result, ["aa", "bb", "cc"], "Map over string collection"); - Pass -} - -/// Run all map conformance tests -fn test_alib_collection_map() -> TestResult { - let suite = { - name: "aLib collection/map conformance", - tests: [ - { name: "map_double", test: test_map_double }, - { name: "map_add_ten", test: test_map_add_ten }, - { name: "map_empty", test: test_map_empty }, - { name: "map_identity", test: test_map_identity }, - { name: "map_string", test: test_map_string } - ] - }; - - let (passed, failed) = run_suite(suite); - - if failed == 0 { - Pass - } else { - Fail("Some map conformance tests failed") - } -} diff --git a/affinescript/tests/conformance/comparison/equal.affine b/affinescript/tests/conformance/comparison/equal.affine deleted file mode 100644 index 7550482..0000000 --- a/affinescript/tests/conformance/comparison/equal.affine +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/equal -// Source: aggregate-library/specs/comparison/equal.md -// -// Conforms to aLib comparison/equal spec v1.0 - -fn test_equal_positive() -> TestResult { - assert(5 == 5, "Equal positive integers"); - Pass -} - -fn test_equal_unequal() -> TestResult { - assert(!(5 == 3), "Unequal positive integers"); - Pass -} - -fn test_equal_zero() -> TestResult { - assert(0 == 0, "Zero equals zero"); - Pass -} - -fn test_equal_negative() -> TestResult { - assert(-5 == -5, "Equal negative integers"); - Pass -} - -fn test_equal_neg_pos() -> TestResult { - assert(!(-5 == 5), "Negative and positive not equal"); - Pass -} - -fn test_equal_decimal() -> TestResult { - assert(2.5 == 2.5, "Equal decimal numbers"); - Pass -} - -fn test_equal_int_decimal() -> TestResult { - assert(1.0 == 1, "Decimal and integer with same value"); - Pass -} - -fn test_alib_comparison_equal() -> TestResult { - let suite = { - name: "aLib comparison/equal conformance", - tests: [ - { name: "equal_positive", test: test_equal_positive }, - { name: "equal_unequal", test: test_equal_unequal }, - { name: "equal_zero", test: test_equal_zero }, - { name: "equal_negative", test: test_equal_negative }, - { name: "equal_neg_pos", test: test_equal_neg_pos }, - { name: "equal_decimal", test: test_equal_decimal }, - { name: "equal_int_decimal", test: test_equal_int_decimal } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some equal tests failed") } -} diff --git a/affinescript/tests/conformance/comparison/greater_equal.affine b/affinescript/tests/conformance/comparison/greater_equal.affine deleted file mode 100644 index 10b34c5..0000000 --- a/affinescript/tests/conformance/comparison/greater_equal.affine +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/greater_equal -// Source: aggregate-library/specs/comparison/greater_equal.md -// -// Conforms to aLib comparison/greater_equal spec v1.0 - -fn test_greater_equal_greater() -> TestResult { - assert(5 >= 2, "First number is greater than second"); - Pass -} - -fn test_greater_equal_equal() -> TestResult { - assert(5 >= 5, "Numbers are equal"); - Pass -} - -fn test_greater_equal_less() -> TestResult { - assert(!(2 >= 5), "First number is less than second"); - Pass -} - -fn test_greater_equal_negative() -> TestResult { - assert(-2 >= -5, "Negative numbers comparison"); - Pass -} - -fn test_greater_equal_zero() -> TestResult { - assert(0 >= 0, "Zero equals zero"); - Pass -} - -fn test_alib_comparison_greater_equal() -> TestResult { - let suite = { - name: "aLib comparison/greater_equal conformance", - tests: [ - { name: "greater_equal_greater", test: test_greater_equal_greater }, - { name: "greater_equal_equal", test: test_greater_equal_equal }, - { name: "greater_equal_less", test: test_greater_equal_less }, - { name: "greater_equal_negative", test: test_greater_equal_negative }, - { name: "greater_equal_zero", test: test_greater_equal_zero } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some greater_equal tests failed") } -} diff --git a/affinescript/tests/conformance/comparison/greater_than.affine b/affinescript/tests/conformance/comparison/greater_than.affine deleted file mode 100644 index f7ee72f..0000000 --- a/affinescript/tests/conformance/comparison/greater_than.affine +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/greater_than -// Source: aggregate-library/specs/comparison/greater_than.md -// -// Conforms to aLib comparison/greater_than spec v1.0 - -fn test_greater_than_true() -> TestResult { - assert(5 > 2, "First number is greater than second"); - Pass -} - -fn test_greater_than_false() -> TestResult { - assert(!(2 > 5), "First number is less than second"); - Pass -} - -fn test_greater_than_equal() -> TestResult { - assert(!(3 > 3), "Numbers are equal"); - Pass -} - -fn test_greater_than_negative() -> TestResult { - assert(-2 > -5, "Negative numbers comparison"); - Pass -} - -fn test_greater_than_zero() -> TestResult { - assert(5 > 0, "Positive greater than zero"); - Pass -} - -fn test_alib_comparison_greater_than() -> TestResult { - let suite = { - name: "aLib comparison/greater_than conformance", - tests: [ - { name: "greater_than_true", test: test_greater_than_true }, - { name: "greater_than_false", test: test_greater_than_false }, - { name: "greater_than_equal", test: test_greater_than_equal }, - { name: "greater_than_negative", test: test_greater_than_negative }, - { name: "greater_than_zero", test: test_greater_than_zero } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some greater_than tests failed") } -} diff --git a/affinescript/tests/conformance/comparison/less_equal.affine b/affinescript/tests/conformance/comparison/less_equal.affine deleted file mode 100644 index 6e8650d..0000000 --- a/affinescript/tests/conformance/comparison/less_equal.affine +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/less_equal -// Source: aggregate-library/specs/comparison/less_equal.md -// -// Conforms to aLib comparison/less_equal spec v1.0 - -fn test_less_equal_less() -> TestResult { - assert(2 <= 5, "First number is less than second"); - Pass -} - -fn test_less_equal_equal() -> TestResult { - assert(5 <= 5, "Numbers are equal"); - Pass -} - -fn test_less_equal_greater() -> TestResult { - assert(!(5 <= 2), "First number is greater than second"); - Pass -} - -fn test_less_equal_negative() -> TestResult { - assert(-5 <= -2, "Negative numbers comparison"); - Pass -} - -fn test_less_equal_zero() -> TestResult { - assert(0 <= 0, "Zero equals zero"); - Pass -} - -fn test_alib_comparison_less_equal() -> TestResult { - let suite = { - name: "aLib comparison/less_equal conformance", - tests: [ - { name: "less_equal_less", test: test_less_equal_less }, - { name: "less_equal_equal", test: test_less_equal_equal }, - { name: "less_equal_greater", test: test_less_equal_greater }, - { name: "less_equal_negative", test: test_less_equal_negative }, - { name: "less_equal_zero", test: test_less_equal_zero } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some less_equal tests failed") } -} diff --git a/affinescript/tests/conformance/comparison/less_than.affine b/affinescript/tests/conformance/comparison/less_than.affine deleted file mode 100644 index d372fe0..0000000 --- a/affinescript/tests/conformance/comparison/less_than.affine +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/less_than -// Source: aggregate-library/specs/comparison/less_than.md -// -// Conforms to aLib comparison/less_than spec v1.0 - -fn test_less_than_true() -> TestResult { - assert(2 < 5, "First number is less than second"); - Pass -} - -fn test_less_than_false() -> TestResult { - assert(!(5 < 2), "First number is greater than second"); - Pass -} - -fn test_less_than_equal() -> TestResult { - assert(!(3 < 3), "Numbers are equal"); - Pass -} - -fn test_less_than_negative() -> TestResult { - assert(-5 < -2, "Negative numbers comparison"); - Pass -} - -fn test_less_than_zero() -> TestResult { - assert(0 < 5, "Zero less than positive"); - Pass -} - -fn test_alib_comparison_less_than() -> TestResult { - let suite = { - name: "aLib comparison/less_than conformance", - tests: [ - { name: "less_than_true", test: test_less_than_true }, - { name: "less_than_false", test: test_less_than_false }, - { name: "less_than_equal", test: test_less_than_equal }, - { name: "less_than_negative", test: test_less_than_negative }, - { name: "less_than_zero", test: test_less_than_zero } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some less_than tests failed") } -} diff --git a/affinescript/tests/conformance/comparison/not_equal.affine b/affinescript/tests/conformance/comparison/not_equal.affine deleted file mode 100644 index 0ce843c..0000000 --- a/affinescript/tests/conformance/comparison/not_equal.affine +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: comparison/not_equal -// Source: aggregate-library/specs/comparison/not_equal.md -// -// Conforms to aLib comparison/not_equal spec v1.0 - -fn test_not_equal_unequal() -> TestResult { - assert(5 != 3, "Unequal positive integers"); - Pass -} - -fn test_not_equal_equal() -> TestResult { - assert(!(5 != 5), "Equal positive integers"); - Pass -} - -fn test_not_equal_zero() -> TestResult { - assert(!(0 != 0), "Zero equals zero"); - Pass -} - -fn test_not_equal_negative() -> TestResult { - assert(!(-5 != -5), "Equal negative integers"); - Pass -} - -fn test_not_equal_neg_pos() -> TestResult { - assert(-5 != 5, "Negative and positive not equal"); - Pass -} - -fn test_alib_comparison_not_equal() -> TestResult { - let suite = { - name: "aLib comparison/not_equal conformance", - tests: [ - { name: "not_equal_unequal", test: test_not_equal_unequal }, - { name: "not_equal_equal", test: test_not_equal_equal }, - { name: "not_equal_zero", test: test_not_equal_zero }, - { name: "not_equal_negative", test: test_not_equal_negative }, - { name: "not_equal_neg_pos", test: test_not_equal_neg_pos } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some not_equal tests failed") } -} diff --git a/affinescript/tests/conformance/conditional/if_then_else.affine b/affinescript/tests/conformance/conditional/if_then_else.affine deleted file mode 100644 index 0c765fa..0000000 --- a/affinescript/tests/conformance/conditional/if_then_else.affine +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: conditional/if_then_else -// Source: aggregate-library/specs/conditional/if_then_else.md -// -// Conforms to aLib conditional/if_then_else spec v1.0 - -fn test_if_then_else_true() -> TestResult { - let result = if true { 10 } else { 20 }; - assert_eq(result, 10, "Condition is true, return then_value"); - Pass -} - -fn test_if_then_else_false() -> TestResult { - let result = if false { 10 } else { 20 }; - assert_eq(result, 20, "Condition is false, return else_value"); - Pass -} - -fn test_if_then_else_string_true() -> TestResult { - let result = if true { "yes" } else { "no" }; - assert_eq(result, "yes", "Works with string values"); - Pass -} - -fn test_if_then_else_string_false() -> TestResult { - let result = if false { "yes" } else { "no" }; - assert_eq(result, "no", "Returns else branch for false condition"); - Pass -} - -fn test_if_then_else_comparison_true() -> TestResult { - let result = if 5 == 5 { 1 } else { 0 }; - assert_eq(result, 1, "Condition from comparison operation"); - Pass -} - -fn test_if_then_else_comparison_false() -> TestResult { - let result = if 3 > 5 { "bigger" } else { "smaller" }; - assert_eq(result, "smaller", "False comparison leads to else branch"); - Pass -} - -fn test_alib_conditional_if_then_else() -> TestResult { - let suite = { - name: "aLib conditional/if_then_else conformance", - tests: [ - { name: "if_then_else_true", test: test_if_then_else_true }, - { name: "if_then_else_false", test: test_if_then_else_false }, - { name: "if_then_else_string_true", test: test_if_then_else_string_true }, - { name: "if_then_else_string_false", test: test_if_then_else_string_false }, - { name: "if_then_else_comparison_true", test: test_if_then_else_comparison_true }, - { name: "if_then_else_comparison_false", test: test_if_then_else_comparison_false } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some if_then_else tests failed") } -} diff --git a/affinescript/tests/conformance/logical/and.affine b/affinescript/tests/conformance/logical/and.affine deleted file mode 100644 index 2d598b1..0000000 --- a/affinescript/tests/conformance/logical/and.affine +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: logical/and -// Source: aggregate-library/specs/logical/and.md -// -// Conforms to aLib logical/and spec v1.0 - -fn test_and_true_true() -> TestResult { - assert(true && true, "Both values are true"); - Pass -} - -fn test_and_true_false() -> TestResult { - assert(!(true && false), "First is true, second is false"); - Pass -} - -fn test_and_false_true() -> TestResult { - assert(!(false && true), "First is false, second is true"); - Pass -} - -fn test_and_false_false() -> TestResult { - assert(!(false && false), "Both values are false"); - Pass -} - -fn test_alib_logical_and() -> TestResult { - let suite = { - name: "aLib logical/and conformance", - tests: [ - { name: "and_true_true", test: test_and_true_true }, - { name: "and_true_false", test: test_and_true_false }, - { name: "and_false_true", test: test_and_false_true }, - { name: "and_false_false", test: test_and_false_false } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some and tests failed") } -} diff --git a/affinescript/tests/conformance/logical/not.affine b/affinescript/tests/conformance/logical/not.affine deleted file mode 100644 index 3dc4b5e..0000000 --- a/affinescript/tests/conformance/logical/not.affine +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: logical/not -// Source: aggregate-library/specs/logical/not.md -// -// Conforms to aLib logical/not spec v1.0 - -fn test_not_true() -> TestResult { - assert(!true == false, "Negation of true is false"); - Pass -} - -fn test_not_false() -> TestResult { - assert(!false == true, "Negation of false is true"); - Pass -} - -fn test_alib_logical_not() -> TestResult { - let suite = { - name: "aLib logical/not conformance", - tests: [ - { name: "not_true", test: test_not_true }, - { name: "not_false", test: test_not_false } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some not tests failed") } -} diff --git a/affinescript/tests/conformance/logical/or.affine b/affinescript/tests/conformance/logical/or.affine deleted file mode 100644 index b5fa9cf..0000000 --- a/affinescript/tests/conformance/logical/or.affine +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: logical/or -// Source: aggregate-library/specs/logical/or.md -// -// Conforms to aLib logical/or spec v1.0 - -fn test_or_true_true() -> TestResult { - assert(true || true, "Both values are true"); - Pass -} - -fn test_or_true_false() -> TestResult { - assert(true || false, "First is true, second is false"); - Pass -} - -fn test_or_false_true() -> TestResult { - assert(false || true, "First is false, second is true"); - Pass -} - -fn test_or_false_false() -> TestResult { - assert(!(false || false), "Both values are false"); - Pass -} - -fn test_alib_logical_or() -> TestResult { - let suite = { - name: "aLib logical/or conformance", - tests: [ - { name: "or_true_true", test: test_or_true_true }, - { name: "or_true_false", test: test_or_true_false }, - { name: "or_false_true", test: test_or_false_true }, - { name: "or_false_false", test: test_or_false_false } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some or tests failed") } -} diff --git a/affinescript/tests/conformance/run_all.affine b/affinescript/tests/conformance/run_all.affine deleted file mode 100644 index f8bb269..0000000 --- a/affinescript/tests/conformance/run_all.affine +++ /dev/null @@ -1,214 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Master conformance test runner for aLib specs -// Runs all conformance tests and generates report - -import stdlib/testing as Testing; -import stdlib/prelude as Prelude; - -// Import collection conformance tests -import tests/conformance/collection/map as MapTest; -import tests/conformance/collection/filter as FilterTest; -import tests/conformance/collection/fold as FoldTest; -import tests/conformance/collection/contains as ContainsTest; - -// Import arithmetic conformance tests -import tests/conformance/arithmetic/add as AddTest; -import tests/conformance/arithmetic/multiply as MultiplyTest; -import tests/conformance/arithmetic/subtract as SubtractTest; -import tests/conformance/arithmetic/divide as DivideTest; -import tests/conformance/arithmetic/modulo as ModuloTest; - -// Import comparison conformance tests -import tests/conformance/comparison/equal as EqualTest; -import tests/conformance/comparison/not_equal as NotEqualTest; -import tests/conformance/comparison/greater_than as GreaterThanTest; -import tests/conformance/comparison/less_than as LessThanTest; -import tests/conformance/comparison/greater_equal as GreaterEqualTest; -import tests/conformance/comparison/less_equal as LessEqualTest; - -// Import logical conformance tests -import tests/conformance/logical/and as AndTest; -import tests/conformance/logical/or as OrTest; -import tests/conformance/logical/not as NotTest; - -// Import string conformance tests -import tests/conformance/string/concat as ConcatTest; -import tests/conformance/string/length as LengthTest; -import tests/conformance/string/substring as SubstringTest; - -// Import conditional conformance tests -import tests/conformance/conditional/if_then_else as IfThenElseTest; - -/// Conformance report entry -type ConformanceResult = { - category: String, - operation: String, - passed: Int, - failed: Int, - conformant: Bool -} - -/// Generate conformance report -fn generate_report(results: [ConformanceResult]) -> () { - println("================================================================================"); - println("aLib Conformance Report"); - println("================================================================================"); - println(""); - - let mut total_passed = 0; - let mut total_failed = 0; - let mut total_conformant = 0; - let mut total_operations = 0; - - for result in results { - total_operations = total_operations + 1; - total_passed = total_passed + result.passed; - total_failed = total_failed + result.failed; - - if result.conformant { - total_conformant = total_conformant + 1; - } - - let status = if result.conformant { "✓ PASS" } else { "✗ FAIL" }; - let spec = result.category ++ "/" ++ result.operation; - - println(status ++ " " ++ spec ++ ": " ++ show(result.passed) ++ "/" ++ show(result.passed + result.failed) ++ " tests"); - } - - println(""); - println("================================================================================"); - println("Summary"); - println("================================================================================"); - println("Total operations tested: " ++ show(total_operations)); - println("Conformant operations: " ++ show(total_conformant) ++ "/" ++ show(total_operations)); - println("Total test cases: " ++ show(total_passed + total_failed)); - println("Tests passed: " ++ show(total_passed)); - println("Tests failed: " ++ show(total_failed)); - - let conformance_rate = (total_conformant * 100) / total_operations; - println("Conformance rate: " ++ show(conformance_rate) ++ "%"); - println(""); - - if conformance_rate >= 95 { - println("✓ Excellent aLib conformance (≥95%)"); - } else if conformance_rate >= 80 { - println("⚠ Good aLib conformance (≥80%)"); - } else if conformance_rate >= 60 { - println("⚠ Moderate aLib conformance (≥60%)"); - } else { - println("✗ Low aLib conformance (<60%)"); - } - - println("================================================================================"); -} - -/// Run a single conformance test and capture result -fn run_conformance_test(category: String, operation: String, test_fn: () -> TestResult) -> ConformanceResult { - match test_fn() { - Pass => { - category: category, - operation: operation, - passed: 1, - failed: 0, - conformant: true - }, - Fail(_) => { - category: category, - operation: operation, - passed: 0, - failed: 1, - conformant: false - } - } -} - -/// Main conformance test runner -fn main() -> () { - println("Running aLib conformance tests..."); - println(""); - - let mut results = []; - - // Collection conformance tests - results = results ++ [ - run_conformance_test("collection", "map", MapTest.test_alib_collection_map) - ]; - results = results ++ [ - run_conformance_test("collection", "filter", FilterTest.test_alib_collection_filter) - ]; - results = results ++ [ - run_conformance_test("collection", "fold", FoldTest.test_alib_collection_fold) - ]; - results = results ++ [ - run_conformance_test("collection", "contains", ContainsTest.test_alib_collection_contains) - ]; - - // Arithmetic conformance tests - results = results ++ [ - run_conformance_test("arithmetic", "add", AddTest.test_alib_arithmetic_add) - ]; - results = results ++ [ - run_conformance_test("arithmetic", "multiply", MultiplyTest.test_alib_arithmetic_multiply) - ]; - results = results ++ [ - run_conformance_test("arithmetic", "subtract", SubtractTest.test_alib_arithmetic_subtract) - ]; - results = results ++ [ - run_conformance_test("arithmetic", "divide", DivideTest.test_alib_arithmetic_divide) - ]; - results = results ++ [ - run_conformance_test("arithmetic", "modulo", ModuloTest.test_alib_arithmetic_modulo) - ]; - - // Comparison conformance tests - results = results ++ [ - run_conformance_test("comparison", "equal", EqualTest.test_alib_comparison_equal) - ]; - results = results ++ [ - run_conformance_test("comparison", "not_equal", NotEqualTest.test_alib_comparison_not_equal) - ]; - results = results ++ [ - run_conformance_test("comparison", "greater_than", GreaterThanTest.test_alib_comparison_greater_than) - ]; - results = results ++ [ - run_conformance_test("comparison", "less_than", LessThanTest.test_alib_comparison_less_than) - ]; - results = results ++ [ - run_conformance_test("comparison", "greater_equal", GreaterEqualTest.test_alib_comparison_greater_equal) - ]; - results = results ++ [ - run_conformance_test("comparison", "less_equal", LessEqualTest.test_alib_comparison_less_equal) - ]; - - // Logical conformance tests - results = results ++ [ - run_conformance_test("logical", "and", AndTest.test_alib_logical_and) - ]; - results = results ++ [ - run_conformance_test("logical", "or", OrTest.test_alib_logical_or) - ]; - results = results ++ [ - run_conformance_test("logical", "not", NotTest.test_alib_logical_not) - ]; - - // String conformance tests - results = results ++ [ - run_conformance_test("string", "concat", ConcatTest.test_alib_string_concat) - ]; - results = results ++ [ - run_conformance_test("string", "length", LengthTest.test_alib_string_length) - ]; - results = results ++ [ - run_conformance_test("string", "substring", SubstringTest.test_alib_string_substring) - ]; - - // Conditional conformance tests - results = results ++ [ - run_conformance_test("conditional", "if_then_else", IfThenElseTest.test_alib_conditional_if_then_else) - ]; - - println(""); - generate_report(results); -} diff --git a/affinescript/tests/conformance/string/concat.affine b/affinescript/tests/conformance/string/concat.affine deleted file mode 100644 index 4a0b3ef..0000000 --- a/affinescript/tests/conformance/string/concat.affine +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: string/concat -// Source: aggregate-library/specs/string/concat.md -// -// Conforms to aLib string/concat spec v1.0 - -fn test_concat_basic() -> TestResult { - let result = "hello" ++ "world"; - assert_eq(result, "helloworld", "Basic string concatenation"); - Pass -} - -fn test_concat_with_space() -> TestResult { - let result = "hello" ++ " world"; - assert_eq(result, "hello world", "Concatenation with space"); - Pass -} - -fn test_concat_empty_left() -> TestResult { - let result = "" ++ "test"; - assert_eq(result, "test", "Concatenation with empty string (left)"); - Pass -} - -fn test_concat_empty_right() -> TestResult { - let result = "test" ++ ""; - assert_eq(result, "test", "Concatenation with empty string (right)"); - Pass -} - -fn test_concat_empty_both() -> TestResult { - let result = "" ++ ""; - assert_eq(result, "", "Concatenation of two empty strings"); - Pass -} - -fn test_alib_string_concat() -> TestResult { - let suite = { - name: "aLib string/concat conformance", - tests: [ - { name: "concat_basic", test: test_concat_basic }, - { name: "concat_with_space", test: test_concat_with_space }, - { name: "concat_empty_left", test: test_concat_empty_left }, - { name: "concat_empty_right", test: test_concat_empty_right }, - { name: "concat_empty_both", test: test_concat_empty_both } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some concat tests failed") } -} diff --git a/affinescript/tests/conformance/string/length.affine b/affinescript/tests/conformance/string/length.affine deleted file mode 100644 index a2523b3..0000000 --- a/affinescript/tests/conformance/string/length.affine +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: string/length -// Source: aggregate-library/specs/string/length.md -// -// Conforms to aLib string/length spec v1.0 - -fn test_length_simple() -> TestResult { - let result = len("hello"); - assert_eq(result, 5, "Length of simple ASCII string"); - Pass -} - -fn test_length_empty() -> TestResult { - let result = len(""); - assert_eq(result, 0, "Length of empty string"); - Pass -} - -fn test_length_single() -> TestResult { - let result = len("a"); - assert_eq(result, 1, "Length of single character"); - Pass -} - -fn test_length_with_space() -> TestResult { - let result = len("hello world"); - assert_eq(result, 11, "Length including space"); - Pass -} - -fn test_length_numeric() -> TestResult { - let result = len("123"); - assert_eq(result, 3, "Length of numeric string"); - Pass -} - -fn test_alib_string_length() -> TestResult { - let suite = { - name: "aLib string/length conformance", - tests: [ - { name: "length_simple", test: test_length_simple }, - { name: "length_empty", test: test_length_empty }, - { name: "length_single", test: test_length_single }, - { name: "length_with_space", test: test_length_with_space }, - { name: "length_numeric", test: test_length_numeric } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some length tests failed") } -} diff --git a/affinescript/tests/conformance/string/substring.affine b/affinescript/tests/conformance/string/substring.affine deleted file mode 100644 index a97c700..0000000 --- a/affinescript/tests/conformance/string/substring.affine +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Conformance tests for aLib spec: string/substring -// Source: aggregate-library/specs/string/substring.md -// -// Conforms to aLib string/substring spec v1.0 -// -// NOTE: These tests require builtin substring implementation -// Currently using placeholder - tests may fail until builtin is available - -fn test_substring_entire() -> TestResult { - let result = substring("hello", 0, 5); - assert_eq(result, "hello", "Extract entire string"); - Pass -} - -fn test_substring_middle() -> TestResult { - let result = substring("hello", 1, 4); - assert_eq(result, "ell", "Extract middle portion"); - Pass -} - -fn test_substring_first() -> TestResult { - let result = substring("hello", 0, 1); - assert_eq(result, "h", "Extract first character"); - Pass -} - -fn test_substring_last() -> TestResult { - let result = substring("hello", 4, 5); - assert_eq(result, "o", "Extract last character"); - Pass -} - -fn test_substring_empty() -> TestResult { - let result = substring("hello", 2, 2); - assert_eq(result, "", "Extract empty substring (start == end)"); - Pass -} - -fn test_alib_string_substring() -> TestResult { - let suite = { - name: "aLib string/substring conformance", - tests: [ - { name: "substring_entire", test: test_substring_entire }, - { name: "substring_middle", test: test_substring_middle }, - { name: "substring_first", test: test_substring_first }, - { name: "substring_last", test: test_substring_last }, - { name: "substring_empty", test: test_substring_empty } - ] - }; - let (passed, failed) = run_suite(suite); - if failed == 0 { Pass } else { Fail("Some substring tests failed") } -} diff --git a/affinescript/tests/effects/basic_effect.affine b/affinescript/tests/effects/basic_effect.affine deleted file mode 100644 index 13f271e..0000000 --- a/affinescript/tests/effects/basic_effect.affine +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test basic effect handler - -effect Ask { - fn get_value() -> Int; -} - -fn main() -> Int { - let x = handle get_value() { - get_value() => { - return 42; - } - }; - return x; -} diff --git a/affinescript/tests/modules/test_const.affine b/affinescript/tests/modules/test_const.affine deleted file mode 100644 index cb658f4..0000000 --- a/affinescript/tests/modules/test_const.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test const - -pub const X: Int = 42; - -fn main() -> Int { - return X; -} diff --git a/affinescript/tests/modules/test_import.affine b/affinescript/tests/modules/test_import.affine deleted file mode 100644 index 26de09d..0000000 --- a/affinescript/tests/modules/test_import.affine +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test module imports - -use Core::{min, max}; -use Math::{pow, gcd}; - -fn main() -> Int { - let a = min(10, 20); - let b = max(10, 20); - let c = pow(2, 3); - let d = gcd(48, 18); - return a + b + c + d; // 10 + 20 + 8 + 6 = 44 -} diff --git a/affinescript/tests/modules/test_import_only.affine b/affinescript/tests/modules/test_import_only.affine deleted file mode 100644 index 92cfad2..0000000 --- a/affinescript/tests/modules/test_import_only.affine +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test that import itself doesn't error - -use Core::{min}; - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tests/modules/test_math_functions.affine b/affinescript/tests/modules/test_math_functions.affine deleted file mode 100644 index 63beb54..0000000 --- a/affinescript/tests/modules/test_math_functions.affine +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test importing and using Math functions - -use Math::{pow, gcd, factorial}; - -fn main() -> Int { - let power = pow(2, 8); // 256 - let divisor = gcd(48, 18); // 6 - let fact = factorial(5); // 120 - return power + divisor + fact; // 256 + 6 + 120 = 382 -} diff --git a/affinescript/tests/modules/test_no_import.affine b/affinescript/tests/modules/test_no_import.affine deleted file mode 100644 index 5701ac6..0000000 --- a/affinescript/tests/modules/test_no_import.affine +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test without imports - just local functions - -fn min(a: Int, b: Int) -> Int { - return if a < b { return a; } else { return b; }; -} - -fn main() -> Int { - let a = min(10, 20); - return a; -} diff --git a/affinescript/tests/modules/test_simple_import.affine b/affinescript/tests/modules/test_simple_import.affine deleted file mode 100644 index 0f46b65..0000000 --- a/affinescript/tests/modules/test_simple_import.affine +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test simple import - -use Core::{min}; - -fn main() -> Int { - return min(10, 20); -} diff --git a/affinescript/tests/parser/baseline_block.affine b/affinescript/tests/parser/baseline_block.affine deleted file mode 100644 index a525205..0000000 --- a/affinescript/tests/parser/baseline_block.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Test baseline block -fn test() -> () { - { } -} diff --git a/affinescript/tests/parser/baseline_stmt.affine b/affinescript/tests/parser/baseline_stmt.affine deleted file mode 100644 index 92aa7f1..0000000 --- a/affinescript/tests/parser/baseline_stmt.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Test baseline statement -fn test() -> () { - let x = 42; -} diff --git a/affinescript/tests/parser/unsafe_all.affine b/affinescript/tests/parser/unsafe_all.affine deleted file mode 100644 index ae4eb63..0000000 --- a/affinescript/tests/parser/unsafe_all.affine +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Comprehensive unsafe operations parser test - -fn test_all_unsafe_ops() -> () { - let ptr = 100; - let value = 42; - - // UnsafeRead - unsafe { - read(ptr); - }; - - // UnsafeWrite - unsafe { - write(ptr, value); - }; - - // UnsafeOffset - unsafe { - offset(ptr, 4); - }; - - // UnsafeForget - unsafe { - forget(value); - }; - - // UnsafeTransmute - unsafe { - transmute(value); - }; - - // UnsafeAssume - unsafe { - assume(ptr > 0); - }; - - // Multiple operations in one block - unsafe { - read(ptr); - write(ptr, 200); - assume(ptr > 0 && ptr < 1000); - }; -} diff --git a/affinescript/tests/parser/unsafe_empty.affine b/affinescript/tests/parser/unsafe_empty.affine deleted file mode 100644 index bac7b30..0000000 --- a/affinescript/tests/parser/unsafe_empty.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Test empty unsafe block -fn test() -> () { - unsafe { } -} diff --git a/affinescript/tests/parser/unsafe_ops.affine b/affinescript/tests/parser/unsafe_ops.affine deleted file mode 100644 index f9209b5..0000000 --- a/affinescript/tests/parser/unsafe_ops.affine +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Parser test for unsafe operations - -fn test_unsafe_operations() -> () { - let ptr = 42; - - // UnsafeRead - unsafe { - read(ptr) - }; - - // UnsafeWrite - unsafe { - write(ptr, 100) - }; - - // UnsafeOffset - unsafe { - offset(ptr, 4) - }; - - // UnsafeForget - unsafe { - forget(ptr) - }; - - // UnsafeTransmute - unsafe { - transmute(ptr) - }; - - // UnsafeAssume - unsafe { - assume(5 > 3) - }; - - // Multiple unsafe ops in one block - unsafe { - read(ptr) - write(ptr, 200) - assume(ptr > 0 && ptr < 1000) - } -} diff --git a/affinescript/tests/parser/unsafe_read.affine b/affinescript/tests/parser/unsafe_read.affine deleted file mode 100644 index 1894c45..0000000 --- a/affinescript/tests/parser/unsafe_read.affine +++ /dev/null @@ -1,7 +0,0 @@ -// Test unsafe read operation -fn test() -> () { - let ptr = 42; - unsafe { - read(ptr); - }; -} diff --git a/affinescript/tests/parser/unsafe_simple.affine b/affinescript/tests/parser/unsafe_simple.affine deleted file mode 100644 index 04520e1..0000000 --- a/affinescript/tests/parser/unsafe_simple.affine +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Test simple unsafe block - -fn test() -> () { - let ptr = 42; - - unsafe { - read(ptr); - } -} diff --git a/affinescript/tests/parser/unsafe_stmt.affine b/affinescript/tests/parser/unsafe_stmt.affine deleted file mode 100644 index b5158a5..0000000 --- a/affinescript/tests/parser/unsafe_stmt.affine +++ /dev/null @@ -1,4 +0,0 @@ -// Test unsafe as statement -fn test() -> () { - unsafe { }; -} diff --git a/affinescript/tests/types/test_dependent_e2e.affine b/affinescript/tests/types/test_dependent_e2e.affine deleted file mode 100644 index ca130b3..0000000 --- a/affinescript/tests/types/test_dependent_e2e.affine +++ /dev/null @@ -1,23 +0,0 @@ -// End-to-end dependent types test - -// Function that requires positive input -fn sqrt_approx(x: Int where (x >= 0)) -> Int { - return x; -} - -// Function that requires value in range -fn percent(x: Int where (x >= 0), max: Int where (max > 0)) -> Int { - return x * 100; -} - -// Multiple refined parameters -fn safe_div(num: Int, denom: Int where (denom != 0)) -> Int { - return num; -} - -fn main() -> Int { - let a = sqrt_approx(25); - let b = percent(5, 10); - let c = safe_div(10, 2); - return a + b + c; -} diff --git a/affinescript/tests/types/test_dependent_parsing.affine b/affinescript/tests/types/test_dependent_parsing.affine deleted file mode 100644 index 335b776..0000000 --- a/affinescript/tests/types/test_dependent_parsing.affine +++ /dev/null @@ -1,25 +0,0 @@ -// Test dependent type parsing - -// Dependent function type: (x: T) -> U -fn dep_func(f: (x: Int) -> Int) -> Int { - return 0; -} - -// Refined type: T where (P) -fn take_positive(x: Int where (x > 0)) -> Int { - return x; -} - -// Dependent arrow with effect -fn dep_with_eff(f: (x: Int) -{IO}-> Int) -> Int { - return 0; -} - -// Refined type with complex predicate -fn bounded(x: Int where (x >= 0), y: Int where (y < 100)) -> Int { - return x + y; -} - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tests/types/test_effect_inference.affine b/affinescript/tests/types/test_effect_inference.affine deleted file mode 100644 index 1b26007..0000000 --- a/affinescript/tests/types/test_effect_inference.affine +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test effect inference and polymorphism - -// Pure function - should infer EPure effect -fn pure_add(x: Int, y: Int) -> Int { - return x + y; -} - -// Another pure function -fn pure_mul(x: Int, y: Int) -> Int { - return x * y; -} - -// Function that calls pure functions - should also be pure -fn compound_pure(x: Int) -> Int { - let a = pure_add(x, 10); - let b = pure_mul(a, 2); - return b; -} - -// Main function that uses multiple pure functions -fn main() -> Int { - let x = pure_add(5, 10); - let y = compound_pure(x); - return y; -} diff --git a/affinescript/tests/types/test_effect_lambda.affine b/affinescript/tests/types/test_effect_lambda.affine deleted file mode 100644 index b7cc3ba..0000000 --- a/affinescript/tests/types/test_effect_lambda.affine +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test effect inference with lambdas - -fn main() -> Int { - // Lambda with pure body - let add_ten = |x: Int| -> Int { - return x + 10; - }; - - // Lambda with multiple operations - let compute = |x: Int| -> Int { - let a = x * 2; - let b = a + 5; - return b; - }; - - let result1 = add_ten(5); - let result2 = compute(10); - - return result1 + result2; -} diff --git a/affinescript/tests/types/test_effect_polymorphism.affine b/affinescript/tests/types/test_effect_polymorphism.affine deleted file mode 100644 index 23eef96..0000000 --- a/affinescript/tests/types/test_effect_polymorphism.affine +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test effect polymorphism with higher-order functions - -// Higher-order function that applies a function twice -// Effect should be polymorphic - works with any effect -fn apply_twice(f: Int -> Int, x: Int) -> Int { - let y = f(x); - return f(y); -} - -// Pure function -fn add_one(x: Int) -> Int { - return x + 1; -} - -// Another pure function -fn double(x: Int) -> Int { - return x * 2; -} - -fn main() -> Int { - // Apply pure functions multiple times - let a = apply_twice(add_one, 10); // 10 + 1 + 1 = 12 - let b = apply_twice(double, 5); // (5 * 2) * 2 = 20 - return a + b; // 12 + 20 = 32 -} diff --git a/affinescript/tests/types/test_generic_programming.affine b/affinescript/tests/types/test_generic_programming.affine deleted file mode 100644 index a0cbd04..0000000 --- a/affinescript/tests/types/test_generic_programming.affine +++ /dev/null @@ -1,39 +0,0 @@ -// Test generic programming with traits and higher-kinded types - -// Functor trait - the foundation of generic programming -trait Functor[F: Type -> Type] { - fn map[A, B](fa: F[A], f: A -> B) -> F[B]; -} - -// Applicative trait - allows applying wrapped functions -trait Applicative[F: Type -> Type] { - fn pure[A](x: A) -> F[A]; - fn ap[A, B](ff: F[A -> B], fa: F[A]) -> F[B]; -} - -// Monad trait - allows sequencing computations -trait Monad[M: Type -> Type] { - fn bind[A, B](ma: M[A], f: A -> M[B]) -> M[B]; - fn pure[A](x: A) -> M[A]; -} - -// Generic function using Functor constraint -fn fmap_twice[F: Type -> Type, A, B, C]( - fa: F[A], - f: A -> B, - g: B -> C -) -> F[C] { - // In a real implementation, we'd look up the Functor impl - // For now, just return a placeholder - return fa; -} - -// Generic function with Monad constraint -fn sequence[M: Type -> Type, A](ma: M[A], mb: M[A]) -> M[A] { - // In a real implementation: bind(ma, |_| mb) - return ma; -} - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tests/types/test_hkt_parsing.affine b/affinescript/tests/types/test_hkt_parsing.affine deleted file mode 100644 index 4477525..0000000 --- a/affinescript/tests/types/test_hkt_parsing.affine +++ /dev/null @@ -1,33 +0,0 @@ -// Test higher-kinded type parsing - -// Function with higher-kinded type parameter -fn map[F: Type -> Type, A, B](fa: F[A], f: A -> B) -> F[B] { - return fa; -} - -// Multiple higher-kinded parameters -fn apply[F: Type -> Type, G: Type -> Type, A](f: F[A], g: G[A]) -> F[A] { - return f; -} - -// Nested arrow kinds -fn compose[F: Type -> Type, G: Type -> Type, A, B, C]( - f: F[A -> B], - g: G[B -> C] -) -> F[A -> C] { - return f; -} - -// Simple polymorphic function -fn id[A](x: A) -> A { - return x; -} - -// Two type parameters -fn const[A, B](x: A, y: B) -> A { - return x; -} - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tests/types/test_kind_checking.affine b/affinescript/tests/types/test_kind_checking.affine deleted file mode 100644 index a1cb65f..0000000 --- a/affinescript/tests/types/test_kind_checking.affine +++ /dev/null @@ -1,25 +0,0 @@ -// Test kind checking integration - -// Function with higher-kinded type parameter -fn map[F: Type -> Type, A, B](fa: F[A], f: A -> B) -> F[B] { - return fa; -} - -// Simple polymorphic type alias -type Box[A] = { - value: A -}; - -// Function using Box -fn box_value[A](x: A) -> Box[A] { - return {value: x}; -} - -// Multiple type parameters -fn swap[A, B](x: A, y: B) -> {fst: B, snd: A} { - return {fst: y, snd: x}; -} - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tests/types/test_lambda_scope_fix.affine b/affinescript/tests/types/test_lambda_scope_fix.affine deleted file mode 100644 index c63114e..0000000 --- a/affinescript/tests/types/test_lambda_scope_fix.affine +++ /dev/null @@ -1,27 +0,0 @@ -// Test lambda parameter scope fix -// This test ensures lambda parameters don't leak into outer scope - -fn main() -> Int { - // First lambda with parameter 'x' - let f = |x: Int| -> Int { - return x + 1; - }; - - // Second lambda with same parameter name 'x' - // This should NOT see the 'x' from the first lambda - let g = |x: Int| -> Int { - return x + 2; - }; - - // Third lambda with parameter 'x' again - let h = |x: Int| -> Int { - return x * 2; - }; - - // Call all lambdas - each should use its own parameter binding - let a = f(5); // 5 + 1 = 6 - let b = g(10); // 10 + 2 = 12 - let c = h(3); // 3 * 2 = 6 - - return a + b + c; // 6 + 12 + 6 = 24 -} diff --git a/affinescript/tests/types/test_lambda_scope_simple.affine b/affinescript/tests/types/test_lambda_scope_simple.affine deleted file mode 100644 index b0142b5..0000000 --- a/affinescript/tests/types/test_lambda_scope_simple.affine +++ /dev/null @@ -1,17 +0,0 @@ -// Simple test for lambda parameter scope -// Just check that two lambdas with same parameter name can be defined - -fn main() -> Int { - // First lambda with parameter 'x' - let f = |x: Int| -> Int { - return x + 1; - }; - - // Second lambda with same parameter name 'x' - // This should work - 'x' from first lambda should not leak - let g = |x: Int| -> Int { - return x + 2; - }; - - return 42; -} diff --git a/affinescript/tests/types/test_parse_row_type.affine b/affinescript/tests/types/test_parse_row_type.affine deleted file mode 100644 index 1c1f51e..0000000 --- a/affinescript/tests/types/test_parse_row_type.affine +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Simple test to check if row type syntax parses - -fn test(r: {x: Int, ..rest}) -> Int { - return 0; -} - -fn main() -> Int { - return 0; -} diff --git a/affinescript/tests/types/test_row_polymorphism.affine b/affinescript/tests/types/test_row_polymorphism.affine deleted file mode 100644 index 5689ada..0000000 --- a/affinescript/tests/types/test_row_polymorphism.affine +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Test row polymorphism with extensible records - -// Function that works on any record with an 'x' field -// The ..rest syntax allows extra fields -fn get_x(r: {x: Int, ..rest}) -> Int { - return r.x; -} - -// Function that adds to the x field -fn add_to_x(r: {x: Int, ..rest}, n: Int) -> Int { - return r.x + n; -} - -fn main() -> Int { - // These should all work due to row polymorphism - let r1 = {x: 10}; - let r2 = {x: 20, y: 30}; - let r3 = {x: 5, y: 10, z: 15}; - - let a = get_x(r1); // 10 - let b = get_x(r2); // 20 - let c = get_x(r3); // 5 - - let d = add_to_x(r2, 5); // 25 - - return a + b + c + d; // 10 + 20 + 5 + 25 = 60 -} diff --git a/affinescript/tests/types/test_row_simple.affine b/affinescript/tests/types/test_row_simple.affine deleted file mode 100644 index 3419251..0000000 --- a/affinescript/tests/types/test_row_simple.affine +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath - -// Simplest possible row polymorphism test - -fn get_x(r: {x: Int, ..rest}) -> Int { - return r.x; -} - -fn main() -> Int { - let r = {x: 42, y: 100}; - return get_x(r); -} diff --git a/affinescript/tests/types/test_traits.affine b/affinescript/tests/types/test_traits.affine deleted file mode 100644 index 4176793..0000000 --- a/affinescript/tests/types/test_traits.affine +++ /dev/null @@ -1,27 +0,0 @@ -// Test trait system for generic programming - -// Simple trait with a method signature -trait Show { - fn show(self: Self) -> String; -} - -// Trait with higher-kinded type parameter -trait Functor[F: Type -> Type] { - fn map[A, B](fa: F[A], f: A -> B) -> F[B]; -} - -// Trait with multiple methods -trait Eq { - fn eq(self: Self, other: Self) -> Bool; - fn ne(self: Self, other: Self) -> Bool; -} - -// Trait with associated type -trait Container { - type Item; - fn get(self: Self, index: Int) -> Item; -} - -fn main() -> Int { - return 42; -} diff --git a/affinescript/tools/affine-doc/Cargo.toml b/affinescript/tools/affine-doc/Cargo.toml deleted file mode 100644 index a0fa940..0000000 --- a/affinescript/tools/affine-doc/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright 2024 AffineScript Contributors - -[package] -name = "affine-doc" -version = "0.1.0" -edition = "2021" -description = "Documentation generator for AffineScript" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/affinescript/affinescript" -keywords = ["documentation", "affinescript"] -categories = ["development-tools", "command-line-utilities"] - -[dependencies] -# CLI -clap = { version = "4", features = ["derive"] } - -# HTML generation -askama = "0.12" -pulldown-cmark = "0.9" - -# Syntax highlighting -syntect = "5" - -# Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -# Path handling -camino = "1" -walkdir = "2" - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Error handling -thiserror = "1" -anyhow = "1" - -# Search index -tantivy = "0.21" - -# HTTP server (for preview) -axum = { version = "0.7", optional = true } -tokio = { version = "1", features = ["full"], optional = true } -tower-http = { version = "0.5", features = ["fs"], optional = true } - -[features] -default = [] -serve = ["dep:axum", "dep:tokio", "dep:tower-http"] - -[[bin]] -name = "affine-doc" -path = "src/main.rs" diff --git a/affinescript/tools/affine-doc/assets/search.js b/affinescript/tools/affine-doc/assets/search.js deleted file mode 100644 index e6c4c45..0000000 --- a/affinescript/tools/affine-doc/assets/search.js +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -// AffineScript Documentation Search - -(function() { - 'use strict'; - - // HTML escape function to prevent XSS. - // Uses character substitution — no DOM element, no innerHTML write. - // (render path uses .textContent assignments throughout, so this helper - // is kept only for any future template-string usage.) - function escapeHtml(text) { - if (typeof text !== 'string') return ''; - return text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - } - - // Safe URL validation - function isValidUrl(url) { - if (typeof url !== 'string') return false; - // Only allow relative URLs or same-origin URLs - try { - if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) { - return true; - } - const parsed = new URL(url, window.location.origin); - return parsed.origin === window.location.origin; - } catch { - return false; - } - } - - // Wait for DOM and search index to load - document.addEventListener('DOMContentLoaded', function() { - const searchInput = document.getElementById('search'); - if (!searchInput) return; - - let searchIndex = []; - let searchResults = null; - - // Load search index - if (window.searchIndex) { - searchIndex = window.searchIndex; - } - - // Create results container - searchResults = document.createElement('div'); - searchResults.id = 'search-results'; - searchResults.className = 'search-results'; - searchInput.parentNode.appendChild(searchResults); - - // Search function - function search(query) { - if (!query || query.length < 2) { - clearResults(); - return; - } - - const queryLower = query.toLowerCase(); - const results = []; - - for (const entry of searchIndex) { - const score = computeScore(entry, queryLower); - if (score > 0) { - results.push({ entry, score }); - } - } - - // Sort by score - results.sort((a, b) => b.score - a.score); - - // Limit results - const topResults = results.slice(0, 20); - - // Render results - renderResults(topResults); - } - - function computeScore(entry, query) { - const nameLower = (entry.name || '').toLowerCase(); - const pathLower = (entry.path || '').toLowerCase(); - - if (nameLower === query) return 100; - if (nameLower.startsWith(query)) return 50; - if (nameLower.includes(query)) return 25; - if (pathLower.includes(query)) return 10; - - return 0; - } - - function clearResults() { - while (searchResults.firstChild) { - searchResults.removeChild(searchResults.firstChild); - } - searchResults.style.display = 'none'; - } - - function renderResults(results) { - clearResults(); - - if (results.length === 0) { - const noResults = document.createElement('div'); - noResults.className = 'no-results'; - noResults.textContent = 'No results found'; - searchResults.appendChild(noResults); - searchResults.style.display = 'block'; - return; - } - - for (const r of results) { - const link = document.createElement('a'); - link.className = 'search-result'; - - // Validate URL before setting href - const url = r.entry.url; - if (isValidUrl(url)) { - link.href = url; - } else { - link.href = '#'; - } - - const kindSpan = document.createElement('span'); - kindSpan.className = 'result-kind'; - kindSpan.textContent = r.entry.kind || ''; - link.appendChild(kindSpan); - - const nameSpan = document.createElement('span'); - nameSpan.className = 'result-name'; - nameSpan.textContent = r.entry.name || ''; - link.appendChild(nameSpan); - - const pathSpan = document.createElement('span'); - pathSpan.className = 'result-path'; - pathSpan.textContent = r.entry.path || ''; - link.appendChild(pathSpan); - - if (r.entry.description) { - const descSpan = document.createElement('span'); - descSpan.className = 'result-desc'; - descSpan.textContent = r.entry.description; - link.appendChild(descSpan); - } - - searchResults.appendChild(link); - } - - searchResults.style.display = 'block'; - } - - // Debounce search input - let debounceTimer; - searchInput.addEventListener('input', function() { - clearTimeout(debounceTimer); - debounceTimer = setTimeout(function() { - search(searchInput.value); - }, 150); - }); - - // Close results on outside click - document.addEventListener('click', function(e) { - if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { - searchResults.style.display = 'none'; - } - }); - - // Keyboard navigation - searchInput.addEventListener('keydown', function(e) { - const results = searchResults.querySelectorAll('.search-result'); - const active = searchResults.querySelector('.search-result.active'); - let index = Array.from(results).indexOf(active); - - if (e.key === 'ArrowDown') { - e.preventDefault(); - if (active) active.classList.remove('active'); - index = (index + 1) % results.length; - if (results[index]) results[index].classList.add('active'); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - if (active) active.classList.remove('active'); - index = (index - 1 + results.length) % results.length; - if (results[index]) results[index].classList.add('active'); - } else if (e.key === 'Enter') { - if (active) { - window.location.href = active.href; - } - } else if (e.key === 'Escape') { - searchResults.style.display = 'none'; - searchInput.blur(); - } - }); - - // Focus search with / - document.addEventListener('keydown', function(e) { - if (e.key === '/' && document.activeElement !== searchInput) { - e.preventDefault(); - searchInput.focus(); - } - }); - }); -})(); diff --git a/affinescript/tools/affine-doc/assets/style.css b/affinescript/tools/affine-doc/assets/style.css deleted file mode 100644 index d1cf1b3..0000000 --- a/affinescript/tools/affine-doc/assets/style.css +++ /dev/null @@ -1,228 +0,0 @@ -/* SPDX-License-Identifier: MPL-2.0 OR MIT */ -/* AffineScript Documentation Styles */ - -:root { - --bg-primary: #ffffff; - --bg-secondary: #f5f5f5; - --text-primary: #1a1a1a; - --text-secondary: #666666; - --accent: #0066cc; - --accent-hover: #0052a3; - --border: #e0e0e0; - --code-bg: #f0f0f0; - --sidebar-width: 280px; -} - -.theme-dark { - --bg-primary: #1a1a1a; - --bg-secondary: #2d2d2d; - --text-primary: #e0e0e0; - --text-secondary: #a0a0a0; - --accent: #4da6ff; - --accent-hover: #80bfff; - --border: #404040; - --code-bg: #2d2d2d; -} - -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; - background-color: var(--bg-primary); - color: var(--text-primary); - line-height: 1.6; -} - -/* Sidebar */ -.sidebar { - position: fixed; - left: 0; - top: 0; - width: var(--sidebar-width); - height: 100vh; - background-color: var(--bg-secondary); - border-right: 1px solid var(--border); - overflow-y: auto; - padding: 1rem; -} - -.search input { - width: 100%; - padding: 0.5rem; - border: 1px solid var(--border); - border-radius: 4px; - background-color: var(--bg-primary); - color: var(--text-primary); -} - -/* Main content */ -main { - margin-left: var(--sidebar-width); - padding: 2rem; - max-width: 900px; -} - -/* Typography */ -h1, h2, h3, h4, h5, h6 { - margin-top: 1.5rem; - margin-bottom: 0.75rem; - font-weight: 600; -} - -h1 { font-size: 2rem; } -h2 { font-size: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; } -h3 { font-size: 1.25rem; } -h4 { font-size: 1.1rem; } - -a { - color: var(--accent); - text-decoration: none; -} - -a:hover { - color: var(--accent-hover); - text-decoration: underline; -} - -/* Code */ -code { - font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace; - font-size: 0.9em; - background-color: var(--code-bg); - padding: 0.2em 0.4em; - border-radius: 3px; -} - -pre { - background-color: var(--code-bg); - padding: 1rem; - border-radius: 6px; - overflow-x: auto; - margin: 1rem 0; -} - -pre code { - background: none; - padding: 0; -} - -.signature { - background-color: var(--code-bg); - padding: 0.75rem 1rem; - border-radius: 6px; - border-left: 3px solid var(--accent); - margin: 0.5rem 0 1rem 0; -} - -/* Items */ -.item { - margin: 2rem 0; - padding: 1rem; - border: 1px solid var(--border); - border-radius: 6px; -} - -.item-header { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.5rem; -} - -.kind { - font-size: 0.75rem; - text-transform: uppercase; - color: var(--text-secondary); - background-color: var(--bg-secondary); - padding: 0.2em 0.5em; - border-radius: 3px; -} - -.name { - font-size: 1.25rem; - font-weight: 600; -} - -.item-doc { - margin-top: 1rem; -} - -/* Quantities */ -.quantity-erased { color: #888; } -.quantity-linear { color: #d32f2f; font-weight: bold; } -.quantity-unrestricted { color: #388e3c; } - -/* Effects */ -.effects { - color: #7b1fa2; -} - -.effect { - color: #7b1fa2; -} - -/* Badges */ -.badge { - display: inline-block; - font-size: 0.75rem; - padding: 0.2em 0.5em; - border-radius: 3px; - margin-left: 0.5rem; -} - -.stability-stable { background-color: #c8e6c9; color: #2e7d32; } -.stability-unstable { background-color: #fff9c4; color: #f9a825; } -.stability-experimental { background-color: #ffccbc; color: #e64a19; } -.stability-deprecated { background-color: #ffcdd2; color: #c62828; } - -/* Deprecated */ -.deprecated { - background-color: #fff3e0; - border: 1px solid #ff9800; - border-radius: 4px; - padding: 0.75rem; - margin: 0.5rem 0; -} - -/* Module list */ -.module-list { - list-style: none; -} - -.module-list li { - padding: 0.5rem 0; - border-bottom: 1px solid var(--border); -} - -/* Examples */ -.example { - margin: 1rem 0; - padding: 1rem; - background-color: var(--bg-secondary); - border-radius: 6px; -} - -.example h4 { - margin-top: 0; - font-size: 0.9rem; - color: var(--text-secondary); -} - -/* Responsive */ -@media (max-width: 768px) { - .sidebar { - position: static; - width: 100%; - height: auto; - border-right: none; - border-bottom: 1px solid var(--border); - } - - main { - margin-left: 0; - } -} diff --git a/affinescript/tools/affine-doc/src/extract.rs b/affinescript/tools/affine-doc/src/extract.rs deleted file mode 100644 index 9d5fb1e..0000000 --- a/affinescript/tools/affine-doc/src/extract.rs +++ /dev/null @@ -1,293 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Documentation Extraction -//! -//! Extracts documentation from AffineScript source code. - -use std::path::Path; - -/// Extracted documentation for a module -#[derive(Debug, Clone)] -pub struct ModuleDoc { - /// Module path (e.g., "std::collections::vec") - pub path: Vec, - - /// Module-level documentation - pub doc: Option, - - /// Items in this module - pub items: Vec, - - /// Submodules - pub submodules: Vec, -} - -/// Documentation for an item -#[derive(Debug, Clone)] -pub struct ItemDoc { - /// Item name - pub name: String, - - /// Item kind - pub kind: ItemKind, - - /// Documentation comment - pub doc: Option, - - /// Type signature - pub signature: String, - - /// Source location - pub location: SourceLocation, - - /// Visibility - pub visibility: Visibility, - - /// Type parameters - pub type_params: Vec, - - /// Effects (for functions) - pub effects: Vec, - - /// Examples from doc comments - pub examples: Vec, -} - -/// Item kind -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ItemKind { - /// Function - Function, - /// Type alias - TypeAlias, - /// Struct - Struct, - /// Enum - Enum, - /// Trait - Trait, - /// Effect - Effect, - /// Constant - Const, - /// Static - Static, - /// Impl block - Impl, - /// Module - Module, -} - -impl ItemKind { - /// Display name for the item kind - pub fn display_name(&self) -> &'static str { - match self { - ItemKind::Function => "Function", - ItemKind::TypeAlias => "Type Alias", - ItemKind::Struct => "Struct", - ItemKind::Enum => "Enum", - ItemKind::Trait => "Trait", - ItemKind::Effect => "Effect", - ItemKind::Const => "Constant", - ItemKind::Static => "Static", - ItemKind::Impl => "Implementation", - ItemKind::Module => "Module", - } - } -} - -/// Visibility level -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Visibility { - /// Public - Public, - /// Public within crate - Crate, - /// Private - Private, -} - -/// Source location -#[derive(Debug, Clone)] -pub struct SourceLocation { - /// File path - pub file: String, - /// Line number - pub line: u32, - /// Column number - pub column: u32, -} - -/// Type parameter documentation -#[derive(Debug, Clone)] -pub struct TypeParamDoc { - /// Parameter name - pub name: String, - /// Bounds - pub bounds: Vec, - /// Default value - pub default: Option, -} - -/// Documentation extractor -pub struct Extractor { - /// Include private items - include_private: bool, -} - -impl Extractor { - /// Create a new extractor - pub fn new(include_private: bool) -> Self { - Extractor { include_private } - } - - /// Extract documentation from a source file - pub fn extract_file(&self, path: impl AsRef) -> anyhow::Result { - let _path = path.as_ref(); - - // TODO(#84): Phase 8 implementation - // - [ ] Parse source file - // - [ ] Walk AST - // - [ ] Extract doc comments - // - [ ] Build ModuleDoc - - Ok(ModuleDoc { - path: vec![], - doc: None, - items: vec![], - submodules: vec![], - }) - } - - /// Extract documentation from a directory - pub fn extract_dir(&self, path: impl AsRef) -> anyhow::Result> { - let _path = path.as_ref(); - - // TODO(#84): Phase 8 implementation - // - [ ] Find all .afs files - // - [ ] Extract each file - // - [ ] Build module hierarchy - - Ok(vec![]) - } - - /// Parse doc comment into structured sections - pub fn parse_doc_comment(&self, comment: &str) -> DocComment { - let mut description = String::new(); - let mut params = vec![]; - let mut returns = None; - let mut examples = vec![]; - let mut panics = None; - let mut safety = None; - - let mut current_section = "description"; - let mut current_content = String::new(); - - for line in comment.lines() { - let line = line.trim(); - - // Check for section headers - if line.starts_with("# ") { - // Save previous section - self.save_section( - current_section, - ¤t_content, - &mut description, - &mut params, - &mut returns, - &mut examples, - &mut panics, - &mut safety, - ); - - // Start new section - current_section = match line.to_lowercase().as_str() { - "# parameters" | "# arguments" => "params", - "# returns" => "returns", - "# examples" | "# example" => "examples", - "# panics" => "panics", - "# safety" => "safety", - _ => "description", - }; - current_content.clear(); - } else { - current_content.push_str(line); - current_content.push('\n'); - } - } - - // Save last section - self.save_section( - current_section, - ¤t_content, - &mut description, - &mut params, - &mut returns, - &mut examples, - &mut panics, - &mut safety, - ); - - DocComment { - description, - params, - returns, - examples, - panics, - safety, - } - } - - fn save_section( - &self, - section: &str, - content: &str, - description: &mut String, - _params: &mut Vec<(String, String)>, - returns: &mut Option, - examples: &mut Vec, - panics: &mut Option, - safety: &mut Option, - ) { - let content = content.trim(); - if content.is_empty() { - return; - } - - match section { - "description" => *description = content.to_string(), - "params" => { - // TODO(#84): Parse parameter docs - } - "returns" => *returns = Some(content.to_string()), - "examples" => examples.push(content.to_string()), - "panics" => *panics = Some(content.to_string()), - "safety" => *safety = Some(content.to_string()), - _ => {} - } - } -} - -/// Parsed doc comment -#[derive(Debug, Clone)] -pub struct DocComment { - /// Main description - pub description: String, - /// Parameter documentation - pub params: Vec<(String, String)>, - /// Return value documentation - pub returns: Option, - /// Example code blocks - pub examples: Vec, - /// Panic conditions - pub panics: Option, - /// Safety requirements (for unsafe functions) - pub safety: Option, -} - -// TODO(#84): Phase 8 implementation -// - [ ] Connect to AffineScript parser -// - [ ] Handle attribute macros (#[doc], #[deprecated], etc.) -// - [ ] Extract impl blocks -// - [ ] Handle re-exports -// - [ ] Support module-level docs (//! comments) diff --git a/affinescript/tools/affine-doc/src/html.rs b/affinescript/tools/affine-doc/src/html.rs deleted file mode 100644 index 98aabf6..0000000 --- a/affinescript/tools/affine-doc/src/html.rs +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! HTML Generation -//! -//! Generates HTML documentation pages. - -use crate::extract::{ItemDoc, ItemKind, ModuleDoc}; -use crate::render; -use std::path::Path; - -/// HTML generator configuration -#[derive(Debug, Clone)] -pub struct HtmlConfig { - /// Output directory - pub output_dir: std::path::PathBuf, - - /// Package name - pub package_name: String, - - /// Package version - pub package_version: String, - - /// Custom CSS - pub custom_css: Option, - - /// Theme (light, dark, auto) - pub theme: String, -} - -/// HTML generator -pub struct HtmlGenerator { - config: HtmlConfig, -} - -impl HtmlGenerator { - /// Create a new HTML generator - pub fn new(config: HtmlConfig) -> Self { - HtmlGenerator { config } - } - - /// Generate documentation for all modules - pub fn generate(&self, modules: &[ModuleDoc]) -> anyhow::Result<()> { - // Create output directory - std::fs::create_dir_all(&self.config.output_dir)?; - - // Generate static assets - self.generate_assets()?; - - // Generate index page - self.generate_index(modules)?; - - // Generate module pages - for module in modules { - self.generate_module(module)?; - } - - // Generate search index - self.generate_search_index(modules)?; - - Ok(()) - } - - /// Generate static assets (CSS, JS) - fn generate_assets(&self) -> anyhow::Result<()> { - let css = include_str!("../assets/style.css"); - let js = include_str!("../assets/search.js"); - - std::fs::write(self.config.output_dir.join("style.css"), css)?; - std::fs::write(self.config.output_dir.join("search.js"), js)?; - - Ok(()) - } - - /// Generate index page - fn generate_index(&self, modules: &[ModuleDoc]) -> anyhow::Result<()> { - let mut html = self.page_header("Index"); - - html.push_str(""); - html.push_str(&self.page_footer()); - - std::fs::write(self.config.output_dir.join("index.html"), html)?; - Ok(()) - } - - /// Generate module documentation - fn generate_module(&self, module: &ModuleDoc) -> anyhow::Result<()> { - let module_path = module.path.join("::"); - let mut html = self.page_header(&module_path); - - html.push_str("
"); - html.push_str(&format!("

Module {}

", module_path)); - - if let Some(doc) = &module.doc { - html.push_str("
"); - html.push_str(&render::render_markdown(doc)); - html.push_str("
"); - } - - // Group items by kind - let mut functions = vec![]; - let mut types = vec![]; - let mut traits = vec![]; - let mut effects = vec![]; - - for item in &module.items { - match item.kind { - ItemKind::Function => functions.push(item), - ItemKind::Struct | ItemKind::Enum | ItemKind::TypeAlias => types.push(item), - ItemKind::Trait => traits.push(item), - ItemKind::Effect => effects.push(item), - _ => {} - } - } - - // Render sections - if !types.is_empty() { - html.push_str(&self.render_section("Types", &types)); - } - if !traits.is_empty() { - html.push_str(&self.render_section("Traits", &traits)); - } - if !effects.is_empty() { - html.push_str(&self.render_section("Effects", &effects)); - } - if !functions.is_empty() { - html.push_str(&self.render_section("Functions", &functions)); - } - - html.push_str("
"); - html.push_str(&self.page_footer()); - - // Write file - let file_path = self - .config - .output_dir - .join(format!("{}.html", module_path.replace("::", "/"))); - - if let Some(parent) = file_path.parent() { - std::fs::create_dir_all(parent)?; - } - std::fs::write(file_path, html)?; - - Ok(()) - } - - /// Render a section of items - fn render_section(&self, title: &str, items: &[&ItemDoc]) -> String { - let mut html = format!("
"); - html.push_str(&format!("

{}

", title)); - - for item in items { - html.push_str(&self.render_item(item)); - } - - html.push_str("
"); - html - } - - /// Render a single item - fn render_item(&self, item: &ItemDoc) -> String { - let mut html = format!( - "
", - item.kind.display_name().to_lowercase(), - item.name - ); - - // Header - html.push_str("
"); - html.push_str(&format!( - "{}", - item.kind.display_name() - )); - html.push_str(&format!("{}", item.name)); - html.push_str("
"); - - // Signature - html.push_str("
");
-        html.push_str(&render::html_escape(&item.signature));
-        html.push_str("
"); - - // Documentation - if let Some(doc) = &item.doc { - html.push_str("
"); - html.push_str(&render::render_markdown(doc)); - html.push_str("
"); - } - - // Examples - for example in &item.examples { - html.push_str("
"); - html.push_str("

Example

"); - html.push_str(&render::render_code(example, Some("affinescript"))); - html.push_str("
"); - } - - html.push_str("
"); - html - } - - /// Generate page header - fn page_header(&self, title: &str) -> String { - format!( - r#" - - - - - {} - {} Documentation - - - - -"#, - title, self.config.package_name, self.config.theme - ) - } - - /// Generate page footer - fn page_footer(&self) -> String { - r#" - - - -"# - .to_string() - } - - /// Generate search index - fn generate_search_index(&self, _modules: &[ModuleDoc]) -> anyhow::Result<()> { - // TODO(#84): Phase 8 implementation - // - [ ] Extract searchable content - // - [ ] Build JSON index - // - [ ] Or build Tantivy index - - Ok(()) - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add navigation sidebar -// - [ ] Add breadcrumbs -// - [ ] Add source links -// - [ ] Add copy permalink -// - [ ] Add implementor lists for traits -// - [ ] Add method lists for types -// - [ ] Support custom templates diff --git a/affinescript/tools/affine-doc/src/index.rs b/affinescript/tools/affine-doc/src/index.rs deleted file mode 100644 index ad43eb1..0000000 --- a/affinescript/tools/affine-doc/src/index.rs +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Search Index -//! -//! Builds and queries the documentation search index. - -use crate::extract::{ItemDoc, ItemKind, ModuleDoc}; -use serde::{Deserialize, Serialize}; -use std::path::Path; - -/// Search index entry -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SearchEntry { - /// Entry name - pub name: String, - - /// Full path (e.g., "std::vec::Vec::push") - pub path: String, - - /// Item kind - pub kind: String, - - /// Brief description (first line of doc) - pub description: String, - - /// URL to documentation page - pub url: String, -} - -/// Search index builder -pub struct IndexBuilder { - entries: Vec, -} - -impl IndexBuilder { - /// Create a new index builder - pub fn new() -> Self { - IndexBuilder { - entries: Vec::new(), - } - } - - /// Add a module to the index - pub fn add_module(&mut self, module: &ModuleDoc) { - let module_path = module.path.join("::"); - - // Add module itself - self.entries.push(SearchEntry { - name: module.path.last().cloned().unwrap_or_default(), - path: module_path.clone(), - kind: "Module".to_string(), - description: first_line(module.doc.as_deref().unwrap_or("")), - url: format!("{}.html", module_path.replace("::", "/")), - }); - - // Add items - for item in &module.items { - self.add_item(&module_path, item); - } - } - - /// Add an item to the index - fn add_item(&mut self, module_path: &str, item: &ItemDoc) { - let full_path = if module_path.is_empty() { - item.name.clone() - } else { - format!("{}::{}", module_path, item.name) - }; - - self.entries.push(SearchEntry { - name: item.name.clone(), - path: full_path, - kind: item.kind.display_name().to_string(), - description: first_line(item.doc.as_deref().unwrap_or("")), - url: format!( - "{}.html#{}", - module_path.replace("::", "/"), - item.name - ), - }); - } - - /// Build the JSON search index - pub fn build_json(&self) -> String { - serde_json::to_string(&self.entries).unwrap_or_else(|_| "[]".to_string()) - } - - /// Write the search index to disk - pub fn write(&self, output_dir: impl AsRef) -> anyhow::Result<()> { - let json = self.build_json(); - std::fs::write(output_dir.as_ref().join("search-index.js"), format!( - "window.searchIndex = {};", - json - ))?; - Ok(()) - } -} - -impl Default for IndexBuilder { - fn default() -> Self { - Self::new() - } -} - -/// Search query -pub struct SearchQuery { - /// Query text - pub query: String, - - /// Filter by kind - pub kind_filter: Option, - - /// Maximum results - pub limit: usize, -} - -impl Default for SearchQuery { - fn default() -> Self { - SearchQuery { - query: String::new(), - kind_filter: None, - limit: 50, - } - } -} - -/// Search result -#[derive(Debug, Clone)] -pub struct SearchResult { - /// Matching entry - pub entry: SearchEntry, - - /// Relevance score - pub score: f32, -} - -/// Simple in-memory search (for client-side) -pub fn search(entries: &[SearchEntry], query: &SearchQuery) -> Vec { - let query_lower = query.query.to_lowercase(); - - let mut results: Vec = entries - .iter() - .filter_map(|entry| { - // Apply kind filter - if let Some(kind) = &query.kind_filter { - if entry.kind != kind.display_name() { - return None; - } - } - - // Score matching - let score = compute_score(&entry.name, &entry.path, &query_lower); - if score > 0.0 { - Some(SearchResult { - entry: entry.clone(), - score, - }) - } else { - None - } - }) - .collect(); - - // Sort by score (descending) - results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap()); - - // Apply limit - results.truncate(query.limit); - - results -} - -/// Compute search relevance score -fn compute_score(name: &str, path: &str, query: &str) -> f32 { - let name_lower = name.to_lowercase(); - let path_lower = path.to_lowercase(); - - let mut score = 0.0; - - // Exact name match - if name_lower == query { - score += 100.0; - } - // Name starts with query - else if name_lower.starts_with(query) { - score += 50.0; - } - // Name contains query - else if name_lower.contains(query) { - score += 25.0; - } - // Path contains query - else if path_lower.contains(query) { - score += 10.0; - } - - // Bonus for shorter names (more specific) - if score > 0.0 { - score += 10.0 / (name.len() as f32); - } - - score -} - -/// Get first line of text -fn first_line(text: &str) -> String { - text.lines() - .next() - .unwrap_or("") - .trim() - .to_string() -} - -// TODO(#84): Phase 8 implementation -// - [ ] Use Tantivy for full-text search -// - [ ] Add fuzzy matching -// - [ ] Add type signature search -// - [ ] Add effect search -// - [ ] Cache search index diff --git a/affinescript/tools/affine-doc/src/main.rs b/affinescript/tools/affine-doc/src/main.rs deleted file mode 100644 index 71bcbd9..0000000 --- a/affinescript/tools/affine-doc/src/main.rs +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! AffineScript Documentation Generator -//! -//! Generates API documentation from AffineScript source code. -//! -//! # Features -//! -//! - Extracts doc comments from source -//! - Generates HTML documentation -//! - Creates search index -//! - Supports cross-linking -//! - Renders Markdown in comments - -use clap::{Parser, Subcommand}; -use std::path::PathBuf; - -mod extract; -mod html; -mod index; -mod render; - -#[derive(Parser)] -#[command(name = "affine-doc")] -#[command(about = "AffineScript documentation generator", long_about = None)] -#[command(version)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Generate documentation - Build { - /// Source directory - #[arg(default_value = ".")] - source: PathBuf, - - /// Output directory - #[arg(short, long, default_value = "target/doc")] - output: PathBuf, - - /// Open in browser after building - #[arg(long)] - open: bool, - - /// Include private items - #[arg(long)] - document_private: bool, - - /// Include dependencies - #[arg(long)] - include_deps: bool, - }, - - /// Start documentation server - #[cfg(feature = "serve")] - Serve { - /// Documentation directory - #[arg(default_value = "target/doc")] - dir: PathBuf, - - /// Port to listen on - #[arg(short, long, default_value = "8080")] - port: u16, - }, - - /// Generate search index only - Index { - /// Documentation directory - #[arg(default_value = "target/doc")] - dir: PathBuf, - }, -} - -fn main() -> anyhow::Result<()> { - // Initialize logging - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env() - .add_directive(tracing::Level::INFO.into()), - ) - .init(); - - let cli = Cli::parse(); - - match cli.command { - Commands::Build { - source, - output, - open, - document_private, - include_deps, - } => { - // TODO(#84): Phase 8 implementation - // - [ ] Parse source files - // - [ ] Extract documentation - // - [ ] Generate HTML - // - [ ] Build search index - // - [ ] Open browser if requested - - println!("Building documentation from {:?} to {:?}", source, output); - let _ = (document_private, include_deps, open); - } - - #[cfg(feature = "serve")] - Commands::Serve { dir, port } => { - // TODO(#84): Phase 8 implementation - // - [ ] Start HTTP server - // - [ ] Serve static files - // - [ ] Handle search API - - println!("Serving documentation from {:?} on port {}", dir, port); - } - - Commands::Index { dir } => { - // TODO(#84): Phase 8 implementation - // - [ ] Scan HTML files - // - [ ] Extract content - // - [ ] Build Tantivy index - - println!("Building search index for {:?}", dir); - } - } - - Ok(()) -} - -// TODO(#84): Phase 8 implementation -// - [ ] Parse AffineScript source and extract types/functions -// - [ ] Process doc comments (Markdown) -// - [ ] Generate HTML with templates -// - [ ] Create type/effect cross-references -// - [ ] Build searchable index -// - [ ] Support theme customization -// - [ ] Add source view with syntax highlighting diff --git a/affinescript/tools/affine-doc/src/render.rs b/affinescript/tools/affine-doc/src/render.rs deleted file mode 100644 index ed6a01e..0000000 --- a/affinescript/tools/affine-doc/src/render.rs +++ /dev/null @@ -1,121 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Markdown Rendering -//! -//! Renders Markdown doc comments to HTML. - -use pulldown_cmark::{html, Options, Parser}; - -/// Render Markdown to HTML -pub fn render_markdown(markdown: &str) -> String { - let options = Options::all(); - let parser = Parser::new_ext(markdown, options); - - let mut html_output = String::new(); - html::push_html(&mut html_output, parser); - - html_output -} - -/// Render code with syntax highlighting -pub fn render_code(code: &str, language: Option<&str>) -> String { - // TODO(#84): Phase 8 implementation - // - [ ] Use syntect for highlighting - // - [ ] Support AffineScript syntax - // - [ ] Add line numbers option - - let lang = language.unwrap_or("affinescript"); - format!( - r#"
{}
"#, - lang, - html_escape(code) - ) -} - -/// Escape HTML entities -pub fn html_escape(s: &str) -> String { - s.replace('&', "&") - .replace('<', "<") - .replace('>', ">") - .replace('"', """) - .replace('\'', "'") -} - -/// Render a type signature with links -pub fn render_signature(signature: &str, _link_resolver: &dyn Fn(&str) -> Option) -> String { - // TODO(#84): Phase 8 implementation - // - [ ] Parse signature - // - [ ] Identify type names - // - [ ] Create links to type documentation - // - [ ] Apply syntax highlighting - - format!( - r#"{}"#, - html_escape(signature) - ) -} - -/// Render effect row -pub fn render_effects(effects: &[String]) -> String { - if effects.is_empty() { - return String::new(); - } - - let effect_links: Vec = effects - .iter() - .map(|e| format!(r#"{}"#, e, e)) - .collect(); - - format!(r#"/ {}"#, effect_links.join(" | ")) -} - -/// Render quantity annotation -pub fn render_quantity(quantity: &str) -> String { - let (class, display) = match quantity { - "0" => ("quantity-erased", "0"), - "1" => ("quantity-linear", "1"), - "ω" | "w" | "omega" => ("quantity-unrestricted", "ω"), - _ => ("quantity-unknown", quantity), - }; - - format!(r#"{}"#, class, display) -} - -/// Render deprecation notice -pub fn render_deprecated(message: Option<&str>) -> String { - match message { - Some(msg) => format!( - r#"
Deprecated: {}
"#, - html_escape(msg) - ), - None => r#"
Deprecated
"#.to_string(), - } -} - -/// Render stability badge -pub fn render_stability(stability: Stability) -> String { - let (class, label) = match stability { - Stability::Stable => ("stability-stable", "Stable"), - Stability::Unstable => ("stability-unstable", "Unstable"), - Stability::Experimental => ("stability-experimental", "Experimental"), - Stability::Deprecated => ("stability-deprecated", "Deprecated"), - }; - - format!(r#"{}"#, class, label) -} - -/// Stability level -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Stability { - Stable, - Unstable, - Experimental, - Deprecated, -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add syntax highlighting for AffineScript -// - [ ] Implement cross-reference resolution -// - [ ] Add heading anchor links -// - [ ] Support custom markdown extensions -// - [ ] Add copy button for code blocks diff --git a/affinescript/tools/affine-pkg/Cargo.toml b/affinescript/tools/affine-pkg/Cargo.toml deleted file mode 100644 index 0102017..0000000 --- a/affinescript/tools/affine-pkg/Cargo.toml +++ /dev/null @@ -1,65 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright 2024 AffineScript Contributors - -[package] -name = "affine-pkg" -version = "0.1.0" -edition = "2021" -description = "Package manager for AffineScript" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/affinescript/affinescript" -keywords = ["package-manager", "affinescript", "build"] -categories = ["development-tools", "command-line-utilities"] - -[dependencies] -# CLI -clap = { version = "4", features = ["derive", "env"] } - -# Async runtime -tokio = { version = "1", features = ["full"] } - -# HTTP client for registry -reqwest = { version = "0.11", features = ["json", "rustls-tls"], default-features = false } - -# Serialization -serde = { version = "1", features = ["derive"] } -toml = "0.8" -serde_json = "1" - -# Hashing (content-addressed storage) -sha2 = "0.10" -hex = "0.4" - -# Archive handling -tar = "0.4" -flate2 = "1" -zip = "0.6" - -# Path handling -camino = "1" -dirs = "5" - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Error handling -thiserror = "1" -anyhow = "1" - -# Semver -semver = { version = "1", features = ["serde"] } - -# Lockfile -toml_edit = "0.21" - -# Progress bars -indicatif = "0.17" - -[dev-dependencies] -tempfile = "3" -tokio-test = "0.4" - -[[bin]] -name = "affine" -path = "src/main.rs" diff --git a/affinescript/tools/affine-pkg/src/build.rs b/affinescript/tools/affine-pkg/src/build.rs deleted file mode 100644 index e7684a9..0000000 --- a/affinescript/tools/affine-pkg/src/build.rs +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Build System -//! -//! Orchestrates compilation of AffineScript packages. - -use std::path::{Path, PathBuf}; -use std::process::Command; - -/// Build configuration -#[derive(Debug, Clone)] -pub struct BuildConfig { - /// Release or debug mode - pub release: bool, - - /// Target directory - pub target_dir: PathBuf, - - /// Number of parallel jobs - pub jobs: Option, - - /// Extra compiler flags - pub flags: Vec, - - /// Features to enable - pub features: Vec, - - /// Disable default features - pub no_default_features: bool, -} - -impl Default for BuildConfig { - fn default() -> Self { - BuildConfig { - release: false, - target_dir: PathBuf::from("target"), - jobs: None, - flags: Vec::new(), - features: Vec::new(), - no_default_features: false, - } - } -} - -/// Build result -#[derive(Debug)] -pub struct BuildResult { - /// Output artifacts - pub artifacts: Vec, - - /// Compilation time in milliseconds - pub duration_ms: u64, - - /// Compiler warnings - pub warnings: Vec, -} - -/// Build artifact -#[derive(Debug)] -pub struct Artifact { - /// Artifact kind - pub kind: ArtifactKind, - - /// Output path - pub path: PathBuf, - - /// Package name - pub package: String, - - /// Size in bytes - pub size: u64, -} - -/// Artifact kind -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ArtifactKind { - /// WebAssembly binary - Wasm, - /// WebAssembly + JavaScript wrapper - WasmJs, - /// Library - Library, - /// Documentation - Doc, -} - -/// Build orchestrator -pub struct Builder { - config: BuildConfig, -} - -impl Builder { - /// Create a new builder - pub fn new(config: BuildConfig) -> Self { - Builder { config } - } - - /// Build a package - pub fn build(&self, package_dir: impl AsRef) -> anyhow::Result { - let _package_dir = package_dir.as_ref(); - let start = std::time::Instant::now(); - - // TODO(#84): Phase 8 implementation - // - [ ] Load manifest - // - [ ] Resolve dependencies - // - [ ] Build dependencies first - // - [ ] Compile source files - // - [ ] Link into output - - let duration_ms = start.elapsed().as_millis() as u64; - - Ok(BuildResult { - artifacts: vec![], - duration_ms, - warnings: vec![], - }) - } - - /// Check (type check without code generation) - pub fn check(&self, package_dir: impl AsRef) -> anyhow::Result> { - let _package_dir = package_dir.as_ref(); - - // TODO(#84): Phase 8 implementation - // - [ ] Load manifest - // - [ ] Parse all source files - // - [ ] Type check - // - [ ] Borrow check - // - [ ] Effect check - // - [ ] Return diagnostics - - Ok(vec![]) - } - - /// Run tests - pub fn test( - &self, - package_dir: impl AsRef, - filter: Option<&str>, - ) -> anyhow::Result { - let _package_dir = package_dir.as_ref(); - let _filter = filter; - - // TODO(#84): Phase 8 implementation - // - [ ] Find test functions (annotated with #[test]) - // - [ ] Build test binary - // - [ ] Run tests - // - [ ] Collect results - - Ok(TestResult { - passed: 0, - failed: 0, - skipped: 0, - duration_ms: 0, - failures: vec![], - }) - } - - /// Generate documentation - pub fn doc(&self, package_dir: impl AsRef) -> anyhow::Result { - let package_dir = package_dir.as_ref(); - let output_dir = self.config.target_dir.join("doc"); - - // TODO(#84): Phase 8 implementation - // - [ ] Parse source files - // - [ ] Extract doc comments - // - [ ] Generate HTML - - let _ = package_dir; - Ok(output_dir) - } - - /// Clean build artifacts - pub fn clean(&self, package_dir: impl AsRef) -> anyhow::Result<()> { - let target_dir = package_dir.as_ref().join(&self.config.target_dir); - if target_dir.exists() { - std::fs::remove_dir_all(target_dir)?; - } - Ok(()) - } - - /// Run the AffineScript compiler - fn run_compiler(&self, args: &[&str]) -> anyhow::Result { - let output = Command::new("affinescript") - .args(args) - .output()?; - - if !output.status.success() { - let stderr = String::from_utf8_lossy(&output.stderr); - anyhow::bail!("Compiler failed: {}", stderr); - } - - Ok(output) - } -} - -/// Test result -#[derive(Debug)] -pub struct TestResult { - /// Number of passing tests - pub passed: usize, - - /// Number of failing tests - pub failed: usize, - - /// Number of skipped tests - pub skipped: usize, - - /// Total duration in milliseconds - pub duration_ms: u64, - - /// Details of failures - pub failures: Vec, -} - -/// Test failure details -#[derive(Debug)] -pub struct TestFailure { - /// Test name - pub name: String, - - /// Failure message - pub message: String, - - /// Source location - pub location: Option, -} - -// TODO(#84): Phase 8 implementation -// - [ ] Implement incremental compilation -// - [ ] Add dependency tracking -// - [ ] Add parallel compilation -// - [ ] Add build caching -// - [ ] Add build scripts support -// - [ ] Add custom compiler invocation diff --git a/affinescript/tools/affine-pkg/src/config.rs b/affinescript/tools/affine-pkg/src/config.rs deleted file mode 100644 index cc26201..0000000 --- a/affinescript/tools/affine-pkg/src/config.rs +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Configuration -//! -//! User and project configuration for the package manager. - -use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; - -/// Global configuration file location -pub fn global_config_path() -> Option { - dirs::config_dir().map(|d| d.join("affine").join("config.toml")) -} - -/// Credentials file location -pub fn credentials_path() -> Option { - dirs::config_dir().map(|d| d.join("affine").join("credentials.toml")) -} - -/// Global configuration -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Config { - /// Registry configuration - #[serde(default)] - pub registries: Vec, - - /// Network configuration - #[serde(default)] - pub net: NetworkConfig, - - /// Build configuration - #[serde(default)] - pub build: BuildConfig, - - /// Environment variables - #[serde(default)] - pub env: std::collections::HashMap, -} - -/// Registry configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RegistryConfig { - /// Registry name - pub name: String, - - /// Registry URL - pub url: String, - - /// Whether this is the default registry - #[serde(default)] - pub default: bool, -} - -/// Network configuration -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct NetworkConfig { - /// HTTP proxy - pub proxy: Option, - - /// HTTPS proxy - pub https_proxy: Option, - - /// No proxy hosts - #[serde(default)] - pub no_proxy: Vec, - - /// Connection timeout in seconds - #[serde(default = "default_timeout")] - pub timeout: u64, - - /// Number of retries - #[serde(default = "default_retries")] - pub retries: u32, - - /// Whether to verify SSL certificates - #[serde(default = "default_true")] - pub verify_ssl: bool, -} - -fn default_timeout() -> u64 { - 30 -} - -fn default_retries() -> u32 { - 3 -} - -fn default_true() -> bool { - true -} - -/// Build configuration -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct BuildConfig { - /// Number of parallel jobs - pub jobs: Option, - - /// Target directory - pub target_dir: Option, - - /// Whether to use incremental compilation - #[serde(default = "default_true")] - pub incremental: bool, - - /// Compiler flags - #[serde(default)] - pub flags: Vec, -} - -/// Credentials -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Credentials { - /// Token by registry name - pub tokens: std::collections::HashMap, -} - -impl Config { - /// Load global configuration - pub fn load_global() -> anyhow::Result { - match global_config_path() { - Some(path) if path.exists() => { - let content = std::fs::read_to_string(&path)?; - Ok(toml::from_str(&content)?) - } - _ => Ok(Self::default()), - } - } - - /// Save global configuration - pub fn save_global(&self) -> anyhow::Result<()> { - if let Some(path) = global_config_path() { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - let content = toml::to_string_pretty(self)?; - std::fs::write(path, content)?; - } - Ok(()) - } - - /// Load project-local configuration - pub fn load_local(project_root: impl AsRef) -> anyhow::Result> { - let path = project_root.as_ref().join(".affine").join("config.toml"); - if path.exists() { - let content = std::fs::read_to_string(&path)?; - Ok(Some(toml::from_str(&content)?)) - } else { - Ok(None) - } - } - - /// Merge with another config (other takes precedence) - pub fn merge(&mut self, other: Self) { - if !other.registries.is_empty() { - self.registries = other.registries; - } - // Merge network config - if other.net.proxy.is_some() { - self.net.proxy = other.net.proxy; - } - if other.net.https_proxy.is_some() { - self.net.https_proxy = other.net.https_proxy; - } - // Merge build config - if other.build.jobs.is_some() { - self.build.jobs = other.build.jobs; - } - if other.build.target_dir.is_some() { - self.build.target_dir = other.build.target_dir; - } - // Merge env - self.env.extend(other.env); - } - - /// Get the default registry URL - pub fn default_registry(&self) -> String { - self.registries - .iter() - .find(|r| r.default) - .map(|r| r.url.clone()) - .unwrap_or_else(|| crate::registry::DEFAULT_REGISTRY.to_string()) - } -} - -impl Credentials { - /// Load credentials - pub fn load() -> anyhow::Result { - match credentials_path() { - Some(path) if path.exists() => { - let content = std::fs::read_to_string(&path)?; - Ok(toml::from_str(&content)?) - } - _ => Ok(Self::default()), - } - } - - /// Save credentials - pub fn save(&self) -> anyhow::Result<()> { - if let Some(path) = credentials_path() { - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - // Set restrictive permissions on credentials file - let content = toml::to_string_pretty(self)?; - std::fs::write(&path, content)?; - - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - let mut perms = std::fs::metadata(&path)?.permissions(); - perms.set_mode(0o600); - std::fs::set_permissions(&path, perms)?; - } - } - Ok(()) - } - - /// Get token for a registry - pub fn get_token(&self, registry: &str) -> Option<&str> { - self.tokens.get(registry).map(|s| s.as_str()) - } - - /// Set token for a registry - pub fn set_token(&mut self, registry: String, token: String) { - self.tokens.insert(registry, token); - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add environment variable overrides -// - [ ] Add configuration validation -// - [ ] Add shell completion config -// - [ ] Add alias support -// - [ ] Add profiles (dev, release, etc.) diff --git a/affinescript/tools/affine-pkg/src/lockfile.rs b/affinescript/tools/affine-pkg/src/lockfile.rs deleted file mode 100644 index e238100..0000000 --- a/affinescript/tools/affine-pkg/src/lockfile.rs +++ /dev/null @@ -1,147 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Lock File (affine.lock) -//! -//! Records the exact versions of all dependencies for reproducibility. - -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::Path; - -/// Lock file format -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Lockfile { - /// Lock file version - pub version: u32, - - /// Locked packages - pub packages: Vec, - - /// Metadata (checksums, etc.) - #[serde(default)] - pub metadata: HashMap, -} - -/// A locked package -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LockedPackage { - /// Package name - pub name: String, - - /// Exact version - pub version: String, - - /// Source (registry, git, path) - pub source: PackageSource, - - /// Content hash (SHA-256) - pub checksum: Option, - - /// Dependencies of this package - #[serde(default)] - pub dependencies: Vec, -} - -/// Package source -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type")] -pub enum PackageSource { - /// From registry - #[serde(rename = "registry")] - Registry { - /// Registry URL - url: String, - }, - - /// From git - #[serde(rename = "git")] - Git { - /// Repository URL - url: String, - /// Commit hash - commit: String, - }, - - /// Local path - #[serde(rename = "path")] - Path { - /// Relative path - path: String, - }, -} - -/// A locked dependency reference -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LockedDependency { - /// Package name - pub name: String, - - /// Version - pub version: String, - - /// Source identifier - pub source: Option, -} - -impl Lockfile { - /// Current lock file version - pub const VERSION: u32 = 1; - - /// Create empty lockfile - pub fn new() -> Self { - Lockfile { - version: Self::VERSION, - packages: Vec::new(), - metadata: HashMap::new(), - } - } - - /// Load lockfile from path - pub fn load(path: impl AsRef) -> anyhow::Result { - let content = std::fs::read_to_string(path)?; - let lockfile: Lockfile = toml::from_str(&content)?; - Ok(lockfile) - } - - /// Save lockfile to path - pub fn save(&self, path: impl AsRef) -> anyhow::Result<()> { - let content = toml::to_string_pretty(self)?; - std::fs::write(path, content)?; - Ok(()) - } - - /// Find a locked package by name and version - pub fn find(&self, name: &str, version: &str) -> Option<&LockedPackage> { - self.packages - .iter() - .find(|p| p.name == name && p.version == version) - } - - /// Add or update a package - pub fn insert(&mut self, package: LockedPackage) { - // Remove existing entry if present - self.packages - .retain(|p| !(p.name == package.name && p.version == package.version)); - - self.packages.push(package); - } - - /// Sort packages for deterministic output - pub fn sort(&mut self) { - self.packages.sort_by(|a, b| { - a.name.cmp(&b.name).then_with(|| a.version.cmp(&b.version)) - }); - } -} - -impl Default for Lockfile { - fn default() -> Self { - Self::new() - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add checksum verification -// - [ ] Add lockfile merging (for conflicts) -// - [ ] Add lockfile diffing -// - [ ] Add source canonicalization diff --git a/affinescript/tools/affine-pkg/src/main.rs b/affinescript/tools/affine-pkg/src/main.rs deleted file mode 100644 index 30bf5cb..0000000 --- a/affinescript/tools/affine-pkg/src/main.rs +++ /dev/null @@ -1,324 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -#![forbid(unsafe_code)] - -//! AffineScript Package Manager -//! -//! A workspace-aware, Cargo-inspired package manager for AffineScript. -//! -//! # Features -//! -//! - Dependency resolution with version constraints -//! - Workspace support for monorepos -//! - Content-addressed storage (like pnpm) -//! - Lock file for reproducibility -//! - Build script support - -use clap::{Parser, Subcommand}; - -mod build; -mod config; -mod lockfile; -mod manifest; -mod registry; -mod resolve; -mod storage; -mod workspace; - -#[derive(Parser)] -#[command(name = "affine")] -#[command(about = "AffineScript package manager", long_about = None)] -#[command(version)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Create a new package - New { - /// Package name - name: String, - /// Create a library instead of a binary - #[arg(long)] - lib: bool, - }, - - /// Initialize a package in the current directory - Init { - /// Package name (defaults to directory name) - #[arg(long)] - name: Option, - /// Create a library instead of a binary - #[arg(long)] - lib: bool, - }, - - /// Build the current package - Build { - /// Build in release mode - #[arg(long)] - release: bool, - /// Build only the specified package - #[arg(short, long)] - package: Option, - }, - - /// Check the current package for errors - Check { - /// Check only the specified package - #[arg(short, long)] - package: Option, - }, - - /// Run the current package - Run { - /// Run in release mode - #[arg(long)] - release: bool, - /// Arguments to pass to the program - #[arg(trailing_var_arg = true)] - args: Vec, - }, - - /// Run tests - Test { - /// Test name filter - filter: Option, - /// Run in release mode - #[arg(long)] - release: bool, - }, - - /// Add a dependency - Add { - /// Dependency to add (name or name@version) - dependency: String, - /// Add as dev dependency - #[arg(long)] - dev: bool, - /// Add as build dependency - #[arg(long)] - build: bool, - }, - - /// Remove a dependency - Remove { - /// Dependency to remove - dependency: String, - }, - - /// Update dependencies - Update { - /// Package to update (updates all if not specified) - package: Option, - }, - - /// Install dependencies - Install, - - /// Publish package to registry - Publish { - /// Don't actually publish, just verify - #[arg(long)] - dry_run: bool, - }, - - /// Search for packages - Search { - /// Search query - query: String, - }, - - /// Show package information - Info { - /// Package name - package: String, - }, - - /// Clean build artifacts - Clean, - - /// Format source code - Fmt { - /// Check formatting without changing files - #[arg(long)] - check: bool, - }, - - /// Run linter - Lint { - /// Auto-fix issues where possible - #[arg(long)] - fix: bool, - }, - - /// Generate documentation - Doc { - /// Open documentation in browser - #[arg(long)] - open: bool, - }, -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - // Initialize logging - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env() - .add_directive(tracing::Level::INFO.into()), - ) - .init(); - - let cli = Cli::parse(); - - match cli.command { - Commands::New { name, lib } => { - // TODO(#84): Phase 8 implementation - // - [ ] Create directory structure - // - [ ] Generate affine.toml - // - [ ] Create src/main.afs or src/lib.afs - println!("Creating new {} package: {}", if lib { "library" } else { "binary" }, name); - } - - Commands::Init { name, lib } => { - // TODO(#84): Phase 8 implementation - // - [ ] Generate affine.toml in current directory - // - [ ] Create src/ directory - let _ = (name, lib); - println!("Initializing package in current directory"); - } - - Commands::Build { release, package } => { - // TODO(#84): Phase 8 implementation - // - [ ] Load manifest - // - [ ] Resolve dependencies - // - [ ] Compile all packages - let _ = (release, package); - println!("Building..."); - } - - Commands::Check { package } => { - // TODO(#84): Phase 8 implementation - // - [ ] Type check without codegen - let _ = package; - println!("Checking..."); - } - - Commands::Run { release, args } => { - // TODO(#84): Phase 8 implementation - // - [ ] Build if needed - // - [ ] Run the binary - let _ = (release, args); - println!("Running..."); - } - - Commands::Test { filter, release } => { - // TODO(#84): Phase 8 implementation - // - [ ] Find test functions - // - [ ] Build test binary - // - [ ] Run tests - let _ = (filter, release); - println!("Testing..."); - } - - Commands::Add { dependency, dev, build } => { - // TODO(#84): Phase 8 implementation - // - [ ] Parse dependency spec - // - [ ] Resolve version - // - [ ] Update manifest - // - [ ] Update lockfile - let _ = (dev, build); - println!("Adding dependency: {}", dependency); - } - - Commands::Remove { dependency } => { - // TODO(#84): Phase 8 implementation - // - [ ] Remove from manifest - // - [ ] Update lockfile - println!("Removing dependency: {}", dependency); - } - - Commands::Update { package } => { - // TODO(#84): Phase 8 implementation - // - [ ] Resolve latest compatible versions - // - [ ] Update lockfile - let _ = package; - println!("Updating dependencies..."); - } - - Commands::Install => { - // TODO(#84): Phase 8 implementation - // - [ ] Read lockfile - // - [ ] Download missing packages - // - [ ] Link to content store - println!("Installing dependencies..."); - } - - Commands::Publish { dry_run } => { - // TODO(#84): Phase 8 implementation - // - [ ] Verify package - // - [ ] Build tarball - // - [ ] Upload to registry - let _ = dry_run; - println!("Publishing..."); - } - - Commands::Search { query } => { - // TODO(#84): Phase 8 implementation - // - [ ] Query registry API - // - [ ] Display results - println!("Searching for: {}", query); - } - - Commands::Info { package } => { - // TODO(#84): Phase 8 implementation - // - [ ] Fetch package info - // - [ ] Display metadata - println!("Package info: {}", package); - } - - Commands::Clean => { - // TODO(#84): Phase 8 implementation - // - [ ] Remove target/ directory - println!("Cleaning..."); - } - - Commands::Fmt { check } => { - // TODO(#84): Phase 8 implementation - // - [ ] Run formatter - let _ = check; - println!("Formatting..."); - } - - Commands::Lint { fix } => { - // TODO(#84): Phase 8 implementation - // - [ ] Run linter - let _ = fix; - println!("Linting..."); - } - - Commands::Doc { open } => { - // TODO(#84): Phase 8 implementation - // - [ ] Generate docs - // - [ ] Open in browser if requested - let _ = open; - println!("Generating documentation..."); - } - } - - Ok(()) -} - -// TODO(#84): Phase 8 implementation -// - [ ] Implement manifest parsing (affine.toml) -// - [ ] Implement dependency resolution (SAT solver or PubGrub) -// - [ ] Implement content-addressed storage -// - [ ] Implement lockfile format -// - [ ] Implement registry client -// - [ ] Implement build orchestration -// - [ ] Add workspace support -// - [ ] Add feature flags -// - [ ] Add build scripts diff --git a/affinescript/tools/affine-pkg/src/manifest.rs b/affinescript/tools/affine-pkg/src/manifest.rs deleted file mode 100644 index 039a9b4..0000000 --- a/affinescript/tools/affine-pkg/src/manifest.rs +++ /dev/null @@ -1,223 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Package Manifest (affine.toml) -//! -//! Defines the package metadata and dependencies. - -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::path::Path; - -/// Package manifest -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Manifest { - /// Package information - pub package: Package, - - /// Dependencies - #[serde(default)] - pub dependencies: HashMap, - - /// Dev dependencies - #[serde(default, rename = "dev-dependencies")] - pub dev_dependencies: HashMap, - - /// Build dependencies - #[serde(default, rename = "build-dependencies")] - pub build_dependencies: HashMap, - - /// Feature flags - #[serde(default)] - pub features: HashMap>, - - /// Binary targets - #[serde(default, rename = "bin")] - pub binaries: Vec, - - /// Library target - pub lib: Option, - - /// Workspace configuration - pub workspace: Option, -} - -/// Package metadata -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Package { - /// Package name - pub name: String, - - /// Package version - pub version: String, - - /// AffineScript edition - #[serde(default = "default_edition")] - pub edition: String, - - /// Package authors - #[serde(default)] - pub authors: Vec, - - /// Package description - pub description: Option, - - /// Documentation URL - pub documentation: Option, - - /// Homepage URL - pub homepage: Option, - - /// Repository URL - pub repository: Option, - - /// License identifier (SPDX) - pub license: Option, - - /// License file path - #[serde(rename = "license-file")] - pub license_file: Option, - - /// Keywords for search - #[serde(default)] - pub keywords: Vec, - - /// Categories for classification - #[serde(default)] - pub categories: Vec, - - /// Build script path - pub build: Option, -} - -fn default_edition() -> String { - "2024".to_string() -} - -/// Dependency specification -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(untagged)] -pub enum Dependency { - /// Simple version string - Version(String), - - /// Detailed dependency specification - Detailed(DependencyDetail), -} - -/// Detailed dependency specification -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DependencyDetail { - /// Version requirement - pub version: Option, - - /// Git repository URL - pub git: Option, - - /// Git branch - pub branch: Option, - - /// Git tag - pub tag: Option, - - /// Git commit - pub rev: Option, - - /// Local path - pub path: Option, - - /// Package name in registry (if different) - pub package: Option, - - /// Required features - #[serde(default)] - pub features: Vec, - - /// Whether this is optional - #[serde(default)] - pub optional: bool, - - /// Default features - #[serde(default = "default_true", rename = "default-features")] - pub default_features: bool, - - /// Registry URL - pub registry: Option, -} - -fn default_true() -> bool { - true -} - -/// Binary target -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BinaryTarget { - /// Binary name - pub name: String, - - /// Source file path - pub path: Option, - - /// Required features - #[serde(default)] - #[serde(rename = "required-features")] - pub required_features: Vec, -} - -/// Library target -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LibraryTarget { - /// Library name - pub name: Option, - - /// Source file path - pub path: Option, - - /// Crate types to generate - #[serde(default, rename = "crate-type")] - pub crate_type: Vec, -} - -/// Workspace configuration -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Workspace { - /// Member packages - pub members: Vec, - - /// Excluded paths - #[serde(default)] - pub exclude: Vec, - - /// Shared dependencies - pub dependencies: Option>, -} - -impl Manifest { - /// Load manifest from file - pub fn load(path: impl AsRef) -> anyhow::Result { - let content = std::fs::read_to_string(path)?; - let manifest: Manifest = toml::from_str(&content)?; - Ok(manifest) - } - - /// Save manifest to file - pub fn save(&self, path: impl AsRef) -> anyhow::Result<()> { - let content = toml::to_string_pretty(self)?; - std::fs::write(path, content)?; - Ok(()) - } - - /// Get all dependencies (including dev and build) - pub fn all_dependencies(&self) -> impl Iterator { - self.dependencies - .iter() - .chain(self.dev_dependencies.iter()) - .chain(self.build_dependencies.iter()) - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add manifest validation -// - [ ] Add version validation (semver) -// - [ ] Add license validation (SPDX) -// - [ ] Add workspace resolution -// - [ ] Add feature resolution diff --git a/affinescript/tools/affine-pkg/src/registry.rs b/affinescript/tools/affine-pkg/src/registry.rs deleted file mode 100644 index 093a779..0000000 --- a/affinescript/tools/affine-pkg/src/registry.rs +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Package Registry Client -//! -//! Communicates with the AffineScript package registry. - -use semver::Version; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// Default registry URL -pub const DEFAULT_REGISTRY: &str = "https://packages.affinescript.dev"; - -/// Registry client -pub struct RegistryClient { - /// HTTP client - client: reqwest::Client, - - /// Registry base URL - base_url: String, - - /// Authentication token - token: Option, -} - -/// Package search result -#[derive(Debug, Clone, Deserialize)] -pub struct SearchResult { - /// Total number of matches - pub total: usize, - - /// Matching packages - pub packages: Vec, -} - -/// Package summary (from search) -#[derive(Debug, Clone, Deserialize)] -pub struct PackageSummary { - /// Package name - pub name: String, - - /// Latest version - pub version: String, - - /// Description - pub description: Option, - - /// Download count - pub downloads: u64, -} - -/// Full package info -#[derive(Debug, Clone, Deserialize)] -pub struct PackageInfo { - /// Package name - pub name: String, - - /// All published versions - pub versions: Vec, - - /// Keywords - pub keywords: Vec, - - /// Categories - pub categories: Vec, - - /// Repository URL - pub repository: Option, - - /// Documentation URL - pub documentation: Option, - - /// Homepage URL - pub homepage: Option, -} - -/// Version info -#[derive(Debug, Clone, Deserialize)] -pub struct VersionInfo { - /// Version number - pub version: String, - - /// Publish date - pub published_at: String, - - /// Download URL - pub download_url: String, - - /// SHA-256 checksum - pub checksum: String, - - /// Dependencies - pub dependencies: HashMap, - - /// Features - pub features: HashMap>, - - /// Whether this version is yanked - pub yanked: bool, -} - -/// Publish request -#[derive(Debug, Clone, Serialize)] -pub struct PublishRequest { - /// Package name - pub name: String, - - /// Version - pub version: String, - - /// Tarball contents (base64) - pub tarball: String, - - /// SHA-256 of tarball - pub checksum: String, -} - -/// Registry error -#[derive(Debug, thiserror::Error)] -pub enum RegistryError { - #[error("HTTP error: {0}")] - Http(#[from] reqwest::Error), - - #[error("Package not found: {0}")] - NotFound(String), - - #[error("Authentication required")] - Unauthorized, - - #[error("Rate limited, retry after {0} seconds")] - RateLimited(u64), - - #[error("Registry error: {0}")] - RegistryError(String), -} - -impl RegistryClient { - /// Create a new registry client - pub fn new(base_url: Option) -> Self { - RegistryClient { - client: reqwest::Client::new(), - base_url: base_url.unwrap_or_else(|| DEFAULT_REGISTRY.to_string()), - token: None, - } - } - - /// Set authentication token - pub fn with_token(mut self, token: String) -> Self { - self.token = Some(token); - self - } - - /// Search for packages - pub async fn search(&self, query: &str, page: u32) -> Result { - // TODO(#84): Phase 8 implementation - // GET /api/v1/search?q={query}&page={page} - let _ = (query, page); - Ok(SearchResult { - total: 0, - packages: vec![], - }) - } - - /// Get package info - pub async fn get_package(&self, name: &str) -> Result { - // TODO(#84): Phase 8 implementation - // GET /api/v1/packages/{name} - let _ = name; - Err(RegistryError::NotFound(name.to_string())) - } - - /// Get specific version info - pub async fn get_version( - &self, - name: &str, - version: &Version, - ) -> Result { - // TODO(#84): Phase 8 implementation - // GET /api/v1/packages/{name}/{version} - let _ = (name, version); - Err(RegistryError::NotFound(format!("{}@{}", name, version))) - } - - /// Download package tarball - pub async fn download(&self, url: &str) -> Result { - // TODO(#84): Phase 8 implementation - // GET {download_url} - let _ = url; - Ok(bytes::Bytes::new()) - } - - /// Publish a package - pub async fn publish(&self, request: PublishRequest) -> Result<(), RegistryError> { - // TODO(#84): Phase 8 implementation - // PUT /api/v1/packages/{name} - // Authorization: Bearer {token} - let _ = request; - - if self.token.is_none() { - return Err(RegistryError::Unauthorized); - } - - Ok(()) - } - - /// Yank a version - pub async fn yank(&self, name: &str, version: &Version) -> Result<(), RegistryError> { - // TODO(#84): Phase 8 implementation - // DELETE /api/v1/packages/{name}/{version} - let _ = (name, version); - - if self.token.is_none() { - return Err(RegistryError::Unauthorized); - } - - Ok(()) - } - - /// Unyank a version - pub async fn unyank(&self, name: &str, version: &Version) -> Result<(), RegistryError> { - // TODO(#84): Phase 8 implementation - // PUT /api/v1/packages/{name}/{version}/unyank - let _ = (name, version); - - if self.token.is_none() { - return Err(RegistryError::Unauthorized); - } - - Ok(()) - } - - /// Get owners of a package - pub async fn get_owners(&self, name: &str) -> Result, RegistryError> { - // TODO(#84): Phase 8 implementation - // GET /api/v1/packages/{name}/owners - let _ = name; - Ok(vec![]) - } - - /// Add an owner - pub async fn add_owner(&self, name: &str, user: &str) -> Result<(), RegistryError> { - // TODO(#84): Phase 8 implementation - // PUT /api/v1/packages/{name}/owners - let _ = (name, user); - - if self.token.is_none() { - return Err(RegistryError::Unauthorized); - } - - Ok(()) - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Implement actual HTTP requests -// - [ ] Add retry logic with backoff -// - [ ] Add caching with ETags -// - [ ] Add offline mode with cached index -// - [ ] Add sparse index support -// - [ ] Add parallel downloads diff --git a/affinescript/tools/affine-pkg/src/resolve.rs b/affinescript/tools/affine-pkg/src/resolve.rs deleted file mode 100644 index f38cb37..0000000 --- a/affinescript/tools/affine-pkg/src/resolve.rs +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Dependency Resolution -//! -//! Resolves version constraints to concrete versions. -//! Uses PubGrub algorithm for efficient conflict detection. - -use crate::manifest::Dependency; -use semver::{Version, VersionReq}; -use std::collections::{HashMap, HashSet}; - -/// A resolved dependency graph -#[derive(Debug, Clone)] -pub struct ResolvedGraph { - /// Packages in topological order - pub packages: Vec, - - /// Dependency edges - pub edges: HashMap>, -} - -/// A resolved package -#[derive(Debug, Clone)] -pub struct ResolvedPackage { - /// Package identifier - pub id: PackageId, - - /// Features enabled - pub features: HashSet, - - /// Source URL/path - pub source: String, -} - -/// Package identifier (name + version) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct PackageId { - /// Package name - pub name: String, - - /// Exact version - pub version: Version, -} - -impl std::fmt::Display for PackageId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}@{}", self.name, self.version) - } -} - -/// Resolution error -#[derive(Debug, thiserror::Error)] -pub enum ResolveError { - /// Version conflict - #[error("version conflict for {package}: {required} conflicts with {available}")] - Conflict { - package: String, - required: String, - available: String, - }, - - /// Package not found - #[error("package not found: {0}")] - NotFound(String), - - /// Cyclic dependency - #[error("cyclic dependency detected: {0}")] - Cycle(String), - - /// Feature not found - #[error("feature {feature} not found in package {package}")] - FeatureNotFound { package: String, feature: String }, -} - -/// Resolver state -pub struct Resolver { - /// Registry client - // registry: RegistryClient, - - /// Cache of available versions - version_cache: HashMap>, - - /// Cache of package metadata - metadata_cache: HashMap, -} - -/// Package metadata from registry -#[derive(Debug, Clone)] -pub struct PackageMetadata { - /// Package name - pub name: String, - - /// Version - pub version: Version, - - /// Dependencies - pub dependencies: HashMap, - - /// Features - pub features: HashMap>, - - /// Default features - pub default_features: Vec, -} - -impl Resolver { - /// Create a new resolver - pub fn new() -> Self { - Resolver { - version_cache: HashMap::new(), - metadata_cache: HashMap::new(), - } - } - - /// Resolve dependencies - pub fn resolve( - &mut self, - _root_deps: &HashMap, - ) -> Result { - // TODO(#84): Phase 8 implementation using PubGrub algorithm - // Reference: https://nex3.medium.com/pubgrub-2fb6470504f - // - // 1. Start with root package requirements - // 2. For each unsatisfied requirement: - // a. Find compatible versions - // b. Pick best version (highest satisfying) - // c. Add package's dependencies to requirements - // 3. If conflict detected: - // a. Analyze conflict - // b. Derive resolution (incompatibility) - // c. Backtrack and try alternative - // 4. Continue until all satisfied or proven unsatisfiable - - Ok(ResolvedGraph { - packages: Vec::new(), - edges: HashMap::new(), - }) - } - - /// Check if a version satisfies a requirement - fn satisfies(&self, version: &Version, req: &VersionReq) -> bool { - req.matches(version) - } - - /// Get available versions for a package - fn get_versions(&mut self, _name: &str) -> Result<&[Version], ResolveError> { - // TODO(#84): Phase 8 implementation - // - [ ] Check cache - // - [ ] Query registry - // - [ ] Parse and cache versions - - Ok(&[]) - } - - /// Get package metadata - fn get_metadata(&mut self, _id: &PackageId) -> Result<&PackageMetadata, ResolveError> { - // TODO(#84): Phase 8 implementation - // - [ ] Check cache - // - [ ] Query registry - // - [ ] Parse and cache metadata - - Err(ResolveError::NotFound("not implemented".into())) - } -} - -impl Default for Resolver { - fn default() -> Self { - Self::new() - } -} - -/// Parse a version requirement string -pub fn parse_requirement(s: &str) -> Result { - // Handle common shortcuts - let normalized = match s { - // Exact version - s if !s.contains(['>', '<', '=', '^', '~', '*']) => format!("^{}", s), - // Already has operator - s => s.to_string(), - }; - - VersionReq::parse(&normalized) -} - -// TODO(#84): Phase 8 implementation -// - [ ] Implement full PubGrub algorithm -// - [ ] Add version preference (prefer newer, prefer locked) -// - [ ] Add feature unification -// - [ ] Add optional dependency handling -// - [ ] Add workspace dependency resolution -// - [ ] Add parallel version fetching diff --git a/affinescript/tools/affine-pkg/src/storage.rs b/affinescript/tools/affine-pkg/src/storage.rs deleted file mode 100644 index 89e2bdd..0000000 --- a/affinescript/tools/affine-pkg/src/storage.rs +++ /dev/null @@ -1,196 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Content-Addressed Storage -//! -//! Stores packages by content hash for deduplication (like pnpm). - -use sha2::{Digest, Sha256}; -use std::path::{Path, PathBuf}; - -/// Content store directory -pub const STORE_DIR: &str = ".affine/store"; - -/// Package store -pub struct PackageStore { - /// Store root directory - root: PathBuf, -} - -impl PackageStore { - /// Create or open a package store - pub fn new(root: impl AsRef) -> std::io::Result { - let root = root.as_ref().to_path_buf(); - std::fs::create_dir_all(&root)?; - Ok(PackageStore { root }) - } - - /// Get the default store location - pub fn default_store() -> std::io::Result { - let home = dirs::home_dir() - .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no home directory"))?; - - Self::new(home.join(STORE_DIR)) - } - - /// Store content and return its hash - pub fn store(&self, content: &[u8]) -> std::io::Result { - let hash = ContentHash::compute(content); - let path = self.path_for(&hash); - - if !path.exists() { - // Create parent directories - if let Some(parent) = path.parent() { - std::fs::create_dir_all(parent)?; - } - - // Write atomically - let temp_path = path.with_extension("tmp"); - std::fs::write(&temp_path, content)?; - std::fs::rename(&temp_path, &path)?; - } - - Ok(hash) - } - - /// Retrieve content by hash - pub fn retrieve(&self, hash: &ContentHash) -> std::io::Result> { - let path = self.path_for(hash); - std::fs::read(path) - } - - /// Check if content exists - pub fn contains(&self, hash: &ContentHash) -> bool { - self.path_for(hash).exists() - } - - /// Get path for a content hash - pub fn path_for(&self, hash: &ContentHash) -> PathBuf { - // Use first 2 chars as directory for sharding - let hex = hash.to_hex(); - self.root.join(&hex[..2]).join(&hex[2..]) - } - - /// Store a package and create a link - pub fn store_package( - &self, - name: &str, - version: &str, - content: &[u8], - ) -> std::io::Result { - let hash = self.store(content)?; - - // Create package directory - let pkg_dir = self.root.join("packages").join(name).join(version); - std::fs::create_dir_all(&pkg_dir)?; - - // Extract tarball - // TODO(#84): Phase 8 implementation - // - [ ] Extract tar.gz to pkg_dir - // - [ ] Create integrity file - - Ok(pkg_dir) - } - - /// Link package to node_modules equivalent - pub fn link_package( - &self, - _name: &str, - _version: &str, - _target: impl AsRef, - ) -> std::io::Result<()> { - // TODO(#84): Phase 8 implementation - // - [ ] Create symlink or copy on Windows - // - [ ] Handle nested dependencies - - Ok(()) - } - - /// Garbage collect unused content - pub fn gc(&self) -> std::io::Result { - // TODO(#84): Phase 8 implementation - // - [ ] Scan all projects for used packages - // - [ ] Remove unreferenced content - // - [ ] Return statistics - - Ok(GcStats { - removed_count: 0, - removed_bytes: 0, - }) - } - - /// Verify store integrity - pub fn verify(&self) -> std::io::Result> { - // TODO(#84): Phase 8 implementation - // - [ ] Scan all content - // - [ ] Verify hashes match - // - [ ] Report errors - - Ok(vec![]) - } -} - -/// Content hash (SHA-256) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ContentHash([u8; 32]); - -impl ContentHash { - /// Compute hash of content - pub fn compute(content: &[u8]) -> Self { - let mut hasher = Sha256::new(); - hasher.update(content); - let result = hasher.finalize(); - ContentHash(result.into()) - } - - /// Parse from hex string - pub fn from_hex(hex: &str) -> Result { - let bytes = hex::decode(hex)?; - if bytes.len() != 32 { - return Err(hex::FromHexError::InvalidStringLength); - } - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes); - Ok(ContentHash(arr)) - } - - /// Convert to hex string - pub fn to_hex(&self) -> String { - hex::encode(self.0) - } -} - -impl std::fmt::Display for ContentHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "sha256:{}", self.to_hex()) - } -} - -/// GC statistics -#[derive(Debug, Default)] -pub struct GcStats { - /// Number of items removed - pub removed_count: usize, - - /// Bytes freed - pub removed_bytes: u64, -} - -/// Verification error -#[derive(Debug)] -pub struct VerifyError { - /// Path to corrupted content - pub path: PathBuf, - - /// Expected hash - pub expected: ContentHash, - - /// Actual hash - pub actual: ContentHash, -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add parallel extraction -// - [ ] Add hardlink support for same-OS -// - [ ] Add copy-on-write support (reflinks) -// - [ ] Add lockfile for concurrent access -// - [ ] Add prune for specific packages diff --git a/affinescript/tools/affine-pkg/src/workspace.rs b/affinescript/tools/affine-pkg/src/workspace.rs deleted file mode 100644 index 2dc80d0..0000000 --- a/affinescript/tools/affine-pkg/src/workspace.rs +++ /dev/null @@ -1,164 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Workspace Support -//! -//! Manages monorepos with multiple packages. - -use crate::manifest::Manifest; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; - -/// A workspace containing multiple packages -#[derive(Debug)] -pub struct Workspace { - /// Root directory - pub root: PathBuf, - - /// Root manifest - pub manifest: Manifest, - - /// Member packages (path -> manifest) - pub members: HashMap, -} - -impl Workspace { - /// Find and load a workspace - pub fn find(start_dir: impl AsRef) -> anyhow::Result> { - let start = start_dir.as_ref().canonicalize()?; - - // Walk up looking for workspace root - let mut current = start.as_path(); - loop { - let manifest_path = current.join("affine.toml"); - if manifest_path.exists() { - let manifest = Manifest::load(&manifest_path)?; - if manifest.workspace.is_some() { - return Ok(Some(Self::load(current)?)); - } - } - - match current.parent() { - Some(parent) => current = parent, - None => break, - } - } - - Ok(None) - } - - /// Load a workspace from its root - pub fn load(root: impl AsRef) -> anyhow::Result { - let root = root.as_ref().to_path_buf(); - let manifest = Manifest::load(root.join("affine.toml"))?; - - let mut members = HashMap::new(); - - if let Some(ws) = &manifest.workspace { - for pattern in &ws.members { - // Expand glob patterns - for entry in glob::glob(&root.join(pattern).to_string_lossy())? { - let path = entry?; - if path.is_dir() { - let member_manifest_path = path.join("affine.toml"); - if member_manifest_path.exists() { - let member_manifest = Manifest::load(&member_manifest_path)?; - members.insert(path, member_manifest); - } - } - } - } - } - - Ok(Workspace { - root, - manifest, - members, - }) - } - - /// Get all packages in the workspace (including root if it's a package) - pub fn packages(&self) -> impl Iterator { - let root_iter = if self.manifest.package.name.is_empty() { - None - } else { - Some((&self.root, &self.manifest)) - }; - - root_iter.into_iter().chain(self.members.iter()) - } - - /// Find a package by name - pub fn find_package(&self, name: &str) -> Option<(&PathBuf, &Manifest)> { - self.packages().find(|(_, m)| m.package.name == name) - } - - /// Get dependency graph between workspace members - pub fn dependency_graph(&self) -> HashMap> { - let mut graph = HashMap::new(); - - for (_, manifest) in self.packages() { - let mut deps = Vec::new(); - - for dep_name in manifest.dependencies.keys() { - // Check if this is a workspace member - if self.find_package(dep_name).is_some() { - deps.push(dep_name.clone()); - } - } - - graph.insert(manifest.package.name.clone(), deps); - } - - graph - } - - /// Topological sort of packages (dependencies first) - pub fn sorted_packages(&self) -> anyhow::Result> { - let graph = self.dependency_graph(); - let mut result = Vec::new(); - let mut visited = std::collections::HashSet::new(); - let mut visiting = std::collections::HashSet::new(); - - fn visit( - name: &str, - graph: &HashMap>, - visited: &mut std::collections::HashSet, - visiting: &mut std::collections::HashSet, - result: &mut Vec, - ) -> anyhow::Result<()> { - if visited.contains(name) { - return Ok(()); - } - if visiting.contains(name) { - anyhow::bail!("Cyclic dependency detected involving: {}", name); - } - - visiting.insert(name.to_string()); - - if let Some(deps) = graph.get(name) { - for dep in deps { - visit(dep, graph, visited, visiting, result)?; - } - } - - visiting.remove(name); - visited.insert(name.to_string()); - result.push(name.to_string()); - - Ok(()) - } - - for name in graph.keys() { - visit(name, &graph, &mut visited, &mut visiting, &mut result)?; - } - - Ok(result) - } -} - -// TODO(#84): Phase 8 implementation -// - [ ] Add workspace inheritance for dependencies -// - [ ] Add workspace-level features -// - [ ] Add parallel builds across workspace -// - [ ] Add affected package detection for CI -// - [ ] Add workspace-wide linting/formatting diff --git a/affinescript/tools/affinescript-dap/Cargo.toml b/affinescript/tools/affinescript-dap/Cargo.toml deleted file mode 100644 index 83bac55..0000000 --- a/affinescript/tools/affinescript-dap/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -[package] -name = "affinescript-dap" -version.workspace = true -edition.workspace = true -license.workspace = true -repository.workspace = true - -[lib] -name = "affinescript_dap" -path = "src/lib.rs" - -[[bin]] -name = "affinescript-dap" -path = "src/main.rs" - -[dependencies] -# DAP -tokio = { version = "1", features = ["full"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -# Affinescript compiler components -affinescript-syntax = { path = "../../src/syntax" } -affinescript-core = { path = "../../src/core" } diff --git a/affinescript/tools/affinescript-dap/src/main.rs b/affinescript/tools/affinescript-dap/src/main.rs deleted file mode 100644 index 0769aa1..0000000 --- a/affinescript/tools/affinescript-dap/src/main.rs +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -#![forbid(unsafe_code)] -//! Debug Adapter Protocol (DAP) implementation for Affinescript -//! -//! This is a minimal DAP adapter for Affinescript. - -use serde::{Deserialize, Serialize}; -use std::io::{BufRead, BufReader, Write}; -use std::net::{TcpListener, TcpStream}; - -#[derive(Debug, Serialize, Deserialize)] -struct DapRequest { - seq: i64, - r#type: String, - command: String, - arguments: Option, -} - -#[derive(Debug, Serialize)] -struct DapResponse { - seq: i64, - r#type: String, - request_seq: i64, - command: String, - success: bool, - message: Option, - body: Option, -} - -fn main() -> Result<(), Box> { - let listener = TcpListener::bind("127.0.0.1:4714")?; - println!("Affinescript DAP server listening on 127.0.0.1:4714"); - - for stream in listener.incoming() { - match stream { - Ok(stream) => { - std::thread::spawn(|| { - if let Err(e) = handle_client(stream) { - eprintln!("Error handling client: {}", e); - } - }); - } - Err(e) => { - eprintln!("Error accepting connection: {}", e); - } - } - } - - Ok(()) -} - -fn handle_client(stream: TcpStream) -> Result<(), Box> { - let reader = BufReader::new(stream.try_clone()?); - let mut writer = stream.try_clone()?; - - for line in reader.lines() { - let line = line?; - let request: DapRequest = serde_json::from_str(&line)?; - let response = match request.command.as_str() { - "initialize" => { - serde_json::to_string(&DapResponse { - seq: 1, - r#type: "response".to_string(), - request_seq: request.seq, - command: "initialize".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({ - "supportsConfigurationDoneRequest": true, - "supportsFunctionBreakpoints": true, - "supportsConditionalBreakpoints": true, - "supportsEvaluateForHovers": true, - "exceptionBreakpointFilters": [], - })), - })? - } - "launch" => { - serde_json::to_string(&DapResponse { - seq: 2, - r#type: "response".to_string(), - request_seq: request.seq, - command: "launch".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"success": true})), - })? - } - "setBreakpoints" => { - serde_json::to_string(&DapResponse { - seq: 3, - r#type: "response".to_string(), - request_seq: request.seq, - command: "setBreakpoints".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"breakpoints": []})), - })? - } - "threads" => { - serde_json::to_string(&DapResponse { - seq: 4, - r#type: "response".to_string(), - request_seq: request.seq, - command: "threads".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"threads": [{"id": 1, "name": "main"}]})); - })? - } - "stackTrace" => { - serde_json::to_string(&DapResponse { - seq: 5, - r#type: "response".to_string(), - request_seq: request.seq, - command: "stackTrace".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"stackFrames": []})), - })? - } - "scopes" => { - serde_json::to_string(&DapResponse { - seq: 6, - r#type: "response".to_string(), - request_seq: request.seq, - command: "scopes".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"scopes": [{"name": "Locals", "variablesReference": 1, "expensive": false}]})); - })? - } - "variables" => { - serde_json::to_string(&DapResponse { - seq: 7, - r#type: "response".to_string(), - request_seq: request.seq, - command: "variables".to_string(), - success: true, - message: None, - body: Some(serde_json::json!({"variables": []})), - })? - } - "disconnect" => { - serde_json::to_string(&DapResponse { - seq: 8, - r#type: "response".to_string(), - request_seq: request.seq, - command: "disconnect".to_string(), - success: true, - message: None, - body: None, - })? - } - _ => { - serde_json::to_string(&DapResponse { - seq: 0, - r#type: "response".to_string(), - request_seq: request.seq, - command: request.command, - success: false, - message: Some("Unknown command".to_string()), - body: None, - })? - } - }; - - writeln!(writer, "{}", response)?; - } - - Ok(()) -} diff --git a/affinescript/tools/affinescript-lsp/Cargo.lock b/affinescript/tools/affinescript-lsp/Cargo.lock deleted file mode 100644 index 814109e..0000000 --- a/affinescript/tools/affinescript-lsp/Cargo.lock +++ /dev/null @@ -1,1344 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "affinescript-lsp" -version = "0.1.0" -dependencies = [ - "anyhow", - "camino", - "lsp-types", - "regex", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-test", - "tower-lsp", - "tracing", - "tracing-subscriber", - "uuid", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "auto_impl" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "js-sys" -version = "0.3.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.183" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lsp-types" -version = "0.94.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" -dependencies = [ - "bitflags 1.3.2", - "serde", - "serde_json", - "serde_repr", - "url", -] - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project" -version = "1.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-macros" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-test" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" -dependencies = [ - "futures-core", - "tokio", - "tokio-stream", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-lsp" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" -dependencies = [ - "async-trait", - "auto_impl", - "bytes", - "dashmap", - "futures", - "httparse", - "lsp-types", - "memchr", - "serde", - "serde_json", - "tokio", - "tokio-util", - "tower", - "tower-lsp-macros", - "tracing", -] - -[[package]] -name = "tower-lsp-macros" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "getrandom", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/affinescript/tools/affinescript-lsp/Cargo.toml b/affinescript/tools/affinescript-lsp/Cargo.toml deleted file mode 100644 index 7ff34aa..0000000 --- a/affinescript/tools/affinescript-lsp/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright 2024 AffineScript Contributors - -[package] -name = "affinescript-lsp" -version = "0.1.0" -edition = "2021" -description = "Language Server Protocol implementation for AffineScript" -license = "Apache-2.0 OR MIT" -repository = "https://github.com/affinescript/affinescript" -keywords = ["lsp", "language-server", "affinescript", "ide"] -categories = ["development-tools", "text-editors"] - -[dependencies] -# LSP protocol types -tower-lsp = "0.20" -lsp-types = "0.94" - -# Async runtime -tokio = { version = "1", features = ["full"] } - -# Serialization -serde = { version = "1", features = ["derive"] } -serde_json = "1" - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Path handling -camino = "1" - -# Error handling -thiserror = "1" -anyhow = "1" - -# Regex for parsing diagnostics -regex = "1" - -# UUID for temp files -uuid = { version = "1", features = ["v4"] } - -# TODO: Add affinescript compiler as dependency once published -# affinescript-compiler = { path = "../../" } - -[dev-dependencies] -tokio-test = "0.4" - -[[bin]] -name = "affinescript-lsp" -path = "src/main.rs" diff --git a/affinescript/tools/affinescript-lsp/src/capabilities.rs b/affinescript/tools/affinescript-lsp/src/capabilities.rs deleted file mode 100644 index a734447..0000000 --- a/affinescript/tools/affinescript-lsp/src/capabilities.rs +++ /dev/null @@ -1,148 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Server Capabilities -//! -//! Defines what features the language server supports. - -use tower_lsp::lsp_types::*; - -/// Build the server capabilities to advertise to the client -pub fn server_capabilities() -> ServerCapabilities { - ServerCapabilities { - // Text document sync - text_document_sync: Some(TextDocumentSyncCapability::Kind( - TextDocumentSyncKind::INCREMENTAL, - )), - - // Hover - hover_provider: Some(HoverProviderCapability::Simple(true)), - - // Completion - completion_provider: Some(CompletionOptions { - trigger_characters: Some(vec![".".to_string(), ":".to_string()]), - resolve_provider: Some(true), - ..Default::default() - }), - - // Definition - definition_provider: Some(OneOf::Left(true)), - - // References - references_provider: Some(OneOf::Left(true)), - - // Document highlight (same-symbol highlighting) - document_highlight_provider: Some(OneOf::Left(true)), - - // Document symbols (outline) - document_symbol_provider: Some(OneOf::Left(true)), - - // Workspace symbol search - workspace_symbol_provider: Some(OneOf::Left(true)), - - // Code actions (quick fixes) - code_action_provider: Some(CodeActionProviderCapability::Simple(true)), - - // Formatting - document_formatting_provider: Some(OneOf::Left(true)), - - // Range formatting - document_range_formatting_provider: Some(OneOf::Left(true)), - - // Rename - rename_provider: Some(OneOf::Right(RenameOptions { - prepare_provider: Some(true), - work_done_progress_options: WorkDoneProgressOptions::default(), - })), - - // Folding - folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), - - // Signature help - signature_help_provider: Some(SignatureHelpOptions { - trigger_characters: Some(vec!["(".to_string(), ",".to_string()]), - retrigger_characters: Some(vec![",".to_string()]), - work_done_progress_options: WorkDoneProgressOptions::default(), - }), - - // Semantic tokens (syntax highlighting) - semantic_tokens_provider: Some( - SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions { - legend: SemanticTokensLegend { - token_types: semantic_token_types(), - token_modifiers: semantic_token_modifiers(), - }, - full: Some(SemanticTokensFullOptions::Bool(true)), - range: Some(true), - ..Default::default() - }), - ), - - // Inlay hints (type annotations) - inlay_hint_provider: Some(OneOf::Left(true)), - - // Workspace capabilities - workspace: Some(WorkspaceServerCapabilities { - workspace_folders: Some(WorkspaceFoldersServerCapabilities { - supported: Some(true), - change_notifications: Some(OneOf::Left(true)), - }), - file_operations: None, - }), - - ..Default::default() - } -} - -/// Semantic token types for syntax highlighting -fn semantic_token_types() -> Vec { - vec![ - SemanticTokenType::NAMESPACE, - SemanticTokenType::TYPE, - SemanticTokenType::CLASS, - SemanticTokenType::ENUM, - SemanticTokenType::INTERFACE, - SemanticTokenType::STRUCT, - SemanticTokenType::TYPE_PARAMETER, - SemanticTokenType::PARAMETER, - SemanticTokenType::VARIABLE, - SemanticTokenType::PROPERTY, - SemanticTokenType::ENUM_MEMBER, - SemanticTokenType::FUNCTION, - SemanticTokenType::METHOD, - SemanticTokenType::MACRO, - SemanticTokenType::KEYWORD, - SemanticTokenType::MODIFIER, - SemanticTokenType::COMMENT, - SemanticTokenType::STRING, - SemanticTokenType::NUMBER, - SemanticTokenType::OPERATOR, - // AffineScript-specific - SemanticTokenType::new("effect"), - SemanticTokenType::new("handler"), - SemanticTokenType::new("quantity"), - SemanticTokenType::new("lifetime"), - ] -} - -/// Semantic token modifiers -fn semantic_token_modifiers() -> Vec { - vec![ - SemanticTokenModifier::DECLARATION, - SemanticTokenModifier::DEFINITION, - SemanticTokenModifier::READONLY, - SemanticTokenModifier::STATIC, - SemanticTokenModifier::DEPRECATED, - SemanticTokenModifier::ABSTRACT, - SemanticTokenModifier::ASYNC, - SemanticTokenModifier::MODIFICATION, - SemanticTokenModifier::DOCUMENTATION, - SemanticTokenModifier::DEFAULT_LIBRARY, - // AffineScript-specific - SemanticTokenModifier::new("linear"), - SemanticTokenModifier::new("affine"), - SemanticTokenModifier::new("unrestricted"), - SemanticTokenModifier::new("erased"), - SemanticTokenModifier::new("mutable"), - SemanticTokenModifier::new("borrowed"), - ] -} diff --git a/affinescript/tools/affinescript-lsp/src/diagnostics.rs b/affinescript/tools/affinescript-lsp/src/diagnostics.rs deleted file mode 100644 index 211235e..0000000 --- a/affinescript/tools/affinescript-lsp/src/diagnostics.rs +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Diagnostics -//! -//! Converts AffineScript compiler diagnostics to LSP diagnostics. - -use tower_lsp::lsp_types::*; - -/// Diagnostic severity mapping -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Severity { - Error, - Warning, - Info, - Hint, -} - -impl From for DiagnosticSeverity { - fn from(severity: Severity) -> Self { - match severity { - Severity::Error => DiagnosticSeverity::ERROR, - Severity::Warning => DiagnosticSeverity::WARNING, - Severity::Info => DiagnosticSeverity::INFORMATION, - Severity::Hint => DiagnosticSeverity::HINT, - } - } -} - -/// AffineScript diagnostic category -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum DiagnosticCategory { - /// Parse error - Parse, - /// Type error - Type, - /// Borrow checker error - Borrow, - /// Effect error - Effect, - /// Quantity error - Quantity, - /// Name resolution error - Name, - /// Refinement type error - Refinement, - /// Warning - Warning, - /// Lint - Lint, -} - -impl DiagnosticCategory { - /// Get the error code prefix - pub fn code_prefix(&self) -> &'static str { - match self { - DiagnosticCategory::Parse => "E0", - DiagnosticCategory::Type => "E1", - DiagnosticCategory::Borrow => "E2", - DiagnosticCategory::Effect => "E3", - DiagnosticCategory::Quantity => "E4", - DiagnosticCategory::Name => "E5", - DiagnosticCategory::Refinement => "E6", - DiagnosticCategory::Warning => "W", - DiagnosticCategory::Lint => "L", - } - } -} - -/// AffineScript diagnostic -#[derive(Debug, Clone)] -pub struct AfsDiagnostic { - /// Error category - pub category: DiagnosticCategory, - /// Error code (within category) - pub code: u32, - /// Error message - pub message: String, - /// Primary span - pub span: AfsSpan, - /// Additional labeled spans - pub labels: Vec<(AfsSpan, String)>, - /// Help text - pub help: Option, - /// Note text - pub note: Option, -} - -/// Source span -#[derive(Debug, Clone)] -pub struct AfsSpan { - pub file: String, - pub start_line: u32, - pub start_col: u32, - pub end_line: u32, - pub end_col: u32, -} - -impl AfsSpan { - /// Convert to LSP range - pub fn to_range(&self) -> Range { - Range { - start: Position { - line: self.start_line.saturating_sub(1), - character: self.start_col.saturating_sub(1), - }, - end: Position { - line: self.end_line.saturating_sub(1), - character: self.end_col.saturating_sub(1), - }, - } - } -} - -impl AfsDiagnostic { - /// Convert to LSP diagnostic - pub fn to_lsp(&self) -> Diagnostic { - let severity = match self.category { - DiagnosticCategory::Warning | DiagnosticCategory::Lint => Severity::Warning, - _ => Severity::Error, - }; - - let mut related = Vec::new(); - for (span, label) in &self.labels { - related.push(DiagnosticRelatedInformation { - location: Location { - uri: Url::parse(&format!("file://{}", span.file)).unwrap(), - range: span.to_range(), - }, - message: label.clone(), - }); - } - - let mut message = self.message.clone(); - if let Some(help) = &self.help { - message.push_str(&format!("\n\nhelp: {}", help)); - } - if let Some(note) = &self.note { - message.push_str(&format!("\n\nnote: {}", note)); - } - - Diagnostic { - range: self.span.to_range(), - severity: Some(severity.into()), - code: Some(NumberOrString::String(format!( - "{}{}", - self.category.code_prefix(), - self.code - ))), - code_description: Some(CodeDescription { - href: Url::parse(&format!( - "https://affinescript.dev/errors/{}{}", - self.category.code_prefix(), - self.code - )) - .unwrap(), - }), - source: Some("affinescript".to_string()), - message, - related_information: if related.is_empty() { - None - } else { - Some(related) - }, - tags: None, - data: None, - } - } -} - -/// Convert compiler diagnostics to LSP diagnostics. -/// -/// Note: As of Phase A, the LSP parses JSON output from `affinescript check --json` -/// directly in `Backend::parse_json_diagnostics` (main.rs). This function and the -/// types above remain available for in-process compiler integration in the future. -pub fn convert_diagnostics(diagnostics: Vec) -> Vec { - diagnostics.iter().map(|d| d.to_lsp()).collect() -} - -// Phase B: Add diagnostic code description links (e.g. affinescript.dev/errors/E0101) -// Phase D: Add diagnostic tags (deprecated, unnecessary) and quickfix suggestions diff --git a/affinescript/tools/affinescript-lsp/src/document.rs b/affinescript/tools/affinescript-lsp/src/document.rs deleted file mode 100644 index 6c9c04a..0000000 --- a/affinescript/tools/affinescript-lsp/src/document.rs +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Document Management -//! -//! Tracks open documents and their state. - -use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use tower_lsp::lsp_types::*; - -/// Manages open documents -#[derive(Debug)] -pub struct DocumentManager { - /// Open documents by URI - documents: Arc>>, -} - -impl DocumentManager { - /// Create a new document manager - pub fn new() -> Self { - DocumentManager { - documents: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Open a document - pub fn open(&self, uri: Url, text: String, version: i32) { - let doc = Document::new(text, version); - self.documents.write().unwrap().insert(uri, doc); - } - - /// Close a document - pub fn close(&self, uri: &Url) { - self.documents.write().unwrap().remove(uri); - } - - /// Apply changes to a document - pub fn apply_changes(&self, uri: &Url, version: i32, changes: Vec) { - let mut docs = self.documents.write().unwrap(); - if let Some(doc) = docs.get_mut(uri) { - doc.apply_changes(version, changes); - } - } - - /// Get document text - pub fn get_text(&self, uri: &Url) -> Option { - self.documents.read().unwrap().get(uri).map(|d| d.text.clone()) - } - - /// Get document version - pub fn get_version(&self, uri: &Url) -> Option { - self.documents.read().unwrap().get(uri).map(|d| d.version) - } -} - -impl Default for DocumentManager { - fn default() -> Self { - Self::new() - } -} - -/// A single document -#[derive(Debug)] -pub struct Document { - /// Document text - pub text: String, - /// Document version - pub version: i32, - /// Line offsets (byte offset of each line start) - line_offsets: Vec, - // Phase B: Add parsed AST cache - // Phase B: Add type-checked state cache -} - -impl Document { - /// Create a new document - pub fn new(text: String, version: i32) -> Self { - let line_offsets = compute_line_offsets(&text); - Document { - text, - version, - line_offsets, - } - } - - /// Apply content changes - pub fn apply_changes(&mut self, version: i32, changes: Vec) { - self.version = version; - - for change in changes { - match change.range { - Some(range) => { - // Incremental change - let start_offset = self.offset_at(range.start); - let end_offset = self.offset_at(range.end); - self.text.replace_range(start_offset..end_offset, &change.text); - } - None => { - // Full document change - self.text = change.text; - } - } - } - - // Recompute line offsets - self.line_offsets = compute_line_offsets(&self.text); - } - - /// Get byte offset from position - pub fn offset_at(&self, pos: Position) -> usize { - let line = pos.line as usize; - if line >= self.line_offsets.len() { - return self.text.len(); - } - - let line_start = self.line_offsets[line]; - let line_end = if line + 1 < self.line_offsets.len() { - self.line_offsets[line + 1] - } else { - self.text.len() - }; - - let col = pos.character as usize; - let line_text = &self.text[line_start..line_end]; - - // Handle UTF-16 code units - let mut char_offset = 0; - let mut utf16_offset = 0; - - for c in line_text.chars() { - if utf16_offset >= col { - break; - } - char_offset += c.len_utf8(); - utf16_offset += c.len_utf16(); - } - - line_start + char_offset - } - - /// Get position from byte offset - pub fn position_at(&self, offset: usize) -> Position { - let offset = offset.min(self.text.len()); - - // Find line - let line = self - .line_offsets - .iter() - .position(|&o| o > offset) - .map(|l| l - 1) - .unwrap_or(self.line_offsets.len() - 1); - - let line_start = self.line_offsets[line]; - let line_text = &self.text[line_start..offset]; - - // Count UTF-16 code units - let character = line_text.chars().map(|c| c.len_utf16()).sum::(); - - Position { - line: line as u32, - character: character as u32, - } - } -} - -/// Compute byte offsets of line starts -fn compute_line_offsets(text: &str) -> Vec { - let mut offsets = vec![0]; - for (i, c) in text.char_indices() { - if c == '\n' { - offsets.push(i + 1); - } - } - offsets -} - -// Phase B: AST caching, incremental parsing, type information caching -// Phase C: Dependency tracking for cross-file references -// Phase D: Per-document diagnostic tracking for quickfix suggestions diff --git a/affinescript/tools/affinescript-lsp/src/handlers.rs b/affinescript/tools/affinescript-lsp/src/handlers.rs deleted file mode 100644 index 423ba30..0000000 --- a/affinescript/tools/affinescript-lsp/src/handlers.rs +++ /dev/null @@ -1,830 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Request Handlers -//! -//! Implementation of LSP request handlers for AffineScript. -//! -//! ## Phase C: go-to-definition, find-references, rename -//! -//! These features operate in two tiers: -//! -//! 1. **Compiler-backed** — When the AffineScript compiler emits v2 JSON with -//! `symbols` and `references` arrays, the handlers use `CompilerOutput` for -//! accurate, scope-aware results. -//! -//! 2. **Text-index fallback** — When compiler output is empty (e.g. the -//! compiler binary is older or unavailable), the handlers fall back to -//! `TextIndex`, which scans source text for definition keywords and -//! identifier occurrences. This is less precise (no scope resolution) -//! but still returns useful results for single-file editing. - -use std::collections::HashMap; -use tower_lsp::lsp_types::*; - -use crate::text_index::{self, TextIndex}; - -/// Handle hover request — Phase B enhanced. -/// -/// First checks the compiler's symbol table for type information. -/// Falls back to keyword descriptions for language constructs. -pub fn hover( - uri: &Url, - position: Position, - text: &str, - compiler_output: &crate::symbols::CompilerOutput, -) -> Option { - let word = get_word_at_position(text, position)?; - - // Phase B: try symbol table first for accurate type information. - if let Some(sym) = compiler_output.find_symbol_by_name(&word) { - return Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: sym.hover_text(), - }), - range: Some(Range { - start: position, - end: Position { - line: position.line, - character: position.character + utf16_len(&word), - }, - }), - }); - } - - // Fall back to keyword hover for language constructs. - let hover_text = match word.as_str() { - "fn" => "Defines a function", - "let" => "Binds a value to a variable", - "type" => "Defines a type alias", - "struct" => "Defines a structure type", - "enum" => "Defines an enumeration type", - "effect" => "Defines an effect type", - "handler" => "Defines an effect handler", - "linear" => "Linear type qualifier (must be used exactly once)", - "affine" => "Affine type qualifier (must be used at most once)", - "unrestricted" => "Unrestricted type qualifier (can be used any number of times)", - "borrow" => "Creates a temporary borrow of a value", - "move" => "Transfers ownership of a value", - _ => return None, - }; - - Some(Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: format!("**{}**\n\n{}", word, hover_text), - }), - range: Some(Range { - start: position, - end: Position { - line: position.line, - character: position.character + utf16_len(&word), - }, - }), - }) -} - -/// Handle goto definition — Phase B + Phase C fallback. -/// -/// Looks up the word at the cursor position in the compiler's symbol table. -/// If the compiler hasn't provided symbols (v1 JSON or unavailable), falls -/// back to the text-based index which scans for `fn`, `let`, `type`, etc. -pub fn goto_definition( - uri: &Url, - position: Position, - text: &str, - compiler_output: &crate::symbols::CompilerOutput, -) -> Option { - let word = get_word_at_position(text, position)?; - - // Tier 1: compiler-backed symbol table. - if let Some(sym) = compiler_output.find_symbol_by_name(&word) { - return sym.to_location(); - } - - // Tier 2: text-index fallback for single-file go-to-definition. - let index = TextIndex::build(text); - let def = index.find_definition(&word)?; - Some(Location { - uri: uri.clone(), - range: Range { - start: Position { line: def.line, character: def.col_start }, - end: Position { line: def.line, character: def.col_end }, - }, - }) -} - -/// Handle find references — Phase C. -/// -/// Finds the symbol at the cursor position, then returns all reference -/// locations. Uses the compiler's reference index when available, otherwise -/// falls back to text-index scanning. -pub fn find_references( - uri: &Url, - position: Position, - text: &str, - include_declaration: bool, - compiler_output: &crate::symbols::CompilerOutput, -) -> Vec { - let word = match get_word_at_position(text, position) { - Some(w) => w, - None => return vec![], - }; - - // Tier 1: compiler-backed reference index. - if let Some(sym) = compiler_output.find_symbol_by_name(&word) { - let refs = compiler_output.find_references(sym.id); - if !refs.is_empty() || compiler_output.symbols.len() > 0 { - let mut locations: Vec = refs - .into_iter() - .filter_map(|r| r.to_location()) - .collect(); - - if include_declaration { - if let Some(def_loc) = sym.to_location() { - locations.insert(0, def_loc); - } - } - - return locations; - } - } - - // Tier 2: text-index fallback. - let index = TextIndex::build(text); - if index.find_definition(&word).is_none() && index.find_occurrences(&word).is_empty() { - return vec![]; - } - - let occurrences = if include_declaration { - index.find_occurrences(&word) - } else { - index.find_usages_only(&word) - }; - - occurrences - .into_iter() - .map(|occ| Location { - uri: uri.clone(), - range: Range { - start: Position { line: occ.line, character: occ.col_start }, - end: Position { line: occ.line, character: occ.col_end }, - }, - }) - .collect() -} - -/// Handle completion -pub fn completion(_uri: &Url, position: Position, text: &str) -> Vec { - let mut items = Vec::new(); - - // Get current line to determine context - let line = match get_line_at_position(text, position.line) { - Some(l) => l, - None => return items, - }; - - let col = utf16_col_to_byte_offset(line, position.character as usize); - let prefix = if col > 0 && col <= line.len() { - &line[..col] - } else { - "" - }; - - // Keywords - let keywords = vec![ - ("fn", "Function definition", "fn ${1:name}(${2:args}) -> ${3:Type} {\n\t$0\n}"), - ("let", "Variable binding", "let ${1:name} = $0"), - ("type", "Type alias", "type ${1:Name} = $0"), - ("struct", "Structure type", "struct ${1:Name} {\n\t$0\n}"), - ("enum", "Enumeration type", "enum ${1:Name} {\n\t$0\n}"), - ("effect", "Effect type", "effect ${1:Name} {\n\t$0\n}"), - ("handler", "Effect handler", "handler ${1:name} {\n\t$0\n}"), - ("match", "Pattern matching", "match ${1:expr} {\n\t$0\n}"), - ("if", "Conditional", "if ${1:condition} {\n\t$0\n}"), - ("else", "Else clause", "else {\n\t$0\n}"), - ("for", "For loop", "for ${1:var} in ${2:expr} {\n\t$0\n}"), - ("while", "While loop", "while ${1:condition} {\n\t$0\n}"), - ("return", "Return statement", "return $0"), - ("linear", "Linear type qualifier", "linear $0"), - ("affine", "Affine type qualifier", "affine $0"), - ("unrestricted", "Unrestricted type qualifier", "unrestricted $0"), - ("borrow", "Borrow expression", "borrow $0"), - ("move", "Move expression", "move $0"), - ]; - - for (keyword, detail, snippet) in keywords { - if keyword.starts_with(&prefix.trim_start()) || prefix.trim().is_empty() { - items.push(CompletionItem { - label: keyword.to_string(), - kind: Some(CompletionItemKind::KEYWORD), - detail: Some(detail.to_string()), - insert_text: Some(snippet.to_string()), - insert_text_format: Some(InsertTextFormat::SNIPPET), - ..Default::default() - }); - } - } - - // Standard library types - let std_types = vec![ - ("Int", "Integer type"), - ("Float", "Floating-point type"), - ("Bool", "Boolean type"), - ("String", "String type"), - ("Unit", "Unit type"), - ("List", "List type"), - ("Option", "Optional type"), - ("Result", "Result type"), - ]; - - for (type_name, detail) in std_types { - items.push(CompletionItem { - label: type_name.to_string(), - kind: Some(CompletionItemKind::CLASS), - detail: Some(detail.to_string()), - ..Default::default() - }); - } - - items -} - -/// Handle prepare-rename — Phase C. -/// -/// Checks whether the symbol at the cursor position is renameable (i.e. it is -/// a user-defined identifier, not a keyword or builtin type). Returns the -/// range of the symbol name so the editor can highlight it in the rename UI. -/// -/// Uses the compiler symbol table when available, otherwise the text index. -pub fn prepare_rename( - uri: &Url, - position: Position, - text: &str, - compiler_output: &crate::symbols::CompilerOutput, -) -> Option { - let word = get_word_at_position(text, position)?; - - // Keywords and builtins are not renameable. - if !text_index::is_renameable(&word) { - return None; - } - - // Tier 1: check compiler symbol table. - if let Some(sym) = compiler_output.find_symbol_by_name(&word) { - // The symbol exists in the compiler output — it's renameable. - let range = Range { - start: Position { - line: sym.start_line.saturating_sub(1), - character: sym.start_col.saturating_sub(1), - }, - end: Position { - line: sym.end_line.saturating_sub(1), - character: sym.end_col.saturating_sub(1), - }, - }; - return Some(PrepareRenameResponse::Range(range)); - } - - // Tier 2: text-index fallback — find the identifier at the cursor. - let index = TextIndex::build(text); - let occ = index.find_at_position(position.line, position.character)?; - - // Only allow rename if this name has a definition in the file. - // (Otherwise we'd be renaming something we can't find the source of.) - if index.find_definition(&occ.name).is_some() || index.find_occurrences(&occ.name).len() > 1 { - Some(PrepareRenameResponse::Range(Range { - start: Position { line: occ.line, character: occ.col_start }, - end: Position { line: occ.line, character: occ.col_end }, - })) - } else { - None - } -} - -/// Handle rename — Phase C. -/// -/// Finds all occurrences of the symbol at the cursor position and produces -/// a `WorkspaceEdit` that replaces every occurrence with the new name. -/// -/// When the compiler provides v2 JSON output with cross-file references, -/// the rename can span multiple files. In text-index fallback mode, the -/// rename is limited to the current document. -pub fn rename( - uri: &Url, - position: Position, - new_name: &str, - text: &str, - compiler_output: &crate::symbols::CompilerOutput, -) -> Option { - let word = get_word_at_position(text, position)?; - - // Validate the new name is a legal identifier. - if !text_index::is_valid_identifier(new_name) { - return None; - } - - // Cannot rename keywords or builtins. - if !text_index::is_renameable(&word) { - return None; - } - - // Tier 1: compiler-backed rename (supports cross-file). - if let Some(sym) = compiler_output.find_symbol_by_name(&word) { - let refs = compiler_output.find_references(sym.id); - if !refs.is_empty() || compiler_output.symbols.len() > 0 { - let mut changes: HashMap> = HashMap::new(); - - // Add the definition site. - if let Some(def_loc) = sym.to_location() { - changes - .entry(def_loc.uri.clone()) - .or_default() - .push(TextEdit { - range: def_loc.range, - new_text: new_name.to_string(), - }); - } - - // Add all reference sites. - for r in &refs { - if let Some(ref_loc) = r.to_location() { - changes - .entry(ref_loc.uri.clone()) - .or_default() - .push(TextEdit { - range: ref_loc.range, - new_text: new_name.to_string(), - }); - } - } - - return Some(WorkspaceEdit { - changes: Some(changes), - document_changes: None, - change_annotations: None, - }); - } - } - - // Tier 2: text-index fallback (single file only). - let index = TextIndex::build(text); - let occurrences = index.find_occurrences(&word); - if occurrences.is_empty() { - return None; - } - - let edits: Vec = occurrences - .into_iter() - .map(|occ| TextEdit { - range: Range { - start: Position { line: occ.line, character: occ.col_start }, - end: Position { line: occ.line, character: occ.col_end }, - }, - new_text: new_name.to_string(), - }) - .collect(); - - let mut changes = HashMap::new(); - changes.insert(uri.clone(), edits); - - Some(WorkspaceEdit { - changes: Some(changes), - document_changes: None, - change_annotations: None, - }) -} - -/// Handle document formatting -pub fn format(uri: &Url, text: &str, options: &FormattingOptions) -> Vec { - // Basic indentation-based formatting - let lines: Vec<&str> = text.lines().collect(); - let mut formatted_lines = Vec::new(); - let mut indent_level: usize = 0; - let indent_str = if options.insert_spaces { - " ".repeat(options.tab_size as usize) - } else { - "\t".to_string() - }; - - let line_count = lines.len(); - for line in lines { - let trimmed = line.trim(); - - // Decrease indent for closing braces - if trimmed.starts_with('}') || trimmed.starts_with(']') || trimmed.starts_with(')') { - indent_level = indent_level.saturating_sub(1); - } - - // Add indented line - if !trimmed.is_empty() { - formatted_lines.push(format!("{}{}", indent_str.repeat(indent_level), trimmed)); - } else { - formatted_lines.push(String::new()); - } - - // Increase indent for opening braces - if trimmed.ends_with('{') || trimmed.ends_with('[') || trimmed.ends_with('(') { - indent_level += 1; - } - } - - let formatted_text = formatted_lines.join("\n"); - - if formatted_text == text { - return vec![]; - } - - vec![TextEdit { - range: Range { - start: Position { line: 0, character: 0 }, - end: Position { - line: line_count as u32, - character: 0, - }, - }, - new_text: formatted_text, - }] -} - -/// Handle code actions -pub fn code_actions(_uri: &Url, _range: Range, _diagnostics: &[Diagnostic]) -> Vec { - // Phase D: requires fix suggestions in --json output - vec![] -} - -/// Handle document symbols -pub fn document_symbols(_uri: &Url, text: &str) -> Vec { - let mut symbols = Vec::new(); - let lines: Vec<&str> = text.lines().collect(); - - for (line_num, line) in lines.iter().enumerate() { - let trimmed = line.trim_start(); - // Byte offset of where trimmed content starts within the original line - let leading_bytes = line.len() - trimmed.len(); - let leading_utf16 = utf16_len(&line[..leading_bytes]); - - // Match function definitions - if trimmed.starts_with("fn ") { - if let Some(name_start) = trimmed.find("fn ").map(|i| i + 3) { - if let Some(name_end) = trimmed[name_start..].find(|c: char| c == '(' || c.is_whitespace()) { - let name = &trimmed[name_start..name_start + name_end]; - let name_start_utf16 = leading_utf16 + utf16_len(&trimmed[..name_start]); - let name_end_utf16 = name_start_utf16 + utf16_len(name); - symbols.push(DocumentSymbol { - name: name.to_string(), - detail: Some(trimmed.to_string()), - kind: SymbolKind::FUNCTION, - range: Range { - start: Position { line: line_num as u32, character: 0 }, - end: Position { line: line_num as u32, character: utf16_len(line) }, - }, - selection_range: Range { - start: Position { line: line_num as u32, character: name_start_utf16 }, - end: Position { line: line_num as u32, character: name_end_utf16 }, - }, - children: None, - tags: None, - deprecated: None, - }); - } - } - } - - // Match type definitions - if trimmed.starts_with("type ") || trimmed.starts_with("struct ") || trimmed.starts_with("enum ") { - let keyword_len = if trimmed.starts_with("type ") { 5 } else if trimmed.starts_with("struct ") { 7 } else { 5 }; - if let Some(name_end) = trimmed[keyword_len..].find(|c: char| c == '=' || c == '{' || c.is_whitespace()) { - let name = trimmed[keyword_len..keyword_len + name_end].trim(); - let name_start_utf16 = leading_utf16 + utf16_len(&trimmed[..keyword_len]); - let name_end_utf16 = name_start_utf16 + utf16_len(name); - symbols.push(DocumentSymbol { - name: name.to_string(), - detail: Some(trimmed.to_string()), - kind: SymbolKind::STRUCT, - range: Range { - start: Position { line: line_num as u32, character: 0 }, - end: Position { line: line_num as u32, character: utf16_len(line) }, - }, - selection_range: Range { - start: Position { line: line_num as u32, character: name_start_utf16 }, - end: Position { line: line_num as u32, character: name_end_utf16 }, - }, - children: None, - tags: None, - deprecated: None, - }); - } - } - } - - symbols -} - -/// Handle signature help -pub fn signature_help(_uri: &Url, _position: Position, _text: &str) -> Option { - // Phase B: requires function signature data in --json output - None -} - -/// Handle inlay hints -pub fn inlay_hints(_uri: &Url, _range: Range, _text: &str) -> Vec { - // Phase D: requires inferred type data in --json output - vec![] -} - -/// Convert a UTF-16 column offset to a byte offset within a line. -/// LSP positions use UTF-16 code units; Rust strings are UTF-8. -fn utf16_col_to_byte_offset(line: &str, utf16_col: usize) -> usize { - let mut utf16_offset = 0; - let mut byte_offset = 0; - for c in line.chars() { - if utf16_offset >= utf16_col { - break; - } - utf16_offset += c.len_utf16(); - byte_offset += c.len_utf8(); - } - byte_offset -} - -/// Count UTF-16 code units in a string (for converting byte lengths to LSP positions). -fn utf16_len(s: &str) -> u32 { - s.chars().map(|c| c.len_utf16()).sum::() as u32 -} - -/// Get word at position in text -fn get_word_at_position(text: &str, position: Position) -> Option { - let lines: Vec<&str> = text.lines().collect(); - if position.line as usize >= lines.len() { - return None; - } - - let line = lines[position.line as usize]; - let col = utf16_col_to_byte_offset(line, position.character as usize); - - if col >= line.len() { - return None; - } - - // Find word boundaries (byte offsets within line) - let start = line[..col] - .rfind(|c: char| !c.is_alphanumeric() && c != '_') - .map(|i| i + 1) - .unwrap_or(0); - - let end = line[col..] - .find(|c: char| !c.is_alphanumeric() && c != '_') - .map(|i| col + i) - .unwrap_or(line.len()); - - if start >= end { - return None; - } - - Some(line[start..end].to_string()) -} - -/// Get line at position -fn get_line_at_position(text: &str, line_num: u32) -> Option<&str> { - text.lines().nth(line_num as usize) -} - -// Phase B: semantic tokens, call hierarchy, type hierarchy (requires compiler integration) -// Phase D: caching for performance (once --json output includes enough data) - -// --------------------------------------------------------------------------- -// Tests — Phase C handler integration tests -// --------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - use crate::symbols::CompilerOutput; - - /// Helper: create a file URI from a dummy path. - fn test_uri() -> Url { - Url::parse("file:///tmp/test.affine").unwrap() - } - - /// Helper: empty compiler output (simulates no v2 JSON available). - fn empty_co() -> CompilerOutput { - CompilerOutput::default() - } - - // -- goto_definition (text-index fallback) -- - - #[test] - fn goto_definition_finds_function() { - let src = "fn greet(name: String) -> Unit {}\nlet x = greet(\"hi\")"; - let uri = test_uri(); - let co = empty_co(); - // Position on "greet" in the second line (col 8 = "g" of "greet"). - let pos = Position { line: 1, character: 8 }; - let result = goto_definition(&uri, pos, src, &co); - assert!(result.is_some(), "goto_definition should find 'greet'"); - let loc = result.unwrap(); - assert_eq!(loc.range.start.line, 0, "definition should be on line 0"); - assert_eq!(loc.range.start.character, 3, "definition should start at col 3 (after 'fn ')"); - } - - #[test] - fn goto_definition_finds_variable() { - let src = "let counter = 0\nlet y = counter + 1"; - let uri = test_uri(); - let co = empty_co(); - // Position on "counter" in the second line. - let pos = Position { line: 1, character: 8 }; - let result = goto_definition(&uri, pos, src, &co); - assert!(result.is_some(), "goto_definition should find 'counter'"); - let loc = result.unwrap(); - assert_eq!(loc.range.start.line, 0); - } - - #[test] - fn goto_definition_returns_none_for_keyword() { - let src = "fn greet() -> Unit {}"; - let uri = test_uri(); - let co = empty_co(); - // Position on "fn" keyword. - let pos = Position { line: 0, character: 0 }; - let result = goto_definition(&uri, pos, src, &co); - // "fn" is a keyword — no user-defined definition for it. - // The text index won't have a definition named "fn" in its definitions list - // (definitions extract the name AFTER the keyword). - assert!(result.is_none()); - } - - // -- find_references (text-index fallback) -- - - #[test] - fn find_references_returns_all_occurrences() { - let src = "let x = 1\nlet y = x + 2\nlet z = x * 3"; - let uri = test_uri(); - let co = empty_co(); - // Position on "x" in the first line. - let pos = Position { line: 0, character: 4 }; - let refs = find_references(&uri, pos, src, true, &co); - // x appears 3 times: definition + 2 usages. - assert_eq!(refs.len(), 3, "should find 3 references to 'x'"); - } - - #[test] - fn find_references_excludes_declaration() { - let src = "let x = 1\nlet y = x + 2\nlet z = x * 3"; - let uri = test_uri(); - let co = empty_co(); - let pos = Position { line: 0, character: 4 }; - let refs = find_references(&uri, pos, src, false, &co); - // x usages only (not the definition). - assert_eq!(refs.len(), 2, "should find 2 usage references to 'x'"); - } - - // -- prepare_rename -- - - #[test] - fn prepare_rename_accepts_user_symbol() { - let src = "fn greet() -> Unit {}"; - let uri = test_uri(); - let co = empty_co(); - // Position on "greet". - let pos = Position { line: 0, character: 4 }; - let result = prepare_rename(&uri, pos, src, &co); - assert!(result.is_some(), "should allow renaming 'greet'"); - } - - #[test] - fn prepare_rename_rejects_keyword() { - let src = "fn greet() -> Unit {}"; - let uri = test_uri(); - let co = empty_co(); - // Position on "fn". - let pos = Position { line: 0, character: 0 }; - let result = prepare_rename(&uri, pos, src, &co); - assert!(result.is_none(), "should not allow renaming 'fn'"); - } - - // -- rename -- - - #[test] - fn rename_replaces_all_occurrences() { - let src = "let x = 1\nlet y = x + 2\nlet z = x * 3"; - let uri = test_uri(); - let co = empty_co(); - let pos = Position { line: 0, character: 4 }; - let result = rename(&uri, pos, "count", src, &co); - assert!(result.is_some(), "rename should produce a WorkspaceEdit"); - let edit = result.unwrap(); - let changes = edit.changes.unwrap(); - let edits = changes.get(&uri).expect("should have edits for the test URI"); - assert_eq!(edits.len(), 3, "should replace all 3 occurrences of 'x'"); - for e in edits { - assert_eq!(e.new_text, "count"); - } - } - - #[test] - fn rename_rejects_invalid_new_name() { - let src = "let x = 1"; - let uri = test_uri(); - let co = empty_co(); - let pos = Position { line: 0, character: 4 }; - // "123" is not a valid identifier. - let result = rename(&uri, pos, "123", src, &co); - assert!(result.is_none(), "should reject invalid identifier '123'"); - } - - #[test] - fn rename_rejects_keyword_as_new_name() { - let src = "let x = 1"; - let uri = test_uri(); - let co = empty_co(); - let pos = Position { line: 0, character: 4 }; - // "fn" is a keyword. - let result = rename(&uri, pos, "fn", src, &co); - assert!(result.is_none(), "should reject keyword 'fn' as new name"); - } - - // -- compiler-backed tests (v2 JSON symbols) -- - - #[test] - fn goto_definition_uses_compiler_output_when_available() { - let json_str = r#"{ - "version": 2, - "diagnostics": [], - "success": true, - "symbols": [ - { - "id": 1, - "name": "add", - "kind": "function", - "file": "/tmp/test.affine", - "start_line": 1, - "start_col": 4, - "end_line": 1, - "end_col": 7, - "type": "(Int, Int) -> Int" - } - ], - "references": { - "1": [ - {"file": "/tmp/test.affine", "start_line": 5, "start_col": 10, "end_line": 5, "end_col": 13} - ] - } - }"#; - let json: serde_json::Value = serde_json::from_str(json_str).unwrap(); - let co = crate::symbols::CompilerOutput::from_json(&json); - - let src = "fn add(a: Int, b: Int) -> Int { a + b }\n\nlet result = add(1, 2)"; - let uri = test_uri(); - // Position on "add" in the usage site. - let pos = Position { line: 2, character: 14 }; - let result = goto_definition(&uri, pos, src, &co); - assert!(result.is_some(), "should find compiler-backed definition"); - let loc = result.unwrap(); - // Compiler reports 1-based line/col; to_location converts to 0-based. - assert_eq!(loc.range.start.line, 0); - assert_eq!(loc.range.start.character, 3); - } - - #[test] - fn find_references_uses_compiler_output_when_available() { - let json_str = r#"{ - "version": 2, - "diagnostics": [], - "success": true, - "symbols": [ - { - "id": 1, - "name": "add", - "kind": "function", - "file": "/tmp/test.affine", - "start_line": 1, - "start_col": 4, - "end_line": 1, - "end_col": 7 - } - ], - "references": { - "1": [ - {"file": "/tmp/test.affine", "start_line": 3, "start_col": 10, "end_line": 3, "end_col": 13}, - {"file": "/tmp/test.affine", "start_line": 5, "start_col": 1, "end_line": 5, "end_col": 4} - ] - } - }"#; - let json: serde_json::Value = serde_json::from_str(json_str).unwrap(); - let co = crate::symbols::CompilerOutput::from_json(&json); - - let src = "fn add(a: Int, b: Int) -> Int { a + b }"; - let uri = test_uri(); - let pos = Position { line: 0, character: 4 }; - - // With declaration included. - let refs = find_references(&uri, pos, src, true, &co); - assert_eq!(refs.len(), 3, "2 refs + 1 declaration = 3"); - - // Without declaration. - let refs = find_references(&uri, pos, src, false, &co); - assert_eq!(refs.len(), 2, "2 refs only"); - } -} diff --git a/affinescript/tools/affinescript-lsp/src/main.rs b/affinescript/tools/affinescript-lsp/src/main.rs deleted file mode 100644 index 714a84f..0000000 --- a/affinescript/tools/affinescript-lsp/src/main.rs +++ /dev/null @@ -1,517 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -#![forbid(unsafe_code)] - -//! AffineScript Language Server -//! -//! Provides IDE features via the Language Server Protocol: -//! - Diagnostics (errors, warnings) — via `affinescript check --json` -//! - Hover information -//! - Go to definition (Phase B) -//! - Find references (Phase C) -//! - Code completion -//! - Rename (Phase C) -//! - Formatting -//! - Code actions (Phase D) -//! -//! ## Phase A: --json contract -//! -//! The LSP invokes the compiler with `--json` and receives a single JSON -//! object on stderr matching this schema: -//! -//! ```json -//! { -//! "version": 1, -//! "diagnostics": [ { "severity", "code", "message", "file", -//! "start_line", "start_col", "end_line", "end_col", -//! "help", "labels" } ], -//! "success": true | false -//! } -//! ``` -//! -//! This replaces the fragile regex parsing from the pre-Phase-A implementation -//! and is the foundation for Phases B-D. - -use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::*; -use tower_lsp::{Client, LanguageServer, LspService, Server}; - -mod capabilities; -mod diagnostics; -mod document; -mod handlers; -pub mod symbols; -pub mod text_index; - -/// The AffineScript language server backend -#[derive(Debug)] -struct Backend { - /// LSP client for sending notifications - client: Client, - /// Document manager - documents: document::DocumentManager, - /// Phase B: cached compiler symbol output (updated on each check). - compiler_output: std::sync::Mutex, -} - -impl Backend { - fn new(client: Client) -> Self { - Backend { - client, - documents: document::DocumentManager::new(), - compiler_output: std::sync::Mutex::new(symbols::CompilerOutput::default()), - } - } - - /// Check a document by calling `affinescript check --json`. - /// - /// Parses the structured JSON output instead of regex-matching stderr - /// text. Falls back to an internal error diagnostic if the compiler - /// is not found or returns unparseable output. - async fn check_document(&self, uri: &Url, text: &str) -> Vec { - use tokio::process::Command; - use std::process::Stdio; - - // Write source to a temp file so the compiler can read it - let temp_path = std::env::temp_dir().join(format!("lsp_{}.affine", uuid::Uuid::new_v4())); - if let Err(e) = tokio::fs::write(&temp_path, text).await { - self.client - .log_message(MessageType::ERROR, format!("Failed to write temp file: {}", e)) - .await; - return vec![]; - } - - // Run `affinescript check --json ` - let output = match Command::new("affinescript") - .arg("check") - .arg("--json") - .arg(&temp_path) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .output() - .await - { - Ok(o) => o, - Err(e) => { - self.client - .log_message( - MessageType::ERROR, - format!("Failed to run affinescript: {}", e), - ) - .await; - let _ = tokio::fs::remove_file(&temp_path).await; - return vec![]; - } - }; - - // Clean up temp file - let _ = tokio::fs::remove_file(&temp_path).await; - - // Parse the JSON diagnostic report from stderr - let stderr = String::from_utf8_lossy(&output.stderr); - self.parse_json_diagnostics(&stderr, uri) - } - - /// Parse the compiler's `--json` diagnostic report into LSP diagnostics. - /// - /// Expected shape (version 1): - /// ```json - /// { - /// "version": 1, - /// "diagnostics": [ ... ], - /// "success": bool - /// } - /// ``` - fn parse_json_diagnostics(&self, stderr: &str, _uri: &Url) -> Vec { - let json: serde_json::Value = match serde_json::from_str(stderr.trim()) { - Ok(v) => v, - Err(_) => { - // If the compiler didn't produce valid JSON (e.g. crash, old - // binary without --json), fall back to the legacy regex parser - // so the LSP degrades gracefully. - return self.parse_diagnostics_legacy(stderr, _uri); - } - }; - - let version = json.get("version").and_then(|v| v.as_i64()).unwrap_or(0); - if version < 1 || version > 2 { - // Unknown protocol version — fall back to legacy - return self.parse_diagnostics_legacy(stderr, _uri); - } - - // Phase B: v2 includes symbols and references for goto-def. - if version >= 2 { - let output = symbols::CompilerOutput::from_json(&json); - if let Ok(mut cached) = self.compiler_output.lock() { - *cached = output; - } - } - - let diags_array = match json.get("diagnostics").and_then(|d| d.as_array()) { - Some(a) => a, - None => return vec![], - }; - - let mut diagnostics = Vec::with_capacity(diags_array.len()); - - for entry in diags_array { - let severity_str = entry.get("severity").and_then(|s| s.as_str()).unwrap_or("error"); - let code = entry.get("code").and_then(|c| c.as_str()).unwrap_or("E0000"); - let message = entry.get("message").and_then(|m| m.as_str()).unwrap_or(""); - let start_line = entry.get("start_line").and_then(|l| l.as_u64()).unwrap_or(1) as u32; - let start_col = entry.get("start_col").and_then(|c| c.as_u64()).unwrap_or(1) as u32; - let end_line = entry.get("end_line").and_then(|l| l.as_u64()).unwrap_or(start_line as u64) as u32; - let end_col = entry.get("end_col").and_then(|c| c.as_u64()).unwrap_or(start_col as u64 + 1) as u32; - let help = entry.get("help").and_then(|h| h.as_str()); - - let severity = match severity_str { - "error" => DiagnosticSeverity::ERROR, - "warning" => DiagnosticSeverity::WARNING, - "hint" => DiagnosticSeverity::HINT, - "info" | "note" => DiagnosticSeverity::INFORMATION, - _ => DiagnosticSeverity::WARNING, - }; - - // Build related information from labels array - let mut related = Vec::new(); - if let Some(labels) = entry.get("labels").and_then(|l| l.as_array()) { - for label in labels { - let lbl_file = label.get("file").and_then(|f| f.as_str()).unwrap_or(""); - let lbl_start_line = label.get("start_line").and_then(|l| l.as_u64()).unwrap_or(1) as u32; - let lbl_start_col = label.get("start_col").and_then(|c| c.as_u64()).unwrap_or(1) as u32; - let lbl_end_line = label.get("end_line").and_then(|l| l.as_u64()).unwrap_or(lbl_start_line as u64) as u32; - let lbl_end_col = label.get("end_col").and_then(|c| c.as_u64()).unwrap_or(lbl_start_col as u64 + 1) as u32; - let lbl_msg = label.get("message").and_then(|m| m.as_str()).unwrap_or(""); - - if let Ok(lbl_uri) = Url::from_file_path(lbl_file) { - related.push(DiagnosticRelatedInformation { - location: Location { - uri: lbl_uri, - range: Range { - start: Position { - line: lbl_start_line.saturating_sub(1), - character: lbl_start_col.saturating_sub(1), - }, - end: Position { - line: lbl_end_line.saturating_sub(1), - character: lbl_end_col.saturating_sub(1), - }, - }, - }, - message: lbl_msg.to_string(), - }); - } - } - } - - // Append help text to message if present - let full_message = match help { - Some(h) => format!("{}\n\nhelp: {}", message, h), - None => message.to_string(), - }; - - diagnostics.push(Diagnostic { - range: Range { - start: Position { - line: start_line.saturating_sub(1), - character: start_col.saturating_sub(1), - }, - end: Position { - line: end_line.saturating_sub(1), - character: end_col.saturating_sub(1), - }, - }, - severity: Some(severity), - code: Some(NumberOrString::String(code.to_string())), - source: Some("affinescript".to_string()), - message: full_message, - related_information: if related.is_empty() { None } else { Some(related) }, - tags: None, - code_description: None, - data: None, - }); - } - - diagnostics - } - - /// Legacy regex-based parser — kept as a fallback for when the compiler - /// binary predates `--json` support. - fn parse_diagnostics_legacy(&self, output: &str, uri: &Url) -> Vec { - let mut diagnostics = Vec::new(); - - for line in output.lines() { - if let Some(diagnostic) = self.parse_diagnostic_line(line, uri) { - diagnostics.push(diagnostic); - } - } - - diagnostics - } - - /// Parse a single diagnostic line (legacy format) - fn parse_diagnostic_line(&self, line: &str, _uri: &Url) -> Option { - use regex::Regex; - - let re = Regex::new( - r"(.+):(\d+):(\d+):\s*(error|warning|hint|info|note)\s*(?:\[([A-Z]\d+)\])?:\s*(.+)" - ).ok()?; - let caps = re.captures(line)?; - - let line = caps.get(2)?.as_str().parse::().ok()?.saturating_sub(1); - let col = caps.get(3)?.as_str().parse::().ok()?.saturating_sub(1); - let severity_str = caps.get(4)?.as_str(); - let error_code = caps.get(5).map(|m| m.as_str().to_string()); - let message = caps.get(6)?.as_str(); - - let severity = match severity_str { - "error" => DiagnosticSeverity::ERROR, - "warning" => DiagnosticSeverity::WARNING, - "hint" => DiagnosticSeverity::HINT, - "info" | "note" => DiagnosticSeverity::INFORMATION, - _ => DiagnosticSeverity::WARNING, - }; - - Some(Diagnostic { - range: Range { - start: Position { - line, - character: col, - }, - end: Position { - line, - character: col + 1, - }, - }, - severity: Some(severity), - code: error_code.map(NumberOrString::String), - source: Some("affinescript".to_string()), - message: message.to_string(), - related_information: None, - tags: None, - code_description: None, - data: None, - }) - } -} - -#[tower_lsp::async_trait] -impl LanguageServer for Backend { - async fn initialize(&self, _: InitializeParams) -> Result { - Ok(InitializeResult { - capabilities: capabilities::server_capabilities(), - server_info: Some(ServerInfo { - name: "affinescript-lsp".to_string(), - version: Some(env!("CARGO_PKG_VERSION").to_string()), - }), - }) - } - - async fn initialized(&self, _: InitializedParams) { - self.client - .log_message(MessageType::INFO, "AffineScript LSP initialized") - .await; - } - - async fn shutdown(&self) -> Result<()> { - Ok(()) - } - - async fn did_open(&self, params: DidOpenTextDocumentParams) { - self.client - .log_message(MessageType::INFO, "Document opened") - .await; - - let uri = params.text_document.uri; - let text = params.text_document.text; - let version = params.text_document.version; - - // Store the document so handlers can access its text. - self.documents.open(uri.clone(), text.clone(), version); - - // Run type checker via --json - let diagnostics = self.check_document(&uri, &text).await; - - // Publish diagnostics - self.client.publish_diagnostics(uri, diagnostics, None).await; - } - - async fn did_change(&self, params: DidChangeTextDocumentParams) { - let uri = params.text_document.uri; - let version = params.text_document.version; - - // Apply incremental edits to the stored document. - self.documents.apply_changes(&uri, version, params.content_changes.clone()); - - // Get the latest text from the document manager. - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => { - // Fallback: use the last change's full text if available. - match params.content_changes.last() { - Some(change) => change.text.clone(), - None => return, - } - } - }; - - // Run type checker via --json - let diagnostics = self.check_document(&uri, &text).await; - - // Publish diagnostics - self.client.publish_diagnostics(uri, diagnostics, None).await; - } - - async fn did_close(&self, params: DidCloseTextDocumentParams) { - let uri = params.text_document.uri; - - // Remove document from manager - self.documents.close(&uri); - - // Clear diagnostics - self.client.publish_diagnostics(uri, vec![], None).await; - } - - async fn hover(&self, params: HoverParams) -> Result> { - let uri = params.text_document_position_params.text_document.uri; - let position = params.text_document_position_params.position; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let co = self.compiler_output.lock().unwrap(); - Ok(handlers::hover(&uri, position, &text, &co)) - } - - async fn goto_definition( - &self, - params: GotoDefinitionParams, - ) -> Result> { - let uri = params.text_document_position_params.text_document.uri; - let position = params.text_document_position_params.position; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let co = self.compiler_output.lock().unwrap(); - match handlers::goto_definition(&uri, position, &text, &co) { - Some(loc) => Ok(Some(GotoDefinitionResponse::Scalar(loc))), - None => Ok(None), - } - } - - async fn references(&self, params: ReferenceParams) -> Result>> { - let uri = params.text_document_position.text_document.uri; - let position = params.text_document_position.position; - let include_declaration = params.context.include_declaration; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let co = self.compiler_output.lock().unwrap(); - let refs = handlers::find_references(&uri, position, &text, include_declaration, &co); - if refs.is_empty() { - Ok(None) - } else { - Ok(Some(refs)) - } - } - - async fn completion(&self, params: CompletionParams) -> Result> { - let uri = params.text_document_position.text_document.uri; - let position = params.text_document_position.position; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let items = handlers::completion(&uri, position, &text); - if items.is_empty() { - Ok(None) - } else { - Ok(Some(CompletionResponse::Array(items))) - } - } - - async fn prepare_rename( - &self, - params: TextDocumentPositionParams, - ) -> Result> { - let uri = params.text_document.uri; - let position = params.position; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let co = self.compiler_output.lock().unwrap(); - Ok(handlers::prepare_rename(&uri, position, &text, &co)) - } - - async fn rename(&self, params: RenameParams) -> Result> { - let uri = params.text_document_position.text_document.uri; - let position = params.text_document_position.position; - let new_name = params.new_name; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let co = self.compiler_output.lock().unwrap(); - Ok(handlers::rename(&uri, position, &new_name, &text, &co)) - } - - async fn formatting(&self, params: DocumentFormattingParams) -> Result>> { - let uri = params.text_document.uri; - - let text = match self.documents.get_text(&uri) { - Some(t) => t, - None => return Ok(None), - }; - - let edits = handlers::format(&uri, &text, ¶ms.options); - if edits.is_empty() { - Ok(None) - } else { - Ok(Some(edits)) - } - } - - async fn code_action(&self, params: CodeActionParams) -> Result> { - // TODO(#84): Phase D — requires structured fix suggestions in --json output - let _ = params; - Ok(None) - } -} - -#[tokio::main] -async fn main() { - // Initialize logging - tracing_subscriber::fmt() - .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env() - .add_directive(tracing::Level::INFO.into()), - ) - .init(); - - tracing::info!("Starting AffineScript Language Server (Phase A: --json diagnostics)"); - - let stdin = tokio::io::stdin(); - let stdout = tokio::io::stdout(); - - let (service, socket) = LspService::new(Backend::new); - Server::new(stdin, stdout, socket).serve(service).await; -} - -// Phase B: Symbol table in --json output → go-to-definition, hover with types -// Phase C: Reference index in --json output → find-references, rename -// Phase D: Fix suggestions in --json output → code actions, inlay hints diff --git a/affinescript/tools/affinescript-lsp/src/symbols.rs b/affinescript/tools/affinescript-lsp/src/symbols.rs deleted file mode 100644 index c2daf8d..0000000 --- a/affinescript/tools/affinescript-lsp/src/symbols.rs +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Phase B: Symbol Table and References -//! -//! Parses the v2 `--json` contract's `symbols` and `references` fields. -//! Enables goto-definition, find-references, and rename. - -use tower_lsp::lsp_types::*; - -/// A symbol from the compiler's symbol table. -#[derive(Debug, Clone)] -pub struct CompilerSymbol { - pub id: i64, - pub name: String, - pub kind: String, - pub file: String, - pub start_line: u32, - pub start_col: u32, - pub end_line: u32, - pub end_col: u32, - pub type_info: Option, - pub quantity: Option, -} - -impl CompilerSymbol { - /// Convert the definition location to an LSP Location. - pub fn to_location(&self) -> Option { - let uri = Url::from_file_path(&self.file).ok()?; - Some(Location { - uri, - range: Range { - start: Position { - line: self.start_line.saturating_sub(1), - character: self.start_col.saturating_sub(1), - }, - end: Position { - line: self.end_line.saturating_sub(1), - character: self.end_col.saturating_sub(1), - }, - }, - }) - } - - /// Convert to LSP SymbolKind. - pub fn to_symbol_kind(&self) -> SymbolKind { - match self.kind.as_str() { - "function" => SymbolKind::FUNCTION, - "variable" => SymbolKind::VARIABLE, - "type" | "type_variable" => SymbolKind::TYPE_PARAMETER, - "effect" | "effect_operation" => SymbolKind::EVENT, - "trait" => SymbolKind::INTERFACE, - "module" => SymbolKind::MODULE, - "constructor" => SymbolKind::ENUM_MEMBER, - _ => SymbolKind::VARIABLE, - } - } - - /// Format hover information for this symbol. - pub fn hover_text(&self) -> String { - let mut parts = vec![format!("**{}** ({})", self.name, self.kind)]; - if let Some(ty) = &self.type_info { - parts.push(format!("Type: `{}`", ty)); - } - if let Some(q) = &self.quantity { - parts.push(format!("Quantity: {}", q)); - } - parts.join("\n\n") - } -} - -/// A reference (use-site) for a symbol. -#[derive(Debug, Clone)] -pub struct CompilerReference { - pub symbol_id: i64, - pub file: String, - pub start_line: u32, - pub start_col: u32, - pub end_line: u32, - pub end_col: u32, -} - -impl CompilerReference { - /// Convert to LSP Location. - pub fn to_location(&self) -> Option { - let uri = Url::from_file_path(&self.file).ok()?; - Some(Location { - uri, - range: Range { - start: Position { - line: self.start_line.saturating_sub(1), - character: self.start_col.saturating_sub(1), - }, - end: Position { - line: self.end_line.saturating_sub(1), - character: self.end_col.saturating_sub(1), - }, - }, - }) - } -} - -/// Parsed v2 JSON output from the compiler. -#[derive(Debug, Default)] -pub struct CompilerOutput { - pub symbols: Vec, - pub references: Vec, -} - -impl CompilerOutput { - /// Parse symbols and references from v2 JSON output. - pub fn from_json(json: &serde_json::Value) -> Self { - let mut output = CompilerOutput::default(); - - // Parse symbols array. - if let Some(syms) = json.get("symbols").and_then(|v| v.as_array()) { - for sym_val in syms { - if let Some(sym) = parse_symbol(sym_val) { - output.symbols.push(sym); - } - } - } - - // Parse references map: { "id": [ spans ] } - if let Some(refs) = json.get("references").and_then(|v| v.as_object()) { - for (id_str, spans_val) in refs { - if let (Ok(sym_id), Some(spans)) = (id_str.parse::(), spans_val.as_array()) { - for span_val in spans { - if let Some(r) = parse_reference(sym_id, span_val) { - output.references.push(r); - } - } - } - } - } - - output - } - - /// Find the symbol whose definition span contains the given position. - pub fn find_symbol_at(&self, file: &str, line: u32, col: u32) -> Option<&CompilerSymbol> { - // First try exact name match at position (for goto-def on usage). - // Then fall back to definition span match. - self.symbols.iter().find(|sym| { - sym.file == file - && line >= sym.start_line && line <= sym.end_line - && (line != sym.start_line || col >= sym.start_col) - && (line != sym.end_line || col <= sym.end_col) - }) - } - - /// Find a symbol by name (for goto-def when cursor is on a usage site). - pub fn find_symbol_by_name(&self, name: &str) -> Option<&CompilerSymbol> { - self.symbols.iter().find(|sym| sym.name == name) - } - - /// Find all references for a symbol ID. - pub fn find_references(&self, symbol_id: i64) -> Vec<&CompilerReference> { - self.references.iter().filter(|r| r.symbol_id == symbol_id).collect() - } -} - -fn parse_symbol(val: &serde_json::Value) -> Option { - Some(CompilerSymbol { - id: val.get("id")?.as_i64()?, - name: val.get("name")?.as_str()?.to_string(), - kind: val.get("kind")?.as_str()?.to_string(), - file: val.get("file")?.as_str()?.to_string(), - start_line: val.get("start_line")?.as_u64()? as u32, - start_col: val.get("start_col")?.as_u64()? as u32, - end_line: val.get("end_line")?.as_u64()? as u32, - end_col: val.get("end_col")?.as_u64()? as u32, - type_info: val.get("type").and_then(|v| v.as_str()).map(|s| s.to_string()), - quantity: val.get("quantity").and_then(|v| v.as_str()).map(|s| s.to_string()), - }) -} - -fn parse_reference(symbol_id: i64, val: &serde_json::Value) -> Option { - Some(CompilerReference { - symbol_id, - file: val.get("file")?.as_str()?.to_string(), - start_line: val.get("start_line")?.as_u64()? as u32, - start_col: val.get("start_col")?.as_u64()? as u32, - end_line: val.get("end_line")?.as_u64()? as u32, - end_col: val.get("end_col")?.as_u64()? as u32, - }) -} diff --git a/affinescript/tools/affinescript-lsp/src/text_index.rs b/affinescript/tools/affinescript-lsp/src/text_index.rs deleted file mode 100644 index a1f8d2e..0000000 --- a/affinescript/tools/affinescript-lsp/src/text_index.rs +++ /dev/null @@ -1,382 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) Jonathan D.A. Jewell -//! Text-Based Symbol Index (Phase C) -//! -//! Provides a fallback symbol index by scanning AffineScript source text for -//! definitions and usages when the compiler's v2 JSON output is unavailable. -//! -//! This module recognises: -//! - `fn ` — function definitions -//! - `let ` — variable bindings -//! - `type ` — type aliases -//! - `struct ` — structure definitions -//! - `enum ` — enumeration definitions -//! - `effect ` — effect type definitions -//! - `handler ` — effect handler definitions -//! -//! For each definition it records the name, location, and kind. It then -//! scans the entire document for all occurrences of that name as an identifier -//! (i.e. bounded by non-alphanumeric/non-underscore characters) to build a -//! reference list. -//! -//! This is intentionally simple — it does not parse the AST or resolve scopes. -//! When the compiler provides v2 JSON with full symbol tables, those take -//! priority. - -/// A definition found by text scanning. -#[derive(Debug, Clone)] -pub struct TextDefinition { - /// The symbol name (e.g. "foo", "MyType"). - pub name: String, - /// The kind of definition (e.g. "function", "variable", "type"). - pub kind: &'static str, - /// Zero-based line number where the name starts. - pub line: u32, - /// Zero-based UTF-16 column where the name starts. - pub col_start: u32, - /// Zero-based UTF-16 column where the name ends (exclusive). - pub col_end: u32, -} - -/// A usage (reference) found by text scanning. -#[derive(Debug, Clone)] -pub struct TextReference { - /// The symbol name. - pub name: String, - /// Zero-based line number. - pub line: u32, - /// Zero-based UTF-16 column where the name starts. - pub col_start: u32, - /// Zero-based UTF-16 column where the name ends (exclusive). - pub col_end: u32, -} - -/// A complete text-based index for a single document. -#[derive(Debug, Clone, Default)] -pub struct TextIndex { - /// All definitions found in the document. - pub definitions: Vec, - /// All identifier occurrences found in the document. - pub occurrences: Vec, -} - -/// AffineScript keywords that should never be treated as user-defined symbols. -/// Renaming these would break the language syntax. -const KEYWORDS: &[&str] = &[ - "fn", "let", "type", "struct", "enum", "effect", "handler", - "match", "if", "else", "for", "while", "return", "true", "false", - "linear", "affine", "unrestricted", "borrow", "move", "mut", - "pub", "mod", "use", "import", "as", "in", "do", "with", - "where", "trait", "impl", "self", "Self", -]; - -/// Standard library type names that should not be renamed. -const BUILTIN_TYPES: &[&str] = &[ - "Int", "Float", "Bool", "String", "Unit", "List", "Option", "Result", -]; - -impl TextIndex { - /// Build a text-based index from source text. - /// - /// Scans every line for definition keywords (`fn`, `let`, `type`, etc.) - /// and records the name that follows. Then scans every line for all - /// identifier occurrences to populate the occurrence list. - pub fn build(text: &str) -> Self { - let mut index = TextIndex::default(); - let lines: Vec<&str> = text.lines().collect(); - - // Pass 1: find definitions. - for (line_num, line) in lines.iter().enumerate() { - let trimmed = line.trim_start(); - let leading_bytes = line.len() - trimmed.len(); - - // Each entry: (keyword, kind label) - let def_keywords: &[(&str, &str)] = &[ - ("fn ", "function"), - ("let ", "variable"), - ("type ", "type"), - ("struct ", "struct"), - ("enum ", "enum"), - ("effect ", "effect"), - ("handler ", "handler"), - ]; - - for &(keyword, kind) in def_keywords { - if !trimmed.starts_with(keyword) { - continue; - } - let after_keyword = &trimmed[keyword.len()..]; - // Extract the identifier that follows the keyword. - let name_end = after_keyword - .find(|c: char| !c.is_alphanumeric() && c != '_') - .unwrap_or(after_keyword.len()); - if name_end == 0 { - continue; - } - let name = &after_keyword[..name_end]; - if name.is_empty() { - continue; - } - - // Compute UTF-16 columns. - let byte_offset_of_name = leading_bytes + keyword.len(); - let col_start = utf16_len(&line[..byte_offset_of_name]); - let col_end = col_start + utf16_len(name); - - index.definitions.push(TextDefinition { - name: name.to_string(), - kind, - line: line_num as u32, - col_start, - col_end, - }); - break; // Only one definition per line. - } - } - - // Pass 2: find all identifier occurrences across the document. - // We scan for every identifier token and record it. - for (line_num, line) in lines.iter().enumerate() { - let mut byte_pos = 0; - let chars: Vec = line.chars().collect(); - let mut char_idx = 0; - - while char_idx < chars.len() { - let c = chars[char_idx]; - - // Skip comment lines (// style). - if c == '/' && char_idx + 1 < chars.len() && chars[char_idx + 1] == '/' { - break; // Rest of line is a comment. - } - - // Skip string literals. - if c == '"' { - byte_pos += c.len_utf8(); - char_idx += 1; - while char_idx < chars.len() && chars[char_idx] != '"' { - if chars[char_idx] == '\\' && char_idx + 1 < chars.len() { - byte_pos += chars[char_idx].len_utf8(); - char_idx += 1; - } - byte_pos += chars[char_idx].len_utf8(); - char_idx += 1; - } - if char_idx < chars.len() { - byte_pos += chars[char_idx].len_utf8(); - char_idx += 1; - } - continue; - } - - if c.is_alphanumeric() || c == '_' { - // Start of an identifier token. - let ident_start_byte = byte_pos; - let mut ident_end_byte = byte_pos + c.len_utf8(); - let start_char_idx = char_idx; - char_idx += 1; - - while char_idx < chars.len() - && (chars[char_idx].is_alphanumeric() || chars[char_idx] == '_') - { - ident_end_byte += chars[char_idx].len_utf8(); - char_idx += 1; - } - - let ident = &line[ident_start_byte..ident_end_byte]; - - // Skip identifiers that start with a digit (they are numbers). - if !chars[start_char_idx].is_ascii_digit() { - let col_start = utf16_len(&line[..ident_start_byte]); - let col_end = col_start + utf16_len(ident); - - index.occurrences.push(TextReference { - name: ident.to_string(), - line: line_num as u32, - col_start, - col_end, - }); - } - - byte_pos = ident_end_byte; - } else { - byte_pos += c.len_utf8(); - char_idx += 1; - } - } - } - - index - } - - /// Find the definition of a symbol by name. - /// - /// Returns the first definition matching the given name. - pub fn find_definition(&self, name: &str) -> Option<&TextDefinition> { - self.definitions.iter().find(|d| d.name == name) - } - - /// Find all occurrences of a symbol by name. - /// - /// Returns every occurrence (both definition sites and usage sites). - pub fn find_occurrences(&self, name: &str) -> Vec<&TextReference> { - self.occurrences.iter().filter(|o| o.name == name).collect() - } - - /// Find all occurrences of a symbol excluding the definition site. - /// - /// Useful for find-references when `include_declaration` is false. - pub fn find_usages_only(&self, name: &str) -> Vec<&TextReference> { - let def = self.find_definition(name); - self.occurrences - .iter() - .filter(|o| { - o.name == name - && !def.map_or(false, |d| { - o.line == d.line && o.col_start == d.col_start - }) - }) - .collect() - } - - /// Find the identifier at a given cursor position. - /// - /// Returns the occurrence whose range contains the position. - pub fn find_at_position(&self, line: u32, character: u32) -> Option<&TextReference> { - self.occurrences.iter().find(|o| { - o.line == line && character >= o.col_start && character < o.col_end - }) - } -} - -/// Check whether a name is a keyword or builtin and therefore not renameable. -pub fn is_renameable(name: &str) -> bool { - !KEYWORDS.contains(&name) && !BUILTIN_TYPES.contains(&name) -} - -/// Validate that a proposed new name is a legal AffineScript identifier. -/// -/// Must start with a letter or underscore, contain only alphanumerics and -/// underscores, and must not be a keyword or builtin type name. -pub fn is_valid_identifier(name: &str) -> bool { - if name.is_empty() { - return false; - } - let mut chars = name.chars(); - let first = chars.next().unwrap(); - if !first.is_alphabetic() && first != '_' { - return false; - } - if !chars.all(|c| c.is_alphanumeric() || c == '_') { - return false; - } - // Must not rename to a keyword or builtin. - is_renameable(name) -} - -/// Count UTF-16 code units in a byte slice of a string. -/// -/// This mirrors the helper in handlers.rs but is kept local to avoid -/// circular module dependencies. -fn utf16_len(s: &str) -> u32 { - s.chars().map(|c| c.len_utf16()).sum::() as u32 -} - -// --------------------------------------------------------------------------- -// Tests -// --------------------------------------------------------------------------- - -#[cfg(test)] -mod tests { - use super::*; - - /// Helper: build index from source and return it. - fn idx(src: &str) -> TextIndex { - TextIndex::build(src) - } - - #[test] - fn finds_function_definition() { - let index = idx("fn greet(name: String) -> Unit {\n let x = 1\n}"); - assert_eq!(index.definitions.len(), 2); // greet + x - assert_eq!(index.definitions[0].name, "greet"); - assert_eq!(index.definitions[0].kind, "function"); - assert_eq!(index.definitions[0].line, 0); - assert_eq!(index.definitions[1].name, "x"); - assert_eq!(index.definitions[1].kind, "variable"); - assert_eq!(index.definitions[1].line, 1); - } - - #[test] - fn finds_type_definitions() { - let src = "type Foo = Int\nstruct Bar {\n x: Int\n}\nenum Baz { A, B }"; - let index = idx(src); - let names: Vec<&str> = index.definitions.iter().map(|d| d.name.as_str()).collect(); - assert!(names.contains(&"Foo")); - assert!(names.contains(&"Bar")); - assert!(names.contains(&"Baz")); - } - - #[test] - fn finds_all_occurrences() { - let src = "let x = 1\nlet y = x + 2\nfn foo() -> Int { x }"; - let index = idx(src); - let x_refs = index.find_occurrences("x"); - // "x" appears: definition, usage in y binding, usage in foo body = 3 - assert_eq!(x_refs.len(), 3); - } - - #[test] - fn find_usages_only_excludes_definition() { - let src = "let x = 1\nlet y = x + 2"; - let index = idx(src); - let usages = index.find_usages_only("x"); - assert_eq!(usages.len(), 1); - assert_eq!(usages[0].line, 1); - } - - #[test] - fn find_at_position_works() { - let src = "fn hello() -> Unit {}"; - let index = idx(src); - // "hello" starts at UTF-16 col 3 (after "fn "), ends at 8. - let hit = index.find_at_position(0, 4); - assert!(hit.is_some()); - assert_eq!(hit.unwrap().name, "hello"); - } - - #[test] - fn keywords_are_not_renameable() { - assert!(!is_renameable("fn")); - assert!(!is_renameable("let")); - assert!(!is_renameable("Int")); - assert!(is_renameable("myVar")); - } - - #[test] - fn validates_identifiers() { - assert!(is_valid_identifier("foo")); - assert!(is_valid_identifier("_bar")); - assert!(is_valid_identifier("Baz123")); - assert!(!is_valid_identifier("123abc")); - assert!(!is_valid_identifier("")); - assert!(!is_valid_identifier("fn")); // keyword - } - - #[test] - fn skips_comments() { - let src = "let x = 1 // x is important\nlet y = x"; - let index = idx(src); - let x_refs = index.find_occurrences("x"); - // definition on line 0, usage on line 1 = 2 (comment "x" skipped) - assert_eq!(x_refs.len(), 2); - } - - #[test] - fn skips_string_literals() { - let src = "let x = \"x is a string\"\nlet y = x"; - let index = idx(src); - let x_refs = index.find_occurrences("x"); - // definition on line 0, usage on line 1 = 2 ("x" in string skipped) - assert_eq!(x_refs.len(), 2); - } -} diff --git a/affinescript/tools/alib_conformance_gen.jl b/affinescript/tools/alib_conformance_gen.jl deleted file mode 100644 index 11a0b65..0000000 --- a/affinescript/tools/alib_conformance_gen.jl +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env julia -# SPDX-License-Identifier: MPL-2.0 -# SPDX-FileCopyrightText: 2025 hyperpolymath -# -# Generate AffineScript conformance tests from aLib specs - -using YAML -using Markdown - -""" -Generate AffineScript test from aLib YAML test case -""" -function generate_test_case(test_case::Dict, fn_name::String) - input = test_case["input"] - output = test_case["output"] - desc = get(test_case, "description", "") - - # Convert YAML representation to AffineScript syntax - collection = format_affinescript_value(input["collection"]) - fn_expr = parse_function_expression(input["fn"]) - expected = format_affinescript_value(output) - - """ - // $(desc) - let input = $(collection); - let result = $(fn_name)(input, $(fn_expr)); - assert_eq(result, $(expected), "$(desc)"); - """ -end - -""" -Format value as AffineScript literal -""" -function format_affinescript_value(val) - if isa(val, Vector) - if isempty(val) - return "[]" - end - elements = join([format_affinescript_value(v) for v in val], ", ") - return "[$(elements)]" - elseif isa(val, String) - return "\"$(val)\"" - elseif isa(val, Number) - return string(val) - else - return string(val) - end -end - -""" -Parse function expression from aLib format to AffineScript -""" -function parse_function_expression(fn_str::String) - # Convert "x => multiply(x, 2)" to "fn(x) => x * 2" - # Convert "x => add(x, 10)" to "fn(x) => x + 10" - # This is simplified - real implementation would parse properly - - fn_str = replace(fn_str, "multiply(x, " => "x * ") - fn_str = replace(fn_str, "add(x, " => "x + ") - fn_str = replace(fn_str, "concat(s, s)" => "s ++ s") - fn_str = replace(fn_str, r"\)$" => "") - - # Convert arrow function to AffineScript syntax - if occursin("=>", fn_str) - parts = split(fn_str, "=>") - param = strip(parts[1]) - body = strip(parts[2]) - return "fn($(param)) => $(body)" - end - - return fn_str -end - -""" -Generate complete conformance test file for a spec -""" -function generate_conformance_test(spec_path::String, output_dir::String) - # Read the markdown spec file - content = read(spec_path, String) - - # Extract YAML test cases (simplified - would use proper markdown parsing) - yaml_match = match(r"```yaml\n(.*?)\n```"s, content) - if yaml_match === nothing - @warn "No YAML test cases found in $(spec_path)" - return - end - - yaml_str = yaml_match.captures[1] - test_data = YAML.load(yaml_str) - - # Extract operation name from filename - op_name = replace(basename(spec_path), ".md" => "") - category = basename(dirname(spec_path)) - - # Generate test file - test_code = """ -// SPDX-License-Identifier: MPL-2.0 -// SPDX-FileCopyrightText: 2025 hyperpolymath -// -// Auto-generated conformance tests for aLib spec: $(category)/$(op_name) -// Source: aggregate-library/specs/$(category)/$(op_name).md - -fn test_alib_$(category)_$(op_name)() -> TestResult { -""" - - # Generate test cases - for test_case in test_data["test_cases"] - test_code *= generate_test_case(test_case, op_name) - test_code *= "\n" - end - - test_code *= """ - Pass -} -""" - - # Write output file - output_file = joinpath(output_dir, "$(category)_$(op_name).affine") - mkpath(output_dir) - write(output_file, test_code) - - println("✓ Generated $(output_file)") -end - -""" -Main function - generate conformance tests from aLib specs -""" -function main() - if length(ARGS) < 2 - println("Usage: julia alib_conformance_gen.jl ") - println("Example: julia alib_conformance_gen.jl ../aggregate-library/specs tests/conformance") - exit(1) - end - - specs_dir = ARGS[1] - output_dir = ARGS[2] - - if !isdir(specs_dir) - @error "Specs directory not found: $(specs_dir)" - exit(1) - end - - println("Generating conformance tests from aLib specs...") - println("Source: $(specs_dir)") - println("Output: $(output_dir)") - println() - - # Find all spec files - spec_files = [] - for (root, dirs, files) in walkdir(specs_dir) - for file in files - if endswith(file, ".md") - push!(spec_files, joinpath(root, file)) - end - end - end - - println("Found $(length(spec_files)) spec files\n") - - # Generate tests for each spec - for spec_file in spec_files - try - generate_conformance_test(spec_file, output_dir) - catch e - @warn "Failed to generate test for $(spec_file): $(e)" - end - end - - println("\n✓ Generated $(length(spec_files)) conformance test files") - println("Run tests with: affinescript test $(output_dir)") -end - -# Run if executed directly -if abspath(PROGRAM_FILE) == @__FILE__ - main() -end diff --git a/affinescript/tools/run_codegen_wasm_tests.sh b/affinescript/tools/run_codegen_wasm_tests.sh deleted file mode 100755 index bf5afbc..0000000 --- a/affinescript/tools/run_codegen_wasm_tests.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" -TEST_DIR="$ROOT_DIR/tests/codegen" - -if [ -x "$ROOT_DIR/_build/default/bin/main.exe" ]; then - COMPILER="$ROOT_DIR/_build/default/bin/main.exe" - COMPILE_CMD=("$COMPILER" compile) -elif command -v affinescript >/dev/null 2>&1; then - COMPILER="affinescript" - COMPILE_CMD=("$COMPILER" compile) -else - COMPILER="dune exec affinescript --" - COMPILE_CMD=(dune exec affinescript -- compile) -fi - -echo "Using compiler: $COMPILER" - -for src in "$TEST_DIR"/*.affine; do - base="${src%.affine}" - wasm="$base.wasm" - echo "Compiling $(basename "$src") -> $(basename "$wasm")" - "${COMPILE_CMD[@]}" "$src" -o "$wasm" -done - -echo "" - -echo "Running JS harnesses" -for js in "$TEST_DIR"/*.mjs; do - echo "node $(basename "$js")" - (cd "$ROOT_DIR" && node "${js#$ROOT_DIR/}") -done - -echo "All codegen WASM tests passed." diff --git a/affinescript/wiki/README.md b/affinescript/wiki/README.md deleted file mode 100644 index 0f817df..0000000 --- a/affinescript/wiki/README.md +++ /dev/null @@ -1,166 +0,0 @@ - -# AffineScript Wiki - -Welcome to the AffineScript language wiki - your comprehensive guide to learning and using AffineScript. - -## Quick Navigation - -### Getting Started -- [Introduction](tutorials/introduction.md) - What is AffineScript? -- [Installation](tutorials/installation.md) - Setting up your environment -- [Hello World](tutorials/hello-world.md) - Your first program -- [Quick Start Guide](tutorials/quickstart.md) - Get productive fast - -### Language Reference -- [Lexical Structure](language-reference/lexical.md) - Tokens, literals, comments -- [Types](language-reference/types.md) - Type system overview -- [Expressions](language-reference/expressions.md) - Expression syntax and semantics -- [Patterns](language-reference/patterns.md) - Pattern matching -- [Functions](language-reference/functions.md) - Function definitions -- [Ownership](language-reference/ownership.md) - Affine types and borrowing -- [Effects](language-reference/effects.md) - Algebraic effect system -- [Traits](language-reference/traits.md) - Type classes -- [Modules](language-reference/modules.md) - Module system -- [Dependent Types](language-reference/dependent-types.md) - Indexed and refined types -- [Row Polymorphism](language-reference/rows.md) - Extensible records - -### Compiler -- [Architecture Overview](compiler/architecture.md) - Compiler pipeline -- [Lexer](compiler/lexer.md) - Lexical analysis -- [Parser](compiler/parser.md) - Syntactic analysis -- [Type Checker](compiler/type-checker.md) - Type inference and checking -- [Borrow Checker](compiler/borrow-checker.md) - Ownership verification -- [Code Generation](compiler/codegen.md) - WASM backend -- [Error Messages](compiler/errors.md) - Diagnostic system - -### Tooling -- [CLI Reference](tooling/cli.md) - Command-line interface -- [REPL Guide](tooling/repl.md) - Interactive environment -- [Package Manager](tooling/package-manager.md) - aspm reference -- [LSP Server](tooling/lsp.md) - Editor integration -- [Formatter](tooling/formatter.md) - Code formatting -- [Linter](tooling/linter.md) - Code analysis - -### Standard Library -- [Overview](stdlib/overview.md) - Library organization -- [Primitives](stdlib/primitives.md) - Basic types -- [Collections](stdlib/collections.md) - Data structures -- [Effects](stdlib/effects.md) - Standard effects -- [I/O](stdlib/io.md) - Input/output -- [Concurrency](stdlib/concurrency.md) - Threading and async - -### Testing -- [Testing Guide](testing/guide.md) - Writing tests -- [Property-Based Testing](testing/property-based.md) - QuickCheck-style testing -- [Fuzzing](testing/fuzzing.md) - Fuzz testing -- [Benchmarking](testing/benchmarks.md) - Performance testing - -### Design Documents -- [Language Design](design/language.md) - Design philosophy -- [Type System](design/type-system.md) - Type theory background -- [Effect System](design/effects.md) - Effect theory -- [Memory Model](design/memory.md) - Memory management - -## Quick Links - -| Resource | Description | -|----------|-------------| -| [Full Specification](../docs/spec.md) | Complete language specification | -| [Roadmap](../ROADMAP.md) | Development roadmap | -| [Examples](../examples/) | Example programs | -| [Contributing](../CONTRIBUTING.md) | How to contribute | - -## Language Features at a Glance - -### Ownership & Borrowing -```affine -fn transfer(file: own File) -> own File { - // file is owned, must be returned or consumed - file -} - -fn read_only(file: &File) -> String { - // file is borrowed immutably - file.read_all() -} - -fn modify(file: &mut File) { - // file is borrowed mutably - file.write("data") -} -``` - -### Dependent Types -```affine -// Length-indexed vectors -fn head[n: Nat, T](vec: Vec[n + 1, T]) -> T { - vec[0] // Statically safe - vector has at least 1 element -} - -fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], - b: Vec[m, T] -) -> Vec[n + m, T] { - // Return type precisely tracks length - ... -} -``` - -### Row Polymorphism -```affine -// Works on any record with 'name' field -fn greet[r](person: {name: String, ..r}) -> String { - "Hello, " ++ person.name -} - -// Can call with any matching record -greet({name: "Alice", age: 30}) -greet({name: "Bob", role: "Admin", active: true}) -``` - -### Algebraic Effects -```affine -effect Ask[A] { - fn ask() -> A -} - -fn double_ask[A]() -{Ask[A]}-> (A, A) { - (ask(), ask()) -} - -fn main() -{IO}-> Unit { - let result = handle double_ask() { - ask() -> resume(42) - }; - print(result) // (42, 42) -} -``` - -### Totality Checking -```affine -// Guaranteed to terminate -total fn factorial(n: Nat) -> Nat { - match n { - 0 -> 1, - n -> n * factorial(n - 1) - } -} - -// May diverge (partial by default) -fn loop_forever() -> Never { - loop_forever() -} -``` - -## Community - -- **GitHub**: [github.com/hyperpolymath/affinescript](https://github.com/hyperpolymath/affinescript) -- **Issues**: Report bugs and request features -- **Discussions**: Ask questions and share ideas - ---- - -*AffineScript - Safe, Expressive, Predictable* diff --git a/affinescript/wiki/compiler/architecture.md b/affinescript/wiki/compiler/architecture.md deleted file mode 100644 index cb1089e..0000000 --- a/affinescript/wiki/compiler/architecture.md +++ /dev/null @@ -1,428 +0,0 @@ - -# Compiler Architecture - -This document describes the architecture of the AffineScript compiler. - -## Overview - -``` -┌─────────────────────────────────────────────────────────────────────────┐ -│ AffineScript Compiler │ -├─────────────────────────────────────────────────────────────────────────┤ -│ │ -│ Source Code (.affine) │ -│ │ │ -│ ▼ │ -│ ┌─────────┐ │ -│ │ Lexer │ sedlex-based, Unicode support │ -│ └────┬────┘ │ -│ │ Token Stream │ -│ ▼ │ -│ ┌─────────┐ │ -│ │ Parser │ Menhir-based, error recovery │ -│ └────┬────┘ │ -│ │ Concrete Syntax Tree (CST) │ -│ ▼ │ -│ ┌───────────┐ │ -│ │ Desugarer │ CST → AST transformation │ -│ └─────┬─────┘ │ -│ │ Abstract Syntax Tree (AST) │ -│ ▼ │ -│ ┌──────────────┐ │ -│ │Name Resolver │ Scope analysis, module resolution │ -│ └──────┬───────┘ │ -│ │ Resolved AST │ -│ ▼ │ -│ ┌──────────────┐ │ -│ │ Type Checker │ Bidirectional inference, dependent types │ -│ └──────┬───────┘ │ -│ │ Typed AST + Constraints │ -│ ▼ │ -│ ┌───────────────┐ │ -│ │ Borrow Checker│ Ownership, lifetimes, linearity │ -│ └───────┬───────┘ │ -│ │ Verified AST │ -│ ▼ │ -│ ┌───────────────┐ │ -│ │ Trait Solver │ Instance resolution, coherence │ -│ └───────┬───────┘ │ -│ │ Elaborated AST │ -│ ▼ │ -│ ┌───────────────┐ │ -│ │Monomorphizer │ Generic specialization │ -│ └───────┬───────┘ │ -│ │ Monomorphic AST │ -│ ▼ │ -│ ┌───────────┐ │ -│ │ IR Lower │ ANF/CPS transformation │ -│ └─────┬─────┘ │ -│ │ Intermediate Representation │ -│ ▼ │ -│ ┌───────────┐ │ -│ │ Optimizer │ Inlining, DCE, constant folding │ -│ └─────┬─────┘ │ -│ │ Optimized IR │ -│ ▼ │ -│ ┌────────────┐ │ -│ │ Code Gen │ Target-specific emission │ -│ └─────┬──────┘ │ -│ │ │ -│ ▼ │ -│ WebAssembly (.wasm) │ -│ │ -└─────────────────────────────────────────────────────────────────────────┘ -``` - -## Compiler Phases - -### Phase 1: Lexical Analysis - -**Module**: `lib/lexer.ml` -**Library**: sedlex - -Converts source text into tokens: - -```ocaml -type token = - | KEYWORD of keyword - | IDENT of string - | INT_LIT of int - | FLOAT_LIT of float - | STRING_LIT of string - | OPERATOR of string - | PUNCT of char - | EOF -``` - -Features: -- Unicode identifier support -- Nested block comments -- Source location tracking (Span) -- Lexer error recovery - -### Phase 2: Parsing - -**Module**: `lib/parser.ml` (planned) -**Library**: Menhir - -Converts token stream to Concrete Syntax Tree (CST): - -```ocaml -type cst_expr = - | CST_Literal of literal * span - | CST_Ident of string * span - | CST_Binary of cst_expr * binop * cst_expr * span - | CST_App of cst_expr * cst_expr list * span - (* ... *) -``` - -Features: -- Operator precedence handling -- Error recovery with synchronization points -- Detailed parse error messages -- Source location preservation - -### Phase 3: Desugaring - -**Module**: `lib/desugar.ml` (planned) - -Transforms CST to cleaner AST: - -Desugarings include: -- `if`/`else` chains → nested matches -- `for` loops → `while` + iterators -- Operator sections → lambdas -- Method calls → function application -- Field punning expansion - -### Phase 4: Name Resolution - -**Module**: `lib/resolve.ml` (planned) - -Resolves all names to their definitions: - -```ocaml -type resolved_name = - | Local of int (* de Bruijn index *) - | Global of path - | Builtin of builtin - -type scope = { - vars: (string, resolved_name) Map.t; - types: (string, type_def) Map.t; - modules: (string, module_sig) Map.t; - parent: scope option; -} -``` - -Responsibilities: -- Variable binding and lookup -- Type name resolution -- Module path resolution -- Import handling -- Visibility checking - -### Phase 5: Type Checking - -**Module**: `lib/typecheck.ml` (planned) - -Bidirectional type inference and checking: - -```ocaml -(* Synthesis: infer type from term *) -val synth : ctx -> expr -> typ * elaborated_expr - -(* Checking: check term against expected type *) -val check : ctx -> expr -> typ -> elaborated_expr - -(* Unification: equate two types *) -val unify : ctx -> typ -> typ -> substitution -``` - -Components: -- **Kind checker**: Ensures types are well-formed -- **Type synthesizer**: Infers types from expressions -- **Type checker**: Verifies expressions against types -- **Constraint solver**: Solves type constraints -- **Effect inferencer**: Infers effect annotations - -### Phase 6: Borrow Checking - -**Module**: `lib/borrow.ml` (planned) - -Verifies ownership and borrowing rules: - -```ocaml -type place = { - base: var; - projections: projection list; -} - -type loan = { - place: place; - kind: Shared | Mutable; - region: region; -} - -type ownership_state = { - loans: loan set; - moves: place set; - drops: place set; -} -``` - -Analyses: -- **Ownership tracking**: Who owns what -- **Borrow tracking**: Active borrows and their regions -- **Move analysis**: Detect use-after-move -- **Drop insertion**: Where values are freed -- **Linearity checking**: Linear types used exactly once - -### Phase 7: Trait Resolution - -**Module**: `lib/traits.ml` (planned) - -Resolves trait methods to implementations: - -```ocaml -type instance = { - trait: path; - typ: typ; - methods: (string, expr) Map.t; - assoc_types: (string, typ) Map.t; -} - -val resolve_method : ctx -> typ -> trait -> string -> instance * expr -``` - -Features: -- Instance search -- Coherence checking (no overlapping instances) -- Associated type resolution -- Superclass resolution - -### Phase 8: Monomorphization - -**Module**: `lib/mono.ml` (planned) - -Specializes generic code for concrete types: - -```ocaml -val monomorphize : typed_program -> mono_program - -(* Tracks which specializations are needed *) -type specialization_queue = (generic_fn * typ list) Queue.t -``` - -Process: -1. Start from `main` and entry points -2. Collect required type instantiations -3. Generate specialized versions -4. Replace generic calls with specialized calls - -### Phase 9: IR Lowering - -**Module**: `lib/lower.ml` (planned) - -Transforms to intermediate representation: - -```ocaml -type ir_expr = - | IR_Var of var - | IR_Lit of literal - | IR_Let of var * ir_expr * ir_expr - | IR_App of var * var list - | IR_If of var * ir_block * ir_block - | IR_Match of var * (pattern * ir_block) list - | IR_Return of var - | IR_Unreachable -``` - -Transformations: -- ANF conversion (all subexpressions named) -- Closure conversion (closures become structs) -- Effect compilation (CPS or evidence passing) -- Pattern match compilation - -### Phase 10: Optimization - -**Module**: `lib/optimize.ml` (planned) - -Standard compiler optimizations: - -- Dead code elimination -- Constant folding and propagation -- Inlining (guided by heuristics) -- Common subexpression elimination -- Tail call optimization -- Escape analysis (stack allocation) - -### Phase 11: Code Generation - -**Module**: `lib/codegen.ml` (planned) - -Emits target code (initially WASM): - -```ocaml -type wasm_instr = - | I32_const of int32 - | I64_const of int64 - | Local_get of int - | Local_set of int - | Call of func_idx - | If of block_type * wasm_instr list * wasm_instr list - | Loop of block_type * wasm_instr list - (* ... *) - -val emit_module : mono_program -> wasm_module -``` - -## Data Structures - -### Source Locations - -```ocaml -type position = { - line: int; - column: int; - offset: int; -} - -type span = { - start: position; - end_: position; - file: string; -} -``` - -### Abstract Syntax Tree - -See `lib/ast.ml` for complete definitions. - -Key types: -- `expr` - Expressions -- `typ` - Type expressions -- `pattern` - Patterns -- `stmt` - Statements -- `decl` - Declarations - -### Error Handling - -```ocaml -type error_code = - | E0001 (* Lexer: unexpected character *) - | E0100 (* Parser: unexpected token *) - | E0300 (* Type: mismatch *) - | E0500 (* Borrow: use after move *) - (* ... *) - -type diagnostic = { - code: error_code; - message: string; - span: span; - labels: (span * string) list; - notes: string list; - help: string option; -} -``` - -## File Organization - -``` -lib/ -├── ast.ml # AST definitions -├── token.ml # Token definitions -├── span.ml # Source locations -├── lexer.ml # Lexical analysis -├── parser.ml # Syntactic analysis (planned) -├── desugar.ml # CST → AST (planned) -├── resolve.ml # Name resolution (planned) -├── typecheck.ml # Type checking (planned) -├── borrow.ml # Borrow checking (planned) -├── traits.ml # Trait resolution (planned) -├── mono.ml # Monomorphization (planned) -├── ir.ml # IR definitions (planned) -├── lower.ml # AST → IR (planned) -├── optimize.ml # IR optimizations (planned) -├── codegen.ml # Code generation (planned) -├── error.ml # Diagnostics -└── driver.ml # Compiler driver (planned) -``` - -## Implementation Order - -1. **Parser** (blocks everything) -2. **Name resolution** (enables type checking) -3. **Basic type checking** (simple types first) -4. **Borrow checking** (ownership semantics) -5. **Basic codegen** (WASM text format) -6. **Advanced types** (dependent, rows) -7. **Effects** (handlers, CPS) -8. **Optimizations** (incremental) - -## Testing Strategy - -Each phase has corresponding tests: - -``` -test/ -├── test_lexer.ml # Lexer unit tests -├── test_parser.ml # Parser unit tests (planned) -├── test_types.ml # Type checker tests (planned) -├── test_borrow.ml # Borrow checker tests (planned) -├── test_codegen.ml # Code generation tests (planned) -└── integration/ # End-to-end tests (planned) -``` - ---- - -## See Also - -- [Lexer](lexer.md) - Lexer implementation details -- [Parser](parser.md) - Parser implementation details -- [Type Checker](type-checker.md) - Type checking algorithm -- [Borrow Checker](borrow-checker.md) - Ownership analysis -- [Code Generation](codegen.md) - WASM emission diff --git a/affinescript/wiki/compiler/borrow-checker.md b/affinescript/wiki/compiler/borrow-checker.md deleted file mode 100644 index 4ed52d7..0000000 --- a/affinescript/wiki/compiler/borrow-checker.md +++ /dev/null @@ -1,379 +0,0 @@ - -# Borrow Checker - -The AffineScript borrow checker verifies ownership and borrowing rules at compile time. - -## Overview - -**File**: `lib/borrow.ml` (planned) -**Algorithm**: Dataflow analysis with non-lexical lifetimes - -## Goals - -1. **No use-after-move**: Cannot use values after ownership transfer -2. **No data races**: No simultaneous mutable access -3. **No dangling references**: References always point to valid data -4. **Linearity enforcement**: Linear types used exactly once - -## Core Concepts - -### Places - -A **place** represents a memory location: - -```ocaml -type place = { - base: local_var; (* The root variable *) - projections: projection list; (* Path to the location *) -} - -type projection = - | Proj_Field of string (* .field *) - | Proj_Index of int (* [index] *) - | Proj_Deref (* * (dereference) *) -``` - -Examples: -- `x` → `{ base = x; projections = [] }` -- `x.field` → `{ base = x; projections = [Proj_Field "field"] }` -- `(*p).data[0]` → `{ base = p; projections = [Proj_Deref; Proj_Field "data"; Proj_Index 0] }` - -### Loans - -A **loan** is an active borrow: - -```ocaml -type loan_kind = Shared | Mutable - -type loan = { - id: loan_id; - place: place; (* What's borrowed *) - kind: loan_kind; (* Shared or mutable *) - region: region; (* How long it's valid *) -} -``` - -### Regions (Lifetimes) - -A **region** is a span of code where a reference is valid: - -```ocaml -type region = - | Region_Var of int (* Named lifetime 'a *) - | Region_Static (* 'static *) - | Region_Scope of scope_id (* Lexical scope *) -``` - -## Analysis Phases - -### Phase 1: Build Control Flow Graph - -```ocaml -type cfg_node = - | CFG_Statement of stmt - | CFG_Branch of { cond: expr; true_: cfg_node; false_: cfg_node } - | CFG_Loop of { body: cfg_node } - | CFG_Return of expr option - | CFG_Drop of place - -type cfg = { - entry: cfg_node; - nodes: cfg_node list; - edges: (cfg_node * cfg_node) list; -} -``` - -### Phase 2: Compute Liveness - -Determine where each variable is "live" (might be used later): - -```ocaml -type liveness = { - live_in: PlaceSet.t; (* Live at entry *) - live_out: PlaceSet.t; (* Live at exit *) -} - -let compute_liveness (cfg : cfg) : (cfg_node, liveness) Map.t = - (* Backward dataflow analysis *) - let rec iterate worklist state = - match worklist with - | [] -> state - | node :: rest -> - let successors = get_successors cfg node in - let live_out = union_map (fun s -> (Map.find s state).live_in) successors in - let gen = uses_of node in - let kill = defs_of node in - let live_in = PlaceSet.union gen (PlaceSet.diff live_out kill) in - let old = Map.find node state in - if PlaceSet.equal live_in old.live_in then - iterate rest state - else - let state' = Map.add node { live_in; live_out } state in - let preds = get_predecessors cfg node in - iterate (preds @ rest) state' - in - iterate (all_nodes cfg) initial_state -``` - -### Phase 3: Track Ownership - -```ocaml -type ownership_state = { - owned: PlaceSet.t; (* Currently owned *) - moved: PlaceSet.t; (* Has been moved *) - borrowed: loan list; (* Active borrows *) -} - -let rec check_ownership (state : ownership_state) (node : cfg_node) : ownership_state = - match node with - | CFG_Statement stmt -> - check_stmt state stmt - - | CFG_Branch { cond; true_; false_ } -> - let state' = check_expr state cond in - let state_t = check_ownership state' true_ in - let state_f = check_ownership state' false_ in - merge_states state_t state_f - - | CFG_Return (Some expr) -> - check_expr_moved state expr - - | CFG_Drop place -> - if PlaceSet.mem place state.moved then - () (* Already moved, nothing to drop *) - else - check_can_drop state place; - { state with owned = PlaceSet.remove place state.owned } - - | _ -> state -``` - -### Phase 4: Verify Borrows - -```ocaml -let check_borrow (state : ownership_state) (place : place) (kind : loan_kind) : loan = - (* Check place is not moved *) - if PlaceSet.mem place state.moved then - error (Borrow_of_moved place); - - (* Check no conflicting borrows *) - List.iter (fun loan -> - if places_conflict place loan.place then - match (kind, loan.kind) with - | (Mutable, _) | (_, Mutable) -> - error (Conflicting_borrow (place, loan)) - | (Shared, Shared) -> - () (* OK: multiple shared borrows *) - ) state.borrowed; - - (* Create new loan *) - let loan = { id = fresh_id (); place; kind; region = current_region () } in - loan - -let check_use (state : ownership_state) (place : place) : unit = - (* Check place is not moved *) - if PlaceSet.mem place state.moved then - error (Use_after_move place); - - (* Check any loans to this place are still valid *) - List.iter (fun loan -> - if places_conflict place loan.place && not (region_valid loan.region) then - error (Dangling_reference loan) - ) state.borrowed -``` - -### Phase 5: Insert Drops - -```ocaml -let insert_drops (cfg : cfg) (liveness : liveness_info) : cfg = - let transform_node node = - let live_before = liveness_at node in - let live_after = liveness_after node in - let to_drop = PlaceSet.diff live_before live_after in - let drops = PlaceSet.fold (fun p acc -> - CFG_Drop p :: acc - ) to_drop [] in - sequence_nodes (node :: drops) - in - map_cfg transform_node cfg -``` - -## Non-Lexical Lifetimes (NLL) - -Traditional borrow checkers use lexical scopes: - -```affine -let mut x = 5 -let r = &x // Borrow starts -println(r) -x = 6 // ERROR in lexical: r still in scope - -// With NLL: -let mut x = 5 -let r = &x // Borrow starts -println(r) // Last use of r -x = 6 // OK: r's region ended at last use -``` - -Implementation: - -```ocaml -(* Compute region constraints from usage *) -let compute_regions (cfg : cfg) (loans : loan list) : region_constraints = - let constraints = ref [] in - - (* For each loan, find its last use *) - List.iter (fun loan -> - let last_use = find_last_use cfg loan in - constraints := (loan.region, Region_Point last_use) :: !constraints - ) loans; - - (* Add outlives constraints from function signatures *) - add_signature_constraints cfg constraints; - - !constraints - -(* Solve region constraints *) -let solve_regions (constraints : region_constraints) : region_solution = - (* Fixed-point iteration *) - let rec iterate solution = - let changed = ref false in - List.iter (fun (r1, r2) -> - if not (region_outlives solution r1 r2) then begin - extend_region solution r1 r2; - changed := true - end - ) constraints; - if !changed then iterate solution else solution - in - iterate empty_solution -``` - -## Linearity Checking - -For linear types (used exactly once): - -```ocaml -let check_linearity (ctx : context) (expr : typed_expr) : unit = - let linear_vars = find_linear_vars ctx expr in - - List.iter (fun var -> - let uses = count_uses expr var in - match uses with - | 0 -> error (Linear_unused var) - | 1 -> () - | n -> error (Linear_multiple_use (var, n)) - ) linear_vars - -let count_uses (expr : typed_expr) (var : var) : int = - let count = ref 0 in - visit_expr (fun e -> - match e.kind with - | TE_Var v when v = var -> incr count - | _ -> () - ) expr; - !count -``` - -## Error Messages - -```ocaml -let format_borrow_error (err : borrow_error) : diagnostic = - match err with - | Use_after_move place -> - { - code = E0500; - message = sprintf "use of moved value: `%s`" (show_place place); - labels = [ - (place.span, "value used here after move"); - (move_location place, "value moved here"); - ]; - notes = []; - help = Some "consider using `clone()` to copy the value"; - } - - | Conflicting_borrow (place, loan) -> - { - code = E0502; - message = sprintf "cannot borrow `%s` as mutable because it is already borrowed" - (show_place place); - labels = [ - (place.span, "mutable borrow occurs here"); - (loan.span, sprintf "%s borrow occurs here" - (if loan.kind = Shared then "immutable" else "mutable")); - ]; - notes = []; - help = None; - } - - | Dangling_reference loan -> - { - code = E0597; - message = sprintf "`%s` does not live long enough" (show_place loan.place); - labels = [ - (loan.span, "borrowed value does not live long enough"); - (scope_end loan.region, "borrowed value dropped here"); - ]; - notes = []; - help = None; - } - - | Linear_unused var -> - { - code = E0550; - message = sprintf "linear value `%s` not used" var; - labels = [(var_span var, "linear value declared here but never used")]; - notes = ["linear types must be used exactly once"]; - help = Some "explicitly drop with `drop(value)` if unused"; - } -``` - -## Testing - -```ocaml -(* Should compile *) -let test_valid_borrow () = - check {| - let x = String::from("hello") - let r = &x - println(r) - |} - -(* Should error: use after move *) -let test_use_after_move () = - expect_error E0500 {| - let x = String::from("hello") - let y = x // Move - println(x) // Error! - |} - -(* Should error: conflicting borrow *) -let test_conflicting_borrow () = - expect_error E0502 {| - let mut x = 5 - let r1 = &x - let r2 = &mut x // Error! - println(r1) - |} - -(* Should compile with NLL *) -let test_nll () = - check {| - let mut x = 5 - let r = &x - println(r) // Last use of r - x = 6 // OK with NLL - |} -``` - ---- - -## See Also - -- [Architecture](architecture.md) - Compiler overview -- [Type Checker](type-checker.md) - Previous phase -- [Code Generation](codegen.md) - Next phase -- [Ownership](../language-reference/ownership.md) - Language reference diff --git a/affinescript/wiki/compiler/codegen.md b/affinescript/wiki/compiler/codegen.md deleted file mode 100644 index 3d96ebd..0000000 --- a/affinescript/wiki/compiler/codegen.md +++ /dev/null @@ -1,435 +0,0 @@ - -# Code Generation - -The AffineScript code generator emits WebAssembly from the typed and verified AST. - -## Overview - -**File**: `lib/codegen.ml` (planned) -**Target**: WebAssembly (WASM) - -## Pipeline - -``` -Typed AST - │ - ▼ -┌─────────────┐ -│ Monomorphize│ Specialize generics -└──────┬──────┘ - │ - ▼ -┌─────────────┐ -│ Lower │ Convert to IR (ANF/CPS) -└──────┬──────┘ - │ - ▼ -┌─────────────┐ -│ Closures │ Convert closures to structs -└──────┬──────┘ - │ - ▼ -┌─────────────┐ -│ Effects │ Compile effect handlers -└──────┬──────┘ - │ - ▼ -┌─────────────┐ -│ Optimize │ IR-level optimizations -└──────┬──────┘ - │ - ▼ -┌─────────────┐ -│ Emit WASM │ Generate WebAssembly -└──────┬──────┘ - │ - ▼ - .wasm file -``` - -## Intermediate Representation - -### ANF (A-Normal Form) - -All intermediate results are named: - -```ocaml -type ir_expr = - | IR_Lit of literal - | IR_Var of var - | IR_Let of var * ir_val * ir_expr - | IR_LetRec of (var * ir_val) list * ir_expr - | IR_App of var * var list - | IR_If of var * ir_expr * ir_expr - | IR_Switch of var * (pattern * ir_expr) list - | IR_Return of var - | IR_Halt - -and ir_val = - | IV_Lit of literal - | IV_Var of var - | IV_Prim of prim_op * var list - | IV_Closure of var list * ir_expr (* free vars, body *) - | IV_Record of (string * var) list - | IV_Project of var * string - | IV_Construct of ctor * var list -``` - -### Conversion to ANF - -```ocaml -let rec to_anf (expr : typed_expr) (k : var -> ir_expr) : ir_expr = - match expr.kind with - | TE_Lit lit -> - let v = fresh_var () in - IR_Let (v, IV_Lit lit, k v) - - | TE_Var x -> - k x - - | TE_Binary (e1, op, e2) -> - to_anf e1 (fun v1 -> - to_anf e2 (fun v2 -> - let v = fresh_var () in - IR_Let (v, IV_Prim (op, [v1; v2]), k v))) - - | TE_App (fn, args) -> - to_anf fn (fun fn_v -> - to_anf_list args (fun arg_vs -> - let v = fresh_var () in - IR_Let (v, IR_App (fn_v, arg_vs), k v))) - - | TE_Lambda (params, body) -> - let free = free_vars expr in - let body_anf = to_anf body (fun v -> IR_Return v) in - let v = fresh_var () in - IR_Let (v, IV_Closure (free, body_anf), k v) - - | TE_If (cond, then_, else_) -> - to_anf cond (fun cond_v -> - let result = fresh_var () in - let then_anf = to_anf then_ (fun v -> IR_Let (result, IV_Var v, k result)) in - let else_anf = to_anf else_ (fun v -> IR_Let (result, IV_Var v, k result)) in - IR_If (cond_v, then_anf, else_anf)) - - | _ -> (* more cases *) -``` - -## Closure Conversion - -Closures become structs containing captured variables: - -```ocaml -(* Before closure conversion *) -let add = |x| |y| x + y -let add5 = add(5) -add5(3) // 8 - -(* After closure conversion *) -struct Closure_add { - code: fn(env: &Self, y: Int) -> Int, -} - -struct Closure_add_inner { - x: Int, - code: fn(env: &Self, y: Int) -> Int, -} - -fn add_outer(env: &Closure_add, x: Int) -> Closure_add_inner { - Closure_add_inner { x, code: add_inner } -} - -fn add_inner(env: &Closure_add_inner, y: Int) -> Int { - env.x + y -} -``` - -Implementation: - -```ocaml -let convert_closure (free_vars : var list) (params : var list) (body : ir_expr) : ir_val * func_def = - let env_type = make_struct_type free_vars in - let env_param = fresh_var "env" in - let body' = substitute_free_vars body env_param free_vars in - let func = { - name = fresh_func_name (); - params = env_param :: params; - body = body'; - } in - let closure_val = IV_Record [ - ("code", func.name); - (* captured variables... *) - ] @ List.map (fun v -> (var_name v, v)) free_vars in - (closure_val, func) -``` - -## Effect Compilation - -Effects are compiled using CPS (Continuation-Passing Style) or evidence passing: - -### CPS Transformation - -```ocaml -(* Before *) -fn get_and_double() -{State[Int]}-> Int { - let x = get() - x * 2 -} - -(* After CPS *) -fn get_and_double(cont: (Int) -> Result, handlers: Handlers) -> Result { - handlers.state.get(|x| { - cont(x * 2) - }) -} -``` - -### Evidence Passing - -```ocaml -(* Before *) -fn program() -{IO, State[Int]}-> Unit { - let x = get() - print(show(x)) -} - -(* After evidence passing *) -fn program(io_impl: IO_Impl, state_impl: State_Impl[Int]) -> Unit { - let x = state_impl.get() - io_impl.print(show(x)) -} -``` - -## WebAssembly Emission - -### Type Mapping - -| AffineScript | WASM | -|--------------|------| -| `Int` | `i32` or `i64` | -| `Float64` | `f64` | -| `Bool` | `i32` (0 or 1) | -| `Unit` | (none) | -| `String` | `i32` (pointer) | -| `Record` | `i32` (pointer) | -| `Closure` | `i32` (pointer) | - -### WASM Generation - -```ocaml -type wasm_instr = - | I32_const of int32 - | I64_const of int64 - | F64_const of float - | Local_get of int - | Local_set of int - | Global_get of int - | Global_set of int - | I32_add | I32_sub | I32_mul | I32_div_s - | I64_add | I64_sub | I64_mul | I64_div_s - | F64_add | F64_sub | F64_mul | F64_div - | I32_eq | I32_ne | I32_lt_s | I32_gt_s | I32_le_s | I32_ge_s - | If of block_type * wasm_instr list * wasm_instr list - | Block of block_type * wasm_instr list - | Loop of block_type * wasm_instr list - | Br of int - | Br_if of int - | Call of func_idx - | Call_indirect of type_idx - | Return - | Drop - | Memory_grow - | I32_load | I32_store - | I64_load | I64_store - | F64_load | F64_store - -let rec emit_expr (expr : ir_expr) : wasm_instr list = - match expr with - | IR_Lit (Lit_Int n) -> - [I32_const (Int32.of_int n)] - - | IR_Lit (Lit_Float f) -> - [F64_const f] - - | IR_Var v -> - [Local_get (var_index v)] - - | IR_Let (v, IV_Prim (Op_Add, [v1; v2]), body) -> - [Local_get (var_index v1); - Local_get (var_index v2); - I32_add; - Local_set (var_index v)] @ - emit_expr body - - | IR_If (cond, then_, else_) -> - [Local_get (var_index cond); - If (BlockType_Val I32, - emit_expr then_, - emit_expr else_)] - - | IR_App (fn, args) -> - List.concat_map (fun a -> [Local_get (var_index a)]) args @ - [Call (func_index fn)] - - | IR_Return v -> - [Local_get (var_index v); Return] - - | _ -> (* more cases *) -``` - -### Memory Layout - -``` -┌─────────────────────────────────────────────────┐ -│ WASM Linear Memory │ -├─────────────────────────────────────────────────┤ -│ 0x00000000: Stack (grows down) │ -│ │ -│ 0x00010000: Heap start │ -│ ┌─────────────────────┐ │ -│ │ Allocation header │ │ -│ │ - size: u32 │ │ -│ │ - type tag: u32 │ │ -│ ├─────────────────────┤ │ -│ │ Object data │ │ -│ └─────────────────────┘ │ -│ │ -│ 0xXXXXXXXX: Static data (strings, etc.) │ -└─────────────────────────────────────────────────┘ -``` - -### Simple Allocator - -```ocaml -let emit_alloc (size : int) : wasm_instr list = - [ - (* Get heap pointer *) - Global_get heap_ptr_idx; - (* Save for return *) - Local_tee temp_idx; - (* Bump heap pointer *) - I32_const (Int32.of_int size); - I32_add; - Global_set heap_ptr_idx; - (* Return old pointer *) - Local_get temp_idx; - ] -``` - -## Optimizations - -### Constant Folding - -```ocaml -let rec fold_constants (expr : ir_expr) : ir_expr = - match expr with - | IR_Let (v, IV_Prim (Op_Add, [v1; v2]), body) -> - (match (lookup v1, lookup v2) with - | (Some (IV_Lit (Lit_Int n1)), Some (IV_Lit (Lit_Int n2))) -> - IR_Let (v, IV_Lit (Lit_Int (n1 + n2)), fold_constants body) - | _ -> IR_Let (v, IV_Prim (Op_Add, [v1; v2]), fold_constants body)) - | _ -> (* recurse *) -``` - -### Dead Code Elimination - -```ocaml -let eliminate_dead_code (funcs : func_def list) : func_def list = - let used = compute_used_vars funcs in - List.map (fun f -> - { f with body = filter_dead f.body used } - ) funcs -``` - -### Inlining - -```ocaml -let should_inline (func : func_def) : bool = - func.size < inline_threshold && not func.is_recursive - -let inline_call (call_site : ir_expr) (func : func_def) : ir_expr = - substitute func.body func.params (get_args call_site) -``` - -### Tail Call Optimization - -```ocaml -let rec is_tail_call (expr : ir_expr) (func_name : string) : bool = - match expr with - | IR_Return (IR_App (fn, _)) when fn = func_name -> true - | IR_If (_, t, e) -> is_tail_call t func_name && is_tail_call e func_name - | IR_Let (_, _, body) -> is_tail_call body func_name - | _ -> false - -let optimize_tail_calls (func : func_def) : func_def = - if is_tail_recursive func then - convert_to_loop func - else - func -``` - -## Output Formats - -### WASM Text Format (.wat) - -```wat -(module - (memory 1) - (global $heap_ptr (mut i32) (i32.const 65536)) - - (func $add (param $x i32) (param $y i32) (result i32) - local.get $x - local.get $y - i32.add - ) - - (func $main (result i32) - i32.const 1 - i32.const 2 - call $add - ) - - (export "main" (func $main)) -) -``` - -### WASM Binary Format (.wasm) - -```ocaml -let emit_wasm_binary (module_ : wasm_module) : bytes = - let buf = Buffer.create 1024 in - - (* Magic number *) - Buffer.add_string buf "\x00asm"; - - (* Version *) - emit_u32_leb128 buf 1; - - (* Type section *) - emit_section buf 1 (emit_types module_.types); - - (* Function section *) - emit_section buf 3 (emit_func_types module_.funcs); - - (* Memory section *) - emit_section buf 5 (emit_memory module_.memory); - - (* Export section *) - emit_section buf 7 (emit_exports module_.exports); - - (* Code section *) - emit_section buf 10 (emit_code module_.funcs); - - Buffer.to_bytes buf -``` - ---- - -## See Also - -- [Architecture](architecture.md) - Compiler overview -- [Borrow Checker](borrow-checker.md) - Previous phase -- [WASM Spec](https://webassembly.github.io/spec/) - WebAssembly specification diff --git a/affinescript/wiki/compiler/lexer.md b/affinescript/wiki/compiler/lexer.md deleted file mode 100644 index ab7e28a..0000000 --- a/affinescript/wiki/compiler/lexer.md +++ /dev/null @@ -1,364 +0,0 @@ - -# Lexer Implementation - -The AffineScript lexer converts source text into a stream of tokens. - -## Overview - -**File**: `lib/lexer.ml` -**Library**: [sedlex](https://github.com/ocaml-community/sedlex) - Unicode-aware lexer generator - -## Token Types - -Defined in `lib/token.ml`: - -```ocaml -type token = - (* Literals *) - | INT_LIT of int - | FLOAT_LIT of float - | CHAR_LIT of char - | STRING_LIT of string - | TRUE | FALSE - - (* Identifiers *) - | IDENT of string - | UPPER_IDENT of string - - (* Keywords *) - | FN | LET | MUT | OWN | REF - | TYPE | STRUCT | ENUM | TRAIT | IMPL - | EFFECT | HANDLE | RESUME | PERFORM - | IF | ELSE | MATCH | WHILE | FOR | IN - | LOOP | RETURN | BREAK | CONTINUE - | PUB | TOTAL | UNSAFE | WHERE | AS - | MOD | USE | FROM | SELF | SUPER - - (* Operators *) - | PLUS | MINUS | STAR | SLASH | PERCENT - | EQ | NE | LT | GT | LE | GE - | AND | OR | NOT - | AMPERSAND | PIPE | CARET | TILDE - | SHL | SHR - | ASSIGN | PLUS_ASSIGN | MINUS_ASSIGN | ... - | ARROW | FAT_ARROW | THIN_ARROW - | DOT | DOTDOT | DOTDOTEQ - | QUESTION | COLON | DOUBLE_COLON - | PIPE_GT | PLUS_PLUS - - (* Punctuation *) - | LPAREN | RPAREN - | LBRACKET | RBRACKET - | LBRACE | RBRACE - | COMMA | SEMICOLON - - (* Special *) - | ROW_VAR of string (* ..name *) - | OMEGA (* w or omega *) - | EOF -``` - -## Lexer Implementation - -### Character Classes - -```ocaml -let digit = [%sedlex.regexp? '0'..'9'] -let hex_digit = [%sedlex.regexp? '0'..'9' | 'a'..'f' | 'A'..'F'] -let letter = [%sedlex.regexp? 'a'..'z' | 'A'..'Z' | '_'] -let ident_char = [%sedlex.regexp? letter | digit | '_'] -let newline = [%sedlex.regexp? '\n' | '\r' | "\r\n"] -let whitespace = [%sedlex.regexp? ' ' | '\t' | newline] -``` - -### Keyword Recognition - -```ocaml -let keyword_or_ident = function - | "fn" -> FN - | "let" -> LET - | "mut" -> MUT - | "own" -> OWN - | "ref" -> REF - | "type" -> TYPE - | "struct" -> STRUCT - | "enum" -> ENUM - | "trait" -> TRAIT - | "impl" -> IMPL - | "effect" -> EFFECT - | "handle" -> HANDLE - | "resume" -> RESUME - | "perform" -> PERFORM - | "if" -> IF - | "else" -> ELSE - | "match" -> MATCH - | "while" -> WHILE - | "for" -> FOR - | "in" -> IN - | "loop" -> LOOP - | "return" -> RETURN - | "break" -> BREAK - | "continue" -> CONTINUE - | "pub" -> PUB - | "total" -> TOTAL - | "unsafe" -> UNSAFE - | "where" -> WHERE - | "as" -> AS - | "mod" -> MOD - | "use" -> USE - | "from" -> FROM - | "Self" -> SELF_TYPE - | "self" -> SELF - | "super" -> SUPER - | "true" -> TRUE - | "false" -> FALSE - | "omega" | "w" -> OMEGA - | s -> IDENT s -``` - -### Main Lexer Function - -```ocaml -let rec token buf = - match%sedlex buf with - (* Whitespace *) - | Plus whitespace -> token buf - - (* Comments *) - | "//" -> line_comment buf; token buf - | "/*" -> block_comment buf 1; token buf - - (* Identifiers and keywords *) - | letter, Star ident_char -> - let s = Sedlexing.Utf8.lexeme buf in - keyword_or_ident s - - (* Numeric literals *) - | "0x", Plus hex_digit -> INT_LIT (parse_hex (Sedlexing.Utf8.lexeme buf)) - | "0b", Plus ('0' | '1') -> INT_LIT (parse_binary (Sedlexing.Utf8.lexeme buf)) - | "0o", Plus ('0'..'7') -> INT_LIT (parse_octal (Sedlexing.Utf8.lexeme buf)) - | Plus digit, '.', Plus digit -> FLOAT_LIT (float_of_string (Sedlexing.Utf8.lexeme buf)) - | Plus digit -> INT_LIT (int_of_string (Sedlexing.Utf8.lexeme buf)) - - (* String and character literals *) - | '"' -> STRING_LIT (string_literal buf (Buffer.create 64)) - | '\'' -> CHAR_LIT (char_literal buf) - - (* Row variables *) - | "..", letter, Star ident_char -> - let s = Sedlexing.Utf8.lexeme buf in - ROW_VAR (String.sub s 2 (String.length s - 2)) - - (* Multi-character operators *) - | "->" -> ARROW - | "=>" -> FAT_ARROW - | "-{" -> EFFECT_ARROW_START - | "}>" -> EFFECT_ARROW_END - | "==" -> EQ - | "!=" -> NE - | "<=" -> LE - | ">=" -> GE - | "&&" -> AND - | "||" -> OR - | "<<" -> SHL - | ">>" -> SHR - | "::" -> DOUBLE_COLON - | ".." -> DOTDOT - | "..=" -> DOTDOTEQ - | "|>" -> PIPE_GT - | "++" -> PLUS_PLUS - | "+=" -> PLUS_ASSIGN - | "-=" -> MINUS_ASSIGN - | "*=" -> STAR_ASSIGN - | "/=" -> SLASH_ASSIGN - - (* Single-character operators *) - | '+' -> PLUS - | '-' -> MINUS - | '*' -> STAR - | '/' -> SLASH - | '%' -> PERCENT - | '<' -> LT - | '>' -> GT - | '=' -> ASSIGN - | '!' -> NOT - | '&' -> AMPERSAND - | '|' -> PIPE - | '^' -> CARET - | '~' -> TILDE - | '?' -> QUESTION - | '.' -> DOT - | ':' -> COLON - | '@' -> AT - - (* Punctuation *) - | '(' -> LPAREN - | ')' -> RPAREN - | '[' -> LBRACKET - | ']' -> RBRACKET - | '{' -> LBRACE - | '}' -> RBRACE - | ',' -> COMMA - | ';' -> SEMICOLON - - (* End of file *) - | eof -> EOF - - (* Error *) - | any -> - let c = Sedlexing.Utf8.lexeme buf in - raise (Lexer_error (unexpected_char c, current_pos buf)) - | _ -> assert false -``` - -### String Literal Parsing - -```ocaml -and string_literal buf acc = - match%sedlex buf with - | '"' -> Buffer.contents acc - | '\\', 'n' -> Buffer.add_char acc '\n'; string_literal buf acc - | '\\', 't' -> Buffer.add_char acc '\t'; string_literal buf acc - | '\\', 'r' -> Buffer.add_char acc '\r'; string_literal buf acc - | '\\', '\\' -> Buffer.add_char acc '\\'; string_literal buf acc - | '\\', '"' -> Buffer.add_char acc '"'; string_literal buf acc - | '\\', '0' -> Buffer.add_char acc '\000'; string_literal buf acc - | '\\', 'x', hex_digit, hex_digit -> - let s = Sedlexing.Utf8.lexeme buf in - let code = int_of_string ("0x" ^ String.sub s 2 2) in - Buffer.add_char acc (Char.chr code); - string_literal buf acc - | '\\', "u{", Plus hex_digit, '}' -> - let s = Sedlexing.Utf8.lexeme buf in - let hex = String.sub s 3 (String.length s - 4) in - let code = int_of_string ("0x" ^ hex) in - Buffer.add_utf_8_uchar acc (Uchar.of_int code); - string_literal buf acc - | newline -> raise (Lexer_error (unterminated_string, current_pos buf)) - | eof -> raise (Lexer_error (unterminated_string, current_pos buf)) - | any -> - Buffer.add_string acc (Sedlexing.Utf8.lexeme buf); - string_literal buf acc - | _ -> assert false -``` - -### Comment Handling - -```ocaml -and line_comment buf = - match%sedlex buf with - | newline | eof -> () - | any -> line_comment buf - | _ -> assert false - -and block_comment buf depth = - match%sedlex buf with - | "*/" -> - if depth = 1 then () - else block_comment buf (depth - 1) - | "/*" -> block_comment buf (depth + 1) - | eof -> raise (Lexer_error (unterminated_comment, current_pos buf)) - | any -> block_comment buf depth - | _ -> assert false -``` - -## Source Location Tracking - -```ocaml -type position = { - line: int; - column: int; - offset: int; -} - -type span = { - start: position; - end_: position; -} - -let current_pos buf = - let pos = Sedlexing.lexing_position buf in - { - line = pos.pos_lnum; - column = pos.pos_cnum - pos.pos_bol; - offset = pos.pos_cnum; - } - -let make_span start end_ = { start; end_ } - -let merge_spans s1 s2 = { - start = s1.start; - end_ = s2.end_; -} -``` - -## Error Handling - -```ocaml -exception Lexer_error of string * position - -let unexpected_char c = - Printf.sprintf "unexpected character '%s'" c - -let unterminated_string = - "unterminated string literal" - -let unterminated_comment = - "unterminated block comment" - -let invalid_escape seq = - Printf.sprintf "invalid escape sequence '\\%s'" seq -``` - -## Testing - -Test cases in `test/test_lexer.ml`: - -```ocaml -let test_keywords () = - let tokens = lex "fn let mut own ref type struct enum" in - Alcotest.(check (list token_testable)) - "keywords" - [FN; LET; MUT; OWN; REF; TYPE; STRUCT; ENUM; EOF] - tokens - -let test_operators () = - let tokens = lex "+ - * / == != && || ->" in - Alcotest.(check (list token_testable)) - "operators" - [PLUS; MINUS; STAR; SLASH; EQ; NE; AND; OR; ARROW; EOF] - tokens - -let test_string_escapes () = - let tokens = lex {|"hello\n\tworld"|} in - Alcotest.(check (list token_testable)) - "string escapes" - [STRING_LIT "hello\n\tworld"; EOF] - tokens - -let test_nested_comments () = - let tokens = lex "/* outer /* inner */ outer */ x" in - Alcotest.(check (list token_testable)) - "nested comments" - [IDENT "x"; EOF] - tokens -``` - -## Future Enhancements - -1. **Raw strings**: `r"no \escapes"`, `r#"contains "quotes""#` -2. **String interpolation**: `"Hello, ${name}!"` -3. **Doc comments**: `///` and `//!` -4. **Attributes**: `#[inline]`, `#[derive(...)]` -5. **Better error recovery**: Continue lexing after errors - ---- - -## See Also - -- [Architecture](architecture.md) - Compiler overview -- [Parser](parser.md) - Next phase -- [Tokens](../../lib/token.ml) - Token definitions diff --git a/affinescript/wiki/compiler/parser.md b/affinescript/wiki/compiler/parser.md deleted file mode 100644 index 42532e2..0000000 --- a/affinescript/wiki/compiler/parser.md +++ /dev/null @@ -1,340 +0,0 @@ - -# Parser Implementation - -The AffineScript parser converts tokens into a Concrete Syntax Tree (CST). - -## Overview - -**File**: `lib/parser.ml` (planned) -**Library**: [Menhir](http://gallium.inria.fr/~fpottier/menhir/) - LR(1) parser generator - -## Grammar Structure - -### Program - -```menhir -program: - | item* EOF { Program $1 } - -item: - | function_def { Item_Fn $1 } - | type_def { Item_Type $1 } - | struct_def { Item_Struct $1 } - | enum_def { Item_Enum $1 } - | trait_def { Item_Trait $1 } - | impl_block { Item_Impl $1 } - | effect_def { Item_Effect $1 } - | module_def { Item_Mod $1 } - | use_stmt { Item_Use $1 } -``` - -### Expressions - -```menhir -expr: - | literal { Expr_Lit $1 } - | IDENT { Expr_Var $1 } - | expr LPAREN args RPAREN { Expr_App ($1, $3) } - | expr DOT IDENT { Expr_Field ($1, $3) } - | expr DOT IDENT LPAREN args RPAREN { Expr_Method ($1, $3, $5) } - | expr binop expr { Expr_Binary ($1, $2, $3) } - | unop expr { Expr_Unary ($1, $2) } - | IF expr block else_branch { Expr_If ($2, $3, $4) } - | MATCH expr LBRACE arms RBRACE { Expr_Match ($2, $4) } - | PIPE params PIPE expr { Expr_Lambda ($2, $4) } - | PIPE params PIPE block { Expr_Lambda ($2, Block $4) } - | block { Expr_Block $1 } - | LPAREN expr RPAREN { $2 } - | LPAREN exprs RPAREN { Expr_Tuple $2 } - | LBRACKET exprs RBRACKET { Expr_Array $2 } - | LBRACE fields RBRACE { Expr_Record $2 } - | HANDLE expr LBRACE handlers RBRACE { Expr_Handle ($2, $4) } - | expr QUESTION { Expr_Try $1 } - | expr AS type_expr { Expr_Cast ($1, $3) } - -binop: - | PLUS { Op_Add } - | MINUS { Op_Sub } - | STAR { Op_Mul } - | SLASH { Op_Div } - | PERCENT { Op_Mod } - | EQ { Op_Eq } - | NE { Op_Ne } - | LT { Op_Lt } - | GT { Op_Gt } - | LE { Op_Le } - | GE { Op_Ge } - | AND { Op_And } - | OR { Op_Or } - | AMPERSAND { Op_BitAnd } - | PIPE { Op_BitOr } - | CARET { Op_BitXor } - | SHL { Op_Shl } - | SHR { Op_Shr } - | PLUS_PLUS { Op_Concat } - | PIPE_GT { Op_Pipe } - -unop: - | MINUS { Op_Neg } - | NOT { Op_Not } - | TILDE { Op_BitNot } - | AMPERSAND { Op_Ref } - | AMPERSAND MUT { Op_RefMut } - | STAR { Op_Deref } -``` - -### Types - -```menhir -type_expr: - | IDENT { Type_Named $1 } - | UPPER_IDENT { Type_Named $1 } - | type_expr LBRACKET type_args RBRACKET { Type_App ($1, $3) } - | LPAREN type_exprs RPAREN { Type_Tuple $2 } - | LPAREN RPAREN { Type_Unit } - | type_expr ARROW type_expr { Type_Fn ($1, $3) } - | type_expr effect_arrow type_expr { Type_FnEff ($1, $2, $3) } - | OWN type_expr { Type_Own $2 } - | REF type_expr { Type_Ref $2 } - | MUT REF type_expr { Type_MutRef $3 } - | LBRACE row_fields RBRACE { Type_Record $2 } - | type_expr WHERE LPAREN expr RPAREN { Type_Refined ($1, $4) } - -effect_arrow: - | MINUS LBRACE effects RBRACE ARROW { $3 } - -effects: - | effect_list { Effects $1 } - | effect_list COMMA ROW_VAR { Effects_Row ($1, $3) } - -row_fields: - | field_list { Row_Closed $1 } - | field_list COMMA ROW_VAR { Row_Open ($1, $3) } -``` - -### Patterns - -```menhir -pattern: - | UNDERSCORE { Pat_Wildcard } - | IDENT { Pat_Var $1 } - | literal { Pat_Lit $1 } - | UPPER_IDENT { Pat_Ctor ($1, []) } - | UPPER_IDENT LPAREN patterns RPAREN { Pat_Ctor ($1, $3) } - | LPAREN patterns RPAREN { Pat_Tuple $2 } - | LBRACE pat_fields RBRACE { Pat_Record $2 } - | pattern PIPE pattern { Pat_Or ($1, $3) } - | IDENT AT pattern { Pat_Bind ($1, $3) } - | pattern COLON type_expr { Pat_Typed ($1, $3) } -``` - -### Statements - -```menhir -stmt: - | LET pattern ASSIGN expr SEMICOLON { Stmt_Let ($2, $4) } - | LET pattern COLON type_expr ASSIGN expr SEMICOLON - { Stmt_LetTyped ($2, $4, $6) } - | expr ASSIGN expr SEMICOLON { Stmt_Assign ($1, $3) } - | expr SEMICOLON { Stmt_Expr $1 } - | WHILE expr block { Stmt_While ($2, $3) } - | FOR pattern IN expr block { Stmt_For ($2, $4, $5) } - | RETURN expr SEMICOLON { Stmt_Return (Some $2) } - | RETURN SEMICOLON { Stmt_Return None } - | BREAK SEMICOLON { Stmt_Break } - | CONTINUE SEMICOLON { Stmt_Continue } - -block: - | LBRACE stmts expr? RBRACE { Block ($2, $3) } -``` - -### Declarations - -```menhir -function_def: - | visibility? TOTAL? FN IDENT type_params? LPAREN params RPAREN - return_type? where_clause? block - { { - vis = $1; - total = $2 <> None; - name = $4; - type_params = $5; - params = $7; - ret = $9; - where_clause = $10; - body = $11; - } } - -struct_def: - | visibility? STRUCT IDENT type_params? where_clause? - LBRACE struct_fields RBRACE - { { vis = $1; name = $3; type_params = $4; where_clause = $5; fields = $7 } } - -enum_def: - | visibility? ENUM IDENT type_params? where_clause? - LBRACE variants RBRACE - { { vis = $1; name = $3; type_params = $4; where_clause = $5; variants = $7 } } - -trait_def: - | visibility? TRAIT IDENT type_params? supertraits? where_clause? - LBRACE trait_items RBRACE - { { vis = $1; name = $3; type_params = $4; super = $5; - where_clause = $6; items = $8 } } - -impl_block: - | IMPL type_params? type_expr FOR type_expr where_clause? - LBRACE impl_items RBRACE - { Impl_Trait { type_params = $2; trait_ = $3; for_ = $5; - where_clause = $6; items = $8 } } - | IMPL type_params? type_expr where_clause? - LBRACE impl_items RBRACE - { Impl_Inherent { type_params = $2; type_ = $3; - where_clause = $4; items = $6 } } - -effect_def: - | visibility? EFFECT IDENT type_params? LBRACE effect_ops RBRACE - { { vis = $1; name = $3; type_params = $4; ops = $6 } } -``` - -## Operator Precedence - -Defined via Menhir precedence declarations: - -```menhir -%left OR -%left AND -%left PIPE -%left CARET -%left AMPERSAND -%left EQ NE -%left LT GT LE GE -%left SHL SHR -%left PLUS MINUS PLUS_PLUS -%left STAR SLASH PERCENT -%right NOT TILDE UMINUS -%left DOT LBRACKET LPAREN -``` - -## Error Recovery - -Menhir supports error recovery via: - -```menhir -%on_error_reduce expr stmt - -stmt: - | error SEMICOLON { Stmt_Error } - -expr: - | error { Expr_Error } -``` - -## Concrete Syntax Tree - -The CST preserves all syntactic information: - -```ocaml -type cst_expr = { - kind: cst_expr_kind; - span: span; -} - -and cst_expr_kind = - | CST_Lit of literal - | CST_Var of string - | CST_Binary of cst_expr * binop * cst_expr - | CST_Unary of unop * cst_expr - | CST_App of cst_expr * cst_expr list - | CST_Field of cst_expr * string - | CST_Method of cst_expr * string * cst_expr list - | CST_If of cst_expr * cst_block * cst_else option - | CST_Match of cst_expr * cst_arm list - | CST_Lambda of cst_param list * cst_expr - | CST_Block of cst_block - | CST_Tuple of cst_expr list - | CST_Array of cst_expr list - | CST_Record of cst_field list - | CST_Handle of cst_expr * cst_handler list - | CST_Error -``` - -## AST Transformation - -The CST is transformed to a cleaner AST: - -```ocaml -let rec desugar_expr (cst : cst_expr) : Ast.expr = - match cst.kind with - | CST_Lit lit -> { kind = E_Lit lit; span = cst.span } - - | CST_Binary (e1, Op_Pipe, e2) -> - (* x |> f becomes f(x) *) - desugar_expr { kind = CST_App (e2, [e1]); span = cst.span } - - | CST_If (cond, then_, None) -> - (* if without else returns Unit *) - { kind = E_If (desugar_expr cond, - desugar_block then_, - { kind = E_Unit; span = cst.span }); - span = cst.span } - - (* ... more cases ... *) -``` - -## Implementation Plan - -### Phase 1: Core Expressions -- Literals, variables, operators -- Parentheses, tuples -- Function application -- Blocks - -### Phase 2: Types -- Named types, type application -- Function types, tuple types -- Ownership modifiers - -### Phase 3: Patterns -- Wildcards, variables, literals -- Constructors, tuples, records -- Or-patterns, binding - -### Phase 4: Declarations -- Functions, structs, enums -- Type aliases -- Imports - -### Phase 5: Advanced -- Traits and impls -- Effects and handlers -- Dependent types -- Row types - -## Testing - -```ocaml -let test_parse_expr () = - let cst = parse_expr "1 + 2 * 3" in - match cst.kind with - | CST_Binary (_, Op_Add, CST_Binary (_, Op_Mul, _)) -> - () (* Correct precedence *) - | _ -> Alcotest.fail "wrong precedence" - -let test_parse_function () = - let cst = parse "fn add(x: Int, y: Int) -> Int { x + y }" in - match cst with - | [Item_Fn { name = "add"; params = [_; _]; _ }] -> () - | _ -> Alcotest.fail "wrong parse" -``` - ---- - -## See Also - -- [Architecture](architecture.md) - Compiler overview -- [Lexer](lexer.md) - Previous phase -- [Type Checker](type-checker.md) - Next phase -- [AST](../../lib/ast.ml) - AST definitions diff --git a/affinescript/wiki/compiler/type-checker.md b/affinescript/wiki/compiler/type-checker.md deleted file mode 100644 index b7ec419..0000000 --- a/affinescript/wiki/compiler/type-checker.md +++ /dev/null @@ -1,410 +0,0 @@ - -# Type Checker - -The AffineScript type checker implements bidirectional type inference with support for dependent types, row polymorphism, and effects. - -## Overview - -**File**: `lib/typecheck.ml` (planned) -**Algorithm**: Bidirectional type checking with constraint solving - -## Architecture - -``` -┌─────────────────────────────────────────────────┐ -│ Type Checker │ -├─────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────┐ ┌─────────────┐ │ -│ │Kind Checker │ │ Unifier │ │ -│ └──────┬──────┘ └──────┬──────┘ │ -│ │ │ │ -│ ▼ ▼ │ -│ ┌────────────────────────────┐ │ -│ │ Bidirectional Checker │ │ -│ │ ├── synth (infer) │ │ -│ │ └── check (verify) │ │ -│ └─────────────┬──────────────┘ │ -│ │ │ -│ ┌──────┴──────┐ │ -│ ▼ ▼ │ -│ ┌───────────┐ ┌───────────┐ │ -│ │ Quantity │ │ Effect │ │ -│ │ Checker │ │ Inferencer│ │ -│ └───────────┘ └───────────┘ │ -│ │ -└─────────────────────────────────────────────────┘ -``` - -## Type Representation - -```ocaml -type typ = - (* Base types *) - | T_Int | T_Float | T_Bool | T_Char | T_String | T_Unit | T_Never - - (* Type constructors *) - | T_Named of path (* Named type *) - | T_App of typ * typ list (* Type application *) - | T_Var of int (* Type variable (de Bruijn) *) - | T_Meta of meta ref (* Unification variable *) - - (* Composite types *) - | T_Tuple of typ list (* (A, B, C) *) - | T_Record of row (* {x: A, y: B, ..r} *) - | T_Variant of row (* [A | B | ..r] *) - - (* Function types *) - | T_Arrow of typ * typ * effects (* A -{E}-> B *) - | T_Forall of string * kind * typ (* forall a. T *) - | T_Pi of string * typ * typ (* (x: A) -> B *) - - (* Ownership *) - | T_Own of typ (* own T *) - | T_Ref of typ (* &T *) - | T_MutRef of typ (* &mut T *) - - (* Advanced *) - | T_Refined of typ * expr (* T where P *) - | T_Erased of typ (* 0 T (compile-time only) *) - | T_Linear of typ (* 1 T (used exactly once) *) - -and row = - | Row_Empty - | Row_Extend of string * typ * row - | Row_Var of int - -and effects = - | Eff_Pure - | Eff_Row of effect_row - -and meta = - | Unsolved of int * kind - | Solved of typ -``` - -## Bidirectional Type Checking - -### Core Judgments - -``` -Γ ⊢ e ⇒ A (synthesis/inference) -Γ ⊢ e ⇐ A (checking) -``` - -### Implementation - -```ocaml -(* Synthesis: infer type from expression *) -let rec synth (ctx : context) (expr : expr) : typ * typed_expr = - match expr.kind with - | E_Lit (Lit_Int n) -> - (T_Int, { kind = TE_Lit (Lit_Int n); typ = T_Int; span = expr.span }) - - | E_Lit (Lit_Float f) -> - (T_Float, { kind = TE_Lit (Lit_Float f); typ = T_Float; span = expr.span }) - - | E_Lit (Lit_String s) -> - (T_String, { kind = TE_Lit (Lit_String s); typ = T_String; span = expr.span }) - - | E_Var name -> - let typ = Context.lookup ctx name in - (typ, { kind = TE_Var name; typ; span = expr.span }) - - | E_App (fn, args) -> - let (fn_typ, fn_te) = synth ctx fn in - synth_app ctx fn_te fn_typ args expr.span - - | E_Field (record, field) -> - let (rec_typ, rec_te) = synth ctx record in - synth_field ctx rec_te rec_typ field expr.span - - | E_Binary (e1, op, e2) -> - let (t1, te1) = synth ctx e1 in - let (t2, te2) = synth ctx e2 in - let result_typ = check_binop op t1 t2 in - (result_typ, { kind = TE_Binary (te1, op, te2); typ = result_typ; span = expr.span }) - - | E_Annot (e, typ) -> - let te = check ctx e typ in - (typ, te) - - | E_Lambda _ -> - error expr.span "Cannot infer type of lambda; add annotation" - - | _ -> - (* More cases... *) - -(* Checking: verify expression has expected type *) -and check (ctx : context) (expr : expr) (expected : typ) : typed_expr = - match (expr.kind, expected) with - | (E_Lambda (params, body), T_Arrow (arg_ty, ret_ty, eff)) -> - let ctx' = Context.add_params ctx params arg_ty in - let body_te = check ctx' body ret_ty in - { kind = TE_Lambda (params, body_te); - typ = expected; - span = expr.span } - - | (E_If (cond, then_, else_), _) -> - let cond_te = check ctx cond T_Bool in - let then_te = check ctx then_ expected in - let else_te = check ctx else_ expected in - { kind = TE_If (cond_te, then_te, else_te); - typ = expected; - span = expr.span } - - | (E_Match (scrutinee, arms), _) -> - let (scrut_typ, scrut_te) = synth ctx scrutinee in - let arms_te = List.map (check_arm ctx scrut_typ expected) arms in - { kind = TE_Match (scrut_te, arms_te); - typ = expected; - span = expr.span } - - | _ -> - (* Fall back to synthesis + subsumption *) - let (inferred, te) = synth ctx expr in - subsume ctx inferred expected; - te -``` - -## Unification - -```ocaml -let rec unify (ctx : context) (t1 : typ) (t2 : typ) : unit = - match (t1, t2) with - | (T_Meta { contents = Solved t1' }, _) -> unify ctx t1' t2 - | (_, T_Meta { contents = Solved t2' }) -> unify ctx t1 t2' - - | (T_Meta ({ contents = Unsolved (id1, k1) } as r1), - T_Meta ({ contents = Unsolved (id2, k2) })) when id1 = id2 -> - () (* Same variable *) - - | (T_Meta ({ contents = Unsolved (id, k) } as r), t) - | (t, T_Meta ({ contents = Unsolved (id, k) } as r)) -> - occurs_check id t; - kind_check ctx t k; - r := Solved t - - | (T_Int, T_Int) | (T_Float, T_Float) | (T_Bool, T_Bool) - | (T_String, T_String) | (T_Unit, T_Unit) -> - () - - | (T_Arrow (a1, r1, e1), T_Arrow (a2, r2, e2)) -> - unify ctx a1 a2; - unify ctx r1 r2; - unify_effects ctx e1 e2 - - | (T_Tuple ts1, T_Tuple ts2) when List.length ts1 = List.length ts2 -> - List.iter2 (unify ctx) ts1 ts2 - - | (T_Record row1, T_Record row2) -> - unify_rows ctx row1 row2 - - | (T_App (f1, args1), T_App (f2, args2)) -> - unify ctx f1 f2; - List.iter2 (unify ctx) args1 args2 - - | _ -> - type_error (Type_mismatch (t1, t2)) -``` - -## Row Unification - -```ocaml -let rec unify_rows (ctx : context) (r1 : row) (r2 : row) : unit = - match (r1, r2) with - | (Row_Empty, Row_Empty) -> () - - | (Row_Var v1, Row_Var v2) when v1 = v2 -> () - - | (Row_Extend (l1, t1, r1'), Row_Extend (l2, t2, r2')) when l1 = l2 -> - unify ctx t1 t2; - unify_rows ctx r1' r2' - - | (Row_Extend (l1, t1, r1'), r2) -> - (* Rewrite r2 to match structure of r1 *) - let (t2, r2') = row_extract l1 r2 in - unify ctx t1 t2; - unify_rows ctx r1' r2' - - | (Row_Var v, Row_Empty) -> - solve_row_var v Row_Empty - - | (Row_Var v, Row_Extend (l, t, r)) -> - (* v ~ {l: t, ..r'} where r' is fresh *) - let r' = fresh_row_var () in - solve_row_var v (Row_Extend (l, t, r')); - unify_rows ctx r' r - - | _ -> - type_error Row_mismatch -``` - -## Kind Checking - -```ocaml -type kind = - | K_Type (* * - types of values *) - | K_Row (* Row - row kinds *) - | K_Effect (* Effect - effect kinds *) - | K_Arrow of kind * kind (* k1 -> k2 *) - | K_Nat (* Natural numbers for dependent types *) - -let rec kind_of (ctx : context) (typ : typ) : kind = - match typ with - | T_Int | T_Float | T_Bool | T_Char | T_String | T_Unit | T_Never -> - K_Type - - | T_Named path -> - Context.lookup_type_kind ctx path - - | T_App (f, args) -> - let k = kind_of ctx f in - kind_apply ctx k args - - | T_Arrow (_, _, _) | T_Forall (_, _, _) | T_Pi (_, _, _) -> - K_Type - - | T_Record row | T_Variant row -> - check_row_kind ctx row; - K_Type - - | T_Meta { contents = Unsolved (_, k) } -> k - | T_Meta { contents = Solved t } -> kind_of ctx t - - | _ -> K_Type -``` - -## Effect Inference - -```ocaml -let rec infer_effects (ctx : context) (expr : expr) : effects = - match expr.kind with - | E_Lit _ | E_Var _ -> - Eff_Pure - - | E_App (fn, args) -> - let fn_eff = infer_effects ctx fn in - let args_eff = List.map (infer_effects ctx) args |> merge_effects in - let call_eff = effect_of_call ctx fn in - merge_effects [fn_eff; args_eff; call_eff] - - | E_Binary (e1, _, e2) -> - merge_effects [infer_effects ctx e1; infer_effects ctx e2] - - | E_If (cond, then_, else_) -> - merge_effects [ - infer_effects ctx cond; - infer_effects ctx then_; - infer_effects ctx else_ - ] - - | E_Handle (body, handlers) -> - let body_eff = infer_effects ctx body in - let handled = effects_handled_by handlers in - effect_subtract body_eff handled - - | _ -> Eff_Pure -``` - -## Quantity Checking - -```ocaml -type quantity = Zero | One | Many - -let rec check_quantity (ctx : context) (expr : typed_expr) : unit = - match expr.typ with - | T_Linear inner -> - let uses = count_uses ctx expr in - if uses <> 1 then - error expr.span (Linear_use_error (uses, 1)) - - | T_Own inner -> - let uses = count_uses ctx expr in - if uses > 1 then - error expr.span (Affine_use_error uses) - - | T_Erased _ -> - let uses = count_runtime_uses ctx expr in - if uses > 0 then - error expr.span Erased_at_runtime - - | _ -> () -``` - -## Dependent Type Support - -```ocaml -(* Type-level evaluation for dependent types *) -let rec eval_type (ctx : context) (typ : typ) : typ = - match typ with - | T_App (T_Named ["Vec"], [n; t]) -> - (* Vec[n, T] - n should be a Nat *) - let n' = eval_nat ctx n in - T_App (T_Named ["Vec"], [n'; eval_type ctx t]) - - | T_Pi (x, a, b) -> - T_Pi (x, eval_type ctx a, b) (* Don't eval b yet *) - - | T_Refined (t, pred) -> - T_Refined (eval_type ctx t, pred) - - | _ -> typ - -(* Check refinement predicates via SMT *) -let check_refinement (ctx : context) (value : expr) (pred : expr) : unit = - let smt_ctx = translate_context ctx in - let smt_pred = translate_pred ctx value pred in - match Smt.check_sat smt_ctx (Smt.Not smt_pred) with - | Smt.Unsat -> () (* Refinement holds *) - | Smt.Sat model -> - error value.span (Refinement_violated (pred, model)) - | Smt.Unknown -> - warn value.span (Refinement_unknown pred) -``` - -## Error Messages - -```ocaml -let format_type_error (err : type_error) : diagnostic = - match err with - | Type_mismatch (expected, actual) -> - { - code = E0300; - message = sprintf "type mismatch"; - labels = [ - (actual.span, sprintf "expected `%s`, found `%s`" - (show_type expected) (show_type actual.typ)) - ]; - notes = []; - help = Some (suggest_fix expected actual); - } - - | Linear_use_error (actual, expected) -> - { - code = E0500; - message = sprintf "linear type used %d times, expected %d" actual expected; - labels = [(* usage sites *)]; - notes = ["linear types must be used exactly once"]; - help = None; - } - - | Row_field_missing (field, row) -> - { - code = E0350; - message = sprintf "record missing field `%s`" field; - (* ... *) - } -``` - ---- - -## See Also - -- [Architecture](architecture.md) - Compiler overview -- [Parser](parser.md) - Previous phase -- [Borrow Checker](borrow-checker.md) - Next phase -- [Types](../language-reference/types.md) - Type system reference diff --git a/affinescript/wiki/language-reference/dependent-types.md b/affinescript/wiki/language-reference/dependent-types.md deleted file mode 100644 index 9617833..0000000 --- a/affinescript/wiki/language-reference/dependent-types.md +++ /dev/null @@ -1,459 +0,0 @@ - -# Dependent Types - -Dependent types allow types to depend on values, enabling compile-time verification of invariants. - -## Table of Contents - -1. [Introduction](#introduction) -2. [Indexed Types](#indexed-types) -3. [Pi Types](#pi-types) -4. [Refinement Types](#refinement-types) -5. [Proof Terms](#proof-terms) -6. [Type-Level Computation](#type-level-computation) -7. [Practical Examples](#practical-examples) - ---- - -## Introduction - -### What Are Dependent Types? - -In most languages, types and values are separate: -```affine -// Non-dependent: Type doesn't know about length -fn head[T](vec: Vec[T]) -> Option[T] -``` - -With dependent types, types can mention values: -```affine -// Dependent: Type knows length is at least 1 -fn head[n: Nat, T](vec: Vec[n + 1, T]) -> T -``` - -### Why Dependent Types? - -1. **Eliminate runtime errors** - Prove properties at compile time -2. **Self-documenting code** - Types express invariants -3. **Safer refactoring** - Compiler catches more mistakes -4. **Better optimization** - Compiler knows more - ---- - -## Indexed Types - -### Length-Indexed Vectors - -```affine -// Vec is indexed by its length -struct Vec[n: Nat, T] { - data: [T; n] -} - -// Empty vector has length 0 -fn empty[T]() -> Vec[0, T] { - Vec { data: [] } -} - -// Cons increases length by 1 -fn cons[n: Nat, T](x: T, xs: Vec[n, T]) -> Vec[n + 1, T] { - Vec { data: [x, ..xs.data] } -} - -// Head requires non-empty vector -fn head[n: Nat, T](vec: Vec[n + 1, T]) -> T { - vec.data[0] // Safe! We know length >= 1 -} - -// Tail decreases length by 1 -fn tail[n: Nat, T](vec: Vec[n + 1, T]) -> Vec[n, T] { - Vec { data: vec.data[1..] } -} -``` - -### Safe Operations - -```affine -// Append preserves exact length information -fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], - b: Vec[m, T] -) -> Vec[n + m, T] { - // Result has exactly n + m elements -} - -// Map preserves length -fn map[n: Nat, A, B](f: (A) -> B, vec: Vec[n, A]) -> Vec[n, B] { - // Output has same length as input -} - -// Zip requires same length -fn zip[n: Nat, A, B]( - a: Vec[n, A], - b: Vec[n, B] -) -> Vec[n, (A, B)] { - // Both inputs must have same length -} -``` - -### Usage Examples - -```affine -let v1: Vec[3, Int] = cons(1, cons(2, cons(3, empty()))) -let v2: Vec[2, Int] = cons(4, cons(5, empty())) - -let first = head(v1) // OK: v1 has 3 elements -let rest = tail(v1) // rest: Vec[2, Int] -let combined = append(v1, v2) // combined: Vec[5, Int] - -// let bad = head(empty()) // ERROR: Vec[0, T] doesn't match Vec[n+1, T] -``` - ---- - -## Pi Types - -### Dependent Functions - -Pi types (dependent function types) allow the return type to depend on the input value: - -```affine -// Return type depends on input n -fn replicate[T](n: Nat, x: T) -> Vec[n, T] { - // Creates exactly n copies of x -} - -// Different inputs, different output types -replicate(3, "hi") // Vec[3, String] -replicate(5, 42) // Vec[5, Int] -``` - -### Implicit Arguments - -```affine -// {n: Nat} is an implicit argument, inferred from context -fn length[{n: Nat}, T](vec: Vec[n, T]) -> Nat { - n // Just return the index! -} - -let v: Vec[5, Int] = ... -length(v) // Returns 5, n inferred -``` - -### First-Class Length - -```affine -// Return the length as a value -fn len[{n: Nat}, T](vec: Vec[n, T]) -> (m: Nat, m == n) { - (n, refl) // Return n with proof it equals n -} -``` - ---- - -## Refinement Types - -### Basic Refinements - -Refinement types constrain values with predicates: - -```affine -// Type of positive integers -type Pos = Int where (self > 0) - -// Type of percentages -type Percent = Int where (self >= 0 && self <= 100) - -// Type of valid indices -type Index[n: Nat] = Nat where (self < n) -``` - -### Using Refinements - -```affine -fn divide(x: Int, y: Int where (y != 0)) -> Int { - x / y // Safe: y cannot be zero -} - -fn safe_index[n: Nat, T]( - vec: Vec[n, T], - i: Nat where (i < n) -) -> T { - vec.data[i] // Safe: i is in bounds -} - -// At call site, refinement must be satisfied -divide(10, 2) // OK -divide(10, 0) // ERROR: 0 != 0 is false -``` - -### Refinement Inference - -```affine -fn example(x: Int where (x > 0)) -> Int where (result > 0) { - x + 1 // Compiler proves: x > 0 => x + 1 > 0 -} - -fn array_access(arr: [Int; 10], i: Int) -> Int { - if i >= 0 && i < 10 { - arr[i] // Compiler knows: 0 <= i < 10 - } else { - 0 - } -} -``` - -### SMT Integration - -The compiler uses SMT solvers (Z3/CVC5) for refinement checking: - -```affine -fn complex_refinement( - x: Int where (x > 10), - y: Int where (y > 5 && y < x) -) -> Int where (result > 15) { - x + y // SMT proves: x > 10 && y > 5 => x + y > 15 -} -``` - ---- - -## Proof Terms - -### Equality Proofs - -```affine -// Propositional equality type -type (==)[A, a: A, b: A] - -// Reflexivity: a == a -fn refl[A, a: A]() -> (a == a) - -// Symmetry: a == b => b == a -fn sym[A, a: A, b: A](pf: a == b) -> (b == a) - -// Transitivity: a == b && b == c => a == c -fn trans[A, a: A, b: A, c: A](pf1: a == b, pf2: b == c) -> (a == c) -``` - -### Using Equality - -```affine -fn append_length[n: Nat, m: Nat, T]( - xs: Vec[n, T], - ys: Vec[m, T] -) -> (result: Vec[n + m, T], length(result) == n + m) { - let combined = append(xs, ys) - (combined, refl()) // Proof that length equals n + m -} -``` - -### Transport - -```affine -// If a == b, we can substitute a for b in any type -fn transport[A, P: A -> Type, a: A, b: A]( - pf: a == b, - pa: P(a) -) -> P(b) { - // "Transport" pa along the equality proof -} - -// Example: convert Vec[n, T] to Vec[m, T] when n == m -fn coerce_length[n: Nat, m: Nat, T]( - pf: n == m, - vec: Vec[n, T] -) -> Vec[m, T] { - transport(pf, vec) -} -``` - ---- - -## Type-Level Computation - -### Type-Level Arithmetic - -```affine -// Types can compute -type Add[n: Nat, m: Nat] = n + m -type Mul[n: Nat, m: Nat] = n * m - -// Example with matrix dimensions -struct Matrix[rows: Nat, cols: Nat, T] { ... } - -fn matmul[m: Nat, n: Nat, p: Nat, T: Num]( - a: Matrix[m, n, T], - b: Matrix[n, p, T] -) -> Matrix[m, p, T] { - // Inner dimensions must match (both n) - // Result has outer dimensions (m x p) -} -``` - -### Type-Level Booleans - -```affine -type If[cond: Bool, then: Type, else: Type] = - match cond { - true -> then, - false -> else - } - -// Conditionally include field -struct MaybeNamed[named: Bool] { - value: Int, - name: If[named, String, Unit] -} -``` - -### Type-Level Lists - -```affine -// Heterogeneous lists with type-level length -type HList[types: [Type]] = - match types { - [] -> Unit, - [T, ..Ts] -> (T, HList[Ts]) - } - -let hlist: HList[[Int, String, Bool]] = (42, ("hello", (true, ()))) -``` - ---- - -## Practical Examples - -### Safe Array Operations - -```affine -struct Array[n: Nat, T] { - data: [T; n] -} - -impl[n: Nat, T] Array[n, T] { - // Index with compile-time bounds check - fn get(self: &Self, i: Nat where (i < n)) -> &T { - &self.data[i] - } - - // Slice with bounds preserved - fn slice[start: Nat, len: Nat]( - self: &Self - ) -> Array[len, T] - where - start + len <= n - { - Array { data: self.data[start..start+len] } - } -} -``` - -### Type-Safe State Machines - -```affine -// States as types -struct Disconnected { } -struct Connecting { addr: String } -struct Connected { socket: Socket } - -// Transitions encoded in types -fn connect(state: Disconnected, addr: String) -> Connecting { - Connecting { addr } -} - -fn established(state: Connecting, socket: Socket) -> Connected { - Connected { socket } -} - -fn send(state: &Connected, data: &[Byte]) -> Result[(), Error] { - state.socket.send(data) -} - -fn disconnect(state: Connected) -> Disconnected { - state.socket.close() - Disconnected { } -} - -// Cannot call send on Disconnected - type error! -``` - -### Units of Measure - -```affine -// Phantom type for units -struct Quantity[unit: Unit, T] { - value: T -} - -// Units -type Meters -type Seconds -type MetersPerSecond = Meters / Seconds - -fn distance(speed: Quantity[MetersPerSecond, Float], time: Quantity[Seconds, Float]) -> Quantity[Meters, Float] { - Quantity { value: speed.value * time.value } -} - -// Type error if units don't match! -let d = distance( - Quantity[MetersPerSecond] { value: 10.0 }, - Quantity[Seconds] { value: 5.0 } -) // d: Quantity[Meters, Float] -``` - -### Protocol Verification - -```affine -// Message types indexed by protocol state -enum Message[from: State, to: State] { - Hello: Message[Init, WaitingForHello], - Ack: Message[WaitingForHello, Ready], - Data(bytes: [Byte]): Message[Ready, Ready], - Close: Message[Ready, Closed] -} - -// Protocol must follow valid transitions -fn protocol_step[s1: State, s2: State]( - state: s1, - msg: Message[s1, s2] -) -> s2 { - // Type system ensures only valid transitions -} -``` - ---- - -## Limitations - -### Decidability - -Not all type-level computations are decidable. The compiler restricts: -- Terminating recursion only -- Limited universe of types -- SMT-solvable refinements - -### Ergonomics - -Dependent types require: -- More type annotations -- Explicit proofs in some cases -- Understanding of type theory - -### Performance - -Type checking dependent types is more expensive: -- SMT solver calls -- Type-level evaluation -- More complex unification - ---- - -## See Also - -- [Types](types.md) - Basic type system -- [Refinement Types](../design/type-system.md#refinements) - Theory -- [Vectors Example](../../examples/vectors.affine) - Length-indexed vectors diff --git a/affinescript/wiki/language-reference/effects.md b/affinescript/wiki/language-reference/effects.md deleted file mode 100644 index 0db61f4..0000000 --- a/affinescript/wiki/language-reference/effects.md +++ /dev/null @@ -1,582 +0,0 @@ - -# Algebraic Effects - -AffineScript features an algebraic effect system for controlled side effects, enabling pure functional programming with practical I/O capabilities. - -## Table of Contents - -1. [Introduction](#introduction) -2. [Declaring Effects](#declaring-effects) -3. [Using Effects](#using-effects) -4. [Effect Handlers](#effect-handlers) -5. [Effect Polymorphism](#effect-polymorphism) -6. [Standard Effects](#standard-effects) -7. [Advanced Patterns](#advanced-patterns) -8. [Implementation Notes](#implementation-notes) - ---- - -## Introduction - -### Why Effects? - -Traditional approaches to side effects: -- **Impure functions**: No control, hard to reason about -- **Monads**: Powerful but complex, don't compose easily -- **Effect systems**: Track effects in types, compose naturally - -AffineScript uses **algebraic effects**: -- Effects are declared explicitly -- Functions declare what effects they need -- Effects are handled (interpreted) by handlers -- Pure core with controlled impurity at boundaries - -### Basic Example - -```affine -// Declare an effect -effect Console { - fn print(msg: String) -> Unit - fn read() -> String -} - -// Use the effect -fn greet() -{Console}-> Unit { - print("What's your name?") - let name = read() - print("Hello, " ++ name ++ "!") -} - -// Handle the effect -fn main() -{IO}-> Unit { - handle greet() { - print(msg) -> { - io_print(msg) - resume(()) - }, - read() -> { - let line = io_read_line() - resume(line) - } - } -} -``` - ---- - -## Declaring Effects - -### Effect Syntax - -```affine -effect EffectName[TypeParams] { - fn operation1(params) -> ReturnType - fn operation2(params) -> ReturnType - // ... -} -``` - -### Simple Effect - -```affine -effect Log { - fn log(level: Level, msg: String) -> Unit -} - -enum Level { Debug, Info, Warn, Error } -``` - -### Parameterized Effect - -```affine -// State effect with type parameter -effect State[S] { - fn get() -> S - fn put(s: S) -> Unit -} - -// Error effect with error type -effect Error[E] { - fn raise(e: E) -> Never -} -``` - -### Effect with Multiple Operations - -```affine -effect FileSystem { - fn read_file(path: String) -> Result[String, IoError] - fn write_file(path: String, content: String) -> Result[Unit, IoError] - fn delete_file(path: String) -> Result[Unit, IoError] - fn exists(path: String) -> Bool -} -``` - ---- - -## Using Effects - -### Effect Annotations - -Functions declare effects in their type: - -```affine -// No effects (pure) -fn add(x: Int, y: Int) -> Int { - x + y -} - -// Single effect -fn log_add(x: Int, y: Int) -{Log}-> Int { - log(Info, "Adding " ++ show(x) ++ " + " ++ show(y)) - x + y -} - -// Multiple effects -fn stateful_log() -{State[Int], Log}-> Int { - let current = get() - log(Debug, "Current state: " ++ show(current)) - put(current + 1) - current -} -``` - -### Performing Operations - -Effect operations are called like regular functions: - -```affine -fn counter() -{State[Int]}-> Int { - let n = get() // Perform get - put(n + 1) // Perform put - n -} - -fn might_fail() -{Error[String]}-> Int { - if problem { - raise("Something went wrong") // Perform raise - } - 42 -} -``` - -### Effect Subsumption - -A function with fewer effects can be used where more are expected: - -```affine -fn pure_fn() -> Int { 42 } - -fn effectful() -{IO}-> Int { - pure_fn() // OK: pure can be used in effectful context -} -``` - ---- - -## Effect Handlers - -### Basic Handler Syntax - -```affine -handle expression { - operation(args) -> handler_body, - operation2(args) -> handler_body, - return(x) -> final_body // Optional: handle pure return -} -``` - -### Handling State - -```affine -fn run_state[S, A](initial: S, f: () -{State[S]}-> A) -> (A, S) { - let mut state = initial - - let result = handle f() { - get() -> resume(state), - put(s) -> { - state = s - resume(()) - } - } - - (result, state) -} - -// Usage -let (value, final_state) = run_state(0, || { - put(get() + 1) - put(get() + 1) - get() -}) -// value = 2, final_state = 2 -``` - -### Handling Errors - -```affine -fn run_error[E, A](f: () -{Error[E]}-> A) -> Result[A, E] { - handle f() { - raise(e) -> Err(e), // Don't resume, return error - return(x) -> Ok(x) - } -} - -// Usage -let result = run_error(|| { - if x < 0 { - raise("Negative value") - } - x * 2 -}) -``` - -### The `resume` Keyword - -`resume` continues execution after the effect: - -```affine -handle computation() { - // resume(value) - continue with 'value' as operation result - log(msg) -> { - println("[LOG] " ++ msg) - resume(()) // Continue, operation returns () - }, - - // Not calling resume aborts the computation - fail(msg) -> { - println("[FAIL] " ++ msg) - // No resume - computation ends here - None - } -} -``` - -### Multi-shot Handlers - -Resume can be called multiple times (for non-determinism): - -```affine -effect Choice { - fn choose[A](options: Vec[A]) -> A -} - -fn all_choices[A](f: () -{Choice}-> A) -> Vec[A] { - handle f() { - choose(options) -> { - let mut results = [] - for option in options { - results.extend(resume(option)) // Resume for each! - } - results - }, - return(x) -> [x] - } -} - -// Usage -let paths = all_choices(|| { - let x = choose([1, 2]) - let y = choose(["a", "b"]) - (x, y) -}) -// paths = [(1, "a"), (1, "b"), (2, "a"), (2, "b")] -``` - ---- - -## Effect Polymorphism - -### Polymorphic Effects - -Functions can be polymorphic over effects: - -```affine -// Works with any effect row -fn map_option[E, A, B]( - opt: Option[A], - f: (A) -{E}-> B -) -{E}-> Option[B] { - match opt { - Some(a) -> Some(f(a)), - None -> None - } -} - -// Usage -let result = map_option(Some(5), |x| { - log(Info, "Doubling") - x * 2 -}) -``` - -### Effect Row Variables - -```affine -// e is an effect row variable -fn sequence[e, A](actions: Vec[() -{e}-> A]) -{e}-> Vec[A] { - let mut results = [] - for action in actions { - results.push(action()) - } - results -} -``` - -### Effect Constraints - -```affine -// Require specific effects in the row -fn with_logging[e: Log, A](f: () -{e}-> A) -{e}-> A { - log(Info, "Starting") - let result = f() - log(Info, "Finished") - result -} -``` - ---- - -## Standard Effects - -### IO Effect - -The primitive effect for I/O: - -```affine -effect IO { - // These are primitive operations - fn io_print(s: String) -> Unit - fn io_read() -> String - fn io_exit(code: Int) -> Never -} -``` - -### Exception Effect - -For recoverable errors: - -```affine -effect Exn[E] { - fn throw(e: E) -> Never -} - -fn catch[E, A](f: () -{Exn[E]}-> A, handler: (E) -> A) -> A { - handle f() { - throw(e) -> handler(e) - } -} -``` - -### Async Effect - -For asynchronous programming: - -```affine -effect Async { - fn await[T](future: Future[T]) -> T - fn spawn[T](f: () -{Async}-> T) -> Future[T] -} - -fn fetch_all(urls: Vec[String]) -{Async, IO}-> Vec[Response] { - let futures = urls.map(|url| spawn(|| fetch(url))) - futures.map(|f| await(f)) -} -``` - -### Reader Effect - -For dependency injection: - -```affine -effect Reader[R] { - fn ask() -> R -} - -fn with_reader[R, A](r: R, f: () -{Reader[R]}-> A) -> A { - handle f() { - ask() -> resume(r) - } -} - -// Usage -fn get_config_value() -{Reader[Config]}-> String { - ask().database_url -} -``` - -### Writer Effect - -For logging/accumulation: - -```affine -effect Writer[W: Monoid] { - fn tell(w: W) -> Unit -} - -fn run_writer[W: Monoid, A](f: () -{Writer[W]}-> A) -> (A, W) { - let mut log = W::empty() - - let result = handle f() { - tell(w) -> { - log = log.append(w) - resume(()) - } - } - - (result, log) -} -``` - ---- - -## Advanced Patterns - -### Effect Interpretation - -Same effect, different interpretations: - -```affine -effect Database { - fn query(sql: String) -> Vec[Row] - fn execute(sql: String) -> Int -} - -// Production handler - real database -fn run_production[A](f: () -{Database}-> A) -{IO}-> A { - let conn = connect_db() - handle f() { - query(sql) -> resume(conn.query(sql)), - execute(sql) -> resume(conn.execute(sql)) - } -} - -// Test handler - in-memory -fn run_test[A](f: () -{Database}-> A) -> A { - let mut data = HashMap::new() - handle f() { - query(sql) -> resume(mock_query(data, sql)), - execute(sql) -> resume(mock_execute(&mut data, sql)) - } -} -``` - -### Effect Composition - -```affine -fn program() -{State[Int], Log, Error[String]}-> Int { - log(Info, "Starting") - let n = get() - if n < 0 { - raise("Negative state") - } - put(n + 1) - get() -} - -fn run() -> Result[Int, String] { - run_error(|| { - run_state(0, || { - run_log(|| { - program() - }) - }).0 - }) -} -``` - -### Delimited Continuations - -Effects give you access to delimited continuations: - -```affine -effect Yield[A, R] { - fn yield(a: A) -> R -} - -fn generator[A](f: () -{Yield[A, Unit]}-> Unit) -> Iterator[A] { - // Captures continuation at each yield point - // Can resume later to get next value -} -``` - -### Scoped Effects - -Effects with resource management: - -```affine -effect Resource { - fn bracket[A]( - acquire: () -> Handle, - release: (Handle) -> Unit, - use: (Handle) -> A - ) -> A -} - -fn with_file[A](path: String, f: (&File) -> A) -{Resource, IO}-> A { - bracket( - || File::open(path), - |h| h.close(), - f - ) -} -``` - ---- - -## Implementation Notes - -### Effect Compilation - -Effects are compiled using one of: - -1. **CPS Transformation**: Convert to continuation-passing style -2. **Evidence Passing**: Pass handler implementations as implicit parameters -3. **Monadic Translation**: Translate to free monad style - -### Performance Considerations - -```affine -// Effects have overhead - use strategically -fn tight_loop() -> Int { - let mut sum = 0 - for i in 0..1000000 { - sum += i - } - sum // Pure is fastest -} - -// Batch effect operations when possible -fn log_batch() -{Log}-> Unit { - let messages = compute_messages() - log(Info, messages.join("\n")) // One effect, not many -} -``` - -### Effect Safety - -The type system ensures: -- All effects are declared -- All effects are handled before program exit -- Effect rows compose correctly - -```affine -fn main() -{IO}-> Unit { - // Must handle all non-IO effects before here - let result = handle program() { - // handlers... - } - println(result) -} -``` - ---- - -## See Also - -- [Types](types.md) - Type system overview -- [Design: Effect System](../design/effects.md) - Theory and design -- [Standard Effects](../stdlib/effects.md) - Standard library effects diff --git a/affinescript/wiki/language-reference/functions.md b/affinescript/wiki/language-reference/functions.md deleted file mode 100644 index 4339521..0000000 --- a/affinescript/wiki/language-reference/functions.md +++ /dev/null @@ -1,498 +0,0 @@ - -# Functions - -Functions are the primary building blocks of AffineScript programs. - -## Table of Contents - -1. [Function Declarations](#function-declarations) -2. [Parameters](#parameters) -3. [Return Types](#return-types) -4. [Generic Functions](#generic-functions) -5. [Closures](#closures) -6. [Totality](#totality) -7. [Methods](#methods) - ---- - -## Function Declarations - -### Basic Syntax - -```affine -fn function_name(param1: Type1, param2: Type2) -> ReturnType { - // body -} -``` - -### Simple Functions - -```affine -fn add(x: Int, y: Int) -> Int { - x + y -} - -fn greet(name: String) -> String { - "Hello, " ++ name ++ "!" -} - -fn main() -{IO}-> Unit { - let result = add(1, 2) - println(greet("World")) -} -``` - -### Expression Bodies - -Single-expression functions: - -```affine -fn double(x: Int) -> Int = x * 2 - -fn square(x: Int) -> Int = x * x - -fn is_even(n: Int) -> Bool = n % 2 == 0 -``` - ---- - -## Parameters - -### Ownership in Parameters - -```affine -// Takes ownership (default for non-Copy) -fn consume(s: String) -> Int { - s.len() // s is consumed -} - -// Shared borrow -fn read(s: &String) -> Int { - s.len() // s is borrowed -} - -// Mutable borrow -fn modify(s: &mut String) { - s.push_str("!") -} - -// Explicit ownership annotation -fn transfer(file: own File) { - file.close() -} -``` - -### Default Parameters (Planned) - -```affine -fn greet(name: String, greeting: String = "Hello") -> String { - greeting ++ ", " ++ name -} - -greet("Alice") // "Hello, Alice" -greet("Bob", "Hi") // "Hi, Bob" -``` - -### Named Arguments (Planned) - -```affine -fn create_user(name: String, age: Int, active: Bool) -> User { - User { name, age, active } -} - -// Call with named arguments -create_user(name: "Alice", age: 30, active: true) -create_user(age: 25, name: "Bob", active: false) // Any order -``` - -### Variadic Parameters (Planned) - -```affine -fn sum(..nums: [Int]) -> Int { - nums.fold(0, |acc, n| acc + n) -} - -sum(1, 2, 3, 4, 5) // 15 -``` - ---- - -## Return Types - -### Explicit Returns - -```affine -fn early_return(x: Int) -> Int { - if x < 0 { - return 0 - } - x * 2 -} -``` - -### Unit Return - -```affine -fn side_effect() { - // Implicit Unit return - println("effect!") -} - -fn explicit_unit() -> Unit { - println("effect!") - () -} -``` - -### Never Return - -Functions that don't return: - -```affine -fn panic(msg: String) -> Never { - print_error(msg) - exit(1) -} - -fn infinite_loop() -> Never { - loop { - process() - } -} -``` - -### Effect Annotations - -```affine -// Pure function -fn pure(x: Int) -> Int { - x + 1 -} - -// With effects -fn impure() -{IO}-> Unit { - println("Hello") -} - -// Multiple effects -fn complex() -{IO, State[Int], Error[String]}-> Int { - // ... -} -``` - ---- - -## Generic Functions - -### Type Parameters - -```affine -fn identity[T](x: T) -> T { - x -} - -fn swap[A, B](pair: (A, B)) -> (B, A) { - let (a, b) = pair - (b, a) -} - -fn first[A, B](pair: (A, B)) -> A { - pair.0 -} -``` - -### Trait Bounds - -```affine -fn print_value[T: Show](x: T) -{IO}-> Unit { - println(x.show()) -} - -fn compare[T: Ord](a: T, b: T) -> Ordering { - a.compare(b) -} - -fn sort[T: Ord](mut list: Vec[T]) -> Vec[T] { - // sorting implementation - list -} -``` - -### Where Clauses - -```affine -fn complex_bounds[A, B, C](a: A, b: B, c: C) -> C -where - A: Clone + Show, - B: Into[A], - C: From[A] + Default -{ - let a2 = b.into() - C::from(a2) -} -``` - -### Associated Type Constraints - -```affine -fn sum_iterator[I](iter: I) -> I::Item -where - I: Iterator, - I::Item: Add + Default -{ - iter.fold(I::Item::default(), |acc, x| acc + x) -} -``` - ---- - -## Closures - -### Lambda Syntax - -```affine -// Type inferred -let double = |x| x * 2 - -// Explicit types -let add: (Int, Int) -> Int = |x, y| x + y - -// Multi-line -let process = |x| { - let y = x * 2 - y + 1 -} -``` - -### Capturing Environment - -```affine -fn make_adder(n: Int) -> (Int) -> Int { - |x| x + n // Captures n by value -} - -let add5 = make_adder(5) -add5(10) // 15 -``` - -### Move Closures - -```affine -fn make_counter() -> () -> Int { - let mut count = 0 - move || { - count += 1 - count - } -} - -let counter = make_counter() -counter() // 1 -counter() // 2 -``` - -### Closure Types - -```affine -// Fn - can be called multiple times, borrows immutably -fn apply_fn[F: Fn(Int) -> Int](f: F, x: Int) -> Int { - f(x) -} - -// FnMut - can be called multiple times, borrows mutably -fn apply_fn_mut[F: FnMut(Int) -> Int](mut f: F, x: Int) -> Int { - f(x) -} - -// FnOnce - can only be called once, takes ownership -fn apply_fn_once[F: FnOnce(Int) -> Int](f: F, x: Int) -> Int { - f(x) -} -``` - ---- - -## Totality - -### Total Functions - -Functions marked `total` must provably terminate: - -```affine -total fn factorial(n: Nat) -> Nat { - match n { - 0 -> 1, - n -> n * factorial(n - 1) // Decreasing argument - } -} - -total fn length[T](list: List[T]) -> Nat { - match list { - Nil -> 0, - Cons(_, tail) -> 1 + length(tail) // Structural recursion - } -} -``` - -### Partial Functions (Default) - -Functions without `total` may diverge: - -```affine -fn partial_search(predicate: (Int) -> Bool) -> Int { - let mut i = 0 - while !predicate(i) { - i += 1 // Might never terminate! - } - i -} -``` - -### Termination Checking - -```affine -// OK: Structural recursion on list -total fn map[A, B](f: (A) -> B, list: List[A]) -> List[B] { - match list { - Nil -> Nil, - Cons(x, xs) -> Cons(f(x), map(f, xs)) - } -} - -// ERROR: Not provably terminating -total fn collatz(n: Nat) -> Nat { - match n { - 1 -> 1, - n if n % 2 == 0 -> collatz(n / 2), - n -> collatz(3 * n + 1) // Not decreasing! - } -} -``` - ---- - -## Methods - -### Method Syntax - -```affine -struct Point { - x: Float64, - y: Float64 -} - -impl Point { - // Associated function (no self) - fn origin() -> Point { - Point { x: 0.0, y: 0.0 } - } - - // Method with &self - fn distance_from_origin(self: &Self) -> Float64 { - (self.x * self.x + self.y * self.y).sqrt() - } - - // Method with &mut self - fn translate(self: &mut Self, dx: Float64, dy: Float64) { - self.x += dx - self.y += dy - } - - // Method that consumes self - fn into_tuple(self: Self) -> (Float64, Float64) { - (self.x, self.y) - } -} - -// Usage -let p = Point::origin() -let dist = p.distance_from_origin() -let (x, y) = p.into_tuple() -``` - -### Self Type Shortcuts - -```affine -impl Point { - fn method1(&self) -> Float64 { ... } // self: &Self - fn method2(&mut self) { ... } // self: &mut Self - fn method3(self) -> (Float64, Float64) { } // self: Self (owned) -} -``` - -### Method Chaining - -```affine -impl Builder { - fn new() -> Self { ... } - - fn field1(self, v: Int) -> Self { - Self { field1: v, ..self } - } - - fn field2(self, v: String) -> Self { - Self { field2: v, ..self } - } - - fn build(self) -> Result { - // ... - } -} - -let result = Builder::new() - .field1(42) - .field2("hello") - .build() -``` - ---- - -## Operators as Functions - -### Operator Definitions - -```affine -trait Add[Rhs = Self] { - type Output - - fn add(self, rhs: Rhs) -> Self::Output -} - -impl Add for Int { - type Output = Int - - fn add(self, rhs: Int) -> Int { - // primitive addition - } -} -``` - -### Custom Operators - -```affine -impl Add for Point { - type Output = Point - - fn add(self, rhs: Point) -> Point { - Point { - x: self.x + rhs.x, - y: self.y + rhs.y - } - } -} - -let p1 = Point { x: 1.0, y: 2.0 } -let p2 = Point { x: 3.0, y: 4.0 } -let p3 = p1 + p2 // Point { x: 4.0, y: 6.0 } -``` - ---- - -## See Also - -- [Types](types.md) - Function types -- [Traits](traits.md) - Trait methods -- [Effects](effects.md) - Effect annotations diff --git a/affinescript/wiki/language-reference/lexical.md b/affinescript/wiki/language-reference/lexical.md deleted file mode 100644 index 3fff07c..0000000 --- a/affinescript/wiki/language-reference/lexical.md +++ /dev/null @@ -1,488 +0,0 @@ - -# Lexical Structure - -This document describes the lexical structure of AffineScript - how source code is broken into tokens. - -## Table of Contents - -1. [Character Set](#character-set) -2. [Whitespace](#whitespace) -3. [Comments](#comments) -4. [Identifiers](#identifiers) -5. [Keywords](#keywords) -6. [Literals](#literals) -7. [Operators](#operators) -8. [Punctuation](#punctuation) -9. [Special Tokens](#special-tokens) - ---- - -## Character Set - -AffineScript source files are encoded in UTF-8. The lexer supports full Unicode for: - -- Identifiers (letters from any script) -- String literals -- Comments - -```affine -// Greek identifiers -let alpha = 1 -let beta = 2 - -// Emoji in strings (but not identifiers) -let greeting = "Hello! :)" - -// Unicode operators -let result = x `add` y -``` - ---- - -## Whitespace - -Whitespace characters are used to separate tokens and are otherwise ignored: - -| Character | Name | Unicode | -|-----------|------|---------| -| ` ` | Space | U+0020 | -| `\t` | Tab | U+0009 | -| `\n` | Newline | U+000A | -| `\r` | Carriage Return | U+000D | - -```affine -// All equivalent: -let x = 1 -let x = 1 -let x = 1 -``` - ---- - -## Comments - -### Line Comments - -Start with `//` and continue to end of line: - -```affine -// This is a line comment -let x = 42 // Inline comment -``` - -### Block Comments - -Enclosed in `/* */` and can be nested: - -```affine -/* This is a block comment */ - -/* - Multi-line - block comment -*/ - -/* Nested /* comments */ are supported */ -``` - -### Documentation Comments (Planned) - -```affine -/// Documentation for the following item -/// Supports **markdown** formatting -fn documented_function() -> Unit { } - -//! Module-level documentation -//! Describes the current module -``` - ---- - -## Identifiers - -### Rules - -- Start with a letter (any Unicode letter) or underscore -- Followed by letters, digits, or underscores -- Case-sensitive - -```ebnf -identifier = (letter | '_') (letter | digit | '_')* -letter = -digit = '0'..'9' -``` - -### Examples - -```affine -// Valid identifiers -x -_private -camelCase -snake_case -PascalCase -SCREAMING_CASE -alpha1 -_0 - -// Invalid -0start // Cannot start with digit -kebab-case // Hyphen not allowed -``` - -### Reserved Patterns - -- `_` alone is a wildcard pattern, not a valid identifier -- Identifiers starting with `_` suppress unused warnings - -```affine -let _ = unused_value() // Wildcard, value discarded -let _temp = compute() // Valid identifier, unused warning suppressed -``` - ---- - -## Keywords - -AffineScript reserves the following keywords: - -### Value Keywords -| Keyword | Description | -|---------|-------------| -| `true` | Boolean true | -| `false` | Boolean false | - -### Declaration Keywords -| Keyword | Description | -|---------|-------------| -| `fn` | Function declaration | -| `let` | Variable binding | -| `type` | Type alias | -| `struct` | Struct definition | -| `enum` | Enum definition | -| `trait` | Trait declaration | -| `impl` | Implementation block | -| `effect` | Effect declaration | -| `mod` | Module declaration | - -### Modifier Keywords -| Keyword | Description | -|---------|-------------| -| `pub` | Public visibility | -| `mut` | Mutable reference | -| `own` | Owned value | -| `ref` | Borrowed reference | -| `total` | Total function marker | -| `unsafe` | Unsafe block | - -### Control Flow Keywords -| Keyword | Description | -|---------|-------------| -| `if` | Conditional | -| `else` | Else branch | -| `match` | Pattern matching | -| `while` | While loop | -| `for` | For loop | -| `in` | Iterator binding | -| `loop` | Infinite loop | -| `return` | Early return | -| `break` | Loop break | -| `continue` | Loop continue | - -### Type Keywords -| Keyword | Description | -|---------|-------------| -| `where` | Type constraints | -| `as` | Type coercion | -| `Self` | Self type | - -### Effect Keywords -| Keyword | Description | -|---------|-------------| -| `handle` | Effect handler | -| `resume` | Resume continuation | -| `perform` | Perform effect | - -### Module Keywords -| Keyword | Description | -|---------|-------------| -| `use` | Import | -| `from` | Import source | - -### Reserved for Future -| Keyword | Description | -|---------|-------------| -| `async` | Async functions | -| `await` | Await expressions | -| `yield` | Generator yield | -| `macro` | Macro definitions | -| `do` | Do notation | -| `forall` | Universal quantification | -| `exists` | Existential quantification | - ---- - -## Literals - -### Integer Literals - -```affine -// Decimal -42 -1_000_000 // Underscores for readability - -// Hexadecimal -0xFF -0xDEAD_BEEF - -// Binary -0b1010 -0b1111_0000 - -// Octal -0o755 -0o777 -``` - -### Floating-Point Literals - -```affine -3.14 -2.0 -1e10 -1.5e-3 -2.5E+10 -``` - -### Character Literals - -```affine -'a' -'\n' // Newline -'\t' // Tab -'\\' // Backslash -'\'' // Single quote -'\0' // Null -'\x7F' // Hex escape (ASCII) -'\u{1F600}' // Unicode escape -``` - -### String Literals - -```affine -"Hello, World!" -"Line 1\nLine 2" -"Tab\there" -"Quote: \"Hello\"" -"Unicode: \u{1F600}" - -// Multi-line strings -"This is a -multi-line -string" -``` - -### Raw String Literals (Planned) - -```affine -r"No \n escapes here" -r#"Can include "quotes" freely"# -r##"Even includes #"# literally"## -``` - -### String Interpolation (Planned) - -```affine -let name = "Alice" -let greeting = "Hello, ${name}!" -let math = "1 + 1 = ${1 + 1}" -``` - ---- - -## Operators - -### Arithmetic Operators -| Operator | Description | -|----------|-------------| -| `+` | Addition | -| `-` | Subtraction | -| `*` | Multiplication | -| `/` | Division | -| `%` | Modulo | - -### Comparison Operators -| Operator | Description | -|----------|-------------| -| `==` | Equality | -| `!=` | Inequality | -| `<` | Less than | -| `>` | Greater than | -| `<=` | Less or equal | -| `>=` | Greater or equal | - -### Logical Operators -| Operator | Description | -|----------|-------------| -| `&&` | Logical AND | -| `\|\|` | Logical OR | -| `!` | Logical NOT | - -### Bitwise Operators -| Operator | Description | -|----------|-------------| -| `&` | Bitwise AND | -| `\|` | Bitwise OR | -| `^` | Bitwise XOR | -| `~` | Bitwise NOT | -| `<<` | Left shift | -| `>>` | Right shift | - -### Assignment Operators -| Operator | Description | -|----------|-------------| -| `=` | Assignment | -| `+=` | Add-assign | -| `-=` | Subtract-assign | -| `*=` | Multiply-assign | -| `/=` | Divide-assign | -| `%=` | Modulo-assign | -| `&=` | AND-assign | -| `\|=` | OR-assign | -| `^=` | XOR-assign | -| `<<=` | Left shift-assign | -| `>>=` | Right shift-assign | - -### Other Operators -| Operator | Description | -|----------|-------------| -| `->` | Function arrow | -| `-{E}->` | Effectful arrow | -| `=>` | Match arm / lambda | -| `..` | Range (exclusive) | -| `..=` | Range (inclusive) | -| `++` | String/list concatenation | -| `\|>` | Pipe operator | -| `?` | Error propagation | -| `@` | Pattern binding | - ---- - -## Punctuation - -| Symbol | Description | -|--------|-------------| -| `(` `)` | Grouping, tuples, function calls | -| `[` `]` | Array indexing, type parameters | -| `{` `}` | Blocks, records | -| `,` | Separator | -| `;` | Statement terminator | -| `:` | Type annotation | -| `::` | Path separator | -| `.` | Field access | -| `..` | Row variable prefix | - ---- - -## Special Tokens - -### Quantity Annotations - -For linear/affine type annotations: - -| Token | Meaning | -|-------|---------| -| `0` | Erased (compile-time only) | -| `1` | Linear (exactly once) | -| `omega` or `w` | Unrestricted | - -```affine -fn linear_use(x: 1 Resource) -> Unit { ... } -fn erased_type[0 T]() -> Unit { ... } -``` - -### Row Variables - -```affine -// ..name introduces a row variable -fn extend[r](rec: {..r}) -> {x: Int, ..r} { - {x: 42, ..rec} -} -``` - -### Wildcards - -```affine -let _ = ignored_value -let (x, _) = pair -match value { - Some(_) -> "has value", - None -> "empty" -} -``` - ---- - -## Operator Precedence - -From highest to lowest precedence: - -| Level | Operators | Associativity | -|-------|-----------|---------------| -| 1 | `.` `::` `[]` `()` | Left | -| 2 | `!` `~` `-` (unary) | Right | -| 3 | `*` `/` `%` | Left | -| 4 | `+` `-` | Left | -| 5 | `<<` `>>` | Left | -| 6 | `&` | Left | -| 7 | `^` | Left | -| 8 | `\|` | Left | -| 9 | `++` | Right | -| 10 | `==` `!=` `<` `>` `<=` `>=` | Left | -| 11 | `&&` | Left | -| 12 | `\|\|` | Left | -| 13 | `\|>` | Left | -| 14 | `..` `..=` | Non-assoc | -| 15 | `=` `+=` etc. | Right | - ---- - -## Lexer Error Messages - -Common lexer errors: - -``` -error[E0001]: unexpected character - --> src/main.affine:1:5 - | -1 | let @x = 5 - | ^ unexpected '@' - | - = help: identifiers cannot contain '@' - -error[E0002]: unterminated string literal - --> src/main.affine:1:9 - | -1 | let s = "hello - | ^^^^^^ string literal not closed - | - = help: add closing `"` at end of string - -error[E0003]: invalid escape sequence - --> src/main.affine:1:13 - | -1 | let s = "hello\q" - | ^^ unknown escape sequence - | - = help: valid escapes are: \n, \t, \r, \\, \', \", \0, \xNN, \u{NNNN} -``` - ---- - -## See Also - -- [Full Specification: Lexical Grammar](../../docs/spec.md#part-1-lexical-grammar) -- [Compiler: Lexer Implementation](../compiler/lexer.md) diff --git a/affinescript/wiki/language-reference/modules.md b/affinescript/wiki/language-reference/modules.md deleted file mode 100644 index 5867352..0000000 --- a/affinescript/wiki/language-reference/modules.md +++ /dev/null @@ -1,472 +0,0 @@ - -# Module System - -AffineScript's module system organizes code into logical units with controlled visibility. - -## Table of Contents - -1. [Module Basics](#module-basics) -2. [File Structure](#file-structure) -3. [Visibility](#visibility) -4. [Imports](#imports) -5. [Re-exports](#re-exports) -6. [Namespacing](#namespacing) -7. [Module Patterns](#module-patterns) - ---- - -## Module Basics - -### Declaring Modules - -```affine -// In main.affine -mod math // Looks for math.affine or math/mod.affine -mod utils // Looks for utils.affine or utils/mod.affine -mod internal // Inline module definition follows - -mod internal { - fn helper() -> Int { 42 } -} -``` - -### Module Contents - -Modules can contain: -- Functions -- Types (structs, enums, type aliases) -- Traits -- Effects -- Constants -- Nested modules - -```affine -mod geometry { - struct Point { x: Float64, y: Float64 } - struct Circle { center: Point, radius: Float64 } - - trait Shape { - fn area(self: &Self) -> Float64 - } - - impl Shape for Circle { - fn area(self: &Self) -> Float64 { - 3.14159 * self.radius * self.radius - } - } - - fn distance(p1: &Point, p2: &Point) -> Float64 { - let dx = p2.x - p1.x - let dy = p2.y - p1.y - (dx * dx + dy * dy).sqrt() - } -} -``` - ---- - -## File Structure - -### Single File Modules - -``` -project/ -├── main.affine -├── math.affine # mod math -└── utils.affine # mod utils -``` - -### Directory Modules - -``` -project/ -├── main.affine -└── math/ - ├── mod.affine # mod math entry point - ├── vectors.affine # mod vectors (inside math) - └── matrices.affine # mod matrices (inside math) -``` - -`math/mod.affine`: -```affine -// Declare submodules -pub mod vectors -pub mod matrices - -// Re-export commonly used items -pub use vectors::Vec3 -pub use matrices::Mat4 -``` - -### Nested Modules - -``` -project/ -├── main.affine -└── graphics/ - ├── mod.affine - ├── renderer/ - │ ├── mod.affine - │ ├── opengl.affine - │ └── vulkan.affine - └── primitives/ - ├── mod.affine - ├── shapes.affine - └── colors.affine -``` - ---- - -## Visibility - -### Default Visibility - -Items are private by default: - -```affine -mod internal { - fn private_helper() -> Int { 42 } // Private to this module -} - -// main.affine -internal::private_helper() // ERROR: private function -``` - -### Public Items - -Use `pub` to make items public: - -```affine -mod math { - pub fn add(x: Int, y: Int) -> Int { x + y } - - fn internal_helper() -> Int { 0 } // Still private -} - -// main.affine -math::add(1, 2) // OK -``` - -### Visibility Modifiers - -```affine -pub // Public to everyone -pub(crate) // Public within the crate -pub(super) // Public to parent module -pub(in path) // Public to specific module path - // (no modifier) - Private to current module -``` - -Examples: - -```affine -mod outer { - pub mod inner { - pub fn public_fn() { } // Everyone can access - pub(super) fn parent_only() { } // Only outer can access - pub(crate) fn crate_only() { } // Only this crate - fn private_fn() { } // Only inner can access - } - - fn test() { - inner::public_fn() // OK - inner::parent_only() // OK - we're the parent - inner::private_fn() // ERROR - private - } -} -``` - -### Struct Field Visibility - -```affine -pub struct Config { - pub name: String, // Public field - pub(crate) internal: Int, // Crate-visible field - secret: String, // Private field -} - -impl Config { - // Constructor needed for private fields - pub fn new(name: String, secret: String) -> Config { - Config { name, internal: 0, secret } - } -} -``` - ---- - -## Imports - -### Basic Imports - -```affine -use math::add -use math::sub - -fn main() { - add(1, 2) // No prefix needed -} -``` - -### Grouped Imports - -```affine -use math::{add, sub, mul, div} - -use std::collections::{Vec, HashMap, HashSet} -``` - -### Nested Imports - -```affine -use std::{ - io::{Read, Write, BufReader}, - collections::{Vec, HashMap}, - fmt::Show -} -``` - -### Glob Imports - -```affine -use math::* // Import all public items - -// Use sparingly - can cause name conflicts -``` - -### Aliased Imports - -```affine -use std::collections::HashMap as Map -use graphics::renderer::opengl as gl - -let map: Map[String, Int] = Map::new() -gl::init() -``` - -### Self Import - -```affine -use std::collections::{self, HashMap} - -// Now can use both: -collections::Vec // Full path -HashMap // Direct -``` - ---- - -## Re-exports - -### Basic Re-export - -```affine -// In lib.affine -mod internal - -pub use internal::PublicType -pub use internal::public_function -``` - -### Aliased Re-export - -```affine -pub use internal::LongTypeName as Short -``` - -### Glob Re-export - -```affine -// Re-export everything from a module -pub use prelude::* -``` - -### Creating a Prelude - -```affine -// prelude.affine -pub use crate::types::{Result, Option, Error} -pub use crate::traits::{Show, Clone, Default} -pub use crate::macros::* - -// Users can import everything commonly needed: -use mylib::prelude::* -``` - ---- - -## Namespacing - -### Path Resolution - -```affine -// Absolute path from crate root -use crate::module::item - -// Relative to current module -use self::submodule::item - -// Relative to parent module -use super::sibling::item - -// External crate -use external_crate::item -``` - -### Name Shadowing - -```affine -use std::option::Option - -// Local definition shadows import -enum Option[T] { - Some(T), - None, - Unknown -} - -// Use explicit path for std version -let x: std::option::Option[Int] = std::option::Some(42) -let y: Option[Int] = Unknown -``` - -### Disambiguating - -```affine -use graphics::Point -use geometry::Point as GeoPoint - -let screen_pt: Point = Point { x: 100, y: 200 } -let world_pt: GeoPoint = GeoPoint { x: 1.0, y: 2.0 } -``` - ---- - -## Module Patterns - -### Facade Pattern - -```affine -// lib.affine - expose clean public API -pub mod types -pub mod traits -pub mod error - -// Re-export main items at crate root -pub use types::{Config, Settings} -pub use traits::Processor -pub use error::{Error, Result} - -// Keep implementation private -mod internal { - // Implementation details -} -``` - -### Feature Modules - -```affine -// Conditionally compiled modules -#[cfg(feature = "async")] -pub mod async_support - -#[cfg(feature = "serde")] -pub mod serialization -``` - -### Test Modules - -```affine -pub fn add(x: Int, y: Int) -> Int { - x + y -} - -#[cfg(test)] -mod tests { - use super::* - - #[test] - fn test_add() { - assert_eq(add(2, 2), 4) - } -} -``` - -### Internal vs External API - -```affine -// Public API -pub mod api { - pub use crate::core::PublicType - pub use crate::core::public_fn -} - -// Internal implementation -mod core { - pub struct PublicType { ... } - pub fn public_fn() { ... } - - // These stay internal - struct InternalHelper { ... } - fn internal_fn() { ... } -} -``` - ---- - -## Crate Structure - -### Library Crate - -``` -mylib/ -├── affine.toml -└── src/ - ├── lib.affine # Crate root - ├── types.affine - ├── traits.affine - └── utils/ - ├── mod.affine - └── helpers.affine -``` - -`lib.affine`: -```affine -pub mod types -pub mod traits -pub mod utils - -pub use types::MainType -pub use traits::MainTrait -``` - -### Binary Crate - -``` -myapp/ -├── affine.toml -└── src/ - ├── main.affine # Binary entry point - └── lib.affine # Optional library part -``` - -### Mixed Crate - -``` -myproject/ -├── affine.toml -└── src/ - ├── main.affine # Uses lib - ├── lib.affine # Library code - └── modules/ -``` - ---- - -## See Also - -- [Package Manager](../tooling/package-manager.md) - Crate dependencies -- [Visibility](../design/language.md#visibility) - Design rationale diff --git a/affinescript/wiki/language-reference/ownership.md b/affinescript/wiki/language-reference/ownership.md deleted file mode 100644 index 48691a2..0000000 --- a/affinescript/wiki/language-reference/ownership.md +++ /dev/null @@ -1,503 +0,0 @@ - -# Ownership and Borrowing - -AffineScript uses an ownership system inspired by Rust and linear types from type theory. This ensures memory safety without garbage collection. - -## Table of Contents - -1. [Core Concepts](#core-concepts) -2. [Ownership Rules](#ownership-rules) -3. [Moving Values](#moving-values) -4. [Borrowing](#borrowing) -5. [Lifetimes](#lifetimes) -6. [Quantity Annotations](#quantity-annotations) -7. [Patterns](#patterns) -8. [Common Errors](#common-errors) - ---- - -## Core Concepts - -### What is Ownership? - -Every value in AffineScript has exactly one **owner** - the variable that holds it. When the owner goes out of scope, the value is automatically cleaned up. - -```affine -fn example() { - let s = String::from("hello") // s owns the string - // s is valid here -} // s goes out of scope, string is dropped -``` - -### The Three Rules - -1. **Each value has exactly one owner** -2. **Owned values must be used exactly once** (affine/linear) -3. **When the owner goes out of scope, the value is dropped** - ---- - -## Ownership Rules - -### Owned Values (`own`) - -Values marked `own` must be consumed exactly once: - -```affine -struct File { - handle: FileHandle -} - -fn process(file: own File) { - // file must be consumed in this function - file.close() // Consumes file -} - -fn bad_example(file: own File) { - // ERROR: file is never consumed -} // Compile error: unused owned value - -fn also_bad(file: own File) { - file.read() - file.read() // ERROR: file already used -} -``` - -### Implicit Ownership - -By default, function parameters take ownership: - -```affine -fn consume(s: String) { // Takes ownership - println(s) -} // s is dropped here - -fn main() { - let s = String::from("hello") - consume(s) // Ownership transferred to consume - // s is no longer valid here - println(s) // ERROR: use of moved value -} -``` - -### Copy Types - -Some types implement `Copy` and are duplicated instead of moved: - -```affine -// Primitive types are Copy -let x: Int = 42 -let y = x // x is copied, not moved -println(x) // OK: x still valid - -// Explicit Copy trait -struct Point: Copy { - x: Float64, - y: Float64 -} - -let p1 = Point { x: 0.0, y: 0.0 } -let p2 = p1 // Copied -println(p1.x) // OK -``` - ---- - -## Moving Values - -### Move Semantics - -When a value is assigned or passed, it **moves** (unless it's `Copy`): - -```affine -let s1 = String::from("hello") -let s2 = s1 // s1 is MOVED to s2 -// s1 is now invalid - -fn take(s: String) { - // s is owned here -} - -let s3 = String::from("world") -take(s3) // s3 is moved into take() -// s3 is now invalid -``` - -### Partial Moves - -Moving part of a struct makes the whole struct unusable: - -```affine -struct Person { - name: String, - age: Int -} - -let p = Person { name: "Alice".into(), age: 30 } -let n = p.name // Partial move of 'name' -// p is now partially moved - -println(p.age) // ERROR: p is partially moved -println(p.name) // ERROR: p.name was moved -``` - -### Returning Ownership - -Functions can return ownership: - -```affine -fn create() -> String { - String::from("created") // Ownership returned to caller -} - -fn transform(s: String) -> String { - s.to_uppercase() // Takes ownership, returns new value -} - -let s = create() // s owns the string -let s2 = transform(s) // s moved in, s2 owns result -``` - ---- - -## Borrowing - -### Shared Borrows (`&` or `ref`) - -Multiple shared borrows allow reading: - -```affine -fn calculate_length(s: &String) -> Int { - s.len() // Can read s -} // s is not dropped (we don't own it) - -fn main() { - let s = String::from("hello") - let len = calculate_length(&s) // Borrow s - println(s) // OK: s still valid -} -``` - -### Mutable Borrows (`&mut` or `mut ref`) - -Exactly one mutable borrow for exclusive access: - -```affine -fn append(s: &mut String) { - s.push_str(" world") // Can modify s -} - -fn main() { - let mut s = String::from("hello") - append(&mut s) // Mutable borrow - println(s) // "hello world" -} -``` - -### Borrowing Rules - -1. **At any time, you can have either:** - - One mutable reference, OR - - Any number of immutable references - -2. **References must always be valid** (no dangling) - -```affine -let mut s = String::from("hello") - -// OK: Multiple shared borrows -let r1 = &s -let r2 = &s -println(r1, r2) - -// ERROR: Can't have mutable while shared exist -let r3 = &mut s // ERROR if r1, r2 still in use - -// OK after r1, r2 are done -println(r1, r2) // Last use of r1, r2 -let r3 = &mut s // OK now -``` - -### Reborrowing - -Mutable references can be reborrowed: - -```affine -fn use_ref(s: &mut String) { - s.push('!') -} - -fn main() { - let mut s = String::from("hello") - let r = &mut s - - // Reborrow: r is temporarily "frozen" - use_ref(r) // Implicitly reborrows - - r.push('?') // r usable again -} -``` - ---- - -## Lifetimes - -### Implicit Lifetimes - -Most lifetimes are inferred: - -```affine -fn first(s: &String) -> &Char { - &s.chars()[0] -} // Lifetime of return tied to input -``` - -### Explicit Lifetimes - -When needed, lifetimes are annotated: - -```affine -fn longest['a](x: &'a String, y: &'a String) -> &'a String { - if x.len() > y.len() { x } else { y } -} - -fn main() { - let s1 = String::from("hello") - let result; - { - let s2 = String::from("hi") - result = longest(&s1, &s2) - } // s2 dropped here - - println(result) // ERROR: result might reference s2 -} -``` - -### Lifetime Bounds - -```affine -struct Reader['a] { - data: &'a [Byte] -} - -impl['a] Reader['a] { - fn new(data: &'a [Byte]) -> Reader['a] { - Reader { data } - } - - fn read(self: &Self) -> &'a [Byte] { - self.data - } -} -``` - ---- - -## Quantity Annotations - -### Linear Types (`1`) - -Must be used exactly once: - -```affine -fn must_close(file: 1 File) { - // Must call exactly one consuming method - file.close() -} - -fn bad(file: 1 File) { - // ERROR: Not used -} - -fn also_bad(file: 1 File) { - file.read() - file.close() // ERROR: Used twice -} -``` - -### Affine Types (default for `own`) - -Can be used at most once: - -```affine -fn maybe_use(resource: own Resource) { - if condition { - resource.consume() - } - // OK: resource dropped if not consumed -} -``` - -### Unrestricted Types (`w`) - -Can be used any number of times (for Copy types): - -```affine -fn many_uses(x: w Int) -> Int { - x + x + x // OK: unrestricted -} -``` - -### Erased Types (`0`) - -Compile-time only, no runtime representation: - -```affine -fn phantom[0 T](x: Int) -> Int { - // T exists for type checking but has no runtime cost - x -} -``` - ---- - -## Patterns - -### RAII (Resource Acquisition Is Initialization) - -```affine -fn process_file(path: &str) -> Result[(), Error] { - let file = File::open(path)? - // file is automatically closed when function returns - // even if there's an error - - let data = file.read_all()? - process(data)? - - Ok(()) -} // file.close() called automatically -``` - -### Handle Pattern - -```affine -fn with_file[T]( - path: &str, - f: (file: &mut File) -> T -) -> Result[T, Error] { - let mut file = File::open(path)? - let result = f(&mut file) - file.close()? - Ok(result) -} - -// Usage -let content = with_file("data.txt", |f| { - f.read_all() -})? -``` - -### Builder Pattern - -```affine -struct RequestBuilder { - url: Option[String], - method: Method, - headers: Vec[(String, String)] -} - -impl RequestBuilder { - fn new() -> own RequestBuilder { ... } - - fn url(self: own Self, url: String) -> own Self { - Self { url: Some(url), ..self } - } - - fn header(self: own Self, k: String, v: String) -> own Self { - let mut headers = self.headers; - headers.push((k, v)); - Self { headers, ..self } - } - - fn build(self: own Self) -> Result[Request, Error] { - // Consumes builder, returns Request - } -} - -let request = RequestBuilder::new() - .url("https://example.com") - .header("Content-Type", "application/json") - .build()? -``` - ---- - -## Common Errors - -### Use After Move - -```affine -let s = String::from("hello") -let s2 = s -println(s) // ERROR: value moved -``` - -**Fix**: Clone if you need both: -```affine -let s = String::from("hello") -let s2 = s.clone() -println(s) // OK -``` - -### Borrow While Mutably Borrowed - -```affine -let mut v = vec![1, 2, 3] -let first = &v[0] -v.push(4) // ERROR: v is borrowed -println(first) -``` - -**Fix**: Don't overlap borrows: -```affine -let mut v = vec![1, 2, 3] -let first = v[0] // Copy the value -v.push(4) // OK -println(first) -``` - -### Dangling Reference - -```affine -fn dangle() -> &String { - let s = String::from("hello") - &s // ERROR: s will be dropped -} -``` - -**Fix**: Return owned value: -```affine -fn no_dangle() -> String { - String::from("hello") // Ownership transferred -} -``` - -### Unused Linear Value - -```affine -fn unused(x: 1 Resource) { - // ERROR: x must be consumed -} -``` - -**Fix**: Consume or explicitly drop: -```affine -fn used(x: 1 Resource) { - x.close() // Consumed -} - -fn dropped(x: 1 Resource) { - drop(x) // Explicitly dropped -} -``` - ---- - -## See Also - -- [Types](types.md) - Type system overview -- [Effects](effects.md) - Effect system -- [Memory Model](../design/memory.md) - Memory layout details diff --git a/affinescript/wiki/language-reference/patterns.md b/affinescript/wiki/language-reference/patterns.md deleted file mode 100644 index 0b35920..0000000 --- a/affinescript/wiki/language-reference/patterns.md +++ /dev/null @@ -1,393 +0,0 @@ - -# Pattern Matching - -Pattern matching is a powerful feature for destructuring and conditionally binding values. - -## Table of Contents - -1. [Basic Patterns](#basic-patterns) -2. [Compound Patterns](#compound-patterns) -3. [Match Expressions](#match-expressions) -4. [Let Patterns](#let-patterns) -5. [Function Parameters](#function-parameters) -6. [Guards](#guards) -7. [Exhaustiveness](#exhaustiveness) - ---- - -## Basic Patterns - -### Wildcard Pattern - -Matches anything, binds nothing: - -```affine -match value { - _ -> "matched anything" -} - -let _ = unused_result() // Discard value -``` - -### Variable Pattern - -Matches anything, binds to name: - -```affine -match value { - x -> "bound to x: " ++ show(x) -} - -let x = some_value // Bind to x -``` - -### Literal Patterns - -Match specific values: - -```affine -match number { - 0 -> "zero", - 1 -> "one", - 42 -> "answer", - _ -> "other" -} - -match char { - 'a'..'z' -> "lowercase", - 'A'..'Z' -> "uppercase", - '0'..'9' -> "digit", - _ -> "other" -} - -match string { - "hello" -> "greeting", - "" -> "empty", - _ -> "other" -} -``` - ---- - -## Compound Patterns - -### Tuple Patterns - -```affine -let (x, y) = (1, 2) -let (first, _, third) = (1, 2, 3) // Ignore second - -match pair { - (0, 0) -> "origin", - (x, 0) -> "on x-axis", - (0, y) -> "on y-axis", - (x, y) -> "at (" ++ show(x) ++ ", " ++ show(y) ++ ")" -} -``` - -### Record Patterns - -```affine -struct Point { x: Int, y: Int } - -let Point { x, y } = point -let Point { x: a, y: b } = point // Rename bindings - -match point { - Point { x: 0, y: 0 } -> "origin", - Point { x: 0, .. } -> "on y-axis", - Point { y: 0, .. } -> "on x-axis", - Point { x, y } -> format("({}, {})", x, y) -} -``` - -### Constructor Patterns - -```affine -enum Option[T] { - Some(T), - None -} - -match opt { - Some(value) -> "has " ++ show(value), - None -> "empty" -} - -enum Result[T, E] { - Ok(T), - Err(E) -} - -match result { - Ok(value) -> process(value), - Err(e) -> handle_error(e) -} -``` - -### Nested Patterns - -```affine -match value { - Some(Some(x)) -> "doubly wrapped: " ++ show(x), - Some(None) -> "wrapped None", - None -> "outer None" -} - -match data { - Ok(Person { name, age: 0..17 }) -> "minor: " ++ name, - Ok(Person { name, age }) -> name ++ " (" ++ show(age) ++ ")", - Err(e) -> "error: " ++ e.message -} -``` - ---- - -## Match Expressions - -### Basic Match - -```affine -let description = match color { - Color::Red -> "red", - Color::Green -> "green", - Color::Blue -> "blue" -} -``` - -### Match with Blocks - -```affine -match event { - Event::Click { x, y } -> { - log("Click at ({}, {})", x, y) - handle_click(x, y) - }, - Event::KeyPress { key, modifiers } -> { - log("Key: {}", key) - handle_key(key, modifiers) - }, - Event::Resize { width, height } -> { - log("Resize to {}x{}", width, height) - resize(width, height) - } -} -``` - -### Or Patterns - -```affine -match day { - "Saturday" | "Sunday" -> "weekend", - _ -> "weekday" -} - -match number { - 0 | 1 -> "binary", - 2..9 -> "single digit", - _ -> "multiple digits" -} -``` - -### Binding with `@` - -```affine -match value { - opt @ Some(_) -> { - // opt is the whole Some value - process(opt) - }, - None -> default() -} - -match number { - n @ 1..100 -> { - // n is bound AND must be in range - "small number: " ++ show(n) - }, - n -> "large number: " ++ show(n) -} -``` - ---- - -## Let Patterns - -### Irrefutable Patterns in Let - -```affine -// Always succeeds -let (x, y) = get_pair() -let Point { x, y } = get_point() -let [a, b, c] = [1, 2, 3] -``` - -### Let-Else for Refutable Patterns - -```affine -// Pattern might not match -let Some(value) = opt else { - return None -} - -let Ok(data) = result else { - panic("Expected Ok") -} - -let [first, ..rest] = list else { - return "empty list" -} -``` - ---- - -## Function Parameters - -### Destructuring in Parameters - -```affine -fn distance((x1, y1): (Float, Float), (x2, y2): (Float, Float)) -> Float { - let dx = x2 - x1 - let dy = y2 - y1 - (dx * dx + dy * dy).sqrt() -} - -fn greet(Person { name, .. }: Person) -> String { - "Hello, " ++ name -} - -fn process_result(Ok(value) | Err(value): Result[Int, Int]) -> Int { - value -} -``` - ---- - -## Guards - -### Pattern Guards - -```affine -match number { - n if n < 0 -> "negative", - n if n == 0 -> "zero", - n if n > 0 -> "positive" -} - -match point { - Point { x, y } if x == y -> "on diagonal", - Point { x, y } if x == -y -> "on anti-diagonal", - Point { x, y } -> "elsewhere" -} -``` - -### Complex Guards - -```affine -match user { - User { role: Admin, .. } if is_authenticated() -> { - show_admin_panel() - }, - User { role: Member, subscription } if subscription.is_active() -> { - show_member_content() - }, - _ -> { - show_login_prompt() - } -} -``` - ---- - -## Exhaustiveness - -### Exhaustive Matching - -The compiler checks that all cases are covered: - -```affine -enum Direction { North, South, East, West } - -// Exhaustive - all cases covered -match dir { - North -> (0, 1), - South -> (0, -1), - East -> (1, 0), - West -> (-1, 0) -} - -// ERROR: non-exhaustive -match dir { - North -> (0, 1), - South -> (0, -1) -} -// error: patterns `East` and `West` not covered -``` - -### Non-Exhaustive with Wildcard - -```affine -match http_status { - 200 -> "OK", - 404 -> "Not Found", - 500 -> "Server Error", - _ -> "Unknown Status" // Catch-all -} -``` - -### Unreachable Patterns - -```affine -match value { - _ -> "catches all", - 42 -> "never reached" // WARNING: unreachable pattern -} -``` - ---- - -## Advanced Patterns - -### Slice Patterns - -```affine -match array { - [] -> "empty", - [x] -> "single: " ++ show(x), - [x, y] -> "pair: " ++ show(x) ++ ", " ++ show(y), - [first, ..middle, last] -> { - "first: " ++ show(first) ++ ", last: " ++ show(last) - } -} -``` - -### Reference Patterns - -```affine -match &opt { - &Some(ref x) -> { - // x is a reference, opt not consumed - println(x) - }, - &None -> println("empty") -} -``` - -### Box Patterns - -```affine -match boxed_value { - Box(inner) -> process(inner) -} -``` - ---- - -## See Also - -- [Types](types.md) - Enum and struct types -- [Expressions](expressions.md) - Match expressions -- [Functions](functions.md) - Parameter patterns diff --git a/affinescript/wiki/language-reference/rows.md b/affinescript/wiki/language-reference/rows.md deleted file mode 100644 index dcdc868..0000000 --- a/affinescript/wiki/language-reference/rows.md +++ /dev/null @@ -1,453 +0,0 @@ - -# Row Polymorphism - -Row polymorphism enables flexible, type-safe operations on extensible records and variants. - -## Table of Contents - -1. [Introduction](#introduction) -2. [Row Types](#row-types) -3. [Record Operations](#record-operations) -4. [Polymorphic Functions](#polymorphic-functions) -5. [Row Constraints](#row-constraints) -6. [Variants](#variants) -7. [Effect Rows](#effect-rows) -8. [Advanced Patterns](#advanced-patterns) - ---- - -## Introduction - -### The Problem - -Traditional record types are rigid: - -```affine -struct Person { name: String, age: Int } -struct Employee { name: String, age: Int, department: String } - -// These are completely different types! -// Can't write a function that works on both -``` - -### Row Polymorphism Solution - -Row polymorphism allows functions to work on records with certain fields, regardless of other fields: - -```affine -// Works on ANY record with a 'name' field -fn greet[r](person: {name: String, ..r}) -> String { - "Hello, " ++ person.name -} - -greet({name: "Alice"}) // OK -greet({name: "Bob", age: 30}) // OK -greet({name: "Carol", department: "Engineering", salary: 100000}) // OK -``` - ---- - -## Row Types - -### Closed Rows - -All fields are known: - -```affine -// Exactly these fields, nothing more -type Point = {x: Float64, y: Float64} - -let p: Point = {x: 1.0, y: 2.0} -// let bad: Point = {x: 1.0, y: 2.0, z: 3.0} // ERROR: extra field -``` - -### Open Rows - -Additional fields allowed: - -```affine -// Has x and y, plus any other fields -type HasXY = {x: Float64, y: Float64, ..} - -let p1: HasXY = {x: 1.0, y: 2.0} // OK -let p2: HasXY = {x: 1.0, y: 2.0, z: 3.0} // OK -let p3: HasXY = {x: 1.0, y: 2.0, name: "origin"} // OK -``` - -### Row Variables - -Named row variables for polymorphism: - -```affine -// r is a row variable - represents "the rest of the fields" -fn with_x[r](rec: {..r}) -> {x: Int, ..r} { - {x: 42, ..rec} -} - -let a = with_x({}) // {x: 42} -let b = with_x({y: 1}) // {x: 42, y: 1} -let c = with_x({y: 1, z: 2}) // {x: 42, y: 1, z: 2} -``` - ---- - -## Record Operations - -### Field Access - -```affine -let rec = {x: 1, y: 2, z: 3} - -rec.x // 1 -rec.y // 2 -``` - -### Record Construction - -```affine -// Literal syntax -let point = {x: 1.0, y: 2.0} - -// From variables (shorthand) -let x = 1.0 -let y = 2.0 -let point = {x, y} // Same as {x: x, y: y} -``` - -### Record Extension - -```affine -let base = {x: 1, y: 2} -let extended = {z: 3, ..base} // {z: 3, x: 1, y: 2} - -// Extension overrides existing fields -let updated = {x: 10, ..base} // {x: 10, y: 2} -``` - -### Record Update - -```affine -let rec = {x: 1, y: 2, z: 3} - -// Update specific fields -let rec2 = {rec with x = 10} // {x: 10, y: 2, z: 3} -let rec3 = {rec with x = 10, y = 20} // {x: 10, y: 20, z: 3} -``` - -### Record Restriction - -```affine -let rec = {x: 1, y: 2, z: 3} - -// Remove a field -let rec2 = rec \ z // {x: 1, y: 2} -let rec3 = rec \ x \ y // {z: 3} -``` - ---- - -## Polymorphic Functions - -### Working with Specific Fields - -```affine -// Access 'name' field from any record that has it -fn get_name[r](rec: {name: String, ..r}) -> String { - rec.name -} - -// Access multiple fields -fn full_name[r](person: {first: String, last: String, ..r}) -> String { - person.first ++ " " ++ person.last -} -``` - -### Transforming Records - -```affine -// Add a field -fn add_id[r](rec: {..r}) -> {id: Int, ..r} { - {id: generate_id(), ..rec} -} - -// Modify a field -fn uppercase_name[r](rec: {name: String, ..r}) -> {name: String, ..r} { - {rec with name = rec.name.to_uppercase()} -} - -// Remove a field -fn remove_secret[r](rec: {secret: String, ..r}) -> {..r} { - rec \ secret -} -``` - -### Preserving Extra Fields - -```affine -fn process_person[r](person: {name: String, age: Int, ..r}) -> {name: String, age: Int, ..r} { - // Process and return - extra fields preserved! - {person with age = person.age + 1} -} - -let employee = {name: "Alice", age: 30, department: "Eng", salary: 100000} -let updated = process_person(employee) -// updated: {name: "Alice", age: 31, department: "Eng", salary: 100000} -// department and salary preserved! -``` - ---- - -## Row Constraints - -### Lacks Constraint - -Ensure a field is NOT present: - -```affine -fn safe_add_x[r](rec: {..r}) -> {x: Int, ..r} -where - r lacks x // r must not already have 'x' -{ - {x: 0, ..rec} -} - -safe_add_x({y: 1}) // OK: {x: 0, y: 1} -// safe_add_x({x: 1}) // ERROR: r already has 'x' -``` - -### Has Constraint - -Ensure a field IS present: - -```affine -fn requires_name[r](rec: {..r}) -> String -where - r has name: String -{ - rec.name -} -``` - -### Multiple Constraints - -```affine -fn complex[r](rec: {..r}) -> {id: Int, ..r} -where - r has name: String, - r lacks id -{ - let id = hash(rec.name) - {id, ..rec} -} -``` - ---- - -## Variants - -Row polymorphism also works with variants (sum types): - -### Extensible Variants - -```affine -// Open variant type -type Error = [ - | NotFound(String) - | Unauthorized - | .. -] - -fn handle_error[r](err: [NotFound(String) | Unauthorized | ..r]) -> String { - match err { - NotFound(path) -> "Not found: " ++ path, - Unauthorized -> "Not authorized", - other -> "Other error" // Handles ..r - } -} -``` - -### Variant Extension - -```affine -type BaseError = [NotFound(String) | Unauthorized] -type ExtendedError = [Timeout | ..BaseError] -// ExtendedError = [Timeout | NotFound(String) | Unauthorized] -``` - -### Polymorphic Variant Functions - -```affine -fn map_error[e1, e2]( - result: Result[T, [..e1]], - f: ([..e1]) -> [..e2] -) -> Result[T, [..e2]] { - match result { - Ok(v) -> Ok(v), - Err(e) -> Err(f(e)) - } -} -``` - ---- - -## Effect Rows - -Effects use row polymorphism internally: - -### Effect Row Variables - -```affine -// e is an effect row variable -fn map_effect[e, A, B](f: (A) -{e}-> B, opt: Option[A]) -{e}-> Option[B] { - match opt { - Some(a) -> Some(f(a)), - None -> None - } -} - -// Works with any effects -map_effect(|x| x + 1, Some(5)) // Pure -map_effect(|x| { print(x); x }, Some(5)) // With IO -``` - -### Effect Combination - -```affine -fn combine[e1, e2]( - f: () -{e1}-> Int, - g: () -{e2}-> Int -) -{e1, e2}-> Int { - f() + g() -} -``` - ---- - -## Advanced Patterns - -### Record-Based APIs - -```affine -// Configuration with defaults -type Config = { - host: String, - port: Int, - timeout: Int, - .. -} - -fn with_defaults[r](partial: {..r}) -> {host: String, port: Int, timeout: Int, ..r} -where - r lacks host, - r lacks port, - r lacks timeout -{ - { - host: "localhost", - port: 8080, - timeout: 30, - ..partial - } -} - -let config = with_defaults({debug: true, max_connections: 100}) -// {host: "localhost", port: 8080, timeout: 30, debug: true, max_connections: 100} -``` - -### Builder Pattern with Rows - -```affine -fn builder[r]() -> {..r} where r = {} { - {} -} - -fn with_name[r](b: {..r}, name: String) -> {name: String, ..r} -where r lacks name -{ - {name, ..b} -} - -fn with_age[r](b: {..r}, age: Int) -> {age: Int, ..r} -where r lacks age -{ - {age, ..b} -} - -let person = builder() - |> with_name("Alice") - |> with_age(30) -// {name: "Alice", age: 30} -``` - -### Lenses with Rows - -```affine -// A lens focuses on a field -struct Lens[S, A] { - get: (S) -> A, - set: (S, A) -> S -} - -fn field_lens[r, A](field: String) -> Lens[{field: A, ..r}, A] { - Lens { - get: |s| s.field, - set: |s, a| {s with field = a} - } -} - -let name_lens = field_lens[_, String]("name") -name_lens.get({name: "Alice", age: 30}) // "Alice" -name_lens.set({name: "Alice", age: 30}, "Bob") // {name: "Bob", age: 30} -``` - -### Structural Subtyping - -```affine -// Wider records can be used where narrower expected -fn needs_point(p: {x: Float64, y: Float64}) -> Float64 { - p.x + p.y -} - -let point3d = {x: 1.0, y: 2.0, z: 3.0} -needs_point(point3d) // OK: point3d has x and y -``` - ---- - -## Implementation Notes - -### Row Unification - -The type checker unifies row types: - -``` -{x: Int, y: String, ..r1} ~ {y: String, z: Bool, ..r2} - -Unifies to: -r1 = {z: Bool, ..r3} -r2 = {x: Int, ..r3} -``` - -### Performance - -Row polymorphism is typically monomorphized: -- At compile time, concrete record types are generated -- Runtime performance equals hand-written code -- Some code size increase from specialization - -### Limitations - -- Cannot iterate over all fields (no reflection) -- Row variable cannot be "split" arbitrarily -- Some complex constraints are undecidable - ---- - -## See Also - -- [Types](types.md) - Record types -- [Effects](effects.md) - Effect rows -- [Design: Rows](../design/type-system.md#rows) - Theory diff --git a/affinescript/wiki/language-reference/traits.md b/affinescript/wiki/language-reference/traits.md deleted file mode 100644 index b7ec7d4..0000000 --- a/affinescript/wiki/language-reference/traits.md +++ /dev/null @@ -1,488 +0,0 @@ - -# Traits - -Traits define shared behavior that types can implement. They enable polymorphism and code reuse. - -## Table of Contents - -1. [Defining Traits](#defining-traits) -2. [Implementing Traits](#implementing-traits) -3. [Using Traits](#using-traits) -4. [Associated Types](#associated-types) -5. [Default Implementations](#default-implementations) -6. [Trait Bounds](#trait-bounds) -7. [Coherence](#coherence) -8. [Standard Traits](#standard-traits) - ---- - -## Defining Traits - -### Basic Trait - -```affine -trait Greet { - fn greet(self: &Self) -> String -} -``` - -### Trait with Multiple Methods - -```affine -trait Shape { - fn area(self: &Self) -> Float64 - fn perimeter(self: &Self) -> Float64 - fn contains(self: &Self, point: Point) -> Bool -} -``` - -### Trait with Type Parameters - -```affine -trait Container[T] { - fn new() -> Self - fn add(self: &mut Self, item: T) - fn get(self: &Self, index: Int) -> Option[&T] - fn len(self: &Self) -> Int -} -``` - ---- - -## Implementing Traits - -### Basic Implementation - -```affine -struct Person { - name: String, - age: Int -} - -impl Greet for Person { - fn greet(self: &Self) -> String { - "Hello, my name is " ++ self.name - } -} - -let p = Person { name: "Alice", age: 30 } -p.greet() // "Hello, my name is Alice" -``` - -### Implementation for Generic Types - -```affine -impl[T] Container[T] for Vec[T] { - fn new() -> Vec[T] { - Vec::empty() - } - - fn add(self: &mut Vec[T], item: T) { - self.push(item) - } - - fn get(self: &Vec[T], index: Int) -> Option[&T] { - if index < self.len() { - Some(&self.data[index]) - } else { - None - } - } - - fn len(self: &Vec[T]) -> Int { - self.data.len() - } -} -``` - -### Conditional Implementation - -```affine -// Only implement Show for Option[T] when T: Show -impl[T: Show] Show for Option[T] { - fn show(self: &Self) -> String { - match self { - Some(x) -> "Some(" ++ x.show() ++ ")", - None -> "None" - } - } -} -``` - ---- - -## Using Traits - -### Method Calls - -```affine -let circle = Circle { radius: 5.0 } -let area = circle.area() // Uses Shape::area - -let vec: Vec[Int] = Container::new() -vec.add(42) // Uses Container::add -``` - -### Trait Objects (Dynamic Dispatch) - -```affine -// dyn Trait for runtime polymorphism -fn describe_shape(shape: &dyn Shape) -> String { - format("Area: {}, Perimeter: {}", shape.area(), shape.perimeter()) -} - -let shapes: Vec[Box[dyn Shape]] = vec![ - Box::new(Circle { radius: 1.0 }), - Box::new(Rectangle { width: 2.0, height: 3.0 }) -] - -for shape in shapes { - println(describe_shape(&*shape)) -} -``` - -### Static Dispatch (Generics) - -```affine -fn describe_shape_generic[S: Shape](shape: &S) -> String { - format("Area: {}, Perimeter: {}", shape.area(), shape.perimeter()) -} - -// More efficient - no runtime dispatch -describe_shape_generic(&circle) -``` - ---- - -## Associated Types - -### Defining Associated Types - -```affine -trait Iterator { - type Item - - fn next(self: &mut Self) -> Option[Self::Item] -} -``` - -### Implementing Associated Types - -```affine -struct RangeIter { - current: Int, - end: Int -} - -impl Iterator for RangeIter { - type Item = Int - - fn next(self: &mut Self) -> Option[Int] { - if self.current < self.end { - let value = self.current - self.current += 1 - Some(value) - } else { - None - } - } -} -``` - -### Using Associated Types in Bounds - -```affine -fn sum_iter[I](iter: &mut I) -> I::Item -where - I: Iterator, - I::Item: Add + Default -{ - let mut total = I::Item::default() - while let Some(x) = iter.next() { - total = total + x - } - total -} -``` - ---- - -## Default Implementations - -### Providing Defaults - -```affine -trait Eq { - fn eq(self: &Self, other: &Self) -> Bool - - // Default implementation using eq - fn ne(self: &Self, other: &Self) -> Bool { - !self.eq(other) - } -} - -impl Eq for Int { - fn eq(self: &Int, other: &Int) -> Bool { - // Only need to implement eq, ne comes free - *self == *other - } -} -``` - -### Overriding Defaults - -```affine -trait Animal { - fn speak(self: &Self) -> String - - fn introduce(self: &Self) -> String { - "I am an animal that says: " ++ self.speak() - } -} - -struct Dog { name: String } - -impl Animal for Dog { - fn speak(self: &Self) -> String { - "Woof!" - } - - // Override default - fn introduce(self: &Self) -> String { - "I am " ++ self.name ++ " and I say: " ++ self.speak() - } -} -``` - ---- - -## Trait Bounds - -### Single Bound - -```affine -fn print_value[T: Show](x: T) -{IO}-> Unit { - println(x.show()) -} -``` - -### Multiple Bounds - -```affine -fn process[T: Clone + Show + Eq](x: T) -> T { - let y = x.clone() - if x.eq(&y) { - println(x.show()) - } - y -} -``` - -### Where Clauses - -```affine -fn complex_function[A, B, C](a: A, b: B) -> C -where - A: Clone, - B: Into[A], - C: From[A] + Default, - A: Show -{ - let converted: A = b.into() - println(converted.show()) - C::from(converted) -} -``` - -### Higher-Ranked Trait Bounds - -```affine -fn apply_to_all[F](items: Vec[Int], f: F) -where - F: for['a] Fn(&'a Int) -> Int -{ - for item in items { - f(&item) - } -} -``` - -### Supertraits - -```affine -// Ord requires Eq -trait Ord: Eq { - fn compare(self: &Self, other: &Self) -> Ordering -} - -// Must implement both Eq and Ord -impl Eq for MyType { ... } -impl Ord for MyType { ... } -``` - ---- - -## Coherence - -### Orphan Rules - -You can only implement a trait if either: -- The trait is defined in your crate, OR -- The type is defined in your crate - -```affine -// OK: Your trait, external type -trait MyTrait { } -impl MyTrait for Vec[Int] { } - -// OK: External trait, your type -struct MyType { } -impl Show for MyType { } - -// ERROR: External trait, external type -// impl Show for Vec[Int] { } // Not allowed -``` - -### Blanket Implementations - -```affine -// Implement for all types that satisfy a bound -impl[T: Show] Show for Vec[T] { - fn show(self: &Self) -> String { - let items = self.iter().map(|x| x.show()).join(", ") - "[" ++ items ++ "]" - } -} -``` - ---- - -## Standard Traits - -### Comparison Traits - -```affine -trait Eq { - fn eq(self: &Self, other: &Self) -> Bool -} - -trait Ord: Eq { - fn compare(self: &Self, other: &Self) -> Ordering -} - -enum Ordering { Less, Equal, Greater } -``` - -### Conversion Traits - -```affine -trait From[T] { - fn from(value: T) -> Self -} - -trait Into[T] { - fn into(self) -> T -} - -// From implies Into -impl[T, U: From[T]] Into[U] for T { - fn into(self) -> U { - U::from(self) - } -} -``` - -### Display and Debug - -```affine -trait Show { - fn show(self: &Self) -> String -} - -trait Debug { - fn debug(self: &Self) -> String -} -``` - -### Clone and Copy - -```affine -trait Clone { - fn clone(self: &Self) -> Self -} - -// Copy is a marker trait - types are bitwise-copyable -trait Copy: Clone { } -``` - -### Default - -```affine -trait Default { - fn default() -> Self -} - -impl Default for Int { - fn default() -> Int { 0 } -} - -impl Default for String { - fn default() -> String { "" } -} -``` - -### Hashing - -```affine -trait Hash { - fn hash(self: &Self, hasher: &mut Hasher) -} -``` - -### Iterator Traits - -```affine -trait Iterator { - type Item - fn next(self: &mut Self) -> Option[Self::Item] -} - -trait IntoIterator { - type Item - type IntoIter: Iterator[Item = Self::Item] - fn into_iter(self) -> Self::IntoIter -} -``` - ---- - -## Derive Macros - -Automatically implement common traits: - -```affine -#[derive(Eq, Ord, Clone, Show, Hash, Default)] -struct Point { - x: Int, - y: Int -} - -// Generates: -// impl Eq for Point { ... } -// impl Ord for Point { ... } -// impl Clone for Point { ... } -// impl Show for Point { ... } -// impl Hash for Point { ... } -// impl Default for Point { ... } -``` - ---- - -## See Also - -- [Types](types.md) - Type system overview -- [Functions](functions.md) - Generic functions -- [Standard Library](../stdlib/overview.md) - Standard traits diff --git a/affinescript/wiki/language-reference/types.md b/affinescript/wiki/language-reference/types.md deleted file mode 100644 index 4fbbb96..0000000 --- a/affinescript/wiki/language-reference/types.md +++ /dev/null @@ -1,607 +0,0 @@ - -# Type System - -AffineScript features an advanced type system combining: -- **Affine types** for memory safety -- **Dependent types** for compile-time verification -- **Row polymorphism** for flexible records -- **Algebraic effects** for controlled side effects - -## Table of Contents - -1. [Primitive Types](#primitive-types) -2. [Compound Types](#compound-types) -3. [Function Types](#function-types) -4. [Generic Types](#generic-types) -5. [Ownership Types](#ownership-types) -6. [Dependent Types](#dependent-types) -7. [Row Types](#row-types) -8. [Effect Types](#effect-types) -9. [Refinement Types](#refinement-types) -10. [Type Inference](#type-inference) - ---- - -## Primitive Types - -### Numeric Types - -| Type | Description | Size | -|------|-------------|------| -| `Int` | Signed integer | Platform-dependent | -| `Int8` | 8-bit signed | 1 byte | -| `Int16` | 16-bit signed | 2 bytes | -| `Int32` | 32-bit signed | 4 bytes | -| `Int64` | 64-bit signed | 8 bytes | -| `Nat` | Natural numbers | Platform-dependent | -| `Float32` | 32-bit IEEE float | 4 bytes | -| `Float64` | 64-bit IEEE float | 8 bytes | - -### Other Primitives - -| Type | Description | -|------|-------------| -| `Bool` | Boolean (`true` / `false`) | -| `Char` | Unicode scalar value | -| `String` | UTF-8 string | -| `Unit` | Unit type (single value `()`) | -| `Never` | Uninhabited type (no values) | - -```affine -let age: Int = 25 -let pi: Float64 = 3.14159 -let active: Bool = true -let letter: Char = 'A' -let name: String = "Alice" -let nothing: Unit = () -``` - ---- - -## Compound Types - -### Tuples - -Fixed-size, heterogeneous collections: - -```affine -// Tuple type -let pair: (Int, String) = (42, "answer") - -// Accessing elements -let x = pair.0 // 42 -let y = pair.1 // "answer" - -// Destructuring -let (num, text) = pair - -// Unit is the empty tuple -let unit: () = () -``` - -### Arrays - -Fixed-size, homogeneous collections: - -```affine -// Array type with length -let nums: [Int; 3] = [1, 2, 3] - -// Indexing -let first = nums[0] - -// Length is part of the type -fn sum_three(arr: [Int; 3]) -> Int { - arr[0] + arr[1] + arr[2] -} -``` - -### Records (Structs) - -Named fields with structural typing: - -```affine -// Anonymous record -let person: {name: String, age: Int} = { - name: "Alice", - age: 30 -} - -// Field access -let n = person.name - -// Named struct -struct Point { - x: Float64, - y: Float64 -} - -let p = Point { x: 1.0, y: 2.0 } -``` - -### Enums (Variants) - -Sum types with constructors: - -```affine -enum Option[T] { - Some(T), - None -} - -enum Result[T, E] { - Ok(T), - Err(E) -} - -enum Shape { - Circle { radius: Float64 }, - Rectangle { width: Float64, height: Float64 }, - Point -} - -let x: Option[Int] = Some(42) -let y: Result[String, Error] = Ok("success") -``` - ---- - -## Function Types - -### Basic Function Types - -```affine -// Simple function type -fn add(x: Int, y: Int) -> Int { - x + y -} - -// Function as value -let f: (Int, Int) -> Int = add - -// Higher-order function -fn apply(f: (Int) -> Int, x: Int) -> Int { - f(x) -} -``` - -### Effectful Function Types - -Functions can declare their effects: - -```affine -// Pure function (no effects) -fn pure_add(x: Int, y: Int) -> Int { - x + y -} - -// Function with IO effect -fn print_sum(x: Int, y: Int) -{IO}-> Unit { - print(x + y) -} - -// Multiple effects -fn read_and_log() -{IO, Log}-> String { - let data = read_file("data.txt"); - log("Read file"); - data -} -``` - -### Closures (Lambdas) - -```affine -// Lambda syntax -let double = |x| x * 2 -let add = |x, y| x + y - -// With type annotations -let typed: (Int) -> Int = |x: Int| -> Int { x * 2 } - -// Capturing environment -let multiplier = 3 -let times_three = |x| x * multiplier -``` - ---- - -## Generic Types - -### Type Parameters - -```affine -// Generic function -fn identity[T](x: T) -> T { - x -} - -// Generic struct -struct Pair[A, B] { - first: A, - second: B -} - -// Generic enum -enum List[T] { - Cons(T, Box[List[T]]), - Nil -} -``` - -### Type Constraints - -```affine -// Single constraint -fn compare[T: Ord](x: T, y: T) -> Ordering { - x.compare(y) -} - -// Multiple constraints -fn hash_and_show[T: Hash + Show](x: T) -> String { - format("hash={}, value={}", x.hash(), x.show()) -} - -// Where clause for complex constraints -fn complex[A, B](x: A, y: B) -> Bool -where - A: Eq + Clone, - B: Eq -{ - x.clone() == x && y == y -} -``` - -### Associated Types - -```affine -trait Iterator { - type Item - - fn next(self: &mut Self) -> Option[Self::Item] -} - -impl Iterator for Range { - type Item = Int - - fn next(self: &mut Range) -> Option[Int] { - if self.current < self.end { - let value = self.current; - self.current += 1; - Some(value) - } else { - None - } - } -} -``` - ---- - -## Ownership Types - -### Ownership Modifiers - -| Modifier | Meaning | Rules | -|----------|---------|-------| -| `own` | Owned value | Must be consumed exactly once | -| `ref` or `&` | Shared borrow | Read-only, multiple allowed | -| `mut` or `&mut` | Mutable borrow | Exclusive access | - -```affine -// Owned - must transfer or consume -fn consume(file: own File) { - // file is consumed here - file.close() -} - -// Shared borrow - read only -fn read(file: &File) -> String { - file.read_all() -} - -// Mutable borrow - exclusive write -fn modify(file: &mut File) { - file.write("data") -} -``` - -### Quantity Annotations - -For fine-grained linearity control: - -| Quantity | Symbol | Meaning | -|----------|--------|---------| -| Erased | `0` | Compile-time only | -| Linear | `1` | Used exactly once | -| Affine | `?` | Used at most once | -| Unrestricted | `w` | Used any number of times | - -```affine -// Linear type - must use exactly once -fn use_linear(x: 1 Resource) -> Result { - x.consume() // Must be called -} - -// Erased type parameter - exists at compile time only -fn phantom[0 T]() -> Unit { - // T has no runtime representation -} -``` - ---- - -## Dependent Types - -### Length-Indexed Types - -```affine -// Vec with compile-time length -struct Vec[n: Nat, T] { - data: [T; n] -} - -// Safe head - requires non-empty -fn head[n: Nat, T](vec: Vec[n + 1, T]) -> T { - vec.data[0] -} - -// Append preserves length information -fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], - b: Vec[m, T] -) -> Vec[n + m, T] { - // Implementation... -} -``` - -### Dependent Functions (Pi Types) - -```affine -// Return type depends on input -fn replicate[T](n: Nat, x: T) -> Vec[n, T] { - // Creates vector of exactly n elements -} - -// Type-level computation -fn zeros(n: Nat) -> Vec[n, Int] { - replicate(n, 0) -} -``` - -### Sigma Types (Dependent Pairs) - -```affine -// Existential quantification -type ExistsVec[T] = (n: Nat, Vec[n, T]) - -fn unknown_length[T](data: [T]) -> ExistsVec[T] { - let n = data.len(); - (n, Vec::from_array(data)) -} -``` - ---- - -## Row Types - -### Extensible Records - -```affine -// Open record type -type HasName = {name: String, ..} - -// Function works on any record with 'name' -fn greet(person: HasName) -> String { - "Hello, " ++ person.name -} - -// Works with different record types -greet({name: "Alice"}) -greet({name: "Bob", age: 30}) -greet({name: "Carol", role: "Admin", active: true}) -``` - -### Row Variables - -```affine -// Explicit row variable -fn add_field[r](rec: {..r}) -> {id: Int, ..r} { - {id: generate_id(), ..rec} -} - -// Row constraint - field must be absent -fn safe_extend[r](rec: {..r}) -> {x: Int, ..r} -where - r lacks x -{ - {x: 0, ..rec} -} -``` - -### Record Operations - -```affine -let rec = {x: 1, y: 2, z: 3} - -// Update -let rec2 = {rec with x = 10} // {x: 10, y: 2, z: 3} - -// Extend -let rec3 = {w: 4, ..rec} // {w: 4, x: 1, y: 2, z: 3} - -// Restrict (remove field) -let rec4 = rec \ z // {x: 1, y: 2} -``` - ---- - -## Effect Types - -### Effect Annotations - -```affine -// Pure function -fn pure(x: Int) -> Int { - x * 2 -} - -// Single effect -fn with_io() -{IO}-> Unit { - print("Hello") -} - -// Multiple effects -fn with_effects() -{IO, State[Int], Exn}-> Int { - let state = get(); - print("Current: " ++ show(state)); - if state < 0 { - raise(NegativeError) - }; - state -} - -// Effect polymorphism -fn map_effect[E, A, B]( - f: (A) -{E}-> B, - opt: Option[A] -) -{E}-> Option[B] { - match opt { - Some(a) -> Some(f(a)), - None -> None - } -} -``` - -### Effect Rows - -```affine -// Effect row variable -fn combine[e1, e2, A]( - f: () -{e1}-> A, - g: (A) -{e2}-> A -) -{e1, e2}-> A { - g(f()) -} -``` - ---- - -## Refinement Types - -### Basic Refinements - -```affine -// Positive integers -type PosInt = Int where (self > 0) - -// Non-empty strings -type NonEmpty = String where (self.len() > 0) - -// Bounded integers -type Percentage = Int where (self >= 0 && self <= 100) - -fn divide(x: Int, y: Int where (y != 0)) -> Int { - x / y -} -``` - -### Refinement Inference - -```affine -fn safe_index[n: Nat, T]( - vec: Vec[n, T], - idx: Nat where (idx < n) // Must be in bounds -) -> T { - vec.data[idx] -} - -// Refinement is checked at call site -let v: Vec[3, Int] = [1, 2, 3]; -safe_index(v, 0) // OK: 0 < 3 -safe_index(v, 2) // OK: 2 < 3 -safe_index(v, 3) // ERROR: 3 < 3 is false -``` - ---- - -## Type Inference - -AffineScript uses bidirectional type inference: - -### Inference Examples - -```affine -// Type inferred from literal -let x = 42 // x: Int -let y = 3.14 // y: Float64 -let z = "hello" // z: String - -// Type inferred from usage -let f = |x| x + 1 // f: (Int) -> Int - -// Type inferred from context -let nums = [1, 2, 3] // nums: [Int; 3] - -// Generic type inferred -let opt = Some(42) // opt: Option[Int] -``` - -### When Annotations Are Required - -```affine -// Ambiguous literals need annotation -let x: Int32 = 42 // Could be Int8, Int16, etc. - -// Empty collections -let empty: Vec[0, Int] = [] - -// Polymorphic return -fn id[T](x: T) -> T { x } -let result: String = id("hello") // T inferred as String -``` - -### Type Ascription - -```affine -// Inline type annotation -let x = (42: Int64) - -// Expression type check -let y = some_function() : ExpectedType -``` - ---- - -## Type Aliases - -```affine -// Simple alias -type UserId = Int - -// Generic alias -type Pair[A] = (A, A) - -// Complex alias -type Handler[E, R] = (E) -{IO}-> R - -// Recursive alias (requires explicit type) -type JsonValue = - | Null - | Bool(Bool) - | Number(Float64) - | String(String) - | Array(Vec[JsonValue]) - | Object(Map[String, JsonValue]) -``` - ---- - -## See Also - -- [Ownership](ownership.md) - Detailed ownership rules -- [Effects](effects.md) - Effect system details -- [Dependent Types](dependent-types.md) - Advanced dependent types -- [Row Polymorphism](rows.md) - Row type details diff --git a/affinescript/wiki/stdlib/overview.md b/affinescript/wiki/stdlib/overview.md deleted file mode 100644 index 090817e..0000000 --- a/affinescript/wiki/stdlib/overview.md +++ /dev/null @@ -1,425 +0,0 @@ - -# Standard Library Overview - -The AffineScript standard library provides essential types, traits, and functions. - -## Library Organization - -``` -std/ -├── prelude.affine # Auto-imported basics -├── primitives/ # Primitive types -│ ├── int.affine -│ ├── float.affine -│ ├── bool.affine -│ ├── char.affine -│ └── string.affine -├── collections/ # Data structures -│ ├── vec.affine -│ ├── array.affine -│ ├── list.affine -│ ├── map.affine -│ ├── set.affine -│ └── deque.affine -├── core/ # Core types -│ ├── option.affine -│ ├── result.affine -│ ├── tuple.affine -│ └── unit.affine -├── memory/ # Memory management -│ ├── box.affine -│ ├── rc.affine -│ ├── arc.affine -│ └── ptr.affine -├── traits/ # Standard traits -│ ├── eq.affine -│ ├── ord.affine -│ ├── hash.affine -│ ├── show.affine -│ ├── clone.affine -│ ├── default.affine -│ └── iter.affine -├── effects/ # Standard effects -│ ├── io.affine -│ ├── exn.affine -│ ├── async.affine -│ ├── state.affine -│ └── random.affine -├── io/ # Input/Output -│ ├── read.affine -│ ├── write.affine -│ ├── fs.affine -│ └── net.affine -├── concurrency/ # Threading -│ ├── thread.affine -│ ├── sync.affine -│ ├── channel.affine -│ └── atomic.affine -├── text/ # Text processing -│ ├── fmt.affine -│ ├── regex.affine -│ └── unicode.affine -└── utils/ # Utilities - ├── time.affine - ├── env.affine - └── debug.affine -``` - -## Prelude - -Auto-imported types and traits: - -```affine -// Types -use std::option::Option::{self, Some, None} -use std::result::Result::{self, Ok, Err} -use std::string::String -use std::vec::Vec - -// Traits -use std::traits::{Eq, Ord, Hash, Show, Clone, Default} -use std::iter::{Iterator, IntoIterator} - -// Functions -use std::io::{print, println, eprint, eprintln} -``` - -## Primitive Types - -### Numeric Types - -```affine -// Signed integers -Int // Platform-dependent (32 or 64 bit) -Int8 // -128 to 127 -Int16 // -32768 to 32767 -Int32 // -2^31 to 2^31-1 -Int64 // -2^63 to 2^63-1 - -// Unsigned integers -Nat // Natural numbers (>= 0) -UInt8 // 0 to 255 -UInt16 // 0 to 65535 -UInt32 // 0 to 2^32-1 -UInt64 // 0 to 2^64-1 - -// Floating point -Float32 // 32-bit IEEE 754 -Float64 // 64-bit IEEE 754 - -// Methods -42.abs() // 42 -(-5).abs() // 5 -10.pow(3) // 1000 -16.to_string() // "16" -Int::max_value() // 2147483647 -``` - -### Bool - -```affine -true.and(false) // false -true.or(false) // true -true.not() // false -true.then(42) // Some(42) -false.then(42) // None -``` - -### Char - -```affine -'a'.is_alphabetic() // true -'5'.is_digit() // true -'A'.to_lowercase() // 'a' -'z'.to_uppercase() // 'Z' -' '.is_whitespace() // true -``` - -### String - -```affine -let s = "hello" - -s.len() // 5 -s.is_empty() // false -s.chars() // Iterator over chars -s.bytes() // Iterator over bytes -s.lines() // Iterator over lines - -s.to_uppercase() // "HELLO" -s.to_lowercase() // "hello" -s.trim() // Remove whitespace -s.split(" ") // Split by delimiter - -s.contains("ell") // true -s.starts_with("he") // true -s.ends_with("lo") // true - -s.replace("l", "L") // "heLLo" - -// String building -let mut buf = String::new() -buf.push_str("hello") -buf.push(' ') -buf.push_str("world") -buf // "hello world" -``` - -## Core Types - -### Option - -```affine -enum Option[T] { - Some(T), - None -} - -let x: Option[Int] = Some(42) -let y: Option[Int] = None - -x.is_some() // true -x.is_none() // false -x.unwrap() // 42 -x.unwrap_or(0) // 42 -y.unwrap_or(0) // 0 - -x.map(|n| n * 2) // Some(84) -y.map(|n| n * 2) // None - -x.and_then(|n| if n > 0 { Some(n) } else { None }) - -x.ok_or("error") // Ok(42) -y.ok_or("error") // Err("error") -``` - -### Result - -```affine -enum Result[T, E] { - Ok(T), - Err(E) -} - -let x: Result[Int, String] = Ok(42) -let y: Result[Int, String] = Err("failed") - -x.is_ok() // true -x.is_err() // false - -x.ok() // Some(42) -x.err() // None - -x.map(|n| n * 2) // Ok(84) -x.map_err(|e| e.len()) // Ok(42) - -x.and_then(|n| Ok(n + 1)) // Ok(43) - -// Error propagation with ? -fn may_fail() -> Result[Int, Error] { - let x = try_something()? - let y = try_another()? - Ok(x + y) -} -``` - -## Collections - -### Vec - -```affine -let mut v: Vec[Int] = vec![] -v.push(1) -v.push(2) -v.push(3) - -v.len() // 3 -v.is_empty() // false -v[0] // 1 -v.get(0) // Some(&1) -v.get(10) // None - -v.pop() // Some(3) -v.first() // Some(&1) -v.last() // Some(&2) - -v.iter() // Iterator -v.iter_mut() // Mutable iterator - -v.map(|x| x * 2) // [2, 4] -v.filter(|x| x > 1) // [2] -v.fold(0, |acc, x| acc + x) // 3 -``` - -### HashMap - -```affine -let mut m: HashMap[String, Int] = HashMap::new() -m.insert("one", 1) -m.insert("two", 2) - -m.get("one") // Some(&1) -m.get("three") // None - -m.contains_key("one") // true -m.remove("one") // Some(1) - -m.keys() // Iterator over keys -m.values() // Iterator over values -m.iter() // Iterator over (key, value) -``` - -### HashSet - -```affine -let mut s: HashSet[Int] = HashSet::new() -s.insert(1) -s.insert(2) - -s.contains(&1) // true -s.remove(&1) // true - -let s2 = HashSet::from([2, 3, 4]) -s.union(&s2) // {2, 3, 4} -s.intersection(&s2) // {2} -s.difference(&s2) // {} -``` - -## Traits - -### Eq - -```affine -trait Eq { - fn eq(self: &Self, other: &Self) -> Bool - fn ne(self: &Self, other: &Self) -> Bool { - !self.eq(other) - } -} -``` - -### Ord - -```affine -trait Ord: Eq { - fn compare(self: &Self, other: &Self) -> Ordering - fn lt(self: &Self, other: &Self) -> Bool - fn le(self: &Self, other: &Self) -> Bool - fn gt(self: &Self, other: &Self) -> Bool - fn ge(self: &Self, other: &Self) -> Bool - fn min(self: Self, other: Self) -> Self - fn max(self: Self, other: Self) -> Self -} - -enum Ordering { Less, Equal, Greater } -``` - -### Iterator - -```affine -trait Iterator { - type Item - fn next(self: &mut Self) -> Option[Self::Item] - - // Provided methods - fn map[B](self, f: (Self::Item) -> B) -> Map[Self, B] - fn filter(self, p: (&Self::Item) -> Bool) -> Filter[Self] - fn fold[B](self, init: B, f: (B, Self::Item) -> B) -> B - fn collect[C: FromIterator](self) -> C - // ... many more -} -``` - -## Effects - -### IO - -```affine -effect IO { - fn print(s: &str) - fn println(s: &str) - fn read_line() -> String - fn read_file(path: &str) -> Result[String, IoError] - fn write_file(path: &str, content: &str) -> Result[(), IoError] -} -``` - -### State - -```affine -effect State[S] { - fn get() -> S - fn put(s: S) - fn modify(f: (S) -> S) -} - -fn run_state[S, A](initial: S, f: () -{State[S]}-> A) -> (A, S) -``` - -### Error - -```affine -effect Error[E] { - fn raise(e: E) -> Never -} - -fn catch[E, A](f: () -{Error[E]}-> A, handler: (E) -> A) -> A -``` - -## I/O - -### File System - -```affine -use std::fs - -// Reading -let content = fs::read_to_string("file.txt")? -let bytes = fs::read("file.bin")? - -// Writing -fs::write("file.txt", "content")? -fs::write("file.bin", bytes)? - -// File operations -fs::exists("file.txt") -fs::remove("file.txt")? -fs::rename("old.txt", "new.txt")? -fs::copy("src.txt", "dst.txt")? - -// Directories -fs::create_dir("path")? -fs::create_dir_all("path/to/dir")? -fs::remove_dir("path")? -fs::read_dir("path")? // Iterator -``` - -### Networking - -```affine -use std::net::{TcpListener, TcpStream} - -// Server -let listener = TcpListener::bind("127.0.0.1:8080")? -for stream in listener.incoming() { - handle_connection(stream?) -} - -// Client -let mut stream = TcpStream::connect("127.0.0.1:8080")? -stream.write_all(b"Hello")? -let mut buf = [0; 1024] -stream.read(&mut buf)? -``` - ---- - -## See Also - -- [Primitives](primitives.md) - Primitive type details -- [Collections](collections.md) - Collection types -- [Effects](effects.md) - Standard effects -- [I/O](io.md) - Input/output operations diff --git a/affinescript/wiki/testing/guide.md b/affinescript/wiki/testing/guide.md deleted file mode 100644 index 307f563..0000000 --- a/affinescript/wiki/testing/guide.md +++ /dev/null @@ -1,631 +0,0 @@ - -# Testing Guide - -Comprehensive guide to testing AffineScript programs and the compiler itself. - -## Table of Contents - -1. [Testing Framework](#testing-framework) -2. [Writing Tests](#writing-tests) -3. [Test Organization](#test-organization) -4. [Property-Based Testing](#property-based-testing) -5. [Fuzzing](#fuzzing) -6. [Compiler Testing](#compiler-testing) -7. [Integration Tests](#integration-tests) -8. [Benchmarking](#benchmarking) - ---- - -## Testing Framework - -AffineScript uses a built-in testing framework: - -### Basic Test Structure - -```affine -#[test] -fn test_addition() { - assert_eq(2 + 2, 4) -} - -#[test] -fn test_string_concat() { - let s = "hello" ++ " " ++ "world" - assert_eq(s, "hello world") -} -``` - -### Running Tests - -```bash -# Run all tests -aspm test - -# Run specific test file -aspm test tests/math_test.affine - -# Run tests matching pattern -aspm test --filter "test_add" - -# Run with verbose output -aspm test --verbose - -# Run tests in parallel -aspm test --jobs 4 -``` - ---- - -## Writing Tests - -### Assertions - -```affine -// Equality -assert_eq(actual, expected) -assert_ne(actual, unexpected) - -// Boolean -assert(condition) -assert(condition, "custom message") - -// Comparisons -assert_lt(a, b) // a < b -assert_le(a, b) // a <= b -assert_gt(a, b) // a > b -assert_ge(a, b) // a >= b - -// Option/Result -assert_some(opt) -assert_none(opt) -assert_ok(result) -assert_err(result) - -// Collections -assert_contains(collection, element) -assert_empty(collection) -assert_len(collection, expected_len) -``` - -### Test Attributes - -```affine -// Basic test -#[test] -fn test_basic() { ... } - -// Test expected to panic -#[test] -#[should_panic] -fn test_panic() { - panic("expected panic") -} - -// Test expected to panic with specific message -#[test] -#[should_panic(expected = "index out of bounds")] -fn test_bounds() { - let arr = [1, 2, 3] - arr[10] // Panics -} - -// Ignore test (skip) -#[test] -#[ignore] -fn test_slow() { ... } - -// Ignore with reason -#[test] -#[ignore = "requires network"] -fn test_network() { ... } - -// Test with timeout -#[test] -#[timeout(1000)] // 1000ms -fn test_fast() { ... } -``` - -### Test Fixtures - -```affine -mod tests { - use super::* - - // Setup function - fn setup() -> TestContext { - TestContext::new() - } - - // Teardown function - fn teardown(ctx: TestContext) { - ctx.cleanup() - } - - #[test] - fn test_with_fixture() { - let ctx = setup() - - // Test code... - - teardown(ctx) - } -} -``` - -### Test Modules - -```affine -// In lib.affine -pub fn add(x: Int, y: Int) -> Int { - x + y -} - -#[cfg(test)] -mod tests { - use super::* - - #[test] - fn test_add_positive() { - assert_eq(add(2, 3), 5) - } - - #[test] - fn test_add_negative() { - assert_eq(add(-1, 1), 0) - } - - #[test] - fn test_add_zero() { - assert_eq(add(0, 0), 0) - } -} -``` - ---- - -## Test Organization - -### Directory Structure - -``` -project/ -├── src/ -│ ├── lib.affine -│ └── math/ -│ └── mod.affine -├── tests/ -│ ├── integration_test.affine # Integration tests -│ ├── math_test.affine # Module tests -│ └── fixtures/ -│ └── test_data.json -└── benches/ - └── performance_bench.affine # Benchmarks -``` - -### Unit vs Integration Tests - -```affine -// tests/unit_test.affine - Unit tests for internal modules -use mylib::internal::parse_number - -#[test] -fn test_parse_number() { - assert_eq(parse_number("42"), Ok(42)) -} - -// tests/integration_test.affine - Integration tests for public API -use mylib - -#[test] -fn test_full_workflow() { - let config = mylib::Config::default() - let result = mylib::process(config, "input data") - assert_ok(result) -} -``` - ---- - -## Property-Based Testing - -Inspired by QuickCheck and Echidna's fuzzing approach. - -### Basic Properties - -```affine -use test::property::* - -#[property] -fn prop_addition_commutative(x: Int, y: Int) -> Bool { - x + y == y + x -} - -#[property] -fn prop_reverse_reverse(xs: Vec[Int]) -> Bool { - xs.reverse().reverse() == xs -} - -#[property] -fn prop_sort_preserves_length[T: Ord](xs: Vec[T]) -> Bool { - xs.len() == xs.sorted().len() -} -``` - -### Custom Generators - -```affine -use test::property::* - -// Custom generator for positive integers -fn gen_positive() -> Gen[Int] { - Gen::int_range(1, 1000) -} - -// Custom generator for non-empty strings -fn gen_non_empty_string() -> Gen[String] { - Gen::string() - .filter(|s| s.len() > 0) -} - -#[property] -fn prop_positive_sqrt(#[gen(gen_positive)] n: Int) -> Bool { - let root = (n as Float64).sqrt() - root >= 0.0 -} - -// Generator for custom types -struct Point { x: Int, y: Int } - -impl Arbitrary for Point { - fn arbitrary() -> Gen[Point] { - Gen::map2(Gen::int(), Gen::int(), |x, y| Point { x, y }) - } - - fn shrink(self) -> Vec[Point] { - // Shrink towards simpler cases - vec![ - Point { x: 0, y: 0 }, - Point { x: self.x, y: 0 }, - Point { x: 0, y: self.y }, - ] - } -} -``` - -### Configuration - -```affine -#[property] -#[tests(1000)] // Run 1000 test cases -#[max_shrinks(100)] // Try up to 100 shrinks on failure -fn prop_many_tests(x: Int) -> Bool { - x * 0 == 0 -} -``` - -### Invariants (Echidna-Style) - -Define invariants that should always hold: - -```affine -struct BankAccount { - balance: Int, - withdrawals: Vec[Int], -} - -impl BankAccount { - fn withdraw(self: &mut Self, amount: Int) -> Result[(), Error] { - if amount > self.balance { - return Err(InsufficientFunds) - } - self.balance -= amount - self.withdrawals.push(amount) - Ok(()) - } - - // Invariant: balance should never be negative - #[invariant] - fn balance_non_negative(self: &Self) -> Bool { - self.balance >= 0 - } - - // Invariant: sum of withdrawals + balance = initial balance - #[invariant] - fn accounting_correct(self: &Self) -> Bool { - let total_withdrawn: Int = self.withdrawals.iter().sum() - // This requires tracking initial balance... - true - } -} - -#[test] -fn fuzz_bank_account() { - test::fuzz::run_invariant_tests::( - 1000, // iterations - BankAccount { balance: 1000, withdrawals: vec![] } - ) -} -``` - ---- - -## Fuzzing - -### Fuzz Testing - -```affine -use test::fuzz::* - -// Fuzz test for parser -#[fuzz] -fn fuzz_parser(data: &[Byte]) { - // Should not panic on any input - let _ = Parser::parse_str(String::from_utf8_lossy(data)) -} - -// Fuzz test with structured input -#[fuzz] -fn fuzz_json_roundtrip(value: JsonValue) { - let serialized = value.to_string() - let parsed = JsonValue::parse(&serialized) - assert_eq(parsed, Ok(value)) -} -``` - -### Coverage-Guided Fuzzing - -```affine -#[fuzz] -#[coverage_guided] -fn fuzz_lexer(input: String) { - let tokens = lexer::lex(&input) - // Should handle all inputs gracefully - for token in tokens { - let _ = token.to_string() - } -} -``` - -### Running Fuzz Tests - -```bash -# Run all fuzz tests -aspm fuzz - -# Run specific fuzz test -aspm fuzz --target fuzz_parser - -# Run for specific duration -aspm fuzz --duration 3600 # 1 hour - -# Use specific corpus -aspm fuzz --corpus ./corpus/ -``` - ---- - -## Compiler Testing - -### Lexer Tests - -```ocaml -(* test/test_lexer.ml *) -open Alcotest -open Affinescript - -let test_keywords () = - let tokens = Lexer.lex "fn let mut own ref" in - check (list token_eq) "keywords" - [FN; LET; MUT; OWN; REF; EOF] - tokens - -let test_string_escapes () = - let tokens = Lexer.lex {|"hello\n\tworld"|} in - check (list token_eq) "escapes" - [STRING_LIT "hello\n\tworld"; EOF] - tokens - -let tests = [ - "keywords", `Quick, test_keywords; - "string escapes", `Quick, test_string_escapes; -] -``` - -### Parser Tests - -```ocaml -let test_parse_expr () = - let ast = Parser.parse_expr "1 + 2 * 3" in - check ast_eq "precedence" - (Binary (Lit 1, Add, Binary (Lit 2, Mul, Lit 3))) - ast - -let test_parse_function () = - let ast = Parser.parse "fn add(x: Int, y: Int) -> Int { x + y }" in - match ast with - | [FnDecl { name = "add"; params; ret_ty; body }] -> - check int "param count" 2 (List.length params) - | _ -> fail "expected function" -``` - -### Type Checker Tests - -```ocaml -(* Positive test - should type check *) -let test_infer_literal () = - let expr = parse_expr "42" in - let (ty, _) = Type_check.infer empty_ctx expr in - check type_eq "int literal" T_Int ty - -(* Negative test - should produce error *) -let test_type_mismatch () = - let program = parse "let x: Int = \"hello\"" in - match Type_check.check_program program with - | Error (Type_mismatch _) -> () - | _ -> fail "expected type mismatch error" -``` - -### Golden Tests - -Compare output against expected files: - -```ocaml -let test_golden name = - let input = read_file (sprintf "tests/golden/%s.affine" name) in - let expected = read_file (sprintf "tests/golden/%s.expected" name) in - let actual = compile_and_run input in - check string name expected actual - -let golden_tests = [ - "hello", `Quick, test_golden "hello"; - "factorial", `Quick, test_golden "factorial"; - "fibonacci", `Quick, test_golden "fibonacci"; -] -``` - ---- - -## Integration Tests - -### End-to-End Tests - -```affine -// tests/e2e_test.affine -use std::process::Command - -#[test] -fn test_compile_and_run() { - // Compile - let compile_result = Command::new("aspm") - .args(["build", "examples/hello.affine"]) - .output() - - assert!(compile_result.status.success()) - - // Run - let run_result = Command::new("./target/hello.wasm") - .output() - - assert_eq(run_result.stdout, "Hello, World!\n") -} -``` - -### Testing with External Dependencies - -```affine -#[test] -#[requires(database)] -fn test_database_integration() { - let db = test::fixtures::setup_test_db() - - db.execute("INSERT INTO users (name) VALUES ('Alice')") - - let users = db.query("SELECT * FROM users") - assert_len(users, 1) - - test::fixtures::teardown_test_db(db) -} -``` - ---- - -## Benchmarking - -### Basic Benchmarks - -```affine -use test::bench::* - -#[bench] -fn bench_vector_push(b: &mut Bencher) { - b.iter(|| { - let mut v = Vec::new() - for i in 0..1000 { - v.push(i) - } - v - }) -} - -#[bench] -fn bench_hashmap_insert(b: &mut Bencher) { - b.iter(|| { - let mut m = HashMap::new() - for i in 0..1000 { - m.insert(i, i * 2) - } - m - }) -} -``` - -### Parameterized Benchmarks - -```affine -#[bench] -#[params(size = [10, 100, 1000, 10000])] -fn bench_sort(b: &mut Bencher, size: Int) { - let data = gen_random_vec(size) - - b.iter(|| { - let mut copy = data.clone() - copy.sort() - copy - }) -} -``` - -### Running Benchmarks - -```bash -# Run all benchmarks -aspm bench - -# Run specific benchmark -aspm bench --filter "bench_sort" - -# Compare against baseline -aspm bench --baseline main - -# Output to file -aspm bench --output results.json -``` - ---- - -## Test Coverage - -### Generating Coverage Reports - -```bash -# Run tests with coverage -aspm test --coverage - -# Generate HTML report -aspm coverage --format html --output coverage/ - -# Generate lcov format -aspm coverage --format lcov --output coverage.lcov -``` - -### Coverage in CI - -```yaml -# .github/workflows/ci.yml -- name: Run tests with coverage - run: aspm test --coverage - -# Coverage stays in-repo (coverage.lcov); no third-party uploader is configured. -``` - ---- - -## See Also - -- [Property-Based Testing](property-based.md) - Advanced QuickCheck-style testing -- [Fuzzing](fuzzing.md) - Coverage-guided fuzzing -- [Benchmarking](benchmarks.md) - Performance testing -- [CI/CD](../tooling/ci.md) - Continuous integration diff --git a/affinescript/wiki/testing/property-based.md b/affinescript/wiki/testing/property-based.md deleted file mode 100644 index f8c1522..0000000 --- a/affinescript/wiki/testing/property-based.md +++ /dev/null @@ -1,395 +0,0 @@ - -# Property-Based Testing - -Property-based testing generates random test cases to verify that properties hold for all inputs. - -## Foundations - -Inspired by: -- **QuickCheck** (Haskell) - Original property-based testing -- **Hypothesis** (Python) - Smart shrinking -- **Echidna** (Solidity) - Invariant-based fuzzing - -## Core Concepts - -### Properties vs Examples - -```affine -// Example-based test (specific cases) -#[test] -fn test_reverse_examples() { - assert_eq([1, 2, 3].reverse(), [3, 2, 1]) - assert_eq([].reverse(), []) - assert_eq([1].reverse(), [1]) -} - -// Property-based test (universal property) -#[property] -fn prop_reverse_involutive(xs: Vec[Int]) -> Bool { - xs.reverse().reverse() == xs -} -``` - -### Properties - -Properties are functions returning Bool that should hold for all inputs: - -```affine -// Algebraic properties -#[property] -fn prop_add_commutative(x: Int, y: Int) -> Bool { - x + y == y + x -} - -#[property] -fn prop_add_associative(x: Int, y: Int, z: Int) -> Bool { - (x + y) + z == x + (y + z) -} - -#[property] -fn prop_mul_distributes_over_add(x: Int, y: Int, z: Int) -> Bool { - x * (y + z) == x * y + x * z -} -``` - -### Generators - -Generators produce random values: - -```affine -use test::property::Gen - -// Built-in generators -Gen::bool() // Random Bool -Gen::int() // Random Int -Gen::int_range(0, 100) // Int in range [0, 100) -Gen::float() // Random Float64 -Gen::char() // Random Char -Gen::string() // Random String -Gen::vec(Gen::int()) // Vec of random Ints -Gen::option(Gen::int()) // Option[Int] -``` - -### Custom Generators - -```affine -// Generator combinators -fn gen_point() -> Gen[Point] { - Gen::map2( - Gen::int_range(-100, 100), - Gen::int_range(-100, 100), - |x, y| Point { x, y } - ) -} - -fn gen_email() -> Gen[String] { - Gen::map3( - Gen::alpha_string(), - Gen::one_of(["gmail.com", "example.com", "test.org"]), - |name, domain| format("{}@{}", name, domain) - ) -} - -fn gen_non_empty_vec[T](g: Gen[T]) -> Gen[Vec[T]] { - Gen::vec(g).filter(|v| !v.is_empty()) -} -``` - -## The Arbitrary Trait - -Types implementing `Arbitrary` can be automatically generated: - -```affine -trait Arbitrary { - fn arbitrary() -> Gen[Self] - fn shrink(self) -> Vec[Self] // For shrinking on failure -} - -// Auto-derived for simple types -#[derive(Arbitrary)] -struct User { - name: String, - age: Int, - active: Bool, -} - -// Manual implementation -impl Arbitrary for Point { - fn arbitrary() -> Gen[Point] { - Gen::map2(Gen::int(), Gen::int(), |x, y| Point { x, y }) - } - - fn shrink(self) -> Vec[Point] { - // Shrink towards origin - let mut shrinks = vec![] - - if self.x != 0 { - shrinks.push(Point { x: self.x / 2, y: self.y }) - } - if self.y != 0 { - shrinks.push(Point { x: self.x, y: self.y / 2 }) - } - if self.x != 0 && self.y != 0 { - shrinks.push(Point { x: 0, y: 0 }) - } - - shrinks - } -} -``` - -## Shrinking - -When a property fails, shrinking finds the minimal failing case: - -```affine -#[property] -fn prop_all_positive(xs: Vec[Int]) -> Bool { - xs.iter().all(|x| x > 0) -} - -// Fails with: [3, -5, 7, 2, -1] -// Shrinks to: [-1] (minimal counterexample) -``` - -### Custom Shrinking - -```affine -fn shrink_tree(tree: Tree) -> Vec[Tree] { - match tree { - Leaf(n) -> { - // Shrink leaf values - n.shrink().map(Leaf) - }, - Branch(left, right) -> { - // Try subtrees, then shrink children - vec![left.clone(), right.clone()] ++ - left.shrink().map(|l| Branch(l, right.clone())) ++ - right.shrink().map(|r| Branch(left.clone(), r)) - } - } -} -``` - -## Advanced Properties - -### Conditional Properties - -```affine -// Only test when precondition holds -#[property] -fn prop_divide_exact(x: Int, y: Int) -> Property { - assume(y != 0 && x % y == 0) - (x / y) * y == x -} - -// Alternative syntax -#[property] -fn prop_sorted_head_min(xs: Vec[Int]) -> Bool { - if xs.is_empty() { - return true - } - xs.sorted().first() == xs.iter().min() -} -``` - -### Classified Properties - -Track distribution of test cases: - -```affine -#[property] -fn prop_classified(xs: Vec[Int]) -> Property { - classify(xs.len() == 0, "empty") |> - classify(xs.len() < 10, "small") |> - classify(xs.len() >= 10, "large") |> - property(xs.reverse().reverse() == xs) -} - -// Output: -// OK, passed 100 tests -// 15% empty -// 45% small -// 40% large -``` - -### Labeled Properties - -Better failure messages: - -```affine -#[property] -fn prop_labeled(x: Int, y: Int) -> Property { - label("sum", x + y) |> - label("product", x * y) |> - property(x + y >= x || y < 0) -} - -// On failure: -// Counterexample: x = 5, y = 3 -// sum = 8 -// product = 15 -``` - -### Property Composition - -```affine -fn prop_list_laws[T: Eq](xs: Vec[T]) -> Property { - prop_all([ - ("reverse involutive", xs.reverse().reverse() == xs), - ("length preserved", xs.len() == xs.reverse().len()), - ("append length", (xs.clone() ++ xs.clone()).len() == xs.len() * 2), - ]) -} -``` - -## State Machine Testing - -Test stateful systems by modeling operations: - -```affine -// Model a queue -enum QueueOp { - Push(Int), - Pop, - IsEmpty, -} - -impl Arbitrary for QueueOp { - fn arbitrary() -> Gen[QueueOp] { - Gen::one_of([ - Gen::int().map(Push), - Gen::const_(Pop), - Gen::const_(IsEmpty), - ]) - } -} - -#[property] -fn prop_queue_model(ops: Vec[QueueOp]) -> Bool { - // Reference model (simple list) - let mut model: Vec[Int] = vec![] - - // System under test - let mut queue = Queue::new() - - for op in ops { - match op { - Push(x) -> { - model.push(x) - queue.push(x) - }, - Pop -> { - let expected = if model.is_empty() { None } else { Some(model.remove(0)) } - let actual = queue.pop() - if expected != actual { - return false - } - }, - IsEmpty -> { - if model.is_empty() != queue.is_empty() { - return false - } - } - } - } - - true -} -``` - -## Compiler Testing Properties - -### Lexer Properties - -```affine -#[property] -fn prop_lexer_roundtrip(tokens: Vec[Token]) -> Bool { - // tokens -> source -> tokens - let source = tokens.iter().map(|t| t.to_source()).join(" ") - let re_lexed = lexer::lex(&source) - tokens == re_lexed -} - -#[property] -fn prop_lexer_no_panic(input: String) -> Bool { - // Lexer should never panic - let _ = std::panic::catch(|| lexer::lex(&input)) - true -} -``` - -### Parser Properties - -```affine -#[property] -fn prop_parse_pretty_roundtrip(ast: Expr) -> Bool { - let pretty = ast.pretty_print() - let re_parsed = parser::parse_expr(&pretty) - match re_parsed { - Ok(ast2) => ast.alpha_equiv(&ast2), - Err(_) => false - } -} -``` - -### Type System Properties - -```affine -// Type preservation: evaluation preserves types -#[property] -fn prop_type_preservation(expr: WellTypedExpr) -> Property { - let ty = type_of(expr) - assume(can_step(expr)) - let expr2 = step(expr) - property(type_of(expr2) == ty) -} - -// Progress: well-typed terms can step or are values -#[property] -fn prop_progress(expr: WellTypedExpr) -> Bool { - is_value(expr) || can_step(expr) -} -``` - -## Configuration - -```affine -use test::property::Config - -#[test] -fn test_with_config() { - let config = Config { - num_tests: 1000, - max_shrinks: 100, - seed: Some(12345), - verbose: true, - } - - check_with_config(config, |xs: Vec[Int]| { - xs.reverse().reverse() == xs - }) -} -``` - -## Debugging Failed Properties - -```affine -#[property] -#[verbose] // Print all test cases -#[replay(seed = 12345)] // Replay specific seed -fn prop_debug(x: Int) -> Bool { - x > 0 -} -``` - ---- - -## See Also - -- [Testing Guide](guide.md) - General testing -- [Fuzzing](fuzzing.md) - Coverage-guided fuzzing -- [QuickCheck Paper](https://www.cs.tufts.edu/~nr/cs257/archive/john-hughes/quick.pdf) diff --git a/affinescript/wiki/tooling/cli.md b/affinescript/wiki/tooling/cli.md deleted file mode 100644 index 6ee3d16..0000000 --- a/affinescript/wiki/tooling/cli.md +++ /dev/null @@ -1,306 +0,0 @@ - -# Command-Line Interface - -The AffineScript CLI provides commands for compiling, testing, and managing projects. - -## Installation - -```bash -# Install from source -git clone https://github.com/hyperpolymath/affinescript -cd affinescript -dune build && dune install - -# Or with opam (planned) -opam install affinescript -``` - -## Commands - -### affinescript - -The main compiler executable: - -```bash -# Lex a file (output tokens) -affinescript lex - -# Parse a file (output AST) -affinescript parse - -# Type-check a file -affinescript check - -# Compile to WASM -affinescript compile -o output.wasm - -# Run directly -affinescript run -``` - -### Common Options - -```bash ---help, -h Show help message ---version, -v Show version ---verbose Verbose output ---quiet, -q Suppress output ---color Color output -``` - -### lex - -Lexical analysis - tokenize source file: - -```bash -affinescript lex src/main.affine - -# Output format -affinescript lex --format json src/main.affine -affinescript lex --format pretty src/main.affine -``` - -Example output: -``` -FN @ 1:1-1:3 -IDENT("main") @ 1:4-1:8 -LPAREN @ 1:8-1:9 -RPAREN @ 1:9-1:10 -ARROW @ 1:11-1:13 -IDENT("Unit") @ 1:14-1:18 -LBRACE @ 1:19-1:20 -... -``` - -### parse - -Parse source file to AST: - -```bash -affinescript parse src/main.affine - -# Output options -affinescript parse --format sexp src/main.affine -affinescript parse --format json src/main.affine -affinescript parse --format pretty src/main.affine -``` - -### check - -Type-check without compiling: - -```bash -affinescript check src/main.affine - -# Check entire project -affinescript check . - -# Show inferred types -affinescript check --show-types src/main.affine -``` - -### compile - -Compile to WebAssembly: - -```bash -# Basic compilation -affinescript compile src/main.affine -o output.wasm - -# With optimizations -affinescript compile --release src/main.affine -o output.wasm - -# Emit text format -affinescript compile --emit wat src/main.affine -o output.wat - -# Debug info -affinescript compile --debug src/main.affine -o output.wasm - -# Target options -affinescript compile --target wasm32 src/main.affine -``` - -### run - -Compile and run: - -```bash -# Run with default WASM runtime -affinescript run src/main.affine - -# Pass arguments -affinescript run src/main.affine -- arg1 arg2 - -# With specific runtime -affinescript run --runtime wasmtime src/main.affine -``` - -## aspm (Package Manager) - -AffineScript Package Manager (planned): - -### Project Management - -```bash -# Create new project -aspm init my-project -aspm init --lib my-library - -# Build project -aspm build -aspm build --release - -# Run main -aspm run - -# Clean build artifacts -aspm clean -``` - -### Dependencies - -```bash -# Add dependency -aspm add json -aspm add http@2.0 - -# Remove dependency -aspm remove json - -# Update dependencies -aspm update -aspm update json -``` - -### Testing - -```bash -# Run all tests -aspm test - -# Run specific tests -aspm test --filter "test_parser" - -# With coverage -aspm test --coverage - -# Verbose -aspm test --verbose -``` - -### Documentation - -```bash -# Generate docs -aspm doc - -# Open in browser -aspm doc --open - -# Include private items -aspm doc --document-private-items -``` - -### Publishing - -```bash -# Login to registry -aspm login - -# Publish package -aspm publish - -# Check before publishing -aspm publish --dry-run -``` - -## aslsp (Language Server) - -Language Server Protocol implementation (planned): - -```bash -# Start LSP server -aslsp - -# With logging -aslsp --log-level debug - -# Specify port (for TCP mode) -aslsp --port 5007 -``` - -## asfmt (Formatter) - -Code formatter (planned): - -```bash -# Format file -asfmt src/main.affine - -# Format in place -asfmt --write src/main.affine - -# Format all files -asfmt --write . - -# Check formatting -asfmt --check src/main.affine - -# Diff mode -asfmt --diff src/main.affine -``` - -Configuration (`.asfmt.toml`): -```toml -max_width = 100 -indent_size = 2 -use_tabs = false -trailing_comma = true -``` - -## aslint (Linter) - -Static analysis (planned): - -```bash -# Run all lints -aslint src/ - -# Specific rules -aslint --warn unused_variables src/ -aslint --deny unsafe_code src/ - -# Fix automatically -aslint --fix src/ -``` - -## Environment Variables - -| Variable | Description | -|----------|-------------| -| `AFFINE_HOME` | Installation directory | -| `AFFINE_CACHE` | Cache directory | -| `AFFINE_LOG` | Log level (error, warn, info, debug, trace) | -| `NO_COLOR` | Disable colored output | - -## Exit Codes - -| Code | Meaning | -|------|---------| -| 0 | Success | -| 1 | General error | -| 2 | Command-line usage error | -| 101 | Compilation failed | -| 102 | Type error | -| 103 | Borrow check error | - ---- - -## See Also - -- [Installation](../tutorials/installation.md) - Setup guide -- [Package Manager](package-manager.md) - aspm details -- [LSP](lsp.md) - Editor integration diff --git a/affinescript/wiki/tooling/repl.md b/affinescript/wiki/tooling/repl.md deleted file mode 100644 index 659a577..0000000 --- a/affinescript/wiki/tooling/repl.md +++ /dev/null @@ -1,293 +0,0 @@ - -# REPL Guide - -The AffineScript REPL (Read-Eval-Print Loop) provides an interactive environment for exploring the language. - -## Starting the REPL - -```bash -# Start interactive session -affinescript repl - -# With a prelude file -affinescript repl --prelude mylib.affine - -# With specific options -affinescript repl --no-color --history-file ~/.as_history -``` - -## Basic Usage - -``` -Welcome to AffineScript v0.1.0 -Type :help for available commands. - -> 1 + 2 -3 : Int - -> "hello" ++ " world" -"hello world" : String - -> let x = 42 -x : Int = 42 - -> x * 2 -84 : Int -``` - -## REPL Commands - -Commands start with `:`: - -| Command | Description | -|---------|-------------| -| `:help` | Show help | -| `:quit` or `:q` | Exit REPL | -| `:type ` | Show type of expression | -| `:kind ` | Show kind of type | -| `:effect ` | Show effects of expression | -| `:load ` | Load file | -| `:reload` | Reload last loaded file | -| `:clear` | Clear screen | -| `:reset` | Reset environment | -| `:env` | Show current bindings | -| `:history` | Show command history | - -### Type Inspection - -``` -> :type [1, 2, 3] -[Int; 3] - -> :type |x| x + 1 -(Int) -> Int - -> :kind Option -* -> * - -> :kind Map -* -> * -> * -``` - -### Effect Inspection - -``` -> :effect println("hello") -IO - -> :effect |x| x + 1 -pure - -> fn get_and_print() -{State[Int], IO}-> Unit { - let x = get() - println(show(x)) - } -> :effect get_and_print -{State[Int], IO} -``` - -### Loading Files - -``` -> :load examples/math.affine -Loaded examples/math.affine - -> add(1, 2) -3 : Int - -> :reload -Reloaded examples/math.affine -``` - -## Multi-line Input - -Use `:{` and `:}` for multi-line: - -``` -> :{ -| fn factorial(n: Int) -> Int { -| if n <= 1 { 1 } -| else { n * factorial(n - 1) } -| } -| :} -factorial : (Int) -> Int - -> factorial(5) -120 : Int -``` - -Or simply continue with open braces: - -``` -> fn double(x: Int) -> Int { -| x * 2 -| } -double : (Int) -> Int -``` - -## Defining Types - -``` -> struct Point { x: Float64, y: Float64 } -Point defined - -> let p = Point { x: 1.0, y: 2.0 } -p : Point = Point { x: 1.0, y: 2.0 } - -> p.x -1.0 : Float64 - -> enum Color { Red, Green, Blue } -Color defined - -> Color::Red -Red : Color -``` - -## Traits in REPL - -``` -> trait Greet { -| fn greet(self: &Self) -> String -| } -Greet defined - -> impl Greet for String { -| fn greet(self: &Self) -> String { -| "Hello, " ++ self -| } -| } -impl Greet for String - -> "World".greet() -"Hello, World" : String -``` - -## Effects in REPL - -``` -> effect Counter { -| fn increment() -> Int -| } -Counter defined - -> fn count_twice() -{Counter}-> Int { -| increment() + increment() -| } -count_twice : () -{Counter}-> Int - -> handle count_twice() { -| let mut n = 0 -| increment() -> { n += 1; resume(n) } -| } -3 : Int -``` - -## Ownership in REPL - -``` -> let s = String::from("hello") -s : String = "hello" - -> let t = s // Move -t : String = "hello" - -> s // Error! -error[E0500]: use of moved value: `s` - --> repl:1:1 - | -1 | s - | ^ value used here after move - -> let r = &t // Borrow -r : &String - -> t // Still valid -"hello" : String -``` - -## Configuration - -Create `~/.affine/repl.toml`: - -```toml -# History settings -history_file = "~/.affine/history" -history_size = 1000 - -# Display settings -color = true -prompt = "> " -continuation_prompt = "| " - -# Type display -show_types = true -show_effects = true - -# Editor -editor = "vim" -``` - -## Key Bindings - -| Key | Action | -|-----|--------| -| `Ctrl+C` | Cancel current input | -| `Ctrl+D` | Exit (on empty line) | -| `Ctrl+L` | Clear screen | -| `Up/Down` | Navigate history | -| `Ctrl+R` | Reverse search history | -| `Tab` | Autocomplete | - -## Autocomplete - -``` -> Str -String StringBuffer Stringify - -> String:: -String::from String::new String::with_capacity - -> let s = String::from("hello") -> s. -s.len s.push s.pop s.chars s.bytes ... -``` - -## Debugging - -``` -> :debug on -Debug mode enabled - -> let x = 1 + 2 -[debug] Parsing: let x = 1 + 2 -[debug] Type inference: 1 : Int, 2 : Int -[debug] Result type: Int -x : Int = 3 - -> :debug off -Debug mode disabled -``` - -## Benchmarking - -``` -> :time factorial(20) -2432902008176640000 : Int -Time: 0.023ms - -> :bench factorial(20) -Running 1000 iterations... -Mean: 22.5μs, Std: 1.2μs -Min: 20.1μs, Max: 35.2μs -``` - ---- - -## See Also - -- [CLI Reference](cli.md) - Command-line options -- [Quick Start](../tutorials/quickstart.md) - Getting started diff --git a/affinescript/wiki/tutorials/introduction.md b/affinescript/wiki/tutorials/introduction.md deleted file mode 100644 index 212831b..0000000 --- a/affinescript/wiki/tutorials/introduction.md +++ /dev/null @@ -1,177 +0,0 @@ - -# Introduction to AffineScript - -AffineScript is a modern systems programming language that combines safety, expressiveness, and performance. - -## Why AffineScript? - -### The Safety-Performance Tradeoff - -Traditional languages force a choice: -- **Safe languages** (Java, Python): Garbage collection, runtime checks -- **Fast languages** (C, C++): Manual memory, undefined behavior - -AffineScript achieves both: -- **Memory safe** without garbage collection -- **Zero-cost abstractions** with predictable performance -- **Compile-time verification** catches bugs early - -### Key Features - -1. **Affine Types**: Memory safety through ownership -2. **Dependent Types**: Verify properties at compile time -3. **Row Polymorphism**: Flexible, type-safe records -4. **Algebraic Effects**: Controlled side effects -5. **WebAssembly Target**: Run anywhere - -## Hello World - -```affine -fn main() -{IO}-> Unit { - println("Hello, World!") -} -``` - -Let's break this down: -- `fn main()` - Function named `main` -- `-{IO}->` - Has IO effect (can do I/O) -- `Unit` - Returns nothing -- `println(...)` - Print to console - -## Core Concepts Preview - -### Ownership - -Values have exactly one owner. When the owner goes out of scope, the value is cleaned up: - -```affine -fn example() { - let s = String::from("hello") // s owns the string - println(s) -} // s goes out of scope, string freed -``` - -No garbage collector needed! - -### Borrowing - -Instead of copying, you can borrow: - -```affine -fn print_length(s: &String) { - println("Length: " ++ show(s.len())) -} - -fn main() -{IO}-> Unit { - let s = String::from("hello") - print_length(&s) // Borrow s - println(s) // Still valid! -} -``` - -### Type Safety - -The type system catches errors at compile time: - -```affine -// Compile-time verified bounds checking -fn safe_index[n: Nat, T]( - vec: Vec[n, T], - i: Nat where (i < n) // Index must be in bounds -) -> T { - vec[i] // Guaranteed safe! -} -``` - -### Effects - -Side effects are tracked in types: - -```affine -// Pure function - no effects -fn add(x: Int, y: Int) -> Int { - x + y -} - -// Effectful function - must declare IO -fn greet(name: String) -{IO}-> Unit { - println("Hello, " ++ name) -} -``` - -## What Can You Build? - -### Systems Software -- Operating system components -- Device drivers -- Embedded systems - -### Web Applications -- WebAssembly backends -- High-performance web services -- Browser-based tools - -### Financial Systems -- Trading systems -- Smart contracts -- Verified algorithms - -### Scientific Computing -- Numerical libraries -- Data processing -- Simulation software - -## Getting Started - -Ready to learn more? - -1. [Installation](installation.md) - Set up your environment -2. [Quick Start](quickstart.md) - Your first project -3. [Tour of Features](tour.md) - Language overview - -## Example: Safe Vector Operations - -```affine -// Vector with compile-time length -struct Vec[n: Nat, T] { - data: [T; n] -} - -// Safe head - requires non-empty -fn head[n: Nat, T](vec: Vec[n + 1, T]) -> T { - vec.data[0] // Always safe - at least 1 element -} - -// Append with length tracking -fn append[n: Nat, m: Nat, T]( - a: Vec[n, T], - b: Vec[m, T] -) -> Vec[n + m, T] { - // Result has exactly n + m elements - Vec { data: a.data ++ b.data } -} - -fn main() -{IO}-> Unit { - let v1 = Vec { data: [1, 2, 3] } // Vec[3, Int] - let v2 = Vec { data: [4, 5] } // Vec[2, Int] - - let first = head(v1) // 1 (safe!) - let combined = append(v1, v2) // Vec[5, Int] - - println(show(combined.data)) // [1, 2, 3, 4, 5] -} -``` - -## Philosophy - -AffineScript is designed around: - -1. **Safety First**: Memory safety, type safety, effect safety -2. **Predictability**: No hidden costs, clear semantics -3. **Expressiveness**: Rich types without complexity -4. **Practicality**: Real-world systems, not just theory - -Welcome to AffineScript! diff --git a/affinescript/wiki/tutorials/quickstart.md b/affinescript/wiki/tutorials/quickstart.md deleted file mode 100644 index d91dd53..0000000 --- a/affinescript/wiki/tutorials/quickstart.md +++ /dev/null @@ -1,403 +0,0 @@ - -# Quick Start Guide - -Get up and running with AffineScript in 10 minutes. - -## Prerequisites - -- OCaml 5.1 or later -- opam package manager -- dune build system - -## Installation - -```bash -# Clone the repository -git clone https://github.com/hyperpolymath/affinescript -cd affinescript - -# Set up OCaml environment -opam switch create . 5.1.0 -eval $(opam env) - -# Install dependencies -opam install . --deps-only --with-test - -# Build -dune build - -# Verify installation -dune exec affinescript -- --version -``` - -## Your First Program - -Create a file `hello.affine`: - -```affine -fn main() -{IO}-> Unit { - println("Hello, AffineScript!") -} -``` - -Run it: - -```bash -dune exec affinescript -- run hello.affine -``` - -Output: -``` -Hello, AffineScript! -``` - -## Variables and Types - -```affine -fn main() -{IO}-> Unit { - // Immutable binding - let x = 42 - let name = "Alice" - let pi = 3.14159 - - // Type annotations (optional) - let y: Int = 100 - let active: Bool = true - - // Mutable binding - let mut counter = 0 - counter += 1 - - println("x = " ++ show(x)) - println("counter = " ++ show(counter)) -} -``` - -## Functions - -```affine -// Basic function -fn add(x: Int, y: Int) -> Int { - x + y -} - -// Function with effects -fn greet(name: String) -{IO}-> Unit { - println("Hello, " ++ name ++ "!") -} - -// Generic function -fn identity[T](x: T) -> T { - x -} - -fn main() -{IO}-> Unit { - let sum = add(1, 2) - greet("World") - let x = identity(42) - let s = identity("hello") - - println("sum = " ++ show(sum)) -} -``` - -## Control Flow - -```affine -fn main() -{IO}-> Unit { - let x = 10 - - // If expression - let description = if x > 0 { - "positive" - } else if x < 0 { - "negative" - } else { - "zero" - } - - // Match expression - let result = match x { - 0 -> "zero", - 1..10 -> "small", - _ -> "large" - } - - // Loops - let mut sum = 0 - for i in 0..5 { - sum += i - } - - while sum < 100 { - sum *= 2 - } - - println(description) - println(result) - println("sum = " ++ show(sum)) -} -``` - -## Data Types - -### Structs - -```affine -struct Point { - x: Float64, - y: Float64 -} - -impl Point { - fn new(x: Float64, y: Float64) -> Point { - Point { x, y } - } - - fn distance(self: &Self, other: &Point) -> Float64 { - let dx = other.x - self.x - let dy = other.y - self.y - (dx * dx + dy * dy).sqrt() - } -} - -fn main() -{IO}-> Unit { - let p1 = Point::new(0.0, 0.0) - let p2 = Point { x: 3.0, y: 4.0 } - - let d = p1.distance(&p2) - println("Distance: " ++ show(d)) // 5.0 -} -``` - -### Enums - -```affine -enum Color { - Red, - Green, - Blue, - Rgb(Int, Int, Int) -} - -fn color_name(c: Color) -> String { - match c { - Red -> "red", - Green -> "green", - Blue -> "blue", - Rgb(r, g, b) -> format("rgb({}, {}, {})", r, g, b) - } -} - -fn main() -{IO}-> Unit { - let c1 = Color::Red - let c2 = Color::Rgb(255, 128, 0) - - println(color_name(c1)) - println(color_name(c2)) -} -``` - -## Option and Result - -```affine -fn divide(x: Int, y: Int) -> Option[Int] { - if y == 0 { - None - } else { - Some(x / y) - } -} - -fn parse_int(s: String) -> Result[Int, String] { - // Simplified example - match s.parse::() { - Some(n) -> Ok(n), - None -> Err("Invalid number: " ++ s) - } -} - -fn main() -{IO}-> Unit { - // Option handling - match divide(10, 2) { - Some(result) -> println("Result: " ++ show(result)), - None -> println("Cannot divide by zero") - } - - // Or with combinators - let doubled = divide(10, 2) - .map(|n| n * 2) - .unwrap_or(0) - - // Result with ? - fn process() -> Result[Int, String] { - let x = parse_int("42")? - let y = parse_int("10")? - Ok(x + y) - } -} -``` - -## Ownership Basics - -```affine -fn main() -{IO}-> Unit { - // Ownership transfer (move) - let s1 = String::from("hello") - let s2 = s1 // s1 is moved to s2 - // println(s1) // Error! s1 is no longer valid - - // Borrowing - let s3 = String::from("world") - print_string(&s3) // Borrow s3 - println(s3) // s3 still valid - - // Mutable borrowing - let mut s4 = String::from("hello") - append_world(&mut s4) - println(s4) // "hello world" -} - -fn print_string(s: &String) -{IO}-> Unit { - println(s) -} - -fn append_world(s: &mut String) { - s.push_str(" world") -} -``` - -## Collections - -```affine -fn main() -{IO}-> Unit { - // Vector - let mut numbers: Vec[Int] = vec![1, 2, 3] - numbers.push(4) - numbers.push(5) - - // Iteration - for n in numbers.iter() { - println(show(n)) - } - - // Functional operations - let doubled = numbers.iter() - .map(|n| n * 2) - .collect::() - - let sum = numbers.iter().fold(0, |acc, n| acc + n) - - // HashMap - let mut scores: HashMap[String, Int] = HashMap::new() - scores.insert("Alice", 100) - scores.insert("Bob", 85) - - match scores.get("Alice") { - Some(score) -> println("Alice: " ++ show(score)), - None -> println("Alice not found") - } -} -``` - -## Error Handling - -```affine -fn read_config(path: String) -> Result[Config, Error] { - let content = fs::read_to_string(path)? // Propagate errors - let config = parse_config(&content)? - Ok(config) -} - -fn main() -{IO}-> Unit { - match read_config("config.toml") { - Ok(config) -> { - println("Loaded config") - run_with_config(config) - }, - Err(e) -> { - eprintln("Error: " ++ e.message) - exit(1) - } - } -} -``` - -## Next Steps - -You've learned the basics! Continue with: - -1. [Ownership Deep Dive](ownership-tutorial.md) - Master ownership -2. [Effects Tutorial](effects-tutorial.md) - Understand effects -3. [Dependent Types](dependent-types-tutorial.md) - Type-level programming - -## Example Project - -Create a simple TODO app: - -```affine -struct Todo { - id: Int, - title: String, - done: Bool -} - -struct TodoList { - todos: Vec[Todo], - next_id: Int -} - -impl TodoList { - fn new() -> TodoList { - TodoList { todos: vec![], next_id: 1 } - } - - fn add(self: &mut Self, title: String) -> Int { - let id = self.next_id - self.next_id += 1 - self.todos.push(Todo { id, title, done: false }) - id - } - - fn complete(self: &mut Self, id: Int) -> Bool { - for todo in self.todos.iter_mut() { - if todo.id == id { - todo.done = true - return true - } - } - false - } - - fn list(self: &Self) -{IO}-> Unit { - for todo in self.todos.iter() { - let status = if todo.done { "[x]" } else { "[ ]" } - println(format("{} {} {}", status, todo.id, todo.title)) - } - } -} - -fn main() -{IO}-> Unit { - let mut todos = TodoList::new() - - todos.add("Learn AffineScript") - todos.add("Build something cool") - todos.add("Share with others") - - todos.complete(1) - - todos.list() -} -``` - -Output: -``` -[x] 1 Learn AffineScript -[ ] 2 Build something cool -[ ] 3 Share with others -``` - -Happy coding! From 96adfd99b46edba5f716c709b193f898a077574c Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:53:46 +0100 Subject: [PATCH 2/9] feat(attempt): consume echidna.prove.result/1 with legacy fallback burrower attempt now asks echidna for the shared prove-result contract (`echidna prove ... --output json`) when the installed binary advertises the flag, and validates the single stdout object as I-JSON that is byte-identical to its RFC 8785 canonical form (hyperpolymath/ijson-jcs, git dependency pinned by rev). A verified result carries a ProofReceipt (prover, axioms, confidence, echidna version) into the ledger's extra.receipt; a success read from legacy text markers carries none and is labelled a warrant, not a receipt. The text-marker path is kept only for an echidna that lacks the flag (detected from `prove --help`, or clap rejecting `--output` with exit 2). A malformed or non-canonical object is a contract violation and is never re-read as text. Spawns retry briefly on ETXTBSY, and a binary that cannot be run is not cached as legacy. Also: --echidna defaults to `echidna` on PATH instead of a developer's absolute path; the oracle doc names tropical-types (formerly tropical-resource-typing); ledger::new_id is documented as the single id minting site. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 52 +- crates/burrower-cli/src/main.rs | 10 +- crates/burrower-core/Cargo.toml | 4 + crates/burrower-core/src/attempt.rs | 549 ++++++++++++++++--- crates/burrower-core/src/echidna_contract.rs | 370 +++++++++++++ crates/burrower-core/src/ledger.rs | 15 +- crates/burrower-core/src/lib.rs | 5 +- crates/burrower-core/src/oracle.rs | 7 +- crates/burrower-core/tests/live_isabelle.rs | 5 +- docs/ECHIDNA-INTEGRATION.adoc | 126 ++++- 10 files changed, 1044 insertions(+), 99 deletions(-) create mode 100644 crates/burrower-core/src/echidna_contract.rs diff --git a/Cargo.lock b/Cargo.lock index 5d31fbb..a00ca38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,6 +89,7 @@ name = "burrower-core" version = "0.0.1" dependencies = [ "anyhow", + "ijson-jcs", "regex", "serde", "serde_json", @@ -141,7 +142,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -224,6 +225,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ijson-jcs" +version = "0.1.0" +source = "git+https://github.com/hyperpolymath/ijson-jcs?rev=0fdeea716fd558e9807362acf6f8202d56d8d88f#0fdeea716fd558e9807362acf6f8202d56d8d88f" +dependencies = [ + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -297,7 +308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -408,7 +419,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -441,6 +452,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -454,6 +476,26 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "unicode-ident" version = "1.0.24" @@ -594,7 +636,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -610,7 +652,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] diff --git a/crates/burrower-cli/src/main.rs b/crates/burrower-cli/src/main.rs index 31ae86e..e17f8e1 100644 --- a/crates/burrower-cli/src/main.rs +++ b/crates/burrower-cli/src/main.rs @@ -2,7 +2,8 @@ // Copyright (c) Jonathan D.A. Jewell //! Proof Burrower CLI. //! -//! Two subcommands: +//! Subcommands: `index`, `find`, `swarm`, `attempt`, `ledger`, `serve`. +//! The first two: //! //! - `burrower index --output ` — walk a //! library directory and persist a JSON index. @@ -86,11 +87,8 @@ enum Cmd { Attempt { /// The proof goal as a string. Quote it. goal: String, - /// Path to the echidna binary. - #[arg( - long, - default_value = "/var/mnt/eclipse/repos/echidna/target/debug/echidna" - )] + /// The echidna binary: a path, or a bare name looked up on PATH. + #[arg(long, default_value = "echidna")] echidna: PathBuf, /// Per-attempt timeout (seconds), passed to `echidna prove -t`. #[arg(long, default_value_t = 60)] diff --git a/crates/burrower-core/Cargo.toml b/crates/burrower-core/Cargo.toml index 2a07d61..557c0a4 100644 --- a/crates/burrower-core/Cargo.toml +++ b/crates/burrower-core/Cargo.toml @@ -15,3 +15,7 @@ serde_json = { workspace = true } walkdir = { workspace = true } regex = { workspace = true } tempfile = "3" +# RFC 8785 (JCS) + RFC 7493 (I-JSON) for the echidna.prove.result/1 +# contract. Not on crates.io; pinned by rev so standalone builds are +# reproducible (a path dependency would break them). +ijson-jcs = { git = "https://github.com/hyperpolymath/ijson-jcs", rev = "0fdeea716fd558e9807362acf6f8202d56d8d88f" } diff --git a/crates/burrower-core/src/attempt.rs b/crates/burrower-core/src/attempt.rs index 4abb7d9..0f6aefd 100644 --- a/crates/burrower-core/src/attempt.rs +++ b/crates/burrower-core/src/attempt.rs @@ -14,7 +14,10 @@ //! 1. [`generate_probe`] wraps a goal + a candidate tactic into a //! self-contained Isabelle probe file. //! 2. [`run_probe`] invokes `echidna prove --prover Isabelle` as a -//! subprocess and parses the output. +//! subprocess. When the installed echidna offers `--output json` it +//! asks for the `echidna.prove.result/1` object and parses it (see +//! [`crate::echidna_contract`]); otherwise it falls back to the legacy +//! text markers. //! 3. The result is recorded as a [`LedgerRecord`] with structured //! `Approach`, `Result`, and (if a clear lesson is extractable) //! `Learning` blocks. @@ -27,14 +30,19 @@ //! useful data (the ledger records "failed: needs import X"). //! - No timeout enforcement beyond what `echidna prove -t` honours. +use crate::echidna_contract::{ + detect_output_mode, output_tolerating_busy, parse_prove_result, rejected_output_flag, + remember_output_mode, OutputMode, ProveResult, ProveStatus, SCHEMA, +}; use crate::goal::Goal; use crate::ledger::{ goal_hash, new_id, now_iso, Approach, Learning, Ledger, LedgerRecord, RecordResult, }; use anyhow::Result; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::process::Command; +use std::ffi::OsStr; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; use std::time::Instant; /// One tactic that a specialist knows how to try. @@ -59,7 +67,7 @@ pub struct Playbook { /// Configuration for prover-backend invocation. #[derive(Debug, Clone)] pub struct ProverConfig { - /// Absolute path to the `echidna` binary. + /// The `echidna` binary: a path, or a bare name looked up on `PATH`. pub echidna_path: PathBuf, /// Per-attempt timeout (passed to `echidna prove -t`). pub timeout_secs: u32, @@ -92,11 +100,46 @@ impl Default for ProverConfig { } } +/// What a success rests on, transported from `echidna.prove.result/1`. +/// +/// Present only when echidna emitted the contract object; a success read +/// from legacy text markers carries no receipt. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProofReceipt { + /// Contract schema the receipt was parsed from. + pub schema: String, + /// Backend that checked the proof. + pub prover: String, + /// Axioms the checked proof depends on. + pub axioms: Vec, + /// echidna's confidence, if it reported one. + pub confidence: Option, + /// The echidna version that issued the receipt. + pub echidna_version: String, +} + +impl ProofReceipt { + /// Build a receipt from a parsed prove result. + pub fn from_result(r: &ProveResult) -> Self { + Self { + schema: r.schema.clone(), + prover: r.prover.clone(), + axioms: r.trust.axioms.clone(), + confidence: r.trust.confidence, + echidna_version: r.echidna_version.clone(), + } + } +} + /// Outcome of one proof attempt. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum AttemptResult { Succeeded { duration_ms: u64, + /// `Some` when echidna issued an `echidna.prove.result/1` receipt; + /// `None` when success was inferred from legacy text markers. + #[serde(default, skip_serializing_if = "Option::is_none")] + receipt: Option, }, Failed { error: String, @@ -110,9 +153,11 @@ pub enum AttemptResult { } impl AttemptResult { + /// True for [`AttemptResult::Succeeded`]. pub fn is_success(&self) -> bool { matches!(self, AttemptResult::Succeeded { .. }) } + /// Ledger status word for this outcome. pub fn status_string(&self) -> &'static str { match self { AttemptResult::Succeeded { .. } => "succeeded", @@ -273,19 +318,66 @@ pub fn run_probe(probe_text: &str, config: &ProverConfig, probe_filename: &str) reason: format!("probe write failed: {e}"), }; } - if !config.echidna_path.exists() { + let Some(echidna) = + resolve_executable(&config.echidna_path, std::env::var_os("PATH").as_deref()) + else { return AttemptResult::Skipped { reason: format!( "echidna binary not found at {}", config.echidna_path.display() ), }; + }; + + let mut mode = detect_output_mode(&echidna); + loop { + let start = Instant::now(); + let output = + output_tolerating_busy(&mut prove_command(&echidna, &probe_path, config, mode)); + let elapsed_ms = start.elapsed().as_millis() as u64; + let o = match output { + Ok(o) => o, + Err(e) => { + return AttemptResult::Skipped { + reason: format!("subprocess failed: {e}"), + } + } + }; + match mode { + OutputMode::Contract + if o.status.code() == Some(2) + && rejected_output_flag(&String::from_utf8_lossy(&o.stderr)) => + { + // `prove --help` advertised the flag but the binary refused + // it: treat this binary as pre-contract from now on. + remember_output_mode(&echidna, OutputMode::Legacy); + mode = OutputMode::Legacy; + } + OutputMode::Contract => return interpret_contract(&o, elapsed_ms), + OutputMode::Legacy => return interpret_legacy(&o, elapsed_ms), + } + } +} + +/// Resolve the configured echidna to an existing file. +/// +/// A value with more than one path component is used as given; a bare +/// name (the default `echidna`) is searched for in `path_var`, the +/// colon-separated `PATH`. Returns `None` when nothing exists. +pub fn resolve_executable(configured: &Path, path_var: Option<&OsStr>) -> Option { + if configured.components().count() != 1 || configured.is_absolute() { + return configured.exists().then(|| configured.to_path_buf()); } + std::env::split_paths(path_var?) + .map(|dir| dir.join(configured)) + .find(|candidate| candidate.is_file()) +} - let start = Instant::now(); - let mut cmd = Command::new(&config.echidna_path); +/// Build the `echidna prove` invocation for one probe file. +fn prove_command(echidna: &Path, probe: &Path, config: &ProverConfig, mode: OutputMode) -> Command { + let mut cmd = Command::new(echidna); cmd.arg("prove") - .arg(&probe_path) + .arg(probe) .arg("--prover") .arg("Isabelle") .arg("-t") @@ -300,76 +392,126 @@ pub fn run_probe(probe_text: &str, config: &ProverConfig, probe_filename: &str) if config.sandbox != "none" && !config.sandbox.is_empty() { cmd.arg("--sandbox").arg(&config.sandbox); } - let output = cmd.output(); - let elapsed = start.elapsed(); - let elapsed_ms = elapsed.as_millis() as u64; - - match output { - Ok(o) => { - let stdout = String::from_utf8_lossy(&o.stdout); - let stderr = String::from_utf8_lossy(&o.stderr); - - // Discovered 2026-04-26 during the swarm-dogfood session: - // echidna's "Proof verified successfully" / "Proof verification - // failed" lines come from `OutputFormatter`, which writes to - // STDERR, not stdout. The previous stdout-only check made every - // attempt look "inconclusive" and demoted real failures to - // generic-failure anti-patterns. We now scan BOTH streams and - // also fall back on the exit code so a non-zero exit with no - // standard marker still becomes Failed (not Inconclusive). - let combined_lines: Vec<&str> = stdout.lines().chain(stderr.lines()).collect(); - let says_success = combined_lines.iter().any(|l| { - l.contains("Proof verified successfully") || l.contains("✓ Proof verified") - }); - let says_failure = combined_lines.iter().any(|l| { - l.contains("Proof verification failed") - || l.contains("✗ Proof verification failed") - || l.contains("FAILED") - }); - let exit_failed = !o.status.success(); - - if says_success && !says_failure && !exit_failed { - AttemptResult::Succeeded { - duration_ms: elapsed_ms, - } - } else if says_failure || exit_failed { - let err_excerpt: String = combined_lines - .iter() - .filter(|l| { - l.contains("***") - || l.contains("Failed") - || l.contains("error") - || l.contains("Unable") - }) - .take(3) - .copied() - .collect::>() - .join(" | "); - AttemptResult::Failed { - error: if err_excerpt.is_empty() { - format!( - "exit {} — no standard diagnostic captured", - o.status.code().unwrap_or(-1) - ) - } else { - err_excerpt - }, - duration_ms: elapsed_ms, - } - } else { - // Truly inconclusive — exit 0, no markers either way. - AttemptResult::Failed { - error: format!( - "inconclusive output (no success/failure marker, exit 0): {}", - stdout.chars().take(200).collect::() - ), - duration_ms: elapsed_ms, - } + if mode == OutputMode::Contract { + cmd.arg("--output").arg("json"); + } + cmd +} + +/// Map an `echidna.prove.result/1` run onto an [`AttemptResult`]. +/// +/// The contract object is authoritative. A malformed object is a +/// contract violation and fails the attempt; it is never re-read as text. +/// A `verified` status with a non-zero exit is also treated as a +/// violation, matching the legacy rule that failure evidence wins. +fn interpret_contract(o: &Output, elapsed_ms: u64) -> AttemptResult { + let stdout = String::from_utf8_lossy(&o.stdout); + let result = match parse_prove_result(&stdout) { + Ok(r) => r, + Err(e) => { + return AttemptResult::Failed { + error: format!( + "{SCHEMA} contract violation (exit {}): {e}", + o.status.code().unwrap_or(-1) + ), + duration_ms: elapsed_ms, } } - Err(e) => AttemptResult::Skipped { - reason: format!("subprocess failed: {e}"), + }; + let message = if result.message.is_empty() { + format!("echidna reported {:?} with no message", result.status) + } else { + result.message.clone() + }; + match result.status { + ProveStatus::Verified if o.status.success() => AttemptResult::Succeeded { + duration_ms: elapsed_ms, + receipt: Some(ProofReceipt::from_result(&result)), + }, + ProveStatus::Verified => AttemptResult::Failed { + error: format!( + "{SCHEMA} contract violation: status verified but exit {}", + o.status.code().unwrap_or(-1) + ), + duration_ms: elapsed_ms, + }, + ProveStatus::Failed => AttemptResult::Failed { + error: message, + duration_ms: elapsed_ms, + }, + ProveStatus::Unknown => AttemptResult::Failed { + error: format!("inconclusive (status unknown): {message}"), + duration_ms: elapsed_ms, }, + ProveStatus::Timeout => AttemptResult::Timeout, + ProveStatus::Error => AttemptResult::Skipped { + reason: format!("echidna error: {message}"), + }, + } +} + +/// Map a pre-contract echidna run onto an [`AttemptResult`] from text markers. +fn interpret_legacy(o: &Output, elapsed_ms: u64) -> AttemptResult { + let stdout = String::from_utf8_lossy(&o.stdout); + let stderr = String::from_utf8_lossy(&o.stderr); + + // Discovered 2026-04-26 during the swarm-dogfood session: + // echidna's "Proof verified successfully" / "Proof verification + // failed" lines come from `OutputFormatter`, which writes to + // STDERR, not stdout. The previous stdout-only check made every + // attempt look "inconclusive" and demoted real failures to + // generic-failure anti-patterns. We now scan BOTH streams and + // also fall back on the exit code so a non-zero exit with no + // standard marker still becomes Failed (not Inconclusive). + let combined_lines: Vec<&str> = stdout.lines().chain(stderr.lines()).collect(); + let says_success = combined_lines + .iter() + .any(|l| l.contains("Proof verified successfully") || l.contains("✓ Proof verified")); + let says_failure = combined_lines.iter().any(|l| { + l.contains("Proof verification failed") + || l.contains("✗ Proof verification failed") + || l.contains("FAILED") + }); + let exit_failed = !o.status.success(); + + if says_success && !says_failure && !exit_failed { + AttemptResult::Succeeded { + duration_ms: elapsed_ms, + receipt: None, + } + } else if says_failure || exit_failed { + let err_excerpt: String = combined_lines + .iter() + .filter(|l| { + l.contains("***") + || l.contains("Failed") + || l.contains("error") + || l.contains("Unable") + }) + .take(3) + .copied() + .collect::>() + .join(" | "); + AttemptResult::Failed { + error: if err_excerpt.is_empty() { + format!( + "exit {} — no standard diagnostic captured", + o.status.code().unwrap_or(-1) + ) + } else { + err_excerpt + }, + duration_ms: elapsed_ms, + } + } else { + // Truly inconclusive — exit 0, no markers either way. + AttemptResult::Failed { + error: format!( + "inconclusive output (no success/failure marker, exit 0): {}", + stdout.chars().take(200).collect::() + ), + duration_ms: elapsed_ms, + } } } @@ -421,7 +563,7 @@ pub fn run_playbook( artifacts: vec![], }), learning, - extra: serde_json::Value::Null, + extra: receipt_extra(&result), }; if let Err(e) = l.append(&record) { eprintln!("warning: ledger append failed: {e}"); @@ -431,16 +573,49 @@ pub fn run_playbook( Ok(attempts) } +/// Ledger `extra` payload: the transported receipt, if any. +fn receipt_extra(r: &AttemptResult) -> serde_json::Value { + match r { + AttemptResult::Succeeded { + receipt: Some(receipt), + .. + } => serde_json::json!({ "receipt": receipt }), + _ => serde_json::Value::Null, + } +} + +/// Keep only characters that are safe in a probe file name. fn sanitize_filename(s: &str) -> String { s.chars() .filter(|c| c.is_alphanumeric() || *c == '_') .collect() } +/// One-line ledger explanation of an attempt outcome. +/// +/// Says whether a success is backed by an echidna receipt or only by +/// legacy text markers, so a reader can tell proof from hearsay. fn explain(r: &AttemptResult) -> String { match r { - AttemptResult::Succeeded { duration_ms } => { - format!("verified in {} ms", duration_ms) + AttemptResult::Succeeded { + duration_ms, + receipt: Some(rc), + } => format!( + "verified in {} ms (receipt: {} from echidna {}, prover {}, axioms [{}])", + duration_ms, + rc.schema, + rc.echidna_version, + rc.prover, + rc.axioms.join(", ") + ), + AttemptResult::Succeeded { + duration_ms, + receipt: None, + } => { + format!( + "verified in {} ms (legacy text markers: a warrant, not a receipt)", + duration_ms + ) } AttemptResult::Failed { error, duration_ms } => { format!("failed in {} ms: {}", duration_ms, error) @@ -450,6 +625,7 @@ fn explain(r: &AttemptResult) -> String { } } +/// Turn an attempt outcome into a ledger learning, if one is extractable. fn derive_learning( specialist: &str, tactic: &TacticTemplate, @@ -496,6 +672,7 @@ fn derive_learning( } } +/// Suggest the next tactic to try after `t` failed. fn suggest_next(t: &TacticTemplate) -> String { match t.name.as_str() { "simp" => "auto, blast, or fastforce", @@ -571,6 +748,7 @@ mod tests { ); } + /// Legacy path: a non-zero exit or failure text beats a success marker. #[cfg(unix)] #[test] fn subprocess_failure_overrides_success_text() { @@ -606,10 +784,225 @@ mod tests { run_probe("fixture", &config, "fixture.thy").is_success(), expected ); + // Each body is a new binary at the same path: forget the + // cached output mode so detection runs again. + crate::echidna_contract::remember_output_mode(&executable, OutputMode::Legacy); } std::fs::remove_dir_all(workdir).unwrap(); } + /// Write an executable fake echidna into `dir` and return its path. + #[cfg(unix)] + fn fake_echidna(dir: &std::path::Path, name: &str, body: &str) -> PathBuf { + use std::os::unix::fs::PermissionsExt; + let path = dir.join(name); + std::fs::write(&path, format!("#!/bin/sh\n{body}\n")).unwrap(); + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o700)).unwrap(); + path + } + + /// A probe-runner config pointing at `echidna`. + fn config_for(echidna: PathBuf) -> ProverConfig { + ProverConfig { + echidna_path: echidna, + timeout_secs: 5, + ..ProverConfig::default() + } + } + + const RESULT: &str = r#"{"duration_ms":7,"echidna_version":"2.4.0","goal":"p.thy","message":"MSG","prover":"Isabelle","schema":"echidna.prove.result/1","status":"STATUS","trust":{"axioms":["sorry"],"confidence":0.5}}"#; + + /// A contract-speaking fake: advertises `--output`, prints `json`, exits `exit`. + #[cfg(unix)] + fn contract_echidna(dir: &std::path::Path, name: &str, json: &str, exit: i32) -> PathBuf { + fake_echidna( + dir, + name, + &format!( + "case \"$*\" in\n *--help*) echo ' --output Output format'; exit 0 ;;\n *'--output json'*) printf '%s\\n' '{json}'; exit {exit} ;;\n *) echo 'Proof verified successfully' >&2; exit 0 ;;\nesac" + ), + ) + } + + /// Contract path: every status maps to the right outcome; success carries the receipt. + #[cfg(unix)] + #[test] + fn contract_mode_maps_every_status_and_carries_the_receipt() { + let dir = tempfile::tempdir().unwrap(); + let verified = RESULT.replace("STATUS", "verified").replace("MSG", ""); + let path = contract_echidna(dir.path(), "verified.sh", &verified, 0); + match run_probe("p", &config_for(path), "p.thy") { + AttemptResult::Succeeded { + receipt: Some(rc), .. + } => { + assert_eq!(rc.prover, "Isabelle"); + assert_eq!(rc.axioms, vec!["sorry".to_string()]); + assert_eq!(rc.confidence, Some(0.5)); + assert_eq!(rc.echidna_version, "2.4.0"); + assert_eq!(rc.schema, SCHEMA); + } + other => panic!("expected receipt-backed success, got {other:?}"), + } + + let cases: [(&str, &str, i32, &str); 6] = [ + ("failed", "Failed to apply", 1, "failed"), + ("failed", "", 1, "failed"), + ("unknown", "no answer", 0, "failed"), + ("timeout", "", 0, "timeout"), + ("error", "Isabelle missing", 1, "skipped"), + ("verified", "", 1, "failed"), + ]; + for (i, (status, msg, exit, expected)) in cases.iter().enumerate() { + let json = RESULT.replace("STATUS", status).replace("MSG", msg); + let path = contract_echidna(dir.path(), &format!("c{i}.sh"), &json, *exit); + let r = run_probe("p", &config_for(path), "p.thy"); + assert_eq!(r.status_string(), *expected, "{status}/{exit}: {r:?}"); + if !msg.is_empty() && *expected != "timeout" { + assert!(format!("{r:?}").contains(msg), "{r:?}"); + } + } + } + + /// Contract path: malformed or non-canonical stdout fails, never falls back to text. + #[cfg(unix)] + #[test] + fn contract_mode_rejects_malformed_output_instead_of_string_matching() { + let dir = tempfile::tempdir().unwrap(); + // Advertises the flag, then prints the legacy success text: a + // contract violation, never a success. + let path = contract_echidna(dir.path(), "liar.sh", "Proof verified successfully", 0); + match run_probe("p", &config_for(path), "p.thy") { + AttemptResult::Failed { error, .. } => { + assert!(error.contains("contract violation"), "{error}") + } + other => panic!("expected contract violation, got {other:?}"), + } + // Planted non-canonical mutant: right data, wrong key order. + let mutant = RESULT + .replace("STATUS", "verified") + .replace("MSG", "") + .replacen( + r#"{"duration_ms":7,"echidna_version":"2.4.0","#, + r#"{"echidna_version":"2.4.0","duration_ms":7,"#, + 1, + ); + let path = contract_echidna(dir.path(), "mutant.sh", &mutant, 0); + assert!(!run_probe("p", &config_for(path), "p.thy").is_success()); + } + + /// A pre-contract echidna is driven through the legacy text markers. + #[cfg(unix)] + #[test] + fn legacy_echidna_without_the_flag_uses_text_markers() { + let dir = tempfile::tempdir().unwrap(); + // Pre-contract echidna: no `--output` in help, and it would reject it. + let path = fake_echidna( + dir.path(), + "legacy.sh", + "case \"$*\" in\n *--help*) echo ' --project-root

'; exit 0 ;;\n *--output*) echo \"error: unexpected argument '--output' found\" >&2; exit 2 ;;\n *) echo 'Proof verified successfully' >&2; exit 0 ;;\nesac", + ); + match run_probe("p", &config_for(path), "p.thy") { + AttemptResult::Succeeded { receipt: None, .. } => {} + other => panic!("expected legacy success without receipt, got {other:?}"), + } + } + + /// A binary whose help lies about `--output` is demoted to legacy mode. + #[cfg(unix)] + #[test] + fn binary_that_advertises_but_rejects_the_flag_falls_back_once() { + let dir = tempfile::tempdir().unwrap(); + let path = fake_echidna( + dir.path(), + "fibber.sh", + "case \"$*\" in\n *--help*) echo ' --output '; exit 0 ;;\n *--output*) echo \"error: unexpected argument '--output' found\" >&2; exit 2 ;;\n *) echo 'Proof verified successfully' >&2; exit 0 ;;\nesac", + ); + let cfg = config_for(path.clone()); + assert!(run_probe("p", &cfg, "p.thy").is_success()); + assert_eq!( + crate::echidna_contract::detect_output_mode(&path), + OutputMode::Legacy + ); + } + + /// Bare names resolve through PATH; explicit paths are used as given. + #[cfg(unix)] + #[test] + fn bare_name_is_resolved_on_path_only() { + let dir = tempfile::tempdir().unwrap(); + let found = fake_echidna(dir.path(), "echidna-fake", "exit 0"); + let path_var = + std::env::join_paths([PathBuf::from("/nonexistent"), dir.path().to_path_buf()]) + .unwrap(); + assert_eq!( + resolve_executable(std::path::Path::new("echidna-fake"), Some(&path_var)), + Some(found.clone()) + ); + assert_eq!( + resolve_executable(std::path::Path::new("echidna-fake"), None), + None + ); + assert_eq!( + resolve_executable(std::path::Path::new("absent-echidna"), Some(&path_var)), + None + ); + assert_eq!(resolve_executable(&found, None), Some(found.clone())); + assert_eq!( + resolve_executable(&dir.path().join("absent"), Some(&path_var)), + None + ); + } + + /// Ledger text and `extra` tell receipt-backed successes from marker-only ones. + #[test] + fn ledger_explanations_distinguish_receipts_from_warrants() { + let receipt = ProofReceipt { + schema: SCHEMA.into(), + prover: "Isabelle".into(), + axioms: vec!["sorry".into()], + confidence: None, + echidna_version: "2.4.0".into(), + }; + let with = AttemptResult::Succeeded { + duration_ms: 3, + receipt: Some(receipt), + }; + let without = AttemptResult::Succeeded { + duration_ms: 3, + receipt: None, + }; + assert!(explain(&with).contains("receipt: echidna.prove.result/1")); + assert!(explain(&with).contains("axioms [sorry]")); + assert!(explain(&without).contains("warrant, not a receipt")); + assert_eq!(receipt_extra(&with)["receipt"]["prover"], "Isabelle"); + assert!(receipt_extra(&without).is_null()); + let json = serde_json::to_string(&without).unwrap(); + assert!(!json.contains("receipt"), "{json}"); + } + + /// `run_playbook` stores the transported receipt in the ledger record. + #[cfg(unix)] + #[test] + fn playbook_ledger_records_the_receipt() { + let dir = tempfile::tempdir().unwrap(); + let verified = RESULT.replace("STATUS", "verified").replace("MSG", ""); + let path = contract_echidna(dir.path(), "pb.sh", &verified, 0); + let ledger = crate::ledger::Ledger::open(dir.path().join("l.jsonl")).unwrap(); + let playbook = Playbook { + specialist: "Algebraist".into(), + tactics: vec![TacticTemplate { + name: "simp".into(), + script: "by simp".into(), + description: "simplifier".into(), + }], + }; + let goal = parse_goal("lemma foo: \"True\" by simp"); + let attempts = run_playbook(&goal, &playbook, &config_for(path), Some(&ledger)).unwrap(); + assert!(attempts[0].result.is_success()); + let records = ledger.read_all().unwrap(); + assert_eq!(records[0].extra["receipt"]["schema"], SCHEMA); + } + #[test] fn generate_probe_makes_well_formed_theory() { let g = parse_goal("lemma foo: \"x + 0 = x\" by simp"); diff --git a/crates/burrower-core/src/echidna_contract.rs b/crates/burrower-core/src/echidna_contract.rs new file mode 100644 index 0000000..3c5d2d7 --- /dev/null +++ b/crates/burrower-core/src/echidna_contract.rs @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! # The `echidna.prove.result/1` contract +//! +//! `echidna prove ... --output json` prints exactly one JCS-canonical +//! (RFC 8785) I-JSON (RFC 7493) object on stdout: +//! +//! ```json +//! {"duration_ms":12,"echidna_version":"2.4.0","goal":"probe.thy", +//! "message":"","prover":"Isabelle","schema":"echidna.prove.result/1", +//! "status":"verified","trust":{"axioms":[],"confidence":null}} +//! ``` +//! +//! (shown wrapped; on the wire it is one line, keys in JCS order). +//! +//! This module is the consumer side. It parses and validates that object +//! and detects whether an installed `echidna` offers the flag at all, so +//! [`crate::attempt::run_probe`] can fall back to the legacy text markers +//! only when the flag is absent. See `docs/ECHIDNA-INTEGRATION.adoc`. +//! +//! ## Receipt, not warrant +//! +//! A `verified` result parsed from this contract is a *receipt*: echidna's +//! own statement of what it checked, with the prover and the axioms it +//! relied on. A success inferred from text markers is only a *warrant* +//! (evidence that something printed "verified"). The distinction follows +//! the factive/non-factive split in `hyperpolymath/epistemic-types` +//! (`FactiveModality`); this crate copies the distinction as a pattern and +//! does not depend on that Agda library. + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::fmt; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; +use std::sync::{Mutex, OnceLock}; + +/// The only schema identifier this consumer accepts. +pub const SCHEMA: &str = "echidna.prove.result/1"; + +/// The `status` field of a prove result. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ProveStatus { + /// The prover accepted the proof. + Verified, + /// The prover rejected the proof. + Failed, + /// echidna or the backend could not run (configuration, missing tool). + Error, + /// The backend exceeded its time budget. + Timeout, + /// The backend finished without a definite answer. + Unknown, +} + +/// The `trust` object: what the verdict rests on. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Trust { + /// echidna's confidence, or `null` when it has no receipt-backed figure. + pub confidence: Option, + /// Axioms (or `sorry`-like escapes) the checked proof depends on. + pub axioms: Vec, +} + +/// One parsed `echidna.prove.result/1` object. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ProveResult { + /// Always [`SCHEMA`]. + pub schema: String, + /// The verdict. + pub status: ProveStatus, + /// Backend that produced the verdict, e.g. `Isabelle`. + pub prover: String, + /// The goal or file echidna was asked to prove. + pub goal: String, + /// echidna's own timing of the backend run. + pub duration_ms: u64, + /// Human-readable diagnostic; may be empty. + pub message: String, + /// What the verdict rests on. + pub trust: Trust, + /// The `echidna --version` that emitted this object. + pub echidna_version: String, +} + +/// Why a stdout payload is not a valid `echidna.prove.result/1` object. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ContractError { + /// stdout was empty. + Empty, + /// stdout held more than one line (the contract allows exactly one object). + ExtraOutput, + /// Not I-JSON (bad JSON, duplicate keys, unsafe integers, ...). + NotIJson(String), + /// Valid I-JSON, but not byte-identical to its JCS canonical form. + NotCanonical, + /// The object does not have the contract's shape. + Shape(String), + /// `schema` names a version this consumer does not understand. + WrongSchema(String), +} + +impl fmt::Display for ContractError { + /// Render the violation as a one-line diagnostic. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ContractError::Empty => write!(f, "empty stdout"), + ContractError::ExtraOutput => write!(f, "more than one line on stdout"), + ContractError::NotIJson(e) => write!(f, "not I-JSON: {e}"), + ContractError::NotCanonical => write!(f, "not JCS-canonical (RFC 8785)"), + ContractError::Shape(e) => write!(f, "wrong shape: {e}"), + ContractError::WrongSchema(s) => write!(f, "unsupported schema {s:?}"), + } + } +} + +impl std::error::Error for ContractError {} + +/// Parse and validate echidna's stdout as one `echidna.prove.result/1` object. +/// +/// Accepts exactly one line (a single trailing newline is allowed). The line +/// must be I-JSON, must equal its own JCS canonicalisation byte for byte, +/// must name [`SCHEMA`], and must carry every contract field with the right +/// type. Unknown extra fields are tolerated so that additive changes do not +/// break older consumers; removing or retyping a field needs a new schema. +pub fn parse_prove_result(stdout: &str) -> Result { + let line = stdout.strip_suffix('\n').unwrap_or(stdout); + if line.is_empty() { + return Err(ContractError::Empty); + } + if line.contains('\n') { + return Err(ContractError::ExtraOutput); + } + let value = ijson_jcs::parse_json(line, ijson_jcs::JsonMode::Strict) + .map_err(|e| ContractError::NotIJson(e.to_string()))?; + let canonical = + ijson_jcs::to_jcs_string(&value).map_err(|e| ContractError::NotIJson(e.to_string()))?; + if canonical != line { + return Err(ContractError::NotCanonical); + } + match value.get("schema").and_then(|s| s.as_str()) { + Some(SCHEMA) => {} + Some(other) => return Err(ContractError::WrongSchema(other.to_string())), + None => return Err(ContractError::Shape("missing string field `schema`".into())), + } + serde_json::from_value(value).map_err(|e| ContractError::Shape(e.to_string())) +} + +/// How proof-burrower talks to a given `echidna` binary. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputMode { + /// `echidna prove ... --output json`, parsed with [`parse_prove_result`]. + Contract, + /// Pre-contract echidna: success and failure are read from text markers. + Legacy, +} + +/// Process-wide cache of [`OutputMode`] per echidna path. +fn mode_cache() -> &'static Mutex> { + static CACHE: OnceLock>> = OnceLock::new(); + CACHE.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Decide whether `echidna prove --help` text advertises `--output`. +/// +/// Matches the option as a whole whitespace-delimited token (clap prints +/// `--output ` or `-o, --output `), so options such as +/// `--output-dir` do not count. +pub fn help_advertises_output(help: &str) -> bool { + help.split_whitespace().any(|token| { + let token = token.trim_end_matches(','); + token == "--output" || token.starts_with("--output=") + }) +} + +/// Detect, once per binary and process, whether `echidna` supports the contract. +/// +/// Runs ` prove --help` and looks for the `--output` option. Any +/// failure to run the binary yields [`OutputMode::Legacy`]; the real attempt +/// then reports the underlying problem. +pub fn detect_output_mode(echidna: &Path) -> OutputMode { + if let Some(mode) = mode_cache() + .lock() + .ok() + .and_then(|c| c.get(echidna).copied()) + { + return mode; + } + let mut help = Command::new(echidna); + help.arg("prove").arg("--help"); + let mode = match output_tolerating_busy(&mut help) { + Ok(out) if help_advertises_output(&String::from_utf8_lossy(&out.stdout)) => { + OutputMode::Contract + } + Ok(_) => OutputMode::Legacy, + // Could not run the binary at all: answer Legacy for this call but + // do not cache it, so a transient failure is not remembered. + Err(_) => return OutputMode::Legacy, + }; + remember_output_mode(echidna, mode); + mode +} + +/// Run `cmd` to completion, retrying briefly while the executable is busy. +/// +/// `ETXTBSY` ("Text file busy") is transient: it means some process still +/// holds the binary open for writing, typically during an install or while +/// another thread is between `fork` and `exec`. Any other error is returned +/// at once. +pub fn output_tolerating_busy(cmd: &mut Command) -> std::io::Result { + const ETXTBSY: i32 = 26; + let mut attempt = 0; + loop { + match cmd.output() { + Err(e) if e.raw_os_error() == Some(ETXTBSY) && attempt < 20 => { + attempt += 1; + std::thread::sleep(std::time::Duration::from_millis(10 * attempt)); + } + other => return other, + } + } +} + +/// Record `mode` for `echidna`, e.g. after a binary rejected `--output`. +pub fn remember_output_mode(echidna: &Path, mode: OutputMode) { + if let Ok(mut cache) = mode_cache().lock() { + cache.insert(echidna.to_path_buf(), mode); + } +} + +/// True when stderr shows clap rejecting the `--output` flag itself. +pub fn rejected_output_flag(stderr: &str) -> bool { + stderr.contains("unexpected argument '--output'") +} + +#[cfg(test)] +mod tests { + use super::*; + + const GOOD: &str = r#"{"duration_ms":12,"echidna_version":"2.4.0","goal":"probe.thy","message":"","prover":"Isabelle","schema":"echidna.prove.result/1","status":"verified","trust":{"axioms":["sorry"],"confidence":null}}"#; + + /// A canonical object parses, with or without one trailing newline. + #[test] + fn parses_a_canonical_result_with_or_without_trailing_newline() { + for out in [GOOD.to_string(), format!("{GOOD}\n")] { + let r = parse_prove_result(&out).unwrap(); + assert_eq!(r.status, ProveStatus::Verified); + assert_eq!(r.prover, "Isabelle"); + assert_eq!(r.duration_ms, 12); + assert_eq!(r.trust.axioms, vec!["sorry".to_string()]); + assert_eq!(r.trust.confidence, None); + } + } + + /// Each of the five statuses parses and serialises back unchanged. + #[test] + fn every_status_round_trips() { + for s in ["verified", "failed", "error", "timeout", "unknown"] { + let out = GOOD.replace("\"verified\"", &format!("\"{s}\"")); + let r = parse_prove_result(&out).unwrap(); + assert_eq!(serde_json::to_value(r.status).unwrap(), s); + } + } + + /// Planted mutants (key order, whitespace) are rejected as non-canonical. + #[test] + fn planted_non_canonical_mutant_is_rejected() { + // Same data, keys out of JCS order: valid JSON, invalid contract. + let mutant = GOOD.replacen( + r#"{"duration_ms":12,"echidna_version":"2.4.0","#, + r#"{"echidna_version":"2.4.0","duration_ms":12,"#, + 1, + ); + assert_ne!(mutant, GOOD); + assert_eq!( + parse_prove_result(&mutant), + Err(ContractError::NotCanonical) + ); + // Whitespace is a non-canonical mutant too. + let spaced = GOOD.replace("\"goal\":", "\"goal\": "); + assert_eq!( + parse_prove_result(&spaced), + Err(ContractError::NotCanonical) + ); + } + + /// Empty, multi-line, non-I-JSON, wrong-schema and wrong-shape inputs are rejected. + #[test] + fn rejects_empty_extra_lines_bad_json_schema_and_shape() { + assert_eq!(parse_prove_result(""), Err(ContractError::Empty)); + assert_eq!(parse_prove_result("\n"), Err(ContractError::Empty)); + assert_eq!( + parse_prove_result(&format!("notice\n{GOOD}\n")), + Err(ContractError::ExtraOutput) + ); + assert!(matches!( + parse_prove_result("Proof verified successfully"), + Err(ContractError::NotIJson(_)) + )); + assert!(matches!( + parse_prove_result(r#"{"a":1,"a":2}"#), + Err(ContractError::NotIJson(_)) + )); + assert!(matches!( + parse_prove_result(r#"{"duration_ms":9007199254740993}"#), + Err(ContractError::NotIJson(_)) + )); + assert_eq!( + parse_prove_result(&GOOD.replace("/1", "/2")), + Err(ContractError::WrongSchema("echidna.prove.result/2".into())) + ); + assert!(matches!( + parse_prove_result(r#"{"status":"verified"}"#), + Err(ContractError::Shape(_)) + )); + assert!(matches!( + parse_prove_result(&GOOD.replace("\"verified\"", "\"proved\"")), + Err(ContractError::Shape(_)) + )); + } + + /// Every contract error renders as a single non-empty line. + #[test] + fn errors_render_as_one_line() { + for e in [ + ContractError::Empty, + ContractError::ExtraOutput, + ContractError::NotIJson("x".into()), + ContractError::NotCanonical, + ContractError::Shape("y".into()), + ContractError::WrongSchema("z".into()), + ] { + let s = e.to_string(); + assert!(!s.is_empty() && !s.contains('\n'), "{s}"); + } + } + + /// Help detection matches `--output` only as a whole option. + #[test] + fn help_detection_matches_whole_option_only() { + assert!(help_advertises_output( + "Options:\n --output Output format [default: text]" + )); + assert!(help_advertises_output(" -o, --output ")); + assert!(help_advertises_output(" --output=json")); + assert!(!help_advertises_output( + " --output-dir

\n --format " + )); + assert!(!help_advertises_output("")); + } + + /// clap's rejection of `--output` is recognised from stderr. + #[test] + fn rejected_flag_is_recognised_from_clap_stderr() { + assert!(rejected_output_flag( + "error: unexpected argument '--output' found\n" + )); + assert!(!rejected_output_flag("error: something else")); + } + + /// An unrunnable binary detects as legacy; a remembered mode is reused. + #[test] + fn missing_binary_detects_as_legacy_and_is_cached() { + let p = Path::new("/nonexistent/echidna-contract-detect"); + assert_eq!(detect_output_mode(p), OutputMode::Legacy); + remember_output_mode(p, OutputMode::Contract); + assert_eq!(detect_output_mode(p), OutputMode::Contract); + } +} diff --git a/crates/burrower-core/src/ledger.rs b/crates/burrower-core/src/ledger.rs index 953fb89..bb67eae 100644 --- a/crates/burrower-core/src/ledger.rs +++ b/crates/burrower-core/src/ledger.rs @@ -199,7 +199,16 @@ pub fn goal_hash(goal_text: &str) -> String { format!("{:016x}", h.finish()) } -/// New record id: epoch millis + sequence counter (process-local). +/// Mint a new ledger record id: epoch millis + a process-local sequence. +/// +/// **This is the only identifier-minting function in proof-burrower.** +/// Every ledger writer (`attempt::run_playbook`, `oracle::record_to_ledger`, +/// [`record_reading`]) calls it; nothing else mints ids. The format is +/// `<13-digit millis>-<4-digit seq>`, which sorts by time within one +/// process. It is not a UUID: two processes appending in the same +/// millisecond can mint the same id. Moving to the estate UUIDv7 standard +/// (`standards/docs/UUID-V7-ESTATE-STANDARD.adoc`) is a recorded follow-up +/// and would change only this function. pub fn new_id() -> String { use std::sync::atomic::{AtomicU64, Ordering}; static SEQ: AtomicU64 = AtomicU64::new(0); @@ -211,6 +220,10 @@ pub fn new_id() -> String { format!("{millis:013}-{n:04}") } +/// Timestamp for a ledger record. +/// +/// Despite the name this is `epoch:`, not ISO 8601; see the +/// inline note. Kept as is so existing ledgers stay comparable. pub fn now_iso() -> String { let secs = SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/crates/burrower-core/src/lib.rs b/crates/burrower-core/src/lib.rs index 7ff7a48..4fa4547 100644 --- a/crates/burrower-core/src/lib.rs +++ b/crates/burrower-core/src/lib.rs @@ -28,6 +28,7 @@ pub mod attempt; pub mod corpus; +pub mod echidna_contract; pub mod goal; pub mod ledger; pub mod oracle; @@ -36,8 +37,8 @@ pub mod serve; pub mod specialist; pub use attempt::{ - generate_probe, run_playbook, run_probe, AttemptResult, Playbook, ProofAttempt, ProverConfig, - TacticTemplate, + generate_probe, run_playbook, run_probe, AttemptResult, Playbook, ProofAttempt, ProofReceipt, + ProverConfig, TacticTemplate, }; pub use corpus::{Corpus, IndexedLemma, LibraryKind}; pub use goal::{parse_goal, Goal}; diff --git a/crates/burrower-core/src/oracle.rs b/crates/burrower-core/src/oracle.rs index 96c8774..919a65d 100644 --- a/crates/burrower-core/src/oracle.rs +++ b/crates/burrower-core/src/oracle.rs @@ -10,8 +10,11 @@ //! `pattern_kind = oracle-counter-example`. //! //! The oracle itself is the Julia process at -//! `tropical-resource-typing/tools/julia-oracle.jl`, invoked with a -//! single A2ML descriptor argument. We parse the verdict line from +//! `tools/julia-oracle.jl` in `hyperpolymath/tropical-types` (formerly +//! `tropical-resource-typing`; last checked against tropical-types +//! `ad7bfdfd5699931df56b1241c60239a4085c203e`). The caller supplies the +//! script path ([`OracleConfig::script`]), so the pin is the caller's +//! checkout. It is invoked with a single A2ML descriptor argument. We parse the verdict line from //! stdout — keeping a small surface so adding new oracle families //! (Kleene-star fixed-point, walks closure, …) only requires adding a //! new family handler in the Julia side. diff --git a/crates/burrower-core/tests/live_isabelle.rs b/crates/burrower-core/tests/live_isabelle.rs index 06facb7..3ab027e 100644 --- a/crates/burrower-core/tests/live_isabelle.rs +++ b/crates/burrower-core/tests/live_isabelle.rs @@ -31,7 +31,10 @@ fn negative_control_requires_an_unproved_goal_diagnostic() { reason: "missing executable".into(), }, AttemptResult::Timeout, - AttemptResult::Succeeded { duration_ms: 0 }, + AttemptResult::Succeeded { + duration_ms: 0, + receipt: None, + }, ] { assert!(!is_isabelle_rejection(&result), "{result:?}"); } diff --git a/docs/ECHIDNA-INTEGRATION.adoc b/docs/ECHIDNA-INTEGRATION.adoc index 5e443bd..2996d58 100644 --- a/docs/ECHIDNA-INTEGRATION.adoc +++ b/docs/ECHIDNA-INTEGRATION.adoc @@ -50,16 +50,134 @@ This doc covers *the wires between Burrower and ECHIDNA / echidnabot*. └──────────────────────────┘ ---- -== Today (no integration) +== Today (what is wired) * `echidna prove ` — single-shot verification, returns pass/fail * `echidnabot` — receives push/PR webhooks, dispatches `echidna prove`, publishes a check run * Burrower's `attempt` subcommand subprocess-invokes `echidna prove` for - each (specialist × tactic) pair (this is the only existing wire today) + each (specialist × tactic) pair, and reads the result through the + `echidna.prove.result/1` contract when the installed echidna supports it + (<>) +* EI-1 (`--project-root`) and safe-learning (b) (`--sandbox`) are + implemented on both sides and forwarded by `ProverConfig` +* BI-1 (`burrower serve`) is implemented on the Burrower side; no echidna + or echidnabot client calls it yet -That third bullet is the *only* current integration point. Everything else -in this document is **planned**. +The third bullet is the only integration that runs end to end today +(exercised by `tests/e2e.sh` in the `Burrower proof safety` workflow). +Everything else in this document from EI-2 onward is **planned**. + +[[prove-result-contract]] +== The `echidna.prove.result/1` contract + +Shared between echidna (producer), proof-burrower and echidnabot +(consumers). Agreed 2026-10-05. + +=== Wire format + +`echidna prove ... --output json` prints **exactly one** JCS-canonical +(RFC 8785) I-JSON (RFC 7493) object on stdout, then a newline. Human +output stays the default when `--output` is absent. + +[source,json] +---- +{"duration_ms":12,"echidna_version":"2.4.0","goal":"probe.thy","message":"","prover":"Isabelle","schema":"echidna.prove.result/1","status":"verified","trust":{"axioms":[],"confidence":null}} +---- + +[cols="1,1,3"] +|=== +|Field |Type |Meaning + +|`schema` |string |Always `echidna.prove.result/1`. +|`status` |string |`verified` \| `failed` \| `error` \| `timeout` \| `unknown`. +|`prover` |string |Backend that produced the verdict. +|`goal` |string |The goal or file echidna was asked to prove. +|`duration_ms` |integer |echidna's timing; within ±(2^53−1) per I-JSON. +|`message` |string |Diagnostic; may be empty. +|`trust.confidence` |number or null |`null` unless echidna has a receipt-backed figure. +|`trust.axioms` |array of string |Axioms / escapes the checked proof relies on. +|`echidna_version` |string |Version of the emitting echidna. +|=== + +=== Consumer rules (proof-burrower) + +Implemented in `crates/burrower-core/src/echidna_contract.rs` and +`attempt.rs`. + +. **Detection.** Once per echidna binary per process, Burrower runs + `echidna prove --help` and looks for `--output` as a whole option token. + Present → contract mode; absent or the binary cannot run → legacy mode. +. **Contract mode.** Burrower appends `--output json` and requires stdout + to be one line that parses as I-JSON (no duplicate keys, safe integers), + equals its own JCS canonicalisation byte for byte (via + `hyperpolymath/ijson-jcs`, pinned by git rev), names the `/1` schema, and + carries every field above with the right type. Extra fields are + tolerated (additive change); removing or retyping a field needs `/2`. +. **Mapping.** ++ +[cols="1,2"] +|=== +|`status` |Burrower `AttemptResult` + +|`verified` + exit 0 |`Succeeded` with a `ProofReceipt` (prover, axioms, confidence, version) +|`verified` + non-zero exit |`Failed` (contract violation: failure evidence wins) +|`failed` |`Failed` with `message` +|`unknown` |`Failed` (inconclusive) +|`timeout` |`Timeout` +|`error` |`Skipped` (infrastructure, no learning recorded) +|malformed / non-canonical |`Failed` (contract violation) — never re-read as text +|=== +. **Fallback.** Only when the flag is absent (detection) or clap rejects + it (`unexpected argument '--output'`, exit 2) does Burrower use the + legacy text markers (`Proof verified successfully` / `Proof verification + failed` on either stream, plus the exit code). A legacy success has no + receipt and the ledger explanation says + "a warrant, not a receipt". +. **Ledger.** A receipt-backed success stores the receipt under the + record's `extra.receipt`. + +The receipt/warrant split follows the factive/non-factive distinction in +`hyperpolymath/epistemic-types` (`FactiveModality`). It is copied as a +pattern; Burrower does not depend on that Agda library. + +=== Tests + +* `echidna_contract::tests` — canonical parse, every status, planted + non-canonical mutants (key order, whitespace), empty / multi-line / + duplicate-key / unsafe-integer / wrong-schema / wrong-shape rejection. +* `attempt::tests` — fake echidna binaries for the contract path (every + status, receipt transport into the ledger), a malformed-output liar, the + legacy path, and a binary that advertises the flag but rejects it. +* `tests/e2e.sh` (CI `Burrower proof safety`) — real Isabelle through the + echidna pinned in `.github/workflows/proof-safety.yml`. That pin predates + the contract, so today this exercises the **legacy** path; bump the pin + once echidna ships `--output json` to exercise the contract path live. + +[[corpus-overlap]] +== Overlap with echidna's corpus subsystem (recommendation) + +echidna ships `echidna corpus ingest|query|stats|crossquery|near| +export-octads` over 20 prover adapters (Agda, Coq, Lean, Isabelle, Idris2, +Mizar, Metamath, HOL4, HOL Light, ACL2, TPTP, SMT-LIB, Why3, F*, Dafny, +MiniF2F, ProofNet, ...), with dependency closure, a cross-prover synonym +table, and hashing-trick vector embeddings (`near`, cosine similarity). + +Burrower's `corpus` + `ranking` index `.thy` / `.v` / `.lean` and rank by +Jaccard over signature tokens. Overlap is real: both build an inverted +index of lemma signatures and answer "what is near this goal". + +What only Burrower has: the specialist swarm (per-vocabulary readings, +consensus homes, boundary objects), tactic playbooks, the attempt loop and +the ledger. + +**Recommendation.** Do not grow Burrower's own indexer further. Keep it as +the zero-dependency fallback (it powers the WASM shim and runs without +echidna), and add an optional retrieval backend that shells out to +`echidna corpus near --format json` when echidna is present, so specialists +rank echidna's richer candidates. That needs a stable JSON contract for +`corpus near` analogous to `echidna.prove.result/1`; until echidna defines +one, the two indexers stay separate. Not implemented in this change. == ECHIDNA — three concrete changes From c8280e3e294d17bed462e2a694e4b914cd28a5ca Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:53:47 +0100 Subject: [PATCH 3/9] docs: restore proof-burrower identity and move toward the RSR template shape - README.adoc described PseudoScript; rewritten for Proof Burrower. - ARCHITECTURE.adoc was the untouched template; replaced by docs/ARCHITECTURE.adoc describing the real crates. - Root docs move under docs/ and docs/status/ (ROADMAP, READINESS, PROOF-*, TEST-NEEDS, AUDIT, GOVERNANCE, MAINTAINERS, EXPLAINME), with their consumers (Mustfile, Trustfile, validate-template.sh, Justfile) updated. Duplicate root SECURITY.adoc / MAINTAINERS removed in favour of .github/SECURITY.md and .github/MAINTAINERS. - Add proof-burrower_chora.deed (identity, clade, status, maturity, ecosystem); deed_lint OK. - Descriptiles describe proof-burrower rather than the template copy. - mise.toml: drop banned tools (python, node, go, java, make, deno...), lockfile = true, add mise.lock. - dependabot.yml: only ecosystems present (github-actions, cargo), cap actions PRs at 2 (folded from chore/dependabot-pr-caps-2026-09-08); KYAML. - settings.yml: squash only, wiki on; KYAML. - Well-known workflow: take the template version, which reads the canonical www/.well-known/security.txt (main was red on every scheduled run because it only looked at the root). security.txt loses its unrendered Encryption placeholder and bogus Hiring line. - docs/wikis/: forge wiki source (Home, Using, ECHIDNA contract, Maintaining) and scripts/sync-wiki.sh to publish it. Co-Authored-By: Claude Opus 5.5 --- .bot_directives/methodology.a2ml | 2 +- .github/dependabot.yml | 96 +++---- .github/settings.yml | 242 ++++++++++-------- .github/workflows/actions.lock | 2 +- .github/workflows/wellknown-enforcement.yml | 89 ++++++- .hypatia-ignore | 2 - .machine_readable/CLADE.a2ml | 4 +- .../contractiles/Adjustfile.a2ml | 4 +- .../contractiles/Intentfile.a2ml | 27 +- .machine_readable/contractiles/Justfile | 12 +- .machine_readable/contractiles/Mustfile.a2ml | 10 +- .machine_readable/contractiles/Trustfile.a2ml | 10 +- .machine_readable/descriptiles/ECOSYSTEM.a2ml | 26 +- .machine_readable/descriptiles/META.a2ml | 50 ++-- .machine_readable/descriptiles/NEUROSYM.a2ml | 2 +- .machine_readable/descriptiles/PLAYBOOK.a2ml | 2 +- .machine_readable/descriptiles/STATE.a2ml | 11 +- .../anchor/ANCHOR_2026_05_21.a2ml | 6 +- .../self-validating/methodology-guard.k9.ncl | 2 +- .mise.toml | 11 - ARCHITECTURE.adoc | 48 ---- CODE_OF_CONDUCT.adoc | 24 -- EXPLAINME.adoc | 85 ------ Justfile | 4 +- MAINTAINERS | 43 ---- QUICKSTART-DEV.adoc | 112 -------- QUICKSTART-MAINTAINER.adoc | 130 ---------- QUICKSTART-USER.adoc | 125 --------- README.adoc | 226 +++++----------- ROADMAP.adoc | 23 -- SECURITY.adoc | 16 -- TEST-NEEDS.adoc | 138 ---------- TOPOLOGY.adoc | 36 --- agents/algebraist.007 | 2 +- agents/combinatorialist.007 | 2 +- agents/order_theorist.007 | 2 +- audits/assail-classifications.a2ml | 16 -- docs/ARCHITECTURE.adoc | 82 ++++++ AUDIT.adoc => docs/AUDIT.adoc | 0 docs/EXPLAINME.adoc | 46 ++++ GOVERNANCE.adoc => docs/GOVERNANCE.adoc | 0 MAINTAINERS.adoc => docs/MAINTAINERS.adoc | 4 +- docs/attribution/CODEOWNERS.adoc | 2 +- .../2026-04-07-template-standards-audit.adoc | 0 .../status/PROOF-NEEDS.adoc | 0 .../status/PROOF-STATUS.adoc | 0 READINESS.adoc => docs/status/READINESS.adoc | 0 docs/status/ROADMAP.adoc | 34 +++ docs/status/TEST-NEEDS.adoc | 41 +++ docs/wikis/ECHIDNA-Contract.md | 28 ++ docs/wikis/Home.md | 24 ++ docs/wikis/Maintaining.md | 32 +++ docs/wikis/README.md | 14 +- docs/wikis/Using-Proof-Burrower.md | 65 +++++ llm-warmup-dev.adoc | 19 -- llm-warmup-user.adoc | 19 -- mise.lock | 89 +++++++ mise.toml | 74 ++---- proof-burrower_chora.deed | 67 +++++ scripts/sync-wiki.sh | 44 ++++ scripts/validate-template.sh | 4 +- session/local-hooks.sh | 2 +- src/interface/ffi/test/integration_test.zig | 2 +- tests/e2e/template_instantiation_test.sh | 2 +- www/.well-known/security.txt | 5 +- 65 files changed, 995 insertions(+), 1346 deletions(-) delete mode 100644 .mise.toml delete mode 100644 ARCHITECTURE.adoc delete mode 100644 CODE_OF_CONDUCT.adoc delete mode 100644 EXPLAINME.adoc delete mode 100644 MAINTAINERS delete mode 100644 QUICKSTART-DEV.adoc delete mode 100644 QUICKSTART-MAINTAINER.adoc delete mode 100644 QUICKSTART-USER.adoc delete mode 100644 ROADMAP.adoc delete mode 100644 SECURITY.adoc delete mode 100644 TEST-NEEDS.adoc delete mode 100644 TOPOLOGY.adoc create mode 100644 docs/ARCHITECTURE.adoc rename AUDIT.adoc => docs/AUDIT.adoc (100%) create mode 100644 docs/EXPLAINME.adoc rename GOVERNANCE.adoc => docs/GOVERNANCE.adoc (100%) rename MAINTAINERS.adoc => docs/MAINTAINERS.adoc (94%) rename TEMPLATE-STANDARDS-AUDIT.adoc => docs/reports/audit/2026-04-07-template-standards-audit.adoc (100%) rename PROOF-NEEDS.adoc => docs/status/PROOF-NEEDS.adoc (100%) rename PROOF-STATUS.adoc => docs/status/PROOF-STATUS.adoc (100%) rename READINESS.adoc => docs/status/READINESS.adoc (100%) create mode 100644 docs/status/ROADMAP.adoc create mode 100644 docs/status/TEST-NEEDS.adoc create mode 100644 docs/wikis/ECHIDNA-Contract.md create mode 100644 docs/wikis/Home.md create mode 100644 docs/wikis/Maintaining.md create mode 100644 docs/wikis/Using-Proof-Burrower.md delete mode 100644 llm-warmup-dev.adoc delete mode 100644 llm-warmup-user.adoc create mode 100644 mise.lock create mode 100644 proof-burrower_chora.deed create mode 100755 scripts/sync-wiki.sh diff --git a/.bot_directives/methodology.a2ml b/.bot_directives/methodology.a2ml index 774105d..19c78fd 100644 --- a/.bot_directives/methodology.a2ml +++ b/.bot_directives/methodology.a2ml @@ -102,7 +102,7 @@ constraints = [ # These rules detect corrupt/template/stale state files. [methodology.state-validation] -reject-if-contains = ["{{PLACEHOLDER}}", "Proof Burrower", "rsr-template-repo"] +reject-if-contains = ["{{PLACEHOLDER}}", "{{PROJECT_NAME}}", "rsr-template-repo"] reject-if-project-name-mismatch = true staleness-threshold-days = 90 fallback-files = ["TODO.md", "TODO.adoc", "ROADMAP.adoc", "README.adoc"] diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1af529e..9f62792 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,57 +1,45 @@ # SPDX-License-Identifier: MPL-2.0 # Dependabot configuration for RSR-compliant repositories -# Covers common ecosystems - remove unused ones for your project +# Only ecosystems present in this repository: github-actions, cargo. -version: 2 -updates: - # GitHub Actions - always include - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - groups: - actions: - patterns: - - "*" - - # Rust/Cargo - # - # `open-pull-requests-limit: 0` suppresses routine version-update PRs - # (no weekly patch-bump noise) while leaving Dependabot SECURITY PRs - # flowing. Under GitHub's current Dependabot behaviour (2024+), using - # an `ignore:` rule with `version-update:semver-patch` would ALSO - # silence security PRs that happen to be patch-level — historically - # the cause of estate-wide vulns sitting un-PR'd for weeks. - # `open-pull-requests-limit: 0` is the GitHub-endorsed way to say - # "security only, not routine bumps". Pair with the - # dependabot-automerge.yml workflow for low-touch security - # maintenance. - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 0 - - # Elixir/Mix - - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" - - # Node.js/npm - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - - # Python/pip - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - - # Nix flakes - - package-ecosystem: "nix" - directory: "/" - schedule: - interval: "weekly" +{ + version: 2, + updates: [ + # GitHub Actions - always include + { + package-ecosystem: "github-actions", + directory: "/", + schedule: { + interval: "weekly", + }, + groups: { + actions: { + patterns: [ + "*", + ], + }, + }, + open-pull-requests-limit: 2, + }, + # Rust/Cargo + # + # `open-pull-requests-limit: 0` suppresses routine version-update PRs + # (no weekly patch-bump noise) while leaving Dependabot SECURITY PRs + # flowing. Under GitHub's current Dependabot behaviour (2024+), using + # an `ignore:` rule with `version-update:semver-patch` would ALSO + # silence security PRs that happen to be patch-level — historically + # the cause of estate-wide vulns sitting un-PR'd for weeks. + # `open-pull-requests-limit: 0` is the GitHub-endorsed way to say + # "security only, not routine bumps". Pair with the + # dependabot-automerge.yml workflow for low-touch security + # maintenance. + { + package-ecosystem: "cargo", + directory: "/", + schedule: { + interval: "weekly", + }, + open-pull-requests-limit: 0, + }, + ], +} diff --git a/.github/settings.yml b/.github/settings.yml index 7dd7fa4..1144280 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -45,111 +45,137 @@ # ─── Repository Settings ─────────────────────────────────────────────────────── -repository: - has_issues: true - has_projects: true - has_wiki: false - has_downloads: true - default_branch: main - allow_squash_merge: true - allow_merge_commit: true - allow_rebase_merge: true - delete_branch_on_merge: true - enable_automated_security_fixes: true - enable_vulnerability_alerts: true - -# ─── Labels ──────────────────────────────────────────────────────────────────── - -labels: - - name: "bug" - color: "d73a4a" - description: "Something isn't working" - - - name: "enhancement" - color: "a2eeef" - description: "New feature or request" - - - name: "documentation" - color: "0075ca" - description: "Improvements or additions to documentation" - - - name: "security" - color: "e4e669" - description: "Security-related issue or vulnerability" - - - name: "good first issue" - color: "7057ff" - description: "Good for newcomers" - - - name: "help wanted" - color: "008672" - description: "Extra attention is needed" - - - name: "question" - color: "d876e3" - description: "Further information is requested" - - - name: "duplicate" - color: "cfd3d7" - description: "This issue or pull request already exists" - - - name: "invalid" - color: "e4e669" - description: "This doesn't seem right" - - - name: "wontfix" - color: "ffffff" - description: "This will not be worked on" - - - name: "dependencies" - color: "0366d6" - description: "Pull requests that update a dependency file" - - - name: "ci/cd" - color: "fbca04" - description: "Continuous integration and deployment" - - - name: "rsr" - color: "006b75" - description: "Rhodium Standard Repository compliance" - - - name: "hypatia" - color: "5319e7" - description: "Hypatia neurosymbolic scanner finding" - - - name: "bot" - color: "b4a8d1" - description: "Automated action by gitbot-fleet" - - - name: "breaking-change" - color: "b60205" - description: "Introduces a breaking change" - - - name: "performance" - color: "f9d0c4" - description: "Performance improvement" - - - name: "refactor" - color: "c5def5" - description: "Code refactoring with no functional change" - -# ─── Branch Protection ───────────────────────────────────────────────────────── - -branches: - - name: "main" - protection: - required_pull_request_reviews: - required_approving_review_count: 1 - dismiss_stale_reviews: true - require_code_owner_reviews: true - required_status_checks: - strict: true - contexts: - - "hypatia-scan" - - "codeql" - - "openssf-compliance" - enforce_admins: true - required_signatures: true - restrictions: null - allow_force_pushes: false - allow_deletions: false +{ + repository: { + has_issues: true, + has_projects: true, + has_wiki: true, + has_downloads: true, + default_branch: "main", + allow_squash_merge: true, + allow_merge_commit: false, + allow_rebase_merge: false, + delete_branch_on_merge: true, + enable_automated_security_fixes: true, + enable_vulnerability_alerts: true, + }, + # ─── Labels ──────────────────────────────────────────────────────────────────── + labels: [ + { + name: "bug", + color: "d73a4a", + description: "Something isn't working", + }, + { + name: "enhancement", + color: "a2eeef", + description: "New feature or request", + }, + { + name: "documentation", + color: "0075ca", + description: "Improvements or additions to documentation", + }, + { + name: "security", + color: "e4e669", + description: "Security-related issue or vulnerability", + }, + { + name: "good first issue", + color: "7057ff", + description: "Good for newcomers", + }, + { + name: "help wanted", + color: "008672", + description: "Extra attention is needed", + }, + { + name: "question", + color: "d876e3", + description: "Further information is requested", + }, + { + name: "duplicate", + color: "cfd3d7", + description: "This issue or pull request already exists", + }, + { + name: "invalid", + color: "e4e669", + description: "This doesn't seem right", + }, + { + name: "wontfix", + color: "ffffff", + description: "This will not be worked on", + }, + { + name: "dependencies", + color: "0366d6", + description: "Pull requests that update a dependency file", + }, + { + name: "ci/cd", + color: "fbca04", + description: "Continuous integration and deployment", + }, + { + name: "rsr", + color: "006b75", + description: "Rhodium Standard Repository compliance", + }, + { + name: "hypatia", + color: "5319e7", + description: "Hypatia neurosymbolic scanner finding", + }, + { + name: "bot", + color: "b4a8d1", + description: "Automated action by gitbot-fleet", + }, + { + name: "breaking-change", + color: "b60205", + description: "Introduces a breaking change", + }, + { + name: "performance", + color: "f9d0c4", + description: "Performance improvement", + }, + { + name: "refactor", + color: "c5def5", + description: "Code refactoring with no functional change", + }, + ], + # ─── Branch Protection ───────────────────────────────────────────────────────── + branches: [ + { + name: "main", + protection: { + required_pull_request_reviews: { + required_approving_review_count: 1, + dismiss_stale_reviews: true, + require_code_owner_reviews: true, + }, + required_status_checks: { + strict: true, + contexts: [ + "hypatia-scan", + "codeql", + "openssf-compliance", + ], + }, + enforce_admins: true, + required_signatures: true, + restrictions: null, + allow_force_pushes: false, + allow_deletions: false, + }, + }, + ], +} diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock index 5dd7028..c1cc304 100644 --- a/.github/workflows/actions.lock +++ b/.github/workflows/actions.lock @@ -59,7 +59,7 @@ workflows: - 'actions/upload-artifact@v4.6.2' - 'erlef/setup-beam@v1.20.4' '.github/workflows/wellknown-enforcement.yml': - - 'actions/checkout@v6.0.2' + - 'actions/checkout@v7.0.1' '.github/workflows/workflow-linter.yml': - 'actions/checkout@v6.0.2' dependencies: diff --git a/.github/workflows/wellknown-enforcement.yml b/.github/workflows/wellknown-enforcement.yml index 5b2ce3f..0fb8b4a 100644 --- a/.github/workflows/wellknown-enforcement.yml +++ b/.github/workflows/wellknown-enforcement.yml @@ -1,36 +1,50 @@ # This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# This workflow is managed by gh actions-lock. name: Well-Known Standards (RFC 9116 + RSR) on: push: branches: [main, master] paths: - - '.well-known/**' + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) - 'security.txt' pull_request: paths: - - '.well-known/**' + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) schedule: - # Weekly expiry check - - cron: '0 9 * * *' + # Weekly expiry check (Mondays 09:00 UTC) + - cron: '0 9 * * 1' workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: - actions: read contents: read jobs: validate: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 15 permissions: contents: read steps: - - uses: actions/checkout@v6.0.2 + - uses: actions/checkout@v7.0.1 - name: RFC 9116 security.txt validation run: | SECTXT="" - [ -f ".well-known/security.txt" ] && SECTXT=".well-known/security.txt" - [ -f "security.txt" ] && SECTXT="security.txt" + if [ -f "www/.well-known/security.txt" ]; then + SECTXT="www/.well-known/security.txt" + elif [ -f ".well-known/security.txt" ]; then + SECTXT=".well-known/security.txt" + echo "::warning::security.txt at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + elif [ -f "security.txt" ]; then + SECTXT="security.txt" + echo "::warning::security.txt at repository root — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi if [ -z "$SECTXT" ]; then echo "::error::No security.txt found — required for OpenSSF Best Practices. See https://github.com/hyperpolymath/well-known-ecosystem" @@ -62,9 +76,19 @@ jobs: - name: RSR well-known compliance run: | MISSING="" - [ ! -f ".well-known/security.txt" ] && [ ! -f "security.txt" ] && MISSING="$MISSING security.txt" - [ ! -f ".well-known/ai.txt" ] && MISSING="$MISSING ai.txt" - [ ! -f ".well-known/humans.txt" ] && MISSING="$MISSING humans.txt" + LEGACY="" + for f in security.txt ai.txt humans.txt; do + if [ -f "www/.well-known/$f" ]; then + : + elif [ -f ".well-known/$f" ] || { [ "$f" = security.txt ] && [ -f security.txt ]; }; then + LEGACY="$LEGACY $f" + else + MISSING="$MISSING $f" + fi + done + if [ -n "$LEGACY" ]; then + echo "::warning::legacy .well-known location (canonical is www/.well-known/):$LEGACY — run scripts/migrate-wellknown-to-www.sh" + fi if [ -n "$MISSING" ]; then echo "::warning::Missing RSR recommended files:$MISSING" @@ -72,6 +96,45 @@ jobs: else echo "✅ RSR well-known compliant" fi + # Stage 5 (#119) sweeps ~270 repositories. #106 claimed CI "drives the + # migrator"; until now it only printed advice, so nothing verified that a + # repo would actually survive the migration. This step runs the real + # migrator and turns every conflict into an annotation. + # + # --dry-run because this job holds contents: read and cannot push: the + # point is to surface divergence early in the migration window, not to + # mutate a checkout that will be thrown away. The sweep driver + # (scripts/sweep-wellknown.sh) is what performs the real migration. + - name: Legacy root .well-known/ migration report + run: | + if [ ! -d .well-known ]; then + echo "No root .well-known/ — nothing to migrate." + exit 0 + fi + if [ ! -f scripts/migrate-wellknown-to-www.sh ]; then + echo "::warning::root .well-known/ present but no migrator — template is behind #53 stage 5." + exit 0 + fi + bash scripts/migrate-wellknown-to-www.sh --dry-run --report wellknown-migration.tsv + echo "--- planned migration ---" + cat wellknown-migration.tsv + # Conflicts are warnings, not errors, for the duration of the + # migration window (#106): a repo must not go red for a condition + # the sweep exists to fix. It must not be silent either. + while IFS=$'\t' read -r action path detail; do + case "$action" in + quarantined|conflict|left) + echo "::warning title=well-known migration::$action $path — $detail" + ;; + esac + done < <(tail -n +2 wellknown-migration.tsv) + - name: www bundle self-test + run: | + if [ -d www/tests ]; then + bash www/tests/run-all.sh + else + echo "www/tests absent — site-operations bundle not present; skipping." + fi - name: Mixed content check run: | MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com' | head -5 || true) diff --git a/.hypatia-ignore b/.hypatia-ignore index f114ccb..a09a8dd 100644 --- a/.hypatia-ignore +++ b/.hypatia-ignore @@ -22,5 +22,3 @@ # Inventory taken 2026-08-06 across all 424 estate repositories. # Files covered: 2 rescript -cicd_rules/banned_language_file:affinescript/packages/affine-res/src/AffineScript.res -cicd_rules/banned_language_file:affinescript/packages/affine-res/src/AffineScriptValue.res diff --git a/.machine_readable/CLADE.a2ml b/.machine_readable/CLADE.a2ml index 514b334..00290a3 100644 --- a/.machine_readable/CLADE.a2ml +++ b/.machine_readable/CLADE.a2ml @@ -13,7 +13,7 @@ prefixed-name = "rm-proof-burrower" primary = "rm" secondary = ["gv"] assigned = "2026-03-16" -rationale = "" +rationale = "Inherited unchanged from the template copy (rm, gv). Proof Burrower is a proof-search companion to ECHIDNA (clade fv); the assignment needs a gv-clade-index review before it is changed here." [forges] github = "hyperpolymath/proof-burrower" @@ -22,7 +22,7 @@ bitbucket = "hyperpolymath/proof-burrower" [lineage] type = "standalone" -parent = "RSR template — scaffold for new repos" +parent = "" born = "2026-03-16" # Lifecycle status (added by clade-status-backfill; see gv-clade-index ADR 0006). diff --git a/.machine_readable/contractiles/Adjustfile.a2ml b/.machine_readable/contractiles/Adjustfile.a2ml index 6f01e89..7a99b8a 100644 --- a/.machine_readable/contractiles/Adjustfile.a2ml +++ b/.machine_readable/contractiles/Adjustfile.a2ml @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 -# Adjustfile — Drift-tolerance contract for rsr-template-repo +# Adjustfile — Drift-tolerance contract for proof-burrower # Author: Jonathan D.A. Jewell # # Cumulative-drift catchment: tolerance bands + corrective actions. @@ -8,7 +8,7 @@ # Fix with: adjust fix (applies deterministic patches; advisory otherwise) @abstract: -Drift tolerances and corrective actions for rsr-template-repo. Unlike +Drift tolerances and corrective actions for proof-burrower. Unlike MUST (hard gate), ADJUST tracks cumulative drift against tolerance bands and proposes corrective actions. Advisory — it warns and trends, it does not block. diff --git a/.machine_readable/contractiles/Intentfile.a2ml b/.machine_readable/contractiles/Intentfile.a2ml index ef74f45..b8d9b2d 100644 --- a/.machine_readable/contractiles/Intentfile.a2ml +++ b/.machine_readable/contractiles/Intentfile.a2ml @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 -# Intentfile (A2ML Canonical) — north-star contractile for rsr-template-repo +# Intentfile (A2ML Canonical) — north-star contractile for proof-burrower # Author: Jonathan D.A. Jewell # # Paired runner: intend.ncl @@ -20,28 +20,25 @@ # contractile for hard gates. @abstract: -North-star contractile for rsr-template-repo. This repository is the -canonical template for Rhodium Standard Repository compliance. It provides -the scaffold that all hyperpolymath repos should copy and customize. +North-star contractile for proof-burrower: find the mathematical home of +a proof goal, route it through specialist readers, attempt proofs through +ECHIDNA, and keep a ledger of what was learned. @end ## Purpose -The rsr-template-repo serves as the master template for all hyperpolymath -repositories. It contains the complete set of contractile files, machine-readable -specifications, and governance documentation that define the Rhodium Standard. - -Every new repository in the hyperpolymath estate should be initialized by -copying this template and substituting the placeholder values with -repo-specific content. +Proof Burrower locates where a stuck proof goal already lives (or nearly +lives) in theorem-prover libraries, reads it through a swarm of specialist +vocabularies, and attempts tactic playbooks through ECHIDNA, recording every +outcome in an append-only ledger so later runs build on earlier ones. +ECHIDNA is the prover; Burrower is the librarian and the lab notebook. ## Anti-Purpose This repository is NOT: -- A general-purpose project scaffold for external use (hyperpolymath-only) -- A replacement for per-repo customization (all files must be bespoke) -- A static template that never changes (evolves with RSR spec) -- A runtime library or framework (build-time only) +- A theorem prover (ECHIDNA proves; Burrower never claims a proof itself) +- A replacement for ECHIDNA's own corpus subsystem (see docs/ECHIDNA-INTEGRATION.adoc) +- A source of truth for proof status (a ledger entry records an attempt, not a theorem) ## If In Doubt diff --git a/.machine_readable/contractiles/Justfile b/.machine_readable/contractiles/Justfile index 6509a54..8b18cf0 100644 --- a/.machine_readable/contractiles/Justfile +++ b/.machine_readable/contractiles/Justfile @@ -19,9 +19,9 @@ set positional-arguments := true import? "contractile.just" # Project metadata — customize these -project := "rsr-template-repo" +project := "proof-burrower" OWNER := "hyperpolymath" -REPO := "rsr-template-repo" +REPO := "proof-burrower" version := "0.1.0" tier := "infrastructure" # 1 | 2 | infrastructure @@ -1302,7 +1302,7 @@ assail: # Self-diagnostic — checks dependencies, permissions, paths doctor: - @echo "Running diagnostics for rsr-template-repo..." + @echo "Running diagnostics for proof-burrower..." @echo "Checking required tools..." @command -v just >/dev/null 2>&1 && echo " [OK] just" || echo " [FAIL] just not found" @command -v git >/dev/null 2>&1 && echo " [OK] git" || echo " [FAIL] git not found" @@ -1312,7 +1312,7 @@ doctor: # Guided tour of key features tour: - @echo "=== rsr-template-repo Tour ===" + @echo "=== proof-burrower Tour ===" @echo "" @echo "1. Project structure:" @ls -la @@ -1327,12 +1327,12 @@ tour: # Open feedback channel with diagnostic context help-me: - @echo "=== rsr-template-repo Help ===" + @echo "=== proof-burrower Help ===" @echo "Platform: $(uname -s) $(uname -m)" @echo "Shell: $SHELL" @echo "" @echo "To report an issue:" - @echo " https://github.com/hyperpolymath/rsr-template-repo/issues/new" + @echo " https://github.com/hyperpolymath/proof-burrower/issues/new" @echo "" @echo "Include the output of 'just doctor' in your report." diff --git a/.machine_readable/contractiles/Mustfile.a2ml b/.machine_readable/contractiles/Mustfile.a2ml index b08509e..15ed5be 100644 --- a/.machine_readable/contractiles/Mustfile.a2ml +++ b/.machine_readable/contractiles/Mustfile.a2ml @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 -# Mustfile — Physical state contract for rsr-template-repo +# Mustfile — Physical state contract for proof-burrower # Author: Jonathan D.A. Jewell # # What MUST be true about this repository. Hard requirements. @@ -7,8 +7,8 @@ # Fix with: must fix (where a deterministic fix exists) @abstract: -Physical-state invariants for rsr-template-repo. This is the canonical -RSR template repository. These are hard requirements — CI and pre-commit +Physical-state invariants for proof-burrower (an RSR-templated repository). +These are hard requirements — CI and pre-commit hooks fail if any check fails. @end @@ -35,8 +35,8 @@ hooks fail if any check fails. - severity: critical ### governance-docs -- description: GOVERNANCE.adoc, MAINTAINERS.adoc, CODEOWNERS must exist -- run: test -f GOVERNANCE.adoc && test -f MAINTAINERS.adoc && test -f .github/CODEOWNERS +- description: docs/GOVERNANCE.adoc, docs/MAINTAINERS.adoc, .github/CODEOWNERS must exist +- run: test -f docs/GOVERNANCE.adoc && test -f docs/MAINTAINERS.adoc && test -f .github/CODEOWNERS - severity: critical ### machine-readable-dir diff --git a/.machine_readable/contractiles/Trustfile.a2ml b/.machine_readable/contractiles/Trustfile.a2ml index e2028b5..fd94626 100644 --- a/.machine_readable/contractiles/Trustfile.a2ml +++ b/.machine_readable/contractiles/Trustfile.a2ml @@ -1,12 +1,12 @@ # SPDX-License-Identifier: MPL-2.0 -# Trustfile — Trust boundaries and integrity invariants for rsr-template-repo +# Trustfile — Trust boundaries and integrity invariants for proof-burrower # Author: Jonathan D.A. Jewell # # Defines what LLM/SLM agents are trusted to do without asking, and # integrity invariants that verify the repo has not been tampered with. @abstract: -Trust boundaries and integrity checks for rsr-template-repo. This file +Trust boundaries and integrity checks for proof-burrower. This file combines the trust-level definitions from the original TRUST.contractile with the integrity invariants from the old Trustfile.a2ml. It defines what AI agents may do autonomously and what requires human approval, @@ -15,7 +15,7 @@ plus checks that verify repository integrity. ## Trust Levels -The rsr-template-repo operates at trust level: maximal +The proof-burrower repository operates at trust level: maximal Trust levels: - maximal: Agent may read, build, test, lint, format, heal freely. @@ -71,8 +71,8 @@ Current trust level: maximal - severity: advisory - areas: - .machine_readable/ - - GOVERNANCE.adoc - - MAINTAINERS.adoc + - docs/GOVERNANCE.adoc + - docs/MAINTAINERS.adoc - .github/CODEOWNERS ## Container Security diff --git a/.machine_readable/descriptiles/ECOSYSTEM.a2ml b/.machine_readable/descriptiles/ECOSYSTEM.a2ml index 76819aa..09598aa 100644 --- a/.machine_readable/descriptiles/ECOSYSTEM.a2ml +++ b/.machine_readable/descriptiles/ECOSYSTEM.a2ml @@ -1,25 +1,25 @@ # SPDX-License-Identifier: MPL-2.0 -# ECOSYSTEM.a2ml — Ecosystem position for rsr-template-repo -# Converted and updated 2026-04-23 +# ECOSYSTEM.a2ml — Ecosystem position for proof-burrower [metadata] -project = "rsr-template-repo" +project = "proof-burrower" ecosystem = "hyperpolymath" [position] -type = "infrastructure" -purpose = "Canonical RSR-compliant repository template providing scaffolding for all hyperpolymath projects — including CI/CD, AI manifests, ABI/FFI standards, container ecosystem, and governance infrastructure." +type = "tool" +purpose = "Locate the mathematical home of a proof goal, read it through specialist vocabularies, attempt tactic playbooks through ECHIDNA, and keep an append-only ledger of outcomes." [pipeline] -position = "foundation" -chain = "standards → rsr-template-repo → all-other-repos" -notes = "This repository serves as the practical implementation of the Hyperpolymath standards. It is the root of the 'scaffolding' tree for the entire ecosystem." +position = "pre-prover" +chain = "author goal → proof-burrower (find/swarm/attempt) → echidna prove → ledger" +notes = "Consumer of ECHIDNA's echidna.prove.result/1 contract; candidate consumer for echidnabot via `burrower serve`." [related-projects] projects = [ - { name = "standards", relationship = "parent", notes = "Defines the high-level policies and specifications that this template implements." }, - { name = "rhodium-standard-repositories", relationship = "specification", notes = "The formal specification of the RSR standard." }, - { name = "git-rsr-certified", relationship = "tooling", notes = "Universal RSR compliance engine that validates repositories against the standards defined here." }, - { name = "stapeln", relationship = "integration", notes = "Container ecosystem standard integrated into the template." }, - { name = "k9-svc", relationship = "integration", notes = "Self-validating components framework used for deployment and validation." }, + { name = "echidna", relationship = "prover", notes = "Subprocess `echidna prove`; contract echidna.prove.result/1 (docs/ECHIDNA-INTEGRATION.adoc)." }, + { name = "echidnabot", relationship = "planned-consumer", notes = "Could consult `burrower serve` on failed proofs; not wired today." }, + { name = "tropical-types", relationship = "oracle", notes = "tools/julia-oracle.jl, optional pre-proof numerical oracle (caller-supplied path)." }, + { name = "ijson-jcs", relationship = "library", notes = "RFC 8785 / RFC 7493 validation of the prove-result contract (git dependency pinned by rev)." }, + { name = "epistemic-types", relationship = "pattern", notes = "Receipt vs warrant distinction copied as a pattern; no dependency." }, + { name = "standards", relationship = "parent", notes = "RSR policies and reusable CI workflows." }, ] diff --git a/.machine_readable/descriptiles/META.a2ml b/.machine_readable/descriptiles/META.a2ml index b13af5d..d423ca8 100644 --- a/.machine_readable/descriptiles/META.a2ml +++ b/.machine_readable/descriptiles/META.a2ml @@ -1,47 +1,51 @@ # SPDX-License-Identifier: MPL-2.0 -# META.a2ml - Project metadata and architectural decisions for rsr-template-repo +# META.a2ml - Project metadata and architectural decisions for proof-burrower # Template from: https://github.com/hyperpolymath/standards +# NOTE: rsr-template-repo#209 retires this file into the repo deed +# (proof-burrower_chora.deed). That migration is a separate increment because +# the required `openssf-compliance` workflow still reads descriptiles/. # ── Project Identity ─────────────────────────────────────────────────────── [project-meta] -version = "1.0.0" -name = "rsr-template-repo" +version = "0.0.1" +name = "proof-burrower" +purpose = "Find the mathematical home of a proof goal; route it through specialist readers; attempt proofs through ECHIDNA; keep a ledger of what was learned." # ── Architecture Decisions ───────────────────────────────────────────────── [project-meta.architecture-decisions] [project-meta.architecture-decisions.adr-001] status = "accepted" -date = "2026-02-08" -context = "Need a unified metadata standard for all repositories." -decision = "Adopt the 6A2 format family (META, STATE, ECOSYSTEM, PLAYBOOK, AGENTIC, NEUROSYM, ANCHOR) in .machine_readable/ directory." -consequences = "Atomic cross-spec changes and machine-readable project status across the ecosystem." +date = "2026-04-26" +context = "Burrower needs a prover but must not become one." +decision = "Invoke ECHIDNA as a subprocess (`echidna prove`) rather than linking it; Burrower stays prover-agnostic." +consequences = "One process boundary to keep stable; see docs/ECHIDNA-INTEGRATION.adoc." [project-meta.architecture-decisions.adr-002] status = "accepted" -date = "2026-04-05" -context = "STATE.a2ml v1 was too verbose and drifted from reality." -decision = "Migrate to STATE.a2ml v2 (Thin Session Journal) focusing only on phase, last_action, next_action, and blockers." -consequences = "Reduced maintenance overhead and higher data fidelity." +date = "2026-10-05" +context = "Success was inferred from ECHIDNA's human-readable text (string matching)." +decision = "Consume the echidna.prove.result/1 JCS/I-JSON contract when the installed ECHIDNA offers `--output json`; fall back to text markers only when the flag is absent." +consequences = "Successes carry a receipt (prover, axioms, version); legacy successes are recorded as warrants." + +[project-meta.architecture-decisions.adr-003] +status = "accepted" +date = "2026-10-05" +context = "The repository was scaffolded by copying another checkout and carried a vendored affinescript/ tree and a PseudoScript README." +decision = "Remove affinescript/ (no workspace member, build, test or CI step referenced it) and restore proof-burrower's own identity docs." +consequences = "Smaller tree; AffineScript remains available upstream at hyperpolymath/affinescript." # ── Development Practices ────────────────────────────────────────────────── [project-meta.development-practices] -code-style = "rsr" +code-style = "rustfmt + clippy -D warnings" security = "openssf-scorecard" -testing = "comprehensive" +testing = "cargo test --workspace; llvm-cov line floor 95%; live Isabelle e2e via ECHIDNA" versioning = "semver" documentation = "asciidoc" branching = "trunk-based" -# ── Design Rationale ────────────────────────────────────────────────────── -[project-meta.design-rationale] -# The RSR template repo exists to enforce organizational standards by default. -# It prioritizes machine-readability, formal verification (Idris2), and -# memory safety (Zig/Rust) while ensuring a consistent developer experience -# via the Just task runner. - # ── Provenance ───────────────────────────────────────────────────────────── [provenance] -last-modified-by = "gemini" -last-modified-at = "2026-04-23T12:00:00Z" -modification-summary = "Updated to 1.0.0 template standards and recorded ADRs" +last-modified-by = "claude" +last-modified-at = "2026-10-05T00:00:00Z" +modification-summary = "Replaced inherited rsr-template-repo identity with proof-burrower's own" diff --git a/.machine_readable/descriptiles/NEUROSYM.a2ml b/.machine_readable/descriptiles/NEUROSYM.a2ml index 82580ae..58ba688 100644 --- a/.machine_readable/descriptiles/NEUROSYM.a2ml +++ b/.machine_readable/descriptiles/NEUROSYM.a2ml @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 -# NEUROSYM.a2ml — Neurosymbolic integration metadata for rsr-template-repo +# NEUROSYM.a2ml — Neurosymbolic integration metadata for proof-burrower # Template from: https://github.com/hyperpolymath/standards [metadata] diff --git a/.machine_readable/descriptiles/PLAYBOOK.a2ml b/.machine_readable/descriptiles/PLAYBOOK.a2ml index 30cc636..cd29af3 100644 --- a/.machine_readable/descriptiles/PLAYBOOK.a2ml +++ b/.machine_readable/descriptiles/PLAYBOOK.a2ml @@ -1,5 +1,5 @@ # SPDX-License-Identifier: MPL-2.0 -# PLAYBOOK.a2ml — Operational playbook for rsr-template-repo +# PLAYBOOK.a2ml — Operational playbook for proof-burrower # Template from: https://github.com/hyperpolymath/standards [metadata] diff --git a/.machine_readable/descriptiles/STATE.a2ml b/.machine_readable/descriptiles/STATE.a2ml index fb26154..960b057 100644 --- a/.machine_readable/descriptiles/STATE.a2ml +++ b/.machine_readable/descriptiles/STATE.a2ml @@ -1,12 +1,13 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# STATE.a2ml v2 — Thin Session Journal for rsr-template-repo +# STATE.a2ml v2 — Thin Session Journal for proof-burrower # See: standards/a2ml-templates/STATE.a2ml.v2.spec.adoc +# Milestones and blockers: docs/status/ROADMAP.adoc @state(version="2.0"): -phase: "maintenance" -next_action: "Harden multi-forge sync for GitLab/Bitbucket mirroring edge cases" -last_action: "Updated template with 6a2 standards and v2 STATE format" -updated: 2026-04-23T12:00:00Z +phase: "pre-alpha" +next_action: "Bump the proof-safety ECHIDNA pin once echidna ships `prove --output json`, so the live e2e exercises the contract path" +last_action: "First-run start point: identity restored, affinescript/ removed, echidna.prove.result/1 consumer with legacy fallback" +updated: 2026-10-05T00:00:00Z @end diff --git a/.machine_readable/descriptiles/anchor/ANCHOR_2026_05_21.a2ml b/.machine_readable/descriptiles/anchor/ANCHOR_2026_05_21.a2ml index 6301981..8feb713 100644 --- a/.machine_readable/descriptiles/anchor/ANCHOR_2026_05_21.a2ml +++ b/.machine_readable/descriptiles/anchor/ANCHOR_2026_05_21.a2ml @@ -1,8 +1,8 @@ -# ⚓ ANCHOR: rsr-template-repo -# Canonical authority and project recalibration trigger for rsr-template-repo. +# ⚓ ANCHOR: proof-burrower +# Canonical authority and project recalibration trigger for proof-burrower. # Template from: https://github.com/hyperpolymath/standards -id: "org.hyperpolymath.rsr-template-repo" +id: "org.hyperpolymath.proof-burrower" version: "1.0.0" clade: "infrastructure" status: "active" diff --git a/.machine_readable/self-validating/methodology-guard.k9.ncl b/.machine_readable/self-validating/methodology-guard.k9.ncl index 7717a42..ac77cad 100644 --- a/.machine_readable/self-validating/methodology-guard.k9.ncl +++ b/.machine_readable/self-validating/methodology-guard.k9.ncl @@ -50,7 +50,7 @@ let methodology_guard = { description = "STATE.a2ml must not contain template placeholders", severity = "warning", file = ".machine_readable/descriptiles/STATE.a2ml", - reject_patterns = ["{{PLACEHOLDER}}", "Proof Burrower", "rsr-template-repo"], + reject_patterns = ["{{PLACEHOLDER}}", "{{PROJECT_NAME}}", "rsr-template-repo"], }, coverage_updated = { diff --git a/.mise.toml b/.mise.toml deleted file mode 100644 index a352423..0000000 --- a/.mise.toml +++ /dev/null @@ -1,11 +0,0 @@ -[tools] -# = "Uncomment and customize for your project" -# = "rust nightly" -# = "just 1.40.0" -# = "nickel 1.10.0" -# = "gleam 1.8.0" -# = "elixir 1.18.0" -# = "erlang 27.2" -# = "zig 0.14.0" -# = "idris2 0.7.0" -rust = "nightly" diff --git a/ARCHITECTURE.adoc b/ARCHITECTURE.adoc deleted file mode 100644 index 1c0a7a6..0000000 --- a/ARCHITECTURE.adoc +++ /dev/null @@ -1,48 +0,0 @@ -== Architecture - -=== Overview - -This repository follows a modular, maintainable architecture designed -for clarity, scalability, and long-term sustainability. - -=== Directory Structure - -.... -. -├── src/ # Source code -├── tests/ # Test suites -├── docs/ # Documentation -├── scripts/ # Utility scripts -├── config/ # Configuration files -├── LICENSE # License file -├── LICENSES/ # Full license texts -└── README.adoc # Project documentation -.... - -=== Design Principles - -* *Separation of Concerns*: Each module has a single responsibility -* *Testability*: Code is written to be easily testable -* *Documentation*: All public APIs are documented -* *Configuration*: Environment-specific settings are externalized - -=== Dependencies - -* External dependencies are minimized and clearly declared -* Version pinning is used for reproducibility - -=== Security Considerations - -* Sensitive data is never committed to the repository -* Secrets are managed through environment variables or secure vaults -* Regular dependency audits are performed - -=== Maintainability - -* Code follows consistent style guidelines -* Pull requests require review and CI checks -* Issues and discussions are tracked transparently - -''''' - -_Last updated: 2026-07-18_ diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc deleted file mode 100644 index bd2a83c..0000000 --- a/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,24 +0,0 @@ -== Contributor Covenant Code of Conduct - -=== Our Pledge - -We pledge to make participation a harassment-free experience for -everyone. - -=== Our Standards - -*Positive behavior:* * Using welcoming language * Being respectful of -differing viewpoints * Accepting constructive criticism * Focusing on -what is best for the community - -*Unacceptable behavior:* * Harassment, trolling, or personal attacks * -Publishing private information without permission - -=== Enforcement - -Report issues to the maintainers. All complaints will be reviewed. - -=== Attribution - -Adapted from https://www.contributor-covenant.org/[Contributor Covenant] -v2.1. diff --git a/EXPLAINME.adoc b/EXPLAINME.adoc deleted file mode 100644 index 1f6a3cc..0000000 --- a/EXPLAINME.adoc +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= RSR Template Repo - Explainme -:toc: -:icons: font - -This file explains how the key template claims map to real files. - -== Central Session Protocol Authority - -Claim: -Session protocols are centrally maintained and not duplicated in this template. - -How this is implemented: - -* The local dispatcher (`session/dispatch.sh`) maps canonical commands to central - protocol paths in `standards/session-management-standards`. -* Local files (`session/custom-checks.k9`, `session/local-hooks.sh`, - `coordination.k9`) are integration-only. - -Caveat: - -* If `SESSION_STANDARDS_DIR` is unset and no adjacent standards checkout exists, - the dispatcher records the command but cannot resolve central checklist paths. - -== Canonical Command Surface - -Claim: -Template bindings align to one canonical command model. - -How this is implemented: - -* `Justfile` provides thin aliases (`intake-repo`, `checkpoint-change`, - `verify-maintenance`, `verify-substantial`, `verify-release`, `close-planned`, - `close-urgent`, `recover-repo`, `handover-*`). -* Every alias calls `session/dispatch.sh` with canonical verb-object pairs. - -Caveat: - -* Recipes are wrappers only. They do not replace protocol content from - the central standards repo. - -== Runtime State Is Local - -Claim: -Session state is per-repository runtime output, not standards text. - -How this is implemented: - -* `session/dispatch.sh` writes command and continuity-core capture stubs to - `.session/LAST-CANONICAL-COMMAND.md` in the target repository path. - -Caveat: - -* Runtime files are intentionally lightweight and require human/agent completion. - -== Template Token Policy - -Claim: -Placeholders are explicit template content until initialization. - -How this is implemented: - -* `README.adoc` and bootstrap recipes keep `{{TOKEN}}` placeholders visible. -* `just init` performs token replacement. - -Caveat: - -* Uninitialized placeholders must not be treated as project-specific truth. - -== Julia Registry Packages — Standalone Repo Requirement - -Claim: -If this template is used to create a Julia package, it must remain a standalone repository registered with the Julia package registry. - -How this is implemented: - -* Julia's package registry (General.jl or other) expects each package to be a standalone GitHub repository with `Project.toml` at the repository root. -* Installation via `Pkg.add()`, dependency resolution, and automated CI/CD all depend on this canonical structure. - -Caveat: - -* Do NOT move this repository into a monorepo or subdirectory, as this breaks registry registration and package discoverability. -* See `JULIA-REGISTRY-REQUIREMENT.md` (workspace root) for the complete list of 34 registered Julia packages and enforcement rationale. -* Non-registry Julia packages can be organized differently if they are not published to any registry. diff --git a/Justfile b/Justfile index 4b4dfdb..fed0c7a 100644 --- a/Justfile +++ b/Justfile @@ -556,7 +556,7 @@ verify: fi } - check_either "SECURITY.md" "SECURITY.adoc" + check_either ".github/SECURITY.md" "SECURITY.md" check_file "LICENSE" check_either "CONTRIBUTING.md" "CONTRIBUTING.adoc" check_either "README.adoc" "README.md" @@ -1321,7 +1321,7 @@ tour: @echo "2. Available commands: just --list" @echo "" @echo "3. Read README.adoc for full overview" - @echo "4. Read EXPLAINME.adoc for architecture decisions" + @echo "4. Read docs/EXPLAINME.adoc for architecture decisions" @echo "5. Run 'just doctor' to check your setup" @echo "" @echo "Tour complete! Try 'just --list' to see all available commands." diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 2bcd01d..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,43 +0,0 @@ -# Maintainers - -This file lists the current maintainers of this project. - -## Active Maintainers - -| Name | GitHub | Role | Since | -|------|--------|------|-------| -| Jonathan D.A. Jewell | @hyperpolymath | Sole Maintainer | Project Start | - -## Emeritus Maintainers - -None at this time. - -## Becoming a Maintainer - -To become a maintainer: - -1. Demonstrate consistent, high-quality contributions -2. Show understanding of the project's goals and architecture -3. Be active in code reviews and community discussions -4. Be nominated by an existing maintainer -5. Be approved by consensus of existing maintainers - -## Maintainer Responsibilities - -- Reviewing and merging pull requests -- Managing releases -- Triaging issues -- Enforcing code standards -- Mentoring new contributors -- Participating in decision-making - -## Maintainer Expectations - -- Respond to issues and PRs in a timely manner -- Follow the code of conduct -- Be transparent in decision-making -- Communicate clearly and respectfully - ---- - -*Last updated: 2026-07-18* diff --git a/QUICKSTART-DEV.adoc b/QUICKSTART-DEV.adoc deleted file mode 100644 index 930459a..0000000 --- a/QUICKSTART-DEV.adoc +++ /dev/null @@ -1,112 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-DEV.adoc — clone → build → test → PR -// Replace rsr-template-repo, cargo build, cargo test, Rust + typed-wasm with actuals -= rsr-template-repo — Quick Start for Developers -:toc: -:toclevels: 2 - -== Tech Stack - -Rust + typed-wasm - -== Set Up Development Environment - -=== Option A: Guix (preferred) - -[source,bash] ----- -guix shell ----- - -=== Option B: Nix (fallback) - -[source,bash] ----- -nix develop ----- - -=== Option C: Manual - -[source,bash] ----- -git clone https://github.com/hyperpolymath/rsr-template-repo.git -cd rsr-template-repo -just setup-dev ----- - -== Build - -[source,bash] ----- -cargo build ----- - -== Test - -[source,bash] ----- -cargo test ----- - -== Project Structure - -[source] ----- -rsr-template-repo/ -├── src/ # Source code -├── src/abi/ # Idris2 ABI definitions (if applicable) -├── ffi/zig/ # Zig FFI bridge (if applicable) -├── tests/ # Test suite -├── docs/ # Documentation -├── .machine_readable/ # Checkpoint files (STATE, META, ECOSYSTEM) -├── Justfile # Task runner recipes -├── guix.scm # Guix environment -├── flake.nix # Nix environment (fallback) -└── 0-AI-MANIFEST.a2ml # AI agent entry point ----- - -== Key Recipes - -[source,bash] ----- -just build # Build the project -just test # Run tests -just doctor # Self-diagnostic -just lint # Lint and format -just panic-scan # Security scan via panic-attacker -just tour # Guided tour of the codebase ----- - -== Before Submitting a PR - -[source,bash] ----- -just lint # Format and lint -just test # All tests pass -just panic-scan # No new security issues ----- - -== Contractile Invariants - -Read `.machine_readable/MUST.contractile` before making changes. -Key invariants that must never be violated: - -Burrower never attempts proof closure; ECHIDNA does that. All emitted lemma-home suggestions cite at least one existing library lemma. Output is reproducible: same goal + same corpus snapshot = same ranking. - -== LLM/AI Agent Development - -If using an AI assistant, load the warmup context first: - -[source,bash] ----- -just llm-context # Outputs role-appropriate context ----- - -Or read `0-AI-MANIFEST.a2ml` and `.claude/CLAUDE.md` directly. - -== Get Help - -* **Architecture**: link:EXPLAINME.adoc[EXPLAINME.adoc] -* **Wiki**: https://github.com/hyperpolymath/rsr-template-repo/wiki -* **Report issue**: `just help-me` diff --git a/QUICKSTART-MAINTAINER.adoc b/QUICKSTART-MAINTAINER.adoc deleted file mode 100644 index 5e60a5b..0000000 --- a/QUICKSTART-MAINTAINER.adoc +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-MAINTAINER.adoc — packaging, deploying, and maintaining -// Replace rsr-template-repo, proof-burrower, {{DEPS}} with actuals -= rsr-template-repo — Quick Start for Platform Maintainers -:toc: -:toclevels: 2 - -== Overview - -This guide covers packaging, deploying, and maintaining rsr-template-repo for -distribution on your platform. - -== Runtime Dependencies - -{{DEPS}} - -== Build from Source - -[source,bash] ----- -git clone https://github.com/hyperpolymath/rsr-template-repo.git -cd rsr-template-repo -just build-release ----- - -Output: `{{BUILD_OUTPUT_PATH}}` - -== Packaging - -=== Guix - -[source,bash] ----- -guix build -f build/guix.scm ----- - -=== Nix - -[source,bash] ----- -nix build ----- - -=== Container (Stapeln) - -[source,bash] ----- -just stapeln-export # Generates Containerfile -podman build -t rsr-template-repo . ----- - -=== Manual Package - -[source,bash] ----- -just install --prefix=/usr/local ----- - -Files installed: - -[cols="1,2"] -|=== -| Path | Contents - -| `$PREFIX/bin/` -| Executables - -| `$PREFIX/share/proof-burrower/` -| Data files, assets - -| `$PREFIX/share/doc/proof-burrower/` -| Documentation - -| `$PREFIX/share/applications/` -| .desktop file (Linux, if GUI) - -| `$PREFIX/share/man/man1/` -| Man pages -|=== - -== Configuration - -Default config location: `$XDG_CONFIG_HOME/proof-burrower/config.toml` - -Fallback: `$HOME/.config/proof-burrower/config.toml` - -== Health Checks - -[source,bash] ----- -just doctor # Full diagnostic -just run --version # Version check -just run --selftest # Built-in self-test ----- - -== Updating - -[source,bash] ----- -git pull -just build-release -just install --prefix=/usr/local ----- - -Or via OPSM: `opsm update proof-burrower` - -== Security Notes - -* License: MPL-2.0 (Palimpsest License) -* All dependencies SHA-pinned -* `panic-attacker` scan results: link:INSTALL-SECURITY-REPORT.adoc[] -* OpenSSF Scorecard: see badge in README - -== Multi-Instance Deployment - -For deploying multiple instances (e.g., different users or tenants): - -[source,bash] ----- -just install --prefix=/opt/proof-burrower-instance1 --config=/etc/proof-burrower/instance1.toml -just install --prefix=/opt/proof-burrower-instance2 --config=/etc/proof-burrower/instance2.toml ----- - -Each instance has isolated config, data, and logs. - -== Reporting Issues - -* Upstream: https://github.com/hyperpolymath/rsr-template-repo/issues -* With diagnostic: `just help-me` (pre-fills context) diff --git a/QUICKSTART-USER.adoc b/QUICKSTART-USER.adoc deleted file mode 100644 index 0893e63..0000000 --- a/QUICKSTART-USER.adoc +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -// Template: QUICKSTART-USER.adoc — 5-minute path to working software -// Replace rsr-template-repo, Rsr Template Repo — See README.adoc for details., just run, Rsr Template Repo started successfully. with actuals -= rsr-template-repo — Quick Start for Users -:toc: -:toclevels: 2 - -== What is rsr-template-repo? - -Rsr Template Repo — See README.adoc for details. - -== Prerequisites - -Before you begin, ensure you have: - -* **just** — task runner (https://github.com/casey/just[install guide]) -* Platform-specific requirements listed below - -[cols="1,3"] -|=== -| Platform | Additional Requirements - -| Linux -| See README.adoc - -| macOS -| See README.adoc - -| Windows -| See README.adoc -|=== - -== Install - -=== Option 1: Standard Install (recommended) - -[source,bash] ----- -# Clone and set up -git clone https://github.com/hyperpolymath/rsr-template-repo.git -cd rsr-template-repo -just setup ----- - -The setup script will: - -* Detect your platform and shell -* Install missing dependencies (with your permission) -* Configure the application -* Offer install location choices -* Run a self-diagnostic to verify everything works - -=== Option 2: Container (via Stapeln) - -[source,bash] ----- -just stapeln-run ----- - -=== Option 3: Portable (no system changes) - -[source,bash] ----- -just install --portable --prefix=./rsr-template-repo-portable ----- - -== First Run - -[source,bash] ----- -just run ----- - -Expected output: - -[source] ----- -Rsr Template Repo started successfully. ----- - -== Self-Diagnostic - -If something isn't working: - -[source,bash] ----- -just doctor ----- - -This checks all dependencies, permissions, paths, and connectivity. -If it finds issues, it will suggest fixes. - -To attempt automatic repair: - -[source,bash] ----- -just heal ----- - -== Get Help - -* **In-app**: `just run --help` -* **Guided tour**: `just tour` -* **Report a problem**: `just help-me` (pre-fills diagnostic context) -* **Wiki**: https://github.com/hyperpolymath/rsr-template-repo/wiki - -== Uninstall - -[source,bash] ----- -just uninstall ----- - -You will be asked: - -1. Which uninstall tier (Bennett reversible, parameter-based, standard, or secure) -2. Whether to include or exclude your data -3. Whether to clear caches and LLM models - -== Next Steps - -* Read the link:README.adoc[README] for full feature overview -* Read the link:EXPLAINME.adoc[EXPLAINME] for architecture and design decisions -* Try `just tour` for a guided walkthrough diff --git a/README.adoc b/README.adoc index 8e5b9ef..2029ce5 100644 --- a/README.adoc +++ b/README.adoc @@ -1,189 +1,101 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 // Copyright (c) Jonathan D.A. Jewell // SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) -= PseudoScript += Proof Burrower :toc: :toc-placement: preamble -image:https://img.shields.io/badge/OpenSSF-Best_Practices-green?logo=opensourcesecurity[OpenSSF Best Practices,link="https://www.bestpractices.dev/en/projects/new?repo_url=https://github.com/hyperpolymath/proof-burrower"] image:https://img.shields.io/badge/License-MPL_2.0-blue.svg[License: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] -image:https://api.thegreenwebfoundation.org/greencheckimage/github.com[Green Web,link="https://www.thegreenwebfoundation.org/green-web-check/?url=github.com"] - -Pseudocode-syntax AffineScript. -Write code that looks like pseudocode. -Get affine resource guarantees and typed WASM out. - -**Status: alpha** — the syntax, type checker, and WASM output are solid. -Effects runtime is in progress upstream. - ---- - -== What it is - -PseudoScript is https://github.com/hyperpolymath/affinescript[AffineScript] -with its pseudocode face pre-selected. If you write pseudocode, you already know -most of the syntax. - -The compiler checks that your resources (files, sockets, tokens) are used -*exactly as many times as you say* — and proves it at compile time. -No null pointer exceptions. No use-after-free. No silent data races. - -[source,pseudocode] ----- -FUNCTION greet(name: STRING) RETURNS STRING - RETURN "Hello, " + name + "!" -END FUNCTION - -FUNCTION main() RETURNS VOID - LET msg = greet("world") - PRINT(msg) -END FUNCTION ----- - -Save as `hello.pseudo` and run: - -[source,bash] ----- -pseudo eval hello.pseudo ----- +image:https://img.shields.io/badge/OpenSSF-Best_Practices-green?logo=opensourcesecurity[OpenSSF Best Practices,link="https://www.bestpractices.dev/en/projects/new?repo_url=https://github.com/hyperpolymath/proof-burrower"] -== Why Pseudocode syntax? +Find the mathematical home of a proof goal. -Because pseudocode programmers deserve a sound type system. +Given a lemma you are stuck on, Proof Burrower searches theorem-prover +libraries for the place where that goal already lives (or nearly lives), +routes it through a small swarm of specialist "readers", and optionally +*attempts* proofs through https://github.com/hyperpolymath/echidna[ECHIDNA], +recording every attempt in an append-only ledger so the next run starts +from what the last one learned. -PseudoScript is pseudocode's more dangerous cousin — same comfortable -whitespace-delimited blocks, but with teeth: the compiler tracks *ownership* -of every value so you cannot accidentally drop, duplicate, or outlive a -resource. You never null-check. You never wonder if a function consumes its -argument or borrows it. The type says so. +ECHIDNA is the prover. Burrower is the librarian and the lab notebook. -The mascot is a pseudocode. +**Status: pre-alpha (v0.0.1).** The Rust engine, CLI, ledger, socket +service and WASM shim are implemented and tested; the CLI surface may +still change. See `docs/status/ROADMAP.adoc`. -== Surface mappings +== What it does -[cols="1,1"] -|=== -|Pseudocode surface |What it means in PseudoScript - -|`FUNCTION f(x: T) RETURNS R:` |function declaration -|`TRUE` / `FALSE` |`true` / `false` -|`NULL` |unit `()` — not null, genuinely nothing -|`AND` / `OR` / `NOT` |`&&` / `||` / `!` -|`CLASS Name:` |`type Name` (algebraic data type) -|`PASS` |`()` (unit expression) -|`IMPORT a.b` |`use a::b;` -|`FROM a IMPORT b` |`use a::b;` -|`IF cond:` / `ELSE:` |`if cond { ... } else { ... }` -|`ELIF cond:` |`} else if cond {` -|`FOR x IN e:` |`for x in e { ... }` -|`MATCH e:` |`match e { ... }` -|`# comment` |`// comment` +[cols="1,3"] |=== +|Command |What it does -Run `pseudo preview-pseudocode-transform ` to see the canonical -AffineScript that the preprocessor generates from any `.pseudo` file. +|`burrower index --output idx.json` +|Walk a library tree (`.thy`, `.v`, `.lean`), extract lemma signatures, +build an inverted index. -== Getting started +|`burrower find "" --index idx.json` +|Rank candidate homes for one goal (Jaccard over signature tokens). -=== Prerequisites +|`burrower swarm "" --index idx.json [--ledger l.jsonl]` +|Route the goal through the specialists (Algebraist, Combinatorialist, +Order-Theorist): each scores relevance, reads the goal in its own +vocabulary, and returns homes; synthesis reports consensus homes and +boundary objects. -* https://ocaml.org/[OCaml] + https://dune.build/[dune] (for the compiler) -* https://www.rust-lang.org/[Rust] + cargo (for the `pseudo` wrapper) -* https://just.systems/[just] (task runner) +|`burrower attempt "" --ledger l.jsonl [--echidna echidna]` +|Run every engaged specialist's tactic playbook through +`echidna prove --prover Isabelle`, and record each outcome in the ledger. -=== Install from source +|`burrower ledger recent\|by-specialist\|anti-patterns\|digest` +|Read the ledger back. -[source,bash] ----- -git clone --recurse-submodules https://github.com/hyperpolymath/pseudoscript -cd pseudoscript +|`burrower serve --socket /tmp/burrower.sock` +|Expose `swarm` / `attempt` / `ledger` over a Unix socket +(line-delimited JSON) for ECHIDNA and echidnabot. +|=== -# Build affinescript compiler + pseudo wrapper in one step -just bootstrap +== Talking to ECHIDNA -# Optional: install pseudo to ~/.cargo/bin -just install ----- +`burrower attempt` invokes `echidna prove`. When the installed echidna +supports `--output json`, Burrower reads the `echidna.prove.result/1` +object it prints (one JCS-canonical I-JSON line) and records a +*receipt*: the prover, the axioms the proof rests on, and echidna's +version. With an older echidna that lacks the flag, Burrower falls back +to the legacy text markers and records the success as a *warrant* (no +receipt). The contract, detection rule and failure mapping are in +`docs/ECHIDNA-INTEGRATION.adoc`. -=== Usage +== Quick start [source,bash] ---- -pseudo check hello.pseudo # type-check -pseudo eval hello.pseudo # run -pseudo compile hello.pseudo # compile to WASM -pseudo fmt hello.pseudo # format -pseudo lint hello.pseudo # static analysis - -# See what the pseudocode preprocessor produces -pseudo preview-pseudocode-transform hello.pseudo +cargo build --release +./target/release/burrower index /path/to/HOL/Library --output idx.json +./target/release/burrower swarm 'lemma foo: "a + b = b + (a::nat)"' --index idx.json +# needs echidna (and Isabelle) on PATH: +./target/release/burrower attempt 'lemma foo: "a + b = b + (a::nat)"' --ledger burrow.jsonl ---- -`.pseudo` files are detected automatically — no `--face` flag needed. -You can also use `.pyaff` if you prefer the AffineScript naming. - -== Ownership in one minute - -[source,pseudocode] ----- -# @linear means: use this exactly once. -FUNCTION send_token(@linear token: AuthToken) RETURNS Receipt: - network.submit(token) # consumes token — compiler verifies this -END FUNCTION - -# Option instead of None crashes. -FUNCTION safe_divide(a: Int, b: Int) RETURNS Option[Int]: - IF b == 0: - NONE - ELSE: - SOME(a / b) - END IF -END FUNCTION - -FUNCTION show(result: Option[Int]) RETURNS VOID: - MATCH result: - SOME(n) => IO.println(Int.to_string(n)) - NONE => IO.println("no result") - END MATCH -END FUNCTION ----- - -The compiler rejects any program that drops a `@linear` value unused, -uses it more than once, or tries to treat `NONE` as a value. - -== Relationship to AffineScript +More: `docs/QUICKSTART.adoc`. -PseudoScript *is* AffineScript. Same compiler, same type system, same WASM -output. The difference is `--face pseudocode` is the default and the entry -point is `pseudo` instead of `affinescript`. +== Layout -The affinescript compiler lives in the `affinescript/` submodule of this -repo. `just update-affinescript` bumps the pointer to the latest upstream -release — all language improvements flow through automatically. - -Face logic: `affinescript/lib/pseudocode_face.ml` (syntax transform) and -`affinescript/lib/face.ml` (error vocabulary). No compiler internals change -when the face changes. - -== Alpha caveats - -* **Effects runtime**: `effect`/`handle` syntax is type-checked but not yet -executed at runtime. The effects story is coming in a near-upstream release. -* **Closures and linear captures**: lambda-body linear capture tracking is -conservative in the current release. -* **Span fidelity**: error locations refer to the transformed canonical source, -not the original `.pseudo` line numbers. Source-map remapping is planned. - -== Contributing - -This repo is a thin distribution layer. Language bugs and features go -upstream: https://github.com/hyperpolymath/affinescript[hyperpolymath/affinescript]. - -PseudoScript-specific contributions (branding, examples, tutorials, the -`pseudo` CLI wrapper) are welcome here. - -== License +[cols="1,3"] +|=== +|Path |Contents + +|`crates/burrower-core` |Engine: goal parsing, corpus index, ranking, +specialists, attempts, the echidna contract, ledger, socket service, +oracle bridge. +|`crates/burrower-cli` |The `burrower` binary. +|`crates/burrower-wasm` |C-ABI WASM shim for ECHIDNA's typed-wasm oracle. +|`agents/*.007` |Specialist definitions in the 007 agent DSL. +|`src/interface/` |Idris2 ABI + Zig FFI layer (template scaffold; see +`docs/status/PROOF-STATUS.adoc` for what is and is not proved). +|`docs/` |Architecture, integration, design notes, status. +|`tests/e2e.sh` |Live end-to-end check against real Isabelle via echidna. +|=== -This project is licensed under the Mozilla Public License, v. 2.0. See the `LICENSE` file for details. +== Licence -SPDX-License-Identifier: CC-BY-SA-4.0 \ No newline at end of file +Code: MPL-2.0. Documentation: CC-BY-SA-4.0. See `LICENSE` and `LICENSES/`. diff --git a/ROADMAP.adoc b/ROADMAP.adoc deleted file mode 100644 index bafe0ce..0000000 --- a/ROADMAP.adoc +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) Jonathan D.A. Jewell -= YOUR Template Repo Roadmap - -== Current Status - -Initial development phase. - -== Milestones - -=== v0.1.0 - Foundation -* [ ] Core functionality -* [ ] Basic documentation -* [ ] CI/CD pipeline - -=== v1.0.0 - Stable Release -* [ ] Full feature set -* [ ] Comprehensive tests -* [ ] Production ready - -== Future Directions - -_To be determined based on community feedback._ diff --git a/SECURITY.adoc b/SECURITY.adoc deleted file mode 100644 index e47fdd9..0000000 --- a/SECURITY.adoc +++ /dev/null @@ -1,16 +0,0 @@ -== Security Policy - -=== Reporting a Vulnerability - -If you discover a security vulnerability, please report it responsibly. - -*Email:* j.d.a.jewell@open.ac.uk - -*Please include:* - Description of the vulnerability - Steps to -reproduce - Potential impact - -*Response timeline:* - Acknowledgement within 48 hours - Initial -assessment within 7 days - Fix or mitigation within 90 days - -*Safe harbour:* We will not pursue legal action against security -researchers who follow responsible disclosure. diff --git a/TEST-NEEDS.adoc b/TEST-NEEDS.adoc deleted file mode 100644 index dc03547..0000000 --- a/TEST-NEEDS.adoc +++ /dev/null @@ -1,138 +0,0 @@ -== TEST-NEEDS: rsr-template-repo - -=== CRG Grade: C — ACHIEVED 2026-04-04 - -=== Current State (Updated 2026-04-04) - -[width="100%",cols="40%,26%,34%",options="header",] -|=== -|Category |Count |Details -|*Source modules* |6 |3 Idris2 ABI (Foreign, Layout, Types), 2 Zig FFI -(build, main), 1 Zig integration test template - -|*Unit tests* |0 |None in main source (inline tests in main.zig) - -|*Integration tests* |1 |test/integration_test.zig (documented template, -1 placeholder test) - -|*E2E tests* |1 |tests/e2e/template_instantiation_test.sh (full -instantiation + validation) - -|*Workflow tests* |1 |tests/workflows/validate_workflows_test.sh (21 -workflows validated) - -|*Validation tests* |1 |scripts/validate-template.sh (8-phase -comprehensive validation) - -|*Benchmarks* |5 |benches/template_bench.sh (validation, Zig build, -tests, workflows, instantiation) - -|*Fuzz tests* |0 |README.adoc scaffold with harness instructions -|=== - -=== Completed Work (CRG C - Testing & Benchmarking) - -==== Template Validation Script ✅ - -* [x] `+scripts/validate-template.sh+` — 8-phase validation -** Phase 1: Core repository structure (root files, directories) -** Phase 2: Machine-readable metadata (.machine_readable/) -** Phase 3: GitHub Actions workflows (17 required + all present) -** Phase 4: Idris2 ABI and Zig FFI source files -** Phase 5: Placeholder token replacement (skipped in template) -** Phase 6: SPDX license headers (100% coverage, 6/6 files) -** Phase 7: Build system verification (zig build + idris2 syntax check) -** Phase 8: Documentation requirements (TOPOLOGY, ABI-FFI-README, etc) -* Status: *PASSING* (0 errors, 3 warnings about template placeholders) - -==== E2E Template Instantiation Test ✅ - -* [x] `+tests/e2e/template_instantiation_test.sh+` — full workflow -** Clones template to temp directory -** Replaces all \{\{PLACEHOLDER}} tokens with test values -** Validates resulting structure with scripts/validate-template.sh -** Verifies Zig build works after instantiation -** Checks no remaining placeholders -** Cleans up temp directory -* Status: *READY TO TEST* (can be verified by CI) - -==== Workflow Validation Test ✅ - -* [x] `+tests/workflows/validate_workflows_test.sh+` -** Validates all 21 workflows exist and have proper structure -** Checks SPDX headers, '`name`' field -** Verifies all 15 required workflows present -* Status: *PASSING* (0 errors, 15/15 required workflows found) - -==== Zig FFI Tests ✅ - -* [x] `+src/interface/ffi/test/integration_test.zig+` — template with -examples -** Converted from \{\{project}} placeholders to "`template`" namespace -** Added comprehensive comments for how to instantiate -** Tests grouped by category (lifecycle, operations, strings, errors, -version, memory safety, threading) -** Compiles and passes placeholder test -* Status: *PASSING* (1 test: placeholder_test_implementation_required -passes) - -==== Benchmarks ✅ - -* [x] `+benches/template_bench.sh+` — 5 benchmark suites -** Validation script: ~5.8s average (3 runs) -** Zig build: ~19ms (clean build) -** Zig tests: ~20ms -** Workflow validation: ~117ms -** Template instantiation: ~427ms -* Formats: human, json, csv -* Status: *PASSING* (all benchmarks execute) - -==== Build System ✅ - -* [x] `+src/interface/ffi/build.zig+` — updated for Zig 0.15.2 -** Simplified to test-only configuration -** Supports both unit tests and integration tests -** Works with `+zig build+` without errors -* Status: *PASSING* (builds successfully) - -=== Test Results Summary - -.... -Validation Script: PASS (0 errors, 3 warnings) -Workflow Validation: PASS (21/21 workflows valid) -Integration Tests: PASS (1/1 placeholder test) -E2E Instantiation: READY (needs CI confirmation) -Benchmarks: PASS (5/5 benchmark suites) -Build System: PASS (zig build succeeds) -.... - -=== CRG C Compliance - -* *Coverage*: 6/6 test categories (unit, integration, E2E, workflow, -validation, benchmarks) -* *Documentation*: All test files have SPDX headers + inline -documentation -* *Author Attribution*: Jonathan D.A. Jewell -6759885+hyperpolymath@users.noreply.github.com -* *License*: MPL-2.0 on all new files -* *Automation*: All scripts executable + working - -=== FLAGGED ISSUES - ALL RESOLVED - -* [line-through]#*Template repo used by ALL new repos has 0 validation -tests*# → FIXED: 4 test suites + validation script -* [line-through]#*fuzz/placeholder.txt*# → FIXED: replaced with -README.adoc containing real harness instructions -* [line-through]#*No E2E tests for template instantiation*# → FIXED: -full E2E test suite -* [line-through]#*Zig FFI integration tests are placeholders*# → FIXED: -converted to documented template format - -=== Next Steps (Future Sessions) - -* [ ] Integrate test scripts into CI/CD workflows -* [ ] Generate test coverage reports -* [ ] Add more specialized benchmarks (memory, threading stress) -* [ ] Document test instantiation patterns for new repos - -=== Priority: P0 (COMPLETE) ✅ diff --git a/TOPOLOGY.adoc b/TOPOLOGY.adoc deleted file mode 100644 index a975f58..0000000 --- a/TOPOLOGY.adoc +++ /dev/null @@ -1,36 +0,0 @@ -== Architecture Topology - -=== System Overview - -RSR (Rhodium Standard Repository) template provides the canonical -scaffold for all hyperpolymath projects, with integrated CI/CD, -documentation, and service discovery patterns. - -=== Component Overview - -[width="100%",cols="37%,33%,30%",options="header",] -|=== -|Component |Language |Purpose -|dogfood-gate workflow |YAML |Quality checks (CRG, security, linting) -|eclexiaiser-validate job |YAML |Resource cost awareness scoring -|Groove discovery |JSON |Service endpoint registration -|=== - -=== Data Flow - -.... -[Code Push] → [GitHub Actions] → [hypatia scan] → [eclexiaiser validate] → [Results] -.... - -=== Integration Points - -* *Upstream*: Hypatia (neurosymbolic CI/CD), eclexiaiser (resource -scoring) -* *Downstream*: All RSR-based repositories (500+ instances) - -=== Deployment - -* Container: Stapeln Six ecosystem -* CI/CD: GitHub Actions → Hypatia scan → eclexiaiser-validate (6 -scorecard dimensions) → Mirror -* Service Discovery: Groove protocol (.well-known/groove/manifest.json) diff --git a/agents/algebraist.007 b/agents/algebraist.007 index 1bd6272..7614672 100644 --- a/agents/algebraist.007 +++ b/agents/algebraist.007 @@ -55,7 +55,7 @@ ], -- Always-included local corpora. local: [ - "verification-ecosystem/tropical-resource-typing" + "tropical-types" ] } diff --git a/agents/combinatorialist.007 b/agents/combinatorialist.007 index d177022..f0d5d72 100644 --- a/agents/combinatorialist.007 +++ b/agents/combinatorialist.007 @@ -35,7 +35,7 @@ "mathcomp/finset" ], local: [ - "verification-ecosystem/tropical-resource-typing" + "tropical-types" ] } diff --git a/agents/order_theorist.007 b/agents/order_theorist.007 index 853e1ed..99d6254 100644 --- a/agents/order_theorist.007 +++ b/agents/order_theorist.007 @@ -35,7 +35,7 @@ "mathcomp/order" ], local: [ - "verification-ecosystem/tropical-resource-typing" + "tropical-types" ] } diff --git a/audits/assail-classifications.a2ml b/audits/assail-classifications.a2ml index 1b3e468..fde0416 100644 --- a/audits/assail-classifications.a2ml +++ b/audits/assail-classifications.a2ml @@ -2,23 +2,7 @@ # SPDX-License-Identifier: MPL-2.0 project = "proof-burrower" schema_version = "1.0" -- suppression: - file: 'affinescript/tools/affine-doc/assets/search.js' - rule: 'DynamicCodeExecution' - justification: 'Static HTML template rendering for documentation search. Inputs are statically generated and sanitized.' - suppression: file: 'crates/burrower-core/src/corpus.rs' rule: 'UnboundedAllocation' justification: 'Reads bounded corpus data files controlled by local project scope.' -- suppression: - file: 'affinescript/tools/affine-pkg/src/lockfile.rs' - rule: 'UnboundedAllocation' - justification: 'Reads local lockfiles; size bounded by project size.' -- suppression: - file: 'affinescript/tools/affine-pkg/src/manifest.rs' - rule: 'UnboundedAllocation' - justification: 'Reads local manifest files; size bounded by project size.' -- suppression: - file: 'affinescript/tools/affine-pkg/src/config.rs' - rule: 'UnboundedAllocation' - justification: 'Reads local config files; size bounded by project size.' diff --git a/docs/ARCHITECTURE.adoc b/docs/ARCHITECTURE.adoc new file mode 100644 index 0000000..52a9e0b --- /dev/null +++ b/docs/ARCHITECTURE.adoc @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Proof Burrower — Architecture +:toc: + +== Overview + +Proof Burrower is a Rust workspace with three crates and one external +process boundary (ECHIDNA). It never proves anything itself: it finds +where a goal belongs, decides what is worth trying, asks ECHIDNA to try +it, and keeps a ledger of the results. + +[source] +---- +goal text ──> goal::parse_goal ──> Goal{tokens, identifiers} + │ + corpus::Corpus::index <─────┤ (library tree → inverted index) + ▼ + ranking::rank / specialist::Swarm + (Jaccard homes; per-specialist readings; synthesis) + │ + [optional] oracle::run ── julia tools/julia-oracle.jl + │ (tropical-types; may veto) + ▼ + attempt::run_playbook ── echidna prove ... [--output json] + │ parsed by echidna_contract + ▼ + ledger::Ledger (append-only JSONL) +---- + +== Crates + +[cols="1,3"] +|=== +|Crate |Responsibility + +|`burrower-core` +|All logic. Modules: `goal`, `corpus`, `ranking`, `specialist` (swarm), +`attempt` (probe generation, echidna invocation, outcome mapping), +`echidna_contract` (`echidna.prove.result/1` parser and capability +detection), `oracle` (pre-proof numerical oracle), `ledger`, `serve` +(Unix-socket service, thread per connection). + +|`burrower-cli` +|`burrower` binary: thin clap front end over `burrower-core`. + +|`burrower-wasm` +|C-ABI shim (`*const u8` + length in, caller buffer out) so the engine +can be shipped as a `.wasm` artefact for ECHIDNA's typed-wasm oracle. +|=== + +== Process boundaries + +* **ECHIDNA** — subprocess `echidna prove --prover Isabelle -t N + [--project-root P] [--sandbox M] [--output json]`. Capability is detected + once per binary from `echidna prove --help`. See + `ECHIDNA-INTEGRATION.adoc`. +* **Julia oracle** — subprocess `julia [--project=P] tools/julia-oracle.jl + ` from `hyperpolymath/tropical-types`; one verdict line + on stdout. +* **Socket clients** — `burrower serve` speaks line-delimited JSON + `{cmd, args}` → `{ok, result|error}`. + +== Identifiers and persistence + +* The ledger is JSON-lines on local disk, single writer. +* `ledger::new_id` is the only identifier-minting function (time + process + sequence; not a UUID). `ledger::goal_hash` keys records by goal. +* Probe files live in a private per-attempt temporary directory that is + removed after the child exits. + +== Trust model + +A success parsed from `echidna.prove.result/1` carries a `ProofReceipt` +(prover, axioms, confidence, echidna version) and is recorded as such. A +success inferred from legacy text markers carries none and the ledger says +so. An oracle `agree` verdict is numerical evidence, not a proof. + +== Out of scope here + +The Idris2 ABI / Zig FFI under `src/interface/` is the RSR template layer +and is not yet connected to the Rust crates. See `status/PROOF-STATUS.adoc`. diff --git a/AUDIT.adoc b/docs/AUDIT.adoc similarity index 100% rename from AUDIT.adoc rename to docs/AUDIT.adoc diff --git a/docs/EXPLAINME.adoc b/docs/EXPLAINME.adoc new file mode 100644 index 0000000..8971c12 --- /dev/null +++ b/docs/EXPLAINME.adoc @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Proof Burrower - Explainme +:toc: +:icons: font + +This file maps each claim in `README.adoc` to the code that backs it, and +says how far each claim goes (implemented, tested, wired, proved). + +== "Finds the mathematical home of a goal" + +Implemented and unit-tested: `crates/burrower-core/src/{goal,corpus,ranking}.rs`. +Ranking is Jaccard similarity over signature tokens; it is a relevance +heuristic, not a semantic match. + +== "Routes the goal through specialists" + +Implemented and unit-tested: `crates/burrower-core/src/specialist.rs`; +specialist definitions in `agents/*.007`. Three specialists today +(Algebraist, Combinatorialist, Order-Theorist). + +== "Attempts proofs through ECHIDNA" + +Implemented, unit-tested with fake binaries, and wired end to end against +real Isabelle in CI (`tests/e2e.sh`, workflow `Burrower proof safety`): +`crates/burrower-core/src/attempt.rs`. Isabelle only. + +== "Reads ECHIDNA's result as a receipt" + +Implemented and unit-tested: `crates/burrower-core/src/echidna_contract.rs`. +Live coverage waits on the pinned echidna gaining `prove --output json`; +until then CI exercises the legacy fallback. Contract: +`docs/ECHIDNA-INTEGRATION.adoc`. + +== "Keeps a ledger" + +Implemented and unit-tested: `crates/burrower-core/src/ledger.rs`. +Single writer, no locking; ids from `ledger::new_id` are time + process +sequence, not UUIDs. + +== What is not claimed + +* Nothing in Burrower is formally proved. `verification/` and + `src/interface/` are RSR template scaffolding; see + `docs/status/PROOF-STATUS.adoc`. +* An oracle `agree` verdict is numerical evidence, not a proof. diff --git a/GOVERNANCE.adoc b/docs/GOVERNANCE.adoc similarity index 100% rename from GOVERNANCE.adoc rename to docs/GOVERNANCE.adoc diff --git a/MAINTAINERS.adoc b/docs/MAINTAINERS.adoc similarity index 94% rename from MAINTAINERS.adoc rename to docs/MAINTAINERS.adoc index daf8a78..b16f709 100644 --- a/MAINTAINERS.adoc +++ b/docs/MAINTAINERS.adoc @@ -61,5 +61,5 @@ For questions about project governance: == See Also * link:GOVERNANCE.adoc[Governance Model] -* link:CODE_OF_CONDUCT.md[Code of Conduct] -* link:CONTRIBUTING.adoc[Contributing Guide] +* link:../.github/CODE_OF_CONDUCT.md[Code of Conduct] +* link:../CONTRIBUTING.adoc[Contributing Guide] diff --git a/docs/attribution/CODEOWNERS.adoc b/docs/attribution/CODEOWNERS.adoc index dd3dabc..566031f 100644 --- a/docs/attribution/CODEOWNERS.adoc +++ b/docs/attribution/CODEOWNERS.adoc @@ -18,4 +18,4 @@ The technical rules for automatic review assignments are maintained in the machi == Component Owners -A full list of maintainers and their contact information can be found in link:MAINTAINERS.adoc[MAINTAINERS.adoc]. +A full list of maintainers and their contact information can be found in link:../MAINTAINERS.adoc[docs/MAINTAINERS.adoc]. diff --git a/TEMPLATE-STANDARDS-AUDIT.adoc b/docs/reports/audit/2026-04-07-template-standards-audit.adoc similarity index 100% rename from TEMPLATE-STANDARDS-AUDIT.adoc rename to docs/reports/audit/2026-04-07-template-standards-audit.adoc diff --git a/PROOF-NEEDS.adoc b/docs/status/PROOF-NEEDS.adoc similarity index 100% rename from PROOF-NEEDS.adoc rename to docs/status/PROOF-NEEDS.adoc diff --git a/PROOF-STATUS.adoc b/docs/status/PROOF-STATUS.adoc similarity index 100% rename from PROOF-STATUS.adoc rename to docs/status/PROOF-STATUS.adoc diff --git a/READINESS.adoc b/docs/status/READINESS.adoc similarity index 100% rename from READINESS.adoc rename to docs/status/READINESS.adoc diff --git a/docs/status/ROADMAP.adoc b/docs/status/ROADMAP.adoc new file mode 100644 index 0000000..e06024b --- /dev/null +++ b/docs/status/ROADMAP.adoc @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Proof Burrower Roadmap + +== Current status (2026-10-05) + +Pre-alpha, v0.0.1. Implemented and tested: goal parsing, corpus index and +Jaccard ranking, the three-specialist swarm, Isabelle attempt playbooks via +ECHIDNA, the `echidna.prove.result/1` consumer with legacy fallback, the +JSONL ledger, `burrower serve` (Unix socket), the tropical-types oracle +bridge, and the WASM shim. CI: `rust-ci` (fmt, clippy, test, audit, +llvm-cov 95% floor) and `Burrower proof safety` (live Isabelle e2e). + +Not done: Lean/Coq dispatch, ledger locking, ISO timestamps, UUIDv7 ids, +any proof about Burrower itself (see `PROOF-STATUS.adoc`). + +== Milestones + +=== v0.1.0 — usable librarian +* [x] `index` / `find` / `swarm` / `attempt` / `ledger` / `serve` +* [x] Contract-based result parsing (`echidna.prove.result/1`) +* [ ] Bump the proof-safety ECHIDNA pin to a contract-speaking echidna +* [ ] Stable goal hash (today `DefaultHasher`, which Rust does not promise to keep stable) +* [ ] ISO 8601 timestamps and UUIDv7 ledger ids (`ledger::new_id` is the single site) +* [ ] Repo deed (`proof-burrower_chora.deed`) replacing STATE/META/ECOSYSTEM/AGENTIC/CLADE (rsr-template-repo#209); needs `openssf-compliance.yml` updated in the same change + +=== v0.2.0 — wider reach +* [ ] Lean and Coq dispatch in `attempt` +* [ ] Optional retrieval through `echidna corpus near` (see `docs/ECHIDNA-INTEGRATION.adoc` §corpus overlap) +* [ ] echidnabot consumes `burrower serve` (EBI-1) + +== Blockers + +* echidna `prove --output json` must land upstream before the live e2e can exercise the contract path. diff --git a/docs/status/TEST-NEEDS.adoc b/docs/status/TEST-NEEDS.adoc new file mode 100644 index 0000000..2b5e1f9 --- /dev/null +++ b/docs/status/TEST-NEEDS.adoc @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += TEST-NEEDS: proof-burrower + +== Current state (2026-10-05) + +[cols="1,1,3",options="header"] +|=== +|Suite |Where |What it covers + +|Unit |`crates/*/src/**` `#[cfg(test)]` +|Goal parsing, statement extraction, ranking, specialists, ledger, +socket protocol, oracle verdict parsing, the `echidna.prove.result/1` +parser (including planted non-canonical mutants), contract and legacy +attempt paths against fake echidna binaries. + +|Process contract |`crates/burrower-core/tests/process_contract.rs` +|Isolation of concurrent probes, argument forwarding, fail-closed +behaviour, oracle transport. + +|CLI contract |`crates/burrower-cli/tests/cli_contract.rs` +|CLI argument and output shape. + +|Live |`tests/e2e.sh`, `crates/burrower-core/tests/live_isabelle.rs` +|Real Isabelle accept/reject through a pinned echidna (CI: `Burrower proof safety`). + +|Coverage |`rust-ci / llvm-cov line coverage` +|Workspace line coverage floor 95%. +|=== + +== Gaps + +* The live e2e runs against an echidna that predates `--output json`, so + the contract path is covered only by fake binaries until the pin moves. +* No fuzzing of `extract_statement` or the socket protocol. +* `burrower-wasm` is tested natively; the `.wasm` artefact itself is not + exercised in CI. +* The template shell scripts under `tests/` (`aspect_tests.sh`, + `tests/e2e/template_instantiation_test.sh`, + `tests/workflows/validate_workflows_test.sh`) test the RSR template, not + Burrower. diff --git a/docs/wikis/ECHIDNA-Contract.md b/docs/wikis/ECHIDNA-Contract.md new file mode 100644 index 0000000..d0d4c0d --- /dev/null +++ b/docs/wikis/ECHIDNA-Contract.md @@ -0,0 +1,28 @@ + + +# ECHIDNA Contract: `echidna.prove.result/1` + +`echidna prove ... --output json` prints exactly one JCS-canonical (RFC 8785) +I-JSON (RFC 7493) object on stdout: + +```json +{"duration_ms":12,"echidna_version":"2.4.0","goal":"probe.thy","message":"","prover":"Isabelle","schema":"echidna.prove.result/1","status":"verified","trust":{"axioms":[],"confidence":null}} +``` + +`status` is one of `verified`, `failed`, `error`, `timeout`, `unknown`. + +## How Burrower uses it + +1. Once per echidna binary, Burrower runs `echidna prove --help`. If `--output` + is listed it uses the contract; otherwise it uses the old text markers. +2. In contract mode the line must be I-JSON, byte-identical to its JCS form, + and carry every field. Anything else is a contract violation and the + attempt fails. It is never re-read as text. +3. `verified` with exit 0 becomes a success with a **receipt** (prover, + axioms, confidence, echidna version), stored in the ledger record's + `extra.receipt`. `failed`/`unknown` fail, `timeout` times out, `error` is + skipped (an infrastructure problem, not a lesson). +4. With an older echidna, a success is recorded as a **warrant**: evidence + that echidna printed "verified", without the receipt. + +The full specification is `docs/ECHIDNA-INTEGRATION.adoc` in the repository. diff --git a/docs/wikis/Home.md b/docs/wikis/Home.md new file mode 100644 index 0000000..3c8842d --- /dev/null +++ b/docs/wikis/Home.md @@ -0,0 +1,24 @@ + + +# Proof Burrower + +Find the mathematical home of a proof goal. + +Proof Burrower searches theorem-prover libraries for the place a stuck lemma +already lives (or nearly lives), reads the goal through a small swarm of +specialists, and can attempt proofs through +[ECHIDNA](https://github.com/hyperpolymath/echidna). Every attempt goes into an +append-only ledger, so the next run starts from what the last one learned. + +ECHIDNA is the prover. Burrower is the librarian and the lab notebook. + +**Status:** pre-alpha (v0.0.1). + +## Pages + +* [Using Proof Burrower](Using-Proof-Burrower) — install, index, find, swarm, attempt +* [ECHIDNA Contract](ECHIDNA-Contract) — how Burrower reads `echidna prove` results +* [Maintaining](Maintaining) — CI, releases, wiki sync + +The source of this wiki lives in the repository under `docs/wikis/` and is +pushed here with `scripts/sync-wiki.sh`. Edit it there, not here. diff --git a/docs/wikis/Maintaining.md b/docs/wikis/Maintaining.md new file mode 100644 index 0000000..9553bdf --- /dev/null +++ b/docs/wikis/Maintaining.md @@ -0,0 +1,32 @@ + + +# Maintaining Proof Burrower + +## Checks that gate `main` + +* `rust-ci / Cargo check + clippy + fmt`, `rust-ci / Cargo test`, + `rust-ci / Cargo audit (security)`, `rust-ci / llvm-cov line coverage` + (workspace line floor 95%) +* `Burrower proof safety` — builds a pinned echidna and runs `tests/e2e.sh` + against real Isabelle 2025-2 +* secret scans (`scan / *`), CodeQL (`analyze (actions, none)`), + `openssf-compliance`, `Build Ddraig Pages artifact` + +Merge form is squash. + +## Moving the echidna pin + +`.github/workflows/proof-safety.yml` checks out echidna at a fixed SHA. Once +echidna ships `prove --output json`, move the pin so the live test exercises +the contract path, and confirm `tests/e2e.sh` still passes. + +## Wiki + +Source: `docs/wikis/*.md` (except `README.md`). Push with: + +```sh +scripts/sync-wiki.sh /path/to/proof-burrower.wiki # a clone of the .wiki.git repo +``` + +The script copies the pages, commits, and prints the push command; it never +pushes by itself. diff --git a/docs/wikis/README.md b/docs/wikis/README.md index 8ac6e45..3a35d62 100644 --- a/docs/wikis/README.md +++ b/docs/wikis/README.md @@ -2,14 +2,6 @@ # Project Wikis -This directory contains the source files for the project wiki. It is intended for long-form documentation, deep-dives, and community-maintained knowledge. - -## Structure - -* **Core Concepts:** Fundamental architectural ideas. -* **Workflows:** Step-by-step guides for contributors. -* **Glossary:** Definitions of project-specific terminology. - -## Wiki Synchronization - -Changes made here should be synchronised with the forge-hosted wiki (GitHub/GitLab) using the project's sync scripts. +Source for the forge wiki at . +Every `*.md` here except this README is a wiki page (`Home.md` is the landing +page). Edit pages here, then push them with `scripts/sync-wiki.sh`. diff --git a/docs/wikis/Using-Proof-Burrower.md b/docs/wikis/Using-Proof-Burrower.md new file mode 100644 index 0000000..525397f --- /dev/null +++ b/docs/wikis/Using-Proof-Burrower.md @@ -0,0 +1,65 @@ + + +# Using Proof Burrower + +## Build + +```sh +cargo build --release # needs Rust stable (rust-toolchain.toml) +``` + +The binary is `target/release/burrower`. + +## Index a library + +```sh +burrower index /path/to/HOL/Library --output idx.json +``` + +Walks `.thy`, `.v` and `.lean` files and stores lemma signatures. + +## Find a home for one goal + +```sh +burrower find 'lemma foo: "a + b = b + (a::nat)"' --index idx.json --top 5 +``` + +## Ask the swarm + +```sh +burrower swarm 'lemma foo: "a + b = b + (a::nat)"' --index idx.json --ledger burrow.jsonl +``` + +Each specialist scores its relevance, gives a reading in its own vocabulary, +and proposes homes. The synthesis lists consensus homes and boundary objects. + +## Attempt proofs + +Needs `echidna` and Isabelle on `PATH` (or `--echidna /path/to/echidna`). + +```sh +burrower attempt 'lemma foo: "a + b = b + (a::nat)"' --ledger burrow.jsonl +``` + +Options: `--timeout`, `--project-root` (goals that import project theories), +`--sandbox none|bwrap|podman`, and `--oracle tools/julia-oracle.jl +--oracle-descriptor d.a2ml` for the optional tropical-types pre-proof oracle. + +## Read the ledger + +```sh +burrower ledger recent --path burrow.jsonl +burrower ledger digest --path burrow.jsonl +``` + +A success recorded with a *receipt* came from echidna's structured result; a +success recorded as a *warrant* came from an older echidna's text output. See +[ECHIDNA Contract](ECHIDNA-Contract). + +## Serve + +```sh +burrower serve --socket /tmp/burrower.sock +``` + +Line-delimited JSON: `{"cmd":"swarm"|"attempt"|"ledger"|"ping","args":{...}}`. diff --git a/llm-warmup-dev.adoc b/llm-warmup-dev.adoc deleted file mode 100644 index 53a88aa..0000000 --- a/llm-warmup-dev.adoc +++ /dev/null @@ -1,19 +0,0 @@ -== LLM Warmup — rsr-template-repo (Developer) - -=== What is rsr-template-repo? - -See README.adoc for overview. - -=== Key Commands - -* `+just setup+` — set up development environment -* `+just build+` — build the project -* `+just test+` — run tests -* `+just doctor+` — diagnose issues -* `+just heal+` — attempt auto-repair - -=== Quick Context - -* License: MPL-2.0 -* Part of hyperpolymath ecosystem -* See EXPLAINME.adoc for architecture diff --git a/llm-warmup-user.adoc b/llm-warmup-user.adoc deleted file mode 100644 index c13529e..0000000 --- a/llm-warmup-user.adoc +++ /dev/null @@ -1,19 +0,0 @@ -== LLM Warmup — rsr-template-repo (User) - -=== What is rsr-template-repo? - -See README.adoc for overview. - -=== Key Commands - -* `+just setup+` — set up development environment -* `+just build+` — build the project -* `+just test+` — run tests -* `+just doctor+` — diagnose issues -* `+just heal+` — attempt auto-repair - -=== Quick Context - -* License: MPL-2.0 -* Part of hyperpolymath ecosystem -* See EXPLAINME.adoc for architecture diff --git a/mise.lock b/mise.lock new file mode 100644 index 0000000..665a715 --- /dev/null +++ b/mise.lock @@ -0,0 +1,89 @@ +# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html + +[[tools.julia]] +version = "1.12.6" +backend = "http:julia" + +[tools.julia."platforms.linux-arm64"] +checksum = "sha256:029b93b857bd0ffd627f9a8580d3bbaa63daf008d7b7aed02fbceb8fd57c4899" +url = "https://julialang-s3.julialang.org/bin/linux/aarch64/1.12/julia-1.12.6-linux-aarch64.tar.gz" + +[tools.julia."platforms.linux-x64"] +checksum = "sha256:bbabf3bef19421a9dbd24a767d807606ab85e444323b5a1c73ffe293fa3d079a" +url = "https://julialang-s3.julialang.org/bin/linux/x64/1.12/julia-1.12.6-linux-x86_64.tar.gz" + +[tools.julia."platforms.macos-arm64"] +checksum = "sha256:277d82fbd2eda99d0963b3e41f3dc979d7486f181399f8430fb637318ccd6a31" +url = "https://julialang-s3.julialang.org/bin/mac/aarch64/1.12/julia-1.12.6-macaarch64.tar.gz" + +[tools.julia."platforms.macos-x64"] +checksum = "sha256:1a70b7c606d6bac38a246e722369e5b30914dccf9378499d2712fb3bd282642c" +url = "https://julialang-s3.julialang.org/bin/mac/x64/1.12/julia-1.12.6-mac64.tar.gz" + +[[tools.just]] +version = "1.56.0" +backend = "aqua:casey/just" + +[tools.just."platforms.linux-arm64"] +checksum = "sha256:c8c1d656e9f47569ec1ae2bf8779af2621cdeea6bbbba3b0cacd64f951d25e2b" +url = "https://github.com/casey/just/releases/download/1.56.0/just-1.56.0-aarch64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/casey/just/releases/assets/472074960" + +[tools.just."platforms.linux-x64"] +checksum = "sha256:fa2a8ec1015d9df5330941ade12437488fc40d33f9c9f8cd4eb70a26de11b639" +url = "https://github.com/casey/just/releases/download/1.56.0/just-1.56.0-x86_64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/casey/just/releases/assets/472074624" + +[tools.just."platforms.macos-arm64"] +checksum = "sha256:f35798d4bcdc4db020eef7d2853ad98bbfb97a4d29ee695ba042f18e7fedcc11" +url = "https://github.com/casey/just/releases/download/1.56.0/just-1.56.0-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/casey/just/releases/assets/472074635" + +[tools.just."platforms.macos-x64"] +checksum = "sha256:09b35ff6d17023ffae37ce408d1a78a976d9e001cae54b88e238f7f40db9b783" +url = "https://github.com/casey/just/releases/download/1.56.0/just-1.56.0-x86_64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/casey/just/releases/assets/472074650" + +[[tools.shellcheck]] +version = "0.11.0" +backend = "aqua:koalaman/shellcheck" + +[tools.shellcheck."platforms.linux-arm64"] +checksum = "sha256:12b331c1d2db6b9eb13cfca64306b1b157a86eb69db83023e261eaa7e7c14588" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.aarch64.tar.xz" +url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056934" + +[tools.shellcheck."platforms.linux-x64"] +checksum = "sha256:8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.linux.x86_64.tar.xz" +url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056942" + +[tools.shellcheck."platforms.macos-arm64"] +checksum = "sha256:56affdd8de5527894dca6dc3d7e0a99a873b0f004d7aabc30ae407d3f48b0a79" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.aarch64.tar.xz" +url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056932" + +[tools.shellcheck."platforms.macos-x64"] +checksum = "sha256:3c89db4edcab7cf1c27bff178882e0f6f27f7afdf54e859fa041fca10febe4c6" +url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.darwin.x86_64.tar.xz" +url_api = "https://api.github.com/repos/koalaman/shellcheck/releases/assets/279056930" + +[[tools.zig]] +version = "0.16.0" +backend = "core:zig" + +[tools.zig."platforms.linux-arm64"] +checksum = "sha256:ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17" +url = "https://ziglang.org/download/0.16.0/zig-aarch64-linux-0.16.0.tar.xz" + +[tools.zig."platforms.linux-x64"] +checksum = "sha256:70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00" +url = "https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz" + +[tools.zig."platforms.macos-arm64"] +checksum = "sha256:b23d70deaa879b5c2d486ed3316f7eaa53e84acf6fc9cc747de152450d401489" +url = "https://ziglang.org/download/0.16.0/zig-aarch64-macos-0.16.0.tar.xz" + +[tools.zig."platforms.macos-x64"] +checksum = "sha256:0387557ed1877bc6a2e1802c8391953baddba76081876301c522f52977b52ba7" +url = "https://ziglang.org/download/0.16.0/zig-x86_64-macos-0.16.0.tar.xz" diff --git a/mise.toml b/mise.toml index 6dd983f..d07d3ce 100644 --- a/mise.toml +++ b/mise.toml @@ -1,57 +1,21 @@ -[tools] -# Language runtimes -node = "latest" -python = "latest" -rust = "latest" -go = "latest" -zig = "latest" -java = "latest" -bun = "latest" -denojs = "latest" - -# Package managers -npm = "latest" -yarn = "latest" -pnpm = "latest" -pip = "latest" -cargo = "latest" -go-task = "latest" - -# Formatting & Linting -gofmt = "latest" -black = "latest" -isort = "latest" -ruff = "latest" -prettier = "latest" -shfmt = "latest" -stylua = "latest" - -# Build tools -cmake = "latest" -make = "latest" -ninja = "latest" +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# The toolchain for proof-burrower. Specs are `latest`; mise.lock records the +# concrete version and per-platform checksum of each, so every checkout +# installs the same bits (PROVISIONING-STANDARD §4). +# +# Rust is not a mise tool here: rust-toolchain.toml pins it through rustup +# (stable + wasm32-unknown-unknown for burrower-wasm). +# Idris2 is not a mise tool: it comes from pack (src/interface/abi ABI). +# Julia is only needed for the optional tropical-types pre-proof oracle. +# Estate policy: no python, deno, node/npm/yarn, go, java or make here. -# Shell tools -git = "latest" -gnu-sed = "latest" -gnu-tar = "latest" -gnu-grep = "latest" +[settings] +lockfile = true -# Testing -vitest = "latest" -pytest = "latest" -jest = "latest" - -[env] -# Common environment variables -NODE_ENV = "development" -PYTHONDONTWRITEBYTECODE = "1" -PYTHONUNBUFFERED = "1" - -# Task runner alias -[alias] -task = "go-task" -build = "cargo build --release || npm run build || go build" -test = "cargo test || npm test || go test ./..." -lint = "ruff check . || prettier --check . || black --check ." -fmt = "ruff format . || prettier --write . || black ." +[tools] +just = "latest" +shellcheck = "latest" +zig = "latest" +julia = "latest" diff --git a/proof-burrower_chora.deed b/proof-burrower_chora.deed new file mode 100644 index 0000000..63ce8d0 --- /dev/null +++ b/proof-burrower_chora.deed @@ -0,0 +1,67 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +; +; The repo deed of proof-burrower (one-deed-per-repo doctrine, standards#837). +; +; Scope of this first deed (2026-10-05): identity, clade, forges, lineage, +; status, maturity and ecosystem, translated from +; .machine_readable/CLADE.a2ml and .machine_readable/descriptiles/ +; {STATE,ECOSYSTEM}.a2ml. Those descriptiles are still read by +; openssf-compliance.yml, the contractiles Justfile and +; scripts/validate-template.sh, so they stay until those consumers move to +; this deed; until then a change to identity or status must be made in both +; places. Milestones and blockers live in docs/status/ROADMAP.adoc. +(repo-deed + :schema-version "1.0.0" + :canonical-name "proof-burrower" + :beholding-chora #u5"estate/chora" + :repo-uuid #u5"github.com/hyperpolymath/proof-burrower" + (identity + :primary-forge github + :owner "hyperpolymath" + :prefixed-name "rm-proof-burrower") + (clade + :primary rm + :primary-name "Repo Management & Tooling" + :secondary (gv) + :assigned "2026-03-16" + :rationale "Inherited unchanged from the template copy (rm, gv). Proof Burrower is a proof-search companion to ECHIDNA (clade fv); the assignment needs a gv-clade-index review before it is changed here.") + (forges + :github "hyperpolymath/proof-burrower" + :gitlab "hyperpolymath/proof-burrower" + :bitbucket "hyperpolymath/proof-burrower") + (lineage + :type standalone + :parent "" + :born "2026-03-16" + :previous-names ()) + (status + :phase active + :since "2026-04-26" + :present #t + :aliases () + :merged-into "" + :superseded-by "" + :successors () + :ended "" + (history + (entry :phase active :since "2026-04-26" :note "first commit of the Rust engine; pre-alpha v0.0.1"))) + (maturity + :level experimental) + (ecosystem + :project "proof-burrower" + :ecosystem "hyperpolymath" + :position-type "tool" + :purpose "Locate the mathematical home of a proof goal, read it through specialist vocabularies, attempt tactic playbooks through ECHIDNA, and keep an append-only ledger of outcomes." + :not ("a theorem prover (ECHIDNA is the prover)" + "a proof corpus (it indexes other libraries)") + :pipeline-position "pre-prover" + :chain "author goal → proof-burrower (find/swarm/attempt) → echidna prove → ledger" + :pipeline-notes "Consumer of ECHIDNA's echidna.prove.result/1 contract (docs/ECHIDNA-INTEGRATION.adoc); candidate consumer for echidnabot via `burrower serve`." + :coordination echidna + (related :name "echidna" :relationship prover :notes "Subprocess `echidna prove`; contract echidna.prove.result/1.") + (related :name "echidnabot" :relationship planned-consumer :notes "Could consult `burrower serve` on failed proofs; not wired today.") + (related :name "tropical-types" :relationship oracle :notes "tools/julia-oracle.jl, optional pre-proof numerical oracle (caller-supplied path).") + (related :name "ijson-jcs" :relationship library :notes "RFC 8785 / RFC 7493 validation of the prove-result contract (git dependency pinned by rev).") + (related :name "epistemic-types" :relationship pattern :notes "Receipt vs warrant distinction copied as a pattern; no dependency.") + (related :name "standards" :relationship parent :notes "RSR policies and reusable CI workflows."))) diff --git a/scripts/sync-wiki.sh b/scripts/sync-wiki.sh new file mode 100755 index 0000000..b8c556d --- /dev/null +++ b/scripts/sync-wiki.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) Jonathan D.A. Jewell +# +# Copy the wiki source in docs/wikis/ into a clone of the forge wiki +# (https://github.com/hyperpolymath/proof-burrower.wiki.git) and commit it. +# It never pushes: it prints the push command for a human to run. +# +# Usage: scripts/sync-wiki.sh +set -euo pipefail + +# Print usage to stderr and exit with status 2. +usage() { + echo "usage: $0 " >&2 + exit 2 +} + +# Copy every docs/wikis/*.md page except README.md into the wiki clone, +# remove pages that no longer have a source, and commit if anything changed. +sync_wiki() { + local src=$1 dest=$2 page name + for page in "$src"/*.md; do + name=$(basename "$page") + [ "$name" = "README.md" ] && continue + cp "$page" "$dest/$name" + done + for page in "$dest"/*.md; do + name=$(basename "$page") + [ -f "$src/$name" ] || git -C "$dest" rm -q -- "$name" + done + git -C "$dest" add -A -- '*.md' + if git -C "$dest" diff --cached --quiet; then + echo "wiki already up to date" + return 0 + fi + git -C "$dest" commit -q -m "docs(wiki): sync from proof-burrower docs/wikis" + echo "committed; push with: git -C '$dest' push origin HEAD" +} + +[ $# -eq 1 ] || usage +dest=$1 +[ -d "$dest/.git" ] || { echo "not a git clone: $dest" >&2; exit 1; } +repo_root=$(cd "$(dirname "$0")/.." && pwd) +sync_wiki "$repo_root/docs/wikis" "$dest" diff --git a/scripts/validate-template.sh b/scripts/validate-template.sh index 14a5482..205de5e 100755 --- a/scripts/validate-template.sh +++ b/scripts/validate-template.sh @@ -94,10 +94,10 @@ echo "" # Root files check_file_exists "0-AI-MANIFEST.a2ml" "AI manifest (universal entry point)" check_file_exists "README.adoc" "High-level pitch" -check_file_exists "EXPLAINME.adoc" "Developer deep-dive" +check_file_exists "docs/EXPLAINME.adoc" "Developer deep-dive" check_file_exists "LICENSE" "License file" check_file_exists "Justfile" "Task runner" -check_file_exists "AUDIT.adoc" "Release audit gate" +check_file_exists "docs/AUDIT.adoc" "Release audit gate" # Directories check_dir_exists ".machine_readable" "Machine-readable metadata" diff --git a/session/local-hooks.sh b/session/local-hooks.sh index 20e9fae..90d15f8 100755 --- a/session/local-hooks.sh +++ b/session/local-hooks.sh @@ -13,7 +13,7 @@ echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) hook: $verb $object $repo_path" >> "$log_fi case "$verb $object" in "verify release") - echo "release hook: ensure AUDIT.adoc and session reports are reviewed" >> "$log_file" + echo "release hook: ensure docs/AUDIT.adoc and session reports are reviewed" >> "$log_file" ;; "close urgent") echo "urgent hook: prioritize EMERGENCY-CHECKPOINT.md generation" >> "$log_file" diff --git a/src/interface/ffi/test/integration_test.zig b/src/interface/ffi/test/integration_test.zig index 484e156..093bd0d 100644 --- a/src/interface/ffi/test/integration_test.zig +++ b/src/interface/ffi/test/integration_test.zig @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) Jonathan D.A. Jewell -// RSR Template FFI Integration Tests +// proof-burrower FFI integration tests (RSR template layer) // // These tests verify that the Zig FFI correctly implements the Idris2 ABI. // This is a TEMPLATE FILE — when instantiating a new project: diff --git a/tests/e2e/template_instantiation_test.sh b/tests/e2e/template_instantiation_test.sh index b939dbf..09636ec 100755 --- a/tests/e2e/template_instantiation_test.sh +++ b/tests/e2e/template_instantiation_test.sh @@ -209,7 +209,7 @@ log_step "Verifying critical files have been instantiated" CRITICAL_FILES=( "README.adoc" - "EXPLAINME.adoc" + "docs/EXPLAINME.adoc" "Justfile" ) diff --git a/www/.well-known/security.txt b/www/.well-known/security.txt index 10fa0ba..ac78dc3 100644 --- a/www/.well-known/security.txt +++ b/www/.well-known/security.txt @@ -4,8 +4,5 @@ Contact: mailto:j.d.a.jewell@open.ac.uk Expires: 2026-12-31T23:59:59.000Z -Encryption: {{PGP_KEY_URL}} Preferred-Languages: en -Canonical: https://github.com/hyperpolymath/proof-burrower/.well-known/security.txt -Policy: https://github.com/hyperpolymath/proof-burrower/blob/main/SECURITY.md -Hiring: https://https://github.com/hyperpolymath/proof-burrower/careers +Policy: https://github.com/hyperpolymath/proof-burrower/blob/main/.github/SECURITY.md From d99a3e0a291c83181ad50a2829947b30d56a2442 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:55:50 +0100 Subject: [PATCH 4/9] ci(workflow-linter): accept SPDX in the leading comment block gh actions-lock prepends its own header line, so every workflow fails a first-line-only SPDX check. Take the template's check, which looks for the licence anywhere in the leading comment block. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/workflow-linter.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml index bac95b1..b381ed2 100644 --- a/.github/workflows/workflow-linter.yml +++ b/.github/workflows/workflow-linter.yml @@ -35,13 +35,15 @@ jobs: failed=0 for file in .github/workflows/*.yml .github/workflows/*.yaml; do [ -f "$file" ] || continue - if ! head -1 "$file" | grep -q "^# SPDX-License-Identifier:"; then + # actions-lock may prepend its own comment. Require the licence + # in the leading comment block, before the workflow body. + if ! awk '/^# SPDX-License-Identifier:/ { found=1 } /^[^#[:space:]]/ { exit } END { exit !found }' "$file"; then echo "ERROR: $file missing SPDX header" failed=1 fi done if [ $failed -eq 1 ]; then - echo "Add '# SPDX-License-Identifier: MPL-2.0' as first line" + echo "Add '# SPDX-License-Identifier: MPL-2.0' to the leading comment block" exit 1 fi echo "All workflows have SPDX headers" From 91865f4cd5702ecb30e83a38e42e79c93eb91758 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:20:27 +0100 Subject: [PATCH 5/9] fix(contract): canonicalise with a public RFC 8785 crate hyperpolymath/ijson-jcs is private, so CI on this public repo (and anyone who downloads it) cannot fetch the git dependency: every Rust job failed at dependency resolution. Use serde_json_canonicalizer instead. Byte equality with the canonical form still rejects duplicate keys and integers beyond 2^53-1, now as NotCanonical; tests updated and a lone surrogate plus ECMAScript number-form cases added. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 231 ++++++++++++++++--- crates/burrower-core/Cargo.toml | 19 +- crates/burrower-core/src/echidna_contract.rs | 46 +++- 3 files changed, 254 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a00ca38..248edbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,6 +73,21 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "burrower" version = "0.0.1" @@ -89,11 +104,13 @@ name = "burrower-core" version = "0.0.1" dependencies = [ "anyhow", - "ijson-jcs", "regex", "serde", "serde_json", + "serde_json_canonicalizer", + "sha2", "tempfile", + "uuid", "walkdir", ] @@ -157,6 +174,35 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -185,6 +231,40 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -225,16 +305,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" -[[package]] -name = "ijson-jcs" -version = "0.1.0" -source = "git+https://github.com/hyperpolymath/ijson-jcs?rev=0fdeea716fd558e9807362acf6f8202d56d8d88f#0fdeea716fd558e9807362acf6f8202d56d8d88f" -dependencies = [ - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "indexmap" version = "2.14.0" @@ -259,6 +329,17 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "leb128fmt" version = "0.1.0" @@ -301,6 +382,12 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "prettyplease" version = "0.2.37" @@ -377,6 +464,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu-js" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04d056b875a9d2e6cb9a61d127afee9ac5999b9f87bcb32079d1318e505be714" + [[package]] name = "same-file" version = "1.0.6" @@ -435,6 +534,34 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_json_canonicalizer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe52319a927259afbfa5180c5157cd8167edfd3e8c254f9558c7fef44c5649f2" +dependencies = [ + "ryu-js", + "serde", + "serde_json", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + [[package]] name = "strsim" version = "0.11.1" @@ -477,24 +604,10 @@ dependencies = [ ] [[package]] -name = "thiserror" -version = "2.0.21" +name = "typenum" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -514,6 +627,23 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -542,6 +672,51 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.6", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + [[package]] name = "wasm-encoder" version = "0.244.0" diff --git a/crates/burrower-core/Cargo.toml b/crates/burrower-core/Cargo.toml index 557c0a4..8e44265 100644 --- a/crates/burrower-core/Cargo.toml +++ b/crates/burrower-core/Cargo.toml @@ -15,7 +15,18 @@ serde_json = { workspace = true } walkdir = { workspace = true } regex = { workspace = true } tempfile = "3" -# RFC 8785 (JCS) + RFC 7493 (I-JSON) for the echidna.prove.result/1 -# contract. Not on crates.io; pinned by rev so standalone builds are -# reproducible (a path dependency would break them). -ijson-jcs = { git = "https://github.com/hyperpolymath/ijson-jcs", rev = "0fdeea716fd558e9807362acf6f8202d56d8d88f" } +# RFC 8785 (JCS) canonicalisation for the echidna.prove.result/1 contract. +# The estate crate hyperpolymath/ijson-jcs is private, so a public repo +# cannot build against it; this published RFC 8785 implementation is used +# until ijson-jcs is public (see docs/ECHIDNA-INTEGRATION.adoc). +serde_json_canonicalizer = "0.3" +# Identifier minting (crate::ids): UUIDv7 record ids from the library API +# (the estate standard forbids hand-rolled version bits) and RFC 9562 +# Appendix B.2 UUIDv8 content ids over SHA-256 of the JCS bytes. +# wasm32-unknown-unknown has no clock or entropy source, so v7 (which needs +# both) is enabled only on other targets; see ids::new_record_id. +uuid = { version = "1.10", features = ["v8"] } +sha2 = "0.10" + +[target.'cfg(not(all(target_arch = "wasm32", target_os = "unknown")))'.dependencies] +uuid = { version = "1.10", features = ["v7", "v8"] } diff --git a/crates/burrower-core/src/echidna_contract.rs b/crates/burrower-core/src/echidna_contract.rs index 3c5d2d7..410ade8 100644 --- a/crates/burrower-core/src/echidna_contract.rs +++ b/crates/burrower-core/src/echidna_contract.rs @@ -91,9 +91,10 @@ pub enum ContractError { Empty, /// stdout held more than one line (the contract allows exactly one object). ExtraOutput, - /// Not I-JSON (bad JSON, duplicate keys, unsafe integers, ...). + /// Not parseable JSON (syntax error, lone surrogate, ...). NotIJson(String), - /// Valid I-JSON, but not byte-identical to its JCS canonical form. + /// Parses, but is not byte-identical to its RFC 8785 canonical form. + /// This includes duplicate keys and integers beyond ±(2^53−1). NotCanonical, /// The object does not have the contract's shape. Shape(String), @@ -120,7 +121,8 @@ impl std::error::Error for ContractError {} /// Parse and validate echidna's stdout as one `echidna.prove.result/1` object. /// /// Accepts exactly one line (a single trailing newline is allowed). The line -/// must be I-JSON, must equal its own JCS canonicalisation byte for byte, +/// must parse as JSON, must equal its own JCS canonicalisation byte for byte +/// (which rules out duplicate keys and unsafe integers, the I-JSON limits), /// must name [`SCHEMA`], and must carry every contract field with the right /// type. Unknown extra fields are tolerated so that additive changes do not /// break older consumers; removing or retyping a field needs a new schema. @@ -132,10 +134,14 @@ pub fn parse_prove_result(stdout: &str) -> Result { if line.contains('\n') { return Err(ContractError::ExtraOutput); } - let value = ijson_jcs::parse_json(line, ijson_jcs::JsonMode::Strict) + let value: serde_json::Value = + serde_json::from_str(line).map_err(|e| ContractError::NotIJson(e.to_string()))?; + // Byte equality with the RFC 8785 form also enforces the I-JSON rules + // the parser alone does not: a duplicate key collapses to one member, + // and an integer beyond ±(2^53−1) re-serialises as a different number, + // so either makes the canonical form differ from the input. + let canonical = serde_json_canonicalizer::to_string(&value) .map_err(|e| ContractError::NotIJson(e.to_string()))?; - let canonical = - ijson_jcs::to_jcs_string(&value).map_err(|e| ContractError::NotIJson(e.to_string()))?; if canonical != line { return Err(ContractError::NotCanonical); } @@ -298,12 +304,16 @@ mod tests { parse_prove_result("Proof verified successfully"), Err(ContractError::NotIJson(_)) )); - assert!(matches!( + assert_eq!( parse_prove_result(r#"{"a":1,"a":2}"#), - Err(ContractError::NotIJson(_)) - )); - assert!(matches!( + Err(ContractError::NotCanonical) + ); + assert_eq!( parse_prove_result(r#"{"duration_ms":9007199254740993}"#), + Err(ContractError::NotCanonical) + ); + assert!(matches!( + parse_prove_result(r#"{"goal":"\ud800"}"#), Err(ContractError::NotIJson(_)) )); assert_eq!( @@ -320,6 +330,22 @@ mod tests { )); } + /// Numbers follow the ECMAScript form RFC 8785 requires. + #[test] + fn confidence_numbers_must_be_in_ecmascript_form() { + let with = |c: &str| GOOD.replace("\"confidence\":null", &format!("\"confidence\":{c}")); + assert!(parse_prove_result(&with("0.5")).is_ok()); + assert!(parse_prove_result(&with("1e-7")).is_ok()); + assert_eq!( + parse_prove_result(&with("0.50")), + Err(ContractError::NotCanonical) + ); + assert_eq!( + parse_prove_result(&with("1.0")), + Err(ContractError::NotCanonical) + ); + } + /// Every contract error renders as a single non-empty line. #[test] fn errors_render_as_one_line() { From b6b0f9f29ff95a57325ab6a104bbdd01214bcb20 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:20:36 +0100 Subject: [PATCH 6/9] feat(ids): centralise id minting; UUIDv7 records, UUIDv8 content ids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New crates/burrower-core/src/ids.rs is the only minting site: new_record_id() = UUIDv7 via the uuid crate API (ledger record ids, via the ledger::new_id alias), content_id(value) = UUIDv8 per RFC 9562 Appendix B.2 over SHA-256 of the JCS bytes. Ledger records gain an optional goal_id and receipts an optional result_id (both v8). goal_hash is unchanged for the wasm ABI and existing ledgers. v7 is gated off wasm32-unknown-unknown, which has no clock or entropy. Documented in docs/ECHIDNA-INTEGRATION.adoc § Identifiers; tests cover v8 determinism, version/variant bits and v7 ordering. Co-Authored-By: Claude Opus 5.5 --- crates/burrower-core/src/attempt.rs | 13 +++ crates/burrower-core/src/ids.rs | 120 ++++++++++++++++++++++++++++ crates/burrower-core/src/ledger.rs | 38 ++++----- crates/burrower-core/src/lib.rs | 2 + crates/burrower-core/src/oracle.rs | 1 + docs/ECHIDNA-INTEGRATION.adoc | 51 ++++++++++-- docs/EXPLAINME.adoc | 5 +- 7 files changed, 202 insertions(+), 28 deletions(-) create mode 100644 crates/burrower-core/src/ids.rs diff --git a/crates/burrower-core/src/attempt.rs b/crates/burrower-core/src/attempt.rs index 0f6aefd..6744541 100644 --- a/crates/burrower-core/src/attempt.rs +++ b/crates/burrower-core/src/attempt.rs @@ -116,17 +116,27 @@ pub struct ProofReceipt { pub confidence: Option, /// The echidna version that issued the receipt. pub echidna_version: String, + /// Content id of the prove result (UUIDv8 over the JCS bytes of its + /// contract fields, see [`crate::ids::content_id`]); identical results + /// share an id. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub result_id: Option, } impl ProofReceipt { /// Build a receipt from a parsed prove result. pub fn from_result(r: &ProveResult) -> Self { + let result_id = serde_json::to_value(r) + .ok() + .and_then(|v| crate::ids::content_id(&v).ok()) + .map(|u| u.to_string()); Self { schema: r.schema.clone(), prover: r.prover.clone(), axioms: r.trust.axioms.clone(), confidence: r.trust.confidence, echidna_version: r.echidna_version.clone(), + result_id, } } } @@ -526,6 +536,7 @@ pub fn run_playbook( ) -> Result> { let mut attempts = Vec::new(); let goal_h = goal_hash(&goal.raw); + let goal_id = crate::ids::goal_content_id(&goal.raw); for (i, tactic) in playbook.tactics.iter().enumerate() { let probe = generate_probe(&goal.raw, tactic); @@ -550,6 +561,7 @@ pub fn run_playbook( id: new_id(), timestamp: now_iso(), goal_hash: goal_h.clone(), + goal_id: Some(goal_id.clone()), goal_excerpt: goal.raw.chars().take(200).collect(), specialist: playbook.specialist.clone(), approach: Some(Approach { @@ -962,6 +974,7 @@ mod tests { axioms: vec!["sorry".into()], confidence: None, echidna_version: "2.4.0".into(), + result_id: None, }; let with = AttemptResult::Succeeded { duration_ms: 3, diff --git a/crates/burrower-core/src/ids.rs b/crates/burrower-core/src/ids.rs new file mode 100644 index 0000000..ea65eb7 --- /dev/null +++ b/crates/burrower-core/src/ids.rs @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Jonathan D.A. Jewell +//! # Identifier minting — the only place proof-burrower mints ids +//! +//! Two kinds of identifier, and nothing else in the crate mints one: +//! +//! * [`new_record_id`] — a **UUIDv7** (RFC 9562 §5.7) for *events and +//! records*: ledger entries, attempts, readings. Time-ordered, minted from +//! the `uuid` crate's library API as `standards/docs/UUID-V7-ESTATE-STANDARD.adoc` +//! requires (no hand-set version bits). +//! * [`content_id`] — a **UUIDv8** (RFC 9562 §5.8, Appendix B.2 style) for +//! *content*: proof goals, prove results, corpus entries. It is SHA-256 +//! over the RFC 8785 (JCS) canonical bytes of the value, truncated to the +//! first 16 bytes, with the version nibble set to `8` and the variant bits +//! set to `10`. The same JSON content always yields the same id, whatever +//! key order or whitespace it arrived in. +//! +//! The v7/v8 split is the owner's delegated policy of 2026-10-05: v7 for +//! things that *happened*, v8 for things that *are* (content addressing). +//! See `docs/ECHIDNA-INTEGRATION.adoc` § Identifiers. + +use sha2::{Digest, Sha256}; +use uuid::Uuid; + +/// Mint a fresh time-ordered record id (UUIDv7, hyphenated lower-case). +/// +/// Use for events and records: ledger entries, attempts, readings. +/// Within one process successive ids sort in minting order (the `uuid` +/// crate keeps a monotonic counter inside the millisecond). +/// +/// On `wasm32-unknown-unknown` there is no clock or entropy source; this +/// panics there, exactly as the `SystemTime::now` it replaces did. The +/// wasm shim (`burrower-wasm`) never writes ledger records. +#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] +pub fn new_record_id() -> String { + Uuid::now_v7().to_string() +} + +/// See the non-wasm definition: no clock or entropy on this target. +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +pub fn new_record_id() -> String { + panic!("new_record_id: wasm32-unknown-unknown has no clock or entropy source") +} + +/// Content id of a JSON value: UUIDv8 over SHA-256 of its JCS bytes. +/// +/// Use for proof goals, prove results and corpus entries. Fails only if the +/// value cannot be canonicalised (RFC 8785 rejects non-finite numbers, +/// which `serde_json::Value` cannot hold anyway, so this is defensive). +pub fn content_id(value: &serde_json::Value) -> Result { + let canonical = serde_json_canonicalizer::to_vec(value)?; + let digest = Sha256::digest(&canonical); + let mut first16 = [0u8; 16]; + first16.copy_from_slice(&digest[..16]); + // `new_v8` overwrites the version nibble with 8 and the variant bits + // with RFC 9562's `10`; the other 122 bits are the hash prefix. + Ok(Uuid::new_v8(first16)) +} + +/// Content id of a proof goal's text (a JSON string), hyphenated. +/// +/// Infallible: a JSON string always canonicalises. +pub fn goal_content_id(goal_text: &str) -> String { + content_id(&serde_json::Value::String(goal_text.to_string())) + .map(|u| u.to_string()) + .unwrap_or_default() +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + /// Same JCS content gives the same v8 id regardless of key order and spacing. + #[test] + fn same_canonical_content_same_v8_id() { + let a: serde_json::Value = serde_json::from_str(r#"{"b":1,"a":[true,null]}"#).unwrap(); + let b: serde_json::Value = + serde_json::from_str("{ \"a\" : [ true , null ] , \"b\" : 1 }").unwrap(); + assert_eq!(content_id(&a).unwrap(), content_id(&b).unwrap()); + assert_ne!( + content_id(&a).unwrap(), + content_id(&json!({"a":[true,null],"b":2})).unwrap() + ); + } + + /// The v8 id is exactly the SHA-256 prefix with version/variant bits applied. + #[test] + fn v8_bits_and_hash_prefix() { + let v = json!("lemma foo: x = y"); + let id = content_id(&v).unwrap(); + assert_eq!(id.get_version_num(), 8); + assert_eq!(id.get_variant(), uuid::Variant::RFC4122); + let bytes = id.as_bytes(); + assert_eq!(bytes[6] >> 4, 0x8); + assert_eq!(bytes[8] >> 6, 0b10); + let digest = Sha256::digest(br#""lemma foo: x = y""#); + for i in 0..16 { + let mask = match i { + 6 => 0x0f, + 8 => 0x3f, + _ => 0xff, + }; + assert_eq!(bytes[i] & mask, digest[i] & mask, "byte {i}"); + } + assert_eq!(goal_content_id("lemma foo: x = y"), id.to_string()); + } + + /// v7 record ids carry version 7 and sort in minting order. + #[test] + fn v7_record_ids_are_versioned_and_monotonic() { + let ids: Vec = (0..1000).map(|_| new_record_id()).collect(); + for w in ids.windows(2) { + assert!(w[0] < w[1], "{} !< {}", w[0], w[1]); + } + let u = Uuid::parse_str(&ids[0]).unwrap(); + assert_eq!(u.get_version_num(), 7); + assert_eq!(u.get_variant(), uuid::Variant::RFC4122); + } +} diff --git a/crates/burrower-core/src/ledger.rs b/crates/burrower-core/src/ledger.rs index bb67eae..9273777 100644 --- a/crates/burrower-core/src/ledger.rs +++ b/crates/burrower-core/src/ledger.rs @@ -34,12 +34,17 @@ use std::time::{SystemTime, UNIX_EPOCH}; /// One ledger record. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LedgerRecord { - /// Unique time-orderable id (millis since epoch + counter). + /// Unique time-ordered record id (UUIDv7, see [`crate::ids`]). pub id: String, /// ISO-8601-ish timestamp string, UTC. pub timestamp: String, /// SHA-style hash of the goal (8 hex chars is enough at our scale). pub goal_hash: String, + /// Content id of the goal text (UUIDv8 over its JCS bytes, see + /// [`crate::ids::content_id`]). Absent in ledgers written before + /// 2026-10-05; `goal_hash` stays for those and for the wasm ABI. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub goal_id: Option, /// First 200 chars of the goal text, for human reading. pub goal_excerpt: String, /// Which specialist authored this record. @@ -189,9 +194,10 @@ impl Ledger { // Helpers // --------------------------------------------------------------------- -/// Make a goal-hash from text. 8 hex chars from a fast non-cryptographic -/// hash is enough for a per-repo ledger; the goal text itself is also -/// kept (excerpted) in each record for verification. +/// Make a goal-hash from text: 16 hex chars from a fast non-cryptographic +/// hash, enough for a per-repo ledger and the fixed-size wasm ABI. The +/// goal text itself is also kept (excerpted) in each record. For a stable +/// cross-tool content address use [`crate::ids::goal_content_id`]. pub fn goal_hash(goal_text: &str) -> String { use std::hash::{Hash, Hasher}; let mut h = std::collections::hash_map::DefaultHasher::new(); @@ -199,25 +205,12 @@ pub fn goal_hash(goal_text: &str) -> String { format!("{:016x}", h.finish()) } -/// Mint a new ledger record id: epoch millis + a process-local sequence. +/// Mint a new ledger record id (UUIDv7). /// -/// **This is the only identifier-minting function in proof-burrower.** -/// Every ledger writer (`attempt::run_playbook`, `oracle::record_to_ledger`, -/// [`record_reading`]) calls it; nothing else mints ids. The format is -/// `<13-digit millis>-<4-digit seq>`, which sorts by time within one -/// process. It is not a UUID: two processes appending in the same -/// millisecond can mint the same id. Moving to the estate UUIDv7 standard -/// (`standards/docs/UUID-V7-ESTATE-STANDARD.adoc`) is a recorded follow-up -/// and would change only this function. +/// Thin alias for [`crate::ids::new_record_id`], the crate's single +/// record-id minting function; kept so ledger writers read naturally. pub fn new_id() -> String { - use std::sync::atomic::{AtomicU64, Ordering}; - static SEQ: AtomicU64 = AtomicU64::new(0); - let millis = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|d| d.as_millis() as u64) - .unwrap_or(0); - let n = SEQ.fetch_add(1, Ordering::SeqCst); - format!("{millis:013}-{n:04}") + crate::ids::new_record_id() } /// Timestamp for a ledger record. @@ -245,6 +238,7 @@ pub fn record_reading( id: new_id(), timestamp: now_iso(), goal_hash: goal_hash(goal_text), + goal_id: Some(crate::ids::goal_content_id(goal_text)), goal_excerpt: goal_text.chars().take(200).collect(), specialist: specialist.to_string(), approach: Some(Approach { @@ -276,6 +270,7 @@ mod tests { id: new_id(), timestamp: now_iso(), goal_hash: goal_hash("lemma foo: x = y"), + goal_id: None, goal_excerpt: "lemma foo: x = y".to_string(), specialist: "Algebraist".to_string(), approach: Some(Approach { @@ -311,6 +306,7 @@ mod tests { let a = new_id(); let b = new_id(); assert_ne!(a, b); + assert!(a < b, "record ids are time-ordered"); } #[test] diff --git a/crates/burrower-core/src/lib.rs b/crates/burrower-core/src/lib.rs index 4fa4547..4d1db1b 100644 --- a/crates/burrower-core/src/lib.rs +++ b/crates/burrower-core/src/lib.rs @@ -30,6 +30,7 @@ pub mod attempt; pub mod corpus; pub mod echidna_contract; pub mod goal; +pub mod ids; pub mod ledger; pub mod oracle; pub mod ranking; @@ -42,6 +43,7 @@ pub use attempt::{ }; pub use corpus::{Corpus, IndexedLemma, LibraryKind}; pub use goal::{parse_goal, Goal}; +pub use ids::{content_id, goal_content_id, new_record_id}; pub use ledger::{ goal_hash, new_id, now_iso, record_reading, Approach, Learning, Ledger, LedgerRecord, RecordResult, diff --git a/crates/burrower-core/src/oracle.rs b/crates/burrower-core/src/oracle.rs index 919a65d..ea2b665 100644 --- a/crates/burrower-core/src/oracle.rs +++ b/crates/burrower-core/src/oracle.rs @@ -218,6 +218,7 @@ pub fn record_to_ledger( id: new_id(), timestamp: now_iso(), goal_hash: goal_hash(&goal.raw), + goal_id: Some(crate::ids::goal_content_id(&goal.raw)), goal_excerpt: goal.raw.chars().take(200).collect(), specialist: "Oracle".to_string(), approach: Some(Approach { diff --git a/docs/ECHIDNA-INTEGRATION.adoc b/docs/ECHIDNA-INTEGRATION.adoc index 2996d58..e07c1e7 100644 --- a/docs/ECHIDNA-INTEGRATION.adoc +++ b/docs/ECHIDNA-INTEGRATION.adoc @@ -109,10 +109,16 @@ Implemented in `crates/burrower-core/src/echidna_contract.rs` and `echidna prove --help` and looks for `--output` as a whole option token. Present → contract mode; absent or the binary cannot run → legacy mode. . **Contract mode.** Burrower appends `--output json` and requires stdout - to be one line that parses as I-JSON (no duplicate keys, safe integers), - equals its own JCS canonicalisation byte for byte (via - `hyperpolymath/ijson-jcs`, pinned by git rev), names the `/1` schema, and - carries every field above with the right type. Extra fields are + to be one line that parses as JSON, equals its own RFC 8785 (JCS) + canonicalisation byte for byte, names the `/1` schema, and carries every + field above with the right type. Byte equality also enforces the I-JSON + limits the parser alone does not: a duplicate key collapses to one + member and an integer beyond ±(2^53−1) re-serialises differently, so + both fail as non-canonical. Canonicalisation uses the published + `serde_json_canonicalizer` crate, because the estate crate + `hyperpolymath/ijson-jcs` is private and this repository is public (a + git dependency on it cannot be fetched by CI or by anyone who downloads + the repo). Switch back once `ijson-jcs` is public or on crates.io. Extra fields are tolerated (additive change); removing or retyping a field needs `/2`. . **Mapping.** + @@ -135,7 +141,39 @@ Implemented in `crates/burrower-core/src/echidna_contract.rs` and receipt and the ledger explanation says "a warrant, not a receipt". . **Ledger.** A receipt-backed success stores the receipt under the - record's `extra.receipt`. + record's `extra.receipt`, including its `result_id` (below). + +[[identifiers]] +=== Identifiers + +All identifier minting lives in `crates/burrower-core/src/ids.rs`; nothing +else in the workspace mints an id. The policy (owner, 2026-10-05) is v7 for +things that happened and v8 for things that are: + +[cols="1,1,2"] +|=== +|Function |Kind |Used for + +|`ids::new_record_id()` |UUIDv7 (RFC 9562 §5.7, `uuid` crate API) +|Ledger record `id` (via `ledger::new_id`, a thin alias). Writers: + `attempt::run_playbook`, `oracle::record_to_ledger`, `ledger::record_reading`. +|`ids::content_id(value)` |UUIDv8 (RFC 9562 §5.8 / Appendix B.2) +|SHA-256 over the JCS bytes of `value`, first 16 bytes, version nibble `8`, + variant `10`. Same JSON content -> same id. +|`ids::goal_content_id(text)` |UUIDv8 |Ledger `goal_id` (content id of the + goal text as a JSON string). +|`ProofReceipt::from_result` |UUIDv8 |Receipt `result_id`: content id of + the prove result's contract fields. +|=== + +`goal_hash` (16 hex chars, non-cryptographic) is kept unchanged because the +wasm ABI (`goal_hash_hex`, 16-byte buffer) and existing ledgers depend on +it. Ledgers written before this change carry no `goal_id`; the field is +optional on read. Record ids before this change were `-` +strings, not UUIDs; old ledgers keep them. + +Not yet content-addressed: corpus entries (`corpus::IndexedLemma`) carry no +id today; when they gain one it must come from `ids::content_id`. The receipt/warrant split follows the factive/non-factive distinction in `hyperpolymath/epistemic-types` (`FactiveModality`). It is copied as a @@ -146,6 +184,9 @@ pattern; Burrower does not depend on that Agda library. * `echidna_contract::tests` — canonical parse, every status, planted non-canonical mutants (key order, whitespace), empty / multi-line / duplicate-key / unsafe-integer / wrong-schema / wrong-shape rejection. +* `ids::tests` — same JCS content gives the same v8 id; the v8 id is the + SHA-256 prefix with exactly the version/variant bits changed; v7 record + ids carry version 7 and sort in minting order. * `attempt::tests` — fake echidna binaries for the contract path (every status, receipt transport into the ledger), a malformed-output liar, the legacy path, and a binary that advertises the flag but rejects it. diff --git a/docs/EXPLAINME.adoc b/docs/EXPLAINME.adoc index 8971c12..e34ab31 100644 --- a/docs/EXPLAINME.adoc +++ b/docs/EXPLAINME.adoc @@ -35,8 +35,9 @@ until then CI exercises the legacy fallback. Contract: == "Keeps a ledger" Implemented and unit-tested: `crates/burrower-core/src/ledger.rs`. -Single writer, no locking; ids from `ledger::new_id` are time + process -sequence, not UUIDs. +Single writer, no locking. Record ids are UUIDv7 and goal ids UUIDv8 +content ids, both minted only in `crates/burrower-core/src/ids.rs` (see +`docs/ECHIDNA-INTEGRATION.adoc` § Identifiers). == What is not claimed From 81cbd3eb70cc9fb96f8afc62df402a14858d2a42 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:21:29 +0100 Subject: [PATCH 7/9] docs: qualify echidna paths and fix a stale ABI example path (Hypatia SD022) Co-Authored-By: Claude Opus 5.5 --- docs/ECHIDNA-INTEGRATION.adoc | 8 ++++---- docs/status/READINESS.adoc | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ECHIDNA-INTEGRATION.adoc b/docs/ECHIDNA-INTEGRATION.adoc index e07c1e7..1045b49 100644 --- a/docs/ECHIDNA-INTEGRATION.adoc +++ b/docs/ECHIDNA-INTEGRATION.adoc @@ -283,10 +283,10 @@ free from the substitution loop. The Burrower hits (e.g., the **Implementation sketch.** [source] ---- -src/rust/main.rs # add Suggest variant to Cli -src/rust/suggest/mod.rs # new module -src/rust/suggest/synonyms.toml # the curated substitution table -src/rust/suggest/burrower.rs # optional client to Burrower socket +echidna/src/rust/main.rs # add Suggest variant to Cli +echidna/src/rust/suggest/mod.rs # new module +echidna/src/rust/suggest/synonyms.toml # the curated substitution table +echidna/src/rust/suggest/burrower.rs # optional client to Burrower socket ---- === EI-3 (large): native ledger consumer diff --git a/docs/status/READINESS.adoc b/docs/status/READINESS.adoc index d216bce..9b10c62 100644 --- a/docs/status/READINESS.adoc +++ b/docs/status/READINESS.adoc @@ -109,7 +109,7 @@ Assessed * One row per independently-gradable unit. If a subsystem can ship or break independently, it gets its own row. * Evidence Summary must cite *numbers* (test counts, LOC, file counts) -or *paths* (e.g. `+src/abi/Module.idr+`), never vague claims. +or *paths* (e.g. `+src/interface/Abi/Types.idr+`), never vague claims. * Promotion blocker must be *actionable* — "`add external consumer in home context, then annotate`" beats "`needs more validation`". * Re-assess every release cycle; date-stamp each row. From 0dfd1215cd7337398ddfbdfcc7becf3c585f75dd Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:22:34 +0100 Subject: [PATCH 8/9] fix(ids): no panic on wasm32; degenerate v7 via uuid::Builder Co-Authored-By: Claude Opus 5.5 --- crates/burrower-core/src/ids.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/burrower-core/src/ids.rs b/crates/burrower-core/src/ids.rs index ea65eb7..ed32d00 100644 --- a/crates/burrower-core/src/ids.rs +++ b/crates/burrower-core/src/ids.rs @@ -28,18 +28,28 @@ use uuid::Uuid; /// Within one process successive ids sort in minting order (the `uuid` /// crate keeps a monotonic counter inside the millisecond). /// -/// On `wasm32-unknown-unknown` there is no clock or entropy source; this -/// panics there, exactly as the `SystemTime::now` it replaces did. The -/// wasm shim (`burrower-wasm`) never writes ledger records. +/// On `wasm32-unknown-unknown` (no clock, no entropy) see the fallback +/// definition below. #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))] pub fn new_record_id() -> String { Uuid::now_v7().to_string() } -/// See the non-wasm definition: no clock or entropy on this target. +/// Degenerate UUIDv7 for `wasm32-unknown-unknown`, which has neither a +/// clock nor an entropy source: timestamp 0 and a process-local counter in +/// the random field, built through the `uuid` crate's `Builder` so the +/// version and variant bits are still set by the library. Unique and +/// ordered within one instance only. The wasm shim (`burrower-wasm`) never +/// writes ledger records, so this exists to keep the crate compiling. #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] pub fn new_record_id() -> String { - panic!("new_record_id: wasm32-unknown-unknown has no clock or entropy source") + use std::sync::atomic::{AtomicU64, Ordering}; + static SEQ: AtomicU64 = AtomicU64::new(0); + let mut tail = [0u8; 10]; + tail[2..].copy_from_slice(&SEQ.fetch_add(1, Ordering::SeqCst).to_be_bytes()); + uuid::Builder::from_unix_timestamp_millis(0, &tail) + .into_uuid() + .to_string() } /// Content id of a JSON value: UUIDv8 over SHA-256 of its JCS bytes. From 5c39509acf44c36ad7bc59adb19ad0a022e77e3a Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:23:25 +0100 Subject: [PATCH 9/9] docs(architecture): point at the ids module Co-Authored-By: Claude Opus 5.5 --- docs/ARCHITECTURE.adoc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.adoc b/docs/ARCHITECTURE.adoc index 52a9e0b..f8bd28c 100644 --- a/docs/ARCHITECTURE.adoc +++ b/docs/ARCHITECTURE.adoc @@ -64,8 +64,11 @@ can be shipped as a `.wasm` artefact for ECHIDNA's typed-wasm oracle. == Identifiers and persistence * The ledger is JSON-lines on local disk, single writer. -* `ledger::new_id` is the only identifier-minting function (time + process - sequence; not a UUID). `ledger::goal_hash` keys records by goal. +* `crates/burrower-core/src/ids.rs` is the only identifier-minting module: + `new_record_id()` (UUIDv7) for ledger records and `content_id(value)` + (UUIDv8 over SHA-256 of the JCS bytes) for goals and prove results. See + `docs/ECHIDNA-INTEGRATION.adoc` § Identifiers. `ledger::goal_hash` still + keys records by goal (wasm ABI and existing ledgers depend on it). * Probe files live in a private per-attempt temporary directory that is removed after the child exits.
"); - html.push_str(&format!("

{}

", self.config.package_name)); - html.push_str(&format!( - "

Version {}

", - self.config.package_version - )); - - html.push_str("

Modules

"); - html.push_str("
"); - - html.push_str("