Compare commits
No commits in common. "main" and "1.0.0-beta" have entirely different histories.
main
...
1.0.0-beta
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,7 +14,6 @@ compiler/target/
|
|||||||
# Benchmark and local runtime artifacts
|
# Benchmark and local runtime artifacts
|
||||||
benchmarks/**/build/
|
benchmarks/**/build/
|
||||||
compiler/glagol-std-layout-local-fs-alpha.txt
|
compiler/glagol-std-layout-local-fs-alpha.txt
|
||||||
**/.slovo/
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
# 1.0.0-beta.10 Developer Experience API Discovery
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.10`.
|
|
||||||
|
|
||||||
`1.0.0-beta.10` is a tooling/docs slice on top of the beta.8 concrete alias
|
|
||||||
foundation and beta.9 collection alias unification work. It improves API
|
|
||||||
discovery for the existing source-authored standard library and adds
|
|
||||||
editor-facing source metadata without adding new source-language execution
|
|
||||||
semantics, compiler-known runtime names, or runtime helpers.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Upgrade `scripts/render-stdlib-api-doc.js` so the generated
|
|
||||||
`docs/language/STDLIB_API.md` catalog lists exact exported helper
|
|
||||||
signatures, not only helper names.
|
|
||||||
- Parse each `lib/std/*.slo` module, collect module-local `(type ...)`
|
|
||||||
aliases, and normalize those aliases recursively in public helper
|
|
||||||
signatures.
|
|
||||||
- Verify exported helper names have matching `(fn ...)` forms.
|
|
||||||
- Omit non-exported helper functions and `(type ...)` aliases from the public
|
|
||||||
catalog.
|
|
||||||
- Regenerate `docs/language/STDLIB_API.md`.
|
|
||||||
- Add `glagol symbols <file.slo|project|workspace>` for deterministic
|
|
||||||
`slovo.symbols` S-expression metadata over modules, imports, exports,
|
|
||||||
aliases, structs, enums, functions, tests, spans/ranges, and workspace
|
|
||||||
package names.
|
|
||||||
- Update README, language docs, compiler docs, and the post-beta roadmap to
|
|
||||||
describe beta API discovery clearly.
|
|
||||||
|
|
||||||
## Public Contract
|
|
||||||
|
|
||||||
The generated catalog is a beta discovery aid for the current `lib/std`
|
|
||||||
surface. Public signatures show concrete types such as `(vec i32)`,
|
|
||||||
`(option string)`, and `(result u64 i32)` instead of module-local alias names
|
|
||||||
such as `VecI32`, `OptionString`, or `ResultU64`.
|
|
||||||
|
|
||||||
The catalog remains generated from source and is not a hand-maintained API
|
|
||||||
freeze. It can help reviewers see current helper signatures, but it does not
|
|
||||||
make those helpers stable `1.0.0` standard-library APIs.
|
|
||||||
|
|
||||||
The `symbols` command is an editor-integration building block, not an LSP
|
|
||||||
server. Its output is deterministic machine-readable S-expression text and
|
|
||||||
uses the beta10 `slovo.symbols` schema label.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no executable generics
|
|
||||||
- no generic aliases or parameterized aliases
|
|
||||||
- no maps or sets
|
|
||||||
- no traits, inference, monomorphization, or iterators
|
|
||||||
- no new compiler-known runtime names
|
|
||||||
- no runtime helper or ABI/layout changes
|
|
||||||
- no LSP server, watch mode, SARIF, or daemon protocol
|
|
||||||
- no stable `1.0.0` standard-library freeze
|
|
||||||
|
|
||||||
## Checks
|
|
||||||
|
|
||||||
Focused checks for this slice:
|
|
||||||
|
|
||||||
- `node scripts/render-stdlib-api-doc.js`
|
|
||||||
- `cargo test --test symbols_beta10`
|
|
||||||
- `git diff --check -- scripts/render-stdlib-api-doc.js docs/language/STDLIB_API.md compiler/src/main.rs compiler/src/symbols.rs compiler/tests/symbols_beta10.rs README.md docs/language/SPEC-v1.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md docs/compiler/ROADMAP.md docs/compiler/RELEASE_NOTES.md docs/POST_BETA_ROADMAP.md .llm/BETA_10_DEVELOPER_EXPERIENCE_API_DISCOVERY.md`
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
# 1.0.0-beta.11 Local Package API Documentation
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.11`.
|
|
||||||
|
|
||||||
`1.0.0-beta.11` extends the beta.10 API discovery lane. The release keeps the
|
|
||||||
`1.0.0-beta` source-language and runtime baseline unchanged while making local
|
|
||||||
package and module documentation show the public API surface users need to
|
|
||||||
review.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Extend `glagol doc <file|project|workspace> -o <dir>` so generated Markdown
|
|
||||||
includes deterministic exported/public API sections for local source files,
|
|
||||||
projects, packages, and workspaces.
|
|
||||||
- Render exact exported function signatures with parameter names, parameter
|
|
||||||
types, and return types.
|
|
||||||
- Render exported struct field names and field types.
|
|
||||||
- Render exported enum variant names and payload types for payloadless and
|
|
||||||
current single-payload variants.
|
|
||||||
- Keep non-exported functions, structs, enums, tests, and `(type ...)` aliases
|
|
||||||
out of the public API sections.
|
|
||||||
- Normalize module-local concrete aliases before rendering public types, so
|
|
||||||
private names such as `VecI32`, `OptionString`, or `ResultU64` do not leak
|
|
||||||
into local package/module public docs.
|
|
||||||
- Update README, language docs, compiler docs, and the post-beta roadmap to
|
|
||||||
describe the beta11 documentation contract clearly.
|
|
||||||
|
|
||||||
## Public Contract
|
|
||||||
|
|
||||||
The generated local documentation is a beta API discovery aid. It exposes what
|
|
||||||
the current local module/package export lists make public, with concrete public
|
|
||||||
types after alias normalization.
|
|
||||||
|
|
||||||
The public API sections are deterministic and suitable for human review, but
|
|
||||||
they are not a stable machine-readable Markdown schema. Headings, anchors, file
|
|
||||||
names, and surrounding prose remain beta-scoped unless a later release freezes
|
|
||||||
them explicitly.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no stable Markdown schema
|
|
||||||
- no stable stdlib/API compatibility freeze
|
|
||||||
- no LSP server or watch mode
|
|
||||||
- no SARIF or daemon protocol
|
|
||||||
- no diagnostics schema policy
|
|
||||||
- no executable generics
|
|
||||||
- no maps or sets
|
|
||||||
- no re-exports, glob imports, or hierarchical modules
|
|
||||||
- no package registry semantics
|
|
||||||
- no new compiler-known runtime names
|
|
||||||
- no runtime helper or ABI/layout changes
|
|
||||||
|
|
||||||
## Checks
|
|
||||||
|
|
||||||
Focused checks for this slice:
|
|
||||||
|
|
||||||
- `git diff --check -- README.md docs/POST_BETA_ROADMAP.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md docs/language/SPEC-v1.md docs/compiler/ROADMAP.md docs/compiler/RELEASE_NOTES.md .llm/BETA_11_LOCAL_PACKAGE_API_DOCUMENTATION.md`
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
# 1.0.0-beta.12 Concrete Vector Query And Prefix Parity
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.12`.
|
|
||||||
|
|
||||||
`1.0.0-beta.12` is a source-authored standard-library/helper parity release for
|
|
||||||
current concrete vectors. It keeps the `1.0.0-beta` source language, typed core,
|
|
||||||
runtime, ABI/layout, and compiler-known `std.vec.*` runtime names unchanged.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `count_of`, `starts_with`, `without_prefix`, `ends_with`, and
|
|
||||||
`without_suffix` to `std.vec_i64`.
|
|
||||||
- Add `count_of` to `std.vec_f64`.
|
|
||||||
- Keep helpers ordinary Slovo source over the existing concrete vector runtime
|
|
||||||
names, equality, `len`, `at`, and already staged recursive helpers.
|
|
||||||
- Extend explicit source-helper project coverage for repeated `count_of`
|
|
||||||
results and prefix/suffix empty, mismatch, exact, and longer-than-input
|
|
||||||
cases where applicable.
|
|
||||||
- Bump the Glagol package version to `1.0.0-beta.12`.
|
|
||||||
- Update README, language docs, compiler docs, and the post-beta roadmap.
|
|
||||||
|
|
||||||
## Public Contract
|
|
||||||
|
|
||||||
The helper additions are concrete-family helpers:
|
|
||||||
|
|
||||||
- `std.vec_i64.count_of(values,target)` counts `i64` elements equal to
|
|
||||||
`target`.
|
|
||||||
- `std.vec_i64.starts_with(values,prefix)` and
|
|
||||||
`std.vec_i64.ends_with(values,suffix)` treat empty prefixes/suffixes and
|
|
||||||
exact full-vector matches as true, reject mismatches, and reject longer
|
|
||||||
prefix/suffix inputs.
|
|
||||||
- `std.vec_i64.without_prefix(values,prefix)` and
|
|
||||||
`std.vec_i64.without_suffix(values,suffix)` return the unmatched original
|
|
||||||
vector or the remaining vector after a match.
|
|
||||||
- `std.vec_f64.count_of(values,target)` counts `f64` elements equal to
|
|
||||||
`target`.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no source-language syntax change
|
|
||||||
- no typed-core or lowering change
|
|
||||||
- no runtime implementation change
|
|
||||||
- no new compiler-known stdlib or runtime names
|
|
||||||
- no executable generics or generic stdlib dispatch
|
|
||||||
- no maps or sets
|
|
||||||
- no iterators
|
|
||||||
- no mutable vector operations
|
|
||||||
- no slice/view APIs
|
|
||||||
- no ABI/layout stability promise
|
|
||||||
- no performance claim
|
|
||||||
- no stable stdlib/API freeze
|
|
||||||
|
|
||||||
## Checks
|
|
||||||
|
|
||||||
Focused checks for this slice:
|
|
||||||
|
|
||||||
- `cargo test --manifest-path compiler/Cargo.toml --test standard_vec_i64_source_helpers_alpha --test standard_vec_f64_source_helpers_alpha`
|
|
||||||
- `cargo fmt --manifest-path compiler/Cargo.toml --check`
|
|
||||||
- `git diff --check -- compiler/tests/standard_vec_i64_source_helpers_alpha.rs compiler/tests/standard_vec_f64_source_helpers_alpha.rs compiler/Cargo.toml compiler/Cargo.lock README.md docs/POST_BETA_ROADMAP.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md docs/language/SPEC-v1.md docs/compiler/ROADMAP.md docs/compiler/RELEASE_NOTES.md .llm/BETA_12_CONCRETE_VECTOR_QUERY_AND_PREFIX_PARITY.md`
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# 1.0.0-beta.13 Diagnostic Catalog And Schema Policy
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.13`.
|
|
||||||
|
|
||||||
`1.0.0-beta.13` is a docs/tooling-only policy slice for the existing
|
|
||||||
diagnostic surface. It keeps the `1.0.0-beta` source language, typed core,
|
|
||||||
runtime, standard library, compiler CLI, diagnostic output shape, ABI/layout,
|
|
||||||
and compiler-known runtime names unchanged.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `docs/language/DIAGNOSTICS.md` as the beta policy for
|
|
||||||
`slovo.diagnostic` version `1`.
|
|
||||||
- Document the S-expression and JSON relationship.
|
|
||||||
- Document required and optional fields, severity/source/range/related-span
|
|
||||||
semantics, JSON-line discipline, source-less diagnostics, and
|
|
||||||
artifact-manifest diagnostic metadata.
|
|
||||||
- Define diagnostic compatibility and migration classes.
|
|
||||||
- Inventory the current diagnostic codes covered by
|
|
||||||
`compiler/tests/diagnostics_contract.rs` and the matching `.diag` snapshots.
|
|
||||||
- Update README, language roadmap, language spec, release notes, migration
|
|
||||||
policy, and post-beta roadmap to introduce beta13 and link the diagnostics
|
|
||||||
policy.
|
|
||||||
|
|
||||||
## Acceptance
|
|
||||||
|
|
||||||
- `docs/language/DIAGNOSTICS.md` names schema `slovo.diagnostic` and version
|
|
||||||
`1`.
|
|
||||||
- The document is clear that S-expression diagnostics and JSON diagnostics are
|
|
||||||
encodings of the same data model.
|
|
||||||
- JSON diagnostics are documented as one object per line on stderr, without an
|
|
||||||
array wrapper or pretty-printing requirement.
|
|
||||||
- Source-less diagnostics are documented without inventing fake source spans.
|
|
||||||
- Artifact manifests are documented as carrying diagnostic schema version,
|
|
||||||
diagnostic encoding, and diagnostic stream metadata, not a second diagnostic
|
|
||||||
schema.
|
|
||||||
- Human prose is documented as beta-flexible, while machine fields, codes,
|
|
||||||
schema/version markers, ranges, JSON-line discipline, and golden fixture
|
|
||||||
shape are compatibility-sensitive.
|
|
||||||
- The code catalog is concise and derived from the current golden diagnostics
|
|
||||||
contract.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no source-language syntax change
|
|
||||||
- no typed-core, lowering, runtime, stdlib, or ABI/layout change
|
|
||||||
- no diagnostic-output shape change
|
|
||||||
- no LSP server, watch mode, SARIF, daemon protocol, or debug adapter
|
|
||||||
- no stable Markdown schema
|
|
||||||
- no stable `1.0.0` diagnostics freeze
|
|
||||||
- no source-map, DWARF, or LLVM debug metadata contract
|
|
||||||
- no compiler-emitted diagnostic catalog artifact
|
|
||||||
- no release publication, tag, push, or version publication work
|
|
||||||
|
|
||||||
## Expected Controller Verification
|
|
||||||
|
|
||||||
- Review the policy for consistency with `compiler/src/diag.rs`,
|
|
||||||
`compiler/src/main.rs`, `compiler/tests/diagnostics_contract.rs`, and the
|
|
||||||
current `.diag` snapshots.
|
|
||||||
- Confirm the catalog count matches the current contract: 358 snapshots and
|
|
||||||
114 unique diagnostic codes.
|
|
||||||
- Run lightweight docs checks:
|
|
||||||
- `git diff --check -- README.md docs/POST_BETA_ROADMAP.md docs/language/DIAGNOSTICS.md docs/language/MIGRATION_POLICY.md docs/language/SPEC-v1.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md .llm/BETA_13_DIAGNOSTIC_CATALOG_AND_SCHEMA_POLICY.md`
|
|
||||||
- run an `rg` check for stale beta12-only current-stage phrasing across the
|
|
||||||
same touched docs
|
|
||||||
- Do not commit, tag, push, or run release publication from this worker scope.
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
# 1.0.0-beta.14 Benchmark Suite Catalog And Metadata Gate
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.14`.
|
|
||||||
|
|
||||||
`1.0.0-beta.14` is a docs/tooling metadata slice for the existing benchmark
|
|
||||||
suite. It keeps the `1.0.0-beta` source language, typed core, runtime,
|
|
||||||
standard library, API surface, compiler diagnostics, diagnostic output shape,
|
|
||||||
ABI/layout behavior, and compiler-known runtime names unchanged.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `benchmarks/README.md` as the top-level benchmark suite catalog.
|
|
||||||
- Document `python3 benchmarks/runner.py --suite-list` as the non-JSON suite
|
|
||||||
inventory command.
|
|
||||||
- Document `python3 benchmarks/runner.py --suite-list --json` as the beta
|
|
||||||
tooling metadata form for the same inventory.
|
|
||||||
- Record the current suite inventory:
|
|
||||||
`math-loop`, `branch-loop`, `parse-loop`, `array-index-loop`,
|
|
||||||
`string-eq-loop`, `array-struct-field-loop`,
|
|
||||||
`enum-struct-payload-loop`, `vec-i32-index-loop`,
|
|
||||||
`vec-string-eq-loop`, and `json-quote-loop`.
|
|
||||||
- Document that benchmark timings are local-machine evidence only.
|
|
||||||
- Update README, language roadmap, language spec, release notes, post-beta
|
|
||||||
roadmap, and this release contract to introduce beta14.
|
|
||||||
|
|
||||||
## Acceptance
|
|
||||||
|
|
||||||
- Public docs name current release/stage `1.0.0-beta.14`.
|
|
||||||
- `benchmarks/README.md` explains both root suite-list commands:
|
|
||||||
`python3 benchmarks/runner.py --suite-list` and
|
|
||||||
`python3 benchmarks/runner.py --suite-list --json`.
|
|
||||||
- The suite catalog lists the current benchmark suite inventory without adding
|
|
||||||
or removing benchmark kernels.
|
|
||||||
- The suite catalog verifies required scaffold files for each suite:
|
|
||||||
`benchmark.json`, `run.py`, `slovo.toml`, and `src/main.slo`.
|
|
||||||
- The docs say timing output is local-machine evidence only and do not publish
|
|
||||||
timing numbers.
|
|
||||||
- The JSON suite listing is described as beta tooling metadata, not a stable
|
|
||||||
public schema.
|
|
||||||
- Explicit exclusions include no new benchmark kernels, no timing publication,
|
|
||||||
no performance thresholds, no stable JSON schema, no source-language change,
|
|
||||||
no runtime change, no stdlib/API change, no diagnostic-output change, and no
|
|
||||||
ABI/layout change.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no new benchmark kernels or implementation language slots
|
|
||||||
- no benchmark timing publication
|
|
||||||
- no release performance threshold
|
|
||||||
- no stable JSON schema or stable benchmark metadata compatibility promise
|
|
||||||
- no source-language syntax, typed-core, lowering, runtime, stdlib, or API
|
|
||||||
change
|
|
||||||
- no compiler diagnostic or diagnostic-output shape change
|
|
||||||
- no ABI/layout or optimizer guarantee
|
|
||||||
- no worker-owned release publication work before controller review and gates
|
|
||||||
|
|
||||||
## Expected Controller Verification
|
|
||||||
|
|
||||||
- Run lightweight docs checks:
|
|
||||||
- `git diff --check -- README.md benchmarks/README.md docs/POST_BETA_ROADMAP.md docs/language/SPEC-v1.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md .llm/BETA_14_BENCHMARK_SUITE_CATALOG_AND_METADATA_GATE.md`
|
|
||||||
- run an `rg` check for stale beta13-only current-stage phrasing across the
|
|
||||||
same touched docs
|
|
||||||
- Smoke the metadata commands:
|
|
||||||
- `python3 benchmarks/runner.py --suite-list`
|
|
||||||
- `python3 benchmarks/runner.py --suite-list --json`
|
|
||||||
- Run focused compiler/tooling checks:
|
|
||||||
- `cargo test --test benchmark_suite_catalog_beta14`
|
|
||||||
- `cargo test --test benchmark_math_loop_scaffold`
|
|
||||||
- Do not commit, tag, push, or run release publication from this worker scope.
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
# 1.0.0-beta.15 Reserved Generic Collection Boundary Hardening And Collection Ledger
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.15`.
|
|
||||||
|
|
||||||
`1.0.0-beta.15` is a docs/design and compiler-boundary hardening slice for
|
|
||||||
the existing concrete collection and value-family surface. It keeps the
|
|
||||||
`1.0.0-beta` source language, typed core, runtime, standard-library/API
|
|
||||||
surface, diagnostic output shape, diagnostic codes, diagnostic schema,
|
|
||||||
benchmark metadata schema, ABI/layout behavior, compiler-known runtime names,
|
|
||||||
and performance claims unchanged while rewording reserved generic/map/set
|
|
||||||
diagnostic prose away from beta.9-specific text.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `docs/language/COLLECTIONS.md` as the collection/value-family ledger.
|
|
||||||
- Inventory the current concrete vector, option, result, and related
|
|
||||||
option/result-returning facade surfaces by linking to
|
|
||||||
`docs/language/STDLIB_API.md` instead of duplicating generated counts.
|
|
||||||
- Record design pressure from duplicated concrete vector, option, and result
|
|
||||||
helper families.
|
|
||||||
- Define prerequisites before executable generics, generic aliases, maps,
|
|
||||||
sets, iterators, mutable vectors, and slice/view APIs can be promoted.
|
|
||||||
- Record current unsupported diagnostics as boundary evidence, not behavior
|
|
||||||
changes.
|
|
||||||
- Centralize reserved generic/collection diagnostics for lowerer, formatter,
|
|
||||||
and checker paths in `compiler/src/reserved.rs`.
|
|
||||||
- Reword affected reserved-boundary snapshots from `beta.9` to current-beta
|
|
||||||
wording while preserving codes, schema, spans, expected/found values, hints,
|
|
||||||
and output shape.
|
|
||||||
- Add focused `reserved_generic_collection_beta15` coverage and run it from
|
|
||||||
`scripts/release-gate.sh`.
|
|
||||||
- Update README, post-beta roadmap, language/compiler roadmaps, language spec,
|
|
||||||
release notes, compiler release notes, and this release contract to introduce
|
|
||||||
beta15.
|
|
||||||
|
|
||||||
## Acceptance
|
|
||||||
|
|
||||||
- Public docs name current release/stage `1.0.0-beta.15`.
|
|
||||||
- `docs/language/COLLECTIONS.md` links to `docs/language/STDLIB_API.md` for
|
|
||||||
exact public helper signatures and does not duplicate generated helper
|
|
||||||
counts.
|
|
||||||
- The ledger inventories the current concrete collection/value-family surface:
|
|
||||||
five concrete vector modules, concrete option families, concrete result
|
|
||||||
families, and option/result-returning host/parsing facades.
|
|
||||||
- The ledger records why repeated concrete vector/option/result facades create
|
|
||||||
design pressure for later generic work.
|
|
||||||
- The ledger defines promotion prerequisites for executable generics, generic
|
|
||||||
aliases, maps, sets, iterators, mutable vectors, and slice/view APIs.
|
|
||||||
- The docs state that current unsupported diagnostics are boundaries, that
|
|
||||||
beta15 rewords only reserved-boundary diagnostic prose, and that beta15 does
|
|
||||||
not change diagnostic output shape, codes, schema, spans, expected/found
|
|
||||||
values, or hints.
|
|
||||||
- Focused compiler tests prove `check`, `fmt --check`, and project-root
|
|
||||||
`check` reject reserved generic/map/set surfaces with stage-neutral current
|
|
||||||
beta wording.
|
|
||||||
- Explicit exclusions include no source-language change, no runtime change, no
|
|
||||||
stdlib/API surface change, no diagnostic output shape/code/schema change, no
|
|
||||||
benchmark metadata schema change, no ABI/layout change, no performance
|
|
||||||
claim, and no stable API freeze.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no executable generics, traits, inference, monomorphization, or generic
|
|
||||||
stdlib dispatch
|
|
||||||
- no generic aliases or parameterized aliases
|
|
||||||
- no map or set semantics
|
|
||||||
- no iterator API
|
|
||||||
- no mutable vector API
|
|
||||||
- no slice/view API
|
|
||||||
- no new compiler-known runtime names or helper symbols
|
|
||||||
- no standard-library/API addition, removal, rename, or stable freeze
|
|
||||||
- no diagnostic output shape, code, schema, span, expected/found, or hint change
|
|
||||||
- no diagnostic policy change beyond reserved-boundary prose rewording
|
|
||||||
- no benchmark metadata schema change
|
|
||||||
- no source-language syntax, typed-core, lowering, runtime, ABI/layout, or
|
|
||||||
optimizer change
|
|
||||||
- no performance threshold or cross-machine performance claim
|
|
||||||
- no worker-owned release publication work before controller review and gates
|
|
||||||
|
|
||||||
## Expected Controller Verification
|
|
||||||
|
|
||||||
- Run lightweight docs checks:
|
|
||||||
- `git diff --check -- README.md docs/POST_BETA_ROADMAP.md docs/language/ROADMAP.md docs/language/SPEC-v1.md docs/language/RELEASE_NOTES.md docs/language/COLLECTIONS.md .llm/BETA_15_RESERVED_GENERIC_COLLECTION_BOUNDARY_HARDENING.md`
|
|
||||||
- run an `rg` check for stale beta14-only current-stage phrasing across the
|
|
||||||
same touched docs
|
|
||||||
- Run focused compiler checks:
|
|
||||||
- `cargo test --test reserved_generic_collection_beta15`
|
|
||||||
- `cargo test --test diagnostics_contract`
|
|
||||||
- `cargo test --test formatter`
|
|
||||||
- `cargo test --test project_mode`
|
|
||||||
- `rg -n "not supported in beta\\.9" compiler/src tests`
|
|
||||||
- Do not commit, tag, push, or run release publication from this worker scope.
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
# 1.0.0-beta.16 String Scanning And Token Boundary Foundation
|
|
||||||
|
|
||||||
Status: combined Slovo/Glagol release scope for `1.0.0-beta.16`.
|
|
||||||
|
|
||||||
`1.0.0-beta.16` adds the first explicit string scanning and token-boundary
|
|
||||||
source facades to `std.string`: byte access, substring extraction, and
|
|
||||||
prefix/suffix checks over the current runtime string representation. The
|
|
||||||
release also promotes the matching compiler-known runtime names through the C
|
|
||||||
runtime, LLVM lowering, source fallback evaluator, diagnostics inventory,
|
|
||||||
local facade fixtures, and release-gate coverage.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
- Add `byte_at_result ((value string) (index i32)) -> (result i32 i32)` to
|
|
||||||
`lib/std/string.slo`.
|
|
||||||
- Add `slice_result ((value string) (start i32) (count i32)) -> (result string i32)`
|
|
||||||
to `lib/std/string.slo`.
|
|
||||||
- Add `starts_with ((value string) (prefix string)) -> bool` to
|
|
||||||
`lib/std/string.slo`.
|
|
||||||
- Add `ends_with ((value string) (suffix string)) -> bool` to
|
|
||||||
`lib/std/string.slo`.
|
|
||||||
- Mirror those source facades in
|
|
||||||
`examples/projects/std-layout-local-string/src/string.slo`.
|
|
||||||
- Extend the local and explicit `std.string` examples with success and ordinary
|
|
||||||
failure checks for the four helpers.
|
|
||||||
- Promote matching Glagol runtime entries for `std.string.byte_at_result`,
|
|
||||||
`std.string.slice_result`, `std.string.starts_with`, and
|
|
||||||
`std.string.ends_with`.
|
|
||||||
- Add LLVM declarations and lowering for the four promoted runtime names.
|
|
||||||
- Add C runtime implementations for byte access, byte slicing, prefix checks,
|
|
||||||
and suffix checks.
|
|
||||||
- Extend the source fallback test runner behavior for those names.
|
|
||||||
- Add focused beta16 compiler coverage for lowering, hosted runtime execution,
|
|
||||||
and source fallback execution.
|
|
||||||
- Extend diagnostics snapshots for arity, type, context, shadowing, and richer
|
|
||||||
unsupported string-scanning names.
|
|
||||||
- Extend standard-library source-search and local-facade fixtures plus
|
|
||||||
promotion-gate inventory alignment.
|
|
||||||
- Update README, `lib/std/README.md`, post-beta roadmap, language roadmap,
|
|
||||||
release notes, v1 spec, generated stdlib API, compiler docs, release gate,
|
|
||||||
and standard-runtime catalog for beta16.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
- Helpers are byte-oriented over bytes before the trailing NUL in current
|
|
||||||
runtime strings.
|
|
||||||
- `byte_at_result` returns `ok byte` for a valid zero-based byte index and
|
|
||||||
`err 1` for invalid indexes.
|
|
||||||
- `slice_result` returns `ok text` for a valid byte range and `err 1` for
|
|
||||||
invalid ranges.
|
|
||||||
- `slice_result` allocation failure may follow the existing string allocation
|
|
||||||
trap policy.
|
|
||||||
- `starts_with` and `ends_with` perform byte prefix/suffix checks; empty
|
|
||||||
prefix and suffix match.
|
|
||||||
|
|
||||||
## Explicit Non-Scope
|
|
||||||
|
|
||||||
- no Unicode scalar, grapheme, display-width, locale, normalization, or
|
|
||||||
case-folding promise
|
|
||||||
- no JSON parser
|
|
||||||
- no object or array parser
|
|
||||||
- no tokenizer/scanner object API
|
|
||||||
- no language slice/view syntax or borrowed substring view
|
|
||||||
- no mutable strings or string containers
|
|
||||||
- no stable stdlib/API freeze
|
|
||||||
- no stable ABI/layout or ownership guarantee for returned string allocations
|
|
||||||
- no performance claim
|
|
||||||
- no maps, sets, generic collections, or recursive JSON value support
|
|
||||||
- no worker-owned commit, tag, push, or publication before controller gates
|
|
||||||
|
|
||||||
## Expected Verification
|
|
||||||
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo test --test standard_string_scanning_beta16`
|
|
||||||
- `cargo test --test diagnostics_contract`
|
|
||||||
- `cargo test --test standard_string_source_fallback_helpers_alpha`
|
|
||||||
- `cargo test --test standard_core_facade_source_search_alpha`
|
|
||||||
- `cargo test --test string_runtime`
|
|
||||||
- `cargo test --test promotion_gate -- --ignored`
|
|
||||||
- `cargo test`
|
|
||||||
- `git diff --check`
|
|
||||||
- stale current-stage scan for `1.0.0-beta.15`
|
|
||||||
- public-text scan for local usernames, local filesystem paths, and private host
|
|
||||||
publication references
|
|
||||||
- final `./scripts/release-gate.sh` after commit and generated catalog sync
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
# 1.0.0-beta.17 JSON Primitive Scalar Parsing Foundation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Slovo-facing `1.0.0-beta.17` adds narrow `std.json` facade names for primitive
|
|
||||||
scalar token parsing:
|
|
||||||
|
|
||||||
- `parse_bool_value_result ((token string)) -> (result bool i32)`
|
|
||||||
- `parse_i32_value_result ((token string)) -> (result i32 i32)`
|
|
||||||
- `parse_u32_value_result ((token string)) -> (result u32 i32)`
|
|
||||||
- `parse_i64_value_result ((token string)) -> (result i64 i32)`
|
|
||||||
- `parse_u64_value_result ((token string)) -> (result u64 i32)`
|
|
||||||
- `parse_f64_value_result ((token string)) -> (result f64 i32)`
|
|
||||||
- `parse_null_value_result ((token string)) -> (result bool i32)`
|
|
||||||
|
|
||||||
The construction surface from `1.0.0-beta.7` remains intact: the existing 24
|
|
||||||
JSON construction exports are still present.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
These helpers parse one already-isolated primitive scalar token. Success
|
|
||||||
returns `ok payload`; ordinary parse failure returns `err 1`.
|
|
||||||
`parse_null_value_result` returns `ok true` only for exact `null` and `err 1`
|
|
||||||
otherwise.
|
|
||||||
|
|
||||||
Numeric and boolean conversion is intentionally concrete and result-returning.
|
|
||||||
The promoted numeric and boolean helpers consume a whole JSON primitive token:
|
|
||||||
no leading/trailing whitespace, no leading `+`, no leading-zero integer form
|
|
||||||
except `0`, and no non-finite f64 values. `parse_f64_value_result` accepts the
|
|
||||||
current JSON-number grammar subset implemented by the runtime, including
|
|
||||||
exponent notation. This slice does not freeze a stable API or ABI.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This is not full JSON parsing. It does not add `parse_string`, `parse_object`,
|
|
||||||
`parse_array`, `parse_value`, tokenizers, recursive `JsonValue`, maps/sets,
|
|
||||||
generic parse APIs, whitespace-tolerant document parsing, schema validation,
|
|
||||||
streaming, Unicode escape handling, stable API freeze, stable ABI/layout, or
|
|
||||||
performance claims.
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# 1.0.0-beta.18 JSON String Token Parsing Foundation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Slovo-facing `1.0.0-beta.18` adds one narrow `std.json` facade name for JSON
|
|
||||||
string token parsing:
|
|
||||||
|
|
||||||
- `parse_string_value_result ((token string)) -> (result string i32)`
|
|
||||||
|
|
||||||
The helper is a thin source wrapper over the promoted compiler/runtime name
|
|
||||||
`std.json.parse_string_value_result`. The existing JSON construction helpers
|
|
||||||
from `1.0.0-beta.7` and primitive scalar parse helpers from `1.0.0-beta.17`
|
|
||||||
remain intact.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
`parse_string_value_result` consumes one already-isolated ASCII JSON string
|
|
||||||
token. The token must start and end with quotes and must not include leading or
|
|
||||||
trailing whitespace outside those quotes. Success returns `ok decoded_text`;
|
|
||||||
ordinary parse failure returns `err 1`.
|
|
||||||
|
|
||||||
The token parser decodes the simple JSON escapes `\"`, `\\`, `\/`, `\b`,
|
|
||||||
`\f`, `\n`, `\r`, and `\t`. It rejects raw control bytes, bad escapes,
|
|
||||||
unterminated strings, trailing bytes after the closing quote, raw non-ASCII,
|
|
||||||
and all `\uXXXX` escapes for this slice.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This is not full JSON parsing. It does not add object parsing, array parsing,
|
|
||||||
recursive `JsonValue`, tokenizer APIs, generic parse APIs,
|
|
||||||
whitespace-tolerant document parsing, streaming decoders or encoders, schema
|
|
||||||
validation, Unicode escape decoding or normalization, embedded NUL policy,
|
|
||||||
stable API freeze, stable ABI/layout, or performance claims.
|
|
||||||
|
|
||||||
## Gate-Supporting Compiler Hardening
|
|
||||||
|
|
||||||
The beta18 release also includes a bounded `glagol test` stack hardening fix:
|
|
||||||
test execution runs on a worker thread with a 16 MiB stack. This is not a new
|
|
||||||
language feature. It keeps deep source-authored stdlib fixtures gateable through
|
|
||||||
normal `glagol test` behavior instead of host process stack overflow, and is
|
|
||||||
covered by the promotion gate's stdlib fixture test execution.
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
# 1.0.0-beta.19 Test Discovery And User-Project Conformance Foundation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.19` is a compiler/tooling and conformance slice. It does
|
|
||||||
not change the Slovo source language or standard library surface.
|
|
||||||
|
|
||||||
Add deterministic list-only test discovery for:
|
|
||||||
|
|
||||||
- `glagol test --list <file|project|workspace>`
|
|
||||||
- `glagol --run-tests --list <file>` for the legacy single-file path
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
List mode must reuse the same checked front-end path as normal test execution:
|
|
||||||
parse, lower, type-check, resolve project/workspace inputs, discover tests, and
|
|
||||||
apply `--filter <substring>`.
|
|
||||||
|
|
||||||
The command then lists discovered/selected tests without evaluating test bodies.
|
|
||||||
It must not execute runtime calls from test bodies, mutate files through test
|
|
||||||
logic, open sockets through test logic, or otherwise trigger user test
|
|
||||||
side-effects.
|
|
||||||
|
|
||||||
Ordering must remain deterministic and match current test execution discovery:
|
|
||||||
|
|
||||||
- single-file tests keep source order
|
|
||||||
- project tests keep existing module/package discovery order
|
|
||||||
- workspace tests keep existing workspace/package discovery order
|
|
||||||
|
|
||||||
Normal `glagol test` behavior and output remain unchanged unless `--list` is
|
|
||||||
present. Invalid files, projects, and workspaces still fail through the
|
|
||||||
existing diagnostic path.
|
|
||||||
|
|
||||||
## Output Shape
|
|
||||||
|
|
||||||
The initial output format is beta tooling. It should be stable enough for local
|
|
||||||
release-gate tests, but it is not a frozen public schema.
|
|
||||||
|
|
||||||
The output should make selected, skipped, total discovered, and filter state
|
|
||||||
visible. A concise text shape is enough; a stable JSON/event stream is out of
|
|
||||||
scope for this slice.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- source-language syntax
|
|
||||||
- runtime helper names
|
|
||||||
- JSON expansion
|
|
||||||
- parallel test execution
|
|
||||||
- retries
|
|
||||||
- tags or groups
|
|
||||||
- coverage reports
|
|
||||||
- event streams
|
|
||||||
- stable artifact-manifest schema freeze
|
|
||||||
- stable Markdown schema freeze
|
|
||||||
- LSP or watch behavior
|
|
||||||
- SARIF or daemon protocols
|
|
||||||
- package registries
|
|
||||||
- semver solving
|
|
||||||
- performance claims
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `glagol test --list <file.slo>` lists checked/discovered tests without
|
|
||||||
executing bodies.
|
|
||||||
- `glagol test --list <project>` and workspace inputs preserve current
|
|
||||||
project/workspace ordering.
|
|
||||||
- `glagol --run-tests --list <file.slo>` works for the legacy single-file path.
|
|
||||||
- `--filter <substring>` marks/selects the same tests as normal filtered
|
|
||||||
execution while avoiding body evaluation.
|
|
||||||
- Normal `glagol test` output stays byte-stable for existing covered cases.
|
|
||||||
- Invalid inputs still emit existing diagnostics.
|
|
||||||
- Docs describe beta19 as a released tooling/conformance slice.
|
|
||||||
- Release-gate coverage includes the focused beta19 test-discovery suite.
|
|
||||||
|
|
||||||
## Suggested Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test test_discovery_beta19
|
|
||||||
cargo test --test project_mode
|
|
||||||
cargo test --test cli_v1_1
|
|
||||||
cargo test --test diagnostics_schema_beta13
|
|
||||||
cargo test
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# Beta.1 Tooling Hardening Scope
|
|
||||||
|
|
||||||
This file tracks the first post-`1.0.0-beta` tooling bundle, released as
|
|
||||||
`1.0.0-beta.1`. The slice keeps the beta language surface unchanged and
|
|
||||||
hardens the local compiler workflow, install layout, and release gate.
|
|
||||||
|
|
||||||
## Implemented In This Slice
|
|
||||||
|
|
||||||
- `glagol run <file.slo|project>` builds through the existing native hosted
|
|
||||||
path, executes the result, forwards stdout/stderr, and exits with the program
|
|
||||||
status.
|
|
||||||
- `glagol run` writes to `.slovo/build/<name>` by default and supports `-o
|
|
||||||
<binary>` for an explicit executable path.
|
|
||||||
- `glagol run ... -- <args>` forwards program arguments to the produced
|
|
||||||
executable.
|
|
||||||
- `glagol clean <file.slo|project>` removes generated `.slovo/build` artifacts.
|
|
||||||
- `glagol new --template binary|library|workspace` supports the existing
|
|
||||||
binary scaffold plus checkable/testable library and local workspace
|
|
||||||
scaffolds.
|
|
||||||
- `scripts/install.sh` installs `bin/glagol`, `share/slovo/std/*.slo`, and
|
|
||||||
`share/slovo/runtime/runtime.c` under a configurable prefix.
|
|
||||||
- Installed `glagol` discovers both standard-library source modules and the
|
|
||||||
runtime C input relative to the executable, with `SLOVO_STD_PATH`,
|
|
||||||
`SLOVO_RUNTIME_C`, `GLAGOL_RUNTIME_C`, and `GLAGOL_CLANG` override paths.
|
|
||||||
- The release gate prints a concise success line after docs, formatting, tests,
|
|
||||||
promotion, binary, and LLVM smoke checks pass.
|
|
||||||
|
|
||||||
## Explicitly Out Of Scope
|
|
||||||
|
|
||||||
- no source-language syntax change
|
|
||||||
- no networking or runtime resource model
|
|
||||||
- no package registry behavior
|
|
||||||
- no stable ABI/layout promise
|
|
||||||
- no operating-system package-manager integration
|
|
||||||
- no stable install layout promise beyond this beta toolchain layout
|
|
||||||
|
|
||||||
## Release Gate
|
|
||||||
|
|
||||||
- rerender publication PDFs when documentation release text changes
|
|
||||||
- run the full release gate from a clean checkout state before tagging
|
|
||||||
- tag only after the connected tooling bundle and generated documents agree
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
# 1.0.0-beta.20 String Search And ASCII Trim Foundation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.20` is a standard-library and compiler-gate slice. It does not
|
|
||||||
change source-language syntax, runtime C, compiler-known runtime names, or
|
|
||||||
ABI/layout policy.
|
|
||||||
|
|
||||||
The release adds source-authored `std.string` helpers:
|
|
||||||
|
|
||||||
- `contains ((value string) (needle string)) -> bool`
|
|
||||||
- `index_of_option ((value string) (needle string)) -> (option i32)`
|
|
||||||
- `last_index_of_option ((value string) (needle string)) -> (option i32)`
|
|
||||||
- `trim_ascii_start ((value string)) -> string`
|
|
||||||
- `trim_ascii_end ((value string)) -> string`
|
|
||||||
- `trim_ascii ((value string)) -> string`
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
Search is byte-oriented over the current runtime string representation.
|
|
||||||
`index_of_option` returns the first zero-based byte offset for a matching
|
|
||||||
needle, `last_index_of_option` returns the last zero-based byte offset, and
|
|
||||||
`contains` is true when `index_of_option` returns `some`.
|
|
||||||
|
|
||||||
Empty needles are valid:
|
|
||||||
|
|
||||||
- `index_of_option value ""` returns `some 0`
|
|
||||||
- `last_index_of_option value ""` returns `some (len value)`
|
|
||||||
- `contains value ""` returns `true`
|
|
||||||
|
|
||||||
Missing needles return `none`.
|
|
||||||
|
|
||||||
ASCII trim removes only these byte values from the requested edges:
|
|
||||||
|
|
||||||
- `9` horizontal tab
|
|
||||||
- `10` line feed
|
|
||||||
- `11` vertical tab
|
|
||||||
- `12` form feed
|
|
||||||
- `13` carriage return
|
|
||||||
- `32` space
|
|
||||||
|
|
||||||
The helpers compose over already-promoted string primitives:
|
|
||||||
|
|
||||||
- `std.string.len`
|
|
||||||
- `std.string.byte_at_result`
|
|
||||||
- `std.string.slice_result`
|
|
||||||
- `std.string.starts_with`
|
|
||||||
- `std.string.ends_with`
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- compiler-known `std.string.*` runtime names for the new helpers
|
|
||||||
- runtime C helper implementations
|
|
||||||
- source-language syntax
|
|
||||||
- Unicode scalar, grapheme, display-width, or normalization semantics
|
|
||||||
- case folding or locale-sensitive search
|
|
||||||
- regular expressions
|
|
||||||
- tokenizer/parser APIs
|
|
||||||
- mutable strings
|
|
||||||
- language slice/view syntax
|
|
||||||
- stable string ABI/layout
|
|
||||||
- stable allocation ownership rules
|
|
||||||
- stable standard-library compatibility
|
|
||||||
- performance claims
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `lib/std/string.slo` exports all six helpers.
|
|
||||||
- Explicit `std.string` import examples exercise contains, first/last search,
|
|
||||||
missing needles, empty needles, leading trim, trailing trim, full trim,
|
|
||||||
all-whitespace trim, and no-trim cases.
|
|
||||||
- Local `std-layout-local-string` examples mirror the public helper surface.
|
|
||||||
- Focused compiler coverage verifies the helpers are source-authored and that
|
|
||||||
direct compiler-known runtime calls for the new names remain unsupported.
|
|
||||||
- `scripts/release-gate.sh` runs the focused beta20 test.
|
|
||||||
- Generated standard-library API documentation includes the new signatures.
|
|
||||||
|
|
||||||
## Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test standard_string_search_trim_beta20
|
|
||||||
cargo test --test standard_string_source_fallback_helpers_alpha
|
|
||||||
cargo test --test standard_string_scanning_beta16
|
|
||||||
cargo test
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
# 1.0.0-beta.21 JSON Document Scalar Parsing Foundation
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.21` is a standard-library and compiler-gate slice. It adds
|
|
||||||
source-authored `std.json` helpers for scalar JSON documents while keeping
|
|
||||||
source-language syntax, runtime C, compiler-known runtime names, and ABI/layout
|
|
||||||
policy unchanged.
|
|
||||||
|
|
||||||
The release adds:
|
|
||||||
|
|
||||||
- `parse_string_document_result ((document string)) -> (result string i32)`
|
|
||||||
- `parse_bool_document_result ((document string)) -> (result bool i32)`
|
|
||||||
- `parse_i32_document_result ((document string)) -> (result i32 i32)`
|
|
||||||
- `parse_u32_document_result ((document string)) -> (result u32 i32)`
|
|
||||||
- `parse_i64_document_result ((document string)) -> (result i64 i32)`
|
|
||||||
- `parse_u64_document_result ((document string)) -> (result u64 i32)`
|
|
||||||
- `parse_f64_document_result ((document string)) -> (result f64 i32)`
|
|
||||||
- `parse_null_document_result ((document string)) -> (result bool i32)`
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
Each helper trims ASCII whitespace around the whole input document with
|
|
||||||
`std.string.trim_ascii`, then delegates to the already released exact
|
|
||||||
value-token parser for that scalar family.
|
|
||||||
|
|
||||||
Leading and trailing ASCII whitespace around one scalar document is accepted.
|
|
||||||
Trailing non-whitespace remains an ordinary parse failure and returns `err 1`
|
|
||||||
through the underlying exact parser.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- compiler-known `std.json.*_document_result` runtime names
|
|
||||||
- private `__glagol_json_*document*` runtime symbols
|
|
||||||
- runtime C helper implementations
|
|
||||||
- source-language syntax
|
|
||||||
- object or array parsing
|
|
||||||
- recursive `JsonValue`
|
|
||||||
- tokenizer/parser objects
|
|
||||||
- maps, sets, executable generics, or generic collections
|
|
||||||
- streaming parsers or encoders
|
|
||||||
- Unicode escape decoding beyond the existing string-token helper
|
|
||||||
- embedded NUL policy
|
|
||||||
- stable JSON APIs
|
|
||||||
- stable ABI/layout
|
|
||||||
- stable standard-library compatibility
|
|
||||||
- performance claims
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `lib/std/json.slo` exports all eight document scalar helpers.
|
|
||||||
- Explicit `std.json` import examples exercise trimmed whitespace success,
|
|
||||||
no-whitespace success, and trailing non-whitespace failure.
|
|
||||||
- Local `std-layout-local-json` examples mirror the public helper surface with
|
|
||||||
an explicit local `string` dependency for `trim_ascii`.
|
|
||||||
- Focused compiler coverage verifies the helpers are source-authored and that
|
|
||||||
direct compiler-known runtime calls for the new names remain unsupported.
|
|
||||||
- `scripts/release-gate.sh` runs the focused beta21 test.
|
|
||||||
- Generated standard-library API documentation includes the new signatures.
|
|
||||||
|
|
||||||
## Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test standard_json_document_scalar_parsing_beta21
|
|
||||||
cargo test --test standard_json_source_facade_alpha
|
|
||||||
cargo test --test promotion_gate
|
|
||||||
cargo test
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
# 1.0.0-beta.22 Run Manifest And Execution Report Hardening
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.22` is a compiler/tooling evidence-hardening slice for
|
|
||||||
`glagol run --manifest`. It keeps the Slovo source language, typed core,
|
|
||||||
runtime capabilities, standard-library surface, compiler-known runtime names,
|
|
||||||
ABI/layout policy, and package behavior unchanged.
|
|
||||||
|
|
||||||
The release adds an additive run-report block to run-mode artifact manifests
|
|
||||||
so manifest evidence can record:
|
|
||||||
|
|
||||||
- process exit status for the invoked program
|
|
||||||
- captured stdout from the run
|
|
||||||
- captured stderr from the run
|
|
||||||
- forwarded program arguments passed through `glagol run`
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
When `glagol run --manifest <path>` completes far enough to write an artifact
|
|
||||||
manifest, the manifest should include run execution evidence in addition to the
|
|
||||||
existing schema marker, command, mode, success, diagnostics metadata, primary
|
|
||||||
output, and artifacts fields.
|
|
||||||
|
|
||||||
The run-report data describes the native executable invocation performed by
|
|
||||||
`glagol run`, not a new source-language or runtime feature. Captured stdout
|
|
||||||
and stderr are evidence fields for tooling and release-gate review; they do
|
|
||||||
not redefine ordinary terminal behavior. Forwarded args are recorded so
|
|
||||||
fixtures can distinguish compiler arguments from user-program arguments.
|
|
||||||
|
|
||||||
The block is additive beta tooling metadata. It is not a stable public schema
|
|
||||||
freeze and does not bump `slovo.artifact-manifest` version `1`.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- source-language syntax
|
|
||||||
- standard-library helpers
|
|
||||||
- compiler-known `std.*` runtime names
|
|
||||||
- runtime C capabilities
|
|
||||||
- package, workspace, import, or registry behavior
|
|
||||||
- stable artifact-manifest schema freeze
|
|
||||||
- stable Markdown schema freeze
|
|
||||||
- LSP, watch, SARIF, or daemon protocols
|
|
||||||
- performance claims
|
|
||||||
- stable ABI/layout
|
|
||||||
- beta maturity beyond the existing `1.0.0-beta` line
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `glagol run --manifest <path>` writes an artifact manifest with an additive
|
|
||||||
run-report block after executing a supported program.
|
|
||||||
- The run report records exit status, captured stdout, captured stderr, and
|
|
||||||
forwarded args.
|
|
||||||
- Existing manifest fields and schema/version markers remain compatible with
|
|
||||||
the beta artifact-manifest contract.
|
|
||||||
- Non-run modes do not need run-report metadata.
|
|
||||||
- Documentation describes beta22 as tooling/CLI evidence hardening only.
|
|
||||||
- Release notes and roadmaps state that beta22 does not add language or
|
|
||||||
stdlib features and does not freeze the manifest schema.
|
|
||||||
|
|
||||||
## Suggested Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test run_manifest_beta22
|
|
||||||
cargo test --test cli_v1_1
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
# 1.0.0-beta.23 Standard Library Stability Tier Ledger And Catalog Alignment
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.23` is a documentation/catalog clarity slice for the standard
|
|
||||||
library. It adds a public stability-tier ledger and aligns the surrounding
|
|
||||||
README, roadmap, release-note, and specification text with the generated API
|
|
||||||
catalog boundary.
|
|
||||||
|
|
||||||
This release does not change the source language, typed core, runtime
|
|
||||||
capabilities, standard-library helper surface, compiler-known runtime names,
|
|
||||||
ABI/layout policy, or package behavior. It does change generated catalog
|
|
||||||
output and release-gate checks so tier metadata is visible and enforced.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
The public standard-library docs use exactly these tier labels:
|
|
||||||
|
|
||||||
- `beta-supported`
|
|
||||||
- `experimental`
|
|
||||||
- `internal`
|
|
||||||
|
|
||||||
The generated `docs/language/STDLIB_API.md` catalog remains the exact exported
|
|
||||||
signature inventory generated from `lib/std/*.slo`. The new
|
|
||||||
`docs/language/STDLIB_TIERS.md` ledger records maturity and stability
|
|
||||||
expectations for those helpers and surrounding standard-library domains.
|
|
||||||
|
|
||||||
Experimental domains in the beta23 ledger include JSON, loopback networking,
|
|
||||||
random/time, and filesystem resource-handle helpers. Concrete vector modules
|
|
||||||
remain beta-supported concrete lanes; they are not a generic collections
|
|
||||||
freeze and do not imply executable generics, maps, sets, iterators, mutable
|
|
||||||
vectors, slice/view APIs, runtime collection changes, or stable ABI/layout.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- source-language syntax
|
|
||||||
- standard-library helpers
|
|
||||||
- compiler-known `std.*` runtime names
|
|
||||||
- runtime C capabilities
|
|
||||||
- package, workspace, import, or registry behavior
|
|
||||||
- stable standard-library/API compatibility freeze
|
|
||||||
- stable manifest schema freeze
|
|
||||||
- stable Markdown schema freeze
|
|
||||||
- stable ABI/layout
|
|
||||||
- performance claims
|
|
||||||
- beta maturity beyond the existing `1.0.0-beta` line
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `docs/language/STDLIB_TIERS.md` defines the public tier labels and their
|
|
||||||
current meaning.
|
|
||||||
- The tier ledger marks JSON, loopback networking, random/time, and filesystem
|
|
||||||
resource-handle helpers as experimental domains.
|
|
||||||
- The tier ledger records concrete vector modules as beta-supported concrete
|
|
||||||
lanes without claiming generic collection stability.
|
|
||||||
- README, `lib/std/README.md`, language release notes, language roadmap,
|
|
||||||
post-beta roadmap, and the v1 spec link or describe the tier ledger.
|
|
||||||
- Documentation states beta23 is docs/catalog tooling clarity only and does
|
|
||||||
not add language, stdlib, runtime, stable schema, or stable API behavior.
|
|
||||||
- The generated API catalog emits tier metadata, and the release gate checks
|
|
||||||
that experimental tiers remain represented.
|
|
||||||
|
|
||||||
## Suggested Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node --check scripts/render-stdlib-api-doc.js
|
|
||||||
node --check scripts/check-stdlib-api-tiers.js
|
|
||||||
./scripts/render-stdlib-api-doc.sh
|
|
||||||
./scripts/check-stdlib-api-tiers.js
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
# 1.0.0-beta.24 Package Manifest Identity And Dependency Discipline
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.24` is a package/workspace discipline hardening slice for local
|
|
||||||
manifest diagnostics. It tightens how package manifest identity and local
|
|
||||||
dependency tables are reported when users write ambiguous or invalid manifest
|
|
||||||
keys.
|
|
||||||
|
|
||||||
This release changes diagnostics only. It does not change the Slovo source
|
|
||||||
language, typed core, runtime behavior, standard-library helper surface,
|
|
||||||
compiler-known runtime names, package graph semantics, ABI/layout policy, or
|
|
||||||
artifact/Markdown schema stability guarantees.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
The beta package model remains a closed local workspace model:
|
|
||||||
|
|
||||||
- package manifests declare `[package]` identity metadata and optional
|
|
||||||
`[dependencies]` local path records
|
|
||||||
- dependency keys name local packages and must match the target package name
|
|
||||||
- dependency paths remain local path records under the existing workspace and
|
|
||||||
package boundary checks
|
|
||||||
|
|
||||||
The beta24 diagnostic hardening makes these manifest errors explicit:
|
|
||||||
|
|
||||||
- duplicate keys in package manifests report `PackageManifestInvalid`
|
|
||||||
- invalid dependency keys report `InvalidPackageDependencyName`
|
|
||||||
- duplicate dependency keys report `DuplicatePackageDependencyName`
|
|
||||||
|
|
||||||
These diagnostics are part of the beta package/workspace discipline surface.
|
|
||||||
They do not promote a package manager, resolver, lockfile, registry, publish
|
|
||||||
flow, or stable package ABI/layout.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- remote registry behavior
|
|
||||||
- lockfiles
|
|
||||||
- semantic-version solving
|
|
||||||
- package publishing
|
|
||||||
- optional, dev, target, or feature-gated dependencies
|
|
||||||
- build scripts or package archives
|
|
||||||
- stable package ABI/layout
|
|
||||||
- stable package manager behavior
|
|
||||||
- source-language syntax or semantics
|
|
||||||
- runtime behavior or runtime C capabilities
|
|
||||||
- standard-library helpers or stdlib behavior
|
|
||||||
- compiler-known `std.*` runtime names
|
|
||||||
- stable artifact-manifest or Markdown schema guarantees
|
|
||||||
- performance claims
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- `docs/language/PACKAGES.md` documents duplicate package-manifest keys,
|
|
||||||
invalid dependency keys, and duplicate dependency keys as explicit beta
|
|
||||||
diagnostics.
|
|
||||||
- Language and compiler release notes describe beta24 as package manifest
|
|
||||||
identity/dependency diagnostic hardening only.
|
|
||||||
- Language and compiler roadmaps record beta24 as a local package/workspace
|
|
||||||
diagnostics slice with all registry, lockfile, semver, publishing,
|
|
||||||
optional/dev/target dependency, ABI/layout, language, runtime, and stdlib
|
|
||||||
work deferred.
|
|
||||||
- README and the post-beta roadmap identify `1.0.0-beta.24` as the current
|
|
||||||
package/workspace discipline hardening slice.
|
|
||||||
- Glagol is versioned as `1.0.0-beta.24`.
|
|
||||||
- `compiler/tests/package_workspace_discipline_beta24.rs` covers the focused
|
|
||||||
package manifest/dependency diagnostics and a positive local dependency
|
|
||||||
workspace.
|
|
||||||
- `scripts/release-gate.sh` runs the focused beta24 test.
|
|
||||||
|
|
||||||
## Suggested Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git diff --check
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test package_workspace_discipline_beta24
|
|
||||||
```
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
# 1.0.0-beta.25 User Project Conformance Matrix
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
`1.0.0-beta.25` is a tooling/conformance evidence slice for ordinary
|
|
||||||
project and workspace usage. It adds a deterministic user-project conformance
|
|
||||||
matrix over the existing checked examples under `examples/projects/` and
|
|
||||||
`examples/workspaces/`.
|
|
||||||
|
|
||||||
The matrix is stable-readiness evidence for the beta toolchain. It records
|
|
||||||
which existing user-shaped example projects and workspaces are expected to
|
|
||||||
pass `glagol check`, `glagol test --list`, and stable `glagol test` execution
|
|
||||||
through ordinary Glagol entry points.
|
|
||||||
|
|
||||||
At release time the matrix covers all 43 top-level fixture roots with
|
|
||||||
`slovo.toml` under those inventories and 655 discovered tests. Environment
|
|
||||||
fixtures run with deterministic in-test environment values so host shell state
|
|
||||||
does not decide the result.
|
|
||||||
|
|
||||||
This release changes tooling evidence only. It does not change the Slovo
|
|
||||||
source language, typed core, runtime behavior, standard-library helper
|
|
||||||
surface, compiler-known runtime names, package-manager behavior, package graph
|
|
||||||
semantics, registry behavior, lockfile behavior, semver behavior, ABI/layout
|
|
||||||
policy, stable schema policy, or performance policy.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
The conformance matrix is deterministic and repository-local:
|
|
||||||
|
|
||||||
- inputs are every top-level `slovo.toml` fixture under the existing
|
|
||||||
`examples/projects/` and `examples/workspaces/` directories
|
|
||||||
- entries are sorted by stable repository-relative path
|
|
||||||
- each entry names the example kind, path, and ordinary project/workspace
|
|
||||||
test count covered by the matrix
|
|
||||||
- fixture inventory drift must fail the focused matrix test until the matrix
|
|
||||||
and release evidence are updated deliberately
|
|
||||||
- generated evidence must not depend on wall-clock time, host-specific
|
|
||||||
absolute paths, random ordering, or network access
|
|
||||||
- matrix output is beta readiness evidence, not a frozen public schema
|
|
||||||
|
|
||||||
The matrix is intended to answer whether normal beta users can exercise the
|
|
||||||
current example projects and workspaces through the documented toolchain. It
|
|
||||||
does not promote new language forms, new standard-library helpers, new runtime
|
|
||||||
capabilities, new package manager behavior, or new compatibility guarantees.
|
|
||||||
|
|
||||||
## Non-Scope
|
|
||||||
|
|
||||||
This scope does not add:
|
|
||||||
|
|
||||||
- source-language syntax or semantics
|
|
||||||
- typed-core changes
|
|
||||||
- standard-library helpers or stdlib behavior changes
|
|
||||||
- compiler-known `std.*` runtime names
|
|
||||||
- runtime behavior or runtime C capabilities
|
|
||||||
- package manager behavior
|
|
||||||
- remote registry behavior
|
|
||||||
- lockfiles
|
|
||||||
- semantic-version solving
|
|
||||||
- package publishing
|
|
||||||
- optional, dev, target, or feature-gated dependencies
|
|
||||||
- stable package ABI/layout
|
|
||||||
- stable artifact-manifest, Markdown, JSON, or conformance-matrix schema
|
|
||||||
guarantees
|
|
||||||
- performance thresholds, performance claims, or timing publication
|
|
||||||
- LSP/watch, SARIF, daemon, coverage, retry, tag/group, or event-stream
|
|
||||||
protocols
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
- README names `1.0.0-beta.25` as the current release and describes the
|
|
||||||
user-project conformance matrix as tooling/readiness evidence only.
|
|
||||||
- Language and compiler release notes describe beta25 as a
|
|
||||||
tooling/conformance evidence slice over existing `examples/projects/` and
|
|
||||||
`examples/workspaces/`.
|
|
||||||
- Language and compiler roadmaps record beta25 as the current
|
|
||||||
stable-readiness evidence slice while keeping language, stdlib, runtime,
|
|
||||||
package-manager, stable-schema, and performance work deferred.
|
|
||||||
- The post-beta roadmap records beta25 under tooling/release hardening.
|
|
||||||
- Glagol is versioned as `1.0.0-beta.25`.
|
|
||||||
- `compiler/tests/user_project_conformance_beta25.rs` covers all 43 top-level
|
|
||||||
project/workspace fixture roots and 655 discovered tests.
|
|
||||||
- Fixture inventory drift is checked against discovered top-level
|
|
||||||
`slovo.toml` roots.
|
|
||||||
- `scripts/release-gate.sh` runs the focused beta25 matrix test.
|
|
||||||
- No compiler, runtime, standard-library, package-manager, registry, lockfile,
|
|
||||||
semver, ABI/layout, stable-schema, or performance claim is introduced by
|
|
||||||
the documentation.
|
|
||||||
|
|
||||||
## Suggested Gates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git diff --check
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test user_project_conformance_beta25
|
|
||||||
```
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# Beta.2 Runtime Resource Foundation Scope
|
|
||||||
|
|
||||||
This file tracks the first post-`1.0.0-beta.1` runtime/resource foundation
|
|
||||||
slice released as `1.0.0-beta.2`.
|
|
||||||
|
|
||||||
## Implemented In This Slice
|
|
||||||
|
|
||||||
- `std.fs.open_text_read_result : (string) -> (result i32 i32)` opens a text
|
|
||||||
file for read-only resource-handle flow.
|
|
||||||
- `std.fs.read_open_text_result : (i32) -> (result string i32)` reads remaining
|
|
||||||
text from an open read handle.
|
|
||||||
- `std.fs.close_result : (i32) -> (result i32 i32)` closes an open resource
|
|
||||||
handle.
|
|
||||||
- `std.fs.exists : (string) -> bool`, `std.fs.is_file : (string) -> bool`,
|
|
||||||
and `std.fs.is_dir : (string) -> bool` expose narrow filesystem status
|
|
||||||
checks.
|
|
||||||
- `std.fs.remove_file_result : (string) -> (result i32 i32)` removes one file,
|
|
||||||
and `std.fs.create_dir_result : (string) -> (result i32 i32)` creates one
|
|
||||||
directory. Both return `ok 0` on success or `err 1` on ordinary host failure.
|
|
||||||
- `lib/std/fs.slo` exposes matching explicit source facade helpers:
|
|
||||||
`open_text_read_result`, `read_open_text_result`, `close_result`,
|
|
||||||
`read_text_via_handle_result`, `close_ok`, `exists`, `is_file`, `is_dir`,
|
|
||||||
`remove_file_result`, `create_dir_result`, `remove_file_ok`, and
|
|
||||||
`create_dir_ok`.
|
|
||||||
- Glagol lowers the new calls to private runtime symbols and keeps the existing
|
|
||||||
concrete `result` families.
|
|
||||||
- The test runner and hosted C runtime both enforce the same basic policy:
|
|
||||||
invalid, missing, closed, or exhausted ordinary host failures return `err 1`
|
|
||||||
instead of panicking.
|
|
||||||
|
|
||||||
## Resource Policy
|
|
||||||
|
|
||||||
- Resource handles are positive `i32` tokens owned by the current process.
|
|
||||||
- Handles are opaque Slovo values, not host file descriptors and not stable ABI
|
|
||||||
values.
|
|
||||||
- A handle is valid only until `std.fs.close_result` succeeds.
|
|
||||||
- Reading a closed or invalid handle returns `err 1`.
|
|
||||||
- Closing a closed or invalid handle returns `err 1`.
|
|
||||||
- Resource cleanup is explicit in this beta slice; no finalizer, destructor,
|
|
||||||
ownership transfer, or affine typing rule is claimed.
|
|
||||||
|
|
||||||
## Explicitly Out Of Scope
|
|
||||||
|
|
||||||
- no writable file handles
|
|
||||||
- no binary IO
|
|
||||||
- no directory handles
|
|
||||||
- no directory enumeration or recursive filesystem operations
|
|
||||||
- no process handles
|
|
||||||
- no sockets or networking
|
|
||||||
- no async/event-loop resource model
|
|
||||||
- no platform-specific error codes
|
|
||||||
- no rich host-error ADT
|
|
||||||
- no stable handle ABI/layout guarantee
|
|
||||||
|
|
||||||
## Release Gate
|
|
||||||
|
|
||||||
- focused resource/host tests must pass
|
|
||||||
- focused `std.fs` source facade tests must pass
|
|
||||||
- promotion-gate std facade alignment must pass
|
|
||||||
- full release gate passed before tagging `1.0.0-beta.2`
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# Beta 3 Standard Library Stabilization
|
|
||||||
|
|
||||||
Release label: `1.0.0-beta.3`
|
|
||||||
|
|
||||||
Release date: 2026-05-22
|
|
||||||
|
|
||||||
Status: released beta standard-library stabilization slice.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This post-`1.0.0-beta.2` slice stabilizes the existing standard-library surface
|
|
||||||
before adding new large API families.
|
|
||||||
|
|
||||||
## Current Work
|
|
||||||
|
|
||||||
- Generate `docs/language/STDLIB_API.md` from `lib/std/*.slo`.
|
|
||||||
- Gate the generated catalog in `scripts/release-gate.sh`.
|
|
||||||
- Add `examples/projects/stdlib-composition` as a realistic multi-module
|
|
||||||
command-line project.
|
|
||||||
- Keep imports explicit and beta-scoped; do not claim stable std APIs, stable
|
|
||||||
ABI, automatic prelude imports, richer host error codes, or general resource
|
|
||||||
ownership semantics.
|
|
||||||
|
|
||||||
## Acceptance Gates
|
|
||||||
|
|
||||||
- `./scripts/render-stdlib-api-doc.sh`
|
|
||||||
- `git diff --check`
|
|
||||||
- `cargo test --test standard_stdlib_composition_beta`
|
|
||||||
- `./scripts/release-gate.sh`
|
|
||||||
|
|
||||||
## Next Decisions
|
|
||||||
|
|
||||||
- Finish naming/tier review for duplicated concrete helper families.
|
|
||||||
- Identify helpers that should pause until generics rather than expanding more
|
|
||||||
type-specific copies.
|
|
||||||
- Release this connected slice as the next beta tag only after the catalog,
|
|
||||||
composition example, docs, and final review all pass together.
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
# Beta 4 Language Usability
|
|
||||||
|
|
||||||
Release label: `1.0.0-beta.4`
|
|
||||||
|
|
||||||
Release date: 2026-05-22
|
|
||||||
|
|
||||||
Status: released beta language-usability diagnostics slice.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This post-`1.0.0-beta.3` slice reduces friction in ordinary project use
|
|
||||||
without changing the typed core or claiming new syntax stability.
|
|
||||||
|
|
||||||
## Current Work
|
|
||||||
|
|
||||||
- Improve project/workspace build and run entry diagnostics.
|
|
||||||
- Keep the accepted entry contract unchanged: `(fn main () -> i32 ...)`.
|
|
||||||
- Use precise diagnostic codes for missing and invalid entry `main` functions.
|
|
||||||
- Make non-exhaustive `match` diagnostics clearer and deterministic.
|
|
||||||
|
|
||||||
## Acceptance Gates
|
|
||||||
|
|
||||||
- `cargo test --test project_mode entry_main`
|
|
||||||
- `cargo test --test diagnostics_contract`
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `git diff --check`
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
- No implicit `main` return conversions.
|
|
||||||
- No argument-taking `main`.
|
|
||||||
- No async, package registry, or stable ABI/layout promises.
|
|
||||||
- Broader type aliases, match diagnostics, and destructuring remain candidate
|
|
||||||
follow-up items in the same language usability roadmap lane.
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# Beta 5 Package And Workspace Discipline
|
|
||||||
|
|
||||||
Release label: `1.0.0-beta.5`
|
|
||||||
|
|
||||||
Release date: 2026-05-22
|
|
||||||
|
|
||||||
Status: released beta package/workspace discipline slice.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This post-`1.0.0-beta.4` slice tightens local package/workspace behavior
|
|
||||||
without adding a remote registry, package solver, lockfile, or stable package
|
|
||||||
ABI promise.
|
|
||||||
|
|
||||||
## Current Work
|
|
||||||
|
|
||||||
- Add `[workspace] default_package = "name"` as an explicit build/run entry
|
|
||||||
selector for workspaces with multiple packages that contain their entry
|
|
||||||
module.
|
|
||||||
- Keep `check`, `test`, `fmt`, and `doc` project-wide over the full closed
|
|
||||||
workspace graph.
|
|
||||||
- Diagnose missing `default_package` references during workspace loading.
|
|
||||||
- Diagnose duplicate workspace members after path normalization before package
|
|
||||||
loading.
|
|
||||||
- Add workspace package and dependency summaries to `glagol doc`.
|
|
||||||
- Teach generated workspace templates and canonical workspace examples to
|
|
||||||
declare `default_package = "app"`.
|
|
||||||
- Add `docs/language/PACKAGES.md` as the public beta local package/workspace
|
|
||||||
guide, including the no-registry/no-lockfile policy.
|
|
||||||
- Keep dependency resolution local-path-only and deterministic.
|
|
||||||
|
|
||||||
## Acceptance Gates
|
|
||||||
|
|
||||||
- `cargo test --test project_mode workspace_default_package`
|
|
||||||
- `cargo test --test project_mode workspace_package_boundaries`
|
|
||||||
- `cargo test --test dx_v1_7 doc_generates_workspace_package_summary`
|
|
||||||
- `cargo test --test dx_v1_7 new_workspace_template`
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `git diff --check`
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
- No remote registry.
|
|
||||||
- No lockfile.
|
|
||||||
- No semantic-version solver.
|
|
||||||
- No optional, dev, target, or feature dependencies.
|
|
||||||
- No package publishing or archive format.
|
|
||||||
- No stable package ABI/layout guarantee.
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# 1.0.0-beta.6 Networking Foundation Target
|
|
||||||
|
|
||||||
Status: released as `1.0.0-beta.6` on 2026-05-22.
|
|
||||||
|
|
||||||
`1.0.0-beta.6` targets a deliberately narrow networking foundation after the
|
|
||||||
resource-handle and host-error policy introduced in `1.0.0-beta.2`. The goal
|
|
||||||
is blocking loopback TCP only, enough for local client/server fixtures and
|
|
||||||
small request/response examples without committing to a full networking stack.
|
|
||||||
|
|
||||||
## Slovo Source Surface
|
|
||||||
|
|
||||||
The staged source facade is `lib/std/net.slo`, importable explicitly as
|
|
||||||
`std.net`.
|
|
||||||
|
|
||||||
Exported helpers:
|
|
||||||
|
|
||||||
- `tcp_connect_loopback_result : (i32) -> (result i32 i32)`
|
|
||||||
- `tcp_listen_loopback_result : (i32) -> (result i32 i32)`
|
|
||||||
- `tcp_bound_port_result : (i32) -> (result i32 i32)`
|
|
||||||
- `tcp_accept_result : (i32) -> (result i32 i32)`
|
|
||||||
- `tcp_read_all_result : (i32) -> (result string i32)`
|
|
||||||
- `tcp_write_text_result : (i32, string) -> (result i32 i32)`
|
|
||||||
- `tcp_close_result : (i32) -> (result i32 i32)`
|
|
||||||
- `tcp_write_text_ok : (i32, string) -> bool`
|
|
||||||
- `tcp_close_ok : (i32) -> bool`
|
|
||||||
|
|
||||||
The `i32` values returned by successful connect/listen/accept operations are
|
|
||||||
opaque process-local handles. They are not host file descriptors, stable ABI
|
|
||||||
values, transferable capabilities, or ownership-checked affine resources.
|
|
||||||
|
|
||||||
## Runtime Calls
|
|
||||||
|
|
||||||
The facade wraps these compiler-known runtime calls:
|
|
||||||
|
|
||||||
- `std.net.tcp_connect_loopback_result(port i32) -> (result i32 i32)`
|
|
||||||
- `std.net.tcp_listen_loopback_result(port i32) -> (result i32 i32)`
|
|
||||||
- `std.net.tcp_bound_port_result(handle i32) -> (result i32 i32)`
|
|
||||||
- `std.net.tcp_accept_result(listener i32) -> (result i32 i32)`
|
|
||||||
- `std.net.tcp_read_all_result(handle i32) -> (result string i32)`
|
|
||||||
- `std.net.tcp_write_text_result(handle i32, text string) -> (result i32 i32)`
|
|
||||||
- `std.net.tcp_close_result(handle i32) -> (result i32 i32)`
|
|
||||||
|
|
||||||
Ordinary host failures return `err 1`. Successful status-returning operations
|
|
||||||
return `ok 0`. Successful handle-returning operations return `ok handle`.
|
|
||||||
Successful `tcp_bound_port_result` returns the bound loopback TCP port.
|
|
||||||
|
|
||||||
## Fixtures
|
|
||||||
|
|
||||||
- `examples/projects/std-import-net/` exercises explicit `std.net` source
|
|
||||||
import.
|
|
||||||
- `examples/projects/std-layout-local-net/` mirrors the facade as a local
|
|
||||||
module fixture and keeps the source-search contract explicit.
|
|
||||||
|
|
||||||
The source-side fixtures use invalid ports and handles for deterministic
|
|
||||||
result-shape checks. Positive loopback client/server behavior is covered by
|
|
||||||
the matching compiler/runtime tests when the local sandbox allows loopback
|
|
||||||
sockets.
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
This scope does not add DNS, TLS, UDP, Unix-domain sockets, non-loopback
|
|
||||||
binding, async IO, event loops, readiness polling, timeouts, buffering policy,
|
|
||||||
HTTP frameworks, socket options beyond the implementation minimum,
|
|
||||||
platform-specific error codes, rich host-error ADTs, stable runtime helper
|
|
||||||
symbols, stable ABI/layout/ownership guarantees, automatic cleanup, or a
|
|
||||||
stable standard-library API freeze.
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
# 1.0.0-beta.7 Serialization And Data Interchange Target
|
|
||||||
|
|
||||||
Status: released as `1.0.0-beta.7` on 2026-05-22.
|
|
||||||
|
|
||||||
`1.0.0-beta.7` targets a deliberately narrow serialization/data-interchange
|
|
||||||
foundation after the beta.6 networking slice. The goal is compact JSON text
|
|
||||||
construction for CLI, file, and loopback-network programs without pretending
|
|
||||||
that Slovo already has the collections and string APIs needed for a complete
|
|
||||||
JSON library.
|
|
||||||
|
|
||||||
## Slovo Source Surface
|
|
||||||
|
|
||||||
The staged source facade is `lib/std/json.slo`, importable explicitly as
|
|
||||||
`std.json`.
|
|
||||||
|
|
||||||
Exported helpers:
|
|
||||||
|
|
||||||
- `quote_string : (string) -> string`
|
|
||||||
- `null_value : () -> string`
|
|
||||||
- `bool_value : (bool) -> string`
|
|
||||||
- `i32_value : (i32) -> string`
|
|
||||||
- `u32_value : (u32) -> string`
|
|
||||||
- `i64_value : (i64) -> string`
|
|
||||||
- `u64_value : (u64) -> string`
|
|
||||||
- `f64_value : (f64) -> string`
|
|
||||||
- `field_string`, `field_bool`, `field_i32`, `field_u32`, `field_i64`,
|
|
||||||
`field_u64`, `field_f64`, and `field_null`
|
|
||||||
- `array0`, `array1`, `array2`, `array3`
|
|
||||||
- `object0`, `object1`, `object2`, `object3`
|
|
||||||
|
|
||||||
The array and object helpers accept already-encoded JSON text fragments. They
|
|
||||||
are compact construction helpers, not recursive data structures.
|
|
||||||
|
|
||||||
## Runtime Call
|
|
||||||
|
|
||||||
The facade wraps one compiler-known runtime call:
|
|
||||||
|
|
||||||
- `std.json.quote_string(value string) -> string`
|
|
||||||
|
|
||||||
The hosted runtime symbol is `__glagol_json_quote_string`. It returns a
|
|
||||||
complete compact JSON string literal including surrounding quotes. It escapes
|
|
||||||
quote, backslash, newline, tab, carriage return, backspace, form feed, and
|
|
||||||
other control bytes. Allocation failure uses the existing string allocation
|
|
||||||
trap.
|
|
||||||
|
|
||||||
## Fixtures And Benchmarks
|
|
||||||
|
|
||||||
- `examples/projects/std-import-json/` exercises explicit `std.json` source
|
|
||||||
import.
|
|
||||||
- `examples/projects/std-layout-local-json/` mirrors the facade as a local
|
|
||||||
module fixture and keeps the source-search contract explicit.
|
|
||||||
- `benchmarks/json-quote-loop/` adds a local-machine timing scaffold for JSON
|
|
||||||
string quoting across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL.
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
This scope does not add JSON parsing, recursive JSON values, maps/sets, generic
|
|
||||||
collections, source-level byte/character scanners, slicing, streaming encoders,
|
|
||||||
schema validation, Unicode normalization, embedded NUL support in the current
|
|
||||||
null-terminated runtime string ABI, stable runtime helper symbols, stable
|
|
||||||
ABI/layout/ownership guarantees, or a stable standard-library API freeze.
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
# 1.0.0-beta.8 Concrete Type Alias Foundation
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.8`.
|
|
||||||
|
|
||||||
`1.0.0-beta.8` targets a deliberately small language-usability slice:
|
|
||||||
transparent top-level aliases for existing concrete Slovo types.
|
|
||||||
|
|
||||||
## Source Surface
|
|
||||||
|
|
||||||
The declaration form is:
|
|
||||||
|
|
||||||
```slo
|
|
||||||
(type Alias TargetType)
|
|
||||||
```
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```slo
|
|
||||||
(type JsonText string)
|
|
||||||
(type Scores (vec i32))
|
|
||||||
(type MaybeName (option string))
|
|
||||||
(type ReadResult (result string i32))
|
|
||||||
```
|
|
||||||
|
|
||||||
Aliases are module-local names. A later declaration in the same module may use
|
|
||||||
the alias wherever the resolved concrete target type is already supported.
|
|
||||||
|
|
||||||
## Resolution Contract
|
|
||||||
|
|
||||||
- Resolve aliases before typed-core lowering, checked import signatures,
|
|
||||||
backend layout, ABI decisions, and runtime behavior.
|
|
||||||
- Treat aliases as transparent names, not new nominal types.
|
|
||||||
- Preserve the target type's existing value-flow, constructor, operator,
|
|
||||||
`match`, field-access, import, and diagnostic behavior.
|
|
||||||
- Normalize exported function or struct signatures that mention local aliases
|
|
||||||
to their concrete target types for cross-module use.
|
|
||||||
- Generated documentation may show source alias spellings; runtime, checked
|
|
||||||
lowering, and cross-module typing must use the resolved concrete target.
|
|
||||||
- Reject failed alias resolution; do not silently fall back to a primitive type.
|
|
||||||
|
|
||||||
## Source Fixtures
|
|
||||||
|
|
||||||
- `lib/std/json.slo` uses local `JsonText` and `JsonField` aliases for already
|
|
||||||
encoded JSON fragments.
|
|
||||||
- `examples/projects/std-import-json/` uses the same alias shape in the
|
|
||||||
explicit standard-import fixture without importing aliases.
|
|
||||||
- `examples/projects/std-layout-local-json/` mirrors the facade and example as
|
|
||||||
a local source fixture.
|
|
||||||
|
|
||||||
These fixtures keep the existing JSON helper surface and test names. They do
|
|
||||||
not add compiler-known runtime calls or new public standard-library helper
|
|
||||||
names.
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
This target does not add generic aliases, parameterized aliases, alias type
|
|
||||||
parameters, alias re-exports, cross-module alias imports, import aliases, glob
|
|
||||||
imports, maps/sets, alias-driven overloads, implicit casts, runtime helpers,
|
|
||||||
hosted runtime symbols, stable ABI/layout promises, or a standard-library API
|
|
||||||
freeze.
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
# 1.0.0-beta.9 Collection Alias Unification And Generic Reservation
|
|
||||||
|
|
||||||
Status: release scope for `1.0.0-beta.9`.
|
|
||||||
|
|
||||||
`1.0.0-beta.9` is a Slovo stdlib/docs slice on top of the beta.8 concrete
|
|
||||||
alias foundation. It applies transparent aliases to the repeated concrete
|
|
||||||
collection and value-family facades without changing the public helper surface
|
|
||||||
or executable semantics.
|
|
||||||
|
|
||||||
## Source Surface
|
|
||||||
|
|
||||||
Current source-authored facades may declare module-local aliases such as:
|
|
||||||
|
|
||||||
```slo
|
|
||||||
(type VecBool (vec bool))
|
|
||||||
(type OptionString (option string))
|
|
||||||
(type ResultU64 (result u64 i32))
|
|
||||||
```
|
|
||||||
|
|
||||||
The aliases are used only inside their defining module. Exported helper names
|
|
||||||
and helper meanings remain concrete, and importers observe normalized concrete
|
|
||||||
types.
|
|
||||||
|
|
||||||
## Applied Scope
|
|
||||||
|
|
||||||
- `lib/std/vec_i32.slo`
|
|
||||||
- `lib/std/vec_i64.slo`
|
|
||||||
- `lib/std/vec_f64.slo`
|
|
||||||
- `lib/std/vec_bool.slo`
|
|
||||||
- `lib/std/vec_string.slo`
|
|
||||||
- `lib/std/option.slo`
|
|
||||||
- `lib/std/result.slo`
|
|
||||||
|
|
||||||
The vector facades use one local alias for the concrete vector family. The
|
|
||||||
option and result facades use local aliases for the current concrete option and
|
|
||||||
result families.
|
|
||||||
|
|
||||||
## Contract
|
|
||||||
|
|
||||||
- Current vectors remain concrete families: `(vec i32)`, `(vec i64)`,
|
|
||||||
`(vec f64)`, `(vec bool)`, and `(vec string)`.
|
|
||||||
- Current options and results remain concrete families over the explicitly
|
|
||||||
promoted payload shapes.
|
|
||||||
- Aliases are transparent, module-local, and erased before typed-core lowering,
|
|
||||||
backend layout, ABI decisions, runtime behavior, and cross-module signatures.
|
|
||||||
- Public helper names, exports, constructors, runtime calls, and behavior are
|
|
||||||
preserved.
|
|
||||||
- Public API documentation must not turn these local aliases into imported
|
|
||||||
public type names; the semantic contract is the normalized concrete target
|
|
||||||
type.
|
|
||||||
|
|
||||||
## Deferrals
|
|
||||||
|
|
||||||
This release does not add executable generics, generic aliases, parameterized
|
|
||||||
aliases, maps, sets, traits, inference, monomorphization, iterators, new
|
|
||||||
compiler-known runtime names, stable ABI/layout promises, or a stable
|
|
||||||
standard-library API freeze.
|
|
||||||
|
|
||||||
The generic/map/set direction is reserved through diagnostics and design
|
|
||||||
language only. Future work must define syntax, typed-core representation,
|
|
||||||
lowering, conformance gates, compatibility policy, and stdlib migration rules
|
|
||||||
before any executable generic collection surface is promoted.
|
|
||||||
@ -13,31 +13,11 @@ Slovo reaches stable `1.0.0` only after:
|
|||||||
|
|
||||||
## Recommended Sequence
|
## Recommended Sequence
|
||||||
|
|
||||||
The detailed post-beta feature plan lives in
|
1. Harden monorepo release tooling, clean docs, and public install/build flow.
|
||||||
`docs/POST_BETA_ROADMAP.md`. Keep this file as the short stable-readiness
|
2. Stabilize `lib/std` module boundaries and document beta-vs-stable APIs.
|
||||||
summary and use the public roadmap document when choosing the next connected
|
3. Broaden numeric completeness with explicit `f32` and additional integer
|
||||||
implementation scope.
|
policy where needed.
|
||||||
|
4. Improve collection and string breadth without exposing unstable ABI details.
|
||||||
1. Harden monorepo release tooling, install flow, and common build/run loops
|
|
||||||
(`1.0.0-beta.1`).
|
|
||||||
2. Define runtime resource and host-error policy before broad IO and
|
|
||||||
networking (released in `1.0.0-beta.2` with read-only text file
|
|
||||||
handles plus narrow filesystem status and mutation calls).
|
|
||||||
3. Stabilize `lib/std` module boundaries and document beta-vs-stable APIs.
|
|
||||||
4. Improve language usability around entry points, `match`, concrete aliases,
|
|
||||||
and concrete numeric completeness.
|
|
||||||
5. Expand package/workspace discipline before remote registry work.
|
5. Expand package/workspace discipline before remote registry work.
|
||||||
6. Add networking only after resource/error policy is coherent.
|
6. Add editor-facing diagnostics and generated documentation improvements.
|
||||||
7. Add serialization/data-interchange helpers before richer network libraries
|
7. Freeze migration/deprecation policy and cut stable only after beta feedback.
|
||||||
(released in `1.0.0-beta.7` with compact JSON text construction and JSON
|
|
||||||
string quoting).
|
|
||||||
8. Promote concrete type aliases before generics so long concrete vector,
|
|
||||||
option/result, array, JSON, and resource-handle signatures can be named
|
|
||||||
without changing runtime representation (released in `1.0.0-beta.8`).
|
|
||||||
9. Design generics and collection unification from real stdlib duplication
|
|
||||||
pressure by first applying concrete aliases to existing collection/value
|
|
||||||
facades and reserving generic/map/set directions without executable
|
|
||||||
semantics (released in `1.0.0-beta.9`).
|
|
||||||
10. Add editor-facing diagnostics, watch mode, and generated documentation
|
|
||||||
improvements.
|
|
||||||
11. Freeze migration/deprecation policy and cut stable only after beta feedback.
|
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
# 1.0.0-beta.10 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after the controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blocking issues found after integrating the stdlib API catalog worker and
|
|
||||||
the compiler symbol-metadata worker.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- `docs/language/STDLIB_API.md` now lists exact exported `lib/std` helper
|
|
||||||
signatures instead of helper names only.
|
|
||||||
- `scripts/render-stdlib-api-doc.js` verifies exported helpers have matching
|
|
||||||
`(fn ...)` forms, omits non-exported helpers and aliases, and normalizes
|
|
||||||
module-local concrete aliases in public signatures.
|
|
||||||
- `glagol symbols <file.slo|project|workspace>` emits deterministic
|
|
||||||
`slovo.symbols` metadata for modules, imports, exports, aliases, structs,
|
|
||||||
enums, functions, tests, spans/ranges, and workspace package labels.
|
|
||||||
- README, roadmaps, release notes, specification text, whitepapers, and PDFs
|
|
||||||
describe beta10 as tooling/API-discovery work only.
|
|
||||||
- Docs do not claim executable generics, maps, sets, new runtime helpers,
|
|
||||||
stable ABI/layout, LSP/watch protocols, or a stable stdlib API freeze.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `node scripts/render-stdlib-api-doc.js`
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo check`
|
|
||||||
- `cargo test --test symbols_beta10`
|
|
||||||
- `cargo test --test dx_v1_7`
|
|
||||||
- `cargo test --test cli_v1_1`
|
|
||||||
- `cargo test --test promotion_gate`
|
|
||||||
- `MD_TO_PDF_PACKAGE=<local md-to-pdf package path> ./scripts/render-doc-pdfs.sh`
|
|
||||||
- `git diff --check`
|
|
||||||
- `./scripts/release-gate.sh`
|
|
||||||
|
|
||||||
Final full `./scripts/release-gate.sh` result: passed docs, generated stdlib
|
|
||||||
API catalog consistency, private-publication text checks, formatter checks, the
|
|
||||||
full cargo test suite, ignored promotion checks, binary smoke, and LLVM smoke.
|
|
||||||
|
|
||||||
## Residual Risk
|
|
||||||
|
|
||||||
The `symbols` command is a stable-shaped beta metadata export, not a complete
|
|
||||||
editor protocol. Future editor work still needs a separate LSP/watch contract,
|
|
||||||
diagnostic stability policy, local package API docs, and compatibility tests
|
|
||||||
before claiming full editor integration.
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
# 1.0.0-beta.11 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after the controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blocking issues remain after controller follow-up. The original workspace
|
|
||||||
package API leak was reproduced, fixed, and covered by a regression test before
|
|
||||||
publication.
|
|
||||||
|
|
||||||
## Resolved Finding
|
|
||||||
|
|
||||||
- Workspace package API docs originally included compiler-loaded standard
|
|
||||||
library modules when a workspace package imported `std.option`.
|
|
||||||
- Controller fix: workspace package API rendering now filters package modules
|
|
||||||
to the package source root before collecting public API sections, matching
|
|
||||||
the existing project-mode local-root behavior. Ordinary module summaries can
|
|
||||||
still show loaded standard-library modules, but package API sections no
|
|
||||||
longer present those modules as package-owned API.
|
|
||||||
- Regression added:
|
|
||||||
`doc_workspace_package_api_excludes_loaded_std_modules`.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- `compiler/src/docgen.rs` adds per-module public API sections and package API
|
|
||||||
sections, renders exported function signatures, struct fields, enum variants,
|
|
||||||
and normalizes module-local aliases before public rendering.
|
|
||||||
- `compiler/tests/doc_api_beta11.rs` covers file, project, workspace scaffold,
|
|
||||||
workspace packages importing `std.*`, alias normalization, non-exported
|
|
||||||
function omission, and byte-identical repeat generation for a file input.
|
|
||||||
- README, compiler/language roadmaps, release notes, SPEC-v1, `STDLIB_API.md`,
|
|
||||||
and the beta11 `.llm` contract include the main deferrals: no stable Markdown
|
|
||||||
schema, no stable stdlib/API compatibility freeze, no LSP/watch, no
|
|
||||||
SARIF/daemon protocol, no diagnostics schema policy, no executable generics,
|
|
||||||
no maps/sets, no re-exports/globs/hierarchical modules, and no registry
|
|
||||||
semantics.
|
|
||||||
- Version bumps in `compiler/Cargo.toml` and `compiler/Cargo.lock` move
|
|
||||||
`glagol` from `1.0.0-beta.10` to `1.0.0-beta.11`.
|
|
||||||
|
|
||||||
## Test Coverage Notes
|
|
||||||
|
|
||||||
- Workspace packages that import `std.*` are now covered by a focused
|
|
||||||
regression that verifies package API excludes loaded standard-library
|
|
||||||
modules and helpers.
|
|
||||||
- Determinism is asserted byte-for-byte only for file docs. Project and
|
|
||||||
workspace determinism are indirectly exercised by sorted rendering but not
|
|
||||||
protected by repeat-generation tests.
|
|
||||||
- Non-export filtering is asserted for functions and aliases. There is no
|
|
||||||
explicit negative test for non-exported structs, non-exported enums, or tests
|
|
||||||
appearing in public API sections.
|
|
||||||
- Alias normalization is covered for file/module docs, but not for project or
|
|
||||||
workspace package API fixtures.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo fmt --check`: passed.
|
|
||||||
- `cargo check`: passed.
|
|
||||||
- `cargo test --test doc_api_beta11`: passed, 6 tests.
|
|
||||||
- `cargo test --test dx_v1_7`: passed, 13 tests.
|
|
||||||
- `cargo test --test symbols_beta10`: passed, 4 tests.
|
|
||||||
- `cargo test --test promotion_gate`: passed, 1 unignored test.
|
|
||||||
- `git diff --check`: passed.
|
|
||||||
- Stale beta10 current-release/version scan over README, docs, `.llm`, and
|
|
||||||
compiler package metadata: no matches.
|
|
||||||
- Private/local publication text scan over README, docs, scripts, compiler
|
|
||||||
source/tests, lib, examples, benchmarks, tests, and `.llm`: no matches.
|
|
||||||
- `node scripts/render-stdlib-api-doc.js`: passed and refreshed the catalog
|
|
||||||
release wording to `1.0.0-beta.11`.
|
|
||||||
- Manual workspace std-import doc generation after the fix: package API no
|
|
||||||
longer includes loaded standard-library modules.
|
|
||||||
- `./scripts/release-gate.sh`: passed docs, generated stdlib API catalog
|
|
||||||
consistency, private-publication text checks, formatter checks, the full cargo
|
|
||||||
test suite, ignored promotion checks, binary smoke, and LLVM smoke.
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# 1.0.0-beta.12 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blocking issues found in the current beta12 worktree. The slice is correctly
|
|
||||||
framed as concrete vector source-helper parity and can proceed to the final
|
|
||||||
publication gate.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- `lib/std/vec_i64.slo` exports and implements `count_of`, `starts_with`,
|
|
||||||
`without_prefix`, `ends_with`, and `without_suffix` as ordinary source
|
|
||||||
helpers over the existing `std.vec.i64` runtime wrappers and staged helper
|
|
||||||
surface.
|
|
||||||
- `lib/std/vec_f64.slo` exports and implements `count_of` as an ordinary
|
|
||||||
source helper; the existing f64 prefix/suffix helpers remain aligned.
|
|
||||||
- Local fixture copies, explicit `std.vec_i64` and `std.vec_f64` examples, and
|
|
||||||
mirrored language-doc examples import and exercise the new helpers.
|
|
||||||
- Focused helper tests, source-search tests, and promotion-gate inventories now
|
|
||||||
require the new exports and expected `20 test(s) passed` fixture output.
|
|
||||||
- README, roadmaps, release notes, SPEC-v1, regenerated `STDLIB_API.md`, the
|
|
||||||
beta12 `.llm` scope note, and the compiler package version bump align on the
|
|
||||||
`1.0.0-beta.12` concrete vector query and prefix parity scope.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blockers.
|
|
||||||
|
|
||||||
- Helper semantics match the existing concrete lanes: exact equality counts,
|
|
||||||
empty prefix/suffix success, longer input rejection, mismatch rejection, exact
|
|
||||||
removal to empty, and unmatched removal returning the original vector.
|
|
||||||
- Release-facing text keeps the scope narrow: no source-language, runtime,
|
|
||||||
typed-core, compiler-known stdlib/runtime-name, generic dispatch, map/set,
|
|
||||||
iterator, mutable-vector, slice/view, ABI/layout, performance, or stable
|
|
||||||
stdlib/API-freeze claim.
|
|
||||||
- `STDLIB_API.md` is consistent with the current `lib/std` sources: 578
|
|
||||||
exported helper signatures total, with 36 each for `std.vec_i64` and
|
|
||||||
`std.vec_f64`.
|
|
||||||
- Publication hygiene review found no beta12 private local path leakage in the
|
|
||||||
touched release-facing text.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo test --manifest-path compiler/Cargo.toml --test standard_vec_i64_source_helpers_alpha --test standard_vec_f64_source_helpers_alpha --test standard_vec_i64_source_search_alpha --test standard_vec_f64_source_search_alpha`: passed.
|
|
||||||
- `cargo fmt --manifest-path compiler/Cargo.toml --check`: passed.
|
|
||||||
- `git diff --check`: passed.
|
|
||||||
- `STDLIB_API.md` was regenerated in an isolated copy and compared against the
|
|
||||||
worktree copy: no diff.
|
|
||||||
|
|
||||||
- `./scripts/release-gate.sh`: passed after the beta12 release commit, including
|
|
||||||
docs/API freshness, `cargo fmt --check`, full `cargo test`, ignored promotion
|
|
||||||
gates, binary smoke, and LLVM smoke.
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
# 1.0.0-beta.13 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blockers after controller catalog-count reconciliation.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings remain.
|
|
||||||
|
|
||||||
Controller reconciliation note: the initial review used `rg -o -h ...` for the
|
|
||||||
catalog count. On the local ripgrep version, `-h` prints help instead of
|
|
||||||
meaning "no filename", so that command mixed help text with diagnostic codes.
|
|
||||||
Using `--no-filename` gives the correct inventory: 358 `.diag` snapshots and
|
|
||||||
114 unique `(code ...)` values. The Current Golden Catalog table matches that
|
|
||||||
114-code snapshot set.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- Public docs in scope: `README.md`, `docs/POST_BETA_ROADMAP.md`,
|
|
||||||
`docs/language/DIAGNOSTICS.md`, `docs/language/MIGRATION_POLICY.md`,
|
|
||||||
`docs/language/SPEC-v1.md`, `docs/language/ROADMAP.md`,
|
|
||||||
`docs/language/RELEASE_NOTES.md`, `docs/compiler/ROADMAP.md`,
|
|
||||||
`docs/compiler/RELEASE_NOTES.md`, `docs/language/STDLIB_API.md`, and
|
|
||||||
`.llm/BETA_13_DIAGNOSTIC_CATALOG_AND_SCHEMA_POLICY.md`.
|
|
||||||
- Compiler/tooling in scope: `compiler/src/diag.rs`, `compiler/src/main.rs`,
|
|
||||||
`compiler/tests/diagnostics_schema_beta13.rs`,
|
|
||||||
`compiler/tests/diagnostics_contract.rs`, `scripts/release-gate.sh`,
|
|
||||||
`compiler/Cargo.toml`, and `compiler/Cargo.lock`.
|
|
||||||
- The compiler diff keeps diagnostic output shape limited to centralizing
|
|
||||||
`slovo.diagnostic` schema name/version constants and reusing the version in
|
|
||||||
artifact manifest diagnostics metadata.
|
|
||||||
- `docs/language/DIAGNOSTICS.md` describes the current S-expression and JSON
|
|
||||||
diagnostic shape, newline-delimited JSON stderr behavior, source-less JSON
|
|
||||||
`file:null` and `span:null`, artifact-manifest diagnostic metadata,
|
|
||||||
compatibility/migration classes, and explicit deferrals.
|
|
||||||
- Release-facing stage wording consistently points at `1.0.0-beta.13` as the
|
|
||||||
current stage and post-beta13 work as the next scope. Focused private-path
|
|
||||||
and stale current-stage beta12 checks found no matches.
|
|
||||||
|
|
||||||
## Verification Commands And Results
|
|
||||||
|
|
||||||
- `cargo fmt --check`: passed.
|
|
||||||
- `cargo test --test diagnostics_schema_beta13`: passed, 3 tests.
|
|
||||||
- `cargo test --test diagnostics_contract current_negative_cases_match_machine_diagnostic_snapshots`:
|
|
||||||
passed, 1 test.
|
|
||||||
- `git diff --check`: passed.
|
|
||||||
- `rg -o 'snapshot: "\\.\\./tests/[^"]+\\.diag"' compiler/tests/diagnostics_contract.rs | wc -l`:
|
|
||||||
`358`.
|
|
||||||
- `rg --files tests | rg '\\.diag$' | wc -l`: `358`.
|
|
||||||
- `rg --no-filename -o '\\(code [A-Za-z][A-Za-z0-9]*\\)' tests/*.diag | sed -E 's/^\\(code ([^)]+)\\)$/\\1/' | sort -u | wc -l`:
|
|
||||||
`114`.
|
|
||||||
- Comparing the unique snapshot code set with the Current Golden Catalog code
|
|
||||||
set produced no differences, confirming the catalog table itself matches the
|
|
||||||
114-code snapshot set.
|
|
||||||
- Focused `rg` check for private/local paths and stale current-stage beta12
|
|
||||||
release wording in the scoped public docs produced no matches.
|
|
||||||
|
|
||||||
- `./scripts/release-gate.sh`: passed after the beta13 release commit,
|
|
||||||
including docs/API freshness, the focused beta13 diagnostics schema test,
|
|
||||||
`cargo fmt --check`, full `cargo test`, ignored promotion gates, binary
|
|
||||||
smoke, and LLVM smoke.
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
# 1.0.0-beta.14 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blocking findings found for the benchmark suite catalog and metadata gate.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
Non-blocking note: `compiler/tests/benchmark_suite_catalog_beta14.rs` verifies
|
|
||||||
byte-stable JSON, the current 10 benchmark names/directories, top-level counts,
|
|
||||||
required scaffold-file status, missing-file lists, checksum metadata presence,
|
|
||||||
timing mode strings, and implementation slot names. The current runner output
|
|
||||||
also includes `run_args`, `source_stem`, and per-implementation source paths.
|
|
||||||
If those fields become contractual in a later release, the focused gate should
|
|
||||||
parse the JSON and assert them per benchmark.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- Public release docs in scope: `README.md`, `benchmarks/README.md`,
|
|
||||||
`docs/POST_BETA_ROADMAP.md`, `docs/language/SPEC-v1.md`,
|
|
||||||
`docs/language/ROADMAP.md`, `docs/language/RELEASE_NOTES.md`,
|
|
||||||
`docs/compiler/ROADMAP.md`, `docs/compiler/RELEASE_NOTES.md`, and
|
|
||||||
`.llm/BETA_14_BENCHMARK_SUITE_CATALOG_AND_METADATA_GATE.md`.
|
|
||||||
- Tooling and tests in scope: `benchmarks/runner.py`,
|
|
||||||
`compiler/tests/benchmark_suite_catalog_beta14.rs`,
|
|
||||||
`compiler/tests/benchmark_math_loop_scaffold.rs`,
|
|
||||||
`scripts/release-gate.sh`, `compiler/Cargo.toml`, and
|
|
||||||
`compiler/Cargo.lock`.
|
|
||||||
- Release-facing current-stage wording points at `1.0.0-beta.14`. Remaining
|
|
||||||
beta13 references are historical beta13 diagnostics/release notes, the
|
|
||||||
existing `diagnostics_schema_beta13` gate, or older review/contract files.
|
|
||||||
- Benchmark docs and runner output consistently keep timing local-machine-only,
|
|
||||||
do not publish timing numbers, do not define performance thresholds, and do
|
|
||||||
not claim a stable benchmark JSON schema.
|
|
||||||
- The top-level catalog documents the current 10 suites and all suite-list
|
|
||||||
commands. Relative links to `benchmarks/README.md` resolve from the files
|
|
||||||
that introduce them.
|
|
||||||
- The runner preserves per-suite `run.py --list --json` behavior and adds root
|
|
||||||
`benchmarks/runner.py --suite-list` and `--suite-list --json` metadata.
|
|
||||||
- `scripts/release-gate.sh` now runs `cargo test --test
|
|
||||||
benchmark_suite_catalog_beta14` before the full compiler test suite.
|
|
||||||
- Cargo package version and lockfile version both read `1.0.0-beta.14`.
|
|
||||||
- Focused private/local publication text scan found no machine-local paths,
|
|
||||||
private checkout/user names, private remotes, or local IP text in the reviewed
|
|
||||||
release surface.
|
|
||||||
|
|
||||||
## Verification Commands And Results
|
|
||||||
|
|
||||||
- `python3 benchmarks/runner.py --suite-list --json`: passed. Output reported
|
|
||||||
`benchmark_count: 10`, `implementation_slots: 60`, status `ok`, cold/hot
|
|
||||||
timing modes, required scaffold-file status with no missing files, checksum
|
|
||||||
metadata, runtime args, implementation source paths, and the local-only
|
|
||||||
timing disclaimer.
|
|
||||||
- `python3 benchmarks/runner.py --suite-list`: passed.
|
|
||||||
- `python3 benchmarks/math-loop/run.py --list --json`: passed, confirming the
|
|
||||||
existing per-benchmark list mode still works through the shared runner.
|
|
||||||
- `python3 benchmarks/math-loop/run.py --suite-list --json`: passed, confirming
|
|
||||||
suite listing also resolves correctly through a per-benchmark wrapper.
|
|
||||||
- `cargo test --test benchmark_suite_catalog_beta14`: passed, 1 test.
|
|
||||||
- `cargo test --test benchmark_math_loop_scaffold`: passed, 1 test.
|
|
||||||
- `git diff --check`: passed.
|
|
||||||
- `git diff --check -- README.md benchmarks/README.md docs/POST_BETA_ROADMAP.md docs/language/SPEC-v1.md docs/language/ROADMAP.md docs/language/RELEASE_NOTES.md .llm/BETA_14_BENCHMARK_SUITE_CATALOG_AND_METADATA_GATE.md`:
|
|
||||||
passed.
|
|
||||||
- `bash -n scripts/release-gate.sh`: passed.
|
|
||||||
- `cargo fmt --check`: passed.
|
|
||||||
- Focused `rg` stale-current-stage scan for beta13 current release/stage wording
|
|
||||||
in the touched release docs produced no matches.
|
|
||||||
- Focused `rg` private/local publication text scan over README, docs,
|
|
||||||
benchmarks, `.llm`, compiler tests, and scripts produced no matches.
|
|
||||||
|
|
||||||
The full `./scripts/release-gate.sh` was not run during this review; the
|
|
||||||
focused beta14 gate coverage and requested lightweight checks passed.
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
# 1.0.0-beta.15 Release Review
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
- Ready for controller release gate. The beta15 candidate consistently scopes
|
|
||||||
this release as reserved generic collection boundary hardening plus a
|
|
||||||
collection ledger, without promoting executable generics, maps, sets,
|
|
||||||
generic stdlib dispatch, runtime names, ABI/layout, or stable API behavior.
|
|
||||||
- Evidence: the release contract preserves diagnostic shape/codes/schema while
|
|
||||||
allowing reserved-boundary prose rewording
|
|
||||||
(`.llm/BETA_15_RESERVED_GENERIC_COLLECTION_BOUNDARY_HARDENING.md:5`,
|
|
||||||
`.llm/BETA_15_RESERVED_GENERIC_COLLECTION_BOUNDARY_HARDENING.md:27`,
|
|
||||||
`.llm/BETA_15_RESERVED_GENERIC_COLLECTION_BOUNDARY_HARDENING.md:49`),
|
|
||||||
public docs describe the same boundary
|
|
||||||
(`README.md:86`, `docs/language/COLLECTIONS.md:12`,
|
|
||||||
`docs/language/COLLECTIONS.md:113`), and compiler docs mirror the
|
|
||||||
centralized-diagnostic scope (`docs/compiler/RELEASE_NOTES.md:28`,
|
|
||||||
`docs/compiler/ROADMAP.md:64`).
|
|
||||||
- Evidence: reserved diagnostic construction is centralized in
|
|
||||||
`compiler/src/reserved.rs:9`, `compiler/src/reserved.rs:62`,
|
|
||||||
`compiler/src/reserved.rs:77`, `compiler/src/reserved.rs:92`, and
|
|
||||||
`compiler/src/reserved.rs:107`; lowerer, formatter, and checker paths import
|
|
||||||
those helpers at `compiler/src/lower.rs:12`,
|
|
||||||
`compiler/src/formatter.rs:6`, and `compiler/src/check.rs:10`.
|
|
||||||
- Evidence: release gate wiring includes the focused beta15 test at
|
|
||||||
`scripts/release-gate.sh:68`, and the version bump is present in
|
|
||||||
`compiler/Cargo.toml:3` and `compiler/Cargo.lock:7`.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo fmt --check`: passed.
|
|
||||||
- `cargo test --test reserved_generic_collection_beta15`: passed, 1 test.
|
|
||||||
- `cargo test --test diagnostics_contract`: passed, 1 test.
|
|
||||||
- `cargo test --test formatter`: passed, 16 tests.
|
|
||||||
- `cargo test --test project_mode`: passed, 36 tests.
|
|
||||||
- `rg -n "not supported in beta\\.9" compiler/src tests`: passed, no matches.
|
|
||||||
- `git diff --check`: passed.
|
|
||||||
|
|
||||||
Additional review scans:
|
|
||||||
|
|
||||||
- Current-stage stale beta14 scan across README/docs/.llm/Cargo metadata:
|
|
||||||
passed, no current-stage matches.
|
|
||||||
- Private/local publication text scan across README, docs, scripts, compiler
|
|
||||||
sources/tests, lib, examples, benchmarks, tests, and `.llm`: passed, no
|
|
||||||
matches.
|
|
||||||
|
|
||||||
## Residual Risk
|
|
||||||
|
|
||||||
Full release gate was not run by this reviewer because the requested scope was
|
|
||||||
focused review-only. Controller should still run `./scripts/release-gate.sh`
|
|
||||||
after accepting the candidate and before tagging/publishing.
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# 1.0.0-beta.16 Release Review
|
|
||||||
|
|
||||||
Scope: `1.0.0-beta.16 String Scanning And Token Boundary Foundation`
|
|
||||||
|
|
||||||
Final verdict: ready for controller commit and final publication gate. I found no remaining blocking findings in the current worktree.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
Previously reported blockers are resolved:
|
|
||||||
|
|
||||||
- The unignored `promotion_gate_artifacts_are_aligned` stack overflow is fixed. The beta16 string runtime handlers are now extracted in `compiler/src/test_runner.rs` and dispatched from the runtime-symbol branch without re-entering the old recursive path in a way that overflows the vec_i32 import fixture.
|
|
||||||
- `.llm/BETA_16_STRING_SCANNING_AND_TOKEN_BOUNDARY_FOUNDATION.md` is now a combined Slovo/Glagol release scope rather than Slovo-side-only.
|
|
||||||
- `lib/std/README.md` now documents `byte_at_result`, `slice_result`, `starts_with`, `ends_with`, and the beta16 non-scope boundaries.
|
|
||||||
|
|
||||||
## Scope Confirmation
|
|
||||||
|
|
||||||
Confirmed: the beta16 candidate adds byte-oriented `std.string.byte_at_result`, `std.string.slice_result`, `std.string.starts_with`, and `std.string.ends_with`, with matching source facades, runtime entries, LLVM lowering, C runtime behavior, test-runner behavior, diagnostics inventory, examples, docs, release-gate coverage, and version bump.
|
|
||||||
|
|
||||||
Confirmed no beta16 claim for JSON parser, Unicode scalar/grapheme/display-width semantics, language-level slice/view syntax or borrowed substring views, generic collections, maps/sets, stable ABI/layout, stable stdlib/API freeze, tokenizers, object/array parsing, mutable strings, or performance claims. Matches for those terms are deferrals or explicit non-scope statements.
|
|
||||||
|
|
||||||
`docs/language/STDLIB_API.md` reflects the generated beta16 surface as 582 exported helper signatures total, with `std.string` at 30 exports including the four beta16 helpers.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Passed:
|
|
||||||
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo test --test standard_string_scanning_beta16`
|
|
||||||
- `cargo test --test diagnostics_contract`
|
|
||||||
- `cargo test --test standard_string_source_fallback_helpers_alpha`
|
|
||||||
- `cargo test --test standard_core_facade_source_search_alpha`
|
|
||||||
- `cargo test --test string_runtime`
|
|
||||||
- `cargo test --test promotion_gate -- --ignored`
|
|
||||||
- `cargo test --test promotion_gate promotion_gate_artifacts_are_aligned`
|
|
||||||
- `cargo test`
|
|
||||||
- `cargo test --test diagnostics_schema_beta13`
|
|
||||||
- `cargo test --test benchmark_suite_catalog_beta14`
|
|
||||||
- `cargo test --test reserved_generic_collection_beta15`
|
|
||||||
- `cargo test --test binary_smoke -- --ignored`
|
|
||||||
- `cargo test --test llvm_smoke -- --ignored`
|
|
||||||
- `bash -n scripts/install.sh`
|
|
||||||
- `bash -n scripts/render-stdlib-api-doc.sh`
|
|
||||||
- `scripts/render-stdlib-api-doc.sh`
|
|
||||||
- `git diff --check`
|
|
||||||
- stale current-stage beta15 scan
|
|
||||||
- local/private publication text scan across source/docs/tests/.llm
|
|
||||||
- required PDF artifact existence check
|
|
||||||
- `pdftotext` plus local/private publication text scan across required PDFs
|
|
||||||
|
|
||||||
Not run:
|
|
||||||
|
|
||||||
- Full `./scripts/release-gate.sh` before commit. The script intentionally requires the generated `docs/language/STDLIB_API.md` diff to already be committed, so running it on this dirty release candidate would produce a known false failure. Its cargo, shell syntax, generated-catalog, private-text, PDF, binary-smoke, and LLVM-smoke components were run manually above. Run the full script after committing beta16 and before tagging.
|
|
||||||
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
# 1.0.0-beta.17 Release Review
|
|
||||||
|
|
||||||
Scope: `1.0.0-beta.17 JSON Primitive Scalar Parsing Foundation`
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
Ready after controller follow-up. The source facade, runtime lowering,
|
|
||||||
test-runner behavior, examples, generated API catalog, focused gates, full
|
|
||||||
`cargo test`, and release diagnostics are coherent for the beta17 scope.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
### P2 - Resolved: unsupported standard-library diagnostics omitted the beta17 promoted JSON names
|
|
||||||
|
|
||||||
`compiler/src/std_runtime.rs:259` through `compiler/src/std_runtime.rs:299`
|
|
||||||
register the six promoted beta17 helpers:
|
|
||||||
|
|
||||||
- `std.json.parse_bool_value_result`
|
|
||||||
- `std.json.parse_i32_value_result`
|
|
||||||
- `std.json.parse_u32_value_result`
|
|
||||||
- `std.json.parse_i64_value_result`
|
|
||||||
- `std.json.parse_u64_value_result`
|
|
||||||
- `std.json.parse_f64_value_result`
|
|
||||||
|
|
||||||
`compiler/src/std_runtime.rs:875` hard-coded the
|
|
||||||
`UnsupportedStandardLibraryCall.expected` text with only
|
|
||||||
`std.json.quote_string` in the JSON family. A user who called an unsupported
|
|
||||||
`std.*` name would receive stale guidance that omitted the newly supported
|
|
||||||
beta17 helpers.
|
|
||||||
|
|
||||||
Controller resolution: the six beta17 `std.json.parse_*_value_result` names
|
|
||||||
are now present in the expected supported-call list, and
|
|
||||||
`compiler/tests/standard_json_scalar_parsing_beta17.rs` includes
|
|
||||||
`unsupported_json_diagnostics_list_beta17_promoted_scalar_parsers` to fail if
|
|
||||||
that user-facing guidance drifts again.
|
|
||||||
|
|
||||||
No P0/P1/P2 runtime, source-facade, grammar, lowering, diagnostics, or
|
|
||||||
release-gate wiring findings remain open.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Inspected:
|
|
||||||
|
|
||||||
- `lib/std/json.slo` and both JSON example fixtures.
|
|
||||||
- `compiler/src/std_runtime.rs`, `compiler/src/llvm.rs`, and
|
|
||||||
`compiler/src/test_runner.rs`.
|
|
||||||
- `runtime/runtime.c`.
|
|
||||||
- `compiler/tests/standard_json_scalar_parsing_beta17.rs`,
|
|
||||||
`compiler/tests/standard_json_source_facade_alpha.rs`, and
|
|
||||||
`compiler/tests/promotion_gate.rs`.
|
|
||||||
- `scripts/release-gate.sh`, version bump, release notes, roadmaps, spec,
|
|
||||||
standard-runtime docs, and generated `docs/language/STDLIB_API.md`.
|
|
||||||
|
|
||||||
Ran:
|
|
||||||
|
|
||||||
- `git -C slovo diff --check` - passed.
|
|
||||||
- `cargo fmt --check` - passed.
|
|
||||||
- `cargo test --test standard_json_scalar_parsing_beta17` - passed, 5/5.
|
|
||||||
- `cargo test --test standard_json_source_facade_alpha` - passed, 2/2.
|
|
||||||
- `cargo test --test standard_json` - passed, 4/4.
|
|
||||||
- `cargo test --test diagnostics_contract` - passed, 1/1.
|
|
||||||
- `cargo test --test promotion_gate` - passed, 1/1 with 2 ignored.
|
|
||||||
- `cargo test --test promotion_gate -- --ignored` - passed, 2/2.
|
|
||||||
- `cargo test` - passed.
|
|
||||||
- Controller follow-up `cargo test --test standard_json_scalar_parsing_beta17`
|
|
||||||
- passed after the diagnostic inventory fix.
|
|
||||||
|
|
||||||
Not run:
|
|
||||||
|
|
||||||
- Full `./scripts/release-gate.sh`; run it after final controller cleanup. Its
|
|
||||||
core `cargo test`, fmt, promotion, and beta17-focused components were
|
|
||||||
covered above, but the full script also runs documentation/private-text
|
|
||||||
checks and ignored binary/LLVM smoke gates.
|
|
||||||
|
|
||||||
## Residual Risks And Non-Scope Confirmation
|
|
||||||
|
|
||||||
Confirmed in-scope behavior:
|
|
||||||
|
|
||||||
- `std.json.parse_bool_value_result`, numeric concrete
|
|
||||||
`parse_*_value_result` helpers, and source-only `parse_null_value_result`
|
|
||||||
are present in `lib/std/json.slo` and mirrored by the local JSON fixture.
|
|
||||||
- Promoted bool/numeric helpers lower to private runtime symbols and have
|
|
||||||
matching test-runner and hosted-runtime behavior.
|
|
||||||
- Primitive JSON token checks reject whitespace, leading `+`, leading-zero
|
|
||||||
forms such as `01`/`01.0`, negative unsigned integers, overflow, and
|
|
||||||
non-finite f64 results. Exponent f64 is accepted. `-0` is accepted
|
|
||||||
consistently as a JSON-valid number token, not treated as a leading-zero
|
|
||||||
form.
|
|
||||||
- `parse_null_value_result` remains source-only; no compiler-known null parser
|
|
||||||
was introduced.
|
|
||||||
|
|
||||||
Confirmed non-scope:
|
|
||||||
|
|
||||||
- No JSON string parser, object parser, array parser, recursive `JsonValue`,
|
|
||||||
tokenizer, schema validator, stream parser, Unicode escape parser, map/set
|
|
||||||
surface, stable ABI/layout claim, stable API freeze, or performance claim is
|
|
||||||
introduced by this candidate.
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
# Beta 18 Release Review: JSON String Token Parsing Foundation
|
|
||||||
|
|
||||||
Reviewer: beta18 release reviewer
|
|
||||||
Date: 2026-05-23
|
|
||||||
Repo: Slovo monorepo
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
### Blocking: unrelated test-runner stack wrapper drift is present in the beta18 worktree
|
|
||||||
|
|
||||||
- `compiler/src/main.rs:27`
|
|
||||||
- `compiler/src/main.rs:30`
|
|
||||||
- `compiler/src/main.rs:57`
|
|
||||||
|
|
||||||
The dirty worktree includes a broad `Mode::RunTests` execution change that imports
|
|
||||||
`std::thread`, defines a 16 MiB test-runner stack size, and runs every `glagol test`
|
|
||||||
invocation inside a spawned thread. That is not part of the documented beta18 JSON
|
|
||||||
string-token parsing scope and is not covered by the beta18 release notes, runtime
|
|
||||||
contract, `.llm/BETA_18_JSON_STRING_TOKEN_PARSING_FOUNDATION.md`, or a targeted test
|
|
||||||
that justifies changing CLI execution behavior.
|
|
||||||
|
|
||||||
This is release-blocking for beta18 as scoped. Either remove this drift from the
|
|
||||||
beta18 release, or promote it into an explicit separate scope with release notes,
|
|
||||||
docs, tests for panic/reporting behavior, and a clear reason for changing the
|
|
||||||
test-runner resource model.
|
|
||||||
|
|
||||||
## Non-Blocking Observations
|
|
||||||
|
|
||||||
- The JSON string-token parser surface is coherent across `lib/std/json.slo`,
|
|
||||||
`examples/projects/std-layout-local-json/src/json.slo`, compiler runtime
|
|
||||||
registration, LLVM lowering, test-runner emulation, hosted C runtime, generated
|
|
||||||
standard-library API docs, and source-facade examples.
|
|
||||||
- The promoted signature is consistently `(string) -> (result string i32)`.
|
|
||||||
- The documented contract is narrow and explicit: one isolated ASCII JSON string
|
|
||||||
token, exact quotes, simple JSON escapes, `err 1` for ordinary parse failure,
|
|
||||||
and deferred Unicode/full JSON parsing.
|
|
||||||
- `docs/compiler/RELEASE_NOTES.md:81` still says beta17 did not implement JSON
|
|
||||||
string parsing. That appears historical and scoped to the beta17 section, not a
|
|
||||||
stale beta18 claim.
|
|
||||||
|
|
||||||
## Verification Run
|
|
||||||
|
|
||||||
Passed:
|
|
||||||
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo test --test standard_json_string_parsing_beta18`
|
|
||||||
- `cargo test --test standard_json_scalar_parsing_beta17`
|
|
||||||
- `cargo test --test standard_json_source_facade_alpha`
|
|
||||||
- `cargo test --test promotion_gate`
|
|
||||||
- `cargo test --test diagnostics_contract`
|
|
||||||
- `cargo test`
|
|
||||||
- `git diff --check`
|
|
||||||
- `git grep -nE '(/home/[[:alnum:]_.-]+|sanjin[0-9]+|[0-9]{1,3}\.64\.0\.1|git\.hermeticum\.io)' -- README.md CONTRIBUTING.md docs scripts compiler lib examples benchmarks tests .llm`
|
|
||||||
|
|
||||||
Not run:
|
|
||||||
|
|
||||||
- `./scripts/release-gate.sh`; the controller should run the full release gate
|
|
||||||
after resolving the blocking drift and committing generated docs/source changes,
|
|
||||||
because the gate intentionally checks generated `docs/language/STDLIB_API.md`,
|
|
||||||
ignored promotion gates, binary smoke, and LLVM smoke.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
Not release-ready yet because of the unrelated `compiler/src/main.rs` test-runner
|
|
||||||
stack wrapper drift. The beta18 JSON string-token parsing implementation itself did
|
|
||||||
not show blocking defects in this review, and the focused plus full Rust test stack
|
|
||||||
passed.
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# Beta 18 Release Review Disposition
|
|
||||||
|
|
||||||
Reviewer file: `.llm/reviews/BETA_18_RELEASE_REVIEW.md`
|
|
||||||
|
|
||||||
## Blocking Finding Disposition
|
|
||||||
|
|
||||||
The reviewer correctly flagged `compiler/src/main.rs` test-runner stack/thread
|
|
||||||
hardening as release drift while it was undocumented. The controller kept the
|
|
||||||
change because `cargo test --test promotion_gate` reproduced a host stack
|
|
||||||
overflow in `std-layout-local-vec_i32` without it, making the release gate
|
|
||||||
unreliable.
|
|
||||||
|
|
||||||
Resolution:
|
|
||||||
|
|
||||||
- `docs/compiler/RELEASE_NOTES.md` now lists the bounded worker-stack behavior
|
|
||||||
in the `1.0.0-beta.18` summary.
|
|
||||||
- `docs/compiler/ROADMAP.md` now records the same behavior as beta18
|
|
||||||
test-runner hardening.
|
|
||||||
- `.llm/BETA_18_JSON_STRING_TOKEN_PARSING_FOUNDATION.md` now marks it as
|
|
||||||
gate-supporting compiler hardening, not a new language feature.
|
|
||||||
- `cargo test --test promotion_gate` passes after the fix and failed with host
|
|
||||||
stack overflow before the fix.
|
|
||||||
|
|
||||||
Verdict after disposition: release may proceed if the full gate stack passes.
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# 1.0.0-beta.19 Release Review
|
|
||||||
|
|
||||||
Scope: Test Discovery And User-Project Conformance Foundation
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
The implementation matches the beta19 contract at the release-blocking level:
|
|
||||||
`glagol test --list <file|project|workspace>` and legacy
|
|
||||||
`glagol --run-tests --list <file>` route through checked discovery, avoid test
|
|
||||||
body evaluation, preserve the existing discovery ordering, honor
|
|
||||||
`--filter <substring>`, keep ordinary test output unchanged, and are wired into
|
|
||||||
the release gate through `cargo test --test test_discovery_beta19`.
|
|
||||||
|
|
||||||
## Non-Blocking Notes
|
|
||||||
|
|
||||||
- Resolved during controller integration: unfiltered list output now prints the
|
|
||||||
same summary suffix as filtered list output, including `total_discovered`,
|
|
||||||
`selected`, `passed`, `failed`, `skipped`, and `filter none`.
|
|
||||||
- Resolved during controller integration: the grammar typo in
|
|
||||||
`docs/POST_BETA_ROADMAP.md` was corrected.
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
Inspected:
|
|
||||||
|
|
||||||
- Working tree status and beta19 diff across CLI parsing/dispatch, project test
|
|
||||||
mode, test-runner listing, focused tests, docs, version files, and
|
|
||||||
`scripts/release-gate.sh`.
|
|
||||||
- Contract drift against
|
|
||||||
`.llm/BETA_19_TEST_DISCOVERY_AND_CONFORMANCE.md`,
|
|
||||||
`docs/language/SPEC-v1.md`, release notes, roadmaps, and README beta scope.
|
|
||||||
- Cached diff status; no cached beta19 changes were present.
|
|
||||||
|
|
||||||
Read-only checks run:
|
|
||||||
|
|
||||||
- `git diff --check` - passed.
|
|
||||||
- `git diff --cached --check` - passed.
|
|
||||||
- Stale-version scan for beta18/beta19 references - no blocking stale current
|
|
||||||
release references found.
|
|
||||||
- Conflict-marker and trailing-whitespace scans for the untracked beta19
|
|
||||||
contract/test files - passed.
|
|
||||||
|
|
||||||
Not run:
|
|
||||||
|
|
||||||
- `cargo fmt --check`, focused cargo tests, full `cargo test`, and
|
|
||||||
`./scripts/release-gate.sh`; those commands write build artifacts, and this
|
|
||||||
review was constrained to read-only commands except for the review file.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
Release-ready from this review. No blocking beta19 issues remain in the current
|
|
||||||
working tree diff. The controller should still run the focused beta19 test suite
|
|
||||||
and full release gate before tagging.
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
# 1.0.0-beta.20 Release Review
|
|
||||||
|
|
||||||
Scope: String Search And ASCII Trim Foundation
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
The beta20 diff matches the documented release contract at the
|
|
||||||
release-blocking level. `lib/std/string.slo:1` exports all six new helpers, and
|
|
||||||
the implementations remain ordinary source helpers over the existing string
|
|
||||||
facade primitives: first search at `lib/std/string.slo:30`, last search at
|
|
||||||
`lib/std/string.slo:47`, `contains` at `lib/std/string.slo:64`, and ASCII trim
|
|
||||||
at `lib/std/string.slo:71`. The local mirror in
|
|
||||||
`examples/projects/std-layout-local-string/src/string.slo:1` exposes the same
|
|
||||||
surface without changing the public contract shape.
|
|
||||||
|
|
||||||
The example coverage is explicit and scoped correctly. The standard import
|
|
||||||
example covers present, missing, empty-needle, first/last index, leading trim,
|
|
||||||
trailing trim, full trim, all-whitespace trim, and no-trim cases at
|
|
||||||
`examples/projects/std-import-string/src/main.slo:204`; the documentation copy
|
|
||||||
matches at `docs/language/examples/projects/std-import-string/src/main.slo:204`;
|
|
||||||
the local fixture mirrors the same behavior at
|
|
||||||
`examples/projects/std-layout-local-string/src/main.slo:204`.
|
|
||||||
|
|
||||||
The compiler gate coverage is aligned with the non-scope. The focused beta20
|
|
||||||
test builds an explicit `std.string` import fixture at
|
|
||||||
`compiler/tests/standard_string_search_trim_beta20.rs:47`, asserts the helpers
|
|
||||||
are source-authored at `compiler/tests/standard_string_search_trim_beta20.rs:178`,
|
|
||||||
and rejects direct compiler-known runtime calls for the new names. The existing
|
|
||||||
local-string fallback test now expects and inventories the new helpers at
|
|
||||||
`compiler/tests/standard_string_source_fallback_helpers_alpha.rs:8` and
|
|
||||||
`compiler/tests/standard_string_source_fallback_helpers_alpha.rs:24`.
|
|
||||||
The repo-root standard-source import gate and promotion gate are also aligned at
|
|
||||||
`compiler/tests/standard_core_facade_source_search_alpha.rs:8`,
|
|
||||||
`compiler/tests/standard_core_facade_source_search_alpha.rs:33`,
|
|
||||||
`compiler/tests/promotion_gate.rs:1318`, and
|
|
||||||
`compiler/tests/promotion_gate.rs:7368`.
|
|
||||||
|
|
||||||
## Contract Drift
|
|
||||||
|
|
||||||
No blocking contract drift found.
|
|
||||||
|
|
||||||
The README, language release notes, language roadmap, v1 spec, compiler release
|
|
||||||
notes, compiler roadmap, post-beta roadmap, stdlib README, and beta20 `.llm`
|
|
||||||
contract all describe the same narrow surface: byte-oriented search, empty
|
|
||||||
needle behavior, ASCII-only trimming over bytes `9`, `10`, `11`, `12`, `13`,
|
|
||||||
and `32`, no new compiler-known runtime names, no runtime C work, no new source
|
|
||||||
syntax, and no Unicode, regex, tokenizer, mutable-string, stable ABI/layout, or
|
|
||||||
stable stdlib/API claims. Representative anchors: `README.md:144`,
|
|
||||||
`docs/language/RELEASE_NOTES.md:39`, `docs/language/ROADMAP.md:80`,
|
|
||||||
`docs/language/SPEC-v1.md:232`, `docs/compiler/RELEASE_NOTES.md:15`,
|
|
||||||
`docs/compiler/ROADMAP.md:100`, `docs/POST_BETA_ROADMAP.md:111`,
|
|
||||||
`lib/std/README.md:233`, and
|
|
||||||
`.llm/BETA_20_STRING_SEARCH_AND_ASCII_TRIM_FOUNDATION.md:1`.
|
|
||||||
|
|
||||||
The version and generated catalog updates are coherent: `compiler/Cargo.toml:3`
|
|
||||||
and `compiler/Cargo.lock` are bumped to `1.0.0-beta.20`,
|
|
||||||
`docs/language/STDLIB_API.md:18` reports 596 exported signatures, and
|
|
||||||
`docs/language/STDLIB_API.md:486` lists 36 `std.string` signatures including
|
|
||||||
the six beta20 helpers. `scripts/release-gate.sh:73` wires the focused beta20
|
|
||||||
test into the release gate.
|
|
||||||
|
|
||||||
## Verification Notes
|
|
||||||
|
|
||||||
Inspected:
|
|
||||||
|
|
||||||
- Working tree diff and untracked beta20 contract/test files for std source,
|
|
||||||
explicit std/local examples, docs, compiler tests, generated catalog, version
|
|
||||||
bump, and release-gate integration.
|
|
||||||
- Contract drift against README, language roadmap/spec/release notes, compiler
|
|
||||||
roadmap/release notes, post-beta roadmap, stdlib README, and the beta20 `.llm`
|
|
||||||
contract.
|
|
||||||
- Sibling `glagol` repository status; no sibling worktree changes were present.
|
|
||||||
|
|
||||||
Read-only checks run:
|
|
||||||
|
|
||||||
- `git diff --check` - passed.
|
|
||||||
- `git diff --cached --check` - passed.
|
|
||||||
- `bash -n scripts/release-gate.sh` - passed.
|
|
||||||
- `cargo fmt --check --manifest-path compiler/Cargo.toml` - passed.
|
|
||||||
- Local/private publication text scan over source/docs/tests/.llm with build
|
|
||||||
artifacts excluded - passed.
|
|
||||||
|
|
||||||
Not run:
|
|
||||||
|
|
||||||
- Focused cargo tests, full `cargo test`, and `./scripts/release-gate.sh`.
|
|
||||||
Those commands write build/generated artifacts, and this review was
|
|
||||||
constrained to read-only commands except for the review file.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
Release-ready from this review. No blocking beta20 issues remain in the current
|
|
||||||
working tree diff. The controller should still run the focused beta20 test stack
|
|
||||||
and full release gate before tagging.
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
# 1.0.0-beta.21 Glagol Implementation Notes
|
|
||||||
|
|
||||||
Scope: JSON Document Scalar Parsing Foundation.
|
|
||||||
|
|
||||||
This Glagol-side slice prepares compiler/test/release-gate coverage for the
|
|
||||||
source-authored `std.json` document scalar helpers:
|
|
||||||
|
|
||||||
- `parse_string_document_result`
|
|
||||||
- `parse_bool_document_result`
|
|
||||||
- `parse_i32_document_result`
|
|
||||||
- `parse_u32_document_result`
|
|
||||||
- `parse_i64_document_result`
|
|
||||||
- `parse_u64_document_result`
|
|
||||||
- `parse_f64_document_result`
|
|
||||||
- `parse_null_document_result`
|
|
||||||
|
|
||||||
The focused beta21 test uses an explicit `std.json` import, checks/formats/tests
|
|
||||||
the imported helpers, requires the helpers to exist in `lib/std/json.slo`, and
|
|
||||||
asserts that no `std.json.parse_*_document_result` compiler-known calls or
|
|
||||||
private `__glagol_json_*document*` runtime symbols are introduced.
|
|
||||||
|
|
||||||
The JSON source-facade and promotion-gate inventories are updated to match the
|
|
||||||
Slovo-side source/export/example work, which adds three JSON document scalar
|
|
||||||
fixture tests and raises local and explicit `std.json` fixture output from 9 to
|
|
||||||
12 tests.
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
# 1.0.0-beta.21 Release Review
|
|
||||||
|
|
||||||
Scope: JSON Document Scalar Parsing Foundation.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking findings.
|
|
||||||
|
|
||||||
The uncommitted beta21 candidate matches the release contract:
|
|
||||||
|
|
||||||
- `lib/std/json.slo` exports all eight source-authored
|
|
||||||
`parse_*_document_result` helpers.
|
|
||||||
- Each document helper trims the whole input with `trim_ascii`, then delegates
|
|
||||||
to the existing exact scalar value-token parser for the matching family.
|
|
||||||
- Direct compiler-known `std.json.parse_*_document_result` calls are not
|
|
||||||
introduced, and no private `__glagol_json_*document*` runtime symbol is
|
|
||||||
introduced.
|
|
||||||
- The explicit `std.json` example imports repo-root `std.json` and covers
|
|
||||||
trimmed success, plain success, trailing non-whitespace failure, scalar
|
|
||||||
token parsing, fields, arrays, objects, and 12 tests.
|
|
||||||
- The local JSON example imports local `json`, the local `json` fixture imports
|
|
||||||
local `string (trim_ascii)`, and it mirrors the same 12-test coverage.
|
|
||||||
- Compiler coverage includes the focused beta21 test, JSON facade inventory,
|
|
||||||
promotion-gate alignment, release-gate wiring, package version bump, and the
|
|
||||||
generated standard-library API catalog signatures.
|
|
||||||
- Release docs describe `1.0.0-beta.21` as released, and the source/docs scan
|
|
||||||
did not find local/private publication text.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Commands run:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git diff --check
|
|
||||||
git diff --cached --check
|
|
||||||
bash -n scripts/release-gate.sh
|
|
||||||
cargo fmt --check --manifest-path compiler/Cargo.toml
|
|
||||||
cargo test --test standard_json_document_scalar_parsing_beta21
|
|
||||||
cargo test --test standard_json_source_facade_alpha
|
|
||||||
cargo test --test promotion_gate -- promotion_gate_artifacts_are_aligned
|
|
||||||
```
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
- `standard_json_document_scalar_parsing_beta21`: 2 passed.
|
|
||||||
- `standard_json_source_facade_alpha`: 2 passed.
|
|
||||||
- `promotion_gate_artifacts_are_aligned`: 1 passed.
|
|
||||||
- Formatting, shell syntax, and diff whitespace checks passed.
|
|
||||||
- Targeted stale-release/private-text scans over source/docs paths passed.
|
|
||||||
|
|
||||||
Not run by design for this review: full `cargo test`, ignored smoke tests, and
|
|
||||||
the full release gate.
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# Beta22 Release Review
|
|
||||||
|
|
||||||
Verdict: PASS with notes.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking or non-blocking findings in the reviewed scope.
|
|
||||||
|
|
||||||
## Scope Reviewed
|
|
||||||
|
|
||||||
- `compiler/src/main.rs` run manifest rendering and exit behavior.
|
|
||||||
- `compiler/tests/run_manifest_beta22.rs` focused coverage.
|
|
||||||
- `.llm/BETA_22_RUN_MANIFEST_AND_EXECUTION_REPORT_HARDENING.md` beta22 contract and suggested gates.
|
|
||||||
- `README.md`, `docs/compiler/RELEASE_NOTES.md`, `docs/compiler/ROADMAP.md`, `docs/language/RELEASE_NOTES.md`, `docs/language/ROADMAP.md`, and `docs/language/SPEC-v1.md` release claims.
|
|
||||||
- `scripts/release-gate.sh` inclusion.
|
|
||||||
|
|
||||||
## Acceptance Checklist
|
|
||||||
|
|
||||||
- PASS: `glagol run --manifest <path>` writes additive run execution evidence after a supported program executes. The run path captures child stdout/stderr/status before manifest rendering and exits with the child status in `compiler/src/main.rs:892` through `compiler/src/main.rs:918`; rendering emits `(run-report ...)` in `compiler/src/main.rs:2162` through `compiler/src/main.rs:2188`.
|
|
||||||
- PASS: Run-report records exit status, captured stdout, captured stderr, and forwarded args. Tests cover success stdout and args in `compiler/tests/run_manifest_beta22.rs:10` through `compiler/tests/run_manifest_beta22.rs:60`, and nonzero exit plus stderr preservation in `compiler/tests/run_manifest_beta22.rs:62` through `compiler/tests/run_manifest_beta22.rs:128`.
|
|
||||||
- PASS: Existing manifest fields and schema/version markers remain compatible. The new path passes `Some(run_report)` only through the run-specific wrapper in `compiler/src/main.rs:2019` through `compiler/src/main.rs:2043`; the renderer still emits `slovo.artifact-manifest` version `1` in `compiler/src/main.rs:2067` through `compiler/src/main.rs:2069`.
|
|
||||||
- PASS: Source failures do not receive fake run evidence. The focused test asserts no run-report for a type failure in `compiler/tests/run_manifest_beta22.rs:130` through `compiler/tests/run_manifest_beta22.rs:161`.
|
|
||||||
- PASS: Non-run modes are outside the run-report requirement. Existing foreign-import/project manifest wrappers pass `None` for the run-report slot in `compiler/src/main.rs:1992` through `compiler/src/main.rs:2017`; beta22 docs state this explicitly in `.llm/BETA_22_RUN_MANIFEST_AND_EXECUTION_REPORT_HARDENING.md:58` and `docs/compiler/RELEASE_NOTES.md:35`.
|
|
||||||
- PASS: Documentation does not overclaim language, stdlib, runtime, package, ABI, or stable manifest-schema changes. Representative deferrals are present in `README.md:172` through `README.md:178`, `docs/compiler/ROADMAP.md:118` through `docs/compiler/ROADMAP.md:124`, `docs/language/RELEASE_NOTES.md:52` through `docs/language/RELEASE_NOTES.md:62`, `docs/language/ROADMAP.md:99` through `docs/language/ROADMAP.md:106`, and `docs/language/SPEC-v1.md:260` through `docs/language/SPEC-v1.md:267`.
|
|
||||||
- PASS: The beta22 contract suggested gate names match the implemented focused test and release gate. `.llm/BETA_22_RUN_MANIFEST_AND_EXECUTION_REPORT_HARDENING.md:63` through `.llm/BETA_22_RUN_MANIFEST_AND_EXECUTION_REPORT_HARDENING.md:71` lists `cargo test --test run_manifest_beta22`; `scripts/release-gate.sh:75` includes the same focused test.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Ran:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test --test run_manifest_beta22
|
|
||||||
```
|
|
||||||
|
|
||||||
Result: passed, 3 tests passed.
|
|
||||||
|
|
||||||
Recommended before release tag:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo fmt --check
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
git diff --check
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: the focused hosted-run tests depend on Clang discovery, matching existing project patterns. In this local run they executed and passed rather than skipping.
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
# Beta23 Release Review
|
|
||||||
|
|
||||||
Verdict: PASS with notes.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking or non-blocking findings in the reviewed scope.
|
|
||||||
|
|
||||||
## Scope Reviewed
|
|
||||||
|
|
||||||
- `scripts/render-stdlib-api-doc.js` tier rendering.
|
|
||||||
- `scripts/check-stdlib-api-tiers.js` tier gate.
|
|
||||||
- `scripts/release-gate.sh` integration.
|
|
||||||
- Generated `docs/language/STDLIB_API.md`.
|
|
||||||
- `docs/language/STDLIB_TIERS.md`, README, release notes, roadmaps, and spec updates.
|
|
||||||
- Compiler package version bump and compiler release documentation.
|
|
||||||
|
|
||||||
## Acceptance Checklist
|
|
||||||
|
|
||||||
- PASS: `docs/language/STDLIB_TIERS.md` defines the public tier labels and explains the current beta meaning. See `docs/language/STDLIB_TIERS.md:21` through `docs/language/STDLIB_TIERS.md:25`.
|
|
||||||
- PASS: The tier ledger marks JSON, loopback networking, random/time, and filesystem resource-handle helpers as experimental domains. See `docs/language/STDLIB_TIERS.md:46` through `docs/language/STDLIB_TIERS.md:49`.
|
|
||||||
- PASS: The tier ledger records concrete vector modules as beta-supported concrete lanes without claiming generic collection stability. See `docs/language/STDLIB_TIERS.md:44`.
|
|
||||||
- PASS: The generated API catalog emits tier metadata and summary counts. See `docs/language/STDLIB_API.md:6` through `docs/language/STDLIB_API.md:24`.
|
|
||||||
- PASS: Generated catalog classification matches the beta23 contract: filesystem handle helpers are experimental in `docs/language/STDLIB_API.md:115` through `docs/language/STDLIB_API.md:119`; `std.json`, `std.net`, `std.random`, and `std.time` are experimental in `docs/language/STDLIB_API.md:195` through `docs/language/STDLIB_API.md:240`, `docs/language/STDLIB_API.md:294` through `docs/language/STDLIB_API.md:308`, `docs/language/STDLIB_API.md:425` through `docs/language/STDLIB_API.md:432`, and `docs/language/STDLIB_API.md:540` through `docs/language/STDLIB_API.md:547`.
|
|
||||||
- PASS: Concrete vector modules retain explicit no-generic-collection-freeze notes in generated output. Representative coverage is `docs/language/STDLIB_API.md:549` through `docs/language/STDLIB_API.md:554`, with the same note repeated for the other concrete `std.vec_*` modules.
|
|
||||||
- PASS: The renderer implements the tier map and emits per-module/per-helper tiers. See `scripts/render-stdlib-api-doc.js:12` through `scripts/render-stdlib-api-doc.js:46`, `scripts/render-stdlib-api-doc.js:245` through `scripts/render-stdlib-api-doc.js:263`, and `scripts/render-stdlib-api-doc.js:297` through `scripts/render-stdlib-api-doc.js:330`.
|
|
||||||
- PASS: The tier gate checks stale wording, summary tier definitions, experimental module/helper coverage, and vector boundary notes. See `scripts/check-stdlib-api-tiers.js:47` through `scripts/check-stdlib-api-tiers.js:80`.
|
|
||||||
- PASS: `scripts/release-gate.sh` runs syntax checks for both tier scripts and executes the tier checker after catalog rendering. See `scripts/release-gate.sh:12` through `scripts/release-gate.sh:16`.
|
|
||||||
- PASS: README, `lib/std/README.md`, language release notes, language roadmap, post-beta roadmap, and v1 spec link or describe the tier ledger and non-scope. Representative references: `README.md:69` through `README.md:79`, `lib/std/README.md:174` through `lib/std/README.md:184`, `docs/language/RELEASE_NOTES.md:53` through `docs/language/RELEASE_NOTES.md:69`, `docs/language/ROADMAP.md:109` through `docs/language/ROADMAP.md:119`, `docs/POST_BETA_ROADMAP.md:121` through `docs/POST_BETA_ROADMAP.md:132`, and `docs/language/SPEC-v1.md:268` through `docs/language/SPEC-v1.md:279`.
|
|
||||||
- PASS: Documentation states beta23 is documentation/catalog tooling clarity only and does not add language, stdlib, runtime, stable schema, ABI/layout, or stable API behavior. See `docs/language/STDLIB_TIERS.md:10` through `docs/language/STDLIB_TIERS.md:15` and `docs/language/STDLIB_TIERS.md:52` through `docs/language/STDLIB_TIERS.md:68`.
|
|
||||||
- PASS: Compiler package version and compiler release docs are aligned to `1.0.0-beta.23`. See `compiler/Cargo.toml:3`, `compiler/Cargo.lock:7`, `docs/compiler/RELEASE_NOTES.md:15` through `docs/compiler/RELEASE_NOTES.md:43`, and `docs/compiler/ROADMAP.md:126` through `docs/compiler/ROADMAP.md:132`.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Ran:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node --check scripts/render-stdlib-api-doc.js
|
|
||||||
node --check scripts/check-stdlib-api-tiers.js
|
|
||||||
./scripts/check-stdlib-api-tiers.js
|
|
||||||
git diff --check
|
|
||||||
rg -n '^- `experimental`' docs/language/STDLIB_API.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Result: all focused checks passed. The `rg` inspection confirmed 58 experimental helper signatures across the expected filesystem-handle, JSON, loopback networking, random, and time surfaces.
|
|
||||||
|
|
||||||
Recommended before release tag:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/render-stdlib-api-doc.sh
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: run the full release gate after the generated catalog changes are staged or committed; the gate intentionally fails if `docs/language/STDLIB_API.md` has unstaged generated-doc drift.
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
# Beta24 Release Review
|
|
||||||
|
|
||||||
Verdict: PASS.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking or non-blocking findings in the reviewed scope.
|
|
||||||
|
|
||||||
## Scope Reviewed
|
|
||||||
|
|
||||||
- `compiler/src/project.rs` package manifest diagnostics.
|
|
||||||
- `compiler/tests/package_workspace_discipline_beta24.rs`.
|
|
||||||
- `scripts/release-gate.sh` beta24 focused gate entry.
|
|
||||||
- `compiler/Cargo.toml` and `compiler/Cargo.lock` version bump.
|
|
||||||
- `.llm/BETA_24_PACKAGE_MANIFEST_IDENTITY_AND_DEPENDENCY_DISCIPLINE.md`.
|
|
||||||
- README, language package/diagnostic/release/roadmap docs, compiler release/roadmap docs, and post-beta roadmap updates.
|
|
||||||
|
|
||||||
## Acceptance Checklist
|
|
||||||
|
|
||||||
- PASS: Duplicate package manifest keys now use package-scoped diagnostics through `set_manifest_key(..., "PackageManifestInvalid", "package")` for package identity keys. See `compiler/src/project.rs:1505` through `compiler/src/project.rs:1545` and `compiler/src/project.rs:1752` through `compiler/src/project.rs:1771`.
|
|
||||||
- PASS: Invalid dependency keys are rejected before graph validation with `InvalidPackageDependencyName`. See `compiler/src/project.rs:1556` through `compiler/src/project.rs:1572`.
|
|
||||||
- PASS: Duplicate dependency keys are rejected before graph validation with `DuplicatePackageDependencyName`, and invalid/duplicate dependency records are not added to the dependency graph. See `compiler/src/project.rs:1573` through `compiler/src/project.rs:1593`.
|
|
||||||
- PASS: Existing local-path-only dependency parsing and later dependency key/name matching remain in place. See `compiler/src/project.rs:1594` through `compiler/src/project.rs:1601`, `compiler/src/project.rs:1743` through `compiler/src/project.rs:1749`, and `compiler/src/project.rs:2970` through `compiler/src/project.rs:3005`.
|
|
||||||
- PASS: Focused tests cover duplicate package keys, invalid dependency keys, duplicate dependency keys, and a clean local dependency workspace. See `compiler/tests/package_workspace_discipline_beta24.rs:11` through `compiler/tests/package_workspace_discipline_beta24.rs:109`.
|
|
||||||
- PASS: `scripts/release-gate.sh` runs the beta24 focused test before full `cargo test`. See `scripts/release-gate.sh:68` through `scripts/release-gate.sh:82`.
|
|
||||||
- PASS: The compiler package version is aligned to `1.0.0-beta.24` in both Cargo files. See `compiler/Cargo.toml:3` and `compiler/Cargo.lock:7`.
|
|
||||||
- PASS: The beta24 contract defines diagnostics-only scope, explicit non-scope, acceptance criteria, and focused gates. See `.llm/BETA_24_PACKAGE_MANIFEST_IDENTITY_AND_DEPENDENCY_DISCIPLINE.md:5` through `.llm/BETA_24_PACKAGE_MANIFEST_IDENTITY_AND_DEPENDENCY_DISCIPLINE.md:79`.
|
|
||||||
- PASS: README identifies `1.0.0-beta.24` as current and describes the package manifest/dependency diagnostic hardening scope without claiming package-manager, runtime, language, or stdlib changes. See `README.md:9` and `README.md:27` through `README.md:50`.
|
|
||||||
- PASS: `docs/language/PACKAGES.md` documents duplicate package manifest keys, invalid dependency keys, and duplicate dependency keys as beta package/workspace diagnostics. See `docs/language/PACKAGES.md:59` through `docs/language/PACKAGES.md:73` and `docs/language/PACKAGES.md:111` through `docs/language/PACKAGES.md:130`.
|
|
||||||
- PASS: `docs/language/DIAGNOSTICS.md` includes the new package/workspace diagnostic codes in the project/workspace catalog. See `docs/language/DIAGNOSTICS.md:240` through `docs/language/DIAGNOSTICS.md:279`.
|
|
||||||
- PASS: Language and compiler release notes describe beta24 as package manifest identity/dependency diagnostic hardening only. See `docs/language/RELEASE_NOTES.md:44` through `docs/language/RELEASE_NOTES.md:68` and `docs/compiler/RELEASE_NOTES.md:15` through `docs/compiler/RELEASE_NOTES.md:42`.
|
|
||||||
- PASS: Language/compiler roadmaps and post-beta roadmap mark beta24 as the current local package/workspace diagnostics slice with registry, lockfile, semver, publishing, optional/dev/target dependencies, ABI/layout, language, runtime, and stdlib work deferred. See `docs/language/ROADMAP.md:11` through `docs/language/ROADMAP.md:33`, `docs/compiler/ROADMAP.md:24` through `docs/compiler/ROADMAP.md:38`, `docs/compiler/ROADMAP.md:722` through `docs/compiler/ROADMAP.md:728`, and `docs/POST_BETA_ROADMAP.md:185` through `docs/POST_BETA_ROADMAP.md:191`.
|
|
||||||
|
|
||||||
## Verification Commands
|
|
||||||
|
|
||||||
Ran:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git diff --check
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test package_workspace_discipline_beta24
|
|
||||||
cargo test --test project_mode workspace_package_boundaries_are_diagnostics
|
|
||||||
cargo test --test project_mode
|
|
||||||
rg -n "beta\\.23|beta23|beta\\.24|beta24|1\\.0\\.0-beta\\.2[0-9]|current|Release state|Last updated" README.md docs/language/PACKAGES.md docs/language/DIAGNOSTICS.md docs/language/RELEASE_NOTES.md docs/language/ROADMAP.md docs/compiler/RELEASE_NOTES.md docs/compiler/ROADMAP.md docs/POST_BETA_ROADMAP.md .llm/BETA_24_PACKAGE_MANIFEST_IDENTITY_AND_DEPENDENCY_DISCIPLINE.md compiler/Cargo.toml compiler/Cargo.lock scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Result: all executed checks passed. The focused beta24 test reported 4 passed tests. The full `project_mode` suite reported 36 passed tests.
|
|
||||||
|
|
||||||
## Final Gate Disposition
|
|
||||||
|
|
||||||
After this review, the controller ran the full release gate:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Result: PASS. The gate completed docs/catalog checks, focused beta13-beta24
|
|
||||||
tests, full `cargo test`, ignored promotion smoke, ignored binary smoke, and
|
|
||||||
ignored LLVM smoke.
|
|
||||||
|
|
||||||
## Residual Risks
|
|
||||||
|
|
||||||
- No release-blocking residual risks remain. The release gate regenerated and
|
|
||||||
checked the standard-library API catalog; publication PDFs were not
|
|
||||||
regenerated because this slice does not touch paper sources, and the gate
|
|
||||||
verified that the required PDF artifacts are present.
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
# Beta25 Release Review
|
|
||||||
|
|
||||||
Verdict: PASS.
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
No blocking or non-blocking findings in the re-reviewed scope.
|
|
||||||
|
|
||||||
The prior blocking finding is resolved. `compiler/tests/user_project_conformance_beta25.rs` now covers all 43 top-level fixture roots currently discovered under `examples/projects/` and `examples/workspaces/`: 41 project fixtures plus 2 workspace fixtures. The test also asserts the matrix length, total discovered-test count, sorted path order, and exact inventory completeness against discovered top-level `slovo.toml` fixture roots.
|
|
||||||
|
|
||||||
## Acceptance Checklist
|
|
||||||
|
|
||||||
- PASS: The conformance matrix covers every current top-level `slovo.toml` fixture root under `examples/projects/` and `examples/workspaces/`.
|
|
||||||
- PASS: `assert_matrix_inventory()` asserts the 43-root count, 655 discovered tests, sorted repository-relative path order, exact discovered fixture inventory, and the full expected path/test/run matrix.
|
|
||||||
- PASS: The test runs `glagol check`, `glagol test --list`, and stable `glagol test` for every matrix entry.
|
|
||||||
- PASS: Deterministic env fixture handling is wired through `configure_conformance_env()`: expected env variables are set to stable values and intentionally-missing env variables are removed before each Glagol command.
|
|
||||||
- PASS: `scripts/release-gate.sh` still runs `cargo test --test user_project_conformance_beta25`.
|
|
||||||
- PASS: `compiler/Cargo.toml` and `compiler/Cargo.lock` both version `glagol` as `1.0.0-beta.25`.
|
|
||||||
- PASS: README, release notes, roadmaps, and the beta25 matrix doc continue to frame the release as tooling/conformance evidence without claiming language, runtime, stdlib, package-manager, stable-schema, ABI/layout, or performance changes.
|
|
||||||
|
|
||||||
## Verification Commands
|
|
||||||
|
|
||||||
Ran:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git status --short
|
|
||||||
git diff --stat
|
|
||||||
sed -n '1,620p' compiler/tests/user_project_conformance_beta25.rs
|
|
||||||
find examples/projects -mindepth 2 -maxdepth 2 -name slovo.toml -printf '%h\n'
|
|
||||||
find examples/workspaces -mindepth 2 -maxdepth 2 -name slovo.toml -printf '%h\n'
|
|
||||||
find examples/projects -mindepth 2 -maxdepth 2 -name slovo.toml -printf '.' | wc -c
|
|
||||||
find examples/workspaces -mindepth 2 -maxdepth 2 -name slovo.toml -printf '.' | wc -c
|
|
||||||
rg -n "assert_eq!\(MATRIX\.len\(\), 43|discover_fixture_paths|configure_conformance_env|GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT|GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT" compiler/tests/user_project_conformance_beta25.rs
|
|
||||||
git diff --check
|
|
||||||
cargo fmt --check
|
|
||||||
cargo test --test user_project_conformance_beta25
|
|
||||||
```
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
- `git diff --check`: PASS.
|
|
||||||
- `cargo fmt --check`: PASS.
|
|
||||||
- `cargo test --test user_project_conformance_beta25`: PASS, 1 test passed.
|
|
||||||
- Fixture inventory counts: 41 top-level project roots and 2 top-level workspace roots.
|
|
||||||
- Matrix inventory assertion: covers 43 roots and 655 discovered tests.
|
|
||||||
|
|
||||||
## Residual Risks
|
|
||||||
|
|
||||||
No release-blocking residual risks remain for this slice.
|
|
||||||
|
|
||||||
## Final Gate Disposition
|
|
||||||
|
|
||||||
The controller reran the full release gate after staging the generated
|
|
||||||
`docs/language/STDLIB_API.md` beta25 catalog update:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./scripts/release-gate.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Result: PASS. The gate reported:
|
|
||||||
|
|
||||||
```text
|
|
||||||
release gate passed: docs, stdlib API catalog, fmt, tests, promotion, binary, and LLVM smoke checks completed
|
|
||||||
```
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# 1.0.0-beta.7 Release Review
|
|
||||||
|
|
||||||
Date: 2026-05-22
|
|
||||||
|
|
||||||
Scope: serialization and data-interchange foundation.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
`1.0.0-beta.7` is a coherent beta follow-up slice. The release adds a narrow
|
|
||||||
runtime-backed JSON string quoting primitive, a source-authored `std/json.slo`
|
|
||||||
facade, explicit std/local example projects, and a `json-quote-loop` benchmark
|
|
||||||
scaffold without claiming full JSON parsing, recursive JSON values, maps,
|
|
||||||
schema validation, or streaming encoders.
|
|
||||||
|
|
||||||
## Review Notes
|
|
||||||
|
|
||||||
- The new compiler-known call is intentionally small:
|
|
||||||
`std.json.quote_string(value string) -> string` lowers to
|
|
||||||
`__glagol_json_quote_string` and is listed in unsupported-call diagnostics.
|
|
||||||
- The source facade composes existing string and numeric helpers and keeps
|
|
||||||
object/array helpers limited to small fixed arities over already-encoded JSON
|
|
||||||
fragments.
|
|
||||||
- The local and std import fixtures preserve the current explicit-import
|
|
||||||
standard-library discipline.
|
|
||||||
- The benchmark scaffold is suitable as a local regression/comparison harness,
|
|
||||||
but the whitepapers still treat the exp-123 nine-row table as the current
|
|
||||||
published numeric baseline until fresh full-suite timing is rerun.
|
|
||||||
- The hosted runtime escaping path covers quotes, backslashes, standard JSON
|
|
||||||
control escapes, and `\u00XX` for remaining control bytes.
|
|
||||||
|
|
||||||
## Remaining Deferred Work
|
|
||||||
|
|
||||||
- JSON parsing and recursive JSON value modeling.
|
|
||||||
- Maps/sets or generic collection-backed object construction.
|
|
||||||
- Streaming encoders and schema-oriented validation.
|
|
||||||
- Unicode normalization or code point policy beyond byte-preserving string
|
|
||||||
literal emission.
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
# 1.0.0-beta.8 Release Review
|
|
||||||
|
|
||||||
Date: 2026-05-22
|
|
||||||
|
|
||||||
Scope: concrete type alias foundation.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
The Slovo documentation and source-fixture side is coherent for a beta.8
|
|
||||||
concrete-alias slice. The contract is intentionally transparent: top-level
|
|
||||||
`(type Alias TargetType)` declarations name existing supported concrete target
|
|
||||||
types, normalize before backend/ABI decisions, and do not create runtime types,
|
|
||||||
hosted symbols, generic alias machinery, maps/sets, or cross-module alias
|
|
||||||
visibility.
|
|
||||||
|
|
||||||
## Review Notes
|
|
||||||
|
|
||||||
- The language spec defines syntax, resolution, formatter shape, diagnostics,
|
|
||||||
and explicit deferrals for concrete aliases.
|
|
||||||
- The public roadmap and stable-readiness notes place beta.8 between JSON data
|
|
||||||
interchange and generics/collection unification.
|
|
||||||
- `std/json.slo` uses `JsonText` and `JsonField` as local aliases only. The
|
|
||||||
helper export list remains unchanged.
|
|
||||||
- The explicit std-import and local JSON fixtures exercise local alias use
|
|
||||||
without importing, exporting, or re-exporting aliases.
|
|
||||||
- Glagol parser, checker, formatter, project import resolution, lowering
|
|
||||||
inspectors, diagnostics fixtures, and promotion inventory now carry the
|
|
||||||
matching implementation side.
|
|
||||||
- Reviewer findings were integrated: JSON fixtures use canonical alias spacing,
|
|
||||||
unsupported alias targets are rejected, alias/value name conflicts are
|
|
||||||
diagnosed, and alias export/import attempts produce explicit visibility
|
|
||||||
diagnostics.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo test --test diagnostics_contract current_negative_cases_match_machine_diagnostic_snapshots`
|
|
||||||
- `cargo test --test concrete_type_aliases_beta`
|
|
||||||
- `cargo test --test project_mode type_aliases_are_local_across_project_visibility`
|
|
||||||
- `cargo test --test lowering_inspector`
|
|
||||||
- `cargo test --test promotion_gate promotion_gate_artifacts_are_aligned`
|
|
||||||
- `cargo test --test standard_json_source_facade_alpha`
|
|
||||||
- `cargo test`
|
|
||||||
- `git diff --check`
|
|
||||||
|
|
||||||
## Remaining Deferred Work
|
|
||||||
|
|
||||||
- Generated documentation may still display source alias spelling. Runtime
|
|
||||||
behavior, checked lowering, backend layout, and cross-module typing use the
|
|
||||||
resolved concrete target.
|
|
||||||
- Any future generic alias, cross-module alias import/export, or re-export
|
|
||||||
design.
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
# 1.0.0-beta.9 Release Review
|
|
||||||
|
|
||||||
Status: ready for publication after the controller release gate.
|
|
||||||
|
|
||||||
## Verdict
|
|
||||||
|
|
||||||
No blocking issues found after integrating the Slovo stdlib/docs worker and
|
|
||||||
the Glagol compiler/test worker.
|
|
||||||
|
|
||||||
## Scope Checked
|
|
||||||
|
|
||||||
- `lib/std/vec_i32.slo`, `lib/std/vec_i64.slo`, `lib/std/vec_f64.slo`,
|
|
||||||
`lib/std/vec_bool.slo`, `lib/std/vec_string.slo`, `lib/std/option.slo`, and
|
|
||||||
`lib/std/result.slo` now use module-local concrete aliases without exporting
|
|
||||||
alias names.
|
|
||||||
- Compiler diagnostics reserve generic functions, parameterized aliases,
|
|
||||||
uppercase single-letter generic type parameters in unsupported positions,
|
|
||||||
map/set type forms, and future generic stdlib calls.
|
|
||||||
- Formatter diagnostics mirror the compiler rejection path for reserved
|
|
||||||
generic/map/set syntax.
|
|
||||||
- Docs and whitepapers describe beta9 as alias-backed collection cleanup and
|
|
||||||
diagnostic reservation only. They do not claim executable generics, maps,
|
|
||||||
sets, traits, inference, monomorphization, iterators, stable ABI/layout, or a
|
|
||||||
stable stdlib API freeze.
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- `cargo fmt --check`
|
|
||||||
- `cargo test --test diagnostics_contract current_negative_cases_match_machine_diagnostic_snapshots`
|
|
||||||
- `cargo test --test formatter formatter_re`
|
|
||||||
- `cargo test --test project_mode`
|
|
||||||
- `cargo test --test concrete_type_aliases_beta`
|
|
||||||
- `cargo test --test promotion_gate`
|
|
||||||
- `cargo test standard_`
|
|
||||||
- `./scripts/render-stdlib-api-doc.sh`
|
|
||||||
- `./scripts/render-doc-pdfs.sh`
|
|
||||||
- `git diff --check`
|
|
||||||
- `./scripts/release-gate.sh`
|
|
||||||
|
|
||||||
Final gate result: passed. The release gate completed docs, generated stdlib
|
|
||||||
API catalog, formatter, default tests, ignored promotion checks, binary smoke,
|
|
||||||
and LLVM smoke checks.
|
|
||||||
|
|
||||||
## Residual Risk
|
|
||||||
|
|
||||||
Beta9 intentionally reserves generics without implementing them. Future
|
|
||||||
generic work still needs typed-core representation, monomorphization policy,
|
|
||||||
project/import semantics, formatter stability, stdlib migration rules, and
|
|
||||||
explicit compatibility gates before any executable generic collection surface
|
|
||||||
is promoted.
|
|
||||||
552
README.md
552
README.md
@ -6,7 +6,7 @@ This repository is the canonical public monorepo for the language design,
|
|||||||
standard library source, compiler, runtime, examples, benchmarks, and technical
|
standard library source, compiler, runtime, examples, benchmarks, and technical
|
||||||
documents.
|
documents.
|
||||||
|
|
||||||
Current release: `1.0.0-beta.25`.
|
Current release: `1.0.0-beta`.
|
||||||
|
|
||||||
## Repository Layout
|
## Repository Layout
|
||||||
|
|
||||||
@ -24,215 +24,20 @@ scripts/ local release and document tooling
|
|||||||
|
|
||||||
## Beta Scope
|
## Beta Scope
|
||||||
|
|
||||||
`1.0.0-beta.25` keeps the `1.0.0-beta` language baseline, includes the
|
`1.0.0-beta` supports practical local command-line programs and libraries with:
|
||||||
`1.0.0-beta.1` tooling/install hardening slice, the `1.0.0-beta.2`
|
|
||||||
runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library
|
|
||||||
stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics
|
|
||||||
bundle, the `1.0.0-beta.5` local package/workspace discipline bundle, and the
|
|
||||||
`1.0.0-beta.6` loopback networking foundation, plus the `1.0.0-beta.7`
|
|
||||||
serialization/data-interchange foundation and the `1.0.0-beta.8` concrete type
|
|
||||||
alias foundation, the `1.0.0-beta.9` collection alias unification and
|
|
||||||
generic reservation slice, the `1.0.0-beta.10` developer-experience API
|
|
||||||
discovery slice, and the `1.0.0-beta.11` local package API documentation
|
|
||||||
slice, plus the `1.0.0-beta.12` concrete vector query and prefix parity
|
|
||||||
slice, the `1.0.0-beta.13` diagnostic catalog and schema policy slice, the
|
|
||||||
`1.0.0-beta.14` benchmark suite catalog and metadata gate, and the
|
|
||||||
`1.0.0-beta.15` reserved generic collection boundary hardening and collection
|
|
||||||
ledger, the `1.0.0-beta.16` string scanning and token boundary foundation,
|
|
||||||
the `1.0.0-beta.17` JSON primitive scalar parsing foundation, the
|
|
||||||
`1.0.0-beta.18` JSON string token parsing foundation, the
|
|
||||||
`1.0.0-beta.19` test discovery and user-project conformance foundation, the
|
|
||||||
`1.0.0-beta.20` string search and ASCII trim foundation, and the
|
|
||||||
`1.0.0-beta.21` JSON document scalar parsing foundation, plus the
|
|
||||||
`1.0.0-beta.22` run manifest and execution report hardening slice, and the
|
|
||||||
`1.0.0-beta.23` standard-library stability tier ledger and catalog alignment
|
|
||||||
slice, and the `1.0.0-beta.24` package manifest identity and local dependency
|
|
||||||
diagnostic hardening slice, plus the `1.0.0-beta.25` user-project
|
|
||||||
conformance matrix evidence slice.
|
|
||||||
|
|
||||||
The language baseline supports practical local command-line, file, and
|
|
||||||
loopback-network programs with:
|
|
||||||
|
|
||||||
- modules, explicit imports, packages, and local workspaces
|
- modules, explicit imports, packages, and local workspaces
|
||||||
- `new`, `check`, `fmt`, `test`, `doc`, `symbols`, `build`, `run`, and
|
- `new`, `check`, `fmt`, `test`, `doc`, and `build`
|
||||||
`clean`
|
|
||||||
- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit`
|
- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit`
|
||||||
- structs, enums, fixed arrays, concrete vectors, option/result families, and
|
- structs, enums, fixed arrays, concrete vectors, option/result families, and
|
||||||
current `match`
|
current `match`
|
||||||
- module-local transparent concrete type aliases
|
|
||||||
- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or
|
- explicit `std/*.slo` imports from `lib/std`, installed `share/slovo/std`, or
|
||||||
`SLOVO_STD_PATH`
|
`SLOVO_STD_PATH`
|
||||||
- beta-scoped loopback TCP handles through `std.net`
|
|
||||||
- JSON string quoting, compact JSON text construction, primitive scalar token
|
|
||||||
parsing, ASCII JSON string-token parsing, and scalar JSON document parsing
|
|
||||||
through `std.json`
|
|
||||||
- byte-oriented string search and ASCII edge trimming through `std.string`
|
|
||||||
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
|
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
|
||||||
|
|
||||||
The generated standard-library API catalog is a beta discovery aid: it lists
|
Still deferred before stable: generics, maps/sets, broad package registry
|
||||||
exported helper signatures from `lib/std/*.slo`, normalizes module-local
|
semantics, networking/async, LSP/watch/debug-adapter guarantees, stable ABI and
|
||||||
concrete aliases such as `VecI32` and `ResultU64` to their concrete public
|
layout, and a stable standard-library compatibility freeze.
|
||||||
types, and omits non-exported helpers and `(type ...)` aliases.
|
|
||||||
The companion
|
|
||||||
[`docs/language/STDLIB_TIERS.md`](docs/language/STDLIB_TIERS.md) ledger
|
|
||||||
defines the public tier labels `beta-supported`, `experimental`, and
|
|
||||||
`internal`, marks JSON, loopback networking, random/time, and filesystem
|
|
||||||
resource-handle helpers as experimental domains, and keeps concrete vector
|
|
||||||
modules as beta-supported concrete lanes rather than a generic collections
|
|
||||||
freeze.
|
|
||||||
`glagol symbols <file.slo|project|workspace>` emits deterministic
|
|
||||||
editor-facing S-expression metadata for modules, imports, exports, aliases,
|
|
||||||
structs, enums, functions, tests, source spans, and workspace package names.
|
|
||||||
`glagol doc <file|project|workspace> -o <dir>` now includes deterministic public
|
|
||||||
API sections for local package and module documentation: exact exported
|
|
||||||
function signatures, exported struct fields, exported enum variants and payload
|
|
||||||
types, non-export filtering, and module-local alias normalization.
|
|
||||||
The `1.0.0-beta.22` tooling slice adds an additive run-report block to
|
|
||||||
`glagol run --manifest` artifact manifests so local evidence can record the
|
|
||||||
program exit status, captured stdout, captured stderr, and forwarded program
|
|
||||||
arguments. This is beta CLI evidence hardening only: it does not add language
|
|
||||||
or stdlib features and does not freeze the artifact-manifest schema.
|
|
||||||
The `1.0.0-beta.12` vector parity slice adds source-authored helper coverage
|
|
||||||
only: `std.vec_i64` gains `count_of`, `starts_with`, `without_prefix`,
|
|
||||||
`ends_with`, and `without_suffix`, while `std.vec_f64` gains `count_of`.
|
|
||||||
The `1.0.0-beta.13` diagnostics slice documents the beta
|
|
||||||
`slovo.diagnostic` version `1` policy in
|
|
||||||
[`docs/language/DIAGNOSTICS.md`](docs/language/DIAGNOSTICS.md): the
|
|
||||||
S-expression/JSON relationship, required and optional fields, JSON-line
|
|
||||||
discipline, source-less diagnostics, manifest diagnostic metadata,
|
|
||||||
compatibility and migration classes, and the current golden diagnostic code
|
|
||||||
catalog.
|
|
||||||
The `1.0.0-beta.14` benchmark metadata slice documents the existing benchmark
|
|
||||||
suite catalog in [`benchmarks/README.md`](benchmarks/README.md). It explains
|
|
||||||
`python3 benchmarks/runner.py --suite-list` for the human-readable suite
|
|
||||||
inventory and `python3 benchmarks/runner.py --suite-list --json` for beta
|
|
||||||
tooling metadata, with required scaffold-file verification for each current
|
|
||||||
suite. Benchmark timings remain local-machine evidence only; the JSON field
|
|
||||||
set is not a stable public schema.
|
|
||||||
The `1.0.0-beta.15` collection ledger and reserved diagnostic hardening slice
|
|
||||||
adds
|
|
||||||
[`docs/language/COLLECTIONS.md`](docs/language/COLLECTIONS.md) as the
|
|
||||||
docs/design ledger for current concrete collection and value-family
|
|
||||||
boundaries. It links to the generated
|
|
||||||
[`docs/language/STDLIB_API.md`](docs/language/STDLIB_API.md) catalog for exact
|
|
||||||
public helper signatures, records design pressure from duplicated concrete
|
|
||||||
vector/option/result families, defines prerequisites before executable
|
|
||||||
generics, generic aliases, maps, sets, iterators, mutable vectors, or
|
|
||||||
slice/view APIs can be promoted, and treats current unsupported diagnostics as
|
|
||||||
boundaries. It rewords affected reserved-boundary diagnostics from
|
|
||||||
beta.9-specific text to current-beta wording while preserving diagnostic
|
|
||||||
codes, schema, spans, expected/found values, hints, and output shape. It
|
|
||||||
changes no source language, runtime, stdlib/API surface, benchmark metadata
|
|
||||||
schema, ABI/layout behavior, or performance claim.
|
|
||||||
The `1.0.0-beta.16` string scanning and token boundary foundation adds
|
|
||||||
source facades and explicit examples for `std.string.byte_at_result`,
|
|
||||||
`std.string.slice_result`, `std.string.starts_with`, and
|
|
||||||
`std.string.ends_with`. These helpers are byte-oriented over the current
|
|
||||||
NUL-terminated runtime string representation; invalid indexes and ranges return
|
|
||||||
`err 1`, and substring allocation failure follows the existing string
|
|
||||||
allocation trap policy. This release does not promise Unicode scalar,
|
|
||||||
grapheme, display-width, or locale semantics; does not add full JSON parsing,
|
|
||||||
object/array parsing, tokenizers, a language slice/view feature, or a stable
|
|
||||||
stdlib/API freeze.
|
|
||||||
|
|
||||||
The `1.0.0-beta.17` JSON primitive scalar parsing foundation adds
|
|
||||||
result-returning `std.json.parse_*_value_result` helpers for booleans,
|
|
||||||
concrete numeric primitives, and exact `null` only. Numeric and boolean parse
|
|
||||||
helpers consume one isolated JSON primitive token: no leading/trailing
|
|
||||||
whitespace, no leading `+`, no leading-zero integer form except `0`, and no
|
|
||||||
non-finite f64 values. This is not full JSON parsing: object/array parsing,
|
|
||||||
tokenizers, recursive `JsonValue`, document parsing beyond the beta21 scalar
|
|
||||||
document helpers, schema validation, streaming, Unicode escape handling,
|
|
||||||
stable ABI/layout, and a stable stdlib/API freeze remain deferred.
|
|
||||||
|
|
||||||
The `1.0.0-beta.18` JSON string token parsing foundation adds
|
|
||||||
`std.json.parse_string_value_result` as a thin source facade over the matching
|
|
||||||
promoted runtime name. It consumes one already-isolated ASCII JSON string token
|
|
||||||
with exact quotes and no leading/trailing whitespace, decodes the simple JSON
|
|
||||||
escapes `\"`, `\\`, `\/`, `\b`, `\f`, `\n`, `\r`, and `\t`, and returns
|
|
||||||
`err 1` for ordinary parse failure. Complete JSON document parsing beyond the
|
|
||||||
beta21 scalar document helpers, object/array parsing, tokenizer APIs, Unicode
|
|
||||||
escape decoding/normalization, embedded NUL policy, stable ABI/layout, and a
|
|
||||||
stable stdlib/API freeze remain deferred.
|
|
||||||
|
|
||||||
The `1.0.0-beta.19` test discovery and user-project conformance foundation
|
|
||||||
adds `glagol test --list <file|project|workspace>` plus legacy
|
|
||||||
`glagol --run-tests --list <file>` support for listing checked and discovered
|
|
||||||
tests without executing test bodies. The list mode preserves existing file,
|
|
||||||
project, and workspace test ordering, honors `--filter <substring>`, and
|
|
||||||
remains beta tooling rather than a stable output schema.
|
|
||||||
|
|
||||||
The `1.0.0-beta.20` string search and ASCII trim foundation adds
|
|
||||||
`std.string.contains`, `std.string.index_of_option`,
|
|
||||||
`std.string.last_index_of_option`, `std.string.trim_ascii_start`,
|
|
||||||
`std.string.trim_ascii_end`, and `std.string.trim_ascii` as ordinary source
|
|
||||||
helpers over the existing byte string primitives. Search is byte-oriented,
|
|
||||||
empty needles match at the first index and at `(len value)` for last search,
|
|
||||||
and ASCII trimming removes only bytes `9`, `10`, `11`, `12`, `13`, and `32`.
|
|
||||||
|
|
||||||
The `1.0.0-beta.21` JSON document scalar parsing foundation adds
|
|
||||||
source-authored `std.json.parse_*_document_result` helpers for string, bool,
|
|
||||||
`i32`, `u32`, `i64`, `u64`, `f64`, and exact `null` scalar JSON documents.
|
|
||||||
Each helper trims ASCII whitespace around the whole document with
|
|
||||||
`std.string.trim_ascii`, then delegates to the already released exact JSON
|
|
||||||
value-token parser for that scalar family. This scope does not add new
|
|
||||||
compiler-known runtime names, object/array parsing, recursive `JsonValue`,
|
|
||||||
tokenizer objects, maps/sets, streaming, Unicode escape decoding beyond the
|
|
||||||
existing string-token behavior, embedded NUL policy, stable ABI/layout, or a
|
|
||||||
stable stdlib/API freeze.
|
|
||||||
|
|
||||||
The `1.0.0-beta.22` run manifest and execution report hardening slice extends
|
|
||||||
`glagol run --manifest` artifact manifests with additive run-report evidence:
|
|
||||||
the executed program's exit status, captured stdout, captured stderr, and
|
|
||||||
forwarded user-program arguments. This release does not add source-language
|
|
||||||
syntax, standard-library helpers, compiler-known runtime names, runtime
|
|
||||||
capabilities, package behavior, stable artifact-manifest schema guarantees,
|
|
||||||
stable ABI/layout, or a stable stdlib/API freeze.
|
|
||||||
|
|
||||||
The `1.0.0-beta.23` standard-library stability tier ledger and catalog
|
|
||||||
alignment slice adds the public
|
|
||||||
[`STDLIB_TIERS.md`](docs/language/STDLIB_TIERS.md) maturity ledger beside the
|
|
||||||
generated [`STDLIB_API.md`](docs/language/STDLIB_API.md) signature catalog.
|
|
||||||
It is documentation/catalog tooling clarity only: no source-language syntax,
|
|
||||||
stdlib helpers, compiler-known runtime names, runtime behavior, stable manifest
|
|
||||||
schema, stable Markdown schema, stable ABI/layout, or stable stdlib/API freeze
|
|
||||||
changes. The generated catalog and release gate now expose and check tier
|
|
||||||
metadata.
|
|
||||||
|
|
||||||
The `1.0.0-beta.24` package manifest identity and local dependency discipline
|
|
||||||
slice tightens local manifest diagnostics only: duplicate package manifest
|
|
||||||
keys, invalid dependency keys, and duplicate dependency keys are explicit beta
|
|
||||||
package/workspace diagnostics. It does not add remote registries, lockfiles,
|
|
||||||
semantic-version solving, package publishing, optional/dev/target
|
|
||||||
dependencies, stable package ABI/layout, source-language changes, runtime
|
|
||||||
changes, or standard-library changes.
|
|
||||||
|
|
||||||
The `1.0.0-beta.25` user-project conformance matrix slice adds deterministic
|
|
||||||
tooling/readiness evidence over the existing `examples/projects/` and
|
|
||||||
`examples/workspaces/` inventories. It currently covers all 43 top-level
|
|
||||||
fixture roots and 655 discovered tests through ordinary `check`,
|
|
||||||
`test --list`, and stable `test` entry points. The matrix is for ordinary
|
|
||||||
project and workspace usage evidence only: it does not add source-language
|
|
||||||
syntax or semantics, standard-library helpers, runtime behavior, package
|
|
||||||
manager or registry behavior, lockfile behavior, semantic-version solving,
|
|
||||||
stable schema guarantees, or performance claims.
|
|
||||||
|
|
||||||
Still deferred before stable: executable generics, generic aliases, maps/sets,
|
|
||||||
broad package registry semantics, stable artifact-manifest schema, stable
|
|
||||||
Markdown schema, stable conformance-matrix schema, stable stdlib/API
|
|
||||||
compatibility freeze, DNS/TLS/async networking, LSP/watch guarantees, SARIF
|
|
||||||
and daemon protocols, stable `1.0.0`
|
|
||||||
diagnostics freeze,
|
|
||||||
re-exports/globs/hierarchical modules, mutable vectors, slice/view APIs,
|
|
||||||
iterators, additional compiler-known runtime names, stable ABI and layout,
|
|
||||||
performance claims, stable benchmark JSON metadata schema, and runtime changes
|
|
||||||
for generic collections.
|
|
||||||
|
|
||||||
The beta19 tooling scope is deliberately tooling-only. It does not add parallel
|
|
||||||
test execution, retries, tags/groups, coverage reports, event streams, stable
|
|
||||||
manifest or Markdown schema guarantees, LSP/watch behavior, SARIF/daemon
|
|
||||||
protocols, JSON expansion, runtime helper names, source-language syntax,
|
|
||||||
remote package registries, semver solving, or performance claims.
|
|
||||||
|
|
||||||
## Build And Test
|
## Build And Test
|
||||||
|
|
||||||
@ -267,356 +72,11 @@ Build a native executable when Clang is available:
|
|||||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin
|
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol build hello -o hello/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
## 1.0.0-beta.1 Tooling Additions
|
|
||||||
|
|
||||||
The `1.0.0-beta.1` release improves the common local development and install
|
|
||||||
loop without adding new source-language syntax.
|
|
||||||
|
|
||||||
Build and execute in one step:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol run hello
|
|
||||||
SLOVO_STD_PATH="$PWD/lib/std" ./compiler/target/debug/glagol clean hello
|
|
||||||
```
|
|
||||||
|
|
||||||
Create alternate project shapes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./compiler/target/debug/glagol new numbers --template library
|
|
||||||
./compiler/target/debug/glagol new workspace-demo --template workspace
|
|
||||||
```
|
|
||||||
|
|
||||||
Install the current checkout:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
PREFIX="$HOME/.local" ./scripts/install.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
The installed layout is:
|
|
||||||
|
|
||||||
```text
|
|
||||||
<prefix>/bin/glagol
|
|
||||||
<prefix>/share/slovo/std/*.slo
|
|
||||||
<prefix>/share/slovo/runtime/runtime.c
|
|
||||||
```
|
|
||||||
|
|
||||||
Installed `glagol` discovers `share/slovo/std` and
|
|
||||||
`share/slovo/runtime/runtime.c` relative to its executable. `SLOVO_STD_PATH`
|
|
||||||
can still override standard-library search, `SLOVO_RUNTIME_C` or
|
|
||||||
`GLAGOL_RUNTIME_C` can override the runtime C input, and `GLAGOL_CLANG` can
|
|
||||||
select the Clang-compatible compiler.
|
|
||||||
|
|
||||||
## 1.0.0-beta.2 Runtime Resource Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.2` release adds beta-scoped runtime/resource foundation work:
|
|
||||||
|
|
||||||
- `std.fs.open_text_read_result`
|
|
||||||
- `std.fs.read_open_text_result`
|
|
||||||
- `std.fs.close_result`
|
|
||||||
- `std.fs.exists`
|
|
||||||
- `std.fs.is_file`
|
|
||||||
- `std.fs.is_dir`
|
|
||||||
- `std.fs.remove_file_result`
|
|
||||||
- `std.fs.create_dir_result`
|
|
||||||
- matching explicit `lib/std/fs.slo` facades
|
|
||||||
|
|
||||||
These APIs use beta-scoped opaque `i32` file handles. They do not claim stable
|
|
||||||
file descriptors, writable streams, binary IO, directory handles, sockets,
|
|
||||||
async IO, platform error codes, or stable handle ABI/layout. Directory creation
|
|
||||||
is intentionally narrow and does not imply directory enumeration or recursive
|
|
||||||
filesystem APIs.
|
|
||||||
|
|
||||||
## 1.0.0-beta.3 Standard Library Stabilization
|
|
||||||
|
|
||||||
The `1.0.0-beta.3` release starts the standard-library stabilization slice. It
|
|
||||||
adds a generated standard-library API catalog and
|
|
||||||
`examples/projects/stdlib-composition`, a checked/tested/run-capable program
|
|
||||||
that composes `std.fs`, `std.string`, `std.math`, and `std.io`.
|
|
||||||
|
|
||||||
## 1.0.0-beta.4 Language Usability Diagnostics
|
|
||||||
|
|
||||||
The `1.0.0-beta.4` release improves diagnostics without changing the source
|
|
||||||
language surface. Project/workspace build and run entry failures now use
|
|
||||||
entry-specific diagnostic codes, and non-exhaustive `match` diagnostics have
|
|
||||||
clearer wording with deterministic found-arm output.
|
|
||||||
|
|
||||||
## 1.0.0-beta.5 Package And Workspace Discipline
|
|
||||||
|
|
||||||
The `1.0.0-beta.5` release tightens local package/workspace behavior. Local
|
|
||||||
workspaces may declare `[workspace] default_package = "name"` to select the
|
|
||||||
build/run entry package when multiple packages have entry modules. Duplicate
|
|
||||||
normalized workspace members and missing default-package references are now
|
|
||||||
dedicated diagnostics. `glagol doc <workspace> -o <dir>` includes a workspace
|
|
||||||
package/dependency summary, new workspace templates declare
|
|
||||||
`default_package = "app"`, and `docs/language/PACKAGES.md` documents the beta
|
|
||||||
local-package rules. Remote registries, lockfiles, semantic-version solving,
|
|
||||||
package publishing, and stable package ABI/layout remain deferred.
|
|
||||||
|
|
||||||
## 1.0.0-beta.24 Package Manifest Identity And Dependency Discipline
|
|
||||||
|
|
||||||
The `1.0.0-beta.24` release keeps the local package model closed and local. It
|
|
||||||
hardens diagnostics for manifest identity/dependency mistakes only: duplicate
|
|
||||||
package manifest keys, invalid dependency keys, and duplicate dependency keys
|
|
||||||
are reported explicitly.
|
|
||||||
|
|
||||||
This scope does not add a remote registry, lockfile, semantic-version solver,
|
|
||||||
package publishing flow, optional/dev/target dependencies, stable package
|
|
||||||
ABI/layout, source-language behavior, runtime behavior, or standard-library
|
|
||||||
behavior.
|
|
||||||
|
|
||||||
## 1.0.0-beta.25 User Project Conformance Matrix
|
|
||||||
|
|
||||||
The `1.0.0-beta.25` release adds deterministic tooling evidence for the
|
|
||||||
existing user-shaped examples under `examples/projects/` and
|
|
||||||
`examples/workspaces/`. The conformance matrix is sorted by repository path and
|
|
||||||
records ordinary `check`, `test --list`, and stable `test` behavior for all
|
|
||||||
43 top-level fixture roots and 655 discovered tests.
|
|
||||||
|
|
||||||
This scope is stable-readiness evidence only. It adds no source-language
|
|
||||||
change, standard-library helper change, runtime behavior change, package
|
|
||||||
manager or registry behavior, lockfile behavior, semantic-version solving,
|
|
||||||
stable schema freeze, or performance claim.
|
|
||||||
|
|
||||||
## 1.0.0-beta.6 Networking Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.6` release adds a narrow blocking loopback TCP foundation:
|
|
||||||
|
|
||||||
- compiler-known `std.net.tcp_*_result` calls for connect, listen,
|
|
||||||
bound-port lookup, accept, read-all, write-text, and close
|
|
||||||
- `lib/std/net.slo` source facades and explicit std/local example projects
|
|
||||||
- opaque beta-scoped `i32` socket handles with concrete `result` values
|
|
||||||
|
|
||||||
This is not a general networking stack. DNS, TLS, UDP, non-loopback binding,
|
|
||||||
async IO, HTTP frameworks, rich host-error ADTs, stable socket ABI/layout, and
|
|
||||||
automatic resource ownership remain deferred.
|
|
||||||
|
|
||||||
## 1.0.0-beta.7 Serialization And Data Interchange
|
|
||||||
|
|
||||||
The `1.0.0-beta.7` release adds a narrow JSON text-construction foundation:
|
|
||||||
|
|
||||||
- compiler-known `std.json.quote_string` for deterministic compact JSON string
|
|
||||||
quoting
|
|
||||||
- `lib/std/json.slo` source helpers for scalar values, fields, small arrays,
|
|
||||||
and small objects
|
|
||||||
- explicit std/local JSON example projects and a `json-quote-loop` benchmark
|
|
||||||
scaffold
|
|
||||||
|
|
||||||
This is not a complete JSON library. Full parsing beyond primitive scalar
|
|
||||||
tokens and the ASCII JSON string-token helper, object/array parsing,
|
|
||||||
recursive JSON values, maps/sets, streaming encoders or decoders, schema
|
|
||||||
validation, Unicode normalization, and a stable data-interchange API freeze
|
|
||||||
remain deferred.
|
|
||||||
|
|
||||||
## 1.0.0-beta.8 Concrete Type Alias Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.8` release adds transparent concrete type aliases:
|
|
||||||
|
|
||||||
```slo
|
|
||||||
(type JsonText string)
|
|
||||||
```
|
|
||||||
|
|
||||||
Aliases are module-local names for already supported concrete target types.
|
|
||||||
They may appear in local signatures and annotations, but they do not create new
|
|
||||||
runtime representations or stable ABI names. Project imports of functions that
|
|
||||||
use aliases see the resolved concrete target type. Alias exports, imports,
|
|
||||||
re-exports, generic aliases, parameterized aliases, maps/sets, and new
|
|
||||||
compiler-known runtime names remain out of scope.
|
|
||||||
|
|
||||||
## 1.0.0-beta.9 Collection Alias Unification And Generic Reservation
|
|
||||||
|
|
||||||
The `1.0.0-beta.9` release applies beta.8 concrete aliases inside the current
|
|
||||||
source-authored collection/value-family facades. The public helper names and
|
|
||||||
runtime behavior remain concrete: current vectors, options, and results are
|
|
||||||
still concrete families, and the local aliases are erased before lowering.
|
|
||||||
|
|
||||||
This release also reserves the generic/map/set direction through diagnostics
|
|
||||||
and documentation only. It does not implement executable generics, maps, sets,
|
|
||||||
traits, inference, monomorphization, iterators, stable ABI/layout promises, or
|
|
||||||
a stable standard-library API freeze.
|
|
||||||
|
|
||||||
## 1.0.0-beta.10 Developer Experience API Discovery
|
|
||||||
|
|
||||||
The `1.0.0-beta.10` release upgrades the generated standard-library API
|
|
||||||
catalog from exported helper names to exact exported helper signatures. The
|
|
||||||
renderer verifies that every exported helper has a matching `(fn ...)` form,
|
|
||||||
normalizes module-local concrete aliases from the public signatures, omits
|
|
||||||
non-exported helpers and `(type ...)` aliases, and keeps the catalog generated
|
|
||||||
from `lib/std/*.slo`.
|
|
||||||
|
|
||||||
It also adds `glagol symbols <file.slo|project|workspace>` as an
|
|
||||||
editor-facing metadata command. The output is deterministic S-expression text
|
|
||||||
using `slovo.symbols` schema version `1.0.0-beta.10`; it includes module paths,
|
|
||||||
source spans/ranges, imports, exports, aliases, structs, enums, functions,
|
|
||||||
tests, and workspace package labels.
|
|
||||||
|
|
||||||
This release is tooling, documentation, and API-discovery work. It does not
|
|
||||||
add executable generics, maps, sets, new runtime helpers, new compiler-known
|
|
||||||
runtime names, ABI/layout guarantees, an LSP server, watch mode, or a stable
|
|
||||||
standard-library API freeze.
|
|
||||||
|
|
||||||
## 1.0.0-beta.11 Local Package API Documentation
|
|
||||||
|
|
||||||
The `1.0.0-beta.11` release extends the beta.10 API discovery lane to local
|
|
||||||
package and module documentation. `glagol doc <file|project|workspace> -o <dir>`
|
|
||||||
includes deterministic exported/public API sections for local modules and
|
|
||||||
workspace packages.
|
|
||||||
|
|
||||||
Those sections list exact exported function signatures, exported struct fields,
|
|
||||||
and exported enum variants with payload types. They omit non-exported
|
|
||||||
functions, structs, enums, tests, and aliases from the public API surface, and
|
|
||||||
they normalize module-local concrete aliases before rendering public types.
|
|
||||||
|
|
||||||
This remains beta API discovery. It does not freeze the Markdown schema, create
|
|
||||||
a stable stdlib/API compatibility freeze, add LSP/watch behavior, define
|
|
||||||
SARIF or daemon protocols, set diagnostics schema policy, implement executable
|
|
||||||
generics, maps, or sets, add re-exports, globs, or hierarchical modules, or
|
|
||||||
define package registry semantics.
|
|
||||||
|
|
||||||
## 1.0.0-beta.12 Concrete Vector Query And Prefix Parity
|
|
||||||
|
|
||||||
The `1.0.0-beta.12` release is a source-authored stdlib/helper parity update
|
|
||||||
for concrete vectors. It adds `count_of`, `starts_with`, `without_prefix`,
|
|
||||||
`ends_with`, and `without_suffix` to `std.vec_i64`, and adds `count_of` to
|
|
||||||
`std.vec_f64`, matching the already staged concrete helper style.
|
|
||||||
|
|
||||||
This release does not change the source language, runtime, compiler-known
|
|
||||||
`std.vec.*` names, ABI/layout contract, or performance contract. Generics,
|
|
||||||
maps/sets, iterators, mutable vectors, slice/view APIs, new runtime helper
|
|
||||||
names, stable stdlib API freeze, and broader collection abstractions remain
|
|
||||||
deferred.
|
|
||||||
|
|
||||||
## 1.0.0-beta.13 Diagnostic Catalog And Schema Policy
|
|
||||||
|
|
||||||
The `1.0.0-beta.13` release is a docs/tooling policy update for the existing
|
|
||||||
diagnostic surface. It adds
|
|
||||||
[`docs/language/DIAGNOSTICS.md`](docs/language/DIAGNOSTICS.md) as the beta
|
|
||||||
policy for `slovo.diagnostic` version `1`.
|
|
||||||
|
|
||||||
The policy documents the S-expression and JSON encodings, required and optional
|
|
||||||
machine fields, severity/source/range/related-span semantics, JSON-line
|
|
||||||
discipline, source-less diagnostics, artifact-manifest diagnostic metadata,
|
|
||||||
compatibility and migration classes, and the current code catalog covered by
|
|
||||||
the golden diagnostics contract.
|
|
||||||
|
|
||||||
This release does not change the source language, runtime, stdlib API,
|
|
||||||
diagnostic output shape, compiler CLI, LSP/watch behavior, SARIF/daemon
|
|
||||||
protocols, stable Markdown schema, or stable `1.0.0` diagnostics freeze.
|
|
||||||
|
|
||||||
## 1.0.0-beta.16 String Scanning And Token Boundary Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.16` release adds the first explicit byte-oriented string
|
|
||||||
scanning and token-boundary helpers to the `std.string` source facade:
|
|
||||||
`byte_at_result`, `slice_result`, `starts_with`, and `ends_with`.
|
|
||||||
|
|
||||||
The helpers operate over the current NUL-terminated runtime string bytes before
|
|
||||||
the trailing NUL. `byte_at_result` and `slice_result` return `err 1` for
|
|
||||||
ordinary invalid indexes or ranges. `slice_result` returns a runtime-owned
|
|
||||||
string on success; allocation failure may trap with the existing string
|
|
||||||
allocation policy.
|
|
||||||
|
|
||||||
This release does not add Unicode scalar, grapheme, display-width, or locale
|
|
||||||
semantics; full JSON parsing; object/array parsing; tokenizer APIs; a language
|
|
||||||
slice/view feature; mutable strings; stable ABI/layout; or a stable stdlib/API
|
|
||||||
freeze.
|
|
||||||
|
|
||||||
## 1.0.0-beta.20 String Search And ASCII Trim Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.20` scope extends the source-authored `std.string` facade
|
|
||||||
with byte-oriented search and ASCII trim helpers:
|
|
||||||
`contains`, `index_of_option`, `last_index_of_option`, `trim_ascii_start`,
|
|
||||||
`trim_ascii_end`, and `trim_ascii`.
|
|
||||||
|
|
||||||
This scope adds no compiler-known runtime names. It does not claim Unicode
|
|
||||||
scalar, grapheme, case-folding, locale, regex, tokenizer, mutable string,
|
|
||||||
language slice/view, stable ABI/layout, or stable stdlib/API semantics.
|
|
||||||
|
|
||||||
## 1.0.0-beta.21 JSON Document Scalar Parsing Foundation
|
|
||||||
|
|
||||||
The `1.0.0-beta.21` scope extends `std.json` with source-authored
|
|
||||||
whole-document scalar helpers:
|
|
||||||
`parse_string_document_result`, `parse_bool_document_result`,
|
|
||||||
`parse_i32_document_result`, `parse_u32_document_result`,
|
|
||||||
`parse_i64_document_result`, `parse_u64_document_result`,
|
|
||||||
`parse_f64_document_result`, and `parse_null_document_result`.
|
|
||||||
|
|
||||||
The helpers accept leading and trailing ASCII whitespace around a single
|
|
||||||
scalar document by composing with `std.string.trim_ascii`; trailing
|
|
||||||
non-whitespace still fails through the existing exact value-token parsers.
|
|
||||||
|
|
||||||
This scope does not add object/array parsing, recursive JSON values, parser
|
|
||||||
objects, maps/sets, streaming, Unicode escape decoding beyond the existing
|
|
||||||
string-token behavior, embedded NUL policy, new compiler-known runtime names,
|
|
||||||
stable ABI/layout, or stable stdlib/API semantics.
|
|
||||||
|
|
||||||
## 1.0.0-beta.22 Run Manifest And Execution Report Hardening
|
|
||||||
|
|
||||||
The `1.0.0-beta.22` scope hardens `glagol run --manifest` evidence by adding
|
|
||||||
an additive run-report block to run-mode artifact manifests. The block records
|
|
||||||
the executed program's exit status, captured stdout, captured stderr, and
|
|
||||||
forwarded program arguments.
|
|
||||||
|
|
||||||
This is tooling/CLI evidence work only. It does not add language syntax,
|
|
||||||
stdlib helpers, compiler-known runtime names, runtime C capabilities, package
|
|
||||||
or import behavior, stable artifact-manifest schema guarantees, stable
|
|
||||||
ABI/layout, or stable stdlib/API semantics.
|
|
||||||
|
|
||||||
## 1.0.0-beta.15 Reserved Generic Collection Boundary Hardening And Collection Ledger
|
|
||||||
|
|
||||||
The `1.0.0-beta.15` release documents the current concrete collection and
|
|
||||||
value-family boundary. It adds
|
|
||||||
[`docs/language/COLLECTIONS.md`](docs/language/COLLECTIONS.md), which links to
|
|
||||||
the generated
|
|
||||||
[`docs/language/STDLIB_API.md`](docs/language/STDLIB_API.md) catalog instead
|
|
||||||
of duplicating generated helper counts.
|
|
||||||
|
|
||||||
The ledger inventories concrete vector, option, result, and related
|
|
||||||
option/result-returning facade surfaces; records the design pressure from
|
|
||||||
duplicated concrete vector/option/result helpers; and defines prerequisites
|
|
||||||
before executable generics, generic aliases, maps, sets, iterators, mutable
|
|
||||||
vectors, or slice/view APIs can be promoted.
|
|
||||||
|
|
||||||
Current unsupported diagnostics are documented as release boundaries, not as
|
|
||||||
new behavior. This release does not change the source language, runtime,
|
|
||||||
stdlib/API surface, diagnostic output shape/codes/schema, benchmark metadata
|
|
||||||
schema, compiler ABI/layout behavior, or performance claims, and it does not
|
|
||||||
create a stable stdlib/API freeze. It does reword affected reserved-boundary
|
|
||||||
diagnostic messages from beta.9-specific text to current-beta wording while
|
|
||||||
preserving diagnostic codes, schema, spans, expected/found values, hints, and
|
|
||||||
output shape.
|
|
||||||
|
|
||||||
## 1.0.0-beta.14 Benchmark Suite Catalog And Metadata Gate
|
|
||||||
|
|
||||||
The `1.0.0-beta.14` release documents the existing benchmark suite catalog as
|
|
||||||
beta-scoped metadata tooling. It adds
|
|
||||||
[`benchmarks/README.md`](benchmarks/README.md) with the current ten-suite
|
|
||||||
inventory, local evidence policy, suite-list commands, and exclusions.
|
|
||||||
|
|
||||||
The root suite catalog commands are:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/runner.py --suite-list
|
|
||||||
python3 benchmarks/runner.py --suite-list --json
|
|
||||||
```
|
|
||||||
|
|
||||||
The non-JSON listing is for local review. The JSON listing is beta tooling
|
|
||||||
metadata for local gates and adapters, not a stable public schema.
|
|
||||||
|
|
||||||
This release does not add benchmark kernels, publish timing numbers, define
|
|
||||||
performance thresholds, change the source language, runtime, stdlib/API
|
|
||||||
surface, diagnostic output, compiler ABI/layout behavior, or make cross-machine
|
|
||||||
performance claims.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- [Language Manifest](docs/language/MANIFEST.md)
|
- [Language Manifest](docs/language/MANIFEST.md)
|
||||||
- [Language Specification](docs/language/SPEC-v1.md)
|
- [Language Specification](docs/language/SPEC-v1.md)
|
||||||
- [Diagnostics Policy](docs/language/DIAGNOSTICS.md)
|
|
||||||
- [Local Package And Workspace Guide](docs/language/PACKAGES.md)
|
|
||||||
- [Standard Library API Catalog](docs/language/STDLIB_API.md)
|
|
||||||
- [Collection Ledger](docs/language/COLLECTIONS.md)
|
|
||||||
- [Benchmark Suite Catalog](benchmarks/README.md)
|
|
||||||
- [Compiler Manifest](docs/compiler/GLAGOL_COMPILER_MANIFEST.md)
|
- [Compiler Manifest](docs/compiler/GLAGOL_COMPILER_MANIFEST.md)
|
||||||
- [Post-Beta Roadmap](docs/POST_BETA_ROADMAP.md)
|
|
||||||
- [Slovo Whitepaper](docs/papers/SLOVO_WHITEPAPER.md)
|
- [Slovo Whitepaper](docs/papers/SLOVO_WHITEPAPER.md)
|
||||||
- [Glagol Whitepaper](docs/papers/GLAGOL_WHITEPAPER.md)
|
- [Glagol Whitepaper](docs/papers/GLAGOL_WHITEPAPER.md)
|
||||||
|
|
||||||
|
|||||||
@ -1,83 +0,0 @@
|
|||||||
# Slovo Benchmark Suite Catalog
|
|
||||||
|
|
||||||
Release stage: `1.0.0-beta.14`.
|
|
||||||
|
|
||||||
The benchmark suite is beta-scoped local tooling. It catalogs deterministic
|
|
||||||
same-machine comparison scaffolds for Slovo and sibling implementations. It
|
|
||||||
does not publish benchmark results, set performance thresholds, define optimizer
|
|
||||||
claims, or create cross-machine comparisons.
|
|
||||||
|
|
||||||
## Suite Listing
|
|
||||||
|
|
||||||
From the repository root, list the suite catalog for humans:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/runner.py --suite-list
|
|
||||||
```
|
|
||||||
|
|
||||||
List the same catalog as beta tooling metadata:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/runner.py --suite-list --json
|
|
||||||
```
|
|
||||||
|
|
||||||
The non-JSON listing is for local review. The JSON listing is for local gates
|
|
||||||
and tooling adapters that need the current suite inventory. The JSON field set
|
|
||||||
is not a stable public schema; it may change during beta releases.
|
|
||||||
Both forms verify the required scaffold files for each suite:
|
|
||||||
`benchmark.json`, `run.py`, `slovo.toml`, and `src/main.slo`.
|
|
||||||
|
|
||||||
Each suite still owns its local metadata and run commands:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/<suite>/run.py --list
|
|
||||||
python3 benchmarks/<suite>/run.py --list --json
|
|
||||||
python3 benchmarks/<suite>/run.py --dry-run
|
|
||||||
```
|
|
||||||
|
|
||||||
## Current Suites
|
|
||||||
|
|
||||||
All current suites provide Slovo, C, Rust, Python, Clojure, and Common
|
|
||||||
Lisp/SBCL source slots. Missing local toolchains are skipped by the per-suite
|
|
||||||
runner where possible.
|
|
||||||
|
|
||||||
| Suite | Focus | Base checksum | Hot-loop checksum | Runtime args |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| `math-loop` | arithmetic and scalar accumulation | `5000001` | `50000001` | none |
|
|
||||||
| `branch-loop` | deterministic branch-heavy integer loop | `1185071` | `220775` | none |
|
|
||||||
| `parse-loop` | repeated signed decimal `i32` parsing | `345000001` | `450000001` | `12345` |
|
|
||||||
| `array-index-loop` | immutable fixed-array indexing | `3875007` | `38750007` | none |
|
|
||||||
| `string-eq-loop` | fixed string lookup and equality | `4600001` | `46000001` | `omega` |
|
|
||||||
| `array-struct-field-loop` | fixed-array access through an immutable struct field | `3875011` | `38750011` | none |
|
|
||||||
| `enum-struct-payload-loop` | enum payload matching with struct and array access | `3500013` | `35000013` | none |
|
|
||||||
| `vec-i32-index-loop` | runtime-owned `(vec i32)` indexing | `3875007` | `38750007` | none |
|
|
||||||
| `vec-string-eq-loop` | runtime-owned `(vec string)` lookup and equality | `4600001` | `46000001` | `omega` |
|
|
||||||
| `json-quote-loop` | compact JSON string quoting | `15000001` | `150000001` | `slo"vo\path` |
|
|
||||||
|
|
||||||
The base loop count is `1000000` for every current suite. The hot-loop count is
|
|
||||||
`10000000` for every current suite. The runner supplies loop counts and runtime
|
|
||||||
arguments at execution time so native compilers cannot fold the work into a
|
|
||||||
constant answer.
|
|
||||||
|
|
||||||
## Local Evidence Only
|
|
||||||
|
|
||||||
Benchmark output is local-machine evidence only:
|
|
||||||
|
|
||||||
- cold-process mode measures execution after each implementation has been built
|
|
||||||
once; it does not include compile time
|
|
||||||
- hot-loop mode is startup-amortized local evidence and reports total time plus
|
|
||||||
normalized timing for the base loop count
|
|
||||||
- Clojure timings include JVM and Clojure startup
|
|
||||||
- Common Lisp timings include SBCL script startup
|
|
||||||
- reported timings depend on the local CPU, OS, compiler versions, toolchain
|
|
||||||
availability, thermal/load state, and runner configuration
|
|
||||||
|
|
||||||
This catalog intentionally publishes no timing numbers.
|
|
||||||
|
|
||||||
## Exclusions
|
|
||||||
|
|
||||||
`1.0.0-beta.14` does not add benchmark kernels, publish timings, define
|
|
||||||
performance thresholds, define a stable JSON schema, change the Slovo source
|
|
||||||
language, change runtime behavior, change standard-library or API contracts,
|
|
||||||
change diagnostic output, change ABI/layout behavior, or make cross-machine
|
|
||||||
performance claims.
|
|
||||||
1
benchmarks/json-quote-loop/.gitignore
vendored
1
benchmarks/json-quote-loop/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
build/
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
# JSON Quote Loop Benchmark Scaffold
|
|
||||||
|
|
||||||
Release: `1.0.0-beta.7`.
|
|
||||||
|
|
||||||
This benchmark compares compact JSON string quoting plus checksum accumulation
|
|
||||||
across Slovo, C, Rust, Python, Clojure, and Common Lisp/SBCL on the same
|
|
||||||
machine. It is same machine local evidence only.
|
|
||||||
|
|
||||||
It is not a published benchmark result, performance threshold, optimizer
|
|
||||||
claim, or cross-machine comparison.
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- `src/main.slo`: Slovo project benchmark fixture
|
|
||||||
- `c/json_quote_loop.c`: C comparison implementation
|
|
||||||
- `rust/json_quote_loop.rs`: Rust comparison implementation
|
|
||||||
- `python/json_quote_loop.py`: Python comparison implementation
|
|
||||||
- `clojure/json_quote_loop.clj`: Clojure comparison implementation
|
|
||||||
- `common-lisp/json_quote_loop.lisp`: Common Lisp/SBCL comparison implementation
|
|
||||||
- `run.py`: build/run/timing harness
|
|
||||||
|
|
||||||
All implementations print checksum `15000001` for loop count `1000000` and
|
|
||||||
target string `slo"vo\path`. Hot-loop mode uses loop count `10000000` and
|
|
||||||
checksum `150000001`.
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
Run from the repository root:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --list
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --dry-run
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --only python --repeats 3 --warmups 1
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --mode hot-loop --only slovo --only c --only rust
|
|
||||||
```
|
|
||||||
|
|
||||||
To include Slovo, build or point at a Glagol binary and make sure host Clang is
|
|
||||||
available:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build --manifest-path compiler/Cargo.toml --bin glagol
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --glagol compiler/target/debug/glagol
|
|
||||||
```
|
|
||||||
|
|
||||||
The runner skips missing C/Rust/Slovo/Clojure/SBCL toolchains where possible.
|
|
||||||
Use `--only` multiple times to select implementations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 benchmarks/json-quote-loop/run.py --only slovo --only c --only rust --only clojure --only common_lisp
|
|
||||||
```
|
|
||||||
|
|
||||||
Clojure is detected with `clojure` on PATH, `CLOJURE`, or `CLOJURE_JAR`.
|
|
||||||
Common Lisp is detected with `sbcl` on PATH, `SBCL`, or `--sbcl`.
|
|
||||||
|
|
||||||
## Comparison Method
|
|
||||||
|
|
||||||
- The runner builds each implementation once before timing. The reported
|
|
||||||
numbers measure execution only, not compile time.
|
|
||||||
- Slovo timings use `glagol build`, which currently lowers to LLVM and then
|
|
||||||
invokes host `clang -O2` with `runtime/runtime.c`.
|
|
||||||
- C timings use `clang -O2 -std=c11`.
|
|
||||||
- Rust timings use `rustc -C opt-level=3 -C debuginfo=0`.
|
|
||||||
- The measured loop quotes one runtime-supplied ASCII string containing a
|
|
||||||
quote and a backslash, then accumulates the quoted byte length.
|
|
||||||
|
|
||||||
Timing is cold-process local-machine evidence only. Clojure timings include
|
|
||||||
JVM and Clojure startup, while Common Lisp timings include SBCL script
|
|
||||||
startup.
|
|
||||||
|
|
||||||
Hot-loop mode is startup-amortized local evidence. It runs a larger loop count
|
|
||||||
and reports total time plus normalized time for the base `1000000` loop count.
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"benchmark": "json-quote-loop",
|
|
||||||
"source_stem": "json_quote_loop",
|
|
||||||
"loop_count": 1000000,
|
|
||||||
"expected_checksum": "15000001",
|
|
||||||
"stdin": "1000000\n",
|
|
||||||
"hot_loop_count": 10000000,
|
|
||||||
"hot_expected_checksum": "150000001",
|
|
||||||
"hot_stdin": "10000000\n",
|
|
||||||
"run_args": ["slo\"vo\\path"]
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#define LOOP_COUNT 1000000
|
|
||||||
#define EXPECTED_CHECKSUM 15000001
|
|
||||||
|
|
||||||
static int32_t configured_loop_count(void) {
|
|
||||||
int32_t value = LOOP_COUNT;
|
|
||||||
if (scanf("%d", &value) != 1 || value <= 0) {
|
|
||||||
return LOOP_COUNT;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *configured_target(int argc, char **argv) {
|
|
||||||
return argc > 1 ? argv[1] : "slo\"vo\\path";
|
|
||||||
}
|
|
||||||
|
|
||||||
static char json_hex_digit(unsigned char value) {
|
|
||||||
return value < 10 ? (char)('0' + value) : (char)('A' + (value - 10));
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *quote_json_string(const char *text) {
|
|
||||||
size_t len = 2;
|
|
||||||
for (const unsigned char *cursor = (const unsigned char *)text; *cursor != '\0'; cursor++) {
|
|
||||||
switch (*cursor) {
|
|
||||||
case '"':
|
|
||||||
case '\\':
|
|
||||||
case '\n':
|
|
||||||
case '\t':
|
|
||||||
case '\r':
|
|
||||||
case '\b':
|
|
||||||
case '\f':
|
|
||||||
len += 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
len += *cursor < 0x20 ? 6 : 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char *out = malloc(len + 1);
|
|
||||||
if (out == NULL) {
|
|
||||||
exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
char *write = out;
|
|
||||||
*write++ = '"';
|
|
||||||
for (const unsigned char *cursor = (const unsigned char *)text; *cursor != '\0'; cursor++) {
|
|
||||||
switch (*cursor) {
|
|
||||||
case '"':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = '"';
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = '\\';
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 'n';
|
|
||||||
break;
|
|
||||||
case '\t':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 't';
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 'r';
|
|
||||||
break;
|
|
||||||
case '\b':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 'b';
|
|
||||||
break;
|
|
||||||
case '\f':
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 'f';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (*cursor < 0x20) {
|
|
||||||
*write++ = '\\';
|
|
||||||
*write++ = 'u';
|
|
||||||
*write++ = '0';
|
|
||||||
*write++ = '0';
|
|
||||||
*write++ = json_hex_digit((unsigned char)(*cursor >> 4));
|
|
||||||
*write++ = json_hex_digit((unsigned char)(*cursor & 0x0F));
|
|
||||||
} else {
|
|
||||||
*write++ = (char)*cursor;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*write++ = '"';
|
|
||||||
*write = '\0';
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int32_t json_quote_loop(int32_t limit, const char *target) {
|
|
||||||
int32_t i = 0;
|
|
||||||
int32_t acc = 1;
|
|
||||||
|
|
||||||
while (i < limit) {
|
|
||||||
char *quoted = quote_json_string(target);
|
|
||||||
acc += (int32_t)strlen(quoted);
|
|
||||||
free(quoted);
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
int32_t result = json_quote_loop(configured_loop_count(), configured_target(argc, argv));
|
|
||||||
printf("%d\n", result);
|
|
||||||
return result == EXPECTED_CHECKSUM ? 0 : 1;
|
|
||||||
}
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
(set! *warn-on-reflection* true)
|
|
||||||
(set! *unchecked-math* :warn-on-boxed)
|
|
||||||
|
|
||||||
(def loop-count 1000000)
|
|
||||||
(def expected-checksum 15000001)
|
|
||||||
|
|
||||||
(defn configured-loop-count []
|
|
||||||
(try
|
|
||||||
(let [line (read-line)
|
|
||||||
value (Integer/parseInt (.trim ^String line))]
|
|
||||||
(if (pos? value) value loop-count))
|
|
||||||
(catch Exception _
|
|
||||||
loop-count)))
|
|
||||||
|
|
||||||
(defn configured-target []
|
|
||||||
(or (first *command-line-args*) "slo\"vo\\path"))
|
|
||||||
|
|
||||||
(defn quote-json-string [^String value]
|
|
||||||
(let [builder (StringBuilder.)]
|
|
||||||
(.append builder \")
|
|
||||||
(dotimes [index (.length value)]
|
|
||||||
(let [ch (.charAt value index)]
|
|
||||||
(case ch
|
|
||||||
\" (.append builder "\\\"")
|
|
||||||
\\ (.append builder "\\\\")
|
|
||||||
\newline (.append builder "\\n")
|
|
||||||
\tab (.append builder "\\t")
|
|
||||||
\return (.append builder "\\r")
|
|
||||||
(.append builder ch))))
|
|
||||||
(.append builder \")
|
|
||||||
(.toString builder)))
|
|
||||||
|
|
||||||
(defn json-quote-loop [limit target]
|
|
||||||
(loop [i 0
|
|
||||||
acc 1]
|
|
||||||
(if (< i limit)
|
|
||||||
(recur (inc i) (+ acc (.length ^String (quote-json-string target))))
|
|
||||||
acc)))
|
|
||||||
|
|
||||||
(let [result (json-quote-loop (configured-loop-count) (configured-target))]
|
|
||||||
(println result)
|
|
||||||
(System/exit (if (= result expected-checksum) 0 1)))
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
(declaim (optimize (speed 3) (safety 0) (debug 0)))
|
|
||||||
|
|
||||||
(defconstant +loop-count+ 1000000)
|
|
||||||
(defconstant +expected-checksum+ 15000001)
|
|
||||||
|
|
||||||
(declaim (ftype (function () fixnum) configured-loop-count))
|
|
||||||
(defun configured-loop-count ()
|
|
||||||
(handler-case
|
|
||||||
(let ((line (read-line *standard-input* nil nil)))
|
|
||||||
(if line
|
|
||||||
(let ((value (parse-integer line :junk-allowed t)))
|
|
||||||
(if (> value 0) value +loop-count+))
|
|
||||||
+loop-count+))
|
|
||||||
(error () +loop-count+)))
|
|
||||||
|
|
||||||
(declaim (ftype (function () string) configured-target))
|
|
||||||
(defun configured-target ()
|
|
||||||
(or (second sb-ext:*posix-argv*) "slo\"vo\\path"))
|
|
||||||
|
|
||||||
(declaim (ftype (function (string) string) quote-json-string))
|
|
||||||
(defun quote-json-string (value)
|
|
||||||
(with-output-to-string (out)
|
|
||||||
(write-char #\" out)
|
|
||||||
(loop for ch across value
|
|
||||||
do (case ch
|
|
||||||
(#\" (write-string "\\\"" out))
|
|
||||||
(#\\ (write-string "\\\\" out))
|
|
||||||
(#\Newline (write-string "\\n" out))
|
|
||||||
(#\Tab (write-string "\\t" out))
|
|
||||||
(#\Return (write-string "\\r" out))
|
|
||||||
(otherwise (write-char ch out))))
|
|
||||||
(write-char #\" out)))
|
|
||||||
|
|
||||||
(declaim (ftype (function (fixnum string) fixnum) json-quote-loop))
|
|
||||||
(defun json-quote-loop (limit target)
|
|
||||||
(declare (type fixnum limit)
|
|
||||||
(type string target))
|
|
||||||
(loop with i of-type fixnum = 0
|
|
||||||
with acc of-type fixnum = 1
|
|
||||||
while (< i limit)
|
|
||||||
do (setf acc (+ acc (length (quote-json-string target)))
|
|
||||||
i (+ i 1))
|
|
||||||
finally (return acc)))
|
|
||||||
|
|
||||||
(let ((result (json-quote-loop (configured-loop-count) (configured-target))))
|
|
||||||
(format t "~D~%" result)
|
|
||||||
(sb-ext:exit :code (if (= result +expected-checksum+) 0 1)))
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
LOOP_COUNT = 1_000_000
|
|
||||||
EXPECTED_CHECKSUM = 15_000_001
|
|
||||||
|
|
||||||
|
|
||||||
def configured_loop_count() -> int:
|
|
||||||
try:
|
|
||||||
value = int(input().strip())
|
|
||||||
except (EOFError, ValueError):
|
|
||||||
return LOOP_COUNT
|
|
||||||
|
|
||||||
return value if value > 0 else LOOP_COUNT
|
|
||||||
|
|
||||||
|
|
||||||
def configured_target() -> str:
|
|
||||||
return sys.argv[1] if len(sys.argv) > 1 else 'slo"vo\\path'
|
|
||||||
|
|
||||||
|
|
||||||
def quote_json_string(value: str) -> str:
|
|
||||||
return (
|
|
||||||
'"'
|
|
||||||
+ value.replace("\\", "\\\\")
|
|
||||||
.replace('"', '\\"')
|
|
||||||
.replace("\n", "\\n")
|
|
||||||
.replace("\t", "\\t")
|
|
||||||
.replace("\r", "\\r")
|
|
||||||
.replace("\b", "\\b")
|
|
||||||
.replace("\f", "\\f")
|
|
||||||
+ '"'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def json_quote_loop(limit: int, target: str) -> int:
|
|
||||||
i = 0
|
|
||||||
acc = 1
|
|
||||||
|
|
||||||
while i < limit:
|
|
||||||
acc += len(quote_json_string(target))
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
return acc
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
|
||||||
result = json_quote_loop(configured_loop_count(), configured_target())
|
|
||||||
print(result)
|
|
||||||
return 0 if result == EXPECTED_CHECKSUM else 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main())
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Run the local json-quote-loop benchmark scaffold."""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
||||||
|
|
||||||
from runner import main
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
use std::io::Read;
|
|
||||||
|
|
||||||
const LOOP_COUNT: i32 = 1_000_000;
|
|
||||||
const EXPECTED_CHECKSUM: i32 = 15_000_001;
|
|
||||||
|
|
||||||
fn configured_loop_count() -> i32 {
|
|
||||||
let mut input = String::new();
|
|
||||||
if std::io::stdin().read_to_string(&mut input).is_err() {
|
|
||||||
return LOOP_COUNT;
|
|
||||||
}
|
|
||||||
|
|
||||||
input
|
|
||||||
.trim()
|
|
||||||
.parse::<i32>()
|
|
||||||
.ok()
|
|
||||||
.filter(|value| *value > 0)
|
|
||||||
.unwrap_or(LOOP_COUNT)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configured_target() -> String {
|
|
||||||
std::env::args()
|
|
||||||
.nth(1)
|
|
||||||
.unwrap_or_else(|| "slo\"vo\\path".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quote_json_string(value: &str) -> String {
|
|
||||||
let mut out = String::with_capacity(value.len() + 2);
|
|
||||||
out.push('"');
|
|
||||||
for ch in value.chars() {
|
|
||||||
match ch {
|
|
||||||
'"' => out.push_str("\\\""),
|
|
||||||
'\\' => out.push_str("\\\\"),
|
|
||||||
'\n' => out.push_str("\\n"),
|
|
||||||
'\t' => out.push_str("\\t"),
|
|
||||||
'\r' => out.push_str("\\r"),
|
|
||||||
'\u{08}' => out.push_str("\\b"),
|
|
||||||
'\u{0c}' => out.push_str("\\f"),
|
|
||||||
_ => out.push(ch),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.push('"');
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_quote_loop(limit: i32, target: &str) -> i32 {
|
|
||||||
let mut i = 0;
|
|
||||||
let mut acc = 1;
|
|
||||||
|
|
||||||
while i < limit {
|
|
||||||
acc += quote_json_string(target).len() as i32;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let target = configured_target();
|
|
||||||
let result = json_quote_loop(configured_loop_count(), &target);
|
|
||||||
println!("{}", result);
|
|
||||||
std::process::exit(if result == EXPECTED_CHECKSUM { 0 } else { 1 });
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
[project]
|
|
||||||
name = "json-quote-loop"
|
|
||||||
source_root = "src"
|
|
||||||
entry = "main"
|
|
||||||
@ -1,56 +0,0 @@
|
|||||||
; Benchmark scaffold fixture for local-machine JSON string quoting comparisons only.
|
|
||||||
; Keep LOOP_COUNT and EXPECTED_CHECKSUM aligned with the C/Rust/Python fixtures.
|
|
||||||
; The runner supplies the target through argv and the loop count through stdin
|
|
||||||
; or argv so the quoting path stays runtime-configured.
|
|
||||||
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn loop_count () -> i32
|
|
||||||
1000000)
|
|
||||||
|
|
||||||
(fn expected_checksum () -> i32
|
|
||||||
15000001)
|
|
||||||
|
|
||||||
(fn parse_stdin_loop_count () -> (result i32 i32)
|
|
||||||
(let input (result string i32) (std.io.read_stdin_result))
|
|
||||||
(match input
|
|
||||||
((ok text)
|
|
||||||
(std.string.parse_i32_result text))
|
|
||||||
((err code)
|
|
||||||
(err i32 i32 code))))
|
|
||||||
|
|
||||||
(fn parse_arg_loop_count () -> (result i32 i32)
|
|
||||||
(std.string.parse_i32_result (std.process.arg 2)))
|
|
||||||
|
|
||||||
(fn configured_stdin_loop_count () -> i32
|
|
||||||
(let parsed_stdin (result i32 i32) (parse_stdin_loop_count))
|
|
||||||
(if (is_ok parsed_stdin)
|
|
||||||
(unwrap_ok parsed_stdin)
|
|
||||||
(loop_count)))
|
|
||||||
|
|
||||||
(fn configured_loop_count () -> i32
|
|
||||||
(let parsed_arg (result i32 i32) (parse_arg_loop_count))
|
|
||||||
(if (is_ok parsed_arg)
|
|
||||||
(unwrap_ok parsed_arg)
|
|
||||||
(configured_stdin_loop_count)))
|
|
||||||
|
|
||||||
(fn target_text () -> string
|
|
||||||
(std.process.arg 1))
|
|
||||||
|
|
||||||
(fn json_quote_loop ((limit i32) (target string)) -> i32
|
|
||||||
(var i i32 0)
|
|
||||||
(var acc i32 1)
|
|
||||||
(while (< i limit)
|
|
||||||
(set acc (+ acc (std.string.len (std.json.quote_string target))))
|
|
||||||
(set i (+ i 1)))
|
|
||||||
acc)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(let result i32 (json_quote_loop (configured_loop_count) (target_text)))
|
|
||||||
(std.io.print_i32 result)
|
|
||||||
(if (= result (expected_checksum))
|
|
||||||
0
|
|
||||||
1))
|
|
||||||
|
|
||||||
(test "json quote loop checksum is deterministic"
|
|
||||||
(= (json_quote_loop 3 "slo\"vo\\path") 46))
|
|
||||||
@ -18,12 +18,6 @@ from typing import Callable
|
|||||||
|
|
||||||
TIMING_SCOPE = "local-machine comparison only"
|
TIMING_SCOPE = "local-machine comparison only"
|
||||||
TIMING_MODES = ["cold-process", "hot-loop"]
|
TIMING_MODES = ["cold-process", "hot-loop"]
|
||||||
SUITE_NAME = "glagol-local-benchmark-suite"
|
|
||||||
LOCAL_TIMING_DISCLAIMER = (
|
|
||||||
"Local timing comparison only; not a published benchmark result and not a cross-machine performance claim."
|
|
||||||
)
|
|
||||||
REQUIRED_BENCHMARK_FILES = ["benchmark.json", "run.py", "slovo.toml", "src/main.slo"]
|
|
||||||
EXPECTED_IMPLEMENTATION_NAMES = ["slovo", "c", "rust", "python", "clojure", "common_lisp"]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -58,14 +52,6 @@ class Implementation:
|
|||||||
|
|
||||||
|
|
||||||
def main(root: Path, argv: list[str]) -> int:
|
def main(root: Path, argv: list[str]) -> int:
|
||||||
if any(arg == "--suite-list" for arg in argv):
|
|
||||||
return main_suite(root, argv)
|
|
||||||
if not (root / "benchmark.json").is_file():
|
|
||||||
parser = argparse.ArgumentParser(description="Shared local Glagol benchmark runner.")
|
|
||||||
parser.add_argument("--suite-list", action="store_true", help="list suite metadata and exit")
|
|
||||||
parser.add_argument("--json", action="store_true", help="emit JSON for --suite-list")
|
|
||||||
parser.error("run from a benchmark run.py, or pass --suite-list at the benchmark suite root")
|
|
||||||
|
|
||||||
spec = read_spec(root)
|
spec = read_spec(root)
|
||||||
implementations = available_implementations(root, spec)
|
implementations = available_implementations(root, spec)
|
||||||
parser = argparse.ArgumentParser(description=f"Run local {spec.name} timing comparisons.")
|
parser = argparse.ArgumentParser(description=f"Run local {spec.name} timing comparisons.")
|
||||||
@ -103,25 +89,6 @@ def main(root: Path, argv: list[str]) -> int:
|
|||||||
return 1 if any(result["status"] == "failed" for result in results) else 0
|
return 1 if any(result["status"] == "failed" for result in results) else 0
|
||||||
|
|
||||||
|
|
||||||
def main_suite(root: Path, argv: list[str]) -> int:
|
|
||||||
parser = argparse.ArgumentParser(description="List local Glagol benchmark suite metadata.")
|
|
||||||
parser.add_argument("--suite-list", action="store_true", help="list suite metadata and exit")
|
|
||||||
parser.add_argument("--json", action="store_true", help="emit JSON for suite metadata")
|
|
||||||
args = parser.parse_args(argv)
|
|
||||||
if not args.suite_list:
|
|
||||||
parser.error("pass --suite-list to list benchmark suite metadata")
|
|
||||||
|
|
||||||
suite_root = resolve_suite_root(root)
|
|
||||||
emit_suite_list(build_suite_catalog(suite_root), args.json)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_suite_root(root: Path) -> Path:
|
|
||||||
if (root / "benchmark.json").is_file():
|
|
||||||
return root.parent
|
|
||||||
return root
|
|
||||||
|
|
||||||
|
|
||||||
def read_spec(root: Path) -> BenchmarkSpec:
|
def read_spec(root: Path) -> BenchmarkSpec:
|
||||||
data = json.loads((root / "benchmark.json").read_text(encoding="utf-8"))
|
data = json.loads((root / "benchmark.json").read_text(encoding="utf-8"))
|
||||||
loop_count = int(data["loop_count"])
|
loop_count = int(data["loop_count"])
|
||||||
@ -311,143 +278,6 @@ def select_implementations(implementations: list[Implementation], names: list[st
|
|||||||
return [impl for impl in implementations if impl.name in selected_names]
|
return [impl for impl in implementations if impl.name in selected_names]
|
||||||
|
|
||||||
|
|
||||||
def build_suite_catalog(suite_root: Path) -> dict[str, object]:
|
|
||||||
benchmarks: list[dict[str, object]] = []
|
|
||||||
implementation_slot_count = 0
|
|
||||||
missing_required_files: list[str] = []
|
|
||||||
missing_implementation_slots: list[str] = []
|
|
||||||
|
|
||||||
for root in suite_benchmark_roots(suite_root):
|
|
||||||
spec = read_spec(root)
|
|
||||||
implementations = available_implementations(root, spec)
|
|
||||||
implementation_slot_count += len(implementations)
|
|
||||||
benchmark = suite_benchmark_metadata(suite_root, root, spec, implementations)
|
|
||||||
benchmarks.append(benchmark)
|
|
||||||
|
|
||||||
directory = str(benchmark["directory"])
|
|
||||||
for required_file in benchmark["required_files"]:
|
|
||||||
assert isinstance(required_file, dict)
|
|
||||||
if required_file["status"] != "present":
|
|
||||||
missing_required_files.append(f"{directory}/{required_file['path']}")
|
|
||||||
|
|
||||||
present_implementations = {
|
|
||||||
str(implementation["name"])
|
|
||||||
for implementation in benchmark["implementation_slots"]
|
|
||||||
if isinstance(implementation, dict)
|
|
||||||
}
|
|
||||||
for expected in EXPECTED_IMPLEMENTATION_NAMES:
|
|
||||||
if expected not in present_implementations:
|
|
||||||
missing_implementation_slots.append(f"{directory}:{expected}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
"suite": SUITE_NAME,
|
|
||||||
"timing_scope": TIMING_SCOPE,
|
|
||||||
"timing_modes": TIMING_MODES,
|
|
||||||
"timing_disclaimer": LOCAL_TIMING_DISCLAIMER,
|
|
||||||
"benchmark_count": len(benchmarks),
|
|
||||||
"benchmarks": benchmarks,
|
|
||||||
"verification": {
|
|
||||||
"status": "ok" if not missing_required_files and not missing_implementation_slots else "incomplete",
|
|
||||||
"benchmark_metadata_files": len(benchmarks),
|
|
||||||
"required_files": len(benchmarks) * len(REQUIRED_BENCHMARK_FILES),
|
|
||||||
"missing_required_files": missing_required_files,
|
|
||||||
"implementation_slots": implementation_slot_count,
|
|
||||||
"expected_implementation_slots": len(benchmarks) * len(EXPECTED_IMPLEMENTATION_NAMES),
|
|
||||||
"missing_implementation_slots": missing_implementation_slots,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def suite_benchmark_roots(suite_root: Path) -> list[Path]:
|
|
||||||
return sorted(
|
|
||||||
[path for path in suite_root.iterdir() if path.is_dir() and (path / "benchmark.json").is_file()],
|
|
||||||
key=lambda path: path.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def suite_benchmark_metadata(
|
|
||||||
suite_root: Path,
|
|
||||||
root: Path,
|
|
||||||
spec: BenchmarkSpec,
|
|
||||||
implementations: list[Implementation],
|
|
||||||
) -> dict[str, object]:
|
|
||||||
return {
|
|
||||||
"name": spec.name,
|
|
||||||
"directory": str(root.relative_to(suite_root)),
|
|
||||||
"source_stem": spec.source_stem,
|
|
||||||
"timing_modes": TIMING_MODES,
|
|
||||||
"loop_count_source": "stdin",
|
|
||||||
"loop_count": spec.loop_count,
|
|
||||||
"hot_loop_count": spec.hot_loop_count,
|
|
||||||
"expected_checksum": spec.expected_checksum,
|
|
||||||
"hot_expected_checksum": spec.hot_expected_checksum,
|
|
||||||
"required_files": [
|
|
||||||
{
|
|
||||||
"path": relative,
|
|
||||||
"status": "present" if (root / relative).is_file() else "missing",
|
|
||||||
}
|
|
||||||
for relative in REQUIRED_BENCHMARK_FILES
|
|
||||||
],
|
|
||||||
"checksum_metadata": {
|
|
||||||
"cold_process": {
|
|
||||||
"expected_checksum": spec.expected_checksum,
|
|
||||||
"stdin": spec.stdin_text,
|
|
||||||
},
|
|
||||||
"hot_loop": {
|
|
||||||
"expected_checksum": spec.hot_expected_checksum,
|
|
||||||
"stdin": spec.hot_stdin_text,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"run_args": spec.run_args,
|
|
||||||
"implementation_slots": [
|
|
||||||
{
|
|
||||||
"name": impl.name,
|
|
||||||
"language": impl.language,
|
|
||||||
"source": str(impl.source.relative_to(suite_root)),
|
|
||||||
}
|
|
||||||
for impl in implementations
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def emit_suite_list(metadata: dict[str, object], as_json: bool) -> None:
|
|
||||||
if as_json:
|
|
||||||
print(json.dumps(metadata, indent=2, sort_keys=True))
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"{metadata['suite']}: {metadata['timing_scope']}")
|
|
||||||
print(str(metadata["timing_disclaimer"]))
|
|
||||||
print(f"benchmark_count={metadata['benchmark_count']}")
|
|
||||||
print(f"timing_modes={','.join(TIMING_MODES)}")
|
|
||||||
verification = metadata["verification"]
|
|
||||||
assert isinstance(verification, dict)
|
|
||||||
print(f"verification_status={verification['status']}")
|
|
||||||
print(f"required_files={verification['required_files']}")
|
|
||||||
print(f"implementation_slots={verification['implementation_slots']}")
|
|
||||||
print("benchmarks:")
|
|
||||||
for benchmark in metadata["benchmarks"]:
|
|
||||||
assert isinstance(benchmark, dict)
|
|
||||||
print(
|
|
||||||
" {name} ({directory}): loop_count={loop_count} hot_loop_count={hot_loop_count} "
|
|
||||||
"expected_checksum={expected_checksum} hot_expected_checksum={hot_expected_checksum}".format(
|
|
||||||
name=benchmark["name"],
|
|
||||||
directory=benchmark["directory"],
|
|
||||||
loop_count=benchmark["loop_count"],
|
|
||||||
hot_loop_count=benchmark["hot_loop_count"],
|
|
||||||
expected_checksum=benchmark["expected_checksum"],
|
|
||||||
hot_expected_checksum=benchmark["hot_expected_checksum"],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
print(" required_files:")
|
|
||||||
for required_file in benchmark["required_files"]:
|
|
||||||
assert isinstance(required_file, dict)
|
|
||||||
print(f" {required_file['path']}: {required_file['status']}")
|
|
||||||
print(" implementations:")
|
|
||||||
for implementation in benchmark["implementation_slots"]:
|
|
||||||
assert isinstance(implementation, dict)
|
|
||||||
print(f" {implementation['name']}: {implementation['language']} ({implementation['source']})")
|
|
||||||
|
|
||||||
|
|
||||||
def emit_list(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], as_json: bool) -> None:
|
def emit_list(root: Path, spec: BenchmarkSpec, implementations: list[Implementation], as_json: bool) -> None:
|
||||||
metadata = {
|
metadata = {
|
||||||
"benchmark": spec.name,
|
"benchmark": spec.name,
|
||||||
@ -690,7 +520,3 @@ def shlex_quote(value: str) -> str:
|
|||||||
if value and all(char.isalnum() or char in "/._:-" for char in value):
|
if value and all(char.isalnum() or char in "/._:-" for char in value):
|
||||||
return value
|
return value
|
||||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raise SystemExit(main(Path(__file__).resolve().parent, sys.argv[1:]))
|
|
||||||
|
|||||||
2
compiler/Cargo.lock
generated
2
compiler/Cargo.lock
generated
@ -4,4 +4,4 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glagol"
|
name = "glagol"
|
||||||
version = "1.0.0-beta.25"
|
version = "1.0.0-beta"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "glagol"
|
name = "glagol"
|
||||||
version = "1.0.0-beta.25"
|
version = "1.0.0-beta"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Glagol, the first compiler for the Slovo language"
|
description = "Glagol, the first compiler for the Slovo language"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@ -3,7 +3,6 @@ use crate::{token::Span, types::Type};
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
pub module: String,
|
pub module: String,
|
||||||
pub type_aliases: Vec<TypeAliasDecl>,
|
|
||||||
pub enums: Vec<EnumDecl>,
|
pub enums: Vec<EnumDecl>,
|
||||||
pub structs: Vec<StructDecl>,
|
pub structs: Vec<StructDecl>,
|
||||||
pub c_imports: Vec<CImportDecl>,
|
pub c_imports: Vec<CImportDecl>,
|
||||||
@ -11,15 +10,6 @@ pub struct Program {
|
|||||||
pub tests: Vec<Test>,
|
pub tests: Vec<Test>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TypeAliasDecl {
|
|
||||||
pub name: String,
|
|
||||||
pub name_span: Span,
|
|
||||||
pub target: Type,
|
|
||||||
pub target_span: Span,
|
|
||||||
pub span: Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EnumDecl {
|
pub struct EnumDecl {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
use std::collections::{hash_map::Entry, BTreeSet, HashMap, HashSet};
|
use std::collections::{hash_map::Entry, HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::{
|
ast::{
|
||||||
BinaryOp, EnumDecl, Expr, ExprKind, Function, MatchArm, MatchPatternKind, Program,
|
BinaryOp, EnumDecl, Expr, ExprKind, Function, MatchArm, MatchPatternKind, Program,
|
||||||
StructDecl, StructInitField, Test, TypeAliasDecl,
|
StructDecl, StructInitField, Test,
|
||||||
},
|
},
|
||||||
diag::Diagnostic,
|
diag::Diagnostic,
|
||||||
lower,
|
lower, std_runtime,
|
||||||
reserved::{is_generic_type_parameter_name, unsupported_generic_type_parameter},
|
|
||||||
std_runtime,
|
|
||||||
token::Span,
|
token::Span,
|
||||||
types::Type,
|
types::Type,
|
||||||
unsafe_ops,
|
unsafe_ops,
|
||||||
@ -296,23 +294,13 @@ pub fn check_program_with_imports(
|
|||||||
|
|
||||||
fn check_program_inner(
|
fn check_program_inner(
|
||||||
file: &str,
|
file: &str,
|
||||||
mut program: Program,
|
program: Program,
|
||||||
external_functions: &[ExternalFunction],
|
external_functions: &[ExternalFunction],
|
||||||
external_structs: &[ExternalStruct],
|
external_structs: &[ExternalStruct],
|
||||||
external_enums: &[ExternalEnum],
|
external_enums: &[ExternalEnum],
|
||||||
project_resolution: bool,
|
project_resolution: bool,
|
||||||
) -> Result<CheckedProgram, Vec<Diagnostic>> {
|
) -> Result<CheckedProgram, Vec<Diagnostic>> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
errors.extend(resolve_type_aliases(
|
|
||||||
file,
|
|
||||||
&mut program,
|
|
||||||
external_structs,
|
|
||||||
external_enums,
|
|
||||||
));
|
|
||||||
if !errors.is_empty() {
|
|
||||||
return Err(errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut functions = HashMap::new();
|
let mut functions = HashMap::new();
|
||||||
let mut structs = HashMap::new();
|
let mut structs = HashMap::new();
|
||||||
let mut enums = HashMap::new();
|
let mut enums = HashMap::new();
|
||||||
@ -592,645 +580,6 @@ fn is_reserved_callable_name(name: &str) -> bool {
|
|||||||
std_runtime::is_reserved_name(name) || unsafe_ops::is_reserved_head(name)
|
std_runtime::is_reserved_name(name) || unsafe_ops::is_reserved_head(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_type_aliases(
|
|
||||||
file: &str,
|
|
||||||
program: &mut Program,
|
|
||||||
external_structs: &[ExternalStruct],
|
|
||||||
external_enums: &[ExternalEnum],
|
|
||||||
) -> Vec<Diagnostic> {
|
|
||||||
if program.type_aliases.is_empty() {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
let mut struct_names = HashMap::new();
|
|
||||||
for struct_decl in &program.structs {
|
|
||||||
struct_names
|
|
||||||
.entry(struct_decl.name.clone())
|
|
||||||
.or_insert(struct_decl.name_span);
|
|
||||||
}
|
|
||||||
for struct_decl in external_structs {
|
|
||||||
struct_names
|
|
||||||
.entry(struct_decl.name.clone())
|
|
||||||
.or_insert(struct_decl.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut enum_names = HashMap::new();
|
|
||||||
for enum_decl in &program.enums {
|
|
||||||
enum_names
|
|
||||||
.entry(enum_decl.name.clone())
|
|
||||||
.or_insert(enum_decl.name_span);
|
|
||||||
}
|
|
||||||
for enum_decl in external_enums {
|
|
||||||
enum_names
|
|
||||||
.entry(enum_decl.name.clone())
|
|
||||||
.or_insert(enum_decl.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut function_names = HashMap::new();
|
|
||||||
for function in &program.functions {
|
|
||||||
function_names
|
|
||||||
.entry(function.name.clone())
|
|
||||||
.or_insert(function.span);
|
|
||||||
}
|
|
||||||
let mut c_import_names = HashMap::new();
|
|
||||||
for import in &program.c_imports {
|
|
||||||
c_import_names
|
|
||||||
.entry(import.name.clone())
|
|
||||||
.or_insert(import.name_span);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut aliases = HashMap::<String, &TypeAliasDecl>::new();
|
|
||||||
for alias in &program.type_aliases {
|
|
||||||
match aliases.entry(alias.name.clone()) {
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(alias);
|
|
||||||
}
|
|
||||||
Entry::Occupied(entry) => errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"DuplicateTypeAlias",
|
|
||||||
format!("duplicate type alias `{}`", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("original type alias", entry.get().name_span),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for alias in &program.type_aliases {
|
|
||||||
if is_reserved_type_name(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!(
|
|
||||||
"type alias `{}` conflicts with a built-in type name",
|
|
||||||
alias.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.hint("choose an alias name distinct from built-in type names"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(span) = struct_names.get(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with a struct", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("struct declaration", *span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(span) = enum_names.get(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with an enum", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("enum declaration", *span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(span) = function_names.get(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with a function", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("function declaration", *span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(span) = c_import_names.get(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with a C import", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("C import declaration", *span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if matches!(&alias.target, Type::Named(name) if name == &alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"SelfTypeAlias",
|
|
||||||
format!("type alias `{}` directly aliases itself", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.target_span)
|
|
||||||
.hint("point the alias at an existing concrete type"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
let known_structs = struct_names.keys().cloned().collect::<HashSet<_>>();
|
|
||||||
let known_enums = enum_names.keys().cloned().collect::<HashSet<_>>();
|
|
||||||
let mut resolved = HashMap::new();
|
|
||||||
let mut failed = HashSet::new();
|
|
||||||
let mut reported_cycles = BTreeSet::new();
|
|
||||||
let mut names = aliases.keys().cloned().collect::<Vec<_>>();
|
|
||||||
names.sort();
|
|
||||||
for name in names {
|
|
||||||
resolve_alias_target(
|
|
||||||
file,
|
|
||||||
&name,
|
|
||||||
&aliases,
|
|
||||||
&known_structs,
|
|
||||||
&known_enums,
|
|
||||||
&mut resolved,
|
|
||||||
&mut failed,
|
|
||||||
&mut Vec::new(),
|
|
||||||
&mut reported_cycles,
|
|
||||||
&mut errors,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
rewrite_program_alias_types(program, &resolved);
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_alias_target(
|
|
||||||
file: &str,
|
|
||||||
name: &str,
|
|
||||||
aliases: &HashMap<String, &TypeAliasDecl>,
|
|
||||||
structs: &HashSet<String>,
|
|
||||||
enums: &HashSet<String>,
|
|
||||||
resolved: &mut HashMap<String, Type>,
|
|
||||||
failed: &mut HashSet<String>,
|
|
||||||
stack: &mut Vec<String>,
|
|
||||||
reported_cycles: &mut BTreeSet<String>,
|
|
||||||
errors: &mut Vec<Diagnostic>,
|
|
||||||
) -> Option<Type> {
|
|
||||||
if let Some(ty) = resolved.get(name) {
|
|
||||||
return Some(ty.clone());
|
|
||||||
}
|
|
||||||
if failed.contains(name) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if let Some(cycle_start) = stack.iter().position(|candidate| candidate == name) {
|
|
||||||
let cycle = stack[cycle_start..].to_vec();
|
|
||||||
let mut key = cycle.clone();
|
|
||||||
key.sort();
|
|
||||||
if reported_cycles.insert(key.join("|")) {
|
|
||||||
errors.push(type_alias_cycle(file, &cycle, aliases));
|
|
||||||
}
|
|
||||||
failed.extend(cycle);
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let alias = aliases.get(name).copied()?;
|
|
||||||
stack.push(name.to_string());
|
|
||||||
let target = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
&alias.target,
|
|
||||||
alias.target_span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
);
|
|
||||||
stack.pop();
|
|
||||||
|
|
||||||
if let Some(target) = target {
|
|
||||||
resolved.insert(name.to_string(), target.clone());
|
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
failed.insert(name.to_string());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_alias_target_type(
|
|
||||||
file: &str,
|
|
||||||
ty: &Type,
|
|
||||||
span: Span,
|
|
||||||
aliases: &HashMap<String, &TypeAliasDecl>,
|
|
||||||
structs: &HashSet<String>,
|
|
||||||
enums: &HashSet<String>,
|
|
||||||
resolved: &mut HashMap<String, Type>,
|
|
||||||
failed: &mut HashSet<String>,
|
|
||||||
stack: &mut Vec<String>,
|
|
||||||
reported_cycles: &mut BTreeSet<String>,
|
|
||||||
errors: &mut Vec<Diagnostic>,
|
|
||||||
) -> Option<Type> {
|
|
||||||
match ty {
|
|
||||||
Type::Named(name) if aliases.contains_key(name) => resolve_alias_target(
|
|
||||||
file,
|
|
||||||
name,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
),
|
|
||||||
Type::Named(name) if structs.contains(name) || enums.contains(name) => {
|
|
||||||
Some(Type::Named(name.clone()))
|
|
||||||
}
|
|
||||||
Type::Named(name) => {
|
|
||||||
if is_generic_type_parameter_name(name) {
|
|
||||||
errors.push(unsupported_generic_type_parameter(file, span, name));
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnknownTypeAliasTarget",
|
|
||||||
format!("type alias target `{}` is not a known concrete type", name),
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("built-in type, known struct, known enum, or known type alias")
|
|
||||||
.found(name.clone())
|
|
||||||
.hint("declare the target type or alias before checking this alias set"),
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Type::Array(inner, len) => {
|
|
||||||
let resolved_inner = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
inner,
|
|
||||||
span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
)?;
|
|
||||||
let target = Type::Array(Box::new(resolved_inner), *len);
|
|
||||||
if alias_target_type_supported(&target, structs, enums) {
|
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
errors.push(unsupported_type_alias_target(file, span, &target));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Vec(inner) => {
|
|
||||||
let resolved_inner = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
inner,
|
|
||||||
span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
)?;
|
|
||||||
let target = Type::Vec(Box::new(resolved_inner));
|
|
||||||
if alias_target_type_supported(&target, structs, enums) {
|
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
errors.push(unsupported_type_alias_target(file, span, &target));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Option(inner) => {
|
|
||||||
let resolved_inner = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
inner,
|
|
||||||
span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
)?;
|
|
||||||
let target = Type::Option(Box::new(resolved_inner));
|
|
||||||
if alias_target_type_supported(&target, structs, enums) {
|
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
errors.push(unsupported_type_alias_target(file, span, &target));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Result(ok, err) => {
|
|
||||||
let resolved_ok = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
ok,
|
|
||||||
span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
)?;
|
|
||||||
let resolved_err = resolve_alias_target_type(
|
|
||||||
file,
|
|
||||||
err,
|
|
||||||
span,
|
|
||||||
aliases,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
resolved,
|
|
||||||
failed,
|
|
||||||
stack,
|
|
||||||
reported_cycles,
|
|
||||||
errors,
|
|
||||||
)?;
|
|
||||||
let target = Type::Result(Box::new(resolved_ok), Box::new(resolved_err));
|
|
||||||
if alias_target_type_supported(&target, structs, enums) {
|
|
||||||
Some(target)
|
|
||||||
} else {
|
|
||||||
errors.push(unsupported_type_alias_target(file, span, &target));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if alias_target_type_supported(ty, structs, enums) {
|
|
||||||
Some(ty.clone())
|
|
||||||
} else {
|
|
||||||
errors.push(unsupported_type_alias_target(file, span, ty));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alias_target_type_supported(
|
|
||||||
ty: &Type,
|
|
||||||
structs: &HashSet<String>,
|
|
||||||
enums: &HashSet<String>,
|
|
||||||
) -> bool {
|
|
||||||
match ty {
|
|
||||||
Type::I32 | Type::I64 | Type::U32 | Type::U64 | Type::F64 | Type::Bool | Type::String => {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Type::Named(name) => structs.contains(name) || enums.contains(name),
|
|
||||||
Type::Array(inner, len) => {
|
|
||||||
*len > 0
|
|
||||||
&& (matches!(
|
|
||||||
&**inner,
|
|
||||||
Type::I32
|
|
||||||
| Type::I64
|
|
||||||
| Type::U32
|
|
||||||
| Type::U64
|
|
||||||
| Type::F64
|
|
||||||
| Type::Bool
|
|
||||||
| Type::String
|
|
||||||
) || matches!(&**inner, Type::Named(name) if structs.contains(name) || enums.contains(name)))
|
|
||||||
}
|
|
||||||
Type::Vec(inner) => matches!(
|
|
||||||
&**inner,
|
|
||||||
Type::I32 | Type::I64 | Type::F64 | Type::Bool | Type::String
|
|
||||||
),
|
|
||||||
Type::Option(inner) => option_payload_type_supported(inner),
|
|
||||||
Type::Result(ok, err) => result_payload_types_supported(ok, err),
|
|
||||||
Type::Unit | Type::Ptr(_) | Type::Slice(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsupported_type_alias_target(file: &str, span: Span, ty: &Type) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedTypeAliasTarget",
|
|
||||||
"type alias target type is not supported in the current beta",
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("i32, i64, u32, u64, f64, bool, string, known struct, known enum, supported array, supported option, supported result, or supported vec")
|
|
||||||
.found(ty.to_string())
|
|
||||||
.hint("aliases are transparent and may only target concrete types already supported in the target use positions")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn type_alias_cycle(
|
|
||||||
file: &str,
|
|
||||||
cycle: &[String],
|
|
||||||
aliases: &HashMap<String, &TypeAliasDecl>,
|
|
||||||
) -> Diagnostic {
|
|
||||||
let first = &cycle[0];
|
|
||||||
let first_alias = aliases[first];
|
|
||||||
let mut diag = Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasCycle",
|
|
||||||
format!("type alias cycle includes `{}`", first),
|
|
||||||
)
|
|
||||||
.with_span(first_alias.name_span)
|
|
||||||
.hint("type aliases must resolve to an existing non-alias concrete type");
|
|
||||||
|
|
||||||
for name in &cycle[1..] {
|
|
||||||
diag = diag.related(
|
|
||||||
format!("cycle also includes `{}`", name),
|
|
||||||
aliases[name].name_span,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
diag
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rewrite_program_alias_types(program: &mut Program, aliases: &HashMap<String, Type>) {
|
|
||||||
for enum_decl in &mut program.enums {
|
|
||||||
for variant in &mut enum_decl.variants {
|
|
||||||
if let Some(payload_ty) = &mut variant.payload_ty {
|
|
||||||
*payload_ty = resolve_use_type(payload_ty, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for struct_decl in &mut program.structs {
|
|
||||||
for field in &mut struct_decl.fields {
|
|
||||||
field.ty = resolve_use_type(&field.ty, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for import in &mut program.c_imports {
|
|
||||||
for param in &mut import.params {
|
|
||||||
param.ty = resolve_use_type(¶m.ty, aliases);
|
|
||||||
}
|
|
||||||
import.return_type = resolve_use_type(&import.return_type, aliases);
|
|
||||||
}
|
|
||||||
|
|
||||||
for function in &mut program.functions {
|
|
||||||
for param in &mut function.params {
|
|
||||||
param.ty = resolve_use_type(¶m.ty, aliases);
|
|
||||||
}
|
|
||||||
function.return_type = resolve_use_type(&function.return_type, aliases);
|
|
||||||
for expr in &mut function.body {
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for test in &mut program.tests {
|
|
||||||
for expr in &mut test.body {
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rewrite_expr_alias_types(expr: &mut Expr, aliases: &HashMap<String, Type>) {
|
|
||||||
match &mut expr.kind {
|
|
||||||
ExprKind::StructInit { fields, .. } => {
|
|
||||||
for field in fields {
|
|
||||||
rewrite_expr_alias_types(&mut field.expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::ArrayInit {
|
|
||||||
elem_ty, elements, ..
|
|
||||||
} => {
|
|
||||||
*elem_ty = resolve_use_type(elem_ty, aliases);
|
|
||||||
for element in elements {
|
|
||||||
rewrite_expr_alias_types(element, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::OptionSome {
|
|
||||||
payload_ty, value, ..
|
|
||||||
} => {
|
|
||||||
*payload_ty = resolve_use_type(payload_ty, aliases);
|
|
||||||
rewrite_expr_alias_types(value, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::OptionNone { payload_ty, .. } => {
|
|
||||||
*payload_ty = resolve_use_type(payload_ty, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::ResultOk {
|
|
||||||
ok_ty,
|
|
||||||
err_ty,
|
|
||||||
value,
|
|
||||||
..
|
|
||||||
}
|
|
||||||
| ExprKind::ResultErr {
|
|
||||||
ok_ty,
|
|
||||||
err_ty,
|
|
||||||
value,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
*ok_ty = resolve_use_type(ok_ty, aliases);
|
|
||||||
*err_ty = resolve_use_type(err_ty, aliases);
|
|
||||||
rewrite_expr_alias_types(value, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::OptionIsSome { value }
|
|
||||||
| ExprKind::OptionIsNone { value }
|
|
||||||
| ExprKind::OptionUnwrapSome { value }
|
|
||||||
| ExprKind::ResultIsOk { value, .. }
|
|
||||||
| ExprKind::ResultIsErr { value, .. }
|
|
||||||
| ExprKind::ResultUnwrapOk { value, .. }
|
|
||||||
| ExprKind::ResultUnwrapErr { value, .. } => rewrite_expr_alias_types(value, aliases),
|
|
||||||
ExprKind::EnumVariant { args, .. } | ExprKind::Call { args, .. } => {
|
|
||||||
for arg in args {
|
|
||||||
rewrite_expr_alias_types(arg, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::FieldAccess { value, .. } => rewrite_expr_alias_types(value, aliases),
|
|
||||||
ExprKind::Index { array, index } => {
|
|
||||||
rewrite_expr_alias_types(array, aliases);
|
|
||||||
rewrite_expr_alias_types(index, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::Local { ty, expr, .. } => {
|
|
||||||
*ty = resolve_use_type(ty, aliases);
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::Set { expr, .. } => rewrite_expr_alias_types(expr, aliases),
|
|
||||||
ExprKind::Binary { left, right, .. } => {
|
|
||||||
rewrite_expr_alias_types(left, aliases);
|
|
||||||
rewrite_expr_alias_types(right, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::If {
|
|
||||||
condition,
|
|
||||||
then_expr,
|
|
||||||
else_expr,
|
|
||||||
} => {
|
|
||||||
rewrite_expr_alias_types(condition, aliases);
|
|
||||||
rewrite_expr_alias_types(then_expr, aliases);
|
|
||||||
rewrite_expr_alias_types(else_expr, aliases);
|
|
||||||
}
|
|
||||||
ExprKind::Match { subject, arms } => {
|
|
||||||
rewrite_expr_alias_types(subject, aliases);
|
|
||||||
for arm in arms {
|
|
||||||
for expr in &mut arm.body {
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::While { condition, body } => {
|
|
||||||
rewrite_expr_alias_types(condition, aliases);
|
|
||||||
for expr in body {
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::Unsafe { body } => {
|
|
||||||
for expr in body {
|
|
||||||
rewrite_expr_alias_types(expr, aliases);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ExprKind::Int(_)
|
|
||||||
| ExprKind::Int64(_)
|
|
||||||
| ExprKind::UInt32(_)
|
|
||||||
| ExprKind::UInt64(_)
|
|
||||||
| ExprKind::Float(_)
|
|
||||||
| ExprKind::Bool(_)
|
|
||||||
| ExprKind::String(_)
|
|
||||||
| ExprKind::Var(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_use_type(ty: &Type, aliases: &HashMap<String, Type>) -> Type {
|
|
||||||
match ty {
|
|
||||||
Type::Named(name) => aliases
|
|
||||||
.get(name)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| Type::Named(name.clone())),
|
|
||||||
Type::Ptr(inner) => Type::Ptr(Box::new(resolve_use_type(inner, aliases))),
|
|
||||||
Type::Array(inner, len) => Type::Array(Box::new(resolve_use_type(inner, aliases)), *len),
|
|
||||||
Type::Vec(inner) => Type::Vec(Box::new(resolve_use_type(inner, aliases))),
|
|
||||||
Type::Slice(inner) => Type::Slice(Box::new(resolve_use_type(inner, aliases))),
|
|
||||||
Type::Option(inner) => Type::Option(Box::new(resolve_use_type(inner, aliases))),
|
|
||||||
Type::Result(ok, err) => Type::Result(
|
|
||||||
Box::new(resolve_use_type(ok, aliases)),
|
|
||||||
Box::new(resolve_use_type(err, aliases)),
|
|
||||||
),
|
|
||||||
_ => ty.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_reserved_type_name(name: &str) -> bool {
|
|
||||||
matches!(
|
|
||||||
name,
|
|
||||||
"i32"
|
|
||||||
| "i64"
|
|
||||||
| "u32"
|
|
||||||
| "u64"
|
|
||||||
| "f64"
|
|
||||||
| "bool"
|
|
||||||
| "unit"
|
|
||||||
| "string"
|
|
||||||
| "ptr"
|
|
||||||
| "slice"
|
|
||||||
| "option"
|
|
||||||
| "result"
|
|
||||||
| "array"
|
|
||||||
| "vec"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_struct_decl(
|
fn check_struct_decl(
|
||||||
file: &str,
|
file: &str,
|
||||||
struct_decl: &StructDecl,
|
struct_decl: &StructDecl,
|
||||||
@ -2659,10 +2008,6 @@ fn check_local_type(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn unknown_named_type(file: &str, span: Span, name: &str, context: &str) -> Diagnostic {
|
fn unknown_named_type(file: &str, span: Span, name: &str, context: &str) -> Diagnostic {
|
||||||
if is_generic_type_parameter_name(name) {
|
|
||||||
return unsupported_generic_type_parameter(file, span, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
"UnknownStructType",
|
"UnknownStructType",
|
||||||
@ -4345,18 +3690,10 @@ fn validate_match_arm_set(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !missing.is_empty() {
|
if !missing.is_empty() {
|
||||||
let found = required
|
|
||||||
.iter()
|
|
||||||
.filter(|pattern| seen.contains_key(*pattern))
|
|
||||||
.map(lower::match_pattern_name)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
return Err(Diagnostic::new(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
"NonExhaustiveMatch",
|
"NonExhaustiveMatch",
|
||||||
format!(
|
format!("match is missing `{}` arm(s)", missing.join("`, `")),
|
||||||
"match is missing required arm(s): `{}`",
|
|
||||||
missing.join("`, `")
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_span(span)
|
.with_span(span)
|
||||||
.expected(
|
.expected(
|
||||||
@ -4366,11 +3703,12 @@ fn validate_match_arm_set(
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" and "),
|
.join(" and "),
|
||||||
)
|
)
|
||||||
.found(if found.is_empty() {
|
.found(
|
||||||
"no valid arms".to_string()
|
seen.keys()
|
||||||
} else {
|
.map(lower::match_pattern_name)
|
||||||
found.join(", ")
|
.collect::<Vec<_>>()
|
||||||
}));
|
.join(", "),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -5513,12 +4851,6 @@ fn check_vector_type(file: &str, ty: &Type, span: Span) -> Result<(), Diagnostic
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Type::Named(name) = &**inner {
|
|
||||||
if is_generic_type_parameter_name(name) {
|
|
||||||
return Err(unsupported_generic_type_parameter(file, span, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if **inner != Type::I32
|
if **inner != Type::I32
|
||||||
&& **inner != Type::I64
|
&& **inner != Type::I64
|
||||||
&& **inner != Type::F64
|
&& **inner != Type::F64
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
use crate::token::Span;
|
use crate::token::Span;
|
||||||
|
|
||||||
pub const DIAGNOSTIC_SCHEMA_NAME: &str = "slovo.diagnostic";
|
|
||||||
pub const DIAGNOSTIC_SCHEMA_VERSION: u32 = 1;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
Error,
|
Error,
|
||||||
@ -187,8 +184,8 @@ impl Diagnostic {
|
|||||||
) -> String {
|
) -> String {
|
||||||
let source = source_for(&self.file).unwrap_or("");
|
let source = source_for(&self.file).unwrap_or("");
|
||||||
let mut parts = vec![
|
let mut parts = vec![
|
||||||
format!(" (schema {})", DIAGNOSTIC_SCHEMA_NAME),
|
" (schema slovo.diagnostic)".to_string(),
|
||||||
format!(" (version {})", DIAGNOSTIC_SCHEMA_VERSION),
|
" (version 1)".to_string(),
|
||||||
format!(" (severity {})", self.severity.as_str()),
|
format!(" (severity {})", self.severity.as_str()),
|
||||||
format!(" (code {})", self.code),
|
format!(" (code {})", self.code),
|
||||||
format!(" (message {})", render_string(&self.message)),
|
format!(" (message {})", render_string(&self.message)),
|
||||||
@ -313,8 +310,8 @@ fn render_json_diagnostic<'a>(
|
|||||||
related: impl Iterator<Item = JsonRelated<'a>>,
|
related: impl Iterator<Item = JsonRelated<'a>>,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut fields = vec![
|
let mut fields = vec![
|
||||||
format!("\"schema\":{}", render_json_string(DIAGNOSTIC_SCHEMA_NAME)),
|
"\"schema\":\"slovo.diagnostic\"".to_string(),
|
||||||
format!("\"version\":{}", DIAGNOSTIC_SCHEMA_VERSION),
|
"\"version\":1".to_string(),
|
||||||
format!("\"severity\":{}", render_json_string(severity)),
|
format!("\"severity\":{}", render_json_string(severity)),
|
||||||
format!("\"code\":{}", render_json_string(code)),
|
format!("\"code\":{}", render_json_string(code)),
|
||||||
format!("\"message\":{}", render_json_string(message)),
|
format!("\"message\":{}", render_json_string(message)),
|
||||||
|
|||||||
@ -1,22 +1,17 @@
|
|||||||
use std::{
|
use std::{fs, path::Path};
|
||||||
collections::{BTreeMap, BTreeSet},
|
|
||||||
fs,
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::Program,
|
ast::Program,
|
||||||
diag::Diagnostic,
|
diag::Diagnostic,
|
||||||
lexer, lower,
|
lexer, lower,
|
||||||
project::{self, ProjectArtifact, SourceFile, ToolFailure, WorkspaceArtifact},
|
project::{self, SourceFile, ToolFailure},
|
||||||
sexpr::{Atom, SExpr, SExprKind},
|
sexpr::{Atom, SExpr, SExprKind},
|
||||||
types::Type,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> {
|
pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> {
|
||||||
let docs = if project::is_project_input(input) {
|
let docs = if project::is_project_input(input) {
|
||||||
let loaded = project::load_project_sources_for_tools(input)?;
|
let loaded = project::load_project_sources_for_tools(input)?;
|
||||||
render_project(&loaded.artifact, &loaded.sources)?
|
render_project(&loaded.artifact.project_name, &loaded.sources)?
|
||||||
} else {
|
} else {
|
||||||
let source = fs::read_to_string(input).map_err(|err| ToolFailure {
|
let source = fs::read_to_string(input).map_err(|err| ToolFailure {
|
||||||
diagnostics: vec![Diagnostic::new(
|
diagnostics: vec![Diagnostic::new(
|
||||||
@ -57,60 +52,18 @@ pub fn generate(input: &str, output_dir: &str) -> Result<(), ToolFailure> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_project(
|
fn render_project(title: &str, sources: &[SourceFile]) -> Result<String, ToolFailure> {
|
||||||
artifact: &ProjectArtifact,
|
|
||||||
sources: &[SourceFile],
|
|
||||||
) -> Result<String, ToolFailure> {
|
|
||||||
let modules = sources
|
|
||||||
.iter()
|
|
||||||
.map(document_source)
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str("# Project ");
|
out.push_str("# Project ");
|
||||||
out.push_str(&artifact.project_name);
|
out.push_str(title);
|
||||||
out.push_str("\n\n");
|
out.push_str("\n\n");
|
||||||
if let Some(workspace) = &artifact.workspace {
|
for source in sources {
|
||||||
render_workspace(&mut out, workspace);
|
let module = document_source(source)?;
|
||||||
render_workspace_package_public_api(&mut out, workspace, &modules);
|
render_module(&mut out, &module);
|
||||||
} else {
|
|
||||||
render_project_package_public_api(&mut out, artifact, &modules);
|
|
||||||
}
|
|
||||||
for module in &modules {
|
|
||||||
render_module(&mut out, module);
|
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_workspace(out: &mut String, workspace: &WorkspaceArtifact) {
|
|
||||||
out.push_str("## Workspace\n\n");
|
|
||||||
render_list(out, "Members", &workspace.members);
|
|
||||||
|
|
||||||
let packages = workspace
|
|
||||||
.packages
|
|
||||||
.iter()
|
|
||||||
.map(|package| {
|
|
||||||
format!(
|
|
||||||
"{} {} (entry {})",
|
|
||||||
package.name, package.version, package.entry
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
render_list(out, "Packages", &packages);
|
|
||||||
|
|
||||||
let dependencies = workspace
|
|
||||||
.dependencies
|
|
||||||
.iter()
|
|
||||||
.map(|dependency| {
|
|
||||||
format!(
|
|
||||||
"{} -> {} ({}, {})",
|
|
||||||
dependency.from, dependency.to, dependency.kind, dependency.path
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
render_list(out, "Package Dependencies", &dependencies);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_file(title: &str, sources: &[SourceFile]) -> Result<String, ToolFailure> {
|
fn render_file(title: &str, sources: &[SourceFile]) -> Result<String, ToolFailure> {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str("# File ");
|
out.push_str("# File ");
|
||||||
@ -132,58 +85,27 @@ fn document_source(source: &SourceFile) -> Result<DocModule, ToolFailure> {
|
|||||||
sources: vec![source.clone()],
|
sources: vec![source.clone()],
|
||||||
artifact: None,
|
artifact: None,
|
||||||
})?;
|
})?;
|
||||||
let lowerable_forms = forms
|
let program = lower::lower_program(&source.path, &forms).ok();
|
||||||
.iter()
|
|
||||||
.filter(|form| !matches!(list_head(form), Some("import")))
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let program = lower::lower_program(&source.path, &lowerable_forms).ok();
|
|
||||||
Ok(module_from_forms(&source.path, &forms, program.as_ref()))
|
Ok(module_from_forms(&source.path, &forms, program.as_ref()))
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DocModule {
|
struct DocModule {
|
||||||
path: String,
|
|
||||||
title: String,
|
title: String,
|
||||||
imports: Vec<String>,
|
imports: Vec<String>,
|
||||||
exports: Vec<String>,
|
exports: Vec<String>,
|
||||||
structs: Vec<String>,
|
structs: Vec<String>,
|
||||||
functions: Vec<String>,
|
functions: Vec<String>,
|
||||||
tests: Vec<String>,
|
tests: Vec<String>,
|
||||||
public_api: PublicApi,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct PublicApi {
|
|
||||||
functions: Vec<DocFunction>,
|
|
||||||
structs: Vec<DocStruct>,
|
|
||||||
enums: Vec<DocEnum>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DocFunction {
|
|
||||||
name: String,
|
|
||||||
signature: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DocStruct {
|
|
||||||
name: String,
|
|
||||||
fields: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DocEnum {
|
|
||||||
name: String,
|
|
||||||
variants: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) -> DocModule {
|
fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) -> DocModule {
|
||||||
let mut module = DocModule {
|
let mut module = DocModule {
|
||||||
path: file.to_string(),
|
|
||||||
title: file.to_string(),
|
title: file.to_string(),
|
||||||
imports: Vec::new(),
|
imports: Vec::new(),
|
||||||
exports: Vec::new(),
|
exports: Vec::new(),
|
||||||
structs: Vec::new(),
|
structs: Vec::new(),
|
||||||
functions: Vec::new(),
|
functions: Vec::new(),
|
||||||
tests: Vec::new(),
|
tests: Vec::new(),
|
||||||
public_api: PublicApi::default(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for form in forms {
|
for form in forms {
|
||||||
@ -248,7 +170,6 @@ fn module_from_forms(file: &str, forms: &[SExpr], program: Option<&Program>) ->
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
module.tests = program.tests.iter().map(|test| test.name.clone()).collect();
|
module.tests = program.tests.iter().map(|test| test.name.clone()).collect();
|
||||||
module.public_api = public_api_from_program(program, &module.exports);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.imports.sort();
|
module.imports.sort();
|
||||||
@ -265,120 +186,11 @@ fn render_module(out: &mut String, module: &DocModule) {
|
|||||||
out.push_str("\n\n");
|
out.push_str("\n\n");
|
||||||
render_list(out, "Imports", &module.imports);
|
render_list(out, "Imports", &module.imports);
|
||||||
render_list(out, "Exports", &module.exports);
|
render_list(out, "Exports", &module.exports);
|
||||||
render_public_api_section(out, "###", &module.public_api);
|
|
||||||
render_list(out, "Structs", &module.structs);
|
render_list(out, "Structs", &module.structs);
|
||||||
render_list(out, "Functions", &module.functions);
|
render_list(out, "Functions", &module.functions);
|
||||||
render_list(out, "Tests", &module.tests);
|
render_list(out, "Tests", &module.tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_project_package_public_api(
|
|
||||||
out: &mut String,
|
|
||||||
artifact: &ProjectArtifact,
|
|
||||||
modules: &[DocModule],
|
|
||||||
) {
|
|
||||||
let local_root = Path::new(&artifact.project_root).join(&artifact.source_root);
|
|
||||||
let local_modules = modules
|
|
||||||
.iter()
|
|
||||||
.filter(|module| Path::new(&module.path).starts_with(&local_root))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
render_package_public_api(out, &artifact.project_name, &local_modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_workspace_package_public_api(
|
|
||||||
out: &mut String,
|
|
||||||
workspace: &WorkspaceArtifact,
|
|
||||||
modules: &[DocModule],
|
|
||||||
) {
|
|
||||||
let by_path = modules
|
|
||||||
.iter()
|
|
||||||
.map(|module| (module.path.as_str(), module))
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
for package in &workspace.packages {
|
|
||||||
let local_root = Path::new(&package.root).join(&package.source_root);
|
|
||||||
let package_modules = package
|
|
||||||
.modules
|
|
||||||
.iter()
|
|
||||||
.filter(|module| Path::new(&module.path).starts_with(&local_root))
|
|
||||||
.filter_map(|module| by_path.get(module.path.as_str()).copied())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
render_package_public_api(
|
|
||||||
out,
|
|
||||||
&format!("{} {}", package.name, package.version),
|
|
||||||
&package_modules,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_package_public_api(out: &mut String, package_name: &str, modules: &[&DocModule]) {
|
|
||||||
out.push_str("## Package API ");
|
|
||||||
out.push_str(package_name);
|
|
||||||
out.push_str("\n\n");
|
|
||||||
|
|
||||||
let public_modules = modules
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|module| !module.public_api.is_empty())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
if public_modules.is_empty() {
|
|
||||||
out.push_str("None.\n\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for module in public_modules {
|
|
||||||
out.push_str("### Module ");
|
|
||||||
out.push_str(&module.title);
|
|
||||||
out.push_str("\n\n");
|
|
||||||
render_public_api_body(out, "####", &module.public_api);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_public_api_section(out: &mut String, heading: &str, public_api: &PublicApi) {
|
|
||||||
out.push_str(heading);
|
|
||||||
out.push_str(" Public API\n\n");
|
|
||||||
render_public_api_body(out, "####", public_api);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_public_api_body(out: &mut String, heading: &str, public_api: &PublicApi) {
|
|
||||||
if public_api.is_empty() {
|
|
||||||
out.push_str("None.\n\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !public_api.functions.is_empty() {
|
|
||||||
out.push_str(heading);
|
|
||||||
out.push_str(" Functions\n");
|
|
||||||
for function in &public_api.functions {
|
|
||||||
render_code_bullet(out, &function.signature);
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if !public_api.structs.is_empty() {
|
|
||||||
out.push_str(heading);
|
|
||||||
out.push_str(" Structs\n");
|
|
||||||
for struct_decl in &public_api.structs {
|
|
||||||
render_code_bullet(out, &format!("struct {}", struct_decl.name));
|
|
||||||
for field in &struct_decl.fields {
|
|
||||||
render_indented_code_bullet(out, field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if !public_api.enums.is_empty() {
|
|
||||||
out.push_str(heading);
|
|
||||||
out.push_str(" Enums\n");
|
|
||||||
for enum_decl in &public_api.enums {
|
|
||||||
render_code_bullet(out, &format!("enum {}", enum_decl.name));
|
|
||||||
for variant in &enum_decl.variants {
|
|
||||||
render_indented_code_bullet(out, variant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_list(out: &mut String, title: &str, values: &[String]) {
|
fn render_list(out: &mut String, title: &str, values: &[String]) {
|
||||||
out.push_str("### ");
|
out.push_str("### ");
|
||||||
out.push_str(title);
|
out.push_str(title);
|
||||||
@ -395,182 +207,6 @@ fn render_list(out: &mut String, title: &str, values: &[String]) {
|
|||||||
out.push('\n');
|
out.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_head(expr: &SExpr) -> Option<&str> {
|
|
||||||
list(expr).and_then(|items| items.first()).and_then(ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_code_bullet(out: &mut String, value: &str) {
|
|
||||||
out.push_str("- `");
|
|
||||||
out.push_str(&value.replace('`', "\\`"));
|
|
||||||
out.push_str("`\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_indented_code_bullet(out: &mut String, value: &str) {
|
|
||||||
out.push_str(" - `");
|
|
||||||
out.push_str(&value.replace('`', "\\`"));
|
|
||||||
out.push_str("`\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PublicApi {
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.functions.is_empty() && self.structs.is_empty() && self.enums.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_api_from_program(program: &Program, exports: &[String]) -> PublicApi {
|
|
||||||
let export_names = exports.iter().cloned().collect::<BTreeSet<_>>();
|
|
||||||
let aliases = alias_targets(program);
|
|
||||||
|
|
||||||
let mut functions = program
|
|
||||||
.functions
|
|
||||||
.iter()
|
|
||||||
.filter(|function| export_names.contains(&function.name))
|
|
||||||
.map(|function| DocFunction {
|
|
||||||
name: function.name.clone(),
|
|
||||||
signature: function_signature(
|
|
||||||
&function.name,
|
|
||||||
function
|
|
||||||
.params
|
|
||||||
.iter()
|
|
||||||
.map(|param| (param.name.as_str(), ¶m.ty)),
|
|
||||||
&function.return_type,
|
|
||||||
&aliases,
|
|
||||||
),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
functions.sort_by(|left, right| left.name.cmp(&right.name));
|
|
||||||
|
|
||||||
let mut structs = program
|
|
||||||
.structs
|
|
||||||
.iter()
|
|
||||||
.filter(|struct_decl| export_names.contains(&struct_decl.name))
|
|
||||||
.map(|struct_decl| DocStruct {
|
|
||||||
name: struct_decl.name.clone(),
|
|
||||||
fields: struct_decl
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| {
|
|
||||||
format!(
|
|
||||||
"{}: {}",
|
|
||||||
field.name,
|
|
||||||
display_public_type(&field.ty, &aliases)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
structs.sort_by(|left, right| left.name.cmp(&right.name));
|
|
||||||
|
|
||||||
let mut enums = program
|
|
||||||
.enums
|
|
||||||
.iter()
|
|
||||||
.filter(|enum_decl| export_names.contains(&enum_decl.name))
|
|
||||||
.map(|enum_decl| DocEnum {
|
|
||||||
name: enum_decl.name.clone(),
|
|
||||||
variants: enum_decl
|
|
||||||
.variants
|
|
||||||
.iter()
|
|
||||||
.map(|variant| match &variant.payload_ty {
|
|
||||||
Some(payload_ty) => {
|
|
||||||
format!(
|
|
||||||
"{}({})",
|
|
||||||
variant.name,
|
|
||||||
display_public_type(payload_ty, &aliases)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
None => variant.name.clone(),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
enums.sort_by(|left, right| left.name.cmp(&right.name));
|
|
||||||
|
|
||||||
PublicApi {
|
|
||||||
functions,
|
|
||||||
structs,
|
|
||||||
enums,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn function_signature<'a>(
|
|
||||||
name: &str,
|
|
||||||
params: impl Iterator<Item = (&'a str, &'a Type)>,
|
|
||||||
return_type: &Type,
|
|
||||||
aliases: &BTreeMap<String, Type>,
|
|
||||||
) -> String {
|
|
||||||
let params = params
|
|
||||||
.map(|(name, ty)| format!("{}: {}", name, display_public_type(ty, aliases)))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
format!(
|
|
||||||
"fn {}({}) -> {}",
|
|
||||||
name,
|
|
||||||
params,
|
|
||||||
display_public_type(return_type, aliases)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alias_targets(program: &Program) -> BTreeMap<String, Type> {
|
|
||||||
let raw = program
|
|
||||||
.type_aliases
|
|
||||||
.iter()
|
|
||||||
.map(|alias| (alias.name.clone(), alias.target.clone()))
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
raw.keys()
|
|
||||||
.map(|name| {
|
|
||||||
let mut visiting = BTreeSet::new();
|
|
||||||
(
|
|
||||||
name.clone(),
|
|
||||||
resolve_alias_type(&Type::Named(name.clone()), &raw, &mut visiting),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_public_type(ty: &Type, aliases: &BTreeMap<String, Type>) -> String {
|
|
||||||
let mut visiting = BTreeSet::new();
|
|
||||||
resolve_alias_type(ty, aliases, &mut visiting).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_alias_type(
|
|
||||||
ty: &Type,
|
|
||||||
aliases: &BTreeMap<String, Type>,
|
|
||||||
visiting: &mut BTreeSet<String>,
|
|
||||||
) -> Type {
|
|
||||||
match ty {
|
|
||||||
Type::Named(name) => {
|
|
||||||
let Some(target) = aliases.get(name) else {
|
|
||||||
return ty.clone();
|
|
||||||
};
|
|
||||||
if !visiting.insert(name.clone()) {
|
|
||||||
return ty.clone();
|
|
||||||
}
|
|
||||||
let resolved = resolve_alias_type(target, aliases, visiting);
|
|
||||||
visiting.remove(name);
|
|
||||||
resolved
|
|
||||||
}
|
|
||||||
Type::Ptr(inner) => Type::Ptr(Box::new(resolve_alias_type(inner, aliases, visiting))),
|
|
||||||
Type::Array(inner, len) => {
|
|
||||||
Type::Array(Box::new(resolve_alias_type(inner, aliases, visiting)), *len)
|
|
||||||
}
|
|
||||||
Type::Vec(inner) => Type::Vec(Box::new(resolve_alias_type(inner, aliases, visiting))),
|
|
||||||
Type::Slice(inner) => Type::Slice(Box::new(resolve_alias_type(inner, aliases, visiting))),
|
|
||||||
Type::Option(inner) => Type::Option(Box::new(resolve_alias_type(inner, aliases, visiting))),
|
|
||||||
Type::Result(ok, err) => Type::Result(
|
|
||||||
Box::new(resolve_alias_type(ok, aliases, visiting)),
|
|
||||||
Box::new(resolve_alias_type(err, aliases, visiting)),
|
|
||||||
),
|
|
||||||
Type::I32
|
|
||||||
| Type::I64
|
|
||||||
| Type::U32
|
|
||||||
| Type::U64
|
|
||||||
| Type::F64
|
|
||||||
| Type::Bool
|
|
||||||
| Type::Unit
|
|
||||||
| Type::String => ty.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list(expr: &SExpr) -> Option<&[SExpr]> {
|
fn list(expr: &SExpr) -> Option<&[SExpr]> {
|
||||||
match &expr.kind {
|
match &expr.kind {
|
||||||
SExprKind::List(items) => Some(items),
|
SExprKind::List(items) => Some(items),
|
||||||
|
|||||||
@ -52,16 +52,6 @@ pub fn run_tests(
|
|||||||
test_runner::run(file, &checked, filter)
|
test_runner::run(file, &checked, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_tests(
|
|
||||||
file: &str,
|
|
||||||
source: &str,
|
|
||||||
filter: Option<&str>,
|
|
||||||
) -> Result<test_runner::TestRunSuccess, test_runner::TestRunFailure> {
|
|
||||||
let checked =
|
|
||||||
checked_program(file, source).map_err(test_runner::TestRunFailure::before_execution)?;
|
|
||||||
Ok(test_runner::list(&checked, filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checked_program(file: &str, source: &str) -> Result<check::CheckedProgram, Vec<Diagnostic>> {
|
fn checked_program(file: &str, source: &str) -> Result<check::CheckedProgram, Vec<Diagnostic>> {
|
||||||
let tokens = lexer::lex(file, source)?;
|
let tokens = lexer::lex(file, source)?;
|
||||||
let forms = sexpr::parse(file, &tokens)?;
|
let forms = sexpr::parse(file, &tokens)?;
|
||||||
|
|||||||
@ -2,11 +2,6 @@ use std::collections::{HashMap, HashSet};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diag::Diagnostic,
|
diag::Diagnostic,
|
||||||
reserved::{
|
|
||||||
is_unsupported_generic_standard_library_call, unsupported_generic_function,
|
|
||||||
unsupported_generic_standard_library_call, unsupported_generic_type_alias,
|
|
||||||
unsupported_reserved_type_diagnostic,
|
|
||||||
},
|
|
||||||
sexpr::{Atom, SExpr, SExprKind},
|
sexpr::{Atom, SExpr, SExprKind},
|
||||||
std_runtime,
|
std_runtime,
|
||||||
token::Span,
|
token::Span,
|
||||||
@ -24,7 +19,6 @@ pub fn format(file: &str, source: &str, forms: &[SExpr]) -> Result<String, Vec<D
|
|||||||
function_names: collect_function_names(forms),
|
function_names: collect_function_names(forms),
|
||||||
struct_names: collect_struct_names(forms),
|
struct_names: collect_struct_names(forms),
|
||||||
enum_names: collect_enum_names(forms),
|
enum_names: collect_enum_names(forms),
|
||||||
type_alias_names: collect_type_alias_names(forms),
|
|
||||||
errors: comment_errors
|
errors: comment_errors
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|comment| unsupported_non_full_line_comment(file, comment.span))
|
.map(|comment| unsupported_non_full_line_comment(file, comment.span))
|
||||||
@ -48,7 +42,6 @@ struct Formatter<'a> {
|
|||||||
function_names: HashSet<String>,
|
function_names: HashSet<String>,
|
||||||
struct_names: HashSet<String>,
|
struct_names: HashSet<String>,
|
||||||
enum_names: HashSet<String>,
|
enum_names: HashSet<String>,
|
||||||
type_alias_names: HashSet<String>,
|
|
||||||
errors: Vec<Diagnostic>,
|
errors: Vec<Diagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +64,6 @@ impl Formatter<'_> {
|
|||||||
Some("module") => self.write_module(form),
|
Some("module") => self.write_module(form),
|
||||||
Some("import") => self.write_import(form),
|
Some("import") => self.write_import(form),
|
||||||
Some("import_c") => self.write_c_import(form),
|
Some("import_c") => self.write_c_import(form),
|
||||||
Some("type") => self.write_type_alias(form),
|
|
||||||
Some("enum") => self.write_enum(form),
|
Some("enum") => self.write_enum(form),
|
||||||
Some("struct") => self.write_struct(form),
|
Some("struct") => self.write_struct(form),
|
||||||
Some("fn") => self.write_function(form),
|
Some("fn") => self.write_function(form),
|
||||||
@ -259,67 +251,6 @@ impl Formatter<'_> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_type_alias(&mut self, form: &SExpr) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
self.errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
self.file,
|
|
||||||
"MalformedTypeAlias",
|
|
||||||
"type alias form must be a list",
|
|
||||||
)
|
|
||||||
.with_span(form.span),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(items.get(2).and_then(list_head), Some("type_params")) {
|
|
||||||
self.errors.push(unsupported_generic_type_alias(
|
|
||||||
self.file,
|
|
||||||
items.get(2).map_or(form.span, |item| item.span),
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if items.len() != 3 {
|
|
||||||
self.errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
self.file,
|
|
||||||
"MalformedTypeAlias",
|
|
||||||
"type alias form must be `(type Alias TargetType)`",
|
|
||||||
)
|
|
||||||
.with_span(form.span),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(name) = expect_ident(&items[1]) else {
|
|
||||||
self.errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
self.file,
|
|
||||||
"InvalidTypeAliasName",
|
|
||||||
"type alias name must be an identifier",
|
|
||||||
)
|
|
||||||
.with_span(items[1].span),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(target) = self.render_alias_target_type(&items[2]) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.reject_comments_before(
|
|
||||||
form.span.end,
|
|
||||||
"formatter does not support comments inside type alias forms",
|
|
||||||
);
|
|
||||||
|
|
||||||
self.output.push_str("(type ");
|
|
||||||
self.output.push_str(name);
|
|
||||||
self.output.push(' ');
|
|
||||||
self.output.push_str(&target);
|
|
||||||
self.output.push(')');
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_struct(&mut self, form: &SExpr) {
|
fn write_struct(&mut self, form: &SExpr) {
|
||||||
let Some(items) = expect_list(form) else {
|
let Some(items) = expect_list(form) else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
@ -581,17 +512,17 @@ impl Formatter<'_> {
|
|||||||
} else if is_ident(&variant_items[1], "string") {
|
} else if is_ident(&variant_items[1], "string") {
|
||||||
"string".to_string()
|
"string".to_string()
|
||||||
} else if let Some(name) = expect_ident(&variant_items[1]) {
|
} else if let Some(name) = expect_ident(&variant_items[1]) {
|
||||||
if self.struct_names.contains(name) || self.type_alias_names.contains(name) {
|
if self.struct_names.contains(name) {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
"UnsupportedFormatterForm",
|
"UnsupportedFormatterForm",
|
||||||
"formatter supports only unary direct i32, i64, f64, bool, string, known type aliases, and known non-recursive struct enum payload variants",
|
"formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants",
|
||||||
)
|
)
|
||||||
.with_span(variant_items[1].span)
|
.with_span(variant_items[1].span)
|
||||||
.expected("i32, i64, f64, bool, string, known type alias, or known non-recursive struct type"),
|
.expected("i32, i64, f64, bool, string, or known non-recursive struct type"),
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -600,10 +531,10 @@ impl Formatter<'_> {
|
|||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
"UnsupportedFormatterForm",
|
"UnsupportedFormatterForm",
|
||||||
"formatter supports only unary direct i32, i64, f64, bool, string, known type aliases, and known non-recursive struct enum payload variants",
|
"formatter supports only unary direct i32, i64, f64, bool, string, and known non-recursive struct enum payload variants",
|
||||||
)
|
)
|
||||||
.with_span(variant_items[1].span)
|
.with_span(variant_items[1].span)
|
||||||
.expected("i32, i64, f64, bool, string, known type alias, or known non-recursive struct type"),
|
.expected("i32, i64, f64, bool, string, or known non-recursive struct type"),
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@ -670,12 +601,6 @@ impl Formatter<'_> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(items.get(2).and_then(list_head), Some("type_params")) {
|
|
||||||
self.errors
|
|
||||||
.push(unsupported_generic_function(self.file, items[2].span));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(name) = expect_ident(&items[1]) else {
|
let Some(name) = expect_ident(&items[1]) else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -922,13 +847,8 @@ impl Formatter<'_> {
|
|||||||
} else if is_ident(&pair[1], "string") {
|
} else if is_ident(&pair[1], "string") {
|
||||||
"string".to_string()
|
"string".to_string()
|
||||||
} else if let Some(name) = expect_ident(&pair[1]) {
|
} else if let Some(name) = expect_ident(&pair[1]) {
|
||||||
if self.struct_names.contains(name)
|
if self.struct_names.contains(name) || self.enum_names.contains(name) {
|
||||||
|| self.enum_names.contains(name)
|
|
||||||
|| self.type_alias_names.contains(name)
|
|
||||||
{
|
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else if self.push_reserved_type_error(&pair[1]) {
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -941,20 +861,14 @@ impl Formatter<'_> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
} else if let Some(items) = expect_list(&pair[1]) {
|
} else if let Some(items) = expect_list(&pair[1]) {
|
||||||
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
|
if let Some(text) = render_option_result_type(items) {
|
||||||
text
|
text
|
||||||
} else if let Some(text) = render_supported_array_type(
|
} else if let Some(text) =
|
||||||
items,
|
render_supported_array_type(items, &self.struct_names, &self.enum_names)
|
||||||
&self.struct_names,
|
|
||||||
&self.enum_names,
|
|
||||||
&self.type_alias_names,
|
|
||||||
) {
|
|
||||||
text
|
|
||||||
} else if let Some(text) = render_supported_vec_type(items, &self.type_alias_names)
|
|
||||||
{
|
{
|
||||||
text
|
text
|
||||||
} else if self.push_reserved_type_error(&pair[1]) {
|
} else if let Some(text) = render_supported_vec_type(items) {
|
||||||
continue;
|
text
|
||||||
} else {
|
} else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -1032,18 +946,11 @@ impl Formatter<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = expect_ident(form) {
|
if let Some(name) = expect_ident(form) {
|
||||||
if self.struct_names.contains(name)
|
if self.struct_names.contains(name) || self.enum_names.contains(name) {
|
||||||
|| self.enum_names.contains(name)
|
|
||||||
|| self.type_alias_names.contains(name)
|
|
||||||
{
|
|
||||||
return Some(name.to_string());
|
return Some(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(items) = expect_list(form) else {
|
let Some(items) = expect_list(form) else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -1056,27 +963,19 @@ impl Formatter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
|
if let Some(text) = render_option_result_type(items) {
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = render_supported_array_type(
|
if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names)
|
||||||
items,
|
{
|
||||||
&self.struct_names,
|
|
||||||
&self.enum_names,
|
|
||||||
&self.type_alias_names,
|
|
||||||
) {
|
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
|
if let Some(text) = render_supported_vec_type(items) {
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
@ -1088,66 +987,6 @@ impl Formatter<'_> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_alias_target_type(&mut self, form: &SExpr) -> Option<String> {
|
|
||||||
if let Some(text) = render_direct_type_atom(
|
|
||||||
form,
|
|
||||||
&self.struct_names,
|
|
||||||
&self.enum_names,
|
|
||||||
&self.type_alias_names,
|
|
||||||
) {
|
|
||||||
return Some(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
self.errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
self.file,
|
|
||||||
"InvalidTypeAliasTarget",
|
|
||||||
"type alias target type is invalid",
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("concrete type"),
|
|
||||||
);
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
|
|
||||||
return Some(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text) = render_supported_array_type(
|
|
||||||
items,
|
|
||||||
&self.struct_names,
|
|
||||||
&self.enum_names,
|
|
||||||
&self.type_alias_names,
|
|
||||||
) {
|
|
||||||
return Some(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
|
|
||||||
return Some(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
self.file,
|
|
||||||
"InvalidTypeAliasTarget",
|
|
||||||
"type alias target type is invalid",
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("concrete type"),
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_c_import_return_type(&mut self, form: &SExpr) -> Option<String> {
|
fn render_c_import_return_type(&mut self, form: &SExpr) -> Option<String> {
|
||||||
if is_ident(form, "i32") {
|
if is_ident(form, "i32") {
|
||||||
return Some("i32".to_string());
|
return Some("i32".to_string());
|
||||||
@ -1320,14 +1159,6 @@ impl Formatter<'_> {
|
|||||||
Some(format!("({})", name))
|
Some(format!("({})", name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
other if is_unsupported_generic_standard_library_call(other) => {
|
|
||||||
self.errors.push(unsupported_generic_standard_library_call(
|
|
||||||
self.file,
|
|
||||||
items[0].span,
|
|
||||||
other,
|
|
||||||
));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
other if std_runtime::is_standard_path(other) => {
|
other if std_runtime::is_standard_path(other) => {
|
||||||
self.errors
|
self.errors
|
||||||
.push(std_runtime::unsupported_standard_library_call(
|
.push(std_runtime::unsupported_standard_library_call(
|
||||||
@ -1487,16 +1318,6 @@ impl Formatter<'_> {
|
|||||||
is_array: false,
|
is_array: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if self.type_alias_names.contains(name) {
|
|
||||||
return Some(RenderedType {
|
|
||||||
text: name.to_string(),
|
|
||||||
is_array: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(items) = expect_list(form) else {
|
let Some(items) = expect_list(form) else {
|
||||||
@ -1511,24 +1332,20 @@ impl Formatter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
|
if let Some(text) = render_option_result_type(items) {
|
||||||
return Some(RenderedType {
|
return Some(RenderedType {
|
||||||
text,
|
text,
|
||||||
is_array: false,
|
is_array: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
|
if let Some(text) = render_supported_vec_type(items) {
|
||||||
return Some(RenderedType {
|
return Some(RenderedType {
|
||||||
text,
|
text,
|
||||||
is_array: false,
|
is_array: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if items.len() != 3 || !is_ident(&items[0], "array") {
|
if items.len() != 3 || !is_ident(&items[0], "array") {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -1545,11 +1362,7 @@ impl Formatter<'_> {
|
|||||||
&items[1],
|
&items[1],
|
||||||
&self.struct_names,
|
&self.struct_names,
|
||||||
&self.enum_names,
|
&self.enum_names,
|
||||||
&self.type_alias_names,
|
|
||||||
) else {
|
) else {
|
||||||
if self.push_reserved_type_error(&items[1]) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
@ -1609,18 +1422,11 @@ impl Formatter<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(name) = expect_ident(form) {
|
if let Some(name) = expect_ident(form) {
|
||||||
if self.struct_names.contains(name)
|
if self.struct_names.contains(name) || self.enum_names.contains(name) {
|
||||||
|| self.enum_names.contains(name)
|
|
||||||
|| self.type_alias_names.contains(name)
|
|
||||||
{
|
|
||||||
return Some(name.to_string());
|
return Some(name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(items) = expect_list(form) else {
|
let Some(items) = expect_list(form) else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -1633,27 +1439,19 @@ impl Formatter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(text) = render_option_result_type(items, &self.type_alias_names) {
|
if let Some(text) = render_option_result_type(items) {
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = render_supported_array_type(
|
if let Some(text) = render_supported_array_type(items, &self.struct_names, &self.enum_names)
|
||||||
items,
|
{
|
||||||
&self.struct_names,
|
|
||||||
&self.enum_names,
|
|
||||||
&self.type_alias_names,
|
|
||||||
) {
|
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text) = render_supported_vec_type(items, &self.type_alias_names) {
|
if let Some(text) = render_supported_vec_type(items) {
|
||||||
return Some(text);
|
return Some(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.push_reserved_type_error(form) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
@ -2253,7 +2051,6 @@ impl Formatter<'_> {
|
|||||||
&items[1],
|
&items[1],
|
||||||
&self.struct_names,
|
&self.struct_names,
|
||||||
&self.enum_names,
|
&self.enum_names,
|
||||||
&self.type_alias_names,
|
|
||||||
) else {
|
) else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -2316,10 +2113,21 @@ impl Formatter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(payload_type) = render_payload_type_atom(&items[1], &self.type_alias_names) else {
|
let payload_type = if is_ident(&items[1], "i32") {
|
||||||
if self.push_reserved_type_error(&items[1]) {
|
"i32"
|
||||||
return None;
|
} else if is_ident(&items[1], "i64") {
|
||||||
}
|
"i64"
|
||||||
|
} else if is_ident(&items[1], "u32") {
|
||||||
|
"u32"
|
||||||
|
} else if is_ident(&items[1], "u64") {
|
||||||
|
"u64"
|
||||||
|
} else if is_ident(&items[1], "f64") {
|
||||||
|
"f64"
|
||||||
|
} else if is_ident(&items[1], "bool") {
|
||||||
|
"bool"
|
||||||
|
} else if is_ident(&items[1], "string") {
|
||||||
|
"string"
|
||||||
|
} else {
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
@ -2361,24 +2169,21 @@ impl Formatter<'_> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(ok_ty) = render_payload_type_atom(&items[1], &self.type_alias_names) else {
|
let result_type = if is_ident(&items[1], "i32") && is_ident(&items[2], "i32") {
|
||||||
if self.push_reserved_type_error(&items[1]) {
|
"i32 i32"
|
||||||
return None;
|
} else if is_ident(&items[1], "i64") && is_ident(&items[2], "i32") {
|
||||||
}
|
"i64 i32"
|
||||||
self.errors.push(
|
} else if is_ident(&items[1], "u32") && is_ident(&items[2], "i32") {
|
||||||
Diagnostic::new(
|
"u32 i32"
|
||||||
self.file,
|
} else if is_ident(&items[1], "u64") && is_ident(&items[2], "i32") {
|
||||||
"UnsupportedFormatterForm",
|
"u64 i32"
|
||||||
"formatter supports only `(result i32 i32)`, `(result i64 i32)`, `(result u32 i32)`, `(result u64 i32)`, `(result f64 i32)`, `(result bool i32)`, and `(result string i32)` constructors",
|
} else if is_ident(&items[1], "f64") && is_ident(&items[2], "i32") {
|
||||||
)
|
"f64 i32"
|
||||||
.with_span(span),
|
} else if is_ident(&items[1], "bool") && is_ident(&items[2], "i32") {
|
||||||
);
|
"bool i32"
|
||||||
return None;
|
} else if is_ident(&items[1], "string") && is_ident(&items[2], "i32") {
|
||||||
};
|
"string i32"
|
||||||
let Some(err_ty) = render_result_err_type_atom(&items[2], &self.type_alias_names) else {
|
} else {
|
||||||
if self.push_reserved_type_error(&items[2]) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.errors.push(
|
self.errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
self.file,
|
self.file,
|
||||||
@ -2391,7 +2196,7 @@ impl Formatter<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let value = self.render_expr(&items[3], env)?;
|
let value = self.render_expr(&items[3], env)?;
|
||||||
Some(format!("({} {} {} {})", name, ok_ty, err_ty, value))
|
Some(format!("({} {} {})", name, result_type, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_call(
|
fn render_call(
|
||||||
@ -2429,15 +2234,6 @@ impl Formatter<'_> {
|
|||||||
Some(output)
|
Some(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_reserved_type_error(&mut self, form: &SExpr) -> bool {
|
|
||||||
if let Some(diagnostic) = unsupported_reserved_type_diagnostic(self.file, form) {
|
|
||||||
self.errors.push(diagnostic);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_indented_rendered(&mut self, indent: &str, rendered: &str) {
|
fn write_indented_rendered(&mut self, indent: &str, rendered: &str) {
|
||||||
push_indented(&mut self.output, indent, rendered);
|
push_indented(&mut self.output, indent, rendered);
|
||||||
}
|
}
|
||||||
@ -2559,19 +2355,6 @@ fn collect_enum_names(forms: &[SExpr]) -> HashSet<String> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_type_alias_names(forms: &[SExpr]) -> HashSet<String> {
|
|
||||||
forms
|
|
||||||
.iter()
|
|
||||||
.filter_map(|form| {
|
|
||||||
let items = expect_list(form)?;
|
|
||||||
if !is_ident(items.first()?, "type") {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
expect_ident(items.get(1)?).map(str::to_string)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn qualified_enum_name(name: &str) -> Option<(&str, &str)> {
|
fn qualified_enum_name(name: &str) -> Option<(&str, &str)> {
|
||||||
let (enum_name, variant) = name.split_once('.')?;
|
let (enum_name, variant) = name.split_once('.')?;
|
||||||
if enum_name.is_empty() || variant.is_empty() || variant.contains('.') {
|
if enum_name.is_empty() || variant.is_empty() || variant.contains('.') {
|
||||||
@ -2684,19 +2467,89 @@ fn list_head(form: &SExpr) -> Option<&str> {
|
|||||||
expect_ident(first)
|
expect_ident(first)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_option_result_type(
|
fn render_option_result_type(items: &[SExpr]) -> Option<String> {
|
||||||
items: &[SExpr],
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i32") {
|
||||||
type_alias_names: &HashSet<String>,
|
return Some("(option i32)".to_string());
|
||||||
) -> Option<String> {
|
|
||||||
if items.len() == 2 && is_ident(&items[0], "option") {
|
|
||||||
let payload = render_payload_type_atom(&items[1], type_alias_names)?;
|
|
||||||
return Some(format!("(option {})", payload));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if items.len() == 3 && is_ident(&items[0], "result") {
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "i64") {
|
||||||
let ok = render_payload_type_atom(&items[1], type_alias_names)?;
|
return Some("(option i64)".to_string());
|
||||||
let err = render_result_err_type_atom(&items[2], type_alias_names)?;
|
}
|
||||||
return Some(format!("(result {} {})", ok, err));
|
|
||||||
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u32") {
|
||||||
|
return Some("(option u32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "u64") {
|
||||||
|
return Some("(option u64)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "f64") {
|
||||||
|
return Some("(option f64)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "bool") {
|
||||||
|
return Some("(option bool)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 2 && is_ident(&items[0], "option") && is_ident(&items[1], "string") {
|
||||||
|
return Some("(option string)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "i32")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result i32 i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "i64")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result i64 i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "u32")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result u32 i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "u64")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result u64 i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "f64")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result f64 i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "bool")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result bool i32)".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if items.len() == 3
|
||||||
|
&& is_ident(&items[0], "result")
|
||||||
|
&& is_ident(&items[1], "string")
|
||||||
|
&& is_ident(&items[2], "i32")
|
||||||
|
{
|
||||||
|
return Some("(result string i32)".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@ -2706,27 +2559,42 @@ fn render_supported_array_constructor_type(
|
|||||||
form: &SExpr,
|
form: &SExpr,
|
||||||
struct_names: &HashSet<String>,
|
struct_names: &HashSet<String>,
|
||||||
enum_names: &HashSet<String>,
|
enum_names: &HashSet<String>,
|
||||||
type_alias_names: &HashSet<String>,
|
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
render_direct_type_atom(form, struct_names, enum_names, type_alias_names)
|
if is_ident(form, "i32") {
|
||||||
|
Some("i32".to_string())
|
||||||
|
} else if is_ident(form, "i64") {
|
||||||
|
Some("i64".to_string())
|
||||||
|
} else if is_ident(form, "u32") {
|
||||||
|
Some("u32".to_string())
|
||||||
|
} else if is_ident(form, "u64") {
|
||||||
|
Some("u64".to_string())
|
||||||
|
} else if is_ident(form, "f64") {
|
||||||
|
Some("f64".to_string())
|
||||||
|
} else if is_ident(form, "bool") {
|
||||||
|
Some("bool".to_string())
|
||||||
|
} else if is_ident(form, "string") {
|
||||||
|
Some("string".to_string())
|
||||||
|
} else if let Some(name) = expect_ident(form) {
|
||||||
|
if struct_names.contains(name) || enum_names.contains(name) {
|
||||||
|
Some(name.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_supported_array_type(
|
fn render_supported_array_type(
|
||||||
items: &[SExpr],
|
items: &[SExpr],
|
||||||
struct_names: &HashSet<String>,
|
struct_names: &HashSet<String>,
|
||||||
enum_names: &HashSet<String>,
|
enum_names: &HashSet<String>,
|
||||||
type_alias_names: &HashSet<String>,
|
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if items.len() != 3 || !is_ident(&items[0], "array") {
|
if items.len() != 3 || !is_ident(&items[0], "array") {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let elem_ty = render_supported_array_constructor_type(
|
let elem_ty = render_supported_array_constructor_type(&items[1], struct_names, enum_names)?;
|
||||||
&items[1],
|
|
||||||
struct_names,
|
|
||||||
enum_names,
|
|
||||||
type_alias_names,
|
|
||||||
)?;
|
|
||||||
let len = expect_int(&items[2])?;
|
let len = expect_int(&items[2])?;
|
||||||
if len <= 0 {
|
if len <= 0 {
|
||||||
return None;
|
return None;
|
||||||
@ -2735,10 +2603,7 @@ fn render_supported_array_type(
|
|||||||
Some(format!("(array {} {})", elem_ty, len))
|
Some(format!("(array {} {})", elem_ty, len))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_supported_vec_type(
|
fn render_supported_vec_type(items: &[SExpr]) -> Option<String> {
|
||||||
items: &[SExpr],
|
|
||||||
type_alias_names: &HashSet<String>,
|
|
||||||
) -> Option<String> {
|
|
||||||
if items.len() == 2 && is_ident(&items[0], "vec") {
|
if items.len() == 2 && is_ident(&items[0], "vec") {
|
||||||
if is_ident(&items[1], "i32") {
|
if is_ident(&items[1], "i32") {
|
||||||
return Some("(vec i32)".to_string());
|
return Some("(vec i32)".to_string());
|
||||||
@ -2755,66 +2620,11 @@ fn render_supported_vec_type(
|
|||||||
if is_ident(&items[1], "string") {
|
if is_ident(&items[1], "string") {
|
||||||
return Some("(vec string)".to_string());
|
return Some("(vec string)".to_string());
|
||||||
}
|
}
|
||||||
if let Some(name) = expect_ident(&items[1]) {
|
|
||||||
if type_alias_names.contains(name) {
|
|
||||||
return Some(format!("(vec {})", name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_payload_type_atom(form: &SExpr, type_alias_names: &HashSet<String>) -> Option<String> {
|
|
||||||
if is_ident(form, "i32") {
|
|
||||||
Some("i32".to_string())
|
|
||||||
} else if is_ident(form, "i64") {
|
|
||||||
Some("i64".to_string())
|
|
||||||
} else if is_ident(form, "u32") {
|
|
||||||
Some("u32".to_string())
|
|
||||||
} else if is_ident(form, "u64") {
|
|
||||||
Some("u64".to_string())
|
|
||||||
} else if is_ident(form, "f64") {
|
|
||||||
Some("f64".to_string())
|
|
||||||
} else if is_ident(form, "bool") {
|
|
||||||
Some("bool".to_string())
|
|
||||||
} else if is_ident(form, "string") {
|
|
||||||
Some("string".to_string())
|
|
||||||
} else if let Some(name) = expect_ident(form) {
|
|
||||||
type_alias_names.contains(name).then(|| name.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_result_err_type_atom(form: &SExpr, type_alias_names: &HashSet<String>) -> Option<String> {
|
|
||||||
if is_ident(form, "i32") {
|
|
||||||
Some("i32".to_string())
|
|
||||||
} else if let Some(name) = expect_ident(form) {
|
|
||||||
type_alias_names.contains(name).then(|| name.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_direct_type_atom(
|
|
||||||
form: &SExpr,
|
|
||||||
struct_names: &HashSet<String>,
|
|
||||||
enum_names: &HashSet<String>,
|
|
||||||
type_alias_names: &HashSet<String>,
|
|
||||||
) -> Option<String> {
|
|
||||||
if let Some(text) = render_payload_type_atom(form, type_alias_names) {
|
|
||||||
return Some(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = expect_ident(form)?;
|
|
||||||
if struct_names.contains(name) || enum_names.contains(name) {
|
|
||||||
Some(name.to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_list(form: &SExpr) -> Option<&[SExpr]> {
|
fn expect_list(form: &SExpr) -> Option<&[SExpr]> {
|
||||||
match &form.kind {
|
match &form.kind {
|
||||||
SExprKind::List(items) => Some(items),
|
SExprKind::List(items) => Some(items),
|
||||||
|
|||||||
@ -26,24 +26,12 @@ pub fn emit(_file: &str, program: &CheckedProgram) -> Result<String, Vec<Diagnos
|
|||||||
out.push_str("declare void @print_bool(i1)\n\n");
|
out.push_str("declare void @print_bool(i1)\n\n");
|
||||||
out.push_str("declare i32 @string_len(ptr)\n\n");
|
out.push_str("declare i32 @string_len(ptr)\n\n");
|
||||||
out.push_str("declare ptr @__glagol_string_concat(ptr, ptr)\n\n");
|
out.push_str("declare ptr @__glagol_string_concat(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i64 @__glagol_string_byte_at_result(ptr, i32)\n\n");
|
|
||||||
out.push_str("declare ptr @__glagol_string_slice_result(ptr, i32, i32)\n\n");
|
|
||||||
out.push_str("declare i1 @__glagol_string_starts_with(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare i1 @__glagol_string_ends_with(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_string_parse_i32_result(ptr)\n\n");
|
out.push_str("declare i64 @__glagol_string_parse_i32_result(ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_string_parse_i64_result(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i64 @__glagol_string_parse_u32_result(ptr)\n\n");
|
out.push_str("declare i64 @__glagol_string_parse_u32_result(ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_string_parse_bool_result(ptr, ptr)\n\n");
|
||||||
out.push_str("declare ptr @__glagol_json_quote_string(ptr)\n\n");
|
|
||||||
out.push_str("declare ptr @__glagol_json_parse_string_value_result(ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_json_parse_bool_value_result(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_json_parse_i32_value_result(ptr)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_json_parse_u32_value_result(ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_json_parse_i64_value_result(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_json_parse_u64_value_result(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_json_parse_f64_value_result(ptr, ptr)\n\n");
|
|
||||||
out.push_str("declare ptr @__glagol_num_i32_to_string(i32)\n\n");
|
out.push_str("declare ptr @__glagol_num_i32_to_string(i32)\n\n");
|
||||||
out.push_str("declare ptr @__glagol_num_i64_to_string(i64)\n\n");
|
out.push_str("declare ptr @__glagol_num_i64_to_string(i64)\n\n");
|
||||||
out.push_str("declare ptr @__glagol_num_u32_to_string(i32)\n\n");
|
out.push_str("declare ptr @__glagol_num_u32_to_string(i32)\n\n");
|
||||||
@ -61,21 +49,6 @@ pub fn emit(_file: &str, program: &CheckedProgram) -> Result<String, Vec<Diagnos
|
|||||||
out.push_str("declare ptr @__glagol_fs_read_text_result(ptr)\n\n");
|
out.push_str("declare ptr @__glagol_fs_read_text_result(ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_fs_write_text(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_fs_write_text(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i32 @__glagol_fs_write_text_result(ptr, ptr)\n\n");
|
out.push_str("declare i32 @__glagol_fs_write_text_result(ptr, ptr)\n\n");
|
||||||
out.push_str("declare i1 @__glagol_fs_exists(ptr)\n\n");
|
|
||||||
out.push_str("declare i1 @__glagol_fs_is_file(ptr)\n\n");
|
|
||||||
out.push_str("declare i1 @__glagol_fs_is_dir(ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_fs_remove_file_result(ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_fs_create_dir_result(ptr)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_fs_open_text_read_result(ptr)\n\n");
|
|
||||||
out.push_str("declare ptr @__glagol_fs_read_open_text_result(i32)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_fs_close_result(i32)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_net_tcp_connect_loopback_result(i32)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_net_tcp_listen_loopback_result(i32)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_net_tcp_bound_port_result(i32)\n\n");
|
|
||||||
out.push_str("declare i64 @__glagol_net_tcp_accept_result(i32)\n\n");
|
|
||||||
out.push_str("declare ptr @__glagol_net_tcp_read_all_result(i32)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_net_tcp_write_text_result(i32, ptr)\n\n");
|
|
||||||
out.push_str("declare i32 @__glagol_net_tcp_close_result(i32)\n\n");
|
|
||||||
out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n");
|
out.push_str("declare i1 @__glagol_string_eq(ptr, ptr)\n\n");
|
||||||
out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n");
|
out.push_str("declare ptr @__glagol_vec_i32_empty()\n\n");
|
||||||
out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n");
|
out.push_str("declare ptr @__glagol_vec_i32_append(ptr, i32)\n\n");
|
||||||
@ -1679,64 +1652,36 @@ impl FunctionGen<'_> {
|
|||||||
"__glagol_process_arg_result"
|
"__glagol_process_arg_result"
|
||||||
| "__glagol_env_get_result"
|
| "__glagol_env_get_result"
|
||||||
| "__glagol_fs_read_text_result"
|
| "__glagol_fs_read_text_result"
|
||||||
| "__glagol_fs_read_open_text_result"
|
|
||||||
| "__glagol_net_tcp_read_all_result"
|
|
||||||
| "__glagol_io_read_stdin_result"
|
| "__glagol_io_read_stdin_result"
|
||||||
| "__glagol_string_slice_result"
|
|
||||||
| "__glagol_json_parse_string_value_result"
|
|
||||||
) {
|
) {
|
||||||
return self.emit_string_result_host_call(expr, callee, &arg_values);
|
return self.emit_string_result_host_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_fs_write_text_result"
|
if callee == "__glagol_fs_write_text_result" {
|
||||||
|| callee == "__glagol_fs_remove_file_result"
|
|
||||||
|| callee == "__glagol_fs_create_dir_result"
|
|
||||||
|| callee == "__glagol_fs_close_result"
|
|
||||||
|| callee == "__glagol_net_tcp_write_text_result"
|
|
||||||
|| callee == "__glagol_net_tcp_close_result"
|
|
||||||
{
|
|
||||||
return self.emit_i32_result_status_call(expr, callee, &arg_values);
|
return self.emit_i32_result_status_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_fs_open_text_read_result"
|
if callee == "__glagol_string_parse_i32_result" {
|
||||||
|| callee == "__glagol_net_tcp_connect_loopback_result"
|
|
||||||
|| callee == "__glagol_net_tcp_listen_loopback_result"
|
|
||||||
|| callee == "__glagol_net_tcp_bound_port_result"
|
|
||||||
|| callee == "__glagol_net_tcp_accept_result"
|
|
||||||
|| callee == "__glagol_string_parse_i32_result"
|
|
||||||
|| callee == "__glagol_json_parse_i32_value_result"
|
|
||||||
|| callee == "__glagol_string_byte_at_result"
|
|
||||||
{
|
|
||||||
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
|
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_string_parse_u32_result"
|
if callee == "__glagol_string_parse_u32_result" {
|
||||||
|| callee == "__glagol_json_parse_u32_value_result"
|
|
||||||
{
|
|
||||||
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
|
return self.emit_i32_result_encoded_i64_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_string_parse_i64_result"
|
if callee == "__glagol_string_parse_i64_result" {
|
||||||
|| callee == "__glagol_json_parse_i64_value_result"
|
|
||||||
{
|
|
||||||
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
|
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_string_parse_u64_result"
|
if callee == "__glagol_string_parse_u64_result" {
|
||||||
|| callee == "__glagol_json_parse_u64_value_result"
|
|
||||||
{
|
|
||||||
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
|
return self.emit_i64_result_out_param_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_string_parse_f64_result"
|
if callee == "__glagol_string_parse_f64_result" {
|
||||||
|| callee == "__glagol_json_parse_f64_value_result"
|
|
||||||
{
|
|
||||||
return self.emit_f64_result_out_param_call(expr, callee, &arg_values);
|
return self.emit_f64_result_out_param_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
if callee == "__glagol_string_parse_bool_result"
|
if callee == "__glagol_string_parse_bool_result" {
|
||||||
|| callee == "__glagol_json_parse_bool_value_result"
|
|
||||||
{
|
|
||||||
return self.emit_bool_result_out_param_call(expr, callee, &arg_values);
|
return self.emit_bool_result_out_param_call(expr, callee, &arg_values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,9 @@ use crate::{
|
|||||||
ast::{
|
ast::{
|
||||||
BinaryOp, CImportDecl, EnumDecl, EnumVariantDecl, Expr, ExprKind, Function, MatchArm,
|
BinaryOp, CImportDecl, EnumDecl, EnumVariantDecl, Expr, ExprKind, Function, MatchArm,
|
||||||
MatchPattern, MatchPatternKind, Param, Program, StructDecl, StructField, StructInitField,
|
MatchPattern, MatchPatternKind, Param, Program, StructDecl, StructField, StructInitField,
|
||||||
Test, TypeAliasDecl,
|
Test,
|
||||||
},
|
},
|
||||||
diag::Diagnostic,
|
diag::Diagnostic,
|
||||||
reserved::{
|
|
||||||
is_unsupported_generic_standard_library_call, unsupported_generic_function,
|
|
||||||
unsupported_generic_standard_library_call, unsupported_generic_type_alias,
|
|
||||||
unsupported_reserved_type_diagnostic,
|
|
||||||
},
|
|
||||||
sexpr::{Atom, SExpr, SExprKind},
|
sexpr::{Atom, SExpr, SExprKind},
|
||||||
token::Span,
|
token::Span,
|
||||||
types::Type,
|
types::Type,
|
||||||
@ -28,8 +23,6 @@ pub fn lower_program_with_imported_names(
|
|||||||
imported_names: &[String],
|
imported_names: &[String],
|
||||||
) -> Result<Program, Vec<Diagnostic>> {
|
) -> Result<Program, Vec<Diagnostic>> {
|
||||||
let mut module = None;
|
let mut module = None;
|
||||||
let mut type_aliases = Vec::new();
|
|
||||||
let mut alias_names = HashMap::new();
|
|
||||||
let mut enums = Vec::new();
|
let mut enums = Vec::new();
|
||||||
let mut enum_names = imported_names.iter().cloned().collect::<HashSet<_>>();
|
let mut enum_names = imported_names.iter().cloned().collect::<HashSet<_>>();
|
||||||
let mut structs = Vec::new();
|
let mut structs = Vec::new();
|
||||||
@ -56,67 +49,10 @@ pub fn lower_program_with_imported_names(
|
|||||||
}
|
}
|
||||||
Err(mut errs) => errors.append(&mut errs),
|
Err(mut errs) => errors.append(&mut errs),
|
||||||
},
|
},
|
||||||
Some("type") => match lower_type_alias(file, form) {
|
|
||||||
Ok(alias) => match alias_names.entry(alias.name.clone()) {
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(alias.name_span);
|
|
||||||
type_aliases.push(alias);
|
|
||||||
}
|
|
||||||
Entry::Occupied(entry) => errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"DuplicateTypeAlias",
|
|
||||||
format!("duplicate type alias `{}`", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("original type alias", *entry.get()),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Err(mut errs) => errors.append(&mut errs),
|
|
||||||
},
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for alias in &type_aliases {
|
|
||||||
if is_reserved_type_name(&alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!(
|
|
||||||
"type alias `{}` conflicts with a built-in type name",
|
|
||||||
alias.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.hint("choose an alias name distinct from built-in type names"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(struct_decl) = structs.iter().find(|decl| decl.name == alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with a struct", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("struct declaration", struct_decl.name_span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(enum_decl) = enums.iter().find(|decl| decl.name == alias.name) {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"TypeAliasNameConflict",
|
|
||||||
format!("type alias `{}` conflicts with an enum", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("enum declaration", enum_decl.name_span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for form in forms {
|
for form in forms {
|
||||||
match list_head(form) {
|
match list_head(form) {
|
||||||
Some("module") => match lower_module(file, form) {
|
Some("module") => match lower_module(file, form) {
|
||||||
@ -125,7 +61,6 @@ pub fn lower_program_with_imported_names(
|
|||||||
},
|
},
|
||||||
Some("enum") => {}
|
Some("enum") => {}
|
||||||
Some("struct") => {}
|
Some("struct") => {}
|
||||||
Some("type") => {}
|
|
||||||
Some("import_c") => match lower_c_import(file, form) {
|
Some("import_c") => match lower_c_import(file, form) {
|
||||||
Ok(import) => c_imports.push(import),
|
Ok(import) => c_imports.push(import),
|
||||||
Err(mut errs) => errors.append(&mut errs),
|
Err(mut errs) => errors.append(&mut errs),
|
||||||
@ -171,7 +106,6 @@ pub fn lower_program_with_imported_names(
|
|||||||
if errors.is_empty() {
|
if errors.is_empty() {
|
||||||
Ok(Program {
|
Ok(Program {
|
||||||
module: module.unwrap_or_else(|| "main".to_string()),
|
module: module.unwrap_or_else(|| "main".to_string()),
|
||||||
type_aliases,
|
|
||||||
enums,
|
enums,
|
||||||
structs,
|
structs,
|
||||||
c_imports,
|
c_imports,
|
||||||
@ -190,14 +124,6 @@ pub fn print_program(program: &Program) -> String {
|
|||||||
output.push_str(&program.module);
|
output.push_str(&program.module);
|
||||||
output.push('\n');
|
output.push('\n');
|
||||||
|
|
||||||
for alias in &program.type_aliases {
|
|
||||||
output.push_str(" type ");
|
|
||||||
output.push_str(&alias.name);
|
|
||||||
output.push_str(" = ");
|
|
||||||
output.push_str(&alias.target.to_string());
|
|
||||||
output.push('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
for enum_decl in &program.enums {
|
for enum_decl in &program.enums {
|
||||||
output.push_str(" enum ");
|
output.push_str(" enum ");
|
||||||
output.push_str(&enum_decl.name);
|
output.push_str(&enum_decl.name);
|
||||||
@ -617,77 +543,6 @@ fn lower_module(file: &str, form: &SExpr) -> Result<String, Diagnostic> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lower_type_alias(file: &str, form: &SExpr) -> Result<TypeAliasDecl, Vec<Diagnostic>> {
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return Err(vec![Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"MalformedTypeAlias",
|
|
||||||
"type alias form must be a list",
|
|
||||||
)
|
|
||||||
.with_span(form.span)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
if matches!(items.get(2).and_then(list_head), Some("type_params")) {
|
|
||||||
return Err(vec![unsupported_generic_type_alias(
|
|
||||||
file,
|
|
||||||
items.get(2).map_or(form.span, |item| item.span),
|
|
||||||
)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if items.len() != 3 {
|
|
||||||
return Err(vec![Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"MalformedTypeAlias",
|
|
||||||
"type alias form must be `(type Alias TargetType)`",
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("(type Alias TargetType)")]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = match expect_ident(&items[1]) {
|
|
||||||
Some(name) => name.to_string(),
|
|
||||||
None => {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"InvalidTypeAliasName",
|
|
||||||
"type alias name must be an identifier",
|
|
||||||
)
|
|
||||||
.with_span(items[1].span),
|
|
||||||
);
|
|
||||||
"<error>".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let target = match lower_type(&items[2]) {
|
|
||||||
Some(ty) => ty,
|
|
||||||
None => {
|
|
||||||
errors.push(invalid_type_diagnostic(
|
|
||||||
file,
|
|
||||||
&items[2],
|
|
||||||
"InvalidTypeAliasTarget",
|
|
||||||
"type alias target type is invalid",
|
|
||||||
Some("concrete type"),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
Type::Unit
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if errors.is_empty() {
|
|
||||||
Ok(TypeAliasDecl {
|
|
||||||
name,
|
|
||||||
name_span: items[1].span,
|
|
||||||
target,
|
|
||||||
target_span: items[2].span,
|
|
||||||
span: form.span,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(errors)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lower_struct(file: &str, form: &SExpr) -> Result<StructDecl, Vec<Diagnostic>> {
|
fn lower_struct(file: &str, form: &SExpr) -> Result<StructDecl, Vec<Diagnostic>> {
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let Some(items) = expect_list(form) else {
|
let Some(items) = expect_list(form) else {
|
||||||
@ -762,14 +617,10 @@ fn lower_struct(file: &str, form: &SExpr) -> Result<StructDecl, Vec<Diagnostic>>
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(ty) = lower_type(&pair[1]) else {
|
let Some(ty) = lower_type(&pair[1]) else {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(file, "InvalidStructFieldType", "invalid struct field type")
|
||||||
&pair[1],
|
.with_span(pair[1].span),
|
||||||
"InvalidStructFieldType",
|
);
|
||||||
"invalid struct field type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -873,14 +724,15 @@ fn lower_enum(file: &str, form: &SExpr) -> Result<EnumDecl, Vec<Diagnostic>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(payload_ty) = lower_type(&variant_items[1]) else {
|
let Some(payload_ty) = lower_type(&variant_items[1]) else {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(
|
||||||
&variant_items[1],
|
file,
|
||||||
"InvalidEnumVariant",
|
"InvalidEnumVariant",
|
||||||
"enum variant payload type is invalid",
|
"enum variant payload type is invalid",
|
||||||
Some("i32"),
|
)
|
||||||
None,
|
.with_span(variant_items[1].span)
|
||||||
));
|
.expected("i32"),
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -932,10 +784,6 @@ fn lower_function(
|
|||||||
.hint("expected `(fn name ((arg Type) ...) -> ReturnType body...)`")]);
|
.hint("expected `(fn name ((arg Type) ...) -> ReturnType body...)`")]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(items.get(2).and_then(list_head), Some("type_params")) {
|
|
||||||
return Err(vec![unsupported_generic_function(file, items[2].span)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = match expect_ident(&items[1]) {
|
let name = match expect_ident(&items[1]) {
|
||||||
Some(name) => name.to_string(),
|
Some(name) => name.to_string(),
|
||||||
None => {
|
None => {
|
||||||
@ -973,14 +821,10 @@ fn lower_function(
|
|||||||
}
|
}
|
||||||
Some(ty) => ty,
|
Some(ty) => ty,
|
||||||
None => {
|
None => {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(file, "InvalidReturnType", "invalid return type")
|
||||||
&items[4],
|
.with_span(items[4].span),
|
||||||
"InvalidReturnType",
|
);
|
||||||
"invalid return type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
Type::Unit
|
Type::Unit
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1077,14 +921,10 @@ fn lower_c_import(file: &str, form: &SExpr) -> Result<CImportDecl, Vec<Diagnosti
|
|||||||
let return_type = match lower_type(&items[4]) {
|
let return_type = match lower_type(&items[4]) {
|
||||||
Some(ty) => ty,
|
Some(ty) => ty,
|
||||||
None => {
|
None => {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(file, "MalformedCImport", "invalid C import return type")
|
||||||
&items[4],
|
.with_span(items[4].span),
|
||||||
"MalformedCImport",
|
);
|
||||||
"invalid C import return type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
Type::Unit
|
Type::Unit
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1190,14 +1030,10 @@ fn lower_c_import_params(file: &str, form: &SExpr) -> Result<Vec<Param>, Vec<Dia
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
let Some(ty) = lower_type(&pair[1]) else {
|
let Some(ty) = lower_type(&pair[1]) else {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(file, "MalformedCImport", "invalid C import parameter type")
|
||||||
&pair[1],
|
.with_span(pair[1].span),
|
||||||
"MalformedCImport",
|
);
|
||||||
"invalid C import parameter type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
params.push(Param {
|
params.push(Param {
|
||||||
@ -1362,14 +1198,10 @@ fn lower_params(file: &str, form: &SExpr) -> Result<Vec<Param>, Vec<Diagnostic>>
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(ty) = lower_type(&pair[1]) else {
|
let Some(ty) = lower_type(&pair[1]) else {
|
||||||
errors.push(invalid_type_diagnostic(
|
errors.push(
|
||||||
file,
|
Diagnostic::new(file, "InvalidParamType", "invalid parameter type")
|
||||||
&pair[1],
|
.with_span(pair[1].span),
|
||||||
"InvalidParamType",
|
);
|
||||||
"invalid parameter type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1417,28 +1249,6 @@ fn unsupported_unit_return_signature(file: &str, span: crate::token::Span) -> Di
|
|||||||
.hint("`unit` is reserved for compiler/runtime unit-producing forms")
|
.hint("`unit` is reserved for compiler/runtime unit-producing forms")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_reserved_type_name(name: &str) -> bool {
|
|
||||||
matches!(
|
|
||||||
name,
|
|
||||||
"i32"
|
|
||||||
| "i64"
|
|
||||||
| "u32"
|
|
||||||
| "u64"
|
|
||||||
| "f64"
|
|
||||||
| "bool"
|
|
||||||
| "unit"
|
|
||||||
| "string"
|
|
||||||
| "ptr"
|
|
||||||
| "slice"
|
|
||||||
| "option"
|
|
||||||
| "result"
|
|
||||||
| "array"
|
|
||||||
| "vec"
|
|
||||||
| "map"
|
|
||||||
| "set"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lower_type(form: &SExpr) -> Option<Type> {
|
fn lower_type(form: &SExpr) -> Option<Type> {
|
||||||
match &form.kind {
|
match &form.kind {
|
||||||
SExprKind::Atom(Atom::Ident(name)) => match name.as_str() {
|
SExprKind::Atom(Atom::Ident(name)) => match name.as_str() {
|
||||||
@ -1746,9 +1556,6 @@ fn lower_expr(
|
|||||||
span: form.span,
|
span: form.span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
name if is_unsupported_generic_standard_library_call(name) => Err(
|
|
||||||
unsupported_generic_standard_library_call(file, items[0].span, name),
|
|
||||||
),
|
|
||||||
name => {
|
name => {
|
||||||
let mut args = Vec::new();
|
let mut args = Vec::new();
|
||||||
for item in &items[1..] {
|
for item in &items[1..] {
|
||||||
@ -2123,14 +1930,13 @@ fn lower_array_init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(elem_ty) = lower_type(&items[1]) else {
|
let Some(elem_ty) = lower_type(&items[1]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[1],
|
|
||||||
"InvalidArrayElementType",
|
"InvalidArrayElementType",
|
||||||
"array constructor element type is invalid",
|
"array constructor element type is invalid",
|
||||||
None,
|
)
|
||||||
Some("fixed arrays use direct scalar `i32`, `i64`, `f64`, `bool`, `string`, known enum, or known non-recursive struct elements"),
|
.with_span(items[1].span)
|
||||||
));
|
.hint("fixed arrays use direct scalar `i32`, `i64`, `f64`, `bool`, `string`, known enum, or known non-recursive struct elements"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
@ -2166,14 +1972,13 @@ fn lower_option_some(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(payload_ty) = lower_type(&items[1]) else {
|
let Some(payload_ty) = lower_type(&items[1]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[1],
|
|
||||||
"InvalidOptionPayloadType",
|
"InvalidOptionPayloadType",
|
||||||
"option constructor payload type is invalid",
|
"option constructor payload type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass options use `i32` payloads"),
|
.with_span(items[1].span)
|
||||||
));
|
.hint("first-pass options use `i32` payloads"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
@ -2198,14 +2003,13 @@ fn lower_option_none(file: &str, form: &SExpr, items: &[SExpr]) -> Result<Expr,
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(payload_ty) = lower_type(&items[1]) else {
|
let Some(payload_ty) = lower_type(&items[1]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[1],
|
|
||||||
"InvalidOptionPayloadType",
|
"InvalidOptionPayloadType",
|
||||||
"option constructor payload type is invalid",
|
"option constructor payload type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass options use `i32` payloads"),
|
.with_span(items[1].span)
|
||||||
));
|
.hint("first-pass options use `i32` payloads"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
@ -2235,24 +2039,22 @@ fn lower_result_ok(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(ok_ty) = lower_type(&items[1]) else {
|
let Some(ok_ty) = lower_type(&items[1]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[1],
|
|
||||||
"InvalidResultPayloadType",
|
"InvalidResultPayloadType",
|
||||||
"result constructor ok type is invalid",
|
"result constructor ok type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass results use `i32` payloads"),
|
.with_span(items[1].span)
|
||||||
));
|
.hint("first-pass results use `i32` payloads"));
|
||||||
};
|
};
|
||||||
let Some(err_ty) = lower_type(&items[2]) else {
|
let Some(err_ty) = lower_type(&items[2]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[2],
|
|
||||||
"InvalidResultPayloadType",
|
"InvalidResultPayloadType",
|
||||||
"result constructor err type is invalid",
|
"result constructor err type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass results use `i32` payloads"),
|
.with_span(items[2].span)
|
||||||
));
|
.hint("first-pass results use `i32` payloads"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
@ -2285,24 +2087,22 @@ fn lower_result_err(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let Some(ok_ty) = lower_type(&items[1]) else {
|
let Some(ok_ty) = lower_type(&items[1]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[1],
|
|
||||||
"InvalidResultPayloadType",
|
"InvalidResultPayloadType",
|
||||||
"result constructor ok type is invalid",
|
"result constructor ok type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass results use `i32` payloads"),
|
.with_span(items[1].span)
|
||||||
));
|
.hint("first-pass results use `i32` payloads"));
|
||||||
};
|
};
|
||||||
let Some(err_ty) = lower_type(&items[2]) else {
|
let Some(err_ty) = lower_type(&items[2]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
&items[2],
|
|
||||||
"InvalidResultPayloadType",
|
"InvalidResultPayloadType",
|
||||||
"result constructor err type is invalid",
|
"result constructor err type is invalid",
|
||||||
None,
|
)
|
||||||
Some("first-pass results use `i32` payloads"),
|
.with_span(items[2].span)
|
||||||
));
|
.hint("first-pass results use `i32` payloads"));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
@ -2424,14 +2224,10 @@ fn lower_local(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(ty) = lower_type(&items[2]) else {
|
let Some(ty) = lower_type(&items[2]) else {
|
||||||
return Err(invalid_type_diagnostic(
|
return Err(
|
||||||
file,
|
Diagnostic::new(file, "InvalidLocalType", "invalid local type")
|
||||||
&items[2],
|
.with_span(items[2].span),
|
||||||
"InvalidLocalType",
|
);
|
||||||
"invalid local type",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Expr {
|
Ok(Expr {
|
||||||
@ -2518,29 +2314,6 @@ fn lower_while(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalid_type_diagnostic(
|
|
||||||
file: &str,
|
|
||||||
form: &SExpr,
|
|
||||||
fallback_code: &'static str,
|
|
||||||
fallback_message: impl Into<String>,
|
|
||||||
fallback_expected: Option<&str>,
|
|
||||||
fallback_hint: Option<&str>,
|
|
||||||
) -> Diagnostic {
|
|
||||||
if let Some(diagnostic) = unsupported_reserved_type_diagnostic(file, form) {
|
|
||||||
return diagnostic;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut diagnostic =
|
|
||||||
Diagnostic::new(file, fallback_code, fallback_message).with_span(form.span);
|
|
||||||
if let Some(expected) = fallback_expected {
|
|
||||||
diagnostic = diagnostic.expected(expected);
|
|
||||||
}
|
|
||||||
if let Some(hint) = fallback_hint {
|
|
||||||
diagnostic = diagnostic.hint(hint);
|
|
||||||
}
|
|
||||||
diagnostic
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_head(form: &SExpr) -> Option<&str> {
|
fn list_head(form: &SExpr) -> Option<&str> {
|
||||||
let items = expect_list(form)?;
|
let items = expect_list(form)?;
|
||||||
let first = items.first()?;
|
let first = items.first()?;
|
||||||
|
|||||||
@ -8,27 +8,21 @@ mod lexer;
|
|||||||
mod llvm;
|
mod llvm;
|
||||||
mod lower;
|
mod lower;
|
||||||
mod project;
|
mod project;
|
||||||
mod reserved;
|
|
||||||
mod scaffold;
|
mod scaffold;
|
||||||
mod sexpr;
|
mod sexpr;
|
||||||
mod std_runtime;
|
mod std_runtime;
|
||||||
mod symbols;
|
|
||||||
mod test_runner;
|
mod test_runner;
|
||||||
mod token;
|
mod token;
|
||||||
mod types;
|
mod types;
|
||||||
mod unsafe_ops;
|
mod unsafe_ops;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env, fs, io,
|
||||||
io::{self, Write},
|
|
||||||
panic::{self, AssertUnwindSafe},
|
panic::{self, AssertUnwindSafe},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{self, Command as ProcessCommand},
|
process::{self, Command as ProcessCommand},
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const TEST_RUNNER_THREAD_STACK_SIZE: usize = 16 * 1024 * 1024;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let raw_args = env::args().collect::<Vec<_>>();
|
let raw_args = env::args().collect::<Vec<_>>();
|
||||||
let command_line = raw_args.join(" ");
|
let command_line = raw_args.join(" ");
|
||||||
@ -54,29 +48,9 @@ fn run_invocation_guarded(invocation: Invocation) -> ! {
|
|||||||
panic::set_hook(Box::new(|_| {}));
|
panic::set_hook(Box::new(|_| {}));
|
||||||
|
|
||||||
let run_invocation = invocation.clone();
|
let run_invocation = invocation.clone();
|
||||||
let result = if invocation.mode == Mode::RunTests {
|
let result = panic::catch_unwind(AssertUnwindSafe(move || {
|
||||||
let thread_invocation = run_invocation.clone();
|
run_invocation_inner(run_invocation);
|
||||||
match thread::Builder::new()
|
}));
|
||||||
.name("glagol-test-runner".to_string())
|
|
||||||
.stack_size(TEST_RUNNER_THREAD_STACK_SIZE)
|
|
||||||
.spawn(move || {
|
|
||||||
panic::catch_unwind(AssertUnwindSafe(move || {
|
|
||||||
run_invocation_inner(thread_invocation);
|
|
||||||
}))
|
|
||||||
}) {
|
|
||||||
Ok(handle) => match handle.join() {
|
|
||||||
Ok(result) => result,
|
|
||||||
Err(payload) => Err(payload),
|
|
||||||
},
|
|
||||||
Err(_) => panic::catch_unwind(AssertUnwindSafe(move || {
|
|
||||||
run_invocation_inner(run_invocation);
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic::catch_unwind(AssertUnwindSafe(move || {
|
|
||||||
run_invocation_inner(run_invocation);
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
panic::set_hook(previous_hook);
|
panic::set_hook(previous_hook);
|
||||||
|
|
||||||
@ -113,32 +87,21 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
|||||||
if invocation.mode == Mode::New {
|
if invocation.mode == Mode::New {
|
||||||
run_new(invocation);
|
run_new(invocation);
|
||||||
}
|
}
|
||||||
if invocation.mode == Mode::Clean {
|
|
||||||
run_clean(invocation);
|
|
||||||
}
|
|
||||||
if invocation.mode == Mode::Doc {
|
if invocation.mode == Mode::Doc {
|
||||||
run_doc(invocation);
|
run_doc(invocation);
|
||||||
}
|
}
|
||||||
if invocation.mode == Mode::Symbols && project::is_project_input(&invocation.path) {
|
|
||||||
run_project_symbols(invocation);
|
|
||||||
}
|
|
||||||
if invocation.mode == Mode::Format && invocation.fmt_action != FmtAction::Stdout {
|
if invocation.mode == Mode::Format && invocation.fmt_action != FmtAction::Stdout {
|
||||||
run_fmt_action(invocation);
|
run_fmt_action(invocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
let project_capable_mode = matches!(
|
let project_capable_mode = matches!(invocation.mode, Mode::Check | Mode::Build)
|
||||||
invocation.mode,
|
|| (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test");
|
||||||
Mode::Check | Mode::Build | Mode::Run | Mode::Symbols
|
|
||||||
) || (invocation.mode == Mode::RunTests
|
|
||||||
&& invocation.manifest_mode_name == "test");
|
|
||||||
if project_capable_mode && project::is_project_input(&invocation.path) {
|
if project_capable_mode && project::is_project_input(&invocation.path) {
|
||||||
match invocation.mode {
|
match invocation.mode {
|
||||||
Mode::Check => run_project_check(invocation),
|
Mode::Check => run_project_check(invocation),
|
||||||
Mode::RunTests => run_project_test(invocation),
|
Mode::RunTests => run_project_test(invocation),
|
||||||
Mode::Build => run_project_build(invocation),
|
Mode::Build => run_project_build(invocation),
|
||||||
Mode::Run => run_project_run(invocation),
|
_ => unreachable!("project mode is selected only for check/test/build"),
|
||||||
Mode::Symbols => run_project_symbols(invocation),
|
|
||||||
_ => unreachable!("project mode is selected only for check/test/build/run/symbols"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +124,6 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
|||||||
|
|
||||||
match invocation.mode {
|
match invocation.mode {
|
||||||
Mode::Build => run_build(invocation, &source),
|
Mode::Build => run_build(invocation, &source),
|
||||||
Mode::Run => run_run(invocation, &source),
|
|
||||||
mode => run_text_mode(invocation, mode, &source),
|
mode => run_text_mode(invocation, mode, &source),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,13 +146,7 @@ fn run_project_check(invocation: Invocation) -> ! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_project_test(invocation: Invocation) -> ! {
|
fn run_project_test(invocation: Invocation) -> ! {
|
||||||
let result = if invocation.test_list {
|
match project::run_tests(&invocation.path, invocation.test_filter.as_deref()) {
|
||||||
project::list_tests(&invocation.path, invocation.test_filter.as_deref())
|
|
||||||
} else {
|
|
||||||
project::run_tests(&invocation.path, invocation.test_filter.as_deref())
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(success) => {
|
Ok(success) => {
|
||||||
let output = success.output;
|
let output = success.output;
|
||||||
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
|
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
|
||||||
@ -241,33 +197,6 @@ fn run_project_build(invocation: Invocation) -> ! {
|
|||||||
run_build_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
run_build_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_project_run(invocation: Invocation) -> ! {
|
|
||||||
let output = match project::compile_to_llvm(&invocation.path) {
|
|
||||||
Ok(output) => output,
|
|
||||||
Err(failure) => exit_project_failure(invocation, failure),
|
|
||||||
};
|
|
||||||
run_native_from_llvm(invocation, output.text, Some(output.artifact), Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_project_symbols(invocation: Invocation) -> ! {
|
|
||||||
let loaded = match project::load_project_sources_for_tools(&invocation.path) {
|
|
||||||
Ok(loaded) => loaded,
|
|
||||||
Err(failure) => exit_tool_failure(invocation, failure),
|
|
||||||
};
|
|
||||||
let output = match symbols::render_project(&loaded) {
|
|
||||||
Ok(output) => output,
|
|
||||||
Err(diagnostics) => exit_tool_failure(
|
|
||||||
invocation.clone(),
|
|
||||||
project::ToolFailure {
|
|
||||||
diagnostics,
|
|
||||||
sources: loaded.sources.clone(),
|
|
||||||
artifact: Some(loaded.artifact.clone()),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
finish_symbols_output(invocation, output, Some(&loaded.artifact));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFailure) -> ! {
|
fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFailure) -> ! {
|
||||||
let rendered = render_source_diagnostics_multi(
|
let rendered = render_source_diagnostics_multi(
|
||||||
&failure.diagnostics,
|
&failure.diagnostics,
|
||||||
@ -290,52 +219,11 @@ fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFai
|
|||||||
process::exit(ExitCode::SourceFailure.code());
|
process::exit(ExitCode::SourceFailure.code());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_tool_failure(invocation: Invocation, failure: project::ToolFailure) -> ! {
|
|
||||||
let rendered = render_source_diagnostics_multi(
|
|
||||||
&failure.diagnostics,
|
|
||||||
&failure.sources,
|
|
||||||
invocation.diagnostics,
|
|
||||||
);
|
|
||||||
eprint!("{}", rendered.stderr);
|
|
||||||
write_manifest_if_requested_with_project(
|
|
||||||
&invocation,
|
|
||||||
false,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: &rendered.machine_text,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
failure.artifact.as_ref(),
|
|
||||||
);
|
|
||||||
process::exit(ExitCode::SourceFailure.code());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
||||||
if mode == Mode::RunTests {
|
if mode == Mode::RunTests {
|
||||||
run_test_mode(invocation, source);
|
run_test_mode(invocation, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == Mode::Symbols {
|
|
||||||
match symbols::render_file(&invocation.path, source) {
|
|
||||||
Ok(output) => finish_symbols_output(invocation, output, None),
|
|
||||||
Err(diagnostics) => {
|
|
||||||
let rendered =
|
|
||||||
render_source_diagnostics(&diagnostics, source, invocation.diagnostics);
|
|
||||||
eprint!("{}", rendered.stderr);
|
|
||||||
write_manifest_if_requested(
|
|
||||||
&invocation,
|
|
||||||
false,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: &rendered.machine_text,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
process::exit(ExitCode::SourceFailure.code());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
||||||
let result = match mode {
|
let result = match mode {
|
||||||
Mode::EmitLlvm => driver::compile_to_llvm(&invocation.path, source),
|
Mode::EmitLlvm => driver::compile_to_llvm(&invocation.path, source),
|
||||||
@ -345,12 +233,9 @@ fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
|||||||
Mode::InspectLoweringSurface => driver::inspect_lowering_surface(&invocation.path, source),
|
Mode::InspectLoweringSurface => driver::inspect_lowering_surface(&invocation.path, source),
|
||||||
Mode::InspectLoweringChecked => driver::inspect_lowering_checked(&invocation.path, source),
|
Mode::InspectLoweringChecked => driver::inspect_lowering_checked(&invocation.path, source),
|
||||||
Mode::CheckTests => driver::check_tests(&invocation.path, source),
|
Mode::CheckTests => driver::check_tests(&invocation.path, source),
|
||||||
Mode::Symbols => unreachable!("symbols mode is handled separately"),
|
|
||||||
Mode::RunTests => unreachable!("test mode is handled separately"),
|
Mode::RunTests => unreachable!("test mode is handled separately"),
|
||||||
Mode::Build => unreachable!("build is handled separately"),
|
Mode::Build => unreachable!("build is handled separately"),
|
||||||
Mode::Run => unreachable!("run is handled separately"),
|
|
||||||
Mode::New => unreachable!("new is handled separately"),
|
Mode::New => unreachable!("new is handled separately"),
|
||||||
Mode::Clean => unreachable!("clean is handled separately"),
|
|
||||||
Mode::Doc => unreachable!("doc is handled separately"),
|
Mode::Doc => unreachable!("doc is handled separately"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -414,54 +299,8 @@ fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_symbols_output(
|
|
||||||
invocation: Invocation,
|
|
||||||
output: String,
|
|
||||||
project_artifact: Option<&project::ProjectArtifact>,
|
|
||||||
) -> ! {
|
|
||||||
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
|
|
||||||
if let Err(err) = fs::write(output_path, &output) {
|
|
||||||
let message = format!("cannot write `{}`: {}", output_path, err);
|
|
||||||
emit_message_diagnostic(
|
|
||||||
&message,
|
|
||||||
"OutputWriteFailed",
|
|
||||||
ExitCode::ArtifactFailure,
|
|
||||||
&invocation,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: message.as_str(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PrimaryOutput::Path {
|
|
||||||
kind: Mode::Symbols.output_kind(),
|
|
||||||
path: output_path,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print!("{}", output);
|
|
||||||
PrimaryOutput::Stdout {
|
|
||||||
kind: Mode::Symbols.output_kind(),
|
|
||||||
text: &output,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
write_manifest_if_requested_with_project(
|
|
||||||
&invocation,
|
|
||||||
true,
|
|
||||||
primary_output,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
project_artifact,
|
|
||||||
);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_new(invocation: Invocation) -> ! {
|
fn run_new(invocation: Invocation) -> ! {
|
||||||
match scaffold::create_project(
|
match scaffold::create_project(&invocation.path, invocation.project_name.as_deref()) {
|
||||||
&invocation.path,
|
|
||||||
invocation.project_name.as_deref(),
|
|
||||||
invocation.project_template,
|
|
||||||
) {
|
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
@ -483,28 +322,6 @@ fn run_new(invocation: Invocation) -> ! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_clean(invocation: Invocation) -> ! {
|
|
||||||
let build_dir = generated_build_dir(&invocation.path);
|
|
||||||
if build_dir.exists() {
|
|
||||||
if let Err(err) = fs::remove_dir_all(&build_dir) {
|
|
||||||
let message = format!("cannot remove `{}`: {}", build_dir.display(), err);
|
|
||||||
emit_message_diagnostic(
|
|
||||||
&message,
|
|
||||||
"CleanFailed",
|
|
||||||
ExitCode::ArtifactFailure,
|
|
||||||
&invocation,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: message.as_str(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write_manifest_if_requested(&invocation, true, PrimaryOutput::NoOutput, None, None);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_doc(invocation: Invocation) -> ! {
|
fn run_doc(invocation: Invocation) -> ! {
|
||||||
let Some(output_dir) = invocation.output_path.as_deref() else {
|
let Some(output_dir) = invocation.output_path.as_deref() else {
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
@ -685,13 +502,7 @@ fn finish_formatted_source(
|
|||||||
|
|
||||||
fn run_test_mode(invocation: Invocation, source: &str) -> ! {
|
fn run_test_mode(invocation: Invocation, source: &str) -> ! {
|
||||||
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
||||||
let result = if invocation.test_list {
|
match driver::run_tests(&invocation.path, source, invocation.test_filter.as_deref()) {
|
||||||
driver::list_tests(&invocation.path, source, invocation.test_filter.as_deref())
|
|
||||||
} else {
|
|
||||||
driver::run_tests(&invocation.path, source, invocation.test_filter.as_deref())
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
let output = result.output;
|
let output = result.output;
|
||||||
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
|
let primary_output = if let Some(output_path) = invocation.output_path.as_deref() {
|
||||||
@ -778,31 +589,6 @@ fn run_build(invocation: Invocation, source: &str) -> ! {
|
|||||||
run_build_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
run_build_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_run(invocation: Invocation, source: &str) -> ! {
|
|
||||||
let foreign_imports = c_imports_for_manifest(&invocation.path, source);
|
|
||||||
let llvm_ir = match driver::compile_to_llvm(&invocation.path, source) {
|
|
||||||
Ok(output) => output,
|
|
||||||
Err(diagnostics) => {
|
|
||||||
let rendered = render_source_diagnostics(&diagnostics, source, invocation.diagnostics);
|
|
||||||
eprint!("{}", rendered.stderr);
|
|
||||||
write_manifest_if_requested_with_foreign_imports(
|
|
||||||
&invocation,
|
|
||||||
false,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: &rendered.machine_text,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
&foreign_imports,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
process::exit(ExitCode::SourceFailure.code());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
run_native_from_llvm(invocation, llvm_ir, None, foreign_imports);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_build_from_llvm(
|
fn run_build_from_llvm(
|
||||||
invocation: Invocation,
|
invocation: Invocation,
|
||||||
llvm_ir: String,
|
llvm_ir: String,
|
||||||
@ -821,125 +607,18 @@ fn run_build_from_llvm(
|
|||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
let output_path = PathBuf::from(output_path);
|
|
||||||
let native = build_native_executable_or_exit(&invocation, llvm_ir, &output_path);
|
|
||||||
let output_display = native.output_path.display().to_string();
|
|
||||||
write_manifest_if_requested_with_foreign_imports(
|
|
||||||
&invocation,
|
|
||||||
true,
|
|
||||||
PrimaryOutput::Path {
|
|
||||||
kind: "native-executable",
|
|
||||||
path: &output_display,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
Some(BuildInfo {
|
|
||||||
clang: &native.clang,
|
|
||||||
runtime: &native.runtime,
|
|
||||||
c_inputs: &invocation.link_c_paths,
|
|
||||||
}),
|
|
||||||
&foreign_imports,
|
|
||||||
project_artifact.as_ref(),
|
|
||||||
);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_native_from_llvm(
|
if let Some(parent) = Path::new(output_path).parent() {
|
||||||
invocation: Invocation,
|
|
||||||
llvm_ir: String,
|
|
||||||
project_artifact: Option<project::ProjectArtifact>,
|
|
||||||
foreign_imports: Vec<project::ProjectArtifactCImport>,
|
|
||||||
) -> ! {
|
|
||||||
let output_path = run_output_path(&invocation, project_artifact.as_ref());
|
|
||||||
if invocation.output_path.is_none() {
|
|
||||||
if let Some(parent) = output_path.parent() {
|
|
||||||
if let Err(err) = fs::create_dir_all(parent) {
|
|
||||||
let message = format!("cannot create `{}`: {}", parent.display(), err);
|
|
||||||
emit_message_diagnostic(
|
|
||||||
&message,
|
|
||||||
"OutputWriteFailed",
|
|
||||||
ExitCode::ArtifactFailure,
|
|
||||||
&invocation,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: message.as_str(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let native = build_native_executable_or_exit(&invocation, llvm_ir, &output_path);
|
|
||||||
let run_output = match ProcessCommand::new(&native.output_path)
|
|
||||||
.args(&invocation.run_args)
|
|
||||||
.output()
|
|
||||||
{
|
|
||||||
Ok(output) => output,
|
|
||||||
Err(err) => {
|
|
||||||
let message = format!("cannot run `{}`: {}", native.output_path.display(), err);
|
|
||||||
emit_message_diagnostic(
|
|
||||||
&message,
|
|
||||||
"RunFailed",
|
|
||||||
ExitCode::ArtifactFailure,
|
|
||||||
&invocation,
|
|
||||||
PrimaryOutput::Diagnostics {
|
|
||||||
text: message.as_str(),
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = io::stdout().write_all(&run_output.stdout);
|
|
||||||
let _ = io::stderr().write_all(&run_output.stderr);
|
|
||||||
let stdout = String::from_utf8_lossy(&run_output.stdout).to_string();
|
|
||||||
let stderr = String::from_utf8_lossy(&run_output.stderr).to_string();
|
|
||||||
let exit_status = run_output.status.code();
|
|
||||||
write_manifest_if_requested_with_run_report(
|
|
||||||
&invocation,
|
|
||||||
run_output.status.success(),
|
|
||||||
PrimaryOutput::Stdout {
|
|
||||||
kind: Mode::Run.output_kind(),
|
|
||||||
text: &stdout,
|
|
||||||
},
|
|
||||||
Some(BuildInfo {
|
|
||||||
clang: &native.clang,
|
|
||||||
runtime: &native.runtime,
|
|
||||||
c_inputs: &invocation.link_c_paths,
|
|
||||||
}),
|
|
||||||
&foreign_imports,
|
|
||||||
project_artifact.as_ref(),
|
|
||||||
RunReport {
|
|
||||||
exit_status,
|
|
||||||
stdout: &stdout,
|
|
||||||
stderr: &stderr,
|
|
||||||
args: &invocation.run_args,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
process::exit(exit_status.unwrap_or(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NativeBuild {
|
|
||||||
output_path: PathBuf,
|
|
||||||
clang: String,
|
|
||||||
runtime: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_native_executable_or_exit(
|
|
||||||
invocation: &Invocation,
|
|
||||||
llvm_ir: String,
|
|
||||||
output_path: &Path,
|
|
||||||
) -> NativeBuild {
|
|
||||||
if let Some(parent) = output_path.parent() {
|
|
||||||
if !parent.as_os_str().is_empty() && !parent.is_dir() {
|
if !parent.as_os_str().is_empty() && !parent.is_dir() {
|
||||||
let message = format!(
|
let message = format!(
|
||||||
"cannot write `{}`: parent directory does not exist",
|
"cannot write `{}`: parent directory does not exist",
|
||||||
output_path.display()
|
output_path
|
||||||
);
|
);
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -955,7 +634,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -971,7 +650,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"InputReadFailed",
|
"InputReadFailed",
|
||||||
ExitCode::SourceFailure,
|
ExitCode::SourceFailure,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -980,11 +659,12 @@ fn build_native_executable_or_exit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_dir = output_path
|
let output = Path::new(output_path);
|
||||||
|
let output_dir = output
|
||||||
.parent()
|
.parent()
|
||||||
.filter(|parent| !parent.as_os_str().is_empty())
|
.filter(|parent| !parent.as_os_str().is_empty())
|
||||||
.unwrap_or_else(|| Path::new("."));
|
.unwrap_or_else(|| Path::new("."));
|
||||||
let stem = output_path
|
let stem = output
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|name| name.to_str())
|
.and_then(|name| name.to_str())
|
||||||
.unwrap_or("glagol-output");
|
.unwrap_or("glagol-output");
|
||||||
@ -1002,7 +682,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -1027,7 +707,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -1041,7 +721,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -1067,7 +747,7 @@ fn build_native_executable_or_exit(
|
|||||||
&message,
|
&message,
|
||||||
"ToolchainUnavailable",
|
"ToolchainUnavailable",
|
||||||
ExitCode::Toolchain,
|
ExitCode::Toolchain,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -1078,12 +758,12 @@ fn build_native_executable_or_exit(
|
|||||||
if let Err(err) = fs::rename(&temp_binary, output_path) {
|
if let Err(err) = fs::rename(&temp_binary, output_path) {
|
||||||
let _ = fs::remove_file(&temp_llvm);
|
let _ = fs::remove_file(&temp_llvm);
|
||||||
let _ = fs::remove_file(&temp_binary);
|
let _ = fs::remove_file(&temp_binary);
|
||||||
let message = format!("cannot write `{}`: {}", output_path.display(), err);
|
let message = format!("cannot write `{}`: {}", output_path, err);
|
||||||
emit_message_diagnostic(
|
emit_message_diagnostic(
|
||||||
&message,
|
&message,
|
||||||
"OutputWriteFailed",
|
"OutputWriteFailed",
|
||||||
ExitCode::ArtifactFailure,
|
ExitCode::ArtifactFailure,
|
||||||
invocation,
|
&invocation,
|
||||||
PrimaryOutput::Diagnostics {
|
PrimaryOutput::Diagnostics {
|
||||||
text: message.as_str(),
|
text: message.as_str(),
|
||||||
},
|
},
|
||||||
@ -1092,11 +772,23 @@ fn build_native_executable_or_exit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let _ = fs::remove_file(&temp_llvm);
|
let _ = fs::remove_file(&temp_llvm);
|
||||||
NativeBuild {
|
write_manifest_if_requested_with_foreign_imports(
|
||||||
output_path: output_path.to_path_buf(),
|
&invocation,
|
||||||
clang,
|
true,
|
||||||
runtime,
|
PrimaryOutput::Path {
|
||||||
}
|
kind: "native-executable",
|
||||||
|
path: output_path,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
Some(BuildInfo {
|
||||||
|
clang: &clang,
|
||||||
|
runtime: &runtime,
|
||||||
|
c_inputs: &invocation.link_c_paths,
|
||||||
|
}),
|
||||||
|
&foreign_imports,
|
||||||
|
project_artifact.as_ref(),
|
||||||
|
);
|
||||||
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -1117,11 +809,8 @@ struct Invocation {
|
|||||||
command_line: String,
|
command_line: String,
|
||||||
fmt_action: FmtAction,
|
fmt_action: FmtAction,
|
||||||
project_name: Option<String>,
|
project_name: Option<String>,
|
||||||
project_template: scaffold::ProjectTemplate,
|
|
||||||
link_c_paths: Vec<String>,
|
link_c_paths: Vec<String>,
|
||||||
test_filter: Option<String>,
|
test_filter: Option<String>,
|
||||||
test_list: bool,
|
|
||||||
run_args: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
@ -1154,11 +843,8 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
let mut diagnostics = DiagnosticFormat::TextAndSexpr;
|
let mut diagnostics = DiagnosticFormat::TextAndSexpr;
|
||||||
let mut fmt_action = FmtAction::Stdout;
|
let mut fmt_action = FmtAction::Stdout;
|
||||||
let mut project_name = None;
|
let mut project_name = None;
|
||||||
let mut project_template = scaffold::ProjectTemplate::Binary;
|
|
||||||
let mut link_c_paths = Vec::new();
|
let mut link_c_paths = Vec::new();
|
||||||
let mut test_filter = None;
|
let mut test_filter = None;
|
||||||
let mut test_list = false;
|
|
||||||
let mut run_args = Vec::new();
|
|
||||||
let mut no_color = false;
|
let mut no_color = false;
|
||||||
let command_line = raw_args.join(" ");
|
let command_line = raw_args.join(" ");
|
||||||
let mut iter = raw_args
|
let mut iter = raw_args
|
||||||
@ -1169,11 +855,6 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
.into_iter();
|
.into_iter();
|
||||||
|
|
||||||
while let Some(arg) = iter.next() {
|
while let Some(arg) = iter.next() {
|
||||||
if arg == "--" {
|
|
||||||
run_args.extend(iter);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-h" | "--help" => return Ok(Args::Help),
|
"-h" | "--help" => return Ok(Args::Help),
|
||||||
"--version" => return Ok(Args::Version),
|
"--version" => return Ok(Args::Version),
|
||||||
@ -1306,26 +987,6 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line: command_line.clone(),
|
command_line: command_line.clone(),
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
"--template" => {
|
|
||||||
let value = iter.next().ok_or_else(|| ParseError {
|
|
||||||
message: "`--template` requires one of: binary, library, workspace".to_string(),
|
|
||||||
manifest_path: manifest_path.clone(),
|
|
||||||
diagnostics,
|
|
||||||
command_line: command_line.clone(),
|
|
||||||
})?;
|
|
||||||
let Some(parsed) = scaffold::ProjectTemplate::parse(&value) else {
|
|
||||||
return parse_error(
|
|
||||||
format!(
|
|
||||||
"unsupported project template `{}`; expected binary, library, or workspace",
|
|
||||||
value
|
|
||||||
),
|
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
project_template = parsed;
|
|
||||||
}
|
|
||||||
"--manifest" => {
|
"--manifest" => {
|
||||||
if manifest_path.is_some() {
|
if manifest_path.is_some() {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
@ -1366,30 +1027,14 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line: command_line.clone(),
|
command_line: command_line.clone(),
|
||||||
})?);
|
})?);
|
||||||
}
|
}
|
||||||
"--list" => {
|
"check" | "fmt" | "test" | "build" | "new" | "doc" if path.is_none() => {
|
||||||
if test_list {
|
|
||||||
return parse_error(
|
|
||||||
"`--list` was provided more than once",
|
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
test_list = true;
|
|
||||||
}
|
|
||||||
"check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc" | "symbols"
|
|
||||||
if path.is_none() =>
|
|
||||||
{
|
|
||||||
let next = match arg.as_str() {
|
let next = match arg.as_str() {
|
||||||
"check" => Mode::Check,
|
"check" => Mode::Check,
|
||||||
"fmt" => Mode::Format,
|
"fmt" => Mode::Format,
|
||||||
"test" => Mode::RunTests,
|
"test" => Mode::RunTests,
|
||||||
"build" => Mode::Build,
|
"build" => Mode::Build,
|
||||||
"run" => Mode::Run,
|
|
||||||
"clean" => Mode::Clean,
|
|
||||||
"new" => Mode::New,
|
"new" => Mode::New,
|
||||||
"doc" => Mode::Doc,
|
"doc" => Mode::Doc,
|
||||||
"symbols" => Mode::Symbols,
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
set_mode(
|
set_mode(
|
||||||
@ -1469,18 +1114,9 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if project_template != scaffold::ProjectTemplate::Binary && mode != Mode::New {
|
if !link_c_paths.is_empty() && mode != Mode::Build {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
"`--template` is only supported with `new`",
|
"`--link-c` is only supported with `build`",
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !link_c_paths.is_empty() && !matches!(mode, Mode::Build | Mode::Run) {
|
|
||||||
return parse_error(
|
|
||||||
"`--link-c` is only supported with `build` and `run`",
|
|
||||||
manifest_path,
|
manifest_path,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
command_line,
|
command_line,
|
||||||
@ -1496,33 +1132,6 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if test_list && mode != Mode::RunTests {
|
|
||||||
return parse_error(
|
|
||||||
"`--list` is only supported with `test` and `--run-tests`",
|
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !run_args.is_empty() && mode != Mode::Run {
|
|
||||||
return parse_error(
|
|
||||||
"`--` program arguments are only supported with `run`",
|
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode == Mode::Clean && output_path.is_some() {
|
|
||||||
return parse_error(
|
|
||||||
"`clean` does not support `-o`",
|
|
||||||
manifest_path,
|
|
||||||
diagnostics,
|
|
||||||
command_line,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode == Mode::Doc && output_path.is_none() {
|
if mode == Mode::Doc && output_path.is_none() {
|
||||||
return parse_error(
|
return parse_error(
|
||||||
"`doc` requires `-o <dir>`",
|
"`doc` requires `-o <dir>`",
|
||||||
@ -1553,11 +1162,8 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
|||||||
command_line,
|
command_line,
|
||||||
fmt_action,
|
fmt_action,
|
||||||
project_name,
|
project_name,
|
||||||
project_template,
|
|
||||||
link_c_paths,
|
link_c_paths,
|
||||||
test_filter,
|
test_filter,
|
||||||
test_list,
|
|
||||||
run_args,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1627,11 +1233,8 @@ fn exit_parse_error(err: ParseError, command_line: &str) -> ! {
|
|||||||
},
|
},
|
||||||
fmt_action: FmtAction::Stdout,
|
fmt_action: FmtAction::Stdout,
|
||||||
project_name: None,
|
project_name: None,
|
||||||
project_template: scaffold::ProjectTemplate::Binary,
|
|
||||||
link_c_paths: Vec::new(),
|
link_c_paths: Vec::new(),
|
||||||
test_filter: None,
|
test_filter: None,
|
||||||
test_list: false,
|
|
||||||
run_args: Vec::new(),
|
|
||||||
};
|
};
|
||||||
write_manifest_or_exit(
|
write_manifest_or_exit(
|
||||||
manifest_path,
|
manifest_path,
|
||||||
@ -1643,7 +1246,6 @@ fn exit_parse_error(err: ParseError, command_line: &str) -> ! {
|
|||||||
PrimaryOutput::Diagnostics { text: &stderr },
|
PrimaryOutput::Diagnostics { text: &stderr },
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
&[],
|
&[],
|
||||||
None,
|
None,
|
||||||
err.diagnostics,
|
err.diagnostics,
|
||||||
@ -1665,11 +1267,8 @@ enum Mode {
|
|||||||
CheckTests,
|
CheckTests,
|
||||||
RunTests,
|
RunTests,
|
||||||
Build,
|
Build,
|
||||||
Run,
|
|
||||||
Clean,
|
|
||||||
New,
|
New,
|
||||||
Doc,
|
Doc,
|
||||||
Symbols,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
@ -1684,11 +1283,8 @@ impl Mode {
|
|||||||
Self::CheckTests => "check-tests",
|
Self::CheckTests => "check-tests",
|
||||||
Self::RunTests => "test",
|
Self::RunTests => "test",
|
||||||
Self::Build => "build",
|
Self::Build => "build",
|
||||||
Self::Run => "run",
|
|
||||||
Self::Clean => "clean",
|
|
||||||
Self::New => "new",
|
Self::New => "new",
|
||||||
Self::Doc => "doc",
|
Self::Doc => "doc",
|
||||||
Self::Symbols => "symbols",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1701,11 +1297,8 @@ impl Mode {
|
|||||||
Self::InspectLoweringSurface | Self::InspectLoweringChecked => "lowering-inspector",
|
Self::InspectLoweringSurface | Self::InspectLoweringChecked => "lowering-inspector",
|
||||||
Self::CheckTests | Self::RunTests => "stdout",
|
Self::CheckTests | Self::RunTests => "stdout",
|
||||||
Self::Build => "native-executable",
|
Self::Build => "native-executable",
|
||||||
Self::Run => "program-stdout",
|
|
||||||
Self::Clean => "no-output",
|
|
||||||
Self::New => "no-output",
|
Self::New => "no-output",
|
||||||
Self::Doc => "documentation",
|
Self::Doc => "documentation",
|
||||||
Self::Symbols => "symbols",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1897,13 +1490,6 @@ struct TestSummary {
|
|||||||
filter: Option<String>,
|
filter: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RunReport<'a> {
|
|
||||||
exit_status: Option<i32>,
|
|
||||||
stdout: &'a str,
|
|
||||||
stderr: &'a str,
|
|
||||||
args: &'a [String],
|
|
||||||
}
|
|
||||||
|
|
||||||
fn test_summary_from_report(report: test_runner::TestReport) -> TestSummary {
|
fn test_summary_from_report(report: test_runner::TestReport) -> TestSummary {
|
||||||
TestSummary {
|
TestSummary {
|
||||||
total_discovered: report.total_discovered,
|
total_discovered: report.total_discovered,
|
||||||
@ -2006,34 +1592,6 @@ fn write_manifest_if_requested_with_foreign_imports(
|
|||||||
success,
|
success,
|
||||||
primary_output,
|
primary_output,
|
||||||
test_summary,
|
test_summary,
|
||||||
None,
|
|
||||||
build_info,
|
|
||||||
foreign_imports,
|
|
||||||
project_info,
|
|
||||||
invocation.diagnostics,
|
|
||||||
);
|
|
||||||
write_manifest_or_exit(manifest_path, &manifest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_manifest_if_requested_with_run_report(
|
|
||||||
invocation: &Invocation,
|
|
||||||
success: bool,
|
|
||||||
primary_output: PrimaryOutput<'_>,
|
|
||||||
build_info: Option<BuildInfo<'_>>,
|
|
||||||
foreign_imports: &[project::ProjectArtifactCImport],
|
|
||||||
project_info: Option<&project::ProjectArtifact>,
|
|
||||||
run_report: RunReport<'_>,
|
|
||||||
) {
|
|
||||||
if let Some(manifest_path) = invocation.manifest_path.as_deref() {
|
|
||||||
let manifest = render_manifest(
|
|
||||||
Some(&invocation.path),
|
|
||||||
&invocation.command_line,
|
|
||||||
Some(invocation.manifest_mode_name.as_str()),
|
|
||||||
success,
|
|
||||||
primary_output,
|
|
||||||
None,
|
|
||||||
Some(run_report),
|
|
||||||
build_info,
|
build_info,
|
||||||
foreign_imports,
|
foreign_imports,
|
||||||
project_info,
|
project_info,
|
||||||
@ -2057,7 +1615,6 @@ fn render_manifest(
|
|||||||
success: bool,
|
success: bool,
|
||||||
primary_output: PrimaryOutput<'_>,
|
primary_output: PrimaryOutput<'_>,
|
||||||
test_summary: Option<TestSummary>,
|
test_summary: Option<TestSummary>,
|
||||||
run_report: Option<RunReport<'_>>,
|
|
||||||
build_info: Option<BuildInfo<'_>>,
|
build_info: Option<BuildInfo<'_>>,
|
||||||
foreign_imports: &[project::ProjectArtifactCImport],
|
foreign_imports: &[project::ProjectArtifactCImport],
|
||||||
project_info: Option<&project::ProjectArtifact>,
|
project_info: Option<&project::ProjectArtifact>,
|
||||||
@ -2081,10 +1638,7 @@ fn render_manifest(
|
|||||||
" (success {})\n",
|
" (success {})\n",
|
||||||
if success { "true" } else { "false" }
|
if success { "true" } else { "false" }
|
||||||
));
|
));
|
||||||
out.push_str(&format!(
|
out.push_str(" (diagnostics-schema-version 1)\n");
|
||||||
" (diagnostics-schema-version {})\n",
|
|
||||||
diag::DIAGNOSTIC_SCHEMA_VERSION
|
|
||||||
));
|
|
||||||
out.push_str(&format!(
|
out.push_str(&format!(
|
||||||
" (diagnostics-encoding {})\n",
|
" (diagnostics-encoding {})\n",
|
||||||
match diagnostics {
|
match diagnostics {
|
||||||
@ -2159,34 +1713,6 @@ fn render_manifest(
|
|||||||
out.push_str(" )");
|
out.push_str(" )");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(report) = run_report {
|
|
||||||
out.push('\n');
|
|
||||||
out.push_str(" (run-report\n");
|
|
||||||
match report.exit_status {
|
|
||||||
Some(status) => out.push_str(&format!(" (exit-status {})\n", status)),
|
|
||||||
None => out.push_str(" (exit-status null)\n"),
|
|
||||||
}
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (stdout {})\n",
|
|
||||||
diag::render_string(report.stdout)
|
|
||||||
));
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (stderr {})\n",
|
|
||||||
diag::render_string(report.stderr)
|
|
||||||
));
|
|
||||||
out.push_str(" (args");
|
|
||||||
if report.args.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
} else {
|
|
||||||
out.push('\n');
|
|
||||||
for arg in report.args {
|
|
||||||
out.push_str(&format!(" (arg {})\n", diag::render_string(arg)));
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(build) = build_info {
|
if let Some(build) = build_info {
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
out.push_str(" (hosted-build\n");
|
out.push_str(" (hosted-build\n");
|
||||||
@ -2472,108 +1998,9 @@ fn parse_test_count(output: &str, suffix: &str) -> Option<usize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_path() -> PathBuf {
|
fn runtime_path() -> PathBuf {
|
||||||
runtime_path_candidates()
|
|
||||||
.into_iter()
|
|
||||||
.find(|path| path.is_file())
|
|
||||||
.unwrap_or_else(checkout_runtime_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn runtime_path_candidates() -> Vec<PathBuf> {
|
|
||||||
let mut candidates = Vec::new();
|
|
||||||
if let Some(path) = env::var_os("SLOVO_RUNTIME_C") {
|
|
||||||
candidates.push(PathBuf::from(path));
|
|
||||||
}
|
|
||||||
if let Some(path) = env::var_os("GLAGOL_RUNTIME_C") {
|
|
||||||
candidates.push(PathBuf::from(path));
|
|
||||||
}
|
|
||||||
if let Ok(exe) = env::current_exe() {
|
|
||||||
if let Some(bin_dir) = exe.parent() {
|
|
||||||
candidates.push(bin_dir.join("runtime/runtime.c"));
|
|
||||||
candidates.push(bin_dir.join("../runtime/runtime.c"));
|
|
||||||
candidates.push(bin_dir.join("../share/slovo/runtime/runtime.c"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
candidates.push(checkout_runtime_path());
|
|
||||||
candidates
|
|
||||||
}
|
|
||||||
|
|
||||||
fn checkout_runtime_path() -> PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../runtime/runtime.c")
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../runtime/runtime.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_output_path(
|
|
||||||
invocation: &Invocation,
|
|
||||||
project_artifact: Option<&project::ProjectArtifact>,
|
|
||||||
) -> PathBuf {
|
|
||||||
if let Some(output_path) = invocation.output_path.as_deref() {
|
|
||||||
return PathBuf::from(output_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
let stem = project_artifact
|
|
||||||
.map(|artifact| artifact.project_name.as_str())
|
|
||||||
.or_else(|| {
|
|
||||||
Path::new(&invocation.path)
|
|
||||||
.file_stem()
|
|
||||||
.and_then(|stem| stem.to_str())
|
|
||||||
})
|
|
||||||
.map(sanitize_output_stem)
|
|
||||||
.filter(|stem| !stem.is_empty())
|
|
||||||
.unwrap_or_else(|| "slovo-program".to_string());
|
|
||||||
generated_build_dir(&invocation.path).join(format!("{}{}", stem, env::consts::EXE_SUFFIX))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generated_build_dir(input: &str) -> PathBuf {
|
|
||||||
generated_build_root(input).join(".slovo").join("build")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generated_build_root(input: &str) -> PathBuf {
|
|
||||||
let path = Path::new(input);
|
|
||||||
if project::is_project_input(input) {
|
|
||||||
if path.is_dir() {
|
|
||||||
return path.to_path_buf();
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
.parent()
|
|
||||||
.filter(|parent| !parent.as_os_str().is_empty())
|
|
||||||
.unwrap_or_else(|| Path::new("."))
|
|
||||||
.to_path_buf();
|
|
||||||
}
|
|
||||||
path.parent()
|
|
||||||
.filter(|parent| !parent.as_os_str().is_empty())
|
|
||||||
.unwrap_or_else(|| Path::new("."))
|
|
||||||
.to_path_buf()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sanitize_output_stem(value: &str) -> String {
|
|
||||||
let mut out = String::new();
|
|
||||||
let mut previous_dash = false;
|
|
||||||
for ch in value.chars().flat_map(char::to_lowercase) {
|
|
||||||
let mapped = if ch.is_ascii_lowercase() || ch.is_ascii_digit() {
|
|
||||||
Some(ch)
|
|
||||||
} else if ch == '-' || ch == '_' || ch == '.' || ch.is_whitespace() {
|
|
||||||
Some('-')
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let Some(ch) = mapped else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if ch == '-' {
|
|
||||||
if !out.is_empty() && !previous_dash {
|
|
||||||
out.push('-');
|
|
||||||
previous_dash = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
out.push(ch);
|
|
||||||
previous_dash = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while out.ends_with('-') {
|
|
||||||
out.pop();
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
fn c_imports_for_manifest(file: &str, source: &str) -> Vec<project::ProjectArtifactCImport> {
|
fn c_imports_for_manifest(file: &str, source: &str) -> Vec<project::ProjectArtifactCImport> {
|
||||||
let Ok(tokens) = lexer::lex(file, source) else {
|
let Ok(tokens) = lexer::lex(file, source) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@ -2675,6 +2102,6 @@ fn normalized_output_path(path: &str) -> Option<PathBuf> {
|
|||||||
|
|
||||||
fn print_usage() {
|
fn print_usage() {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"usage: glagol [check|fmt|test|build|run|clean|symbols] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] [--list] <file.slo|project> [-- <program-args>...]\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>] [--template binary|library|workspace]\n glagol doc <file.slo|project> -o <dir>\n glagol symbols <file.slo|project|workspace>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] [--list] <file.slo>\n glagol --version"
|
"usage: glagol [check|fmt|test|build] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <file.slo|project>\n glagol fmt [--check|--write] <file.slo|project>\n glagol new <project-dir> [--name <name>]\n glagol doc <file.slo|project> -o <dir>\n glagol [--emit=llvm|--format|--print-tree|--inspect-lowering=surface|--inspect-lowering=checked|--check-tests|--run-tests] [--json-diagnostics] [--no-color] [-o <path>] [--manifest <path>] [--filter <substring>] <file.slo>\n glagol --version"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,7 +130,6 @@ struct WorkspaceManifest {
|
|||||||
source: String,
|
source: String,
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
members: Vec<String>,
|
members: Vec<String>,
|
||||||
default_package: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -177,7 +176,6 @@ struct ModuleUnit {
|
|||||||
local_functions: HashMap<String, DeclInfo>,
|
local_functions: HashMap<String, DeclInfo>,
|
||||||
local_structs: HashMap<String, DeclInfo>,
|
local_structs: HashMap<String, DeclInfo>,
|
||||||
local_enums: HashMap<String, DeclInfo>,
|
local_enums: HashMap<String, DeclInfo>,
|
||||||
local_aliases: HashMap<String, DeclInfo>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -205,7 +203,6 @@ enum DeclKind {
|
|||||||
CImport,
|
CImport,
|
||||||
Struct,
|
Struct,
|
||||||
Enum,
|
Enum,
|
||||||
TypeAlias,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -452,26 +449,6 @@ pub fn run_tests(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_tests(
|
|
||||||
input: &str,
|
|
||||||
filter: Option<&str>,
|
|
||||||
) -> Result<ProjectTestSuccess, ProjectTestFailure> {
|
|
||||||
let checked = load_checked_project(input, false).map_err(|failure| ProjectTestFailure {
|
|
||||||
diagnostics: failure.diagnostics,
|
|
||||||
report: None,
|
|
||||||
sources: failure.sources,
|
|
||||||
artifact: failure.artifact,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let success = test_runner::list(&checked.program, filter);
|
|
||||||
Ok(ProjectTestSuccess {
|
|
||||||
output: success.output,
|
|
||||||
report: success.report,
|
|
||||||
sources: checked.sources,
|
|
||||||
artifact: checked.artifact,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CheckedProject {
|
struct CheckedProject {
|
||||||
program: CheckedProgram,
|
program: CheckedProgram,
|
||||||
sources: Vec<SourceFile>,
|
sources: Vec<SourceFile>,
|
||||||
@ -1124,16 +1101,7 @@ fn parse_manifest(path: PathBuf, source: String) -> Result<Manifest, Vec<Diagnos
|
|||||||
};
|
};
|
||||||
|
|
||||||
match key {
|
match key {
|
||||||
"name" => set_manifest_key(
|
"name" => set_manifest_key(&file, &mut errors, &mut name, parsed, line.span, "name"),
|
||||||
&file,
|
|
||||||
&mut errors,
|
|
||||||
&mut name,
|
|
||||||
parsed,
|
|
||||||
line.span,
|
|
||||||
"name",
|
|
||||||
"ProjectManifestInvalid",
|
|
||||||
"project",
|
|
||||||
),
|
|
||||||
"source_root" => set_manifest_key(
|
"source_root" => set_manifest_key(
|
||||||
&file,
|
&file,
|
||||||
&mut errors,
|
&mut errors,
|
||||||
@ -1141,19 +1109,8 @@ fn parse_manifest(path: PathBuf, source: String) -> Result<Manifest, Vec<Diagnos
|
|||||||
parsed,
|
parsed,
|
||||||
line.span,
|
line.span,
|
||||||
"source_root",
|
"source_root",
|
||||||
"ProjectManifestInvalid",
|
|
||||||
"project",
|
|
||||||
),
|
|
||||||
"entry" => set_manifest_key(
|
|
||||||
&file,
|
|
||||||
&mut errors,
|
|
||||||
&mut entry,
|
|
||||||
parsed,
|
|
||||||
line.span,
|
|
||||||
"entry",
|
|
||||||
"ProjectManifestInvalid",
|
|
||||||
"project",
|
|
||||||
),
|
),
|
||||||
|
"entry" => set_manifest_key(&file, &mut errors, &mut entry, parsed, line.span, "entry"),
|
||||||
_ => errors.push(
|
_ => errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&file,
|
&file,
|
||||||
@ -1237,7 +1194,6 @@ fn parse_workspace_manifest(
|
|||||||
let mut in_workspace = false;
|
let mut in_workspace = false;
|
||||||
let mut saw_workspace = false;
|
let mut saw_workspace = false;
|
||||||
let mut members = None::<Vec<String>>;
|
let mut members = None::<Vec<String>>;
|
||||||
let mut default_package = None::<String>;
|
|
||||||
|
|
||||||
for line in manifest_lines(&source) {
|
for line in manifest_lines(&source) {
|
||||||
let trimmed = line.text.trim();
|
let trimmed = line.text.trim();
|
||||||
@ -1308,28 +1264,6 @@ fn parse_workspace_manifest(
|
|||||||
.with_span(line.span),
|
.with_span(line.span),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
"default_package" => match parse_manifest_string(value.trim()) {
|
|
||||||
Some(parsed) => {
|
|
||||||
if default_package.replace(parsed).is_some() {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"WorkspaceManifestInvalid",
|
|
||||||
"duplicate workspace manifest key `default_package`",
|
|
||||||
)
|
|
||||||
.with_span(line.span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"WorkspaceManifestInvalid",
|
|
||||||
"workspace default_package must be a string",
|
|
||||||
)
|
|
||||||
.with_span(line.span),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
other => errors.push(
|
other => errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&file,
|
&file,
|
||||||
@ -1358,22 +1292,8 @@ fn parse_workspace_manifest(
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut normalized_members = Vec::new();
|
for member in &members {
|
||||||
let mut seen_members = BTreeMap::<String, String>::new();
|
if normalize_workspace_member(member).is_none() {
|
||||||
for member in members {
|
|
||||||
if let Some(normalized) = normalize_workspace_member(&member) {
|
|
||||||
if let Some(original) = seen_members.insert(normalized.clone(), member.clone()) {
|
|
||||||
errors.push(Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"DuplicateWorkspaceMember",
|
|
||||||
format!(
|
|
||||||
"workspace member path `{}` duplicates `{}` after normalization",
|
|
||||||
member, original
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
normalized_members.push(normalized);
|
|
||||||
} else {
|
|
||||||
errors.push(Diagnostic::new(
|
errors.push(Diagnostic::new(
|
||||||
&file,
|
&file,
|
||||||
"WorkspaceMemberPathEscape",
|
"WorkspaceMemberPathEscape",
|
||||||
@ -1384,19 +1304,12 @@ fn parse_workspace_manifest(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
members = normalized_members;
|
members = members
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|member| normalize_workspace_member(&member))
|
||||||
|
.collect();
|
||||||
members.sort();
|
members.sort();
|
||||||
|
|
||||||
if let Some(default_package) = &default_package {
|
|
||||||
if !is_project_name(default_package) {
|
|
||||||
errors.push(Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"WorkspaceManifestInvalid",
|
|
||||||
"workspace default_package must be a package name",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
return Err(errors);
|
return Err(errors);
|
||||||
}
|
}
|
||||||
@ -1412,7 +1325,6 @@ fn parse_workspace_manifest(
|
|||||||
source,
|
source,
|
||||||
root,
|
root,
|
||||||
members,
|
members,
|
||||||
default_package,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1432,7 +1344,6 @@ fn parse_package_manifest(
|
|||||||
let mut source_root = None::<String>;
|
let mut source_root = None::<String>;
|
||||||
let mut entry = None::<String>;
|
let mut entry = None::<String>;
|
||||||
let mut dependencies = Vec::new();
|
let mut dependencies = Vec::new();
|
||||||
let mut dependency_keys = BTreeSet::<String>::new();
|
|
||||||
|
|
||||||
for line in manifest_lines(&source) {
|
for line in manifest_lines(&source) {
|
||||||
let trimmed = line.text.trim();
|
let trimmed = line.text.trim();
|
||||||
@ -1510,8 +1421,6 @@ fn parse_package_manifest(
|
|||||||
parsed,
|
parsed,
|
||||||
line.span,
|
line.span,
|
||||||
"name",
|
"name",
|
||||||
"PackageManifestInvalid",
|
|
||||||
"package",
|
|
||||||
),
|
),
|
||||||
"version" => set_manifest_key(
|
"version" => set_manifest_key(
|
||||||
&file,
|
&file,
|
||||||
@ -1520,8 +1429,6 @@ fn parse_package_manifest(
|
|||||||
parsed,
|
parsed,
|
||||||
line.span,
|
line.span,
|
||||||
"version",
|
"version",
|
||||||
"PackageManifestInvalid",
|
|
||||||
"package",
|
|
||||||
),
|
),
|
||||||
"source_root" => set_manifest_key(
|
"source_root" => set_manifest_key(
|
||||||
&file,
|
&file,
|
||||||
@ -1530,8 +1437,6 @@ fn parse_package_manifest(
|
|||||||
parsed,
|
parsed,
|
||||||
line.span,
|
line.span,
|
||||||
"source_root",
|
"source_root",
|
||||||
"PackageManifestInvalid",
|
|
||||||
"package",
|
|
||||||
),
|
),
|
||||||
"entry" => set_manifest_key(
|
"entry" => set_manifest_key(
|
||||||
&file,
|
&file,
|
||||||
@ -1540,8 +1445,6 @@ fn parse_package_manifest(
|
|||||||
parsed,
|
parsed,
|
||||||
line.span,
|
line.span,
|
||||||
"entry",
|
"entry",
|
||||||
"PackageManifestInvalid",
|
|
||||||
"package",
|
|
||||||
),
|
),
|
||||||
other => errors.push(
|
other => errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
@ -1553,54 +1456,21 @@ fn parse_package_manifest(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"dependencies" => {
|
"dependencies" => match parse_dependency_path(value) {
|
||||||
let valid_key = if is_project_name(key) {
|
Some(path) => dependencies.push(PackageDependency {
|
||||||
true
|
key: key.to_string(),
|
||||||
} else {
|
path,
|
||||||
errors.push(
|
span: line.span,
|
||||||
Diagnostic::new(
|
}),
|
||||||
&file,
|
None => errors.push(
|
||||||
"InvalidPackageDependencyName",
|
Diagnostic::new(
|
||||||
format!(
|
&file,
|
||||||
"package dependency name `{}` must start with `a-z` and contain only `a-z`, `0-9`, and `-`",
|
"UnsupportedDependency",
|
||||||
key
|
"workspace dependencies must use `{ path = \"...\" }` local path records only",
|
||||||
),
|
)
|
||||||
)
|
.with_span(line.span),
|
||||||
.with_span(line.span),
|
),
|
||||||
);
|
},
|
||||||
false
|
|
||||||
};
|
|
||||||
let unique_key = if dependency_keys.insert(key.to_string()) {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"DuplicatePackageDependencyName",
|
|
||||||
format!("duplicate package dependency name `{}`", key),
|
|
||||||
)
|
|
||||||
.with_span(line.span),
|
|
||||||
);
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
match parse_dependency_path(value) {
|
|
||||||
Some(path) if valid_key && unique_key => dependencies.push(PackageDependency {
|
|
||||||
key: key.to_string(),
|
|
||||||
path,
|
|
||||||
span: line.span,
|
|
||||||
}),
|
|
||||||
Some(_) => {}
|
|
||||||
None => errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&file,
|
|
||||||
"UnsupportedDependency",
|
|
||||||
"workspace dependencies must use `{ path = \"...\" }` local path records only",
|
|
||||||
)
|
|
||||||
.with_span(line.span),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"" | "project" => errors.push(
|
"" | "project" => errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&file,
|
&file,
|
||||||
@ -1756,15 +1626,13 @@ fn set_manifest_key(
|
|||||||
value: String,
|
value: String,
|
||||||
span: Span,
|
span: Span,
|
||||||
key: &str,
|
key: &str,
|
||||||
code: &'static str,
|
|
||||||
manifest_kind: &str,
|
|
||||||
) {
|
) {
|
||||||
if slot.replace(value).is_some() {
|
if slot.replace(value).is_some() {
|
||||||
errors.push(
|
errors.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
file,
|
file,
|
||||||
code,
|
"ProjectManifestInvalid",
|
||||||
format!("duplicate {} manifest key `{}`", manifest_kind, key),
|
format!("duplicate project manifest key `{}`", key),
|
||||||
)
|
)
|
||||||
.with_span(span),
|
.with_span(span),
|
||||||
);
|
);
|
||||||
@ -2390,7 +2258,6 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
|
|||||||
errors.append(&mut errs);
|
errors.append(&mut errs);
|
||||||
Program {
|
Program {
|
||||||
module: name.clone(),
|
module: name.clone(),
|
||||||
type_aliases: Vec::new(),
|
|
||||||
enums: Vec::new(),
|
enums: Vec::new(),
|
||||||
structs: Vec::new(),
|
structs: Vec::new(),
|
||||||
c_imports: Vec::new(),
|
c_imports: Vec::new(),
|
||||||
@ -2400,7 +2267,7 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (local_functions, local_structs, local_enums, local_aliases, mut duplicate_errors) =
|
let (local_functions, local_structs, local_enums, mut duplicate_errors) =
|
||||||
local_declarations(&file, &program);
|
local_declarations(&file, &program);
|
||||||
errors.append(&mut duplicate_errors);
|
errors.append(&mut duplicate_errors);
|
||||||
|
|
||||||
@ -2414,7 +2281,6 @@ fn parse_module(path: &Path, file: String, source: String) -> Result<ModuleUnit,
|
|||||||
local_functions,
|
local_functions,
|
||||||
local_structs,
|
local_structs,
|
||||||
local_enums,
|
local_enums,
|
||||||
local_aliases,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(errors)
|
Err(errors)
|
||||||
@ -2709,44 +2575,6 @@ fn external_enum_from_module(module: &ModuleUnit, name: &str) -> Option<External
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn external_enum_from_checked_module(
|
|
||||||
module: &ModuleUnit,
|
|
||||||
checked: &CheckedProgram,
|
|
||||||
name: &str,
|
|
||||||
) -> Option<ExternalEnum> {
|
|
||||||
let source_enum = module
|
|
||||||
.program
|
|
||||||
.enums
|
|
||||||
.iter()
|
|
||||||
.find(|enum_decl| enum_decl.name == name)?;
|
|
||||||
let checked_enum = checked
|
|
||||||
.enums
|
|
||||||
.iter()
|
|
||||||
.find(|enum_decl| enum_decl.name == name)?;
|
|
||||||
|
|
||||||
Some(ExternalEnum {
|
|
||||||
name: name.to_string(),
|
|
||||||
span: source_enum.name_span,
|
|
||||||
variants: checked_enum
|
|
||||||
.variants
|
|
||||||
.iter()
|
|
||||||
.map(|checked_variant| {
|
|
||||||
let source_variant = source_enum
|
|
||||||
.variants
|
|
||||||
.iter()
|
|
||||||
.find(|variant| variant.name == checked_variant.name);
|
|
||||||
ExternalEnumVariant {
|
|
||||||
name: checked_variant.name.clone(),
|
|
||||||
name_span: source_variant
|
|
||||||
.map_or(source_enum.name_span, |variant| variant.name_span),
|
|
||||||
payload_ty: checked_variant.payload_ty.clone(),
|
|
||||||
payload_ty_span: source_variant.and_then(|variant| variant.payload_ty_span),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn local_declarations(
|
fn local_declarations(
|
||||||
file: &str,
|
file: &str,
|
||||||
program: &Program,
|
program: &Program,
|
||||||
@ -2754,44 +2582,14 @@ fn local_declarations(
|
|||||||
HashMap<String, DeclInfo>,
|
HashMap<String, DeclInfo>,
|
||||||
HashMap<String, DeclInfo>,
|
HashMap<String, DeclInfo>,
|
||||||
HashMap<String, DeclInfo>,
|
HashMap<String, DeclInfo>,
|
||||||
HashMap<String, DeclInfo>,
|
|
||||||
Vec<Diagnostic>,
|
Vec<Diagnostic>,
|
||||||
) {
|
) {
|
||||||
let mut all = HashMap::<String, DeclInfo>::new();
|
let mut all = HashMap::<String, DeclInfo>::new();
|
||||||
let mut functions = HashMap::new();
|
let mut functions = HashMap::new();
|
||||||
let mut structs = HashMap::new();
|
let mut structs = HashMap::new();
|
||||||
let mut enums = HashMap::new();
|
let mut enums = HashMap::new();
|
||||||
let mut aliases = HashMap::new();
|
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
|
|
||||||
for alias in &program.type_aliases {
|
|
||||||
let decl = DeclInfo {
|
|
||||||
span: alias.name_span,
|
|
||||||
kind: DeclKind::TypeAlias,
|
|
||||||
};
|
|
||||||
if let Some(original) = all.insert(alias.name.clone(), decl.clone()) {
|
|
||||||
if original.kind == DeclKind::CImport {
|
|
||||||
errors.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"DuplicateTopLevelName",
|
|
||||||
format!("duplicate top-level name `{}`", alias.name),
|
|
||||||
)
|
|
||||||
.with_span(alias.name_span)
|
|
||||||
.related("original declaration", original.span),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
errors.push(duplicate_name(
|
|
||||||
file,
|
|
||||||
&alias.name,
|
|
||||||
alias.name_span,
|
|
||||||
original.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliases.insert(alias.name.clone(), decl);
|
|
||||||
}
|
|
||||||
|
|
||||||
for function in &program.functions {
|
for function in &program.functions {
|
||||||
let decl = DeclInfo {
|
let decl = DeclInfo {
|
||||||
span: function.span,
|
span: function.span,
|
||||||
@ -2923,7 +2721,7 @@ fn local_declarations(
|
|||||||
enums.insert(enum_decl.name.clone(), decl);
|
enums.insert(enum_decl.name.clone(), decl);
|
||||||
}
|
}
|
||||||
|
|
||||||
(functions, structs, enums, aliases, errors)
|
(functions, structs, enums, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_workspace_packages(
|
fn validate_workspace_packages(
|
||||||
@ -2952,18 +2750,6 @@ fn validate_workspace_packages(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(default_package) = &workspace.default_package {
|
|
||||||
if !by_name.contains_key(default_package) {
|
|
||||||
diagnostics.push(Diagnostic::new(
|
|
||||||
&workspace.path.display().to_string(),
|
|
||||||
"WorkspaceDefaultPackageMissing",
|
|
||||||
format!(
|
|
||||||
"workspace default_package `{}` is not a workspace package",
|
|
||||||
default_package
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut resolved_dependencies = vec![Vec::<usize>::new(); packages.len()];
|
let mut resolved_dependencies = vec![Vec::<usize>::new(); packages.len()];
|
||||||
for (package_index, package) in packages.iter().enumerate() {
|
for (package_index, package) in packages.iter().enumerate() {
|
||||||
@ -3184,20 +2970,9 @@ fn resolve_and_check_workspace(
|
|||||||
let mut external_enums = Vec::new();
|
let mut external_enums = Vec::new();
|
||||||
for (name, binding) in imports {
|
for (name, binding) in imports {
|
||||||
let provider = &packages[binding.provider_package].modules[binding.provider_module];
|
let provider = &packages[binding.provider_package].modules[binding.provider_module];
|
||||||
let checked_provider =
|
|
||||||
checked_by_module.get(&(binding.provider_package, provider.name.clone()));
|
|
||||||
match binding.kind {
|
match binding.kind {
|
||||||
DeclKind::Function => {
|
DeclKind::Function => {
|
||||||
if let Some(function) = checked_provider
|
if let Some(function) =
|
||||||
.and_then(|program| program.functions.iter().find(|f| f.name == *name))
|
|
||||||
{
|
|
||||||
external_functions.push(ExternalFunction {
|
|
||||||
name: name.clone(),
|
|
||||||
params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
|
|
||||||
return_type: function.return_type.clone(),
|
|
||||||
foreign: false,
|
|
||||||
});
|
|
||||||
} else if let Some(function) =
|
|
||||||
provider.program.functions.iter().find(|f| f.name == *name)
|
provider.program.functions.iter().find(|f| f.name == *name)
|
||||||
{
|
{
|
||||||
external_functions.push(ExternalFunction {
|
external_functions.push(ExternalFunction {
|
||||||
@ -3209,16 +2984,7 @@ fn resolve_and_check_workspace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::CImport => {
|
DeclKind::CImport => {
|
||||||
if let Some(import) = checked_provider
|
if let Some(import) =
|
||||||
.and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
|
|
||||||
{
|
|
||||||
external_functions.push(ExternalFunction {
|
|
||||||
name: name.clone(),
|
|
||||||
params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
|
|
||||||
return_type: import.return_type.clone(),
|
|
||||||
foreign: true,
|
|
||||||
});
|
|
||||||
} else if let Some(import) =
|
|
||||||
provider.program.c_imports.iter().find(|f| f.name == *name)
|
provider.program.c_imports.iter().find(|f| f.name == *name)
|
||||||
{
|
{
|
||||||
external_functions.push(ExternalFunction {
|
external_functions.push(ExternalFunction {
|
||||||
@ -3230,15 +2996,7 @@ fn resolve_and_check_workspace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::Struct => {
|
DeclKind::Struct => {
|
||||||
if let Some(struct_decl) = checked_provider
|
if let Some(struct_decl) =
|
||||||
.and_then(|program| program.structs.iter().find(|s| s.name == *name))
|
|
||||||
{
|
|
||||||
external_structs.push(ExternalStruct {
|
|
||||||
name: name.clone(),
|
|
||||||
fields: struct_decl.fields.clone(),
|
|
||||||
span: struct_decl.span,
|
|
||||||
});
|
|
||||||
} else if let Some(struct_decl) =
|
|
||||||
provider.program.structs.iter().find(|s| s.name == *name)
|
provider.program.structs.iter().find(|s| s.name == *name)
|
||||||
{
|
{
|
||||||
external_structs.push(ExternalStruct {
|
external_structs.push(ExternalStruct {
|
||||||
@ -3253,16 +3011,10 @@ fn resolve_and_check_workspace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::Enum => {
|
DeclKind::Enum => {
|
||||||
if let Some(enum_decl) = checked_provider
|
if let Some(enum_decl) = external_enum_from_module(provider, name) {
|
||||||
.and_then(|program| {
|
|
||||||
external_enum_from_checked_module(provider, program, name)
|
|
||||||
})
|
|
||||||
.or_else(|| external_enum_from_module(provider, name))
|
|
||||||
{
|
|
||||||
external_enums.push(enum_decl);
|
external_enums.push(enum_decl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::TypeAlias => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3336,9 +3088,9 @@ fn resolve_and_check_workspace(
|
|||||||
|
|
||||||
let mut selected_build_entry_package = None;
|
let mut selected_build_entry_package = None;
|
||||||
if require_entry_wrapper {
|
if require_entry_wrapper {
|
||||||
match select_workspace_build_entry(&workspace, &packages, &module_maps) {
|
selected_build_entry_package = select_workspace_build_entry(&packages, &module_maps);
|
||||||
Ok(package_index) => {
|
match selected_build_entry_package {
|
||||||
selected_build_entry_package = Some(package_index);
|
Some(package_index) => {
|
||||||
add_workspace_entry_wrapper(
|
add_workspace_entry_wrapper(
|
||||||
&packages,
|
&packages,
|
||||||
package_index,
|
package_index,
|
||||||
@ -3347,7 +3099,11 @@ fn resolve_and_check_workspace(
|
|||||||
&mut diagnostics,
|
&mut diagnostics,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(diagnostic) => diagnostics.push(diagnostic),
|
None => diagnostics.push(Diagnostic::new(
|
||||||
|
&workspace.path.display().to_string(),
|
||||||
|
"WorkspaceBuildAmbiguousEntryPackage",
|
||||||
|
"workspace build requires exactly one package with its entry module",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
if !diagnostics.is_empty() {
|
if !diagnostics.is_empty() {
|
||||||
return Err(ProjectLoadFailure {
|
return Err(ProjectLoadFailure {
|
||||||
@ -3433,19 +3189,9 @@ fn resolve_and_check(
|
|||||||
let mut external_enums = Vec::new();
|
let mut external_enums = Vec::new();
|
||||||
for (name, binding) in imports {
|
for (name, binding) in imports {
|
||||||
let provider = &modules[by_name[&binding.provider]];
|
let provider = &modules[by_name[&binding.provider]];
|
||||||
let checked_provider = checked_by_module.get(&binding.provider);
|
|
||||||
match binding.kind {
|
match binding.kind {
|
||||||
DeclKind::Function => {
|
DeclKind::Function => {
|
||||||
if let Some(function) = checked_provider
|
if let Some(function) =
|
||||||
.and_then(|program| program.functions.iter().find(|f| f.name == *name))
|
|
||||||
{
|
|
||||||
external_functions.push(ExternalFunction {
|
|
||||||
name: name.clone(),
|
|
||||||
params: function.params.iter().map(|(_, ty)| ty.clone()).collect(),
|
|
||||||
return_type: function.return_type.clone(),
|
|
||||||
foreign: false,
|
|
||||||
});
|
|
||||||
} else if let Some(function) =
|
|
||||||
provider.program.functions.iter().find(|f| f.name == *name)
|
provider.program.functions.iter().find(|f| f.name == *name)
|
||||||
{
|
{
|
||||||
external_functions.push(ExternalFunction {
|
external_functions.push(ExternalFunction {
|
||||||
@ -3457,16 +3203,7 @@ fn resolve_and_check(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::CImport => {
|
DeclKind::CImport => {
|
||||||
if let Some(import) = checked_provider
|
if let Some(import) =
|
||||||
.and_then(|program| program.c_imports.iter().find(|f| f.name == *name))
|
|
||||||
{
|
|
||||||
external_functions.push(ExternalFunction {
|
|
||||||
name: name.clone(),
|
|
||||||
params: import.params.iter().map(|(_, ty)| ty.clone()).collect(),
|
|
||||||
return_type: import.return_type.clone(),
|
|
||||||
foreign: true,
|
|
||||||
});
|
|
||||||
} else if let Some(import) =
|
|
||||||
provider.program.c_imports.iter().find(|f| f.name == *name)
|
provider.program.c_imports.iter().find(|f| f.name == *name)
|
||||||
{
|
{
|
||||||
external_functions.push(ExternalFunction {
|
external_functions.push(ExternalFunction {
|
||||||
@ -3478,15 +3215,7 @@ fn resolve_and_check(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::Struct => {
|
DeclKind::Struct => {
|
||||||
if let Some(struct_decl) = checked_provider
|
if let Some(struct_decl) =
|
||||||
.and_then(|program| program.structs.iter().find(|s| s.name == *name))
|
|
||||||
{
|
|
||||||
external_structs.push(ExternalStruct {
|
|
||||||
name: name.clone(),
|
|
||||||
fields: struct_decl.fields.clone(),
|
|
||||||
span: struct_decl.span,
|
|
||||||
});
|
|
||||||
} else if let Some(struct_decl) =
|
|
||||||
provider.program.structs.iter().find(|s| s.name == *name)
|
provider.program.structs.iter().find(|s| s.name == *name)
|
||||||
{
|
{
|
||||||
external_structs.push(ExternalStruct {
|
external_structs.push(ExternalStruct {
|
||||||
@ -3501,16 +3230,10 @@ fn resolve_and_check(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::Enum => {
|
DeclKind::Enum => {
|
||||||
if let Some(enum_decl) = checked_provider
|
if let Some(enum_decl) = external_enum_from_module(provider, name) {
|
||||||
.and_then(|program| {
|
|
||||||
external_enum_from_checked_module(provider, program, name)
|
|
||||||
})
|
|
||||||
.or_else(|| external_enum_from_module(provider, name))
|
|
||||||
{
|
|
||||||
external_enums.push(enum_decl);
|
external_enums.push(enum_decl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeclKind::TypeAlias => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3604,22 +3327,6 @@ fn build_export_maps(
|
|||||||
Some(kind) => {
|
Some(kind) => {
|
||||||
exports.insert(export.name.clone(), kind);
|
exports.insert(export.name.clone(), kind);
|
||||||
}
|
}
|
||||||
None if module.local_aliases.contains_key(&export.name) => diagnostics.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&module.file,
|
|
||||||
"Visibility",
|
|
||||||
format!(
|
|
||||||
"type alias `{}` is module-local and cannot be exported",
|
|
||||||
export.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(export.span)
|
|
||||||
.related(
|
|
||||||
"type alias declaration",
|
|
||||||
module.local_aliases[&export.name].span,
|
|
||||||
)
|
|
||||||
.hint("export functions, structs, or enums; imported signatures see concrete target types"),
|
|
||||||
),
|
|
||||||
None => diagnostics.push(
|
None => diagnostics.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&module.file,
|
&module.file,
|
||||||
@ -3667,7 +3374,6 @@ fn resolve_imports(
|
|||||||
.get(&name.name)
|
.get(&name.name)
|
||||||
.or_else(|| module.local_structs.get(&name.name))
|
.or_else(|| module.local_structs.get(&name.name))
|
||||||
.or_else(|| module.local_enums.get(&name.name))
|
.or_else(|| module.local_enums.get(&name.name))
|
||||||
.or_else(|| module.local_aliases.get(&name.name))
|
|
||||||
{
|
{
|
||||||
diagnostics.push(duplicate_name(
|
diagnostics.push(duplicate_name(
|
||||||
&module.file,
|
&module.file,
|
||||||
@ -3710,8 +3416,7 @@ fn resolve_imports(
|
|||||||
.local_functions
|
.local_functions
|
||||||
.get(&name.name)
|
.get(&name.name)
|
||||||
.or_else(|| provider.local_structs.get(&name.name))
|
.or_else(|| provider.local_structs.get(&name.name))
|
||||||
.or_else(|| provider.local_enums.get(&name.name))
|
.or_else(|| provider.local_enums.get(&name.name));
|
||||||
.or_else(|| provider.local_aliases.get(&name.name));
|
|
||||||
let exported = export_maps[provider_index].get(&name.name).copied();
|
let exported = export_maps[provider_index].get(&name.name).copied();
|
||||||
match (provider_local, exported) {
|
match (provider_local, exported) {
|
||||||
(_, Some(kind)) => {
|
(_, Some(kind)) => {
|
||||||
@ -3724,23 +3429,6 @@ fn resolve_imports(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(Some(decl), None) if decl.kind == DeclKind::TypeAlias => diagnostics.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&module.file,
|
|
||||||
"Visibility",
|
|
||||||
format!(
|
|
||||||
"type alias `{}` is module-local and cannot be imported from module `{}`",
|
|
||||||
name.name, provider.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(name.span)
|
|
||||||
.related_in_file(
|
|
||||||
provider.file.clone(),
|
|
||||||
"type alias declaration",
|
|
||||||
decl.span,
|
|
||||||
)
|
|
||||||
.hint("import functions, structs, or enums; function signatures expose the alias target type"),
|
|
||||||
),
|
|
||||||
(Some(decl), None) => diagnostics.push(
|
(Some(decl), None) => diagnostics.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&module.file,
|
&module.file,
|
||||||
@ -3815,14 +3503,13 @@ fn resolve_workspace_imports(
|
|||||||
&packages[provider_package_index].modules[provider_module_index];
|
&packages[provider_package_index].modules[provider_module_index];
|
||||||
for name in &import.names {
|
for name in &import.names {
|
||||||
if let Some(local) = module
|
if let Some(local) = module
|
||||||
.local_functions
|
.local_functions
|
||||||
.get(&name.name)
|
.get(&name.name)
|
||||||
.or_else(|| module.local_structs.get(&name.name))
|
.or_else(|| module.local_structs.get(&name.name))
|
||||||
.or_else(|| module.local_enums.get(&name.name))
|
.or_else(|| module.local_enums.get(&name.name))
|
||||||
.or_else(|| module.local_aliases.get(&name.name))
|
{
|
||||||
{
|
diagnostics.push(duplicate_name(
|
||||||
diagnostics.push(duplicate_name(
|
&module.file,
|
||||||
&module.file,
|
|
||||||
&name.name,
|
&name.name,
|
||||||
name.span,
|
name.span,
|
||||||
local.span,
|
local.span,
|
||||||
@ -3867,8 +3554,7 @@ fn resolve_workspace_imports(
|
|||||||
.local_functions
|
.local_functions
|
||||||
.get(&name.name)
|
.get(&name.name)
|
||||||
.or_else(|| provider.local_structs.get(&name.name))
|
.or_else(|| provider.local_structs.get(&name.name))
|
||||||
.or_else(|| provider.local_enums.get(&name.name))
|
.or_else(|| provider.local_enums.get(&name.name));
|
||||||
.or_else(|| provider.local_aliases.get(&name.name));
|
|
||||||
let exported = export_maps[provider_package_index]
|
let exported = export_maps[provider_package_index]
|
||||||
[provider_module_index]
|
[provider_module_index]
|
||||||
.get(&name.name)
|
.get(&name.name)
|
||||||
@ -3885,25 +3571,6 @@ fn resolve_workspace_imports(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
(Some(decl), None) if decl.kind == DeclKind::TypeAlias => {
|
|
||||||
diagnostics.push(
|
|
||||||
Diagnostic::new(
|
|
||||||
&module.file,
|
|
||||||
"Visibility",
|
|
||||||
format!(
|
|
||||||
"type alias `{}` is module-local and cannot be imported from module `{}`",
|
|
||||||
name.name, import.module
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(name.span)
|
|
||||||
.related_in_file(
|
|
||||||
provider.file.clone(),
|
|
||||||
"type alias declaration",
|
|
||||||
decl.span,
|
|
||||||
)
|
|
||||||
.hint("import functions, structs, or enums; function signatures expose the alias target type"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
(Some(decl), None) => diagnostics.push(
|
(Some(decl), None) => diagnostics.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&module.file,
|
&module.file,
|
||||||
@ -4489,11 +4156,8 @@ fn add_entry_wrapper(
|
|||||||
else {
|
else {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
&entry_module.file,
|
&entry_module.file,
|
||||||
"ProjectEntryMainMissing",
|
"MissingImport",
|
||||||
format!(
|
format!("entry module `{}` has no `main` function", manifest.entry),
|
||||||
"entry module `{}` has no `main` function; build/run require `(fn main () -> i32 ...)`",
|
|
||||||
manifest.entry
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -4502,12 +4166,8 @@ fn add_entry_wrapper(
|
|||||||
diagnostics.push(
|
diagnostics.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&entry_module.file,
|
&entry_module.file,
|
||||||
"ProjectEntryMainInvalidSignature",
|
"MissingImport",
|
||||||
format!(
|
"project entry `main` must have no parameters and return `i32`",
|
||||||
"project entry `main` must have signature `(fn main () -> i32 ...)`; found {} parameter(s) and return `{}`",
|
|
||||||
entry_function.params.len(),
|
|
||||||
entry_function.return_type
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_span(entry_function.span),
|
.with_span(entry_function.span),
|
||||||
);
|
);
|
||||||
@ -4533,38 +4193,9 @@ fn add_entry_wrapper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_workspace_build_entry(
|
fn select_workspace_build_entry(
|
||||||
workspace: &WorkspaceManifest,
|
|
||||||
packages: &[PackageUnit],
|
packages: &[PackageUnit],
|
||||||
module_maps: &[HashMap<String, usize>],
|
module_maps: &[HashMap<String, usize>],
|
||||||
) -> Result<usize, Diagnostic> {
|
) -> Option<usize> {
|
||||||
if let Some(default_package) = &workspace.default_package {
|
|
||||||
let Some(package_index) = packages
|
|
||||||
.iter()
|
|
||||||
.position(|package| package.manifest.name == *default_package)
|
|
||||||
else {
|
|
||||||
return Err(Diagnostic::new(
|
|
||||||
&workspace.path.display().to_string(),
|
|
||||||
"WorkspaceDefaultPackageMissing",
|
|
||||||
format!(
|
|
||||||
"workspace default_package `{}` is not a workspace package",
|
|
||||||
default_package
|
|
||||||
),
|
|
||||||
));
|
|
||||||
};
|
|
||||||
let package = &packages[package_index];
|
|
||||||
if module_maps[package_index].contains_key(&package.manifest.entry) {
|
|
||||||
return Ok(package_index);
|
|
||||||
}
|
|
||||||
return Err(Diagnostic::new(
|
|
||||||
&package.manifest.path.display().to_string(),
|
|
||||||
"WorkspaceDefaultPackageEntryMissing",
|
|
||||||
format!(
|
|
||||||
"workspace default_package `{}` has no entry module `{}`; build/run require one selected package entry module",
|
|
||||||
default_package, package.manifest.entry
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let candidates = packages
|
let candidates = packages
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -4572,13 +4203,9 @@ fn select_workspace_build_entry(
|
|||||||
.map(|(index, _)| index)
|
.map(|(index, _)| index)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
if candidates.len() == 1 {
|
if candidates.len() == 1 {
|
||||||
Ok(candidates[0])
|
candidates.first().copied()
|
||||||
} else {
|
} else {
|
||||||
Err(Diagnostic::new(
|
None
|
||||||
&workspace.path.display().to_string(),
|
|
||||||
"WorkspaceBuildAmbiguousEntryPackage",
|
|
||||||
"workspace build requires exactly one package with its entry module or `[workspace] default_package = \"name\"`",
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4602,9 +4229,9 @@ fn add_workspace_entry_wrapper(
|
|||||||
else {
|
else {
|
||||||
diagnostics.push(Diagnostic::new(
|
diagnostics.push(Diagnostic::new(
|
||||||
&entry_module.file,
|
&entry_module.file,
|
||||||
"WorkspaceEntryMainMissing",
|
"MissingImport",
|
||||||
format!(
|
format!(
|
||||||
"entry module `{}` in package `{}` has no `main` function; build/run require `(fn main () -> i32 ...)`",
|
"entry module `{}` in package `{}` has no `main` function",
|
||||||
package.manifest.entry, package.manifest.name
|
package.manifest.entry, package.manifest.name
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@ -4615,12 +4242,8 @@ fn add_workspace_entry_wrapper(
|
|||||||
diagnostics.push(
|
diagnostics.push(
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
&entry_module.file,
|
&entry_module.file,
|
||||||
"WorkspaceEntryMainInvalidSignature",
|
"MissingImport",
|
||||||
format!(
|
"workspace entry `main` must have no parameters and return `i32`",
|
||||||
"workspace entry `main` must have signature `(fn main () -> i32 ...)`; found {} parameter(s) and return `{}`",
|
|
||||||
entry_function.params.len(),
|
|
||||||
entry_function.return_type
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_span(entry_function.span),
|
.with_span(entry_function.span),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,163 +0,0 @@
|
|||||||
use crate::{
|
|
||||||
diag::Diagnostic,
|
|
||||||
sexpr::{Atom, SExpr, SExprKind},
|
|
||||||
token::Span,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CURRENT_BETA_UNSUPPORTED: &str = "reserved but not supported in the current beta";
|
|
||||||
|
|
||||||
pub(crate) fn unsupported_reserved_type_diagnostic(file: &str, form: &SExpr) -> Option<Diagnostic> {
|
|
||||||
if let Some(name) = expect_ident(form) {
|
|
||||||
if is_generic_type_parameter_name(name) {
|
|
||||||
return Some(unsupported_generic_type_parameter(file, form.span, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let items = expect_list(form)?;
|
|
||||||
let head = items.first().and_then(expect_ident)?;
|
|
||||||
match head {
|
|
||||||
"map" => Some(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedMapType",
|
|
||||||
format!("`map` types are {}", CURRENT_BETA_UNSUPPORTED),
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("supported concrete type")
|
|
||||||
.found(render_type_form(form))
|
|
||||||
.hint(
|
|
||||||
"use current concrete arrays, vectors, option/result, structs, enums, or scalars",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"set" => Some(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedSetType",
|
|
||||||
format!("`set` types are {}", CURRENT_BETA_UNSUPPORTED),
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("supported concrete type")
|
|
||||||
.found(render_type_form(form))
|
|
||||||
.hint(
|
|
||||||
"use current concrete arrays, vectors, option/result, structs, enums, or scalars",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"vec" if items.len() != 2 => Some(
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedGenericTypeParameter",
|
|
||||||
format!("generic vector syntax is {}", CURRENT_BETA_UNSUPPORTED),
|
|
||||||
)
|
|
||||||
.with_span(form.span)
|
|
||||||
.expected("(vec i32), (vec i64), (vec f64), (vec bool), or (vec string)")
|
|
||||||
.found(render_type_form(form))
|
|
||||||
.hint("choose one current concrete vector family explicitly"),
|
|
||||||
),
|
|
||||||
_ => items
|
|
||||||
.iter()
|
|
||||||
.find_map(|item| unsupported_reserved_type_diagnostic(file, item)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn unsupported_generic_function(file: &str, span: Span) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedGenericFunction",
|
|
||||||
format!(
|
|
||||||
"generic function declarations are {}",
|
|
||||||
CURRENT_BETA_UNSUPPORTED
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("(fn name ((arg ConcreteType) ...) -> ConcreteType body...)")
|
|
||||||
.found("(type_params ...)")
|
|
||||||
.hint("write a concrete function for each currently supported type family")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn unsupported_generic_type_alias(file: &str, span: Span) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedGenericTypeAlias",
|
|
||||||
format!(
|
|
||||||
"parameterized type aliases are {}",
|
|
||||||
CURRENT_BETA_UNSUPPORTED
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("(type Alias ConcreteType)")
|
|
||||||
.found("(type_params ...)")
|
|
||||||
.hint("aliases remain transparent names for concrete supported target types")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn unsupported_generic_type_parameter(file: &str, span: Span, name: &str) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedGenericTypeParameter",
|
|
||||||
format!(
|
|
||||||
"generic type parameter `{}` is {}",
|
|
||||||
name, CURRENT_BETA_UNSUPPORTED
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("concrete supported type")
|
|
||||||
.found(name.to_string())
|
|
||||||
.hint("use a concrete promoted type such as `i32`, `string`, or `(vec i32)`")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn unsupported_generic_standard_library_call(
|
|
||||||
file: &str,
|
|
||||||
span: Span,
|
|
||||||
name: &str,
|
|
||||||
) -> Diagnostic {
|
|
||||||
Diagnostic::new(
|
|
||||||
file,
|
|
||||||
"UnsupportedGenericStandardLibraryCall",
|
|
||||||
format!(
|
|
||||||
"generic standard-library call `{}` is {}",
|
|
||||||
name, CURRENT_BETA_UNSUPPORTED
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_span(span)
|
|
||||||
.expected("promoted concrete standard-library function")
|
|
||||||
.found(name.to_string())
|
|
||||||
.hint("use current concrete families such as `std.vec.i32.empty`")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_unsupported_generic_standard_library_call(name: &str) -> bool {
|
|
||||||
matches!(name, "std.vec.empty" | "std.result.map")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_generic_type_parameter_name(name: &str) -> bool {
|
|
||||||
name.len() == 1 && name.bytes().all(|byte| byte.is_ascii_uppercase())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_list(expr: &SExpr) -> Option<&[SExpr]> {
|
|
||||||
match &expr.kind {
|
|
||||||
SExprKind::List(items) => Some(items),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_ident(expr: &SExpr) -> Option<&str> {
|
|
||||||
match &expr.kind {
|
|
||||||
SExprKind::Atom(Atom::Ident(name)) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_type_form(form: &SExpr) -> String {
|
|
||||||
match &form.kind {
|
|
||||||
SExprKind::Atom(Atom::Ident(name)) => name.clone(),
|
|
||||||
SExprKind::Atom(Atom::Int(value)) => value.to_string(),
|
|
||||||
SExprKind::Atom(Atom::I64(value)) => format!("{}i64", value),
|
|
||||||
SExprKind::Atom(Atom::U32(value)) => format!("{}u32", value),
|
|
||||||
SExprKind::Atom(Atom::U64(value)) => format!("{}u64", value),
|
|
||||||
SExprKind::Atom(Atom::Float(value)) => value.to_string(),
|
|
||||||
SExprKind::Atom(Atom::String(value)) => format!("{:?}", value),
|
|
||||||
SExprKind::Atom(Atom::Arrow) => "->".to_string(),
|
|
||||||
SExprKind::List(items) => {
|
|
||||||
let parts = items.iter().map(render_type_form).collect::<Vec<_>>();
|
|
||||||
format!("({})", parts.join(" "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,29 +5,7 @@ use std::{
|
|||||||
|
|
||||||
use crate::diag::Diagnostic;
|
use crate::diag::Diagnostic;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
pub fn create_project(target: &str, explicit_name: Option<&str>) -> Result<(), Diagnostic> {
|
||||||
pub enum ProjectTemplate {
|
|
||||||
Binary,
|
|
||||||
Library,
|
|
||||||
Workspace,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectTemplate {
|
|
||||||
pub fn parse(value: &str) -> Option<Self> {
|
|
||||||
match value {
|
|
||||||
"binary" => Some(Self::Binary),
|
|
||||||
"library" => Some(Self::Library),
|
|
||||||
"workspace" => Some(Self::Workspace),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_project(
|
|
||||||
target: &str,
|
|
||||||
explicit_name: Option<&str>,
|
|
||||||
template: ProjectTemplate,
|
|
||||||
) -> Result<(), Diagnostic> {
|
|
||||||
if target.trim().is_empty() {
|
if target.trim().is_empty() {
|
||||||
return Err(Diagnostic::new(
|
return Err(Diagnostic::new(
|
||||||
target,
|
target,
|
||||||
@ -71,14 +49,6 @@ pub fn create_project(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
match template {
|
|
||||||
ProjectTemplate::Binary => create_binary_project(root, target, &name),
|
|
||||||
ProjectTemplate::Library => create_library_project(root, target, &name),
|
|
||||||
ProjectTemplate::Workspace => create_workspace_project(root, target),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_binary_project(root: &Path, target: &str, name: &str) -> Result<(), Diagnostic> {
|
|
||||||
let src = root.join("src");
|
let src = root.join("src");
|
||||||
create_dir_all_checked(&src, target)?;
|
create_dir_all_checked(&src, target)?;
|
||||||
write_checked(
|
write_checked(
|
||||||
@ -96,56 +66,6 @@ fn create_binary_project(root: &Path, target: &str, name: &str) -> Result<(), Di
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_library_project(root: &Path, target: &str, name: &str) -> Result<(), Diagnostic> {
|
|
||||||
let src = root.join("src");
|
|
||||||
create_dir_all_checked(&src, target)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("slovo.toml"),
|
|
||||||
&format!(
|
|
||||||
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"lib\"\n",
|
|
||||||
name
|
|
||||||
),
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
write_checked(
|
|
||||||
&src.join("lib.slo"),
|
|
||||||
"(module lib (export answer double))\n\n(fn answer () -> i32\n 42)\n\n(fn double ((value i32)) -> i32\n (+ value value))\n\n(test \"answer is stable\"\n (= (answer) 42))\n\n(test \"double works\"\n (= (double 21) 42))\n",
|
|
||||||
target,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_workspace_project(root: &Path, target: &str) -> Result<(), Diagnostic> {
|
|
||||||
let app_src = root.join("packages/app/src");
|
|
||||||
let lib_src = root.join("packages/libutil/src");
|
|
||||||
create_dir_all_checked(&app_src, target)?;
|
|
||||||
create_dir_all_checked(&lib_src, target)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("slovo.toml"),
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\ndefault_package = \"app\"\n",
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("packages/libutil/slovo.toml"),
|
|
||||||
"[package]\nname = \"libutil\"\nversion = \"0.1.0\"\n",
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("packages/libutil/src/libutil.slo"),
|
|
||||||
"(module libutil (export answer label))\n\n(fn answer () -> i32\n 42)\n\n(fn label () -> string\n \"libutil\")\n\n(test \"answer is stable\"\n (= (answer) 42))\n",
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("packages/app/slovo.toml"),
|
|
||||||
"[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nlibutil = { path = \"../libutil\" }\n",
|
|
||||||
target,
|
|
||||||
)?;
|
|
||||||
write_checked(
|
|
||||||
&root.join("packages/app/src/main.slo"),
|
|
||||||
"(module main)\n\n(import libutil.libutil (answer label))\n\n(fn main () -> i32\n (if (= (answer) 42)\n 0\n 1))\n\n(test \"app uses libutil\"\n (if (= (label) \"libutil\")\n (= (answer) 42)\n false))\n",
|
|
||||||
target,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_entries(path: &Path) -> Result<bool, Diagnostic> {
|
fn has_entries(path: &Path) -> Result<bool, Diagnostic> {
|
||||||
let mut entries = fs::read_dir(path).map_err(|err| io_diagnostic(path, err))?;
|
let mut entries = fs::read_dir(path).map_err(|err| io_diagnostic(path, err))?;
|
||||||
match entries.next() {
|
match entries.next() {
|
||||||
|
|||||||
@ -68,10 +68,6 @@ const F64_PARAM: &[RuntimeType] = &[RuntimeType::F64];
|
|||||||
const BOOL_PARAM: &[RuntimeType] = &[RuntimeType::Bool];
|
const BOOL_PARAM: &[RuntimeType] = &[RuntimeType::Bool];
|
||||||
const STRING_PARAM: &[RuntimeType] = &[RuntimeType::String];
|
const STRING_PARAM: &[RuntimeType] = &[RuntimeType::String];
|
||||||
const STRING_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::String, RuntimeType::String];
|
const STRING_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::String, RuntimeType::String];
|
||||||
const STRING_I32_PARAMS: &[RuntimeType] = &[RuntimeType::String, RuntimeType::I32];
|
|
||||||
const STRING_I32_I32_PARAMS: &[RuntimeType] =
|
|
||||||
&[RuntimeType::String, RuntimeType::I32, RuntimeType::I32];
|
|
||||||
const I32_STRING_PARAMS: &[RuntimeType] = &[RuntimeType::I32, RuntimeType::String];
|
|
||||||
const VEC_I32_PARAM: &[RuntimeType] = &[RuntimeType::VecI32];
|
const VEC_I32_PARAM: &[RuntimeType] = &[RuntimeType::VecI32];
|
||||||
const VEC_I32_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecI32, RuntimeType::I32];
|
const VEC_I32_I32_PARAMS: &[RuntimeType] = &[RuntimeType::VecI32, RuntimeType::I32];
|
||||||
const VEC_I64_PARAM: &[RuntimeType] = &[RuntimeType::VecI64];
|
const VEC_I64_PARAM: &[RuntimeType] = &[RuntimeType::VecI64];
|
||||||
@ -179,34 +175,6 @@ pub const FUNCTIONS: &[RuntimeFunction] = &[
|
|||||||
return_type: RuntimeType::String,
|
return_type: RuntimeType::String,
|
||||||
promoted: true,
|
promoted: true,
|
||||||
},
|
},
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.string.byte_at_result",
|
|
||||||
runtime_symbol: "__glagol_string_byte_at_result",
|
|
||||||
params: STRING_I32_PARAMS,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.string.slice_result",
|
|
||||||
runtime_symbol: "__glagol_string_slice_result",
|
|
||||||
params: STRING_I32_I32_PARAMS,
|
|
||||||
return_type: RuntimeType::ResultStringI32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.string.starts_with",
|
|
||||||
runtime_symbol: "__glagol_string_starts_with",
|
|
||||||
params: STRING_STRING_PARAMS,
|
|
||||||
return_type: RuntimeType::Bool,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.string.ends_with",
|
|
||||||
runtime_symbol: "__glagol_string_ends_with",
|
|
||||||
params: STRING_STRING_PARAMS,
|
|
||||||
return_type: RuntimeType::Bool,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
RuntimeFunction {
|
||||||
source_name: "std.string.parse_i32_result",
|
source_name: "std.string.parse_i32_result",
|
||||||
runtime_symbol: "__glagol_string_parse_i32_result",
|
runtime_symbol: "__glagol_string_parse_i32_result",
|
||||||
@ -249,62 +217,6 @@ pub const FUNCTIONS: &[RuntimeFunction] = &[
|
|||||||
return_type: RuntimeType::ResultBoolI32,
|
return_type: RuntimeType::ResultBoolI32,
|
||||||
promoted: true,
|
promoted: true,
|
||||||
},
|
},
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.quote_string",
|
|
||||||
runtime_symbol: "__glagol_json_quote_string",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::String,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_string_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_string_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultStringI32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_bool_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_bool_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultBoolI32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_i32_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_i32_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_u32_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_u32_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultU32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_i64_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_i64_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI64I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_u64_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_u64_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultU64I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.json.parse_f64_value_result",
|
|
||||||
runtime_symbol: "__glagol_json_parse_f64_value_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultF64I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
RuntimeFunction {
|
||||||
source_name: "std.io.eprint",
|
source_name: "std.io.eprint",
|
||||||
runtime_symbol: "__glagol_io_eprint",
|
runtime_symbol: "__glagol_io_eprint",
|
||||||
@ -382,111 +294,6 @@ pub const FUNCTIONS: &[RuntimeFunction] = &[
|
|||||||
return_type: RuntimeType::ResultI32I32,
|
return_type: RuntimeType::ResultI32I32,
|
||||||
promoted: true,
|
promoted: true,
|
||||||
},
|
},
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.exists",
|
|
||||||
runtime_symbol: "__glagol_fs_exists",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::Bool,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.is_file",
|
|
||||||
runtime_symbol: "__glagol_fs_is_file",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::Bool,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.is_dir",
|
|
||||||
runtime_symbol: "__glagol_fs_is_dir",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::Bool,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.remove_file_result",
|
|
||||||
runtime_symbol: "__glagol_fs_remove_file_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.create_dir_result",
|
|
||||||
runtime_symbol: "__glagol_fs_create_dir_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.open_text_read_result",
|
|
||||||
runtime_symbol: "__glagol_fs_open_text_read_result",
|
|
||||||
params: STRING_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.read_open_text_result",
|
|
||||||
runtime_symbol: "__glagol_fs_read_open_text_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultStringI32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.fs.close_result",
|
|
||||||
runtime_symbol: "__glagol_fs_close_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_connect_loopback_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_connect_loopback_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_listen_loopback_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_listen_loopback_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_bound_port_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_bound_port_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_accept_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_accept_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_read_all_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_read_all_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultStringI32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_write_text_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_write_text_result",
|
|
||||||
params: I32_STRING_PARAMS,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
|
||||||
source_name: "std.net.tcp_close_result",
|
|
||||||
runtime_symbol: "__glagol_net_tcp_close_result",
|
|
||||||
params: I32_PARAM,
|
|
||||||
return_type: RuntimeType::ResultI32I32,
|
|
||||||
promoted: true,
|
|
||||||
},
|
|
||||||
RuntimeFunction {
|
RuntimeFunction {
|
||||||
source_name: "std.vec.i32.empty",
|
source_name: "std.vec.i32.empty",
|
||||||
runtime_symbol: "__glagol_vec_i32_empty",
|
runtime_symbol: "__glagol_vec_i32_empty",
|
||||||
@ -794,21 +601,6 @@ const RESERVED_HELPER_SYMBOLS: &[&str] = &[
|
|||||||
"__glagol_fs_read_trap",
|
"__glagol_fs_read_trap",
|
||||||
"__glagol_fs_read_text_result",
|
"__glagol_fs_read_text_result",
|
||||||
"__glagol_fs_write_text_result",
|
"__glagol_fs_write_text_result",
|
||||||
"__glagol_fs_exists",
|
|
||||||
"__glagol_fs_is_file",
|
|
||||||
"__glagol_fs_is_dir",
|
|
||||||
"__glagol_fs_remove_file_result",
|
|
||||||
"__glagol_fs_create_dir_result",
|
|
||||||
"__glagol_fs_open_text_read_result",
|
|
||||||
"__glagol_fs_read_open_text_result",
|
|
||||||
"__glagol_fs_close_result",
|
|
||||||
"__glagol_net_tcp_connect_loopback_result",
|
|
||||||
"__glagol_net_tcp_listen_loopback_result",
|
|
||||||
"__glagol_net_tcp_bound_port_result",
|
|
||||||
"__glagol_net_tcp_accept_result",
|
|
||||||
"__glagol_net_tcp_read_all_result",
|
|
||||||
"__glagol_net_tcp_write_text_result",
|
|
||||||
"__glagol_net_tcp_close_result",
|
|
||||||
"__glagol_vec_i32_eq",
|
"__glagol_vec_i32_eq",
|
||||||
"__glagol_vec_i32_allocation_trap",
|
"__glagol_vec_i32_allocation_trap",
|
||||||
"__glagol_vec_i32_index_trap",
|
"__glagol_vec_i32_index_trap",
|
||||||
@ -827,18 +619,6 @@ const RESERVED_HELPER_SYMBOLS: &[&str] = &[
|
|||||||
"__glagol_string_parse_u64_result",
|
"__glagol_string_parse_u64_result",
|
||||||
"__glagol_string_parse_f64_result",
|
"__glagol_string_parse_f64_result",
|
||||||
"__glagol_string_parse_bool_result",
|
"__glagol_string_parse_bool_result",
|
||||||
"__glagol_string_byte_at_result",
|
|
||||||
"__glagol_string_slice_result",
|
|
||||||
"__glagol_string_starts_with",
|
|
||||||
"__glagol_string_ends_with",
|
|
||||||
"__glagol_json_quote_string",
|
|
||||||
"__glagol_json_parse_string_value_result",
|
|
||||||
"__glagol_json_parse_bool_value_result",
|
|
||||||
"__glagol_json_parse_i32_value_result",
|
|
||||||
"__glagol_json_parse_u32_value_result",
|
|
||||||
"__glagol_json_parse_i64_value_result",
|
|
||||||
"__glagol_json_parse_u64_value_result",
|
|
||||||
"__glagol_json_parse_f64_value_result",
|
|
||||||
"__glagol_num_u32_to_string",
|
"__glagol_num_u32_to_string",
|
||||||
"__glagol_num_u64_to_string",
|
"__glagol_num_u64_to_string",
|
||||||
"__glagol_num_f64_to_string",
|
"__glagol_num_f64_to_string",
|
||||||
@ -880,7 +660,7 @@ pub fn unsupported_standard_library_call(file: &str, span: Span, source_name: &s
|
|||||||
format!("standard library call `{}` is not supported", source_name),
|
format!("standard library call `{}` is not supported", source_name),
|
||||||
)
|
)
|
||||||
.with_span(span)
|
.with_span(span)
|
||||||
.expected("std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.byte_at_result, std.string.slice_result, std.string.starts_with, std.string.ends_with, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.json.quote_string, std.json.parse_string_value_result, std.json.parse_bool_value_result, std.json.parse_i32_value_result, std.json.parse_u32_value_result, std.json.parse_i64_value_result, std.json.parse_u64_value_result, std.json.parse_f64_value_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.fs.exists, std.fs.is_file, std.fs.is_dir, std.fs.remove_file_result, std.fs.create_dir_result, std.fs.open_text_read_result, std.fs.read_open_text_result, std.fs.close_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err")
|
.expected("std.io.print_i32, std.io.print_u32, std.io.print_i64, std.io.print_u64, std.io.print_f64, std.io.print_string, std.io.print_bool, std.io.eprint, std.io.read_stdin_result, std.string.len, std.string.concat, std.string.parse_i32_result, std.string.parse_u32_result, std.string.parse_i64_result, std.string.parse_u64_result, std.string.parse_f64_result, std.string.parse_bool_result, std.process.argc, std.process.arg, std.process.arg_result, std.env.get, std.env.get_result, std.fs.read_text, std.fs.read_text_result, std.fs.write_text, std.fs.write_text_result, std.vec.i32.empty, std.vec.i32.append, std.vec.i32.len, std.vec.i32.index, std.vec.i64.empty, std.vec.i64.append, std.vec.i64.len, std.vec.i64.index, std.vec.f64.empty, std.vec.f64.append, std.vec.f64.len, std.vec.f64.index, std.vec.bool.empty, std.vec.bool.append, std.vec.bool.len, std.vec.bool.index, std.vec.string.empty, std.vec.string.append, std.vec.string.len, std.vec.string.index, std.time.monotonic_ms, std.time.sleep_ms, std.random.i32, std.num.i32_to_i64, std.num.i32_to_f64, std.num.i64_to_f64, std.num.i64_to_i32_result, std.num.f64_to_i32_result, std.num.f64_to_i64_result, std.num.i32_to_string, std.num.u32_to_string, std.num.i64_to_string, std.num.u64_to_string, std.num.f64_to_string, std.result.is_ok, std.result.is_err, std.result.unwrap_ok, or std.result.unwrap_err")
|
||||||
.found(source_name)
|
.found(source_name)
|
||||||
.hint("use a promoted standard-runtime name or a legacy intrinsic alias")
|
.hint("use a promoted standard-runtime name or a legacy intrinsic alias")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,667 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
diag, lexer,
|
|
||||||
project::{ProjectArtifact, SourceFile, ToolProject},
|
|
||||||
sexpr::{Atom, SExpr, SExprKind},
|
|
||||||
token::Span,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn render_file(path: &str, source: &str) -> Result<String, Vec<diag::Diagnostic>> {
|
|
||||||
let source = SourceFile {
|
|
||||||
path: path.to_string(),
|
|
||||||
source: source.to_string(),
|
|
||||||
};
|
|
||||||
render_sources(&[source], None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_project(project: &ToolProject) -> Result<String, Vec<diag::Diagnostic>> {
|
|
||||||
render_sources(&project.sources, Some(&project.artifact))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_sources(
|
|
||||||
sources: &[SourceFile],
|
|
||||||
artifact: Option<&ProjectArtifact>,
|
|
||||||
) -> Result<String, Vec<diag::Diagnostic>> {
|
|
||||||
let package_by_path = package_names_by_path(artifact);
|
|
||||||
let mut modules = Vec::new();
|
|
||||||
let mut diagnostics = Vec::new();
|
|
||||||
|
|
||||||
let mut sorted_sources = sources.iter().collect::<Vec<_>>();
|
|
||||||
sorted_sources.sort_by(|left, right| left.path.cmp(&right.path));
|
|
||||||
|
|
||||||
for source in sorted_sources {
|
|
||||||
match module_symbols(
|
|
||||||
source,
|
|
||||||
package_by_path.get(&source.path).map(String::as_str),
|
|
||||||
) {
|
|
||||||
Ok(module) => modules.push(module),
|
|
||||||
Err(mut errs) => diagnostics.append(&mut errs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !diagnostics.is_empty() {
|
|
||||||
return Err(diagnostics);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut out = String::new();
|
|
||||||
out.push_str("(symbols\n");
|
|
||||||
out.push_str(" (schema slovo.symbols)\n");
|
|
||||||
out.push_str(" (version \"1.0.0-beta.10\")\n");
|
|
||||||
out.push_str(" (modules");
|
|
||||||
if modules.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
} else {
|
|
||||||
out.push('\n');
|
|
||||||
for module in &modules {
|
|
||||||
render_module(module, &mut out);
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(")\n");
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn package_names_by_path(artifact: Option<&ProjectArtifact>) -> HashMap<String, String> {
|
|
||||||
let mut packages = HashMap::new();
|
|
||||||
let Some(workspace) = artifact.and_then(|artifact| artifact.workspace.as_ref()) else {
|
|
||||||
return packages;
|
|
||||||
};
|
|
||||||
for package in &workspace.packages {
|
|
||||||
for module in &package.modules {
|
|
||||||
packages.insert(module.path.clone(), package.name.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packages
|
|
||||||
}
|
|
||||||
|
|
||||||
fn module_symbols(
|
|
||||||
source: &SourceFile,
|
|
||||||
package: Option<&str>,
|
|
||||||
) -> Result<ModuleSymbols, Vec<diag::Diagnostic>> {
|
|
||||||
let tokens = lexer::lex(&source.path, &source.source)?;
|
|
||||||
let forms = crate::sexpr::parse(&source.path, &tokens)?;
|
|
||||||
|
|
||||||
let mut module = ModuleSymbols {
|
|
||||||
name: "main".to_string(),
|
|
||||||
path: source.path.clone(),
|
|
||||||
package: package.map(str::to_string),
|
|
||||||
module_span: None,
|
|
||||||
exports: Vec::new(),
|
|
||||||
imports: Vec::new(),
|
|
||||||
type_aliases: Vec::new(),
|
|
||||||
structs: Vec::new(),
|
|
||||||
enums: Vec::new(),
|
|
||||||
functions: Vec::new(),
|
|
||||||
tests: Vec::new(),
|
|
||||||
source: source.source.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for form in &forms {
|
|
||||||
match list_head(form) {
|
|
||||||
Some("module") => extract_module(form, &mut module),
|
|
||||||
Some("import") => extract_import(form, &mut module.imports),
|
|
||||||
Some("type") => extract_type_alias(form, &mut module.type_aliases),
|
|
||||||
Some("struct") => extract_struct(form, &mut module.structs),
|
|
||||||
Some("enum") => extract_enum(form, &mut module.enums),
|
|
||||||
Some("fn") => extract_function(form, &mut module.functions),
|
|
||||||
Some("test") => extract_test(form, &mut module.tests),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(module)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_module(form: &SExpr, module: &mut ModuleSymbols) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
if let Some(name) = items.get(1).and_then(expect_ident) {
|
|
||||||
module.name = name.to_string();
|
|
||||||
module.module_span = Some(form.span);
|
|
||||||
}
|
|
||||||
if let Some(export_items) = items.get(2).and_then(expect_list) {
|
|
||||||
if matches!(export_items.first().and_then(expect_ident), Some("export")) {
|
|
||||||
module
|
|
||||||
.exports
|
|
||||||
.extend(export_items[1..].iter().filter_map(named_symbol_from_ident));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_import(form: &SExpr, imports: &mut Vec<ImportSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(module_name) = items.get(1).and_then(expect_ident) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let names = items
|
|
||||||
.get(2)
|
|
||||||
.and_then(expect_list)
|
|
||||||
.map(|items| items.iter().filter_map(named_symbol_from_ident).collect())
|
|
||||||
.unwrap_or_default();
|
|
||||||
imports.push(ImportSymbol {
|
|
||||||
module: module_name.to_string(),
|
|
||||||
span: form.span,
|
|
||||||
module_span: items[1].span,
|
|
||||||
names,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_type_alias(form: &SExpr, aliases: &mut Vec<TypeAliasSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name) = items.get(1).and_then(named_symbol_from_ident) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
aliases.push(TypeAliasSymbol {
|
|
||||||
name,
|
|
||||||
span: form.span,
|
|
||||||
target_span: items.get(2).map(|item| item.span),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_struct(form: &SExpr, structs: &mut Vec<StructSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name) = items.get(1).and_then(named_symbol_from_ident) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let fields = items[2..]
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| {
|
|
||||||
let pair = expect_list(item)?;
|
|
||||||
let field = pair.first().and_then(named_symbol_from_ident)?;
|
|
||||||
Some(FieldSymbol {
|
|
||||||
name: field,
|
|
||||||
span: item.span,
|
|
||||||
type_span: pair.get(1).map(|ty| ty.span),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
structs.push(StructSymbol {
|
|
||||||
name,
|
|
||||||
span: form.span,
|
|
||||||
fields,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_enum(form: &SExpr, enums: &mut Vec<EnumSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name) = items.get(1).and_then(named_symbol_from_ident) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let variants = items[2..]
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| {
|
|
||||||
if let Some(name) = named_symbol_from_ident(item) {
|
|
||||||
return Some(VariantSymbol {
|
|
||||||
name,
|
|
||||||
span: item.span,
|
|
||||||
payload_span: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let variant_items = expect_list(item)?;
|
|
||||||
let name = variant_items.first().and_then(named_symbol_from_ident)?;
|
|
||||||
Some(VariantSymbol {
|
|
||||||
name,
|
|
||||||
span: item.span,
|
|
||||||
payload_span: variant_items.get(1).map(|payload| payload.span),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
enums.push(EnumSymbol {
|
|
||||||
name,
|
|
||||||
span: form.span,
|
|
||||||
variants,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_function(form: &SExpr, functions: &mut Vec<FunctionSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name) = items.get(1).and_then(named_symbol_from_ident) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let params = items
|
|
||||||
.get(2)
|
|
||||||
.and_then(expect_list)
|
|
||||||
.map(|items| {
|
|
||||||
items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|item| {
|
|
||||||
let pair = expect_list(item)?;
|
|
||||||
let name = pair.first().and_then(named_symbol_from_ident)?;
|
|
||||||
Some(ParamSymbol {
|
|
||||||
name,
|
|
||||||
span: item.span,
|
|
||||||
type_span: pair.get(1).map(|ty| ty.span),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
functions.push(FunctionSymbol {
|
|
||||||
name,
|
|
||||||
span: form.span,
|
|
||||||
params,
|
|
||||||
return_span: items.get(4).map(|item| item.span),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_test(form: &SExpr, tests: &mut Vec<TestSymbol>) {
|
|
||||||
let Some(items) = expect_list(form) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name_expr) = items.get(1) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(name) = expect_string(name_expr) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
tests.push(TestSymbol {
|
|
||||||
name: NamedSymbol {
|
|
||||||
name: name.to_string(),
|
|
||||||
span: name_expr.span,
|
|
||||||
},
|
|
||||||
span: form.span,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_module(module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (module\n");
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (name {})\n",
|
|
||||||
diag::render_string(&module.name)
|
|
||||||
));
|
|
||||||
if let Some(package) = module.package.as_deref() {
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (package {})\n",
|
|
||||||
diag::render_string(package)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (path {})\n",
|
|
||||||
diag::render_string(&module.path)
|
|
||||||
));
|
|
||||||
if let Some(span) = module.module_span {
|
|
||||||
render_span(" ", "module_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
render_named_symbols(
|
|
||||||
"exports",
|
|
||||||
"export",
|
|
||||||
&module.exports,
|
|
||||||
" ",
|
|
||||||
&module.source,
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
render_imports(&module.imports, module, out);
|
|
||||||
render_type_aliases(&module.type_aliases, module, out);
|
|
||||||
render_structs(&module.structs, module, out);
|
|
||||||
render_enums(&module.enums, module, out);
|
|
||||||
render_functions(&module.functions, module, out);
|
|
||||||
render_tests(&module.tests, module, out);
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_named_symbols(
|
|
||||||
section: &str,
|
|
||||||
item_name: &str,
|
|
||||||
symbols: &[NamedSymbol],
|
|
||||||
indent: &str,
|
|
||||||
source: &str,
|
|
||||||
out: &mut String,
|
|
||||||
) {
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push('(');
|
|
||||||
out.push_str(section);
|
|
||||||
if symbols.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for symbol in symbols {
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push_str(" (");
|
|
||||||
out.push_str(item_name);
|
|
||||||
out.push('\n');
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push_str(" (name ");
|
|
||||||
out.push_str(&diag::render_string(&symbol.name));
|
|
||||||
out.push_str(")\n");
|
|
||||||
let symbol_indent = format!("{} ", indent);
|
|
||||||
render_span(&symbol_indent, "span", symbol.span, source, out);
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push_str(")\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_imports(imports: &[ImportSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (imports");
|
|
||||||
if imports.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for import in imports {
|
|
||||||
out.push_str(" (import\n");
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (module {})\n",
|
|
||||||
diag::render_string(&import.module)
|
|
||||||
));
|
|
||||||
render_span(" ", "span", import.span, &module.source, out);
|
|
||||||
render_span(
|
|
||||||
" ",
|
|
||||||
"module_span",
|
|
||||||
import.module_span,
|
|
||||||
&module.source,
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
render_named_symbols(
|
|
||||||
"names",
|
|
||||||
"name",
|
|
||||||
&import.names,
|
|
||||||
" ",
|
|
||||||
&module.source,
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_type_aliases(aliases: &[TypeAliasSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (type_aliases");
|
|
||||||
if aliases.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for alias in aliases {
|
|
||||||
out.push_str(" (type_alias\n");
|
|
||||||
render_decl_name(&alias.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", alias.span, &module.source, out);
|
|
||||||
if let Some(span) = alias.target_span {
|
|
||||||
render_span(" ", "target_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_structs(structs: &[StructSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (structs");
|
|
||||||
if structs.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for struct_symbol in structs {
|
|
||||||
out.push_str(" (struct\n");
|
|
||||||
render_decl_name(&struct_symbol.name, " ", &module.source, out);
|
|
||||||
render_span(
|
|
||||||
" ",
|
|
||||||
"span",
|
|
||||||
struct_symbol.span,
|
|
||||||
&module.source,
|
|
||||||
out,
|
|
||||||
);
|
|
||||||
out.push_str(" (fields");
|
|
||||||
if struct_symbol.fields.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
} else {
|
|
||||||
out.push('\n');
|
|
||||||
for field in &struct_symbol.fields {
|
|
||||||
out.push_str(" (field\n");
|
|
||||||
render_decl_name(&field.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", field.span, &module.source, out);
|
|
||||||
if let Some(span) = field.type_span {
|
|
||||||
render_span(" ", "type_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_enums(enums: &[EnumSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (enums");
|
|
||||||
if enums.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for enum_symbol in enums {
|
|
||||||
out.push_str(" (enum\n");
|
|
||||||
render_decl_name(&enum_symbol.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", enum_symbol.span, &module.source, out);
|
|
||||||
out.push_str(" (variants");
|
|
||||||
if enum_symbol.variants.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
} else {
|
|
||||||
out.push('\n');
|
|
||||||
for variant in &enum_symbol.variants {
|
|
||||||
out.push_str(" (variant\n");
|
|
||||||
render_decl_name(&variant.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", variant.span, &module.source, out);
|
|
||||||
if let Some(span) = variant.payload_span {
|
|
||||||
render_span(" ", "payload_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_functions(functions: &[FunctionSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (functions");
|
|
||||||
if functions.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for function in functions {
|
|
||||||
out.push_str(" (function\n");
|
|
||||||
render_decl_name(&function.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", function.span, &module.source, out);
|
|
||||||
if let Some(span) = function.return_span {
|
|
||||||
render_span(" ", "return_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
out.push_str(" (params");
|
|
||||||
if function.params.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
} else {
|
|
||||||
out.push('\n');
|
|
||||||
for param in &function.params {
|
|
||||||
out.push_str(" (param\n");
|
|
||||||
render_decl_name(¶m.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", param.span, &module.source, out);
|
|
||||||
if let Some(span) = param.type_span {
|
|
||||||
render_span(" ", "type_span", span, &module.source, out);
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_tests(tests: &[TestSymbol], module: &ModuleSymbols, out: &mut String) {
|
|
||||||
out.push_str(" (tests");
|
|
||||||
if tests.is_empty() {
|
|
||||||
out.push_str(")\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
out.push('\n');
|
|
||||||
for test in tests {
|
|
||||||
out.push_str(" (test\n");
|
|
||||||
render_decl_name(&test.name, " ", &module.source, out);
|
|
||||||
render_span(" ", "span", test.span, &module.source, out);
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
out.push_str(" )\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_decl_name(symbol: &NamedSymbol, indent: &str, source: &str, out: &mut String) {
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push_str(&format!("(name {})\n", diag::render_string(&symbol.name)));
|
|
||||||
render_span(indent, "name_span", symbol.span, source, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_span(indent: &str, label: &str, span: Span, source: &str, out: &mut String) {
|
|
||||||
let start = position(source, span.start);
|
|
||||||
let end = position(source, span.end);
|
|
||||||
out.push_str(indent);
|
|
||||||
out.push('(');
|
|
||||||
out.push_str(label);
|
|
||||||
out.push_str(&format!(
|
|
||||||
" (span (start {}) (end {})) (range (start (line {}) (column {})) (end (line {}) (column {}))))\n",
|
|
||||||
span.start, span.end, start.line, start.column, end.line, end.column
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn position(source: &str, offset: usize) -> Position {
|
|
||||||
let mut line = 1;
|
|
||||||
let mut column = 1;
|
|
||||||
for (index, byte) in source.as_bytes().iter().enumerate() {
|
|
||||||
if index >= offset {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if *byte == b'\n' {
|
|
||||||
line += 1;
|
|
||||||
column = 1;
|
|
||||||
} else {
|
|
||||||
column += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Position { line, column }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn list_head(form: &SExpr) -> Option<&str> {
|
|
||||||
expect_list(form)?.first().and_then(expect_ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_list(form: &SExpr) -> Option<&[SExpr]> {
|
|
||||||
match &form.kind {
|
|
||||||
SExprKind::List(items) => Some(items),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_ident(form: &SExpr) -> Option<&str> {
|
|
||||||
match &form.kind {
|
|
||||||
SExprKind::Atom(Atom::Ident(name)) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expect_string(form: &SExpr) -> Option<&str> {
|
|
||||||
match &form.kind {
|
|
||||||
SExprKind::Atom(Atom::String(name)) => Some(name),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn named_symbol_from_ident(form: &SExpr) -> Option<NamedSymbol> {
|
|
||||||
Some(NamedSymbol {
|
|
||||||
name: expect_ident(form)?.to_string(),
|
|
||||||
span: form.span,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ModuleSymbols {
|
|
||||||
name: String,
|
|
||||||
path: String,
|
|
||||||
package: Option<String>,
|
|
||||||
module_span: Option<Span>,
|
|
||||||
exports: Vec<NamedSymbol>,
|
|
||||||
imports: Vec<ImportSymbol>,
|
|
||||||
type_aliases: Vec<TypeAliasSymbol>,
|
|
||||||
structs: Vec<StructSymbol>,
|
|
||||||
enums: Vec<EnumSymbol>,
|
|
||||||
functions: Vec<FunctionSymbol>,
|
|
||||||
tests: Vec<TestSymbol>,
|
|
||||||
source: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NamedSymbol {
|
|
||||||
name: String,
|
|
||||||
span: Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportSymbol {
|
|
||||||
module: String,
|
|
||||||
span: Span,
|
|
||||||
module_span: Span,
|
|
||||||
names: Vec<NamedSymbol>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TypeAliasSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
target_span: Option<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct StructSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
fields: Vec<FieldSymbol>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FieldSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
type_span: Option<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EnumSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
variants: Vec<VariantSymbol>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct VariantSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
payload_span: Option<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FunctionSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
params: Vec<ParamSymbol>,
|
|
||||||
return_span: Option<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ParamSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
type_span: Option<Span>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TestSymbol {
|
|
||||||
name: NamedSymbol,
|
|
||||||
span: Span,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Position {
|
|
||||||
line: usize,
|
|
||||||
column: usize,
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -154,7 +154,6 @@ fn benchmark_roots() -> Vec<PathBuf> {
|
|||||||
root.join("math-loop"),
|
root.join("math-loop"),
|
||||||
root.join("branch-loop"),
|
root.join("branch-loop"),
|
||||||
root.join("parse-loop"),
|
root.join("parse-loop"),
|
||||||
root.join("json-quote-loop"),
|
|
||||||
root.join("array-index-loop"),
|
root.join("array-index-loop"),
|
||||||
root.join("string-eq-loop"),
|
root.join("string-eq-loop"),
|
||||||
root.join("array-struct-field-loop"),
|
root.join("array-struct-field-loop"),
|
||||||
|
|||||||
@ -1,134 +0,0 @@
|
|||||||
use std::{
|
|
||||||
env,
|
|
||||||
path::Path,
|
|
||||||
process::{Command, Output},
|
|
||||||
};
|
|
||||||
|
|
||||||
const BENCHMARKS: &[&str] = &[
|
|
||||||
"array-index-loop",
|
|
||||||
"array-struct-field-loop",
|
|
||||||
"branch-loop",
|
|
||||||
"enum-struct-payload-loop",
|
|
||||||
"json-quote-loop",
|
|
||||||
"math-loop",
|
|
||||||
"parse-loop",
|
|
||||||
"string-eq-loop",
|
|
||||||
"vec-i32-index-loop",
|
|
||||||
"vec-string-eq-loop",
|
|
||||||
];
|
|
||||||
|
|
||||||
const IMPLEMENTATIONS: &[&str] = &["slovo", "c", "rust", "python", "clojure", "common_lisp"];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn suite_catalog_is_byte_stable_and_lists_current_benchmarks() {
|
|
||||||
let repo = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
|
|
||||||
let python = python_command();
|
|
||||||
|
|
||||||
let first = run_suite_catalog(&repo, &python);
|
|
||||||
let second = run_suite_catalog(&repo, &python);
|
|
||||||
|
|
||||||
assert_success("first suite catalog run", &first);
|
|
||||||
assert_success("second suite catalog run", &second);
|
|
||||||
assert_eq!(
|
|
||||||
first.stdout, second.stdout,
|
|
||||||
"suite catalog JSON must be byte-stable across runs"
|
|
||||||
);
|
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&first.stdout);
|
|
||||||
for needle in [
|
|
||||||
r#""suite": "glagol-local-benchmark-suite""#,
|
|
||||||
r#""benchmark_count": 10"#,
|
|
||||||
r#""benchmark_metadata_files": 10"#,
|
|
||||||
r#""required_files": 40"#,
|
|
||||||
r#""missing_required_files": []"#,
|
|
||||||
r#""implementation_slots": 60"#,
|
|
||||||
r#""expected_implementation_slots": 60"#,
|
|
||||||
r#""missing_implementation_slots": []"#,
|
|
||||||
r#""status": "ok""#,
|
|
||||||
r#""timing_scope": "local-machine comparison only""#,
|
|
||||||
r#""timing_disclaimer": "Local timing comparison only; not a published benchmark result and not a cross-machine performance claim.""#,
|
|
||||||
r#""cold-process""#,
|
|
||||||
r#""hot-loop""#,
|
|
||||||
r#""loop_count": 1000000"#,
|
|
||||||
r#""hot_loop_count": 10000000"#,
|
|
||||||
r#""checksum_metadata""#,
|
|
||||||
r#""expected_checksum""#,
|
|
||||||
r#""hot_expected_checksum""#,
|
|
||||||
r#""required_files""#,
|
|
||||||
r#""path": "benchmark.json""#,
|
|
||||||
r#""path": "run.py""#,
|
|
||||||
r#""path": "slovo.toml""#,
|
|
||||||
r#""path": "src/main.slo""#,
|
|
||||||
r#""status": "present""#,
|
|
||||||
r#""implementation_slots""#,
|
|
||||||
r#""loop_count_source": "stdin""#,
|
|
||||||
] {
|
|
||||||
assert_contains(&stdout, needle);
|
|
||||||
}
|
|
||||||
|
|
||||||
for benchmark in BENCHMARKS {
|
|
||||||
assert_contains(&stdout, &format!(r#""name": "{}""#, benchmark));
|
|
||||||
assert_contains(&stdout, &format!(r#""directory": "{}""#, benchmark));
|
|
||||||
}
|
|
||||||
|
|
||||||
for implementation in IMPLEMENTATIONS {
|
|
||||||
assert_contains(&stdout, &format!(r#""name": "{}""#, implementation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_suite_catalog(repo: &Path, python: &str) -> Output {
|
|
||||||
Command::new(python)
|
|
||||||
.arg("benchmarks/runner.py")
|
|
||||||
.arg("--suite-list")
|
|
||||||
.arg("--json")
|
|
||||||
.current_dir(repo)
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
panic!(
|
|
||||||
"run `{}` benchmarks/runner.py --suite-list --json: {}",
|
|
||||||
python, err
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn python_command() -> String {
|
|
||||||
if let Some(python) = env::var_os("PYTHON") {
|
|
||||||
return python.to_string_lossy().into_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
for candidate in ["python3", "python"] {
|
|
||||||
if Command::new(candidate)
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
.map(|output| output.status.success())
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return candidate.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic!("benchmark suite catalog test requires python3 or python")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success(context: &str, output: &Output) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_contains(haystack: &str, needle: &str) {
|
|
||||||
assert!(
|
|
||||||
haystack.contains(needle),
|
|
||||||
"suite catalog output missing `{}`\nstdout:\n{}",
|
|
||||||
needle,
|
|
||||||
haystack
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn concrete_type_alias_fixture_erases_before_llvm_and_runs_tests() {
|
|
||||||
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");
|
|
||||||
|
|
||||||
let compile = run_glagol([fixture.as_os_str()]);
|
|
||||||
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
||||||
assert!(
|
|
||||||
compile.status.success(),
|
|
||||||
"compiler rejected concrete type alias fixture\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stdout.contains("define i32 @bump(i32 %value)")
|
|
||||||
&& stdout.contains("define [3 x i32] @make_counts(i32 %base)")
|
|
||||||
&& stdout.contains("define i32 @pick_count([3 x i32] %values)")
|
|
||||||
&& stdout.contains("define { i32, ptr } @make_measure(i32 %amount)")
|
|
||||||
&& stdout.contains("define { i1, i32 } @maybe_amount(i32 %amount)")
|
|
||||||
&& stdout.contains("define { i1, i32 } @ok_amount(i32 %amount)")
|
|
||||||
&& stdout.contains("define ptr @empty_counts()")
|
|
||||||
&& stdout.contains("define { i32, i32 } @reading_value(i32 %amount)")
|
|
||||||
&& stdout.contains("define i32 @reading_score({ i32, i32 } %reading)")
|
|
||||||
&& stdout.contains("call ptr @__glagol_vec_i32_empty()"),
|
|
||||||
"LLVM output did not show erased concrete alias shapes\nstdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!stdout.contains("Count")
|
|
||||||
&& !stdout.contains("Score")
|
|
||||||
&& !stdout.contains("MaybeCount")
|
|
||||||
&& !stdout.contains("ReadingAlias"),
|
|
||||||
"LLVM output leaked source alias names\nstdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
||||||
|
|
||||||
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
||||||
assert_success_stdout(
|
|
||||||
run,
|
|
||||||
concat!(
|
|
||||||
"test \"aliases erase through arrays and structs\" ... ok\n",
|
|
||||||
"test \"aliases erase through option result vec and enum\" ... ok\n",
|
|
||||||
"2 test(s) passed\n",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn concrete_type_alias_fixture_formats_and_lowers_stably() {
|
|
||||||
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.slo");
|
|
||||||
|
|
||||||
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
||||||
assert_success_stdout(
|
|
||||||
formatted,
|
|
||||||
&std::fs::read_to_string(&fixture).expect("read type alias fixture"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let surface = run_glagol([
|
|
||||||
OsStr::new("--inspect-lowering=surface"),
|
|
||||||
fixture.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success_stdout(
|
|
||||||
surface,
|
|
||||||
&std::fs::read_to_string(
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.surface.lower"),
|
|
||||||
)
|
|
||||||
.expect("read type alias surface snapshot"),
|
|
||||||
);
|
|
||||||
|
|
||||||
let checked = run_glagol([
|
|
||||||
OsStr::new("--inspect-lowering=checked"),
|
|
||||||
fixture.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success_stdout(
|
|
||||||
checked,
|
|
||||||
&std::fs::read_to_string(
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/type-aliases.checked.lower"),
|
|
||||||
)
|
|
||||||
.expect("read type alias checked snapshot"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(glagol_bin())
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn glagol_bin() -> PathBuf {
|
|
||||||
PathBuf::from(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success_stdout(output: Output, expected: &str) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"command failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert_eq!(stdout, expected);
|
|
||||||
assert!(stderr.is_empty(), "stderr was not empty:\n{}", stderr);
|
|
||||||
}
|
|
||||||
@ -83,148 +83,6 @@ const CASES: &[DiagnosticCase] = &[
|
|||||||
"#,
|
"#,
|
||||||
snapshot: "../tests/unknown-top-level-form.diag",
|
snapshot: "../tests/unknown-top-level-form.diag",
|
||||||
},
|
},
|
||||||
DiagnosticCase {
|
|
||||||
name: "malformed-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type Count)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/malformed-type-alias.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "duplicate-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type Count i32)
|
|
||||||
(type Count i64)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/duplicate-type-alias.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "type-alias-name-conflict",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(struct Count
|
|
||||||
(value i32))
|
|
||||||
|
|
||||||
(type Count i32)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/type-alias-name-conflict.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "type-alias-value-name-conflict",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(import_c c_add ((value i32)) -> i32)
|
|
||||||
|
|
||||||
(type main i32)
|
|
||||||
|
|
||||||
(type c_add i32)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/type-alias-value-name-conflict.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unknown-type-alias-target",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type Count Missing)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unknown-type-alias-target.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-type-alias-target",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type BadUnit unit)
|
|
||||||
|
|
||||||
(type BadPtr (ptr i32))
|
|
||||||
|
|
||||||
(type BadSlice (slice i32))
|
|
||||||
|
|
||||||
(type BadVec (vec u32))
|
|
||||||
|
|
||||||
(type BadResult (result i32 string))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-type-alias-target.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "self-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type Count Count)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/self-type-alias.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "cyclic-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type A B)
|
|
||||||
(type B A)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/cyclic-type-alias.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-generic-function",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn id (type_params T) ((value T)) -> T
|
|
||||||
value)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-generic-function.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-generic-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type VecOf (type_params T) (vec T))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-generic-type-alias.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-generic-type-parameter",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(let xs (vec T) (std.vec.i32.empty))
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-generic-type-parameter.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-map-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (map string i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-map-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "unsupported-set-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (set string)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/unsupported-set-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
DiagnosticCase {
|
||||||
name: "enum-empty",
|
name: "enum-empty",
|
||||||
source: r#"
|
source: r#"
|
||||||
@ -1036,250 +894,6 @@ const CASES: &[DiagnosticCase] = &[
|
|||||||
"#,
|
"#,
|
||||||
snapshot: "../tests/std-string-concat-unsupported-string-container.diag",
|
snapshot: "../tests/std-string-concat-unsupported-string-container.diag",
|
||||||
},
|
},
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-arity",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (result i32 i32)
|
|
||||||
(std.string.byte_at_result "abc"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-arity.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (result i32 i32)
|
|
||||||
(std.string.byte_at_result "abc" "0"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.byte_at_result "abc" 0))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-bool-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (std.string.byte_at_result "abc" 0) 1 0))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-bool-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-name-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn std.string.byte_at_result ((text string) (index i32)) -> (result i32 i32)
|
|
||||||
(err i32 i32 1))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-name-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-result-helper-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn __glagol_string_byte_at_result ((text string) (index i32)) -> (result i32 i32)
|
|
||||||
(err i32 i32 1))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-result-helper-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-arity",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (result string i32)
|
|
||||||
(std.string.slice_result "abc" 0))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-arity.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (result string i32)
|
|
||||||
(std.string.slice_result "abc" 0 "1"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> string
|
|
||||||
(std.string.slice_result "abc" 0 1))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-bool-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (std.string.slice_result "abc" 0 1) 1 0))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-bool-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-name-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn std.string.slice_result ((text string) (start i32) (count i32)) -> (result string i32)
|
|
||||||
(err string i32 1))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-name-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-slice-result-helper-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn __glagol_string_slice_result ((text string) (start i32) (count i32)) -> (result string i32)
|
|
||||||
(err string i32 1))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-slice-result-helper-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-starts-with-arity",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> bool
|
|
||||||
(std.string.starts_with "abc"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-starts-with-arity.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-starts-with-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> bool
|
|
||||||
(std.string.starts_with "abc" 1))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-starts-with-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-starts-with-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.starts_with "abc" "a"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-starts-with-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-starts-with-name-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn std.string.starts_with ((text string) (prefix string)) -> bool
|
|
||||||
false)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-starts-with-name-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-starts-with-helper-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn __glagol_string_starts_with ((text string) (prefix string)) -> bool
|
|
||||||
false)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-starts-with-helper-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-ends-with-arity",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> bool
|
|
||||||
(std.string.ends_with "abc"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-ends-with-arity.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-ends-with-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> bool
|
|
||||||
(std.string.ends_with "abc" 1))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-ends-with-type.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-ends-with-context",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.ends_with "abc" "c"))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-ends-with-context.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-ends-with-name-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn std.string.ends_with ((text string) (suffix string)) -> bool
|
|
||||||
false)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-ends-with-name-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-ends-with-helper-shadow",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn __glagol_string_ends_with ((text string) (suffix string)) -> bool
|
|
||||||
false)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-ends-with-helper-shadow.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
DiagnosticCase {
|
||||||
name: "std-string-parse-i32-result-arity",
|
name: "std-string-parse-i32-result-arity",
|
||||||
source: r#"
|
source: r#"
|
||||||
@ -1774,16 +1388,6 @@ const CASES: &[DiagnosticCase] = &[
|
|||||||
"#,
|
"#,
|
||||||
snapshot: "../tests/std-string-index-unsupported.diag",
|
snapshot: "../tests/std-string-index-unsupported.diag",
|
||||||
},
|
},
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-byte-at-unsupported",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.byte_at "42" 0))
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-byte-at-unsupported.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
DiagnosticCase {
|
||||||
name: "std-string-slice-unsupported",
|
name: "std-string-slice-unsupported",
|
||||||
source: r#"
|
source: r#"
|
||||||
@ -1795,39 +1399,6 @@ const CASES: &[DiagnosticCase] = &[
|
|||||||
"#,
|
"#,
|
||||||
snapshot: "../tests/std-string-slice-unsupported.diag",
|
snapshot: "../tests/std-string-slice-unsupported.diag",
|
||||||
},
|
},
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-contains-unsupported",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.contains "slovo" "lo")
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-contains-unsupported.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-find-result-unsupported",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.find_result "slovo" "lo")
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-find-result-unsupported.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-string-split-unsupported",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.string.split "a,b" ",")
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-string-split-unsupported.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
DiagnosticCase {
|
||||||
name: "std-string-tokenize-unsupported",
|
name: "std-string-tokenize-unsupported",
|
||||||
source: r#"
|
source: r#"
|
||||||
@ -2480,22 +2051,11 @@ const CASES: &[DiagnosticCase] = &[
|
|||||||
(module main)
|
(module main)
|
||||||
|
|
||||||
(fn main () -> i32
|
(fn main () -> i32
|
||||||
(std.result.map (ok string i32 "a") mapper)
|
(std.result.map (ok string i32 "a"))
|
||||||
0)
|
0)
|
||||||
"#,
|
"#,
|
||||||
snapshot: "../tests/std-result-map-unsupported.diag",
|
snapshot: "../tests/std-result-map-unsupported.diag",
|
||||||
},
|
},
|
||||||
DiagnosticCase {
|
|
||||||
name: "std-vec-empty-generic-unsupported",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.vec.empty i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
snapshot: "../tests/std-vec-empty-generic-unsupported.diag",
|
|
||||||
},
|
|
||||||
DiagnosticCase {
|
DiagnosticCase {
|
||||||
name: "std-package-load-unsupported",
|
name: "std-package-load-unsupported",
|
||||||
source: r#"
|
source: r#"
|
||||||
|
|||||||
@ -1,486 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn sexpr_diagnostics_keep_v1_schema_across_source_pipelines() {
|
|
||||||
let cases = [
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "parse",
|
|
||||||
args: &["check"],
|
|
||||||
source: "(module main",
|
|
||||||
code: "UnclosedList",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "check",
|
|
||||||
args: &["check"],
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn id ((value i32)) -> i32
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(id true))
|
|
||||||
"#,
|
|
||||||
code: "TypeMismatch",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "fmt",
|
|
||||||
args: &["fmt"],
|
|
||||||
source: r#"
|
|
||||||
(module main) ; comments stay outside formatter input
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedFormatterComment",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "test",
|
|
||||||
args: &["test"],
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(test "false case"
|
|
||||||
false)
|
|
||||||
"#,
|
|
||||||
code: "TestFailed",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for case in cases {
|
|
||||||
let fixture = write_fixture(case.name, case.source);
|
|
||||||
let mut args = case.args.iter().map(OsStr::new).collect::<Vec<_>>();
|
|
||||||
args.push(fixture.as_os_str());
|
|
||||||
|
|
||||||
let output = run_glagol(args);
|
|
||||||
|
|
||||||
assert_exit_code(case.name, &output, 1);
|
|
||||||
assert_stdout_empty(case.name, &output);
|
|
||||||
assert_sexpr_diagnostic_schema(case.name, &output, case.code, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let project = write_project(
|
|
||||||
"sexpr-project",
|
|
||||||
"(module main)\n\n(import missing (value))\n",
|
|
||||||
);
|
|
||||||
let output = run_glagol(["check".as_ref(), project.as_os_str()]);
|
|
||||||
|
|
||||||
assert_exit_code("project", &output, 1);
|
|
||||||
assert_stdout_empty("project", &output);
|
|
||||||
assert_sexpr_diagnostic_schema("project", &output, "MissingImport", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn json_diagnostics_keep_v1_schema_across_policy_boundaries() {
|
|
||||||
let cases = [
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "json-parse",
|
|
||||||
args: &["--json-diagnostics", "check"],
|
|
||||||
source: "(module main",
|
|
||||||
code: "UnclosedList",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "json-check",
|
|
||||||
args: &["--json-diagnostics", "check"],
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn id ((value i32)) -> i32
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(id true))
|
|
||||||
"#,
|
|
||||||
code: "TypeMismatch",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "json-fmt",
|
|
||||||
args: &["--json-diagnostics", "fmt"],
|
|
||||||
source: r#"
|
|
||||||
(module main) ; comments stay outside formatter input
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedFormatterComment",
|
|
||||||
},
|
|
||||||
SourceDiagnosticCase {
|
|
||||||
name: "json-test",
|
|
||||||
args: &["--json-diagnostics", "test"],
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(test "false case"
|
|
||||||
false)
|
|
||||||
"#,
|
|
||||||
code: "TestFailed",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for case in cases {
|
|
||||||
let fixture = write_fixture(case.name, case.source);
|
|
||||||
let mut args = case.args.iter().map(OsStr::new).collect::<Vec<_>>();
|
|
||||||
args.push(fixture.as_os_str());
|
|
||||||
|
|
||||||
let output = run_glagol(args);
|
|
||||||
|
|
||||||
assert_exit_code(case.name, &output, 1);
|
|
||||||
assert_stdout_empty(case.name, &output);
|
|
||||||
assert_json_diagnostic_schema(case.name, &output, case.code, JsonSource::Source);
|
|
||||||
}
|
|
||||||
|
|
||||||
let project = write_project(
|
|
||||||
"json-project",
|
|
||||||
"(module main)\n\n(import missing (value))\n",
|
|
||||||
);
|
|
||||||
let output = run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"check".as_ref(),
|
|
||||||
project.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_exit_code("json project", &output, 1);
|
|
||||||
assert_stdout_empty("json project", &output);
|
|
||||||
assert_json_diagnostic_schema("json project", &output, "MissingImport", JsonSource::Source);
|
|
||||||
|
|
||||||
let usage_manifest = temp_path("json-usage", "manifest.slo");
|
|
||||||
let usage = run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"--manifest".as_ref(),
|
|
||||||
usage_manifest.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_exit_code("json usage", &usage, 2);
|
|
||||||
assert_stdout_empty("json usage", &usage);
|
|
||||||
assert_json_diagnostic_schema("json usage", &usage, "UsageError", JsonSource::SourceLess);
|
|
||||||
assert_manifest_schema_fields(&read_manifest(&usage_manifest), "json");
|
|
||||||
|
|
||||||
let toolchain_fixture = write_fixture(
|
|
||||||
"json-toolchain",
|
|
||||||
"(module main)\n\n(fn main () -> i32\n 0)\n",
|
|
||||||
);
|
|
||||||
let toolchain_manifest = temp_path("json-toolchain", "manifest.slo");
|
|
||||||
let output_path = temp_path("json-toolchain", "bin");
|
|
||||||
let missing_clang = temp_path("json-toolchain", "not-a-clang");
|
|
||||||
let toolchain = Command::new(compiler_path())
|
|
||||||
.arg("--json-diagnostics")
|
|
||||||
.arg("build")
|
|
||||||
.arg(&toolchain_fixture)
|
|
||||||
.arg("-o")
|
|
||||||
.arg(&output_path)
|
|
||||||
.arg("--manifest")
|
|
||||||
.arg(&toolchain_manifest)
|
|
||||||
.env("GLAGOL_CLANG", &missing_clang)
|
|
||||||
.env_remove("GLAGOL_RUNTIME_C")
|
|
||||||
.env_remove("SLOVO_RUNTIME_C")
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|err| panic!("run glagol build: {}", err));
|
|
||||||
|
|
||||||
assert_exit_code("json toolchain", &toolchain, 3);
|
|
||||||
assert_stdout_empty("json toolchain", &toolchain);
|
|
||||||
assert_json_diagnostic_schema(
|
|
||||||
"json toolchain",
|
|
||||||
&toolchain,
|
|
||||||
"ToolchainUnavailable",
|
|
||||||
JsonSource::SourceLess,
|
|
||||||
);
|
|
||||||
assert_manifest_schema_fields(&read_manifest(&toolchain_manifest), "json");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn project_failure_manifests_record_schema_encoding_and_count_deterministically() {
|
|
||||||
let project = write_project(
|
|
||||||
"manifest-project",
|
|
||||||
"(module main)\n\n(import missing (value))\n",
|
|
||||||
);
|
|
||||||
|
|
||||||
let first_sexpr = run_project_failure_manifest(&project, "sexpr", false);
|
|
||||||
let second_sexpr = run_project_failure_manifest(&project, "sexpr-repeat", false);
|
|
||||||
assert_manifest_schema_fields(&first_sexpr, "sexpr");
|
|
||||||
assert_project_diagnostics_count(&first_sexpr, 1);
|
|
||||||
assert_eq!(
|
|
||||||
project_block(&first_sexpr),
|
|
||||||
project_block(&second_sexpr),
|
|
||||||
"S-expression failure manifest project block drifted"
|
|
||||||
);
|
|
||||||
|
|
||||||
let first_json = run_project_failure_manifest(&project, "json", true);
|
|
||||||
let second_json = run_project_failure_manifest(&project, "json-repeat", true);
|
|
||||||
assert_manifest_schema_fields(&first_json, "json");
|
|
||||||
assert_project_diagnostics_count(&first_json, 1);
|
|
||||||
assert_eq!(
|
|
||||||
project_block(&first_json),
|
|
||||||
project_block(&second_json),
|
|
||||||
"JSON failure manifest project block drifted"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SourceDiagnosticCase {
|
|
||||||
name: &'static str,
|
|
||||||
args: &'static [&'static str],
|
|
||||||
source: &'static str,
|
|
||||||
code: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
enum JsonSource {
|
|
||||||
Source,
|
|
||||||
SourceLess,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_project_failure_manifest(project: &Path, name: &str, json: bool) -> String {
|
|
||||||
let manifest = temp_path(name, "manifest.slo");
|
|
||||||
let output = if json {
|
|
||||||
run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"check".as_ref(),
|
|
||||||
"--manifest".as_ref(),
|
|
||||||
manifest.as_os_str(),
|
|
||||||
project.as_os_str(),
|
|
||||||
])
|
|
||||||
} else {
|
|
||||||
run_glagol([
|
|
||||||
"check".as_ref(),
|
|
||||||
"--manifest".as_ref(),
|
|
||||||
manifest.as_os_str(),
|
|
||||||
project.as_os_str(),
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_exit_code(name, &output, 1);
|
|
||||||
assert_stdout_empty(name, &output);
|
|
||||||
read_manifest(&manifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_sexpr_diagnostic_schema(
|
|
||||||
context: &str,
|
|
||||||
output: &Output,
|
|
||||||
expected_code: &str,
|
|
||||||
expected_count: usize,
|
|
||||||
) {
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
!stderr.trim().is_empty(),
|
|
||||||
"{} did not emit diagnostics",
|
|
||||||
context
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!stderr
|
|
||||||
.lines()
|
|
||||||
.all(|line| line.starts_with('{') && line.ends_with('}')),
|
|
||||||
"{} unexpectedly emitted JSON diagnostics:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
stderr.matches("(diagnostic\n").count(),
|
|
||||||
expected_count,
|
|
||||||
"{} diagnostic block count drifted:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
stderr.matches(" (schema slovo.diagnostic)\n").count(),
|
|
||||||
expected_count,
|
|
||||||
"{} diagnostic schema name drifted:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
stderr.matches(" (version 1)\n").count(),
|
|
||||||
expected_count,
|
|
||||||
"{} diagnostic schema version drifted:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(" (severity error)\n")
|
|
||||||
&& stderr.contains(&format!(" (code {})\n", expected_code))
|
|
||||||
&& stderr.contains(" (file ")
|
|
||||||
&& stderr.contains(" (span\n"),
|
|
||||||
"{} S-expression diagnostic missed required structural fields:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_json_diagnostic_schema(
|
|
||||||
context: &str,
|
|
||||||
output: &Output,
|
|
||||||
expected_code: &str,
|
|
||||||
source: JsonSource,
|
|
||||||
) {
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
let lines = stderr
|
|
||||||
.lines()
|
|
||||||
.filter(|line| !line.trim().is_empty())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert!(!lines.is_empty(), "{} did not emit diagnostics", context);
|
|
||||||
|
|
||||||
for line in &lines {
|
|
||||||
assert!(
|
|
||||||
line.starts_with('{') && line.ends_with('}'),
|
|
||||||
"{} emitted non-JSON diagnostic text:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
line.contains(r#""schema":"slovo.diagnostic""#)
|
|
||||||
&& line.contains(r#""version":1"#)
|
|
||||||
&& line.contains(r#""severity":"error""#)
|
|
||||||
&& line.contains(r#""message":"#)
|
|
||||||
&& line.contains(r#""file":"#)
|
|
||||||
&& line.contains(r#""span":"#),
|
|
||||||
"{} JSON diagnostic missed required schema fields:\n{}",
|
|
||||||
context,
|
|
||||||
line
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
lines
|
|
||||||
.iter()
|
|
||||||
.any(|line| line.contains(&format!(r#""code":"{}""#, expected_code))),
|
|
||||||
"{} JSON diagnostics did not include code `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
expected_code,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
|
|
||||||
match source {
|
|
||||||
JsonSource::Source => assert!(
|
|
||||||
lines
|
|
||||||
.iter()
|
|
||||||
.any(|line| line.contains(r#""span":{"byte_start":"#)),
|
|
||||||
"{} JSON diagnostics should include a concrete source span:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
),
|
|
||||||
JsonSource::SourceLess => assert!(
|
|
||||||
lines
|
|
||||||
.iter()
|
|
||||||
.any(|line| line.contains(r#""file":null"#) && line.contains(r#""span":null"#)),
|
|
||||||
"{} JSON diagnostics should be source-less:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_manifest_schema_fields(manifest: &str, encoding: &str) {
|
|
||||||
assert!(
|
|
||||||
manifest.contains(" (schema slovo.artifact-manifest)\n")
|
|
||||||
&& manifest.contains(" (version 1)\n")
|
|
||||||
&& manifest.contains(" (diagnostics-schema-version 1)\n")
|
|
||||||
&& manifest.contains(&format!(" (diagnostics-encoding {})\n", encoding))
|
|
||||||
&& manifest.contains("slovo.diagnostic"),
|
|
||||||
"manifest diagnostic schema fields drifted:\n{}",
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_project_diagnostics_count(manifest: &str, expected: usize) {
|
|
||||||
assert!(
|
|
||||||
manifest.contains(&format!(" (diagnostics_count {})\n", expected)),
|
|
||||||
"manifest diagnostics count drifted:\n{}",
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn project_block(manifest: &str) -> &str {
|
|
||||||
let start = manifest
|
|
||||||
.find(" (project\n")
|
|
||||||
.expect("manifest did not contain project block");
|
|
||||||
manifest[start..]
|
|
||||||
.strip_suffix("\n)\n")
|
|
||||||
.expect("manifest did not end with artifact-manifest close")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(compiler_path())
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|err| panic!("run glagol: {}", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compiler_path() -> &'static str {
|
|
||||||
env!("CARGO_BIN_EXE_glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
||||||
let path = temp_path(name, "slo");
|
|
||||||
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_project(name: &str, main_source: &str) -> PathBuf {
|
|
||||||
let root = temp_dir(name);
|
|
||||||
let source_root = root.join("src");
|
|
||||||
fs::create_dir_all(&source_root)
|
|
||||||
.unwrap_or_else(|err| panic!("create `{}`: {}", source_root.display(), err));
|
|
||||||
fs::write(
|
|
||||||
root.join("slovo.toml"),
|
|
||||||
"[project]\nname = \"beta13\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|err| panic!("write project manifest: {}", err));
|
|
||||||
fs::write(source_root.join("main.slo"), main_source)
|
|
||||||
.unwrap_or_else(|err| panic!("write main source: {}", err));
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_manifest(path: &Path) -> String {
|
|
||||||
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn temp_path(name: &str, extension: &str) -> PathBuf {
|
|
||||||
let mut path = std::env::temp_dir();
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
path.push(format!(
|
|
||||||
"glagol-beta13-{}-{}-{}.{}",
|
|
||||||
std::process::id(),
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
extension,
|
|
||||||
));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn temp_dir(name: &str) -> PathBuf {
|
|
||||||
let mut path = std::env::temp_dir();
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
path.push(format!(
|
|
||||||
"glagol-beta13-{}-{}-{}",
|
|
||||||
std::process::id(),
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_exit_code(context: &str, output: &Output, expected: i32) {
|
|
||||||
assert_eq!(
|
|
||||||
output.status.code(),
|
|
||||||
Some(expected),
|
|
||||||
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_stdout_empty(context: &str, output: &Output) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
assert!(stdout.is_empty(), "{} wrote stdout:\n{}", context, stdout);
|
|
||||||
}
|
|
||||||
@ -1,420 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn doc_file_renders_public_api_with_signatures_and_shapes() {
|
|
||||||
let source = r#"(module api (export Point Status make))
|
|
||||||
|
|
||||||
(struct Point
|
|
||||||
(x i32)
|
|
||||||
(label string))
|
|
||||||
|
|
||||||
(enum Status Ready (Blocked i32))
|
|
||||||
|
|
||||||
(fn helper ((value i32)) -> i32
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn make ((x i32) (label string)) -> Point
|
|
||||||
(Point (x x) (label label)))
|
|
||||||
|
|
||||||
(test "make is documented"
|
|
||||||
true)
|
|
||||||
"#;
|
|
||||||
let file = write_file("file-api", source);
|
|
||||||
let docs = unique_path("file-api-docs");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
file.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("doc file", &output);
|
|
||||||
let index = read_index(&docs);
|
|
||||||
assert!(index.contains("## Module api"));
|
|
||||||
assert!(index.contains("### Imports\n\nNone.\n\n"));
|
|
||||||
assert!(index.contains("- `Point`"));
|
|
||||||
assert!(index.contains("- `make(x i32, label string) -> Point`"));
|
|
||||||
assert!(index.contains("- `make is documented`"));
|
|
||||||
|
|
||||||
let api = public_api_for_module(&index, "api");
|
|
||||||
assert!(api.contains("- `fn make(x: i32, label: string) -> Point`"));
|
|
||||||
assert!(api.contains("- `struct Point`"));
|
|
||||||
assert!(api.contains(" - `x: i32`"));
|
|
||||||
assert!(api.contains(" - `label: string`"));
|
|
||||||
assert!(api.contains("- `enum Status`"));
|
|
||||||
assert!(api.contains(" - `Ready`"));
|
|
||||||
assert!(api.contains(" - `Blocked(i32)`"));
|
|
||||||
assert!(
|
|
||||||
!api.contains("helper"),
|
|
||||||
"non-exported helper leaked into public API:\n{}",
|
|
||||||
api
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn doc_project_renders_package_and_module_public_api() {
|
|
||||||
let project = write_project(
|
|
||||||
"project-api",
|
|
||||||
&[(
|
|
||||||
"math",
|
|
||||||
r#"(module math (export add Pair))
|
|
||||||
|
|
||||||
(struct Pair
|
|
||||||
(left i32)
|
|
||||||
(right i32))
|
|
||||||
|
|
||||||
(fn add ((left i32) (right i32)) -> i32
|
|
||||||
(+ left right))
|
|
||||||
|
|
||||||
(fn private_double ((value i32)) -> i32
|
|
||||||
(+ value value))
|
|
||||||
"#,
|
|
||||||
)],
|
|
||||||
"(module main)\n\n(import math (add Pair))\n\n(fn main () -> i32\n (add 1 2))\n",
|
|
||||||
);
|
|
||||||
let docs = unique_path("project-api-docs");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
project.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("doc project", &output);
|
|
||||||
let index = read_index(&docs);
|
|
||||||
assert!(index.contains("# Project project-api"));
|
|
||||||
assert!(index.contains("## Package API project-api"));
|
|
||||||
assert!(index.contains("## Module math"));
|
|
||||||
assert!(index.contains("## Module main"));
|
|
||||||
assert!(index.contains("- `math`"));
|
|
||||||
assert!(index.contains("- `add`"));
|
|
||||||
|
|
||||||
let package_api = package_api(&index, "project-api");
|
|
||||||
assert!(package_api.contains("### Module math"));
|
|
||||||
assert!(package_api.contains("- `fn add(left: i32, right: i32) -> i32`"));
|
|
||||||
assert!(package_api.contains("- `struct Pair`"));
|
|
||||||
assert!(
|
|
||||||
!package_api.contains("private_double"),
|
|
||||||
"non-exported function leaked into package API:\n{}",
|
|
||||||
package_api
|
|
||||||
);
|
|
||||||
|
|
||||||
let math_api = public_api_for_module(&index, "math");
|
|
||||||
assert!(math_api.contains("- `fn add(left: i32, right: i32) -> i32`"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn doc_workspace_renders_each_package_api_deterministically() {
|
|
||||||
let workspace = unique_path("workspace-api");
|
|
||||||
let scaffold = run_glagol([
|
|
||||||
OsStr::new("new"),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
OsStr::new("--template"),
|
|
||||||
OsStr::new("workspace"),
|
|
||||||
]);
|
|
||||||
assert_success("workspace scaffold", &scaffold);
|
|
||||||
|
|
||||||
let docs = unique_path("workspace-api-docs");
|
|
||||||
let output = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("doc workspace", &output);
|
|
||||||
let index = read_index(&docs);
|
|
||||||
assert!(index.contains("## Workspace"));
|
|
||||||
assert!(index.contains("- `packages/app`"));
|
|
||||||
assert!(index.contains("- `packages/libutil`"));
|
|
||||||
assert!(index.contains("## Package API app 0.1.0"));
|
|
||||||
assert!(index.contains("## Package API libutil 0.1.0"));
|
|
||||||
|
|
||||||
let app_api = package_api(&index, "app 0.1.0");
|
|
||||||
assert!(app_api.contains("None."));
|
|
||||||
|
|
||||||
let lib_api = package_api(&index, "libutil 0.1.0");
|
|
||||||
assert!(lib_api.contains("### Module libutil"));
|
|
||||||
assert!(lib_api.contains("- `fn answer() -> i32`"));
|
|
||||||
assert!(lib_api.contains("- `fn label() -> string`"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn doc_workspace_package_api_excludes_loaded_std_modules() {
|
|
||||||
let workspace = write_workspace_with_std_import("workspace-std-api");
|
|
||||||
let docs = unique_path("workspace-std-api-docs");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("doc workspace std import", &output);
|
|
||||||
let index = read_index(&docs);
|
|
||||||
assert!(
|
|
||||||
index.contains("## Module option"),
|
|
||||||
"module summaries should still include loaded std module docs:\n{}",
|
|
||||||
index
|
|
||||||
);
|
|
||||||
|
|
||||||
let app_api = package_api(&index, "app 0.1.0");
|
|
||||||
assert!(app_api.contains("### Module main"));
|
|
||||||
assert!(app_api.contains("- `fn local_some(value: i32) -> (option i32)`"));
|
|
||||||
assert!(
|
|
||||||
!app_api.contains("Module std.option") && !app_api.contains("Module option"),
|
|
||||||
"loaded std module leaked into package API:\n{}",
|
|
||||||
app_api
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!app_api.contains("some_i32"),
|
|
||||||
"loaded std helper leaked into package API:\n{}",
|
|
||||||
app_api
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn public_api_normalizes_local_aliases_and_omits_alias_exports() {
|
|
||||||
let source = r#"(module aliases (export Count Score Status measure))
|
|
||||||
|
|
||||||
(type Count i32)
|
|
||||||
(type MaybeCount (option Count))
|
|
||||||
|
|
||||||
(struct Score
|
|
||||||
(value Count)
|
|
||||||
(maybe MaybeCount))
|
|
||||||
|
|
||||||
(enum Status Ready (Blocked Count) (Maybe MaybeCount))
|
|
||||||
|
|
||||||
(fn hidden ((value Count)) -> Count
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn measure ((value Count) (maybe MaybeCount)) -> Count
|
|
||||||
value)
|
|
||||||
"#;
|
|
||||||
let file = write_file("alias-api", source);
|
|
||||||
let docs = unique_path("alias-api-docs");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
file.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("doc aliases", &output);
|
|
||||||
let index = read_index(&docs);
|
|
||||||
assert!(
|
|
||||||
index.contains("- `Count`"),
|
|
||||||
"exports summary should retain the alias name"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
index.contains("- `hidden(value Count) -> Count`"),
|
|
||||||
"function summary should retain non-public declarations"
|
|
||||||
);
|
|
||||||
|
|
||||||
let api = public_api_for_module(&index, "aliases");
|
|
||||||
assert!(api.contains("- `fn measure(value: i32, maybe: (option i32)) -> i32`"));
|
|
||||||
assert!(api.contains(" - `value: i32`"));
|
|
||||||
assert!(api.contains(" - `maybe: (option i32)`"));
|
|
||||||
assert!(api.contains(" - `Blocked(i32)`"));
|
|
||||||
assert!(api.contains(" - `Maybe((option i32))`"));
|
|
||||||
assert!(
|
|
||||||
!api.contains("Count"),
|
|
||||||
"alias names leaked into public API:\n{}",
|
|
||||||
api
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!api.contains("hidden"),
|
|
||||||
"non-exported function leaked into public API:\n{}",
|
|
||||||
api
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn repeated_doc_generation_is_byte_identical() {
|
|
||||||
let source = r#"(module stable (export value))
|
|
||||||
|
|
||||||
(fn value () -> i32
|
|
||||||
42)
|
|
||||||
"#;
|
|
||||||
let file = write_file("stable-api", source);
|
|
||||||
let docs = unique_path("stable-api-docs");
|
|
||||||
|
|
||||||
let first = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
file.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success("first doc", &first);
|
|
||||||
let first_bytes = fs::read(docs.join("index.md")).expect("read first docs");
|
|
||||||
|
|
||||||
let second = run_glagol([
|
|
||||||
OsStr::new("doc"),
|
|
||||||
file.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success("second doc", &second);
|
|
||||||
let second_bytes = fs::read(docs.join("index.md")).expect("read second docs");
|
|
||||||
|
|
||||||
assert_eq!(first_bytes, second_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf {
|
|
||||||
let project = unique_path(name);
|
|
||||||
fs::create_dir_all(project.join("src")).expect("create project src");
|
|
||||||
fs::write(
|
|
||||||
project.join("slovo.toml"),
|
|
||||||
format!(
|
|
||||||
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
||||||
name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.expect("write manifest");
|
|
||||||
for (module, source) in modules {
|
|
||||||
fs::write(project.join("src").join(format!("{}.slo", module)), source)
|
|
||||||
.expect("write module");
|
|
||||||
}
|
|
||||||
fs::write(project.join("src/main.slo"), main).expect("write main");
|
|
||||||
project
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_workspace_with_std_import(name: &str) -> PathBuf {
|
|
||||||
let workspace = unique_path(name);
|
|
||||||
let package = workspace.join("packages/app");
|
|
||||||
fs::create_dir_all(package.join("src")).expect("create workspace package src");
|
|
||||||
fs::write(
|
|
||||||
workspace.join("slovo.toml"),
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\ndefault_package = \"app\"\n",
|
|
||||||
)
|
|
||||||
.expect("write workspace manifest");
|
|
||||||
fs::write(
|
|
||||||
package.join("slovo.toml"),
|
|
||||||
"[package]\nname = \"app\"\nversion = \"0.1.0\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
||||||
)
|
|
||||||
.expect("write package manifest");
|
|
||||||
fs::write(
|
|
||||||
package.join("src/main.slo"),
|
|
||||||
r#"(module main (export local_some))
|
|
||||||
|
|
||||||
(import std.option (some_i32))
|
|
||||||
|
|
||||||
(fn local_some ((value i32)) -> (option i32)
|
|
||||||
(some_i32 value))
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.expect("write package main");
|
|
||||||
workspace
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_file(name: &str, source: &str) -> PathBuf {
|
|
||||||
let path = unique_path(name).with_extension("slo");
|
|
||||||
fs::write(&path, source).expect("write fixture");
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_index(docs: &Path) -> String {
|
|
||||||
fs::read_to_string(docs.join("index.md")).expect("read generated docs")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn package_api<'a>(docs: &'a str, package: &str) -> &'a str {
|
|
||||||
let heading = format!("## Package API {}", package);
|
|
||||||
let start = docs
|
|
||||||
.find(&heading)
|
|
||||||
.unwrap_or_else(|| panic!("missing package API heading `{}`\n{}", heading, docs));
|
|
||||||
let rest = &docs[start..];
|
|
||||||
let end = rest
|
|
||||||
.find("\n## Package API ")
|
|
||||||
.or_else(|| rest.find("\n## Module "))
|
|
||||||
.unwrap_or(rest.len());
|
|
||||||
&rest[..end]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_api_for_module<'a>(docs: &'a str, module: &str) -> &'a str {
|
|
||||||
let heading = format!("## Module {}", module);
|
|
||||||
let module_start = if docs.starts_with(&heading) {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
let marker = format!("\n{}", heading);
|
|
||||||
docs.find(&marker)
|
|
||||||
.map(|index| index + 1)
|
|
||||||
.unwrap_or_else(|| panic!("missing module heading `{}`\n{}", heading, docs))
|
|
||||||
};
|
|
||||||
let module_docs = &docs[module_start..];
|
|
||||||
let module_end = module_docs
|
|
||||||
.find("\n## Module ")
|
|
||||||
.unwrap_or(module_docs.len());
|
|
||||||
let module_docs = &module_docs[..module_end];
|
|
||||||
let public_start = module_docs.find("### Public API").unwrap_or_else(|| {
|
|
||||||
panic!(
|
|
||||||
"missing public API for module `{}`\n{}",
|
|
||||||
module, module_docs
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let public_docs = &module_docs[public_start..];
|
|
||||||
let public_end = public_docs
|
|
||||||
.find("\n### Structs")
|
|
||||||
.unwrap_or(public_docs.len());
|
|
||||||
&public_docs[..public_end]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unique_path(name: &str) -> PathBuf {
|
|
||||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let nanos = std::time::SystemTime::now()
|
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
|
||||||
.expect("system clock before UNIX_EPOCH")
|
|
||||||
.as_nanos();
|
|
||||||
std::env::temp_dir().join(format!(
|
|
||||||
"glagol-doc-api-beta11-{}-{}-{}-{}",
|
|
||||||
std::process::id(),
|
|
||||||
nanos,
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success(context: &str, output: &Output) {
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.stdout.is_empty(),
|
|
||||||
"{} wrote stdout:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.stderr.is_empty(),
|
|
||||||
"{} wrote stderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -70,179 +70,6 @@ fn new_creates_minimal_valid_project() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_builds_executes_and_clean_removes_generated_artifacts() {
|
|
||||||
let project = unique_path("run-project");
|
|
||||||
|
|
||||||
let new_output = run_glagol(["new".as_ref(), project.as_os_str()]);
|
|
||||||
assert_success("glagol new for run", &new_output);
|
|
||||||
|
|
||||||
let run = run_glagol(["run".as_ref(), project.as_os_str()]);
|
|
||||||
if run.status.success() {
|
|
||||||
assert!(run.stdout.is_empty(), "run wrote stdout");
|
|
||||||
assert!(run.stderr.is_empty(), "run wrote stderr");
|
|
||||||
assert!(
|
|
||||||
project.join(".slovo/build").is_dir(),
|
|
||||||
"run did not create generated build directory"
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
assert_stderr_contains("generated project run", &run, "ToolchainUnavailable");
|
|
||||||
fs::create_dir_all(project.join(".slovo/build")).expect("create synthetic build dir");
|
|
||||||
fs::write(project.join(".slovo/build/stale"), "").expect("write stale build file");
|
|
||||||
}
|
|
||||||
|
|
||||||
let clean = run_glagol(["clean".as_ref(), project.as_os_str()]);
|
|
||||||
assert_success("glagol clean", &clean);
|
|
||||||
assert!(clean.stdout.is_empty(), "clean wrote stdout");
|
|
||||||
assert!(clean.stderr.is_empty(), "clean wrote stderr");
|
|
||||||
assert!(
|
|
||||||
!project.join(".slovo/build").exists(),
|
|
||||||
"clean left generated build directory behind"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_forwards_program_arguments_when_host_toolchain_is_available() {
|
|
||||||
let project = write_project(
|
|
||||||
"run-args-project",
|
|
||||||
&[],
|
|
||||||
"(module main)\n\n(import std.process (argc))\n\n(fn main () -> i32\n (if (= (argc) 3)\n 0\n 1))\n",
|
|
||||||
);
|
|
||||||
|
|
||||||
let run = run_glagol([
|
|
||||||
"run".as_ref(),
|
|
||||||
project.as_os_str(),
|
|
||||||
"--".as_ref(),
|
|
||||||
"alpha".as_ref(),
|
|
||||||
"beta".as_ref(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if run.status.success() {
|
|
||||||
assert!(run.stdout.is_empty(), "run args wrote stdout");
|
|
||||||
assert!(run.stderr.is_empty(), "run args wrote stderr");
|
|
||||||
} else {
|
|
||||||
assert_stderr_contains("run args project", &run, "ToolchainUnavailable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn installed_layout_discovers_std_and_runtime_without_checkout_paths() {
|
|
||||||
let prefix = unique_path("installed-layout");
|
|
||||||
let bin_dir = prefix.join("bin");
|
|
||||||
let std_dir = prefix.join("share/slovo/std");
|
|
||||||
let runtime_dir = prefix.join("share/slovo/runtime");
|
|
||||||
fs::create_dir_all(&bin_dir).expect("create installed bin dir");
|
|
||||||
fs::create_dir_all(&std_dir).expect("create installed std dir");
|
|
||||||
fs::create_dir_all(&runtime_dir).expect("create installed runtime dir");
|
|
||||||
|
|
||||||
let installed_glagol = bin_dir.join(format!("glagol{}", std::env::consts::EXE_SUFFIX));
|
|
||||||
fs::copy(env!("CARGO_BIN_EXE_glagol"), &installed_glagol).expect("copy glagol");
|
|
||||||
make_executable(&installed_glagol);
|
|
||||||
|
|
||||||
let repo_root = Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
||||||
.parent()
|
|
||||||
.expect("compiler has repo parent");
|
|
||||||
fs::copy(
|
|
||||||
repo_root.join("runtime/runtime.c"),
|
|
||||||
runtime_dir.join("runtime.c"),
|
|
||||||
)
|
|
||||||
.expect("copy runtime");
|
|
||||||
for entry in fs::read_dir(repo_root.join("lib/std")).expect("read std dir") {
|
|
||||||
let entry = entry.expect("read std entry");
|
|
||||||
let path = entry.path();
|
|
||||||
if path.extension().and_then(|ext| ext.to_str()) == Some("slo") {
|
|
||||||
fs::copy(
|
|
||||||
&path,
|
|
||||||
std_dir.join(path.file_name().expect("std file name")),
|
|
||||||
)
|
|
||||||
.expect("copy std module");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let project = write_project(
|
|
||||||
"installed-layout-project",
|
|
||||||
&[],
|
|
||||||
"(module main)\n\n(import std.io (print_string_zero))\n(import std.string (concat))\n\n(fn main () -> i32\n (print_string_zero (concat \"installed\" \"-ok\")))\n",
|
|
||||||
);
|
|
||||||
|
|
||||||
let check = run_installed_glagol(&installed_glagol, ["check".as_ref(), project.as_os_str()]);
|
|
||||||
assert_success("installed layout check", &check);
|
|
||||||
|
|
||||||
let run = run_installed_glagol(&installed_glagol, ["run".as_ref(), project.as_os_str()]);
|
|
||||||
if host_clang_available() {
|
|
||||||
assert_success("installed layout run", &run);
|
|
||||||
assert_eq!(String::from_utf8_lossy(&run.stdout), "installed-ok\n");
|
|
||||||
assert!(run.stderr.is_empty(), "installed run wrote stderr");
|
|
||||||
} else {
|
|
||||||
assert_exit_code("installed layout run without clang", &run, 3);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"installed layout run without clang",
|
|
||||||
&run,
|
|
||||||
"ToolchainUnavailable",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_library_template_creates_checkable_testable_library_project() {
|
|
||||||
let project = unique_path("library-template");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"new".as_ref(),
|
|
||||||
project.as_os_str(),
|
|
||||||
"--template".as_ref(),
|
|
||||||
"library".as_ref(),
|
|
||||||
"--name".as_ref(),
|
|
||||||
"numbers".as_ref(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("glagol new --template library", &output);
|
|
||||||
assert_eq!(
|
|
||||||
fs::read_to_string(project.join("slovo.toml")).expect("read library manifest"),
|
|
||||||
"[project]\nname = \"numbers\"\nsource_root = \"src\"\nentry = \"lib\"\n"
|
|
||||||
);
|
|
||||||
let source = fs::read_to_string(project.join("src/lib.slo")).expect("read library source");
|
|
||||||
assert!(source.contains("(module lib (export answer double))"));
|
|
||||||
|
|
||||||
let check = run_glagol(["check".as_ref(), project.as_os_str()]);
|
|
||||||
assert_success("library template check", &check);
|
|
||||||
let test = run_glagol(["test".as_ref(), project.as_os_str()]);
|
|
||||||
assert_success("library template test", &test);
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8_lossy(&test.stdout),
|
|
||||||
"test \"answer is stable\" ... ok\ntest \"double works\" ... ok\n2 test(s) passed\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn new_workspace_template_creates_local_package_workspace() {
|
|
||||||
let workspace = unique_path("workspace-template");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"new".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
"--template".as_ref(),
|
|
||||||
"workspace".as_ref(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_success("glagol new --template workspace", &output);
|
|
||||||
assert_eq!(
|
|
||||||
fs::read_to_string(workspace.join("slovo.toml")).expect("read workspace manifest"),
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/libutil\"]\ndefault_package = \"app\"\n"
|
|
||||||
);
|
|
||||||
assert!(workspace.join("packages/app/slovo.toml").is_file());
|
|
||||||
assert!(workspace.join("packages/libutil/slovo.toml").is_file());
|
|
||||||
|
|
||||||
let check = run_glagol(["check".as_ref(), workspace.as_os_str()]);
|
|
||||||
assert_success("workspace template check", &check);
|
|
||||||
let test = run_glagol(["test".as_ref(), workspace.as_os_str()]);
|
|
||||||
assert_success("workspace template test", &test);
|
|
||||||
let stdout = String::from_utf8_lossy(&test.stdout);
|
|
||||||
assert!(stdout.contains("test \"app uses libutil\" ... ok"));
|
|
||||||
assert!(stdout.contains("test \"answer is stable\" ... ok"));
|
|
||||||
assert!(stdout.contains("2 test(s) passed"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_rejects_non_empty_target_with_structured_diagnostic() {
|
fn new_rejects_non_empty_target_with_structured_diagnostic() {
|
||||||
let project = unique_path("new-non-empty");
|
let project = unique_path("new-non-empty");
|
||||||
@ -351,38 +178,6 @@ fn doc_generates_markdown_for_file_and_project() {
|
|||||||
assert!(project_index.contains("- `math`"));
|
assert!(project_index.contains("- `math`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn doc_generates_workspace_package_summary() {
|
|
||||||
let workspace = unique_path("doc-workspace");
|
|
||||||
let new_output = run_glagol([
|
|
||||||
"new".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
"--template".as_ref(),
|
|
||||||
"workspace".as_ref(),
|
|
||||||
]);
|
|
||||||
assert_success("doc workspace scaffold", &new_output);
|
|
||||||
|
|
||||||
let workspace_docs = unique_path("doc-workspace-out");
|
|
||||||
let doc_output = run_glagol([
|
|
||||||
"doc".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
"-o".as_ref(),
|
|
||||||
workspace_docs.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success("doc workspace", &doc_output);
|
|
||||||
|
|
||||||
let index = fs::read_to_string(workspace_docs.join("index.md")).expect("read workspace docs");
|
|
||||||
assert!(index.contains("## Workspace"));
|
|
||||||
assert!(index.contains("### Members"));
|
|
||||||
assert!(index.contains("- `packages/app`"));
|
|
||||||
assert!(index.contains("- `packages/libutil`"));
|
|
||||||
assert!(index.contains("### Packages"));
|
|
||||||
assert!(index.contains("- `app 0.1.0 (entry main)`"));
|
|
||||||
assert!(index.contains("- `libutil 0.1.0 (entry main)`"));
|
|
||||||
assert!(index.contains("### Package Dependencies"));
|
|
||||||
assert!(index.contains("- `app -> libutil (local-path, packages/libutil)`"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
#[test]
|
#[test]
|
||||||
fn project_tooling_rejects_symlinked_module_escape() {
|
fn project_tooling_rejects_symlinked_module_escape() {
|
||||||
@ -419,7 +214,6 @@ fn release_gate_script_exists_and_names_required_commands() {
|
|||||||
let script = Path::new("../scripts/release-gate.sh");
|
let script = Path::new("../scripts/release-gate.sh");
|
||||||
let text = fs::read_to_string(script).expect("read release gate script");
|
let text = fs::read_to_string(script).expect("read release gate script");
|
||||||
assert!(text.contains("git diff --check"));
|
assert!(text.contains("git diff --check"));
|
||||||
assert!(text.contains("scripts/install.sh"));
|
|
||||||
assert!(text.contains("cargo fmt --check"));
|
assert!(text.contains("cargo fmt --check"));
|
||||||
assert!(text.contains("cargo test"));
|
assert!(text.contains("cargo test"));
|
||||||
assert!(text.contains("dx_v1_7"));
|
assert!(text.contains("dx_v1_7"));
|
||||||
@ -481,39 +275,6 @@ where
|
|||||||
.expect("run glagol")
|
.expect("run glagol")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_installed_glagol<I, S>(binary: &Path, args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<std::ffi::OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(binary)
|
|
||||||
.env_remove("SLOVO_STD_PATH")
|
|
||||||
.env_remove("SLOVO_RUNTIME_C")
|
|
||||||
.env_remove("GLAGOL_RUNTIME_C")
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run installed glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn host_clang_available() -> bool {
|
|
||||||
let clang = std::env::var("GLAGOL_CLANG").unwrap_or_else(|_| "clang".to_string());
|
|
||||||
Command::new(clang)
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
.map(|output| output.status.success())
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_executable(path: &Path) {
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
let mut permissions = fs::metadata(path).expect("stat executable").permissions();
|
|
||||||
permissions.set_mode(0o755);
|
|
||||||
fs::set_permissions(path, permissions).expect("chmod executable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success(context: &str, output: &Output) {
|
fn assert_success(context: &str, output: &Output) {
|
||||||
assert!(
|
assert!(
|
||||||
output.status.success(),
|
output.status.success(),
|
||||||
|
|||||||
@ -334,103 +334,6 @@ fn formatter_reports_unsupported_standard_library_calls() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn formatter_rejects_reserved_generic_collection_syntax() {
|
|
||||||
let compiler = env!("CARGO_BIN_EXE_glagol");
|
|
||||||
let cases = [
|
|
||||||
(
|
|
||||||
"generic-function",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn id (type_params T) ((value T)) -> T
|
|
||||||
value)
|
|
||||||
"#,
|
|
||||||
"UnsupportedGenericFunction",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"generic-type-alias",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type VecOf (type_params T) (vec T))
|
|
||||||
"#,
|
|
||||||
"UnsupportedGenericTypeAlias",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"generic-type-parameter",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(let xs (vec T) (std.vec.i32.empty))
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"UnsupportedGenericTypeParameter",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"map-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (map string i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"UnsupportedMapType",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"set-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (set string)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"UnsupportedSetType",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"generic-std-call",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.vec.empty i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"UnsupportedGenericStandardLibraryCall",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (name, source, code) in cases {
|
|
||||||
let fixture = write_fixture(name, source);
|
|
||||||
let output = run_formatter(compiler, &fixture);
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!output.status.success(),
|
|
||||||
"formatter unexpectedly accepted reserved generic syntax `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
name,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stdout.is_empty(),
|
|
||||||
"formatter emitted stdout for reserved generic syntax `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
name,
|
|
||||||
stdout,
|
|
||||||
stderr,
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(code),
|
|
||||||
"formatter stderr did not contain {} for `{}`\nstderr:\n{}",
|
|
||||||
code,
|
|
||||||
name,
|
|
||||||
stderr,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn formatter_preserves_full_line_comments_inside_function_bodies() {
|
fn formatter_preserves_full_line_comments_inside_function_bodies() {
|
||||||
let compiler = env!("CARGO_BIN_EXE_glagol");
|
let compiler = env!("CARGO_BIN_EXE_glagol");
|
||||||
|
|||||||
@ -13,12 +13,6 @@ const LOWERING_FIXTURES: &[LoweringFixture] = &[
|
|||||||
surface_snapshot: "../tests/top-level-test.surface.lower",
|
surface_snapshot: "../tests/top-level-test.surface.lower",
|
||||||
checked_snapshot: "../tests/top-level-test.checked.lower",
|
checked_snapshot: "../tests/top-level-test.checked.lower",
|
||||||
},
|
},
|
||||||
LoweringFixture {
|
|
||||||
name: "type-aliases",
|
|
||||||
source: "../tests/type-aliases.slo",
|
|
||||||
surface_snapshot: "../tests/type-aliases.surface.lower",
|
|
||||||
checked_snapshot: "../tests/type-aliases.checked.lower",
|
|
||||||
},
|
|
||||||
LoweringFixture {
|
LoweringFixture {
|
||||||
name: "comments",
|
name: "comments",
|
||||||
source: "../tests/comments.slo",
|
source: "../tests/comments.slo",
|
||||||
|
|||||||
@ -1,229 +0,0 @@
|
|||||||
use std::{
|
|
||||||
fs,
|
|
||||||
path::PathBuf,
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_WORKSPACE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn duplicate_package_keys_report_package_manifest_invalid() {
|
|
||||||
let workspace = write_workspace(
|
|
||||||
"duplicate-package-key",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nname = \"other\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"check".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("duplicate package key", &output, 1);
|
|
||||||
assert_json_diagnostic_code("duplicate package key", &output, "PackageManifestInvalid");
|
|
||||||
assert_json_diagnostic_code_absent("duplicate package key", &output, "ProjectManifestInvalid");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn invalid_dependency_key_reports_invalid_package_dependency_name() {
|
|
||||||
let workspace = write_workspace(
|
|
||||||
"invalid-dependency-key",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nBad_Name = { path = \"../util\" }\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"check".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("invalid dependency key", &output, 1);
|
|
||||||
assert_json_diagnostic_code(
|
|
||||||
"invalid dependency key",
|
|
||||||
&output,
|
|
||||||
"InvalidPackageDependencyName",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn duplicate_dependency_keys_report_duplicate_package_dependency_name() {
|
|
||||||
let workspace = write_workspace(
|
|
||||||
"duplicate-dependency-key",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\nutil = { path = \"../util-again\" }\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"--json-diagnostics".as_ref(),
|
|
||||||
"check".as_ref(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("duplicate dependency key", &output, 1);
|
|
||||||
assert_json_diagnostic_code(
|
|
||||||
"duplicate dependency key",
|
|
||||||
&output,
|
|
||||||
"DuplicatePackageDependencyName",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn valid_dependency_identity_checks_cleanly() {
|
|
||||||
let workspace = write_workspace(
|
|
||||||
"valid-dependency-identity",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/util\"]\n",
|
|
||||||
&[
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/util",
|
|
||||||
manifest: "[package]\nname = \"util\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[(
|
|
||||||
"util",
|
|
||||||
"(module util (export answer))\n\n(fn answer () -> i32\n 42)\n",
|
|
||||||
)],
|
|
||||||
},
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n\n[dependencies]\nutil = { path = \"../util\" }\n",
|
|
||||||
modules: &[(
|
|
||||||
"main",
|
|
||||||
"(module main)\n\n(import util.util (answer))\n\n(fn main () -> i32\n (answer))\n",
|
|
||||||
)],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = run_glagol(["check".as_ref(), workspace.as_os_str()]);
|
|
||||||
assert_success_stdout("valid dependency identity", output, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WorkspacePackageSpec<'a> {
|
|
||||||
member: &'a str,
|
|
||||||
manifest: &'a str,
|
|
||||||
modules: &'a [(&'a str, &'a str)],
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_workspace(
|
|
||||||
name: &str,
|
|
||||||
workspace_manifest: &str,
|
|
||||||
packages: &[WorkspacePackageSpec<'_>],
|
|
||||||
) -> PathBuf {
|
|
||||||
let root = unique_path(name);
|
|
||||||
fs::create_dir_all(&root).expect("create workspace root");
|
|
||||||
fs::write(root.join("slovo.toml"), workspace_manifest).expect("write workspace manifest");
|
|
||||||
|
|
||||||
for package in packages {
|
|
||||||
let package_root = root.join(package.member);
|
|
||||||
let src = package_root.join("src");
|
|
||||||
fs::create_dir_all(&src).expect("create workspace package src");
|
|
||||||
fs::write(package_root.join("slovo.toml"), package.manifest)
|
|
||||||
.expect("write workspace package manifest");
|
|
||||||
for (module, source) in package.modules {
|
|
||||||
fs::write(src.join(format!("{}.slo", module)), source)
|
|
||||||
.expect("write workspace package module");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unique_path(name: &str) -> PathBuf {
|
|
||||||
let id = NEXT_WORKSPACE_ID.fetch_add(1, Ordering::SeqCst);
|
|
||||||
let nanos = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.map(|duration| duration.as_nanos())
|
|
||||||
.unwrap_or(0);
|
|
||||||
std::env::temp_dir().join(format!(
|
|
||||||
"glagol-package-workspace-discipline-beta24-{}-{}-{}-{}",
|
|
||||||
std::process::id(),
|
|
||||||
nanos,
|
|
||||||
id,
|
|
||||||
name
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<std::ffi::OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success_stdout(context: &str, output: Output, expected: &str) {
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
expected,
|
|
||||||
"{} stdout mismatch",
|
|
||||||
context
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.stderr.is_empty(),
|
|
||||||
"{} wrote stderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_exit_code(context: &str, output: &Output, expected: i32) {
|
|
||||||
assert_eq!(
|
|
||||||
output.status.code(),
|
|
||||||
Some(expected),
|
|
||||||
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_json_diagnostic_code(context: &str, output: &Output, expected: &str) {
|
|
||||||
let diagnostics = diagnostic_text(output);
|
|
||||||
assert!(
|
|
||||||
diagnostics.contains(&format!(r#""code":"{}""#, expected)),
|
|
||||||
"{} did not report `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
expected,
|
|
||||||
diagnostics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_json_diagnostic_code_absent(context: &str, output: &Output, unexpected: &str) {
|
|
||||||
let diagnostics = diagnostic_text(output);
|
|
||||||
assert!(
|
|
||||||
!diagnostics.contains(&format!(r#""code":"{}""#, unexpected)),
|
|
||||||
"{} unexpectedly reported `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
unexpected,
|
|
||||||
diagnostics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn diagnostic_text(output: &Output) -> String {
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@ -587,117 +587,6 @@ fn workspace_build_requires_one_entry_package() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workspace_default_package_selects_build_entry() {
|
|
||||||
let workspace = write_workspace(
|
|
||||||
"workspace-default-build-entry",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/tool\"]\ndefault_package = \"app\"\n",
|
|
||||||
&[
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
},
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/tool",
|
|
||||||
manifest: "[package]\nname = \"tool\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 7)\n")],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
let binary = unique_path("workspace-default-build-bin");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"build".as_ref(),
|
|
||||||
"-o".as_ref(),
|
|
||||||
binary.as_os_str(),
|
|
||||||
workspace.as_os_str(),
|
|
||||||
]);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
!stderr.contains("WorkspaceBuildAmbiguousEntryPackage"),
|
|
||||||
"default package should avoid ambiguous build entry diagnostic:\n{}",
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
|
|
||||||
if output.status.code() == Some(3) {
|
|
||||||
assert_stderr_contains(
|
|
||||||
"workspace default package build toolchain",
|
|
||||||
&output,
|
|
||||||
"ToolchainUnavailable",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_success("workspace default package build", &output);
|
|
||||||
let run = Command::new(&binary)
|
|
||||||
.output()
|
|
||||||
.expect("run workspace default package build output");
|
|
||||||
assert_success("workspace default package build binary", &run);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn workspace_build_reports_entry_main_contract() {
|
|
||||||
let missing_main = write_workspace(
|
|
||||||
"workspace-missing-entry-main",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(test \"ok\"\n true)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
let missing_binary = unique_path("workspace-missing-entry-main-bin");
|
|
||||||
let missing_output = run_glagol([
|
|
||||||
"build".as_ref(),
|
|
||||||
"-o".as_ref(),
|
|
||||||
missing_binary.as_os_str(),
|
|
||||||
missing_main.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("workspace missing entry main", &missing_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"workspace missing entry main",
|
|
||||||
&missing_output,
|
|
||||||
"WorkspaceEntryMainMissing",
|
|
||||||
);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"workspace missing entry main",
|
|
||||||
&missing_output,
|
|
||||||
"build/run require `(fn main () -> i32 ...)`",
|
|
||||||
);
|
|
||||||
|
|
||||||
let bad_signature = write_workspace(
|
|
||||||
"workspace-bad-entry-main",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[(
|
|
||||||
"main",
|
|
||||||
"(module main)\n\n(fn main () -> string\n \"bad\")\n",
|
|
||||||
)],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
let bad_binary = unique_path("workspace-bad-entry-main-bin");
|
|
||||||
let bad_output = run_glagol([
|
|
||||||
"build".as_ref(),
|
|
||||||
"-o".as_ref(),
|
|
||||||
bad_binary.as_os_str(),
|
|
||||||
bad_signature.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("workspace bad entry main", &bad_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"workspace bad entry main",
|
|
||||||
&bad_output,
|
|
||||||
"WorkspaceEntryMainInvalidSignature",
|
|
||||||
);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"workspace bad entry main",
|
|
||||||
&bad_output,
|
|
||||||
"found 0 parameter(s) and return `string`",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn workspace_package_boundaries_are_diagnostics() {
|
fn workspace_package_boundaries_are_diagnostics() {
|
||||||
let missing = write_workspace(
|
let missing = write_workspace(
|
||||||
@ -825,74 +714,6 @@ fn workspace_package_boundaries_are_diagnostics() {
|
|||||||
&escape_output,
|
&escape_output,
|
||||||
"WorkspaceMemberPathEscape",
|
"WorkspaceMemberPathEscape",
|
||||||
);
|
);
|
||||||
|
|
||||||
let duplicate_member = write_workspace(
|
|
||||||
"workspace-duplicate-member",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/./app\"]\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
let duplicate_member_output = run_glagol(["check".as_ref(), duplicate_member.as_os_str()]);
|
|
||||||
assert_exit_code("duplicate workspace member", &duplicate_member_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"duplicate workspace member",
|
|
||||||
&duplicate_member_output,
|
|
||||||
"DuplicateWorkspaceMember",
|
|
||||||
);
|
|
||||||
|
|
||||||
let missing_default = write_workspace(
|
|
||||||
"workspace-missing-default-package",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\"]\ndefault_package = \"tool\"\n",
|
|
||||||
&[WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
}],
|
|
||||||
);
|
|
||||||
let missing_default_output = run_glagol(["check".as_ref(), missing_default.as_os_str()]);
|
|
||||||
assert_exit_code("missing default package", &missing_default_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"missing default package",
|
|
||||||
&missing_default_output,
|
|
||||||
"WorkspaceDefaultPackageMissing",
|
|
||||||
);
|
|
||||||
|
|
||||||
let missing_default_entry = write_workspace(
|
|
||||||
"workspace-missing-default-entry",
|
|
||||||
"[workspace]\nmembers = [\"packages/app\", \"packages/tool\"]\ndefault_package = \"app\"\n",
|
|
||||||
&[
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/app",
|
|
||||||
manifest: "[package]\nname = \"app\"\nversion = \"0.1.0\"\nentry = \"app\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
},
|
|
||||||
WorkspacePackageSpec {
|
|
||||||
member: "packages/tool",
|
|
||||||
manifest: "[package]\nname = \"tool\"\nversion = \"0.1.0\"\n",
|
|
||||||
modules: &[("main", "(module main)\n\n(fn main () -> i32\n 0)\n")],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
);
|
|
||||||
let missing_default_entry_binary = unique_path("workspace-missing-default-entry-bin");
|
|
||||||
let missing_default_entry_output = run_glagol([
|
|
||||||
"build".as_ref(),
|
|
||||||
"-o".as_ref(),
|
|
||||||
missing_default_entry_binary.as_os_str(),
|
|
||||||
missing_default_entry.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code(
|
|
||||||
"missing default package entry",
|
|
||||||
&missing_default_entry_output,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"missing default package entry",
|
|
||||||
&missing_default_entry_output,
|
|
||||||
"WorkspaceDefaultPackageEntryMissing",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1279,98 +1100,6 @@ fn enum_import_visibility_and_duplicate_cases_are_diagnostics() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn type_aliases_are_local_across_project_visibility() {
|
|
||||||
let erased_signature = write_project(
|
|
||||||
"alias-erased-signature",
|
|
||||||
&[(
|
|
||||||
"types",
|
|
||||||
"(module types (export make_count))\n\n(type Count i32)\n\n(fn make_count ((value Count)) -> Count\n value)\n",
|
|
||||||
)],
|
|
||||||
"(module main)\n\n(import types (make_count))\n\n(fn main () -> i32\n (make_count 42))\n",
|
|
||||||
);
|
|
||||||
let erased_output = run_glagol(["check".as_ref(), erased_signature.as_os_str()]);
|
|
||||||
assert_success_stdout("alias erased project signature", erased_output, "");
|
|
||||||
|
|
||||||
let export_alias = write_project(
|
|
||||||
"alias-export",
|
|
||||||
&[(
|
|
||||||
"types",
|
|
||||||
"(module types (export Count))\n\n(type Count i32)\n\n(fn make_count ((value Count)) -> Count\n value)\n",
|
|
||||||
)],
|
|
||||||
"(module main)\n",
|
|
||||||
);
|
|
||||||
let export_output = run_glagol(["check".as_ref(), export_alias.as_os_str()]);
|
|
||||||
assert_exit_code("alias export", &export_output, 1);
|
|
||||||
assert_stderr_contains("alias export", &export_output, "Visibility");
|
|
||||||
assert_stderr_contains(
|
|
||||||
"alias export message",
|
|
||||||
&export_output,
|
|
||||||
"type alias `Count` is module-local and cannot be exported",
|
|
||||||
);
|
|
||||||
|
|
||||||
let import_alias = write_project(
|
|
||||||
"alias-import",
|
|
||||||
&[("types", "(module types)\n\n(type Count i32)\n")],
|
|
||||||
"(module main)\n\n(import types (Count))\n",
|
|
||||||
);
|
|
||||||
let import_output = run_glagol(["check".as_ref(), import_alias.as_os_str()]);
|
|
||||||
assert_exit_code("alias import", &import_output, 1);
|
|
||||||
assert_stderr_contains("alias import", &import_output, "Visibility");
|
|
||||||
assert_stderr_contains(
|
|
||||||
"alias import message",
|
|
||||||
&import_output,
|
|
||||||
"type alias `Count` is module-local and cannot be imported",
|
|
||||||
);
|
|
||||||
|
|
||||||
let duplicate_local = write_project(
|
|
||||||
"alias-import-duplicate",
|
|
||||||
&[(
|
|
||||||
"types",
|
|
||||||
"(module types (export value))\n\n(fn value () -> i32\n 1)\n",
|
|
||||||
)],
|
|
||||||
"(module main)\n\n(import types (Count))\n\n(type Count i32)\n",
|
|
||||||
);
|
|
||||||
let duplicate_output = run_glagol(["check".as_ref(), duplicate_local.as_os_str()]);
|
|
||||||
assert_exit_code("alias import duplicate", &duplicate_output, 1);
|
|
||||||
assert_stderr_contains("alias import duplicate", &duplicate_output, "DuplicateName");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn project_rejects_exported_generic_alias_and_function_before_visibility_leakage() {
|
|
||||||
let generic_function = write_project(
|
|
||||||
"generic-function-export",
|
|
||||||
&[(
|
|
||||||
"ids",
|
|
||||||
"(module ids (export id))\n\n(fn id (type_params T) ((value T)) -> T\n value)\n",
|
|
||||||
)],
|
|
||||||
"(module main)\n",
|
|
||||||
);
|
|
||||||
let function_output = run_glagol(["check".as_ref(), generic_function.as_os_str()]);
|
|
||||||
assert_exit_code("generic function export", &function_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"generic function export",
|
|
||||||
&function_output,
|
|
||||||
"UnsupportedGenericFunction",
|
|
||||||
);
|
|
||||||
|
|
||||||
let generic_alias = write_project(
|
|
||||||
"generic-alias-export",
|
|
||||||
&[(
|
|
||||||
"types",
|
|
||||||
"(module types (export VecOf))\n\n(type VecOf (type_params T) (vec T))\n",
|
|
||||||
)],
|
|
||||||
"(module main)\n",
|
|
||||||
);
|
|
||||||
let alias_output = run_glagol(["check".as_ref(), generic_alias.as_os_str()]);
|
|
||||||
assert_exit_code("generic alias export", &alias_output, 1);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"generic alias export",
|
|
||||||
&alias_output,
|
|
||||||
"UnsupportedGenericTypeAlias",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_diagnostic_families_have_json_golden_coverage() {
|
fn project_diagnostic_families_have_json_golden_coverage() {
|
||||||
let duplicate = write_project(
|
let duplicate = write_project(
|
||||||
@ -1515,16 +1244,7 @@ fn project_build_requires_entry_main_contract() {
|
|||||||
missing_main.as_os_str(),
|
missing_main.as_os_str(),
|
||||||
]);
|
]);
|
||||||
assert_exit_code("missing entry main", &missing_output, 1);
|
assert_exit_code("missing entry main", &missing_output, 1);
|
||||||
assert_stderr_contains(
|
assert_stderr_contains("missing entry main", &missing_output, "MissingImport");
|
||||||
"missing entry main",
|
|
||||||
&missing_output,
|
|
||||||
"ProjectEntryMainMissing",
|
|
||||||
);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"missing entry main",
|
|
||||||
&missing_output,
|
|
||||||
"build/run require `(fn main () -> i32 ...)`",
|
|
||||||
);
|
|
||||||
|
|
||||||
let bad_signature = write_project(
|
let bad_signature = write_project(
|
||||||
"bad-main-signature",
|
"bad-main-signature",
|
||||||
@ -1539,16 +1259,7 @@ fn project_build_requires_entry_main_contract() {
|
|||||||
bad_signature.as_os_str(),
|
bad_signature.as_os_str(),
|
||||||
]);
|
]);
|
||||||
assert_exit_code("bad entry main signature", &bad_output, 1);
|
assert_exit_code("bad entry main signature", &bad_output, 1);
|
||||||
assert_stderr_contains(
|
assert_stderr_contains("bad entry main signature", &bad_output, "MissingImport");
|
||||||
"bad entry main signature",
|
|
||||||
&bad_output,
|
|
||||||
"ProjectEntryMainInvalidSignature",
|
|
||||||
);
|
|
||||||
assert_stderr_contains(
|
|
||||||
"bad entry main signature",
|
|
||||||
&bad_output,
|
|
||||||
"found 1 parameter(s) and return `i32`",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,239 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
path::PathBuf,
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
const CASES: &[ReservedCase] = &[
|
|
||||||
ReservedCase {
|
|
||||||
name: "generic-function",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn id (type_params T) ((value T)) -> T
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericFunction",
|
|
||||||
message: "generic function declarations are reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "generic-type-alias",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(type VecOf (type_params T) (vec T))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericTypeAlias",
|
|
||||||
message: "parameterized type aliases are reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "generic-type-parameter",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(let xs (vec T) (std.vec.i32.empty))
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericTypeParameter",
|
|
||||||
message: "generic type parameter `T` is reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "generic-vector-spelling",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (vec)
|
|
||||||
(std.vec.i32.empty))
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericTypeParameter",
|
|
||||||
message: "generic vector syntax is reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "map-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (map string i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedMapType",
|
|
||||||
message: "`map` types are reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "set-type",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> (set string)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedSetType",
|
|
||||||
message: "`set` types are reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "std-vec-empty",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.vec.empty i32)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericStandardLibraryCall",
|
|
||||||
message:
|
|
||||||
"generic standard-library call `std.vec.empty` is reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
ReservedCase {
|
|
||||||
name: "std-result-map",
|
|
||||||
source: r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.result.map (ok string i32 "a") mapper)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
code: "UnsupportedGenericStandardLibraryCall",
|
|
||||||
message:
|
|
||||||
"generic standard-library call `std.result.map` is reserved but not supported in the current beta",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_fmt_and_project_paths_reject_reserved_generic_collection_surface() {
|
|
||||||
for case in CASES {
|
|
||||||
let fixture = write_fixture(case);
|
|
||||||
|
|
||||||
let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]);
|
|
||||||
assert_rejection(&format!("{} check", case.name), &check, case);
|
|
||||||
|
|
||||||
let fmt = run_glagol([
|
|
||||||
OsStr::new("fmt"),
|
|
||||||
OsStr::new("--check"),
|
|
||||||
fixture.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_rejection(&format!("{} fmt --check", case.name), &fmt, case);
|
|
||||||
|
|
||||||
let project = write_project(case);
|
|
||||||
let project_check = run_glagol([OsStr::new("check"), project.as_os_str()]);
|
|
||||||
assert_rejection(
|
|
||||||
&format!("{} project check", case.name),
|
|
||||||
&project_check,
|
|
||||||
case,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReservedCase {
|
|
||||||
name: &'static str,
|
|
||||||
source: &'static str,
|
|
||||||
code: &'static str,
|
|
||||||
message: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_rejection(context: &str, output: &Output, case: &ReservedCase) {
|
|
||||||
assert_eq!(
|
|
||||||
output.status.code(),
|
|
||||||
Some(1),
|
|
||||||
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.stdout.is_empty(),
|
|
||||||
"{} wrote stdout:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout)
|
|
||||||
);
|
|
||||||
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(&format!("error[{}]", case.code)),
|
|
||||||
"{} human diagnostic did not contain code `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
case.code,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(&format!(" (code {})", case.code)),
|
|
||||||
"{} machine diagnostic did not contain code `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
case.code,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(case.message),
|
|
||||||
"{} stderr did not contain message `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
case.message,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!stderr.contains("beta.9"),
|
|
||||||
"{} stderr still used beta.9 wording:\n{}",
|
|
||||||
context,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fixture(case: &ReservedCase) -> PathBuf {
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let path = unique_base_path(&format!("file-{}-{}", id, case.name)).with_extension("slo");
|
|
||||||
fs::write(&path, case.source)
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_project(case: &ReservedCase) -> PathBuf {
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let root = unique_base_path(&format!("project-{}-{}", id, case.name));
|
|
||||||
let src = root.join("src");
|
|
||||||
fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err));
|
|
||||||
fs::write(
|
|
||||||
root.join("slovo.toml"),
|
|
||||||
format!(
|
|
||||||
"[project]\nname = \"reserved-beta15-{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
||||||
case.name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|err| panic!("write project manifest for `{}`: {}", case.name, err));
|
|
||||||
fs::write(src.join("main.slo"), case.source)
|
|
||||||
.unwrap_or_else(|err| panic!("write project source for `{}`: {}", case.name, err));
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unique_base_path(name: &str) -> PathBuf {
|
|
||||||
let nanos = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.map(|duration| duration.as_nanos())
|
|
||||||
.unwrap_or(0);
|
|
||||||
std::env::temp_dir().join(format!(
|
|
||||||
"glagol-reserved-beta15-{}-{}-{}",
|
|
||||||
std::process::id(),
|
|
||||||
nanos,
|
|
||||||
name
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
@ -6,7 +6,6 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
process::{Command, Output, Stdio},
|
process::{Command, Output, Stdio},
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
||||||
@ -256,61 +255,6 @@ fn exp10_diagnostics_cover_promoted_and_deferred_boundaries() {
|
|||||||
(fn main () -> i32
|
(fn main () -> i32
|
||||||
(std.fs.write_text_result "path" 1)
|
(std.fs.write_text_result "path" 1)
|
||||||
0)
|
0)
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fs-exists-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.fs.exists 1)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fs-remove-file-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.fs.remove_file_result 1)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fs-create-dir-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.fs.create_dir_result 1)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fs-open-handle-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.fs.open_text_read_result 1)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"fs-close-handle-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.fs.close_result "handle")
|
|
||||||
0)
|
|
||||||
"#,
|
"#,
|
||||||
"TypeMismatch",
|
"TypeMismatch",
|
||||||
),
|
),
|
||||||
@ -354,7 +298,7 @@ fn exp10_diagnostics_cover_promoted_and_deferred_boundaries() {
|
|||||||
(std.result.map (ok string i32 "a"))
|
(std.result.map (ok string i32 "a"))
|
||||||
0)
|
0)
|
||||||
"#,
|
"#,
|
||||||
"UnsupportedGenericStandardLibraryCall",
|
"UnsupportedStandardLibraryCall",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"promoted-name-shadow",
|
"promoted-name-shadow",
|
||||||
@ -413,348 +357,6 @@ fn exp10_diagnostics_cover_promoted_and_deferred_boundaries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn beta2_file_resource_handles_execute_in_test_runner() {
|
|
||||||
let root = temp_root("resource-test-runner");
|
|
||||||
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
|
|
||||||
let existing = root.join("resource.txt");
|
|
||||||
let missing = root.join("missing-resource.txt");
|
|
||||||
fs::write(&existing, "resource text")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", existing.display(), err));
|
|
||||||
|
|
||||||
let source = format!(
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn read_len_and_close ((path string)) -> i32
|
|
||||||
(match (std.fs.open_text_read_result path)
|
|
||||||
((ok handle)
|
|
||||||
(let text string (unwrap_ok (std.fs.read_open_text_result handle)))
|
|
||||||
(let close_status i32 (unwrap_ok (std.fs.close_result handle)))
|
|
||||||
(+ (std.string.len text) close_status))
|
|
||||||
((err code)
|
|
||||||
code)))
|
|
||||||
|
|
||||||
(fn read_after_close_err ((path string)) -> bool
|
|
||||||
(match (std.fs.open_text_read_result path)
|
|
||||||
((ok handle)
|
|
||||||
(std.fs.close_result handle)
|
|
||||||
(= (unwrap_err (std.fs.read_open_text_result handle)) 1))
|
|
||||||
((err code)
|
|
||||||
false)))
|
|
||||||
|
|
||||||
(test "resource handle open read close"
|
|
||||||
(= (read_len_and_close "{}") 13))
|
|
||||||
|
|
||||||
(test "resource handle missing open is err one"
|
|
||||||
(= (unwrap_err (std.fs.open_text_read_result "{}")) 1))
|
|
||||||
|
|
||||||
(test "resource handle closed read is err one"
|
|
||||||
(read_after_close_err "{}"))
|
|
||||||
|
|
||||||
(test "resource handle invalid close is err one"
|
|
||||||
(= (unwrap_err (std.fs.close_result -1)) 1))
|
|
||||||
"#,
|
|
||||||
slovo_path(&existing),
|
|
||||||
slovo_path(&missing),
|
|
||||||
slovo_path(&existing)
|
|
||||||
);
|
|
||||||
let fixture = write_fixture("resource-test-runner", &source);
|
|
||||||
let run = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
||||||
|
|
||||||
assert_success_stdout(
|
|
||||||
run,
|
|
||||||
concat!(
|
|
||||||
"test \"resource handle open read close\" ... ok\n",
|
|
||||||
"test \"resource handle missing open is err one\" ... ok\n",
|
|
||||||
"test \"resource handle closed read is err one\" ... ok\n",
|
|
||||||
"test \"resource handle invalid close is err one\" ... ok\n",
|
|
||||||
"4 test(s) passed\n",
|
|
||||||
),
|
|
||||||
"beta.2 resource handle test-runner output",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn beta2_file_resource_handles_emit_expected_private_runtime_shape() {
|
|
||||||
let source = r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(match (std.fs.open_text_read_result "missing.txt")
|
|
||||||
((ok handle)
|
|
||||||
(std.string.len (unwrap_ok (std.fs.read_open_text_result handle))))
|
|
||||||
((err code)
|
|
||||||
(unwrap_err (std.fs.close_result -1)))))
|
|
||||||
"#;
|
|
||||||
let fixture = write_fixture("resource-lowering", source);
|
|
||||||
let compile = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_success("compile beta.2 resource handle lowering", &compile);
|
|
||||||
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
stdout.contains("__glagol_fs_open_text_read_result")
|
|
||||||
&& stdout.contains("__glagol_fs_read_open_text_result")
|
|
||||||
&& stdout.contains("__glagol_fs_close_result")
|
|
||||||
&& stdout.contains("declare i64 @__glagol_fs_open_text_read_result(ptr)")
|
|
||||||
&& stdout.contains("declare ptr @__glagol_fs_read_open_text_result(i32)")
|
|
||||||
&& stdout.contains("declare i32 @__glagol_fs_close_result(i32)")
|
|
||||||
&& !stdout.contains("@std.fs.open_text_read_result")
|
|
||||||
&& !stdout.contains("@std.fs.read_open_text_result")
|
|
||||||
&& !stdout.contains("@std.fs.close_result"),
|
|
||||||
"LLVM output did not contain expected beta.2 resource runtime shape\nstdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn beta2_file_status_and_mutation_execute_in_test_runner() {
|
|
||||||
let root = temp_root("resource-status-test-runner");
|
|
||||||
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
|
|
||||||
let existing = root.join("existing.txt");
|
|
||||||
let removable = root.join("removable.txt");
|
|
||||||
let missing = root.join("missing.txt");
|
|
||||||
let created_dir = root.join("created-dir");
|
|
||||||
fs::write(&existing, "existing")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", existing.display(), err));
|
|
||||||
fs::write(&removable, "remove me")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", removable.display(), err));
|
|
||||||
|
|
||||||
let source = format!(
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn existing_file_status_ok ((path string)) -> bool
|
|
||||||
(if (std.fs.exists path)
|
|
||||||
(if (std.fs.is_file path)
|
|
||||||
(if (std.fs.is_dir path)
|
|
||||||
false
|
|
||||||
true)
|
|
||||||
false)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(test "fs existing file status"
|
|
||||||
(existing_file_status_ok "{}"))
|
|
||||||
|
|
||||||
(test "fs missing status false"
|
|
||||||
(if (std.fs.exists "{}")
|
|
||||||
false
|
|
||||||
true))
|
|
||||||
|
|
||||||
(test "fs create dir result ok"
|
|
||||||
(= (unwrap_ok (std.fs.create_dir_result "{}")) 0))
|
|
||||||
|
|
||||||
(test "fs created path is dir"
|
|
||||||
(std.fs.is_dir "{}"))
|
|
||||||
|
|
||||||
(test "fs create existing dir err one"
|
|
||||||
(= (unwrap_err (std.fs.create_dir_result "{}")) 1))
|
|
||||||
|
|
||||||
(test "fs remove file result ok"
|
|
||||||
(= (unwrap_ok (std.fs.remove_file_result "{}")) 0))
|
|
||||||
|
|
||||||
(test "fs removed file no longer exists"
|
|
||||||
(if (std.fs.exists "{}")
|
|
||||||
false
|
|
||||||
true))
|
|
||||||
|
|
||||||
(test "fs remove missing file err one"
|
|
||||||
(= (unwrap_err (std.fs.remove_file_result "{}")) 1))
|
|
||||||
"#,
|
|
||||||
slovo_path(&existing),
|
|
||||||
slovo_path(&missing),
|
|
||||||
slovo_path(&created_dir),
|
|
||||||
slovo_path(&created_dir),
|
|
||||||
slovo_path(&created_dir),
|
|
||||||
slovo_path(&removable),
|
|
||||||
slovo_path(&removable),
|
|
||||||
slovo_path(&missing)
|
|
||||||
);
|
|
||||||
let fixture = write_fixture("resource-status-test-runner", &source);
|
|
||||||
let run = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
||||||
|
|
||||||
assert_success_stdout(
|
|
||||||
run,
|
|
||||||
concat!(
|
|
||||||
"test \"fs existing file status\" ... ok\n",
|
|
||||||
"test \"fs missing status false\" ... ok\n",
|
|
||||||
"test \"fs create dir result ok\" ... ok\n",
|
|
||||||
"test \"fs created path is dir\" ... ok\n",
|
|
||||||
"test \"fs create existing dir err one\" ... ok\n",
|
|
||||||
"test \"fs remove file result ok\" ... ok\n",
|
|
||||||
"test \"fs removed file no longer exists\" ... ok\n",
|
|
||||||
"test \"fs remove missing file err one\" ... ok\n",
|
|
||||||
"8 test(s) passed\n",
|
|
||||||
),
|
|
||||||
"beta.2 fs status/mutation test-runner output",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn beta2_file_status_and_mutation_emit_expected_private_runtime_shape() {
|
|
||||||
let source = r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (std.fs.exists "existing.txt")
|
|
||||||
(if (std.fs.is_file "existing.txt")
|
|
||||||
(if (std.fs.is_dir "existing.txt")
|
|
||||||
1
|
|
||||||
(unwrap_ok (std.fs.remove_file_result "existing.txt")))
|
|
||||||
2)
|
|
||||||
(unwrap_err (std.fs.create_dir_result "created-dir"))))
|
|
||||||
"#;
|
|
||||||
let fixture = write_fixture("resource-status-lowering", source);
|
|
||||||
let compile = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_success("compile beta.2 fs status/mutation lowering", &compile);
|
|
||||||
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
stdout.contains("call i1 @__glagol_fs_exists")
|
|
||||||
&& stdout.contains("call i1 @__glagol_fs_is_file")
|
|
||||||
&& stdout.contains("call i1 @__glagol_fs_is_dir")
|
|
||||||
&& stdout.contains("call i32 @__glagol_fs_remove_file_result")
|
|
||||||
&& stdout.contains("call i32 @__glagol_fs_create_dir_result")
|
|
||||||
&& !stdout.contains("@std.fs.exists")
|
|
||||||
&& !stdout.contains("@std.fs.is_file")
|
|
||||||
&& !stdout.contains("@std.fs.is_dir")
|
|
||||||
&& !stdout.contains("@std.fs.remove_file_result")
|
|
||||||
&& !stdout.contains("@std.fs.create_dir_result"),
|
|
||||||
"LLVM output did not contain expected beta.2 fs status/mutation runtime shape\nstdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hosted_runtime_executes_beta2_file_resource_handles_when_clang_is_available() {
|
|
||||||
let Some(clang) = find_clang() else {
|
|
||||||
eprintln!("skipping beta.2 resource runtime smoke: set GLAGOL_CLANG or install clang");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let root = temp_root("resource-native");
|
|
||||||
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
|
|
||||||
let existing = root.join("native-resource.txt");
|
|
||||||
fs::write(&existing, "native handle")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", existing.display(), err));
|
|
||||||
|
|
||||||
let source = format!(
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(match (std.fs.open_text_read_result "{}")
|
|
||||||
((ok handle)
|
|
||||||
(let text string (unwrap_ok (std.fs.read_open_text_result handle)))
|
|
||||||
(let close_status i32 (unwrap_ok (std.fs.close_result handle)))
|
|
||||||
(+ (std.string.len text) close_status))
|
|
||||||
((err code)
|
|
||||||
code)))
|
|
||||||
"#,
|
|
||||||
slovo_path(&existing)
|
|
||||||
);
|
|
||||||
let fixture = write_fixture("resource-native", &source);
|
|
||||||
let compile = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_success("compile beta.2 resource native smoke", &compile);
|
|
||||||
|
|
||||||
let run =
|
|
||||||
compile_and_run_with_runtime(&clang, "beta2-resource-native", &compile.stdout, |_| {});
|
|
||||||
assert_eq!(
|
|
||||||
run.status.code(),
|
|
||||||
Some(13),
|
|
||||||
"beta.2 resource native smoke exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stdout),
|
|
||||||
String::from_utf8_lossy(&run.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
run.stdout.is_empty(),
|
|
||||||
"beta.2 resource native smoke wrote stdout:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stdout)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
run.stderr.is_empty(),
|
|
||||||
"beta.2 resource native smoke wrote stderr:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hosted_runtime_executes_beta2_file_status_and_mutation_when_clang_is_available() {
|
|
||||||
let Some(clang) = find_clang() else {
|
|
||||||
eprintln!(
|
|
||||||
"skipping beta.2 fs status/mutation runtime smoke: set GLAGOL_CLANG or install clang"
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let root = temp_root("resource-status-native");
|
|
||||||
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
|
|
||||||
let existing = root.join("native-existing.txt");
|
|
||||||
let removable = root.join("native-removable.txt");
|
|
||||||
let created_dir = root.join("native-created-dir");
|
|
||||||
fs::write(&existing, "native existing")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", existing.display(), err));
|
|
||||||
fs::write(&removable, "native removable")
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", removable.display(), err));
|
|
||||||
|
|
||||||
let source = format!(
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (std.fs.exists "{}")
|
|
||||||
(if (std.fs.is_file "{}")
|
|
||||||
(if (std.fs.is_dir "{}")
|
|
||||||
1
|
|
||||||
(if (= (unwrap_ok (std.fs.create_dir_result "{}")) 0)
|
|
||||||
(if (std.fs.is_dir "{}")
|
|
||||||
(if (= (unwrap_ok (std.fs.remove_file_result "{}")) 0)
|
|
||||||
(if (std.fs.exists "{}")
|
|
||||||
2
|
|
||||||
0)
|
|
||||||
3)
|
|
||||||
4)
|
|
||||||
5))
|
|
||||||
6)
|
|
||||||
7))
|
|
||||||
"#,
|
|
||||||
slovo_path(&existing),
|
|
||||||
slovo_path(&existing),
|
|
||||||
slovo_path(&existing),
|
|
||||||
slovo_path(&created_dir),
|
|
||||||
slovo_path(&created_dir),
|
|
||||||
slovo_path(&removable),
|
|
||||||
slovo_path(&removable)
|
|
||||||
);
|
|
||||||
let fixture = write_fixture("resource-status-native", &source);
|
|
||||||
let compile = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_success("compile beta.2 fs status/mutation native smoke", &compile);
|
|
||||||
|
|
||||||
let run = compile_and_run_with_runtime(
|
|
||||||
&clang,
|
|
||||||
"beta2-resource-status-native",
|
|
||||||
&compile.stdout,
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
run.status.code(),
|
|
||||||
Some(0),
|
|
||||||
"beta.2 fs status/mutation native smoke exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stdout),
|
|
||||||
String::from_utf8_lossy(&run.stderr)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
run.stdout.is_empty(),
|
|
||||||
"beta.2 fs status/mutation native smoke wrote stdout:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stdout)
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
run.stderr.is_empty(),
|
|
||||||
"beta.2 fs status/mutation native smoke wrote stderr:\n{}",
|
|
||||||
String::from_utf8_lossy(&run.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hosted_runtime_executes_exp10_results_when_clang_is_available() {
|
fn hosted_runtime_executes_exp10_results_when_clang_is_available() {
|
||||||
let Some(clang) = find_clang() else {
|
let Some(clang) = find_clang() else {
|
||||||
@ -952,16 +554,11 @@ fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn temp_root(name: &str) -> PathBuf {
|
fn temp_root(name: &str) -> PathBuf {
|
||||||
let nanos = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.expect("system clock before Unix epoch")
|
|
||||||
.as_nanos();
|
|
||||||
env::temp_dir().join(format!(
|
env::temp_dir().join(format!(
|
||||||
"glagol-exp10-host-result-{}-{}-{}-{}",
|
"glagol-exp10-host-result-{}-{}-{}",
|
||||||
name,
|
name,
|
||||||
std::process::id(),
|
std::process::id(),
|
||||||
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed),
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
||||||
nanos
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,7 @@ fn result_helpers_alpha_rejects_deferred_and_misused_std_names() {
|
|||||||
(std.result.map (ok i32 i32 1))
|
(std.result.map (ok i32 i32 1))
|
||||||
0)
|
0)
|
||||||
"#,
|
"#,
|
||||||
"UnsupportedGenericStandardLibraryCall",
|
"UnsupportedStandardLibraryCall",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"unwrap-or-deferred",
|
"unwrap-or-deferred",
|
||||||
|
|||||||
@ -1,267 +0,0 @@
|
|||||||
use std::{
|
|
||||||
env, fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_manifest_records_success_report_stdout_and_program_args() {
|
|
||||||
let Some(clang) = find_clang() else {
|
|
||||||
eprintln!("skipping run manifest success report: set GLAGOL_CLANG or install clang");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let source = write_fixture(
|
|
||||||
"success",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(print_string "beta22-out")
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"slo",
|
|
||||||
);
|
|
||||||
let manifest_path = temp_path("success-manifest", "manifest.slo");
|
|
||||||
|
|
||||||
let mut command = Command::new(compiler_path());
|
|
||||||
command
|
|
||||||
.arg("run")
|
|
||||||
.arg(&source)
|
|
||||||
.arg("--manifest")
|
|
||||||
.arg(&manifest_path)
|
|
||||||
.arg("--")
|
|
||||||
.arg("alpha")
|
|
||||||
.arg("two words")
|
|
||||||
.arg("--literal")
|
|
||||||
.env("GLAGOL_CLANG", &clang);
|
|
||||||
configure_clang_runtime_env(&mut command, &clang);
|
|
||||||
|
|
||||||
let output = command.output().expect("run glagol success manifest");
|
|
||||||
assert_success_stdout("run manifest success", &output, "beta22-out\n");
|
|
||||||
|
|
||||||
let manifest = read_manifest(&manifest_path);
|
|
||||||
assert!(
|
|
||||||
manifest.contains(" (mode run)\n")
|
|
||||||
&& manifest.contains(" (success true)\n")
|
|
||||||
&& manifest.contains(" (run-report\n")
|
|
||||||
&& manifest.contains(" (exit-status 0)\n")
|
|
||||||
&& manifest.contains(" (stdout \"beta22-out\\n\")\n")
|
|
||||||
&& manifest.contains(" (stderr \"\")\n")
|
|
||||||
&& manifest.contains(" (arg \"alpha\")\n")
|
|
||||||
&& manifest.contains(" (arg \"two words\")\n")
|
|
||||||
&& manifest.contains(" (arg \"--literal\")\n"),
|
|
||||||
"successful run manifest mismatch:\n{}",
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_manifest_records_nonzero_report_and_preserves_program_stderr() {
|
|
||||||
let Some(clang) = find_clang() else {
|
|
||||||
eprintln!("skipping run manifest nonzero report: set GLAGOL_CLANG or install clang");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let source = write_fixture(
|
|
||||||
"nonzero",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(import_c beta22_stderr () -> i32)
|
|
||||||
|
|
||||||
(fn emit_stderr () -> i32
|
|
||||||
(unsafe
|
|
||||||
(beta22_stderr)))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(emit_stderr)
|
|
||||||
7)
|
|
||||||
"#,
|
|
||||||
"slo",
|
|
||||||
);
|
|
||||||
let c_source = write_fixture(
|
|
||||||
"nonzero-stderr",
|
|
||||||
r#"
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
int beta22_stderr(void) {
|
|
||||||
fputs("beta22-err\n", stderr);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
"#,
|
|
||||||
"c",
|
|
||||||
);
|
|
||||||
let manifest_path = temp_path("nonzero-manifest", "manifest.slo");
|
|
||||||
|
|
||||||
let mut command = Command::new(compiler_path());
|
|
||||||
command
|
|
||||||
.arg("run")
|
|
||||||
.arg(&source)
|
|
||||||
.arg("--link-c")
|
|
||||||
.arg(&c_source)
|
|
||||||
.arg("--manifest")
|
|
||||||
.arg(&manifest_path)
|
|
||||||
.env("GLAGOL_CLANG", &clang);
|
|
||||||
configure_clang_runtime_env(&mut command, &clang);
|
|
||||||
|
|
||||||
let output = command.output().expect("run glagol nonzero manifest");
|
|
||||||
assert_exit_code("run manifest nonzero", &output, 7);
|
|
||||||
assert_eq!(output.stdout, b"", "nonzero run stdout drifted");
|
|
||||||
assert_eq!(output.stderr, b"beta22-err\n", "nonzero run stderr drifted");
|
|
||||||
|
|
||||||
let manifest = read_manifest(&manifest_path);
|
|
||||||
assert!(
|
|
||||||
manifest.contains(" (mode run)\n")
|
|
||||||
&& manifest.contains(" (success false)\n")
|
|
||||||
&& manifest.contains(" (run-report\n")
|
|
||||||
&& manifest.contains(" (exit-status 7)\n")
|
|
||||||
&& manifest.contains(" (stdout \"\")\n")
|
|
||||||
&& manifest.contains(" (stderr \"beta22-err\\n\")\n")
|
|
||||||
&& manifest.contains(" (args)\n"),
|
|
||||||
"nonzero run manifest mismatch:\n{}",
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn run_manifest_source_failure_does_not_record_fake_run_report() {
|
|
||||||
let source = write_fixture(
|
|
||||||
"source-failure",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
true)
|
|
||||||
"#,
|
|
||||||
"slo",
|
|
||||||
);
|
|
||||||
let manifest_path = temp_path("source-failure-manifest", "manifest.slo");
|
|
||||||
|
|
||||||
let output = run_glagol([
|
|
||||||
"run".as_ref(),
|
|
||||||
source.as_os_str(),
|
|
||||||
"--manifest".as_ref(),
|
|
||||||
manifest_path.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_exit_code("run manifest source failure", &output, 1);
|
|
||||||
|
|
||||||
let manifest = read_manifest(&manifest_path);
|
|
||||||
assert!(
|
|
||||||
manifest.contains(" (mode run)\n")
|
|
||||||
&& manifest.contains(" (success false)\n")
|
|
||||||
&& manifest.contains(" (kind diagnostics)\n")
|
|
||||||
&& manifest.contains("TypeMismatch")
|
|
||||||
&& !manifest.contains(" (run-report\n"),
|
|
||||||
"source failure manifest included fake run report:\n{}",
|
|
||||||
manifest
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<const N: usize>(args: [&std::ffi::OsStr; N]) -> Output {
|
|
||||||
Command::new(compiler_path())
|
|
||||||
.args(args)
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compiler_path() -> &'static str {
|
|
||||||
env!("CARGO_BIN_EXE_glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fixture(name: &str, source: &str, extension: &str) -> PathBuf {
|
|
||||||
let path = temp_path(name, extension);
|
|
||||||
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn temp_path(name: &str, extension: &str) -> PathBuf {
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let mut path = env::temp_dir();
|
|
||||||
path.push(format!(
|
|
||||||
"glagol-run-manifest-beta22-{}-{}-{}.{}",
|
|
||||||
std::process::id(),
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
extension
|
|
||||||
));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_manifest(path: &Path) -> String {
|
|
||||||
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success_stdout(context: &str, output: &Output, expected: &str) {
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
expected,
|
|
||||||
"{} stdout mismatch",
|
|
||||||
context
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
output.stderr.is_empty(),
|
|
||||||
"{} wrote stderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_exit_code(context: &str, output: &Output, expected: i32) {
|
|
||||||
assert_eq!(
|
|
||||||
output.status.code(),
|
|
||||||
Some(expected),
|
|
||||||
"{} exit code mismatch\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_clang() -> Option<PathBuf> {
|
|
||||||
if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) {
|
|
||||||
let path = PathBuf::from(path);
|
|
||||||
if path.is_file() {
|
|
||||||
return Some(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang");
|
|
||||||
if hermetic_clang.is_file() {
|
|
||||||
return Some(hermetic_clang);
|
|
||||||
}
|
|
||||||
|
|
||||||
find_on_path("clang")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_on_path(name: &str) -> Option<PathBuf> {
|
|
||||||
let path = env::var_os("PATH")?;
|
|
||||||
env::split_paths(&path)
|
|
||||||
.map(|dir| dir.join(name))
|
|
||||||
.find(|candidate| candidate.is_file())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn configure_clang_runtime_env(command: &mut Command, clang: &Path) {
|
|
||||||
if !clang.starts_with("/tmp/glagol-clang-root") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let root = Path::new("/tmp/glagol-clang-root");
|
|
||||||
let lib64 = root.join("usr/lib64");
|
|
||||||
let lib = root.join("usr/lib");
|
|
||||||
let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default();
|
|
||||||
let mut paths = vec![lib64, lib];
|
|
||||||
paths.extend(env::split_paths(&existing));
|
|
||||||
let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH");
|
|
||||||
command.env("LD_LIBRARY_PATH", joined);
|
|
||||||
}
|
|
||||||
@ -7,18 +7,13 @@ use std::{
|
|||||||
|
|
||||||
const EXPECTED_STD_STRING_OUTPUT: &str = concat!(
|
const EXPECTED_STD_STRING_OUTPUT: &str = concat!(
|
||||||
"test \"explicit std string len concat\" ... ok\n",
|
"test \"explicit std string len concat\" ... ok\n",
|
||||||
"test \"explicit std string byte_at_result wrapper\" ... ok\n",
|
|
||||||
"test \"explicit std string slice_result wrapper\" ... ok\n",
|
|
||||||
"test \"explicit std string boundary wrappers\" ... ok\n",
|
|
||||||
"test \"explicit std string parse result wrappers\" ... ok\n",
|
"test \"explicit std string parse result wrappers\" ... ok\n",
|
||||||
"test \"explicit std string parse option wrappers\" ... ok\n",
|
"test \"explicit std string parse option wrappers\" ... ok\n",
|
||||||
"test \"explicit std string parse integer fallbacks\" ... ok\n",
|
"test \"explicit std string parse integer fallbacks\" ... ok\n",
|
||||||
"test \"explicit std string parse float bool fallbacks\" ... ok\n",
|
"test \"explicit std string parse float bool fallbacks\" ... ok\n",
|
||||||
"test \"explicit std string parse custom fallbacks\" ... ok\n",
|
"test \"explicit std string parse custom fallbacks\" ... ok\n",
|
||||||
"test \"explicit std string search helpers\" ... ok\n",
|
|
||||||
"test \"explicit std string ascii trim helpers\" ... ok\n",
|
|
||||||
"test \"explicit std string helpers all\" ... ok\n",
|
"test \"explicit std string helpers all\" ... ok\n",
|
||||||
"12 test(s) passed\n",
|
"7 test(s) passed\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
const EXPECTED_STD_NUM_OUTPUT: &str = concat!(
|
const EXPECTED_STD_NUM_OUTPUT: &str = concat!(
|
||||||
@ -37,16 +32,6 @@ fn explicit_std_string_import_loads_repo_root_standard_source() {
|
|||||||
&[
|
&[
|
||||||
"len",
|
"len",
|
||||||
"concat",
|
"concat",
|
||||||
"byte_at_result",
|
|
||||||
"slice_result",
|
|
||||||
"starts_with",
|
|
||||||
"ends_with",
|
|
||||||
"contains",
|
|
||||||
"index_of_option",
|
|
||||||
"last_index_of_option",
|
|
||||||
"trim_ascii_start",
|
|
||||||
"trim_ascii_end",
|
|
||||||
"trim_ascii",
|
|
||||||
"parse_i32_result",
|
"parse_i32_result",
|
||||||
"parse_i32_option",
|
"parse_i32_option",
|
||||||
"parse_u32_result",
|
"parse_u32_result",
|
||||||
|
|||||||
@ -14,17 +14,6 @@ const EXPECTED_TEST_OUTPUT: &str = concat!(
|
|||||||
"test \"explicit local fs read text facade\" ... ok\n",
|
"test \"explicit local fs read text facade\" ... ok\n",
|
||||||
"test \"explicit local fs write text result facade\" ... ok\n",
|
"test \"explicit local fs write text result facade\" ... ok\n",
|
||||||
"test \"explicit local fs read text result facade\" ... ok\n",
|
"test \"explicit local fs read text result facade\" ... ok\n",
|
||||||
"test \"explicit local fs read text via handle facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs open read close handle facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs closed handle read err facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs invalid close err facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs exists file facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs exists missing facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs create dir result facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs is dir facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs create dir ok facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs remove file result facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs remove file ok facade\" ... ok\n",
|
|
||||||
"test \"explicit local fs read text option missing facade\" ... ok\n",
|
"test \"explicit local fs read text option missing facade\" ... ok\n",
|
||||||
"test \"explicit local fs read text option present facade\" ... ok\n",
|
"test \"explicit local fs read text option present facade\" ... ok\n",
|
||||||
"test \"explicit local fs read text or missing facade\" ... ok\n",
|
"test \"explicit local fs read text or missing facade\" ... ok\n",
|
||||||
@ -45,7 +34,7 @@ const EXPECTED_TEST_OUTPUT: &str = concat!(
|
|||||||
"test \"explicit local fs typed option facade\" ... ok\n",
|
"test \"explicit local fs typed option facade\" ... ok\n",
|
||||||
"test \"explicit local fs typed custom fallback facade\" ... ok\n",
|
"test \"explicit local fs typed custom fallback facade\" ... ok\n",
|
||||||
"test \"explicit local fs facade all\" ... ok\n",
|
"test \"explicit local fs facade all\" ... ok\n",
|
||||||
"35 test(s) passed\n",
|
"24 test(s) passed\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[
|
const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[
|
||||||
@ -54,18 +43,6 @@ const STANDARD_FS_SOURCE_FACADE_ALPHA: &[&str] = &[
|
|||||||
"read_text_option",
|
"read_text_option",
|
||||||
"write_text_status",
|
"write_text_status",
|
||||||
"write_text_result",
|
"write_text_result",
|
||||||
"exists",
|
|
||||||
"is_file",
|
|
||||||
"is_dir",
|
|
||||||
"remove_file_result",
|
|
||||||
"create_dir_result",
|
|
||||||
"remove_file_ok",
|
|
||||||
"create_dir_ok",
|
|
||||||
"open_text_read_result",
|
|
||||||
"read_open_text_result",
|
|
||||||
"close_result",
|
|
||||||
"read_text_via_handle_result",
|
|
||||||
"close_ok",
|
|
||||||
"read_text_or",
|
"read_text_or",
|
||||||
"write_text_ok",
|
"write_text_ok",
|
||||||
"read_i32_result",
|
"read_i32_result",
|
||||||
@ -167,10 +144,7 @@ fn assert_local_fs_fixture_is_source_authored(project: &Path) {
|
|||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-u64.txt\"")
|
&& main.contains("\"glagol-std-layout-local-fs-alpha-u64.txt\"")
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-f64.txt\"")
|
&& main.contains("\"glagol-std-layout-local-fs-alpha-f64.txt\"")
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-bool.txt\"")
|
&& main.contains("\"glagol-std-layout-local-fs-alpha-bool.txt\"")
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-invalid.txt\"")
|
&& main.contains("\"glagol-std-layout-local-fs-alpha-invalid.txt\""),
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-dir\"")
|
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-dir-helper\"")
|
|
||||||
&& main.contains("\"glagol-std-layout-local-fs-alpha-remove.txt\""),
|
|
||||||
"fs fixture must use deterministic relative paths and text content"
|
"fs fixture must use deterministic relative paths and text content"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
@ -179,15 +153,7 @@ fn assert_local_fs_fixture_is_source_authored(project: &Path) {
|
|||||||
&& fs_source.contains("(std.fs.read_text path)")
|
&& fs_source.contains("(std.fs.read_text path)")
|
||||||
&& fs_source.contains("(std.fs.read_text_result path)")
|
&& fs_source.contains("(std.fs.read_text_result path)")
|
||||||
&& fs_source.contains("(std.fs.write_text path text)")
|
&& fs_source.contains("(std.fs.write_text path text)")
|
||||||
&& fs_source.contains("(std.fs.write_text_result path text)")
|
&& fs_source.contains("(std.fs.write_text_result path text)"),
|
||||||
&& fs_source.contains("(std.fs.exists path)")
|
|
||||||
&& fs_source.contains("(std.fs.is_file path)")
|
|
||||||
&& fs_source.contains("(std.fs.is_dir path)")
|
|
||||||
&& fs_source.contains("(std.fs.remove_file_result path)")
|
|
||||||
&& fs_source.contains("(std.fs.create_dir_result path)")
|
|
||||||
&& fs_source.contains("(std.fs.open_text_read_result path)")
|
|
||||||
&& fs_source.contains("(std.fs.read_open_text_result handle)")
|
|
||||||
&& fs_source.contains("(std.fs.close_result handle)"),
|
|
||||||
"fs.slo must stay a local source facade over fs calls plus local string and result bridge helpers"
|
"fs.slo must stay a local source facade over fs calls plus local string and result bridge helpers"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
|
|||||||
@ -72,17 +72,6 @@ const EXPECTED_STD_FS_OUTPUT: &str = concat!(
|
|||||||
"test \"explicit std fs read text facade\" ... ok\n",
|
"test \"explicit std fs read text facade\" ... ok\n",
|
||||||
"test \"explicit std fs write text result facade\" ... ok\n",
|
"test \"explicit std fs write text result facade\" ... ok\n",
|
||||||
"test \"explicit std fs read text result facade\" ... ok\n",
|
"test \"explicit std fs read text result facade\" ... ok\n",
|
||||||
"test \"explicit std fs read text via handle facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs open read close handle facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs closed handle read err facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs invalid close err facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs exists file facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs exists missing facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs create dir result facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs is dir facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs create dir ok facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs remove file result facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs remove file ok facade\" ... ok\n",
|
|
||||||
"test \"explicit std fs read text option missing facade\" ... ok\n",
|
"test \"explicit std fs read text option missing facade\" ... ok\n",
|
||||||
"test \"explicit std fs read text option present facade\" ... ok\n",
|
"test \"explicit std fs read text option present facade\" ... ok\n",
|
||||||
"test \"explicit std fs read text or missing facade\" ... ok\n",
|
"test \"explicit std fs read text or missing facade\" ... ok\n",
|
||||||
@ -103,7 +92,7 @@ const EXPECTED_STD_FS_OUTPUT: &str = concat!(
|
|||||||
"test \"explicit std fs typed option facade\" ... ok\n",
|
"test \"explicit std fs typed option facade\" ... ok\n",
|
||||||
"test \"explicit std fs typed custom fallback facade\" ... ok\n",
|
"test \"explicit std fs typed custom fallback facade\" ... ok\n",
|
||||||
"test \"explicit std fs facade all\" ... ok\n",
|
"test \"explicit std fs facade all\" ... ok\n",
|
||||||
"35 test(s) passed\n",
|
"24 test(s) passed\n",
|
||||||
);
|
);
|
||||||
|
|
||||||
const EXPECTED_STD_PROCESS_OUTPUT: &str = concat!(
|
const EXPECTED_STD_PROCESS_OUTPUT: &str = concat!(
|
||||||
@ -215,18 +204,6 @@ fn explicit_std_fs_import_loads_repo_root_standard_source() {
|
|||||||
"read_text_option",
|
"read_text_option",
|
||||||
"write_text_status",
|
"write_text_status",
|
||||||
"write_text_result",
|
"write_text_result",
|
||||||
"exists",
|
|
||||||
"is_file",
|
|
||||||
"is_dir",
|
|
||||||
"remove_file_result",
|
|
||||||
"create_dir_result",
|
|
||||||
"remove_file_ok",
|
|
||||||
"create_dir_ok",
|
|
||||||
"open_text_read_result",
|
|
||||||
"read_open_text_result",
|
|
||||||
"close_result",
|
|
||||||
"read_text_via_handle_result",
|
|
||||||
"close_ok",
|
|
||||||
"read_text_or",
|
"read_text_or",
|
||||||
"write_text_ok",
|
"write_text_ok",
|
||||||
"read_i32_result",
|
"read_i32_result",
|
||||||
|
|||||||
@ -1,226 +0,0 @@
|
|||||||
use std::{
|
|
||||||
env,
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn standard_json_lowers_to_private_runtime_helper() {
|
|
||||||
let fixture = write_fixture(
|
|
||||||
"lowering",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (= (std.json.quote_string "slo\"vo") "\"slo\\\"vo\"")
|
|
||||||
0
|
|
||||||
1))
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let output = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_success("compile standard json lowering", &output);
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
stdout.contains("declare ptr @__glagol_json_quote_string(ptr)")
|
|
||||||
&& stdout.contains("call ptr @__glagol_json_quote_string(")
|
|
||||||
&& !stdout.contains("@std.json.quote_string"),
|
|
||||||
"standard json LLVM shape drifted\nstdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_runner_reports_deterministic_json_quoting() {
|
|
||||||
let fixture = write_fixture(
|
|
||||||
"test-runner",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(test "quote plain string"
|
|
||||||
(= (std.json.quote_string "slovo") "\"slovo\""))
|
|
||||||
|
|
||||||
(test "quote embedded quote"
|
|
||||||
(= (std.json.quote_string "slo\"vo") "\"slo\\\"vo\""))
|
|
||||||
|
|
||||||
(test "quote backslash"
|
|
||||||
(= (std.json.quote_string "slo\\vo") "\"slo\\\\vo\""))
|
|
||||||
|
|
||||||
(test "quote newline tab"
|
|
||||||
(= (std.json.quote_string "line\n\tnext") "\"line\\n\\tnext\""))
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
||||||
assert_success("run standard json tests", &output);
|
|
||||||
assert_eq!(
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
concat!(
|
|
||||||
"test \"quote plain string\" ... ok\n",
|
|
||||||
"test \"quote embedded quote\" ... ok\n",
|
|
||||||
"test \"quote backslash\" ... ok\n",
|
|
||||||
"test \"quote newline tab\" ... ok\n",
|
|
||||||
"4 test(s) passed\n",
|
|
||||||
),
|
|
||||||
"standard json test runner stdout drifted"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn standard_json_diagnostics_cover_promoted_and_deferred_names() {
|
|
||||||
let cases = [
|
|
||||||
(
|
|
||||||
"quote-arity",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.json.quote_string))
|
|
||||||
"#,
|
|
||||||
"ArityMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"quote-type",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.json.quote_string 42)
|
|
||||||
0)
|
|
||||||
"#,
|
|
||||||
"TypeMismatch",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"parse-object-deferred",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(std.json.parse_object_result "{}"))
|
|
||||||
"#,
|
|
||||||
"UnsupportedStandardLibraryCall",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"promoted-shadow",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn std.json.quote_string ((value string)) -> string
|
|
||||||
value)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (= (std.json.quote_string "x") "\"x\"") 0 1))
|
|
||||||
"#,
|
|
||||||
"DuplicateFunction",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
for (name, source, diagnostic) in cases {
|
|
||||||
let fixture = write_fixture(name, source);
|
|
||||||
let output = run_glagol([fixture.as_os_str()]);
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!output.status.success(),
|
|
||||||
"compiler unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
name,
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stdout.is_empty(),
|
|
||||||
"rejected compile wrote stdout:\n{}",
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(diagnostic),
|
|
||||||
"diagnostic `{}` was not reported for `{}`\nstderr:\n{}",
|
|
||||||
diagnostic,
|
|
||||||
name,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn hosted_json_quote_smoke_when_clang_is_available() {
|
|
||||||
if !clang_is_available() {
|
|
||||||
eprintln!("skipping standard json runtime smoke: set GLAGOL_CLANG or install clang");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fixture = write_fixture(
|
|
||||||
"hosted",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (= (std.json.quote_string "line\nnext") "\"line\\nnext\"")
|
|
||||||
0
|
|
||||||
1))
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
let binary = fixture.with_extension(env::consts::EXE_EXTENSION);
|
|
||||||
let build = run_glagol([
|
|
||||||
OsStr::new("build"),
|
|
||||||
fixture.as_os_str(),
|
|
||||||
OsStr::new("-o"),
|
|
||||||
binary.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success("build standard json hosted smoke", &build);
|
|
||||||
|
|
||||||
let run = Command::new(&binary)
|
|
||||||
.output()
|
|
||||||
.unwrap_or_else(|err| panic!("run `{}`: {}", binary.display(), err));
|
|
||||||
assert_success("run standard json hosted smoke", &run);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
||||||
let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed);
|
|
||||||
let dir = env::temp_dir().join(format!("glagol-standard-json-{id}-{name}"));
|
|
||||||
fs::create_dir_all(&dir).unwrap_or_else(|err| panic!("create `{}`: {}", dir.display(), err));
|
|
||||||
let path = dir.join("main.slo");
|
|
||||||
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clang_is_available() -> bool {
|
|
||||||
if env::var_os("GLAGOL_CLANG").is_some() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Command::new("clang")
|
|
||||||
.arg("--version")
|
|
||||||
.output()
|
|
||||||
.is_ok_and(|output| output.status.success())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<std::ffi::OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
.args(args)
|
|
||||||
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success(context: &str, output: &Output) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
||||||
}
|
|
||||||
@ -1,313 +0,0 @@
|
|||||||
use std::{
|
|
||||||
env,
|
|
||||||
ffi::OsStr,
|
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Command, Output},
|
|
||||||
sync::atomic::{AtomicUsize, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
|
|
||||||
const EXPECTED_TEST_OUTPUT: &str = concat!(
|
|
||||||
"test \"explicit std json document scalar string\" ... ok\n",
|
|
||||||
"test \"explicit std json document scalar bool\" ... ok\n",
|
|
||||||
"test \"explicit std json document scalar integer\" ... ok\n",
|
|
||||||
"test \"explicit std json document scalar float null\" ... ok\n",
|
|
||||||
"test \"explicit std json document scalar failures\" ... ok\n",
|
|
||||||
"test \"explicit std json document scalar all\" ... ok\n",
|
|
||||||
"6 test(s) passed\n",
|
|
||||||
);
|
|
||||||
|
|
||||||
const STANDARD_JSON_DOCUMENT_SCALAR_BETA21: &[&str] = &[
|
|
||||||
"parse_string_document_result",
|
|
||||||
"parse_bool_document_result",
|
|
||||||
"parse_i32_document_result",
|
|
||||||
"parse_u32_document_result",
|
|
||||||
"parse_i64_document_result",
|
|
||||||
"parse_u64_document_result",
|
|
||||||
"parse_f64_document_result",
|
|
||||||
"parse_null_document_result",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn explicit_std_json_document_scalar_helpers_check_and_test() {
|
|
||||||
let project = write_project(
|
|
||||||
"std-json-document-scalar-beta21",
|
|
||||||
r#"
|
|
||||||
(module main)
|
|
||||||
|
|
||||||
(import std.json (parse_string_document_result parse_bool_document_result parse_i32_document_result parse_u32_document_result parse_i64_document_result parse_u64_document_result parse_f64_document_result parse_null_document_result))
|
|
||||||
|
|
||||||
(fn imported_json_document_string_ok () -> bool
|
|
||||||
(if (= (std.result.unwrap_ok (parse_string_document_result " \"slovo\" ")) "slovo")
|
|
||||||
(= (std.result.unwrap_ok (parse_string_document_result "\n\t\"slo\\\"vo\"\t")) "slo\"vo")
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn imported_json_document_bool_ok () -> bool
|
|
||||||
(if (std.result.unwrap_ok (parse_bool_document_result " true "))
|
|
||||||
(= (std.result.unwrap_ok (parse_bool_document_result "\nfalse\t")) false)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn imported_json_document_integer_ok () -> bool
|
|
||||||
(if (= (std.result.unwrap_ok (parse_i32_document_result " -7 ")) -7)
|
|
||||||
(if (= (std.result.unwrap_ok (parse_u32_document_result "\n7\t")) 7u32)
|
|
||||||
(if (= (std.result.unwrap_ok (parse_i64_document_result " -8 ")) -8i64)
|
|
||||||
(= (std.result.unwrap_ok (parse_u64_document_result "9 ")) 9u64)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn imported_json_document_float_null_ok () -> bool
|
|
||||||
(if (= (std.result.unwrap_ok (parse_f64_document_result " 1e2 ")) 100.0)
|
|
||||||
(std.result.unwrap_ok (parse_null_document_result "\nnull\t"))
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn imported_json_document_failures_ok () -> bool
|
|
||||||
(if (= (std.result.unwrap_err (parse_string_document_result "\"slovo\" x")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_bool_document_result " TRUE ")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_i32_document_result " 01 ")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_u32_document_result " -1 ")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_i64_document_result " 8i64 ")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_u64_document_result " ")) 1)
|
|
||||||
(if (= (std.result.unwrap_err (parse_f64_document_result " 01.0 ")) 1)
|
|
||||||
(= (std.result.unwrap_err (parse_null_document_result " NULL ")) 1)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn imported_json_document_scalar_all_ok () -> bool
|
|
||||||
(if (imported_json_document_string_ok)
|
|
||||||
(if (imported_json_document_bool_ok)
|
|
||||||
(if (imported_json_document_integer_ok)
|
|
||||||
(if (imported_json_document_float_null_ok)
|
|
||||||
(imported_json_document_failures_ok)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false)
|
|
||||||
false))
|
|
||||||
|
|
||||||
(fn main () -> i32
|
|
||||||
(if (imported_json_document_scalar_all_ok)
|
|
||||||
42
|
|
||||||
1))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar string"
|
|
||||||
(imported_json_document_string_ok))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar bool"
|
|
||||||
(imported_json_document_bool_ok))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar integer"
|
|
||||||
(imported_json_document_integer_ok))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar float null"
|
|
||||||
(imported_json_document_float_null_ok))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar failures"
|
|
||||||
(imported_json_document_failures_ok))
|
|
||||||
|
|
||||||
(test "explicit std json document scalar all"
|
|
||||||
(= (main) 42))
|
|
||||||
"#,
|
|
||||||
);
|
|
||||||
|
|
||||||
let source = read(&project.join("src/main.slo"));
|
|
||||||
let std_json = read(&std_json_path());
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!project.join("src/json.slo").exists(),
|
|
||||||
"beta21 fixture must exercise repo-root std.json, not a local module copy"
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
source.starts_with("(module main)\n\n(import std.json ("),
|
|
||||||
"beta21 fixture must use an explicit std.json import"
|
|
||||||
);
|
|
||||||
assert_json_document_scalar_helpers_are_source_authored(&std_json);
|
|
||||||
|
|
||||||
let fmt = run_glagol([
|
|
||||||
OsStr::new("fmt"),
|
|
||||||
OsStr::new("--check"),
|
|
||||||
project.as_os_str(),
|
|
||||||
]);
|
|
||||||
assert_success("std json document scalar fmt --check", &fmt);
|
|
||||||
|
|
||||||
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
|
|
||||||
assert_success_stdout(check, "", "std json document scalar check");
|
|
||||||
|
|
||||||
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
|
|
||||||
assert_success_stdout(test, EXPECTED_TEST_OUTPUT, "std json document scalar test");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn json_document_scalar_helpers_are_not_compiler_known_runtime_calls() {
|
|
||||||
let std_json = read(&std_json_path());
|
|
||||||
assert_json_document_scalar_helpers_are_source_authored(&std_json);
|
|
||||||
|
|
||||||
for helper in STANDARD_JSON_DOCUMENT_SCALAR_BETA21 {
|
|
||||||
let fixture = write_fixture(
|
|
||||||
helper,
|
|
||||||
&format!(
|
|
||||||
"(module main)\n\n(fn main () -> i32\n (std.result.unwrap_err (std.json.{} \"invalid\")))\n",
|
|
||||||
helper
|
|
||||||
),
|
|
||||||
);
|
|
||||||
let output = run_glagol([fixture.as_os_str()]);
|
|
||||||
assert_failure_stderr_contains(
|
|
||||||
&format!("direct std.json.{} runtime call", helper),
|
|
||||||
&output,
|
|
||||||
&format!(
|
|
||||||
"standard library call `std.json.{}` is not supported",
|
|
||||||
helper
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_json_document_scalar_helpers_are_source_authored(std_json: &str) {
|
|
||||||
assert!(
|
|
||||||
std_json.starts_with("(module json (export "),
|
|
||||||
"lib/std/json.slo must stay a source-authored module export"
|
|
||||||
);
|
|
||||||
|
|
||||||
for helper in STANDARD_JSON_DOCUMENT_SCALAR_BETA21 {
|
|
||||||
assert!(
|
|
||||||
std_json.contains(&format!("(fn {} ", helper)),
|
|
||||||
"lib/std/json.slo is missing source facade `{}`",
|
|
||||||
helper
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
!std_json.contains(&format!("std.json.{}", helper)),
|
|
||||||
"std.json.{} must remain source-authored, not a compiler-known runtime call",
|
|
||||||
helper
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for private_prefix in [
|
|
||||||
"__glagol_json_parse_string_document",
|
|
||||||
"__glagol_json_parse_bool_document",
|
|
||||||
"__glagol_json_parse_i32_document",
|
|
||||||
"__glagol_json_parse_u32_document",
|
|
||||||
"__glagol_json_parse_i64_document",
|
|
||||||
"__glagol_json_parse_u64_document",
|
|
||||||
"__glagol_json_parse_f64_document",
|
|
||||||
"__glagol_json_parse_null_document",
|
|
||||||
] {
|
|
||||||
assert!(
|
|
||||||
!std_json.contains(private_prefix),
|
|
||||||
"lib/std/json.slo must not introduce private JSON document runtime symbol `{}`",
|
|
||||||
private_prefix
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_glagol<I, S>(args: I) -> Output
|
|
||||||
where
|
|
||||||
I: IntoIterator<Item = S>,
|
|
||||||
S: AsRef<OsStr>,
|
|
||||||
{
|
|
||||||
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
||||||
.args(args)
|
|
||||||
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
|
|
||||||
.output()
|
|
||||||
.expect("run glagol")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_project(name: &str, source: &str) -> PathBuf {
|
|
||||||
let root = temp_root(name);
|
|
||||||
let src = root.join("src");
|
|
||||||
fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err));
|
|
||||||
fs::write(
|
|
||||||
root.join("slovo.toml"),
|
|
||||||
format!(
|
|
||||||
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
||||||
name
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap_or_else(|err| panic!("write project manifest: {}", err));
|
|
||||||
fs::write(src.join("main.slo"), source.trim_start())
|
|
||||||
.unwrap_or_else(|err| panic!("write project main.slo: {}", err));
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
||||||
let mut path = env::temp_dir();
|
|
||||||
path.push(format!(
|
|
||||||
"glagol-standard-json-document-scalar-beta21-{}-{}-{}.slo",
|
|
||||||
name,
|
|
||||||
std::process::id(),
|
|
||||||
NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
|
|
||||||
));
|
|
||||||
fs::write(&path, source.trim_start())
|
|
||||||
.unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
||||||
path
|
|
||||||
}
|
|
||||||
|
|
||||||
fn temp_root(name: &str) -> PathBuf {
|
|
||||||
let root = env::temp_dir().join(format!(
|
|
||||||
"glagol-standard-json-document-scalar-beta21-{}-{}-{}",
|
|
||||||
name,
|
|
||||||
std::process::id(),
|
|
||||||
NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
|
|
||||||
));
|
|
||||||
let _ = fs::remove_dir_all(&root);
|
|
||||||
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
fn std_json_path() -> PathBuf {
|
|
||||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../lib/std/json.slo")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(path: &Path) -> String {
|
|
||||||
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success(context: &str, output: &Output) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
output.status.success(),
|
|
||||||
"{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
output.status.code(),
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_success_stdout(output: Output, expected: &str, context: &str) {
|
|
||||||
assert_success(context, &output);
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
assert_eq!(stdout, expected, "{}", context);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_failure_stderr_contains(context: &str, output: &Output, needle: &str) {
|
|
||||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
||||||
assert!(
|
|
||||||
!output.status.success(),
|
|
||||||
"{} unexpectedly passed\nstdout:\n{}\nstderr:\n{}",
|
|
||||||
context,
|
|
||||||
stdout,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stdout.is_empty(),
|
|
||||||
"{} rejected compile wrote stdout:\n{}",
|
|
||||||
context,
|
|
||||||
stdout
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
stderr.contains(needle),
|
|
||||||
"{} stderr did not contain `{}`:\n{}",
|
|
||||||
context,
|
|
||||||
needle,
|
|
||||||
stderr
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user