Release 1.0.0-beta.10 developer experience api discovery
This commit is contained in:
parent
5a3ed0c41e
commit
f8f0862ee3
62
.llm/BETA_10_DEVELOPER_EXPERIENCE_API_DISCOVERY.md
Normal file
62
.llm/BETA_10_DEVELOPER_EXPERIENCE_API_DISCOVERY.md
Normal file
@ -0,0 +1,62 @@
|
||||
# 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`
|
||||
47
.llm/reviews/BETA_10_RELEASE_REVIEW.md
Normal file
47
.llm/reviews/BETA_10_RELEASE_REVIEW.md
Normal file
@ -0,0 +1,47 @@
|
||||
# 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.
|
||||
55
README.md
55
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
|
||||
documents.
|
||||
|
||||
Current release: `1.0.0-beta.9`.
|
||||
Current release: `1.0.0-beta.10`.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
@ -24,7 +24,7 @@ scripts/ local release and document tooling
|
||||
|
||||
## Beta Scope
|
||||
|
||||
`1.0.0-beta.9` keeps the `1.0.0-beta` language baseline, includes the
|
||||
`1.0.0-beta.10` keeps the `1.0.0-beta` language baseline, includes the
|
||||
`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
|
||||
@ -32,11 +32,13 @@ 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, and the `1.0.0-beta.9` collection alias unification and
|
||||
generic reservation slice. The language baseline supports practical local
|
||||
generic reservation slice, plus the `1.0.0-beta.10` developer-experience API
|
||||
discovery slice. The language baseline supports practical local
|
||||
command-line, file, and loopback-network programs with:
|
||||
|
||||
- modules, explicit imports, packages, and local workspaces
|
||||
- `new`, `check`, `fmt`, `test`, `doc`, and `build`
|
||||
- `new`, `check`, `fmt`, `test`, `doc`, `symbols`, `build`, `run`, and
|
||||
`clean`
|
||||
- `i32`, `i64`, `u32`, `u64`, `f64`, `bool`, `string`, and internal `unit`
|
||||
- structs, enums, fixed arrays, concrete vectors, option/result families, and
|
||||
current `match`
|
||||
@ -47,15 +49,24 @@ command-line, file, and loopback-network programs with:
|
||||
- JSON string quoting and compact JSON text construction through `std.json`
|
||||
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
|
||||
|
||||
Still deferred before stable: generics, maps/sets, broad package registry
|
||||
semantics, DNS/TLS/async networking, LSP/watch/debug-adapter guarantees,
|
||||
stable ABI and layout, and a stable standard-library compatibility freeze.
|
||||
The generated standard-library API catalog is a beta discovery aid: it lists
|
||||
exported helper signatures from `lib/std/*.slo`, normalizes module-local
|
||||
concrete aliases such as `VecI32` and `ResultU64` to their concrete public
|
||||
types, and omits non-exported helpers and `(type ...)` aliases.
|
||||
`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.
|
||||
|
||||
The next likely language slice after `1.0.0-beta.9` should continue from the
|
||||
reserved generic and collection diagnostics without claiming executable
|
||||
generics, maps, sets, traits, inference, monomorphization, iterators, ABI
|
||||
stability, or a standard-library API freeze until the contract and gates are
|
||||
explicit.
|
||||
Still deferred before stable: executable generics, maps/sets, broad package
|
||||
registry semantics, DNS/TLS/async networking, LSP/watch/debug-adapter
|
||||
guarantees, stable ABI and layout, runtime changes for generic collections,
|
||||
and a stable standard-library compatibility freeze.
|
||||
|
||||
The next likely language slice after `1.0.0-beta.10` should continue from the
|
||||
developer-experience and reserved generic/collection diagnostics without
|
||||
claiming executable generics, maps, sets, traits, inference,
|
||||
monomorphization, iterators, ABI stability, runtime changes, or a
|
||||
standard-library API freeze until the contract and gates are explicit.
|
||||
|
||||
## Build And Test
|
||||
|
||||
@ -230,6 +241,26 @@ 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.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Language Manifest](docs/language/MANIFEST.md)
|
||||
|
||||
2
compiler/Cargo.lock
generated
2
compiler/Cargo.lock
generated
@ -4,4 +4,4 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "glagol"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.10"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "glagol"
|
||||
version = "1.0.0-beta.9"
|
||||
version = "1.0.0-beta.10"
|
||||
edition = "2021"
|
||||
description = "Glagol, the first compiler for the Slovo language"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@ -11,6 +11,7 @@ mod project;
|
||||
mod scaffold;
|
||||
mod sexpr;
|
||||
mod std_runtime;
|
||||
mod symbols;
|
||||
mod test_runner;
|
||||
mod token;
|
||||
mod types;
|
||||
@ -94,19 +95,26 @@ fn run_invocation_inner(invocation: Invocation) -> ! {
|
||||
if invocation.mode == Mode::Doc {
|
||||
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 {
|
||||
run_fmt_action(invocation);
|
||||
}
|
||||
|
||||
let project_capable_mode = matches!(invocation.mode, Mode::Check | Mode::Build | Mode::Run)
|
||||
|| (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test");
|
||||
let project_capable_mode = matches!(
|
||||
invocation.mode,
|
||||
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) {
|
||||
match invocation.mode {
|
||||
Mode::Check => run_project_check(invocation),
|
||||
Mode::RunTests => run_project_test(invocation),
|
||||
Mode::Build => run_project_build(invocation),
|
||||
Mode::Run => run_project_run(invocation),
|
||||
_ => unreachable!("project mode is selected only for check/test/build/run"),
|
||||
Mode::Symbols => run_project_symbols(invocation),
|
||||
_ => unreachable!("project mode is selected only for check/test/build/run/symbols"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,6 +219,25 @@ fn run_project_run(invocation: Invocation) -> ! {
|
||||
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) -> ! {
|
||||
let rendered = render_source_diagnostics_multi(
|
||||
&failure.diagnostics,
|
||||
@ -233,11 +260,52 @@ fn exit_project_failure(invocation: Invocation, failure: project::ProjectTestFai
|
||||
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) -> ! {
|
||||
if mode == Mode::RunTests {
|
||||
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 result = match mode {
|
||||
Mode::EmitLlvm => driver::compile_to_llvm(&invocation.path, source),
|
||||
@ -247,6 +315,7 @@ fn run_text_mode(invocation: Invocation, mode: Mode, source: &str) -> ! {
|
||||
Mode::InspectLoweringSurface => driver::inspect_lowering_surface(&invocation.path, source),
|
||||
Mode::InspectLoweringChecked => driver::inspect_lowering_checked(&invocation.path, source),
|
||||
Mode::CheckTests => driver::check_tests(&invocation.path, source),
|
||||
Mode::Symbols => unreachable!("symbols mode is handled separately"),
|
||||
Mode::RunTests => unreachable!("test mode is handled separately"),
|
||||
Mode::Build => unreachable!("build is handled separately"),
|
||||
Mode::Run => unreachable!("run is handled separately"),
|
||||
@ -315,6 +384,48 @@ 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) -> ! {
|
||||
match scaffold::create_project(
|
||||
&invocation.path,
|
||||
@ -1210,7 +1321,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
||||
command_line: command_line.clone(),
|
||||
})?);
|
||||
}
|
||||
"check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc"
|
||||
"check" | "fmt" | "test" | "build" | "run" | "clean" | "new" | "doc" | "symbols"
|
||||
if path.is_none() =>
|
||||
{
|
||||
let next = match arg.as_str() {
|
||||
@ -1222,6 +1333,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
|
||||
"clean" => Mode::Clean,
|
||||
"new" => Mode::New,
|
||||
"doc" => Mode::Doc,
|
||||
"symbols" => Mode::Symbols,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
set_mode(
|
||||
@ -1489,6 +1601,7 @@ enum Mode {
|
||||
Clean,
|
||||
New,
|
||||
Doc,
|
||||
Symbols,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
@ -1507,6 +1620,7 @@ impl Mode {
|
||||
Self::Clean => "clean",
|
||||
Self::New => "new",
|
||||
Self::Doc => "doc",
|
||||
Self::Symbols => "symbols",
|
||||
}
|
||||
}
|
||||
|
||||
@ -1523,6 +1637,7 @@ impl Mode {
|
||||
Self::Clean => "no-output",
|
||||
Self::New => "no-output",
|
||||
Self::Doc => "documentation",
|
||||
Self::Symbols => "symbols",
|
||||
}
|
||||
}
|
||||
|
||||
@ -2425,6 +2540,6 @@ fn normalized_output_path(path: &str) -> Option<PathBuf> {
|
||||
|
||||
fn print_usage() {
|
||||
eprintln!(
|
||||
"usage: glagol [check|fmt|test|build|run|clean] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <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 [--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"
|
||||
"usage: glagol [check|fmt|test|build|run|clean|symbols] [--json-diagnostics] [--no-color] [--manifest <path>] [--link-c <path>] [-o <path>] [--filter <substring>] <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>] <file.slo>\n glagol --version"
|
||||
);
|
||||
}
|
||||
|
||||
667
compiler/src/symbols.rs
Normal file
667
compiler/src/symbols.rs
Normal file
@ -0,0 +1,667 @@
|
||||
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,
|
||||
}
|
||||
184
compiler/tests/symbols_beta10.rs
Normal file
184
compiler/tests/symbols_beta10.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
process::{Command, Output},
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[test]
|
||||
fn symbols_single_file_reports_editor_facing_source_metadata() {
|
||||
let source = r#"(module docs (export main))
|
||||
|
||||
(type Count i32)
|
||||
|
||||
(struct Point
|
||||
(x i32)
|
||||
(y i32))
|
||||
|
||||
(enum Status Ready (Blocked i32))
|
||||
|
||||
(fn main () -> i32
|
||||
0)
|
||||
|
||||
(test "main returns zero"
|
||||
(= (main) 0))
|
||||
"#;
|
||||
let file = write_file("symbols-file", source);
|
||||
|
||||
let output = run_glagol(["symbols".as_ref(), file.as_os_str()]);
|
||||
|
||||
assert_success("symbols file", &output);
|
||||
assert!(output.stderr.is_empty(), "symbols wrote stderr");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(stdout.starts_with("(symbols\n"));
|
||||
assert!(stdout.contains(" (schema slovo.symbols)\n"));
|
||||
assert!(stdout.contains(" (version \"1.0.0-beta.10\")\n"));
|
||||
assert!(stdout.contains(&format!(" (path \"{}\")\n", file.display())));
|
||||
assert!(stdout.contains(" (name \"docs\")\n"));
|
||||
assert!(stdout.contains(" (module_span (span "));
|
||||
assert!(stdout.contains("(range (start (line 1) (column 1))"));
|
||||
assert!(stdout.contains(" (name \"main\")\n"));
|
||||
assert!(stdout.contains(" (name \"Count\")\n"));
|
||||
assert!(stdout.contains(" (name \"Point\")\n"));
|
||||
assert!(stdout.contains(" (name \"Status\")\n"));
|
||||
assert!(stdout.contains(" (name \"Blocked\")\n"));
|
||||
assert!(stdout.contains(" (name \"main returns zero\")\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbols_project_writes_output_and_manifest_without_stdout() {
|
||||
let project = write_project(
|
||||
"symbols-project",
|
||||
&[(
|
||||
"math",
|
||||
"(module math (export one))\n\n(fn one () -> i32\n 1)\n",
|
||||
)],
|
||||
"(module main)\n\n(import math (one))\n\n(fn main () -> i32\n (one))\n",
|
||||
);
|
||||
let output_path = unique_path("symbols-project-out").with_extension("sexpr");
|
||||
let manifest_path = unique_path("symbols-project-manifest").with_extension("slo");
|
||||
|
||||
let output = run_glagol([
|
||||
"symbols".as_ref(),
|
||||
project.as_os_str(),
|
||||
"-o".as_ref(),
|
||||
output_path.as_os_str(),
|
||||
"--manifest".as_ref(),
|
||||
manifest_path.as_os_str(),
|
||||
]);
|
||||
|
||||
assert_success("symbols project", &output);
|
||||
assert!(output.stdout.is_empty(), "symbols -o wrote stdout");
|
||||
assert!(output.stderr.is_empty(), "symbols project wrote stderr");
|
||||
|
||||
let symbols = fs::read_to_string(&output_path).expect("read symbols output");
|
||||
assert!(symbols.contains(" (name \"main\")\n"));
|
||||
assert!(symbols.contains(" (name \"math\")\n"));
|
||||
assert!(symbols.contains(" (module \"math\")\n"));
|
||||
assert!(symbols.contains(" (name \"one\")\n"));
|
||||
assert!(symbols.contains(" (imports\n"));
|
||||
assert!(symbols.contains(" (functions\n"));
|
||||
|
||||
let manifest = fs::read_to_string(&manifest_path).expect("read symbols manifest");
|
||||
assert!(manifest.contains(" (mode symbols)\n"));
|
||||
assert!(manifest.contains(" (kind symbols)\n"));
|
||||
assert!(manifest.contains(&format!(" (path \"{}\")\n", output_path.display())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbols_workspace_includes_package_names_deterministically() {
|
||||
let workspace = unique_path("symbols-workspace");
|
||||
let scaffold = run_glagol([
|
||||
"new".as_ref(),
|
||||
workspace.as_os_str(),
|
||||
"--template".as_ref(),
|
||||
"workspace".as_ref(),
|
||||
]);
|
||||
assert_success("workspace scaffold", &scaffold);
|
||||
|
||||
let first = run_glagol(["symbols".as_ref(), workspace.as_os_str()]);
|
||||
let second = run_glagol(["symbols".as_ref(), workspace.as_os_str()]);
|
||||
|
||||
assert_success("symbols workspace first", &first);
|
||||
assert_success("symbols workspace second", &second);
|
||||
assert_eq!(
|
||||
first.stdout, second.stdout,
|
||||
"workspace symbols output was not deterministic"
|
||||
);
|
||||
let stdout = String::from_utf8_lossy(&first.stdout);
|
||||
assert!(stdout.contains(" (package \"app\")\n"));
|
||||
assert!(stdout.contains(" (package \"libutil\")\n"));
|
||||
assert!(stdout.contains(" (module \"libutil.libutil\")\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn symbols_usage_is_part_of_the_public_cli_surface() {
|
||||
let output = run_glagol([std::ffi::OsStr::new("--help")]);
|
||||
|
||||
assert_success("glagol help", &output);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(stderr.contains("glagol symbols <file.slo|project|workspace>"));
|
||||
}
|
||||
|
||||
fn write_project(name: &str, modules: &[(&str, &str)], main: &str) -> PathBuf {
|
||||
let project = unique_path(name);
|
||||
fs::create_dir_all(project.join("src")).expect("create project src");
|
||||
fs::write(
|
||||
project.join("slovo.toml"),
|
||||
format!(
|
||||
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
||||
name
|
||||
),
|
||||
)
|
||||
.expect("write manifest");
|
||||
for (module, source) in modules {
|
||||
fs::write(project.join("src").join(format!("{}.slo", module)), source)
|
||||
.expect("write module");
|
||||
}
|
||||
fs::write(project.join("src/main.slo"), main).expect("write main");
|
||||
project
|
||||
}
|
||||
|
||||
fn write_file(name: &str, source: &str) -> PathBuf {
|
||||
let path = unique_path(name).with_extension("slo");
|
||||
fs::write(&path, source).expect("write fixture");
|
||||
path
|
||||
}
|
||||
|
||||
fn unique_path(name: &str) -> PathBuf {
|
||||
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system clock before UNIX_EPOCH")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!(
|
||||
"glagol-symbols-beta10-{}-{}-{}-{}",
|
||||
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(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)
|
||||
);
|
||||
}
|
||||
@ -254,18 +254,36 @@ Why ninth: generics are important, but they should be introduced after the
|
||||
compiler, docs, tests, and stdlib have enough real pressure to validate the
|
||||
design.
|
||||
|
||||
### 10. Developer Experience
|
||||
### 10. Developer Experience API Discovery
|
||||
|
||||
Goal: make Slovo comfortable for repeated daily use.
|
||||
Goal: make Slovo comfortable for repeated daily use by making the current
|
||||
standard-library API surface easier to inspect before deeper editor work.
|
||||
|
||||
Work:
|
||||
|
||||
- upgrade the generated `lib/std` API catalog from exported names to exact
|
||||
exported helper signatures
|
||||
- normalize module-local beta.8/beta.9 concrete aliases in public signatures
|
||||
so local aliases do not leak into docs
|
||||
- validate that exported helpers have matching `(fn ...)` forms
|
||||
- keep non-exported helpers and `(type ...)` aliases out of the public catalog
|
||||
- language-server diagnostics and document symbols
|
||||
- editor-facing symbol metadata for files, projects, and workspaces
|
||||
- project watch mode
|
||||
- generated API documentation for local packages and `lib/std`
|
||||
- generated API documentation for local packages
|
||||
- clearer benchmark harness output
|
||||
- machine-readable diagnostics stability policy
|
||||
|
||||
Released in `1.0.0-beta.10`: `docs/language/STDLIB_API.md` now lists exact
|
||||
exported helper signatures from `lib/std/*.slo`, and the renderer validates
|
||||
exported helper forms while normalizing module-local aliases to concrete
|
||||
public types. `glagol symbols <file.slo|project|workspace>` now emits
|
||||
deterministic `slovo.symbols` metadata for editor integrations without
|
||||
starting an LSP server. This is beta API discovery only; it does not add
|
||||
executable generics, maps, sets, runtime changes, or a stable standard-library
|
||||
API freeze. LSP, watch mode, local-package API docs, benchmark-output work,
|
||||
and a machine-readable diagnostics stability policy remain deferred.
|
||||
|
||||
Why tenth: editor support matters, but it should follow a stable enough syntax,
|
||||
project model, and diagnostic model.
|
||||
|
||||
|
||||
@ -10,11 +10,47 @@ integration/readiness release, not the first real beta.
|
||||
|
||||
## Unreleased
|
||||
|
||||
Next scoped Glagol work is expected to continue after the `1.0.0-beta.9`
|
||||
reservation update.
|
||||
Next scoped Glagol work is expected to continue after the `1.0.0-beta.10`
|
||||
developer-experience API discovery and symbol-metadata update.
|
||||
|
||||
No unreleased compiler scope is committed here yet.
|
||||
|
||||
## 1.0.0-beta.10
|
||||
|
||||
Release label: `1.0.0-beta.10`
|
||||
|
||||
Release date: 2026-05-22
|
||||
|
||||
Release state: developer-experience API discovery and symbol-metadata update
|
||||
|
||||
### Summary
|
||||
|
||||
The beta.10 tooling/docs slice upgrades generated standard-library API
|
||||
discovery and adds deterministic editor-facing symbol metadata without
|
||||
changing source-language execution semantics.
|
||||
|
||||
- `scripts/render-stdlib-api-doc.js` now validates exported `lib/std` helper
|
||||
names against matching `(fn ...)` forms.
|
||||
- `docs/language/STDLIB_API.md` now lists exact exported helper signatures
|
||||
rather than names only.
|
||||
- Module-local concrete aliases are normalized in public signatures, so beta.8
|
||||
and beta.9 aliases do not leak into the public catalog.
|
||||
- Non-exported helper functions and `(type ...)` aliases remain omitted from
|
||||
the catalog.
|
||||
- `glagol symbols <file.slo|project|workspace>` emits deterministic
|
||||
`slovo.symbols` S-expression metadata for modules, imports, exports,
|
||||
aliases, structs, enums, functions, tests, source spans/ranges, and
|
||||
workspace package labels.
|
||||
- `compiler/tests/symbols_beta10.rs` covers single-file, project, workspace,
|
||||
`-o`/manifest, and help-surface behavior.
|
||||
|
||||
### Explicit Deferrals
|
||||
|
||||
This release does not implement source-language runtime changes, executable
|
||||
generics, generic stdlib dispatch, generic vectors, maps, sets, iterators,
|
||||
collection unification, stable collection ABI/layout, new standard-library
|
||||
runtime APIs, an LSP server, SARIF, watch mode, or daemon protocols.
|
||||
|
||||
## 1.0.0-beta.9
|
||||
|
||||
Release label: `1.0.0-beta.9`
|
||||
|
||||
@ -22,9 +22,9 @@ general-purpose beta release.
|
||||
|
||||
A Glagol feature is done only when it has parser/lowerer support, checker behavior, diagnostics for invalid forms, backend behavior or explicit unsupported diagnostics, and tests.
|
||||
|
||||
Current stage: `1.0.0-beta.9`, released on 2026-05-22 as a compiler
|
||||
reservation update for generic-shaped collection and stdlib syntax. It keeps
|
||||
the `1.0.0-beta` language/compiler support baseline and includes the
|
||||
Current stage: `1.0.0-beta.10`, released on 2026-05-22 as a
|
||||
developer-experience API discovery and symbol-metadata update. It keeps the `1.0.0-beta`
|
||||
language/compiler support baseline and includes the
|
||||
`1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2` runtime/resource
|
||||
foundation release, the `1.0.0-beta.3` standard-library stabilization release,
|
||||
the `1.0.0-beta.4` language-usability diagnostics release, the
|
||||
@ -36,10 +36,17 @@ matching source-facade, test-runner, hosted smoke, and benchmark-scaffold
|
||||
coverage, the `1.0.0-beta.8` concrete type alias foundation update, and
|
||||
focused diagnostics/formatter/project-mode gates for reserved generic
|
||||
functions, parameterized aliases, generic type parameters, maps, sets, and
|
||||
future generic stdlib calls.
|
||||
future generic stdlib calls. The beta.10 tooling/docs slice upgrades
|
||||
generated `lib/std` API discovery so public documentation lists exact exported
|
||||
helper signatures after alias normalization, and adds
|
||||
`glagol symbols <file.slo|project|workspace>` for deterministic
|
||||
editor-facing source metadata over modules, imports, exports, aliases,
|
||||
structs, enums, functions, tests, spans/ranges, and workspace package labels.
|
||||
It adds no source-language runtime behavior.
|
||||
|
||||
Next stage target: post-`1.0.0-beta.9` collection/generic planning. Generic
|
||||
vectors, maps, sets, generic stdlib dispatch, and collection unification remain
|
||||
Next stage target: post-`1.0.0-beta.10` developer-experience and
|
||||
collection/generic planning. Generic vectors, maps, sets, generic stdlib
|
||||
dispatch, runtime collection changes, and collection unification remain
|
||||
unimplemented until a later scoped contract promotes them explicitly.
|
||||
|
||||
The final experimental precursor scope is `exp-125`. Its unsigned direct-value
|
||||
|
||||
@ -8,7 +8,7 @@ Historical `exp-*` releases listed here are experimental maturity milestones.
|
||||
The pushed tag `v2.0.0-beta.1` is historical. It is now documented as an
|
||||
experimental integration/readiness release, not as a beta maturity claim.
|
||||
|
||||
The current release is `1.0.0-beta.9`, published on 2026-05-22. It keeps the
|
||||
The current release is `1.0.0-beta.10`, published on 2026-05-22. It keeps the
|
||||
`1.0.0-beta` language surface, includes the first post-beta tooling/install
|
||||
hardening bundle from `1.0.0-beta.1`, and adds the first runtime/resource
|
||||
foundation bundle from `1.0.0-beta.2` plus the first standard-library
|
||||
@ -19,12 +19,48 @@ loopback networking foundation bundle from `1.0.0-beta.6`, and the first
|
||||
serialization/data-interchange foundation bundle from `1.0.0-beta.7`, and the
|
||||
first concrete type alias foundation from `1.0.0-beta.8`, plus the first
|
||||
collection alias unification and generic reservation slice from
|
||||
`1.0.0-beta.9`.
|
||||
`1.0.0-beta.9`, and the first developer-experience API discovery slice from
|
||||
`1.0.0-beta.10`.
|
||||
|
||||
## Unreleased
|
||||
|
||||
No unreleased language scope is committed here yet.
|
||||
|
||||
## 1.0.0-beta.10
|
||||
|
||||
Release label: `1.0.0-beta.10`
|
||||
|
||||
Release name: Developer Experience API Discovery
|
||||
|
||||
Release date: 2026-05-22
|
||||
|
||||
Status: released beta documentation/API-discovery update on the
|
||||
`1.0.0-beta` language baseline.
|
||||
|
||||
`1.0.0-beta.10` upgrades `docs/language/STDLIB_API.md` from an exported-name
|
||||
index to a generated exported-signature catalog and adds an editor-facing
|
||||
symbol metadata command:
|
||||
|
||||
- `scripts/render-stdlib-api-doc.js` parses each `lib/std/*.slo` module,
|
||||
validates that exported helpers have matching `(fn ...)` forms, and renders
|
||||
their public parameter and return types.
|
||||
- Module-local concrete aliases introduced in beta.8 and used across beta.9
|
||||
collection/value-family facades are normalized before rendering, so public
|
||||
signatures show concrete types such as `(vec i32)`, `(option string)`, and
|
||||
`(result u64 i32)` instead of local names such as `VecI32` or `ResultU64`.
|
||||
- Non-exported helper functions and `(type ...)` aliases remain omitted from
|
||||
the public catalog.
|
||||
- `glagol symbols <file.slo|project|workspace>` emits deterministic
|
||||
`slovo.symbols` S-expression metadata for modules, imports, exports,
|
||||
aliases, structs, enums, functions, tests, source spans/ranges, and
|
||||
workspace package labels.
|
||||
|
||||
This release does not add executable generics, generic aliases,
|
||||
parameterized aliases, maps, sets, traits, inference, monomorphization,
|
||||
iterators, runtime changes, new compiler-known runtime names, stable
|
||||
ABI/layout promises, an LSP server, watch mode, SARIF, daemon protocols, or a
|
||||
stable standard-library API freeze.
|
||||
|
||||
## 1.0.0-beta.9
|
||||
|
||||
Release label: `1.0.0-beta.9`
|
||||
|
||||
@ -10,8 +10,8 @@ Long-horizon planning lives in
|
||||
release train from the historical `v2.0.0-beta.1` tag toward and beyond the
|
||||
first real general-purpose beta Slovo contract.
|
||||
|
||||
Current stage: `1.0.0-beta.9`, released on 2026-05-22 as the first post-beta
|
||||
collection alias unification and generic reservation update. It keeps the `1.0.0-beta` language contract and
|
||||
Current stage: `1.0.0-beta.10`, released on 2026-05-22 as the first post-beta
|
||||
developer-experience API discovery update. It keeps the `1.0.0-beta` language contract and
|
||||
includes the `1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2`
|
||||
runtime/resource foundation release, the `1.0.0-beta.3` standard-library
|
||||
stabilization release, the `1.0.0-beta.4` language-usability diagnostics
|
||||
@ -20,16 +20,22 @@ release, the `1.0.0-beta.5` package/workspace discipline release, and a narrow
|
||||
opaque `i32` handles and concrete `result` families, plus a narrow `std.json`
|
||||
source facade for compact JSON text construction, plus top-level module-local
|
||||
transparent aliases for supported concrete target types, plus local alias use
|
||||
inside the current concrete vector, option, and result facades. JSON parsing,
|
||||
recursive JSON values, executable generics, generic aliases, parameterized
|
||||
aliases, cross-module alias visibility, maps/sets, traits, inference,
|
||||
monomorphization, iterators, DNS, TLS, UDP, async IO, non-loopback binding,
|
||||
HTTP frameworks, rich host-error ADTs, stable ABI/layout, and a stable
|
||||
standard-library API freeze remain deferred.
|
||||
inside the current concrete vector, option, and result facades, plus a
|
||||
generated standard-library API catalog that lists exact exported helper
|
||||
signatures with module-local aliases normalized to concrete public types, plus
|
||||
`glagol symbols` deterministic source metadata for files, projects, and
|
||||
workspaces. JSON
|
||||
parsing, recursive JSON values, executable generics, generic aliases,
|
||||
parameterized aliases, cross-module alias visibility, maps/sets, traits,
|
||||
inference, monomorphization, iterators, runtime changes for generic
|
||||
collections, DNS, TLS, UDP, async IO, non-loopback binding, HTTP frameworks,
|
||||
rich host-error ADTs, stable ABI/layout, and a stable standard-library API
|
||||
freeze remain deferred.
|
||||
|
||||
Next stage target: continue from the reserved generic/map/set diagnostics and
|
||||
collection-unification design pressure without claiming executable generics
|
||||
until the exact scope is frozen from the manifest and roadmap.
|
||||
Next stage target: continue from developer-experience and reserved
|
||||
generic/map/set diagnostics without claiming executable generics, an LSP/watch
|
||||
protocol, or stable standard-library APIs until the exact scope is frozen from
|
||||
the manifest and roadmap.
|
||||
|
||||
The final experimental precursor scope is `exp-125`, defined in
|
||||
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# Slovo v1 Specification
|
||||
|
||||
Status: living beta contract for `1.0.0-beta`, with the post-beta
|
||||
`1.0.0-beta.1` tooling/install update and `1.0.0-beta.2` runtime/resource
|
||||
foundation update. The language contract integrates
|
||||
`1.0.0-beta.1` tooling/install update, `1.0.0-beta.2` runtime/resource
|
||||
foundation update, and `1.0.0-beta.10` developer-experience API discovery
|
||||
update. The language contract integrates
|
||||
promoted language slices through `exp-125` and the historical publication
|
||||
baseline through `exp-123`. `1.0.0-beta` is the first real general-purpose
|
||||
beta release. `exp-125` completed the unsigned numeric and stdlib breadth
|
||||
@ -118,7 +119,16 @@ Current v1 release surface and explicit experimental targets:
|
||||
existing concrete vector, option, and result source facades may use
|
||||
module-local concrete aliases internally while exported helper behavior and
|
||||
cross-module types remain concrete; executable generics, maps, sets, traits,
|
||||
inference, monomorphization, iterators, and stable ABI/layout remain deferred
|
||||
inference, monomorphization, iterators, stable ABI/layout, and runtime
|
||||
changes remain out of scope, once the matching Glagol gates pass
|
||||
- `1.0.0-beta.10` developer-experience API discovery target: the generated
|
||||
`docs/language/STDLIB_API.md` catalog lists exact exported helper
|
||||
signatures from `lib/std/*.slo`, normalizes module-local concrete aliases to
|
||||
public concrete types, and omits non-exported helpers and `(type ...)`
|
||||
aliases; `glagol symbols` emits deterministic editor-facing source metadata
|
||||
for files, projects, and workspaces; this is beta API discovery only, not
|
||||
executable generics, maps, sets, runtime changes, LSP/watch, or a stable
|
||||
`1.0.0` standard-library freeze
|
||||
- `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two
|
||||
`string` values and returns an immutable runtime-owned `string`; existing
|
||||
string equality, length, printing, locals, parameters, returns, and calls work
|
||||
@ -943,6 +953,14 @@ structs, functions, and tests. It is a documentation generator, not a semantic
|
||||
reflection API, and it does not expose stable typed-core, debug metadata,
|
||||
source-map, ABI, layout, runtime reflection, or compiler-internal contracts.
|
||||
|
||||
`glagol symbols <file.slo|project|workspace>` emits deterministic
|
||||
machine-readable S-expression metadata using the `slovo.symbols` schema. It
|
||||
reports module paths, package labels when available, imports, exports,
|
||||
module-local type aliases, structs, enums, functions, tests, and source
|
||||
spans/ranges. It is an editor-integration building block, not an LSP server,
|
||||
watch protocol, semantic reflection API, debug metadata, source-map contract,
|
||||
ABI/layout promise, or stable compiler-internal contract.
|
||||
|
||||
The repository must provide a local release-gate script or documented command
|
||||
entry point that runs the full v1 release gate for the matching Slovo and
|
||||
Glagol release without network, tag, push, or release-publication side effects.
|
||||
@ -5494,7 +5512,7 @@ Successful modes that intentionally produce no primary text may use:
|
||||
Recommended v1 modes are `check`, `format`, `emit-llvm`,
|
||||
`inspect-lowering`, and `test`. Recommended output and artifact kinds are
|
||||
`diagnostics`, `formatted-source`, `llvm-ir`, `lowering-inspector`, `stdout`,
|
||||
`stderr`, `no-output`, and `test-report`.
|
||||
`stderr`, `symbols`, `no-output`, and `test-report`.
|
||||
|
||||
A failed invocation still writes a manifest when the tool mode supports
|
||||
manifests. In that case `(success false)` is paired with a diagnostics artifact
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -5,7 +5,7 @@
|
||||
Sanjin Gumbarevic<br>
|
||||
hermeticum_lab@protonmail.com
|
||||
|
||||
Publication release: `1.0.0-beta.9`
|
||||
Publication release: `1.0.0-beta.10`
|
||||
|
||||
Technical behavior baseline: compiler and language support through
|
||||
`1.0.0-beta`; tooling and install workflow through `1.0.0-beta.1`;
|
||||
@ -15,12 +15,14 @@ stabilization through `1.0.0-beta.3`; language-usability diagnostics through
|
||||
loopback networking foundation through `1.0.0-beta.6`;
|
||||
serialization/data-interchange foundation through `1.0.0-beta.7`;
|
||||
concrete type alias foundation through `1.0.0-beta.8`;
|
||||
generic and collection reservation diagnostics through `1.0.0-beta.9`
|
||||
generic and collection reservation diagnostics through `1.0.0-beta.9`;
|
||||
developer-experience API discovery and symbol metadata through
|
||||
`1.0.0-beta.10`
|
||||
|
||||
Date: 2026-05-22
|
||||
|
||||
Evidence source: paired Slovo/Glagol monorepo verification and benchmark
|
||||
reruns from the public source tree; beta.9 release-gate verification from the
|
||||
reruns from the public source tree; beta.10 release-gate verification from the
|
||||
public monorepo
|
||||
|
||||
Maturity: beta
|
||||
@ -32,7 +34,7 @@ Slovo. It exists to make the language support boundary inspectable: tokens,
|
||||
S-expression tree, AST, typed AST, LLVM IR, hosted native executable, tests,
|
||||
diagnostics, and release documents should agree.
|
||||
|
||||
The current publication release, `1.0.0-beta.9`, keeps the first real
|
||||
The current publication release, `1.0.0-beta.10`, keeps the first real
|
||||
general-purpose beta toolchain baseline from `1.0.0-beta` and records the
|
||||
first post-beta tooling/install hardening update plus the first
|
||||
runtime/resource foundation update plus the first standard-library
|
||||
@ -40,21 +42,23 @@ stabilization update plus the first language-usability diagnostics update and
|
||||
the first local package/workspace discipline update plus the first loopback
|
||||
networking foundation update plus the first serialization/data-interchange
|
||||
foundation update plus the first concrete type alias foundation and the first
|
||||
generic/collection reservation diagnostic update. The beta
|
||||
generic/collection reservation diagnostic update, plus the first
|
||||
developer-experience API discovery and symbol-metadata update. The beta
|
||||
baseline includes the completed `u32` / `u64` unsigned compiler and stdlib
|
||||
breadth scope, the narrow `std.net` loopback TCP runtime family, the narrow
|
||||
`std.json.quote_string` runtime family, transparent concrete alias parsing and
|
||||
erasure, explicit beta9 diagnostics for reserved generic/map/set shapes, and
|
||||
the current ten-scaffold benchmark suite. This paper records the
|
||||
current beta
|
||||
the current ten-scaffold benchmark suite. It also includes alias-normalized
|
||||
stdlib signature generation and deterministic `glagol symbols` metadata for
|
||||
files, projects, and workspaces. This paper records the current beta
|
||||
implementation surface, the benchmark method and results, the distinction
|
||||
between Glagol and Lisp-family implementations, the beta.1 tooling update, the
|
||||
beta.2 runtime/resource foundation, the beta.3 standard-library stabilization
|
||||
slice, the beta.4 diagnostics usability slice, the beta.5 package discipline
|
||||
slice, the beta.6 networking foundation slice, the beta.7 serialization
|
||||
foundation slice, the beta.8 concrete alias foundation slice, the beta.9
|
||||
generic/collection reservation slice, and the compiler path from beta to
|
||||
stable.
|
||||
generic/collection reservation slice, the beta.10 API-discovery and
|
||||
symbol-metadata slice, and the compiler path from beta to stable.
|
||||
|
||||
## 1. Compiler Thesis
|
||||
|
||||
@ -316,8 +320,9 @@ diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
|
||||
`1.0.0-beta.7` adds a narrow JSON construction foundation, and
|
||||
`1.0.0-beta.8` adds transparent concrete type aliases, and `1.0.0-beta.9`
|
||||
adds focused diagnostics for reserved generic, map, set, and future generic
|
||||
stdlib forms. None of these post-beta slices claims changed benchmark
|
||||
performance. The beta.7 `json-quote-loop`
|
||||
stdlib forms, and `1.0.0-beta.10` adds alias-normalized stdlib signature
|
||||
generation plus deterministic `glagol symbols` metadata. None of these
|
||||
post-beta slices claims changed benchmark performance. The beta.7 `json-quote-loop`
|
||||
scaffold is present for local follow-up timing and is not part of the exp-123
|
||||
nine-row result table below.
|
||||
|
||||
@ -407,7 +412,7 @@ coverage and compatibility:
|
||||
- package behavior becoming stable before dependency, manifest, and versioning
|
||||
rules are precise
|
||||
|
||||
## 9. Path Beyond `1.0.0-beta.9`
|
||||
## 9. Path Beyond `1.0.0-beta.10`
|
||||
|
||||
Glagol now implements the first real beta Slovo contract, the first
|
||||
post-beta tooling/install hardening release, the first runtime/resource
|
||||
@ -416,7 +421,8 @@ first diagnostics usability release, the first package/workspace discipline
|
||||
release, the first loopback networking foundation release, and the first
|
||||
serialization/data-interchange foundation release, the first concrete type
|
||||
alias foundation release, and the first generic/collection reservation
|
||||
diagnostic release. The remaining path is from beta to stable.
|
||||
diagnostic release, and the first API-discovery and symbol-metadata release.
|
||||
The remaining path is from beta to stable.
|
||||
|
||||
Recommended compiler sequence:
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -5,7 +5,7 @@
|
||||
Sanjin Gumbarevic<br>
|
||||
hermeticum_lab@protonmail.com
|
||||
|
||||
Publication release: `1.0.0-beta.9`
|
||||
Publication release: `1.0.0-beta.10`
|
||||
|
||||
Technical behavior baseline: language surface through `1.0.0-beta`; tooling
|
||||
and install workflow through `1.0.0-beta.1`; runtime/resource foundation through
|
||||
@ -15,11 +15,12 @@ discipline through `1.0.0-beta.5`; loopback networking foundation through
|
||||
`1.0.0-beta.6`; serialization/data-interchange foundation through
|
||||
`1.0.0-beta.7`; concrete type alias foundation through `1.0.0-beta.8`;
|
||||
collection alias unification and generic reservation through `1.0.0-beta.9`
|
||||
and developer-experience API discovery through `1.0.0-beta.10`
|
||||
|
||||
Date: 2026-05-22
|
||||
|
||||
Evidence source: paired Slovo/Glagol monorepo verification and benchmark
|
||||
reruns from the public source tree; beta.9 release-gate verification from the
|
||||
reruns from the public source tree; beta.10 release-gate verification from the
|
||||
public monorepo
|
||||
|
||||
Maturity: beta
|
||||
@ -34,7 +35,7 @@ explicit types, explicit failure through `option` and `result`, lexical
|
||||
`unsafe`, and native compilation through the Glagol compiler to LLVM IR and
|
||||
hosted executables.
|
||||
|
||||
The current publication release, `1.0.0-beta.9`, keeps the first real
|
||||
The current publication release, `1.0.0-beta.10`, keeps the first real
|
||||
general-purpose beta language baseline from `1.0.0-beta` and records the first
|
||||
post-beta tooling/install hardening update plus the first runtime/resource
|
||||
foundation update, the first standard-library stabilization update, and the
|
||||
@ -42,27 +43,31 @@ first language-usability diagnostics update, plus the first local
|
||||
package/workspace discipline update, the first loopback networking foundation
|
||||
update, the first serialization/data-interchange foundation update, and the
|
||||
first concrete type alias foundation plus the first collection alias
|
||||
unification and generic-reservation update. The beta baseline includes the
|
||||
unification and generic-reservation update, plus the first
|
||||
developer-experience API discovery update. The beta baseline includes the
|
||||
completed `u32` / `u64`
|
||||
unsigned scope, the staged stdlib breadth that makes ordinary command-line
|
||||
programs practical, the current narrow `std.net` loopback TCP surface, the
|
||||
current narrow `std.json` text-construction surface, module-local transparent
|
||||
aliases for supported concrete types, and the current ten-scaffold benchmark
|
||||
suite. This paper records the current beta
|
||||
technical state, the difference between Slovo and Lisp-family languages, the
|
||||
benchmark methodology, the beta.1 tooling update, the beta.2 runtime/resource
|
||||
suite. It also includes an alias-normalized generated standard-library
|
||||
signature catalog and deterministic `glagol symbols` source metadata for
|
||||
editor-facing integrations. This paper records the current beta technical
|
||||
state, the difference between Slovo and Lisp-family languages, the benchmark
|
||||
methodology, the beta.1 tooling update, the beta.2 runtime/resource
|
||||
foundation, the beta.3 standard-library stabilization slice, the beta.4
|
||||
diagnostics usability slice, the beta.5 package discipline slice, the beta.6
|
||||
networking foundation slice, the beta.7 serialization foundation slice, and
|
||||
the beta.8 concrete alias foundation slice, the beta.9 collection alias and
|
||||
generic-reservation slice, and the remaining path from beta to stable.
|
||||
networking foundation slice, the beta.7 serialization foundation slice, the
|
||||
beta.8 concrete alias foundation slice, the beta.9 collection alias and
|
||||
generic-reservation slice, the beta.10 API-discovery and symbol-metadata
|
||||
slice, and the remaining path from beta to stable.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
This document is a technical state paper for the current beta baseline. It
|
||||
summarizes the behavior represented by the paired Slovo and Glagol source
|
||||
tree, with `1.0.0-beta` as the current language-surface baseline and
|
||||
`1.0.0-beta.9` as the current publication baseline.
|
||||
`1.0.0-beta.10` as the current publication baseline.
|
||||
|
||||
The support rule remains strict:
|
||||
|
||||
@ -74,7 +79,7 @@ The support rule remains strict:
|
||||
- partial parser recognition or speculative examples do not count as support
|
||||
|
||||
Historical `exp-*` releases remain experimental alpha maturity. The current
|
||||
publication accompanies `1.0.0-beta.9`.
|
||||
publication accompanies `1.0.0-beta.10`.
|
||||
|
||||
## 2. Design Thesis
|
||||
|
||||
@ -388,8 +393,10 @@ diagnostics, `1.0.0-beta.5` tightens package/workspace discipline, and
|
||||
`1.0.0-beta.7` adds a narrow JSON construction foundation, and
|
||||
`1.0.0-beta.8` adds transparent concrete type aliases, and `1.0.0-beta.9`
|
||||
applies those aliases inside current collection/value-family facades while
|
||||
reserving generic, map, and set syntax through diagnostics. None of these
|
||||
post-beta slices claims changed benchmark performance. The beta.7 `json-quote-loop`
|
||||
reserving generic, map, and set syntax through diagnostics, and
|
||||
`1.0.0-beta.10` adds alias-normalized stdlib signature discovery plus
|
||||
deterministic `glagol symbols` metadata. None of these post-beta slices claims
|
||||
changed benchmark performance. The beta.7 `json-quote-loop`
|
||||
scaffold is present for local follow-up timing and is not part of the exp-123
|
||||
nine-row result table below.
|
||||
|
||||
@ -512,7 +519,7 @@ Major remaining gaps before `1.0.0`:
|
||||
- semantic versioning and deprecation policy
|
||||
- a clear separation between stable and experimental features
|
||||
|
||||
## 10. Path Beyond `1.0.0-beta.9`
|
||||
## 10. Path Beyond `1.0.0-beta.10`
|
||||
|
||||
The beta threshold is now real. The next work should treat `1.0.0-beta` as
|
||||
the language compatibility-governed baseline, `1.0.0-beta.1` as the first
|
||||
@ -524,7 +531,9 @@ and `1.0.0-beta.6` as the first loopback networking foundation point, and
|
||||
`1.0.0-beta.7` as the first serialization/data-interchange foundation point,
|
||||
`1.0.0-beta.8` as the first concrete type alias foundation point, and
|
||||
`1.0.0-beta.9` as the first collection alias unification and generic
|
||||
reservation point, then move deliberately toward stable general-purpose status.
|
||||
reservation point, and `1.0.0-beta.10` as the first API-discovery and
|
||||
symbol-metadata point, then move deliberately toward stable general-purpose
|
||||
status.
|
||||
|
||||
Recommended sequence:
|
||||
|
||||
|
||||
Binary file not shown.
@ -7,15 +7,67 @@ const path = require("path");
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const stdDir = path.join(repoRoot, "lib", "std");
|
||||
const outputPath = path.join(repoRoot, "docs", "language", "STDLIB_API.md");
|
||||
const cargoTomlPath = path.join(repoRoot, "compiler", "Cargo.toml");
|
||||
const readmePath = path.join(repoRoot, "README.md");
|
||||
|
||||
function tokenize(source) {
|
||||
const tokens = [];
|
||||
const pattern = /\(|\)|[^\s()]+/g;
|
||||
let match;
|
||||
while ((match = pattern.exec(source)) !== null) {
|
||||
tokens.push(match[0]);
|
||||
let index = 0;
|
||||
|
||||
while (index < source.length) {
|
||||
const ch = source[index];
|
||||
|
||||
if (/\s/.test(ch)) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === ";") {
|
||||
while (index < source.length && source[index] !== "\n") {
|
||||
index += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "(" || ch === ")") {
|
||||
tokens.push(ch);
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ch === "\"") {
|
||||
const start = index;
|
||||
index += 1;
|
||||
while (index < source.length) {
|
||||
if (source[index] === "\\") {
|
||||
index += 2;
|
||||
continue;
|
||||
}
|
||||
if (source[index] === "\"") {
|
||||
index += 1;
|
||||
break;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
if (source[index - 1] !== "\"") {
|
||||
throw new Error("unterminated string literal");
|
||||
}
|
||||
tokens.push(source.slice(start, index));
|
||||
continue;
|
||||
}
|
||||
|
||||
const start = index;
|
||||
while (
|
||||
index < source.length &&
|
||||
!/\s/.test(source[index]) &&
|
||||
source[index] !== "(" &&
|
||||
source[index] !== ")" &&
|
||||
source[index] !== ";"
|
||||
) {
|
||||
index += 1;
|
||||
}
|
||||
tokens.push(source.slice(start, index));
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@ -40,12 +92,24 @@ function parseList(tokens, cursor) {
|
||||
return list;
|
||||
}
|
||||
|
||||
function moduleForm(source, file) {
|
||||
function topLevelForms(source, file) {
|
||||
const tokens = tokenize(source);
|
||||
if (tokens[0] !== "(") {
|
||||
const cursor = { index: 0 };
|
||||
const forms = [];
|
||||
while (cursor.index < tokens.length) {
|
||||
if (tokens[cursor.index] !== "(") {
|
||||
throw new Error(`${file}: expected top-level list at token ${cursor.index}`);
|
||||
}
|
||||
forms.push(parseList(tokens, cursor));
|
||||
}
|
||||
return forms;
|
||||
}
|
||||
|
||||
function moduleForm(forms, file) {
|
||||
const form = forms.find((item) => Array.isArray(item) && item[0] === "module");
|
||||
if (!form) {
|
||||
throw new Error(`${file}: expected module form`);
|
||||
}
|
||||
const form = parseList(tokens, { index: 0 });
|
||||
if (form[0] !== "module" || typeof form[1] !== "string") {
|
||||
throw new Error(`${file}: expected (module name ...) form`);
|
||||
}
|
||||
@ -59,6 +123,71 @@ function moduleForm(source, file) {
|
||||
};
|
||||
}
|
||||
|
||||
function renderType(type, aliases, stack = []) {
|
||||
if (Array.isArray(type)) {
|
||||
return `(${type.map((part) => renderType(part, aliases, stack)).join(" ")})`;
|
||||
}
|
||||
|
||||
if (aliases.has(type)) {
|
||||
if (stack.includes(type)) {
|
||||
throw new Error(`cyclic type alias while rendering ${stack.concat(type).join(" -> ")}`);
|
||||
}
|
||||
return renderType(aliases.get(type), aliases, stack.concat(type));
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
function functionSignature(fnForm, aliases, file) {
|
||||
if (fnForm.length < 5 || typeof fnForm[1] !== "string" || !Array.isArray(fnForm[2]) || fnForm[3] !== "->") {
|
||||
throw new Error(`${file}: malformed (fn ...) form for ${fnForm[1] || "<unknown>"}`);
|
||||
}
|
||||
|
||||
const name = fnForm[1];
|
||||
const params = fnForm[2].map((param) => {
|
||||
if (!Array.isArray(param) || param.length !== 2 || typeof param[0] !== "string") {
|
||||
throw new Error(`${file}: malformed parameter in ${name}`);
|
||||
}
|
||||
return `(${param[0]} ${renderType(param[1], aliases)})`;
|
||||
});
|
||||
const renderedParams = params.length === 0 ? "()" : `(${params.join(" ")})`;
|
||||
const returnType = renderType(fnForm[4], aliases);
|
||||
return `${name} ${renderedParams} -> ${returnType}`;
|
||||
}
|
||||
|
||||
function indexForms(forms, file) {
|
||||
const aliases = new Map();
|
||||
const functions = new Map();
|
||||
|
||||
for (const form of forms) {
|
||||
if (!Array.isArray(form) || form.length < 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (form[0] === "type") {
|
||||
if (form.length !== 3 || typeof form[1] !== "string") {
|
||||
throw new Error(`${file}: malformed (type ...) form`);
|
||||
}
|
||||
if (aliases.has(form[1]) || functions.has(form[1])) {
|
||||
throw new Error(`${file}: duplicate public name ${form[1]}`);
|
||||
}
|
||||
aliases.set(form[1], form[2]);
|
||||
}
|
||||
|
||||
if (form[0] === "fn") {
|
||||
if (typeof form[1] !== "string") {
|
||||
throw new Error(`${file}: malformed (fn ...) form`);
|
||||
}
|
||||
if (functions.has(form[1]) || aliases.has(form[1])) {
|
||||
throw new Error(`${file}: duplicate public name ${form[1]}`);
|
||||
}
|
||||
functions.set(form[1], form);
|
||||
}
|
||||
}
|
||||
|
||||
return { aliases, functions };
|
||||
}
|
||||
|
||||
function stdModules() {
|
||||
return fs
|
||||
.readdirSync(stdDir)
|
||||
@ -67,31 +196,48 @@ function stdModules() {
|
||||
.map((fileName) => {
|
||||
const relativePath = path.join("lib", "std", fileName);
|
||||
const source = fs.readFileSync(path.join(stdDir, fileName), "utf8");
|
||||
const module = moduleForm(source, relativePath);
|
||||
const forms = topLevelForms(source, relativePath);
|
||||
const module = moduleForm(forms, relativePath);
|
||||
const { aliases, functions } = indexForms(forms, relativePath);
|
||||
const omittedAliasExports = [];
|
||||
const signatures = [];
|
||||
|
||||
for (const exported of module.exports) {
|
||||
if (functions.has(exported)) {
|
||||
signatures.push(functionSignature(functions.get(exported), aliases, relativePath));
|
||||
} else if (aliases.has(exported)) {
|
||||
omittedAliasExports.push(exported);
|
||||
} else {
|
||||
throw new Error(`${relativePath}: export ${exported} has no matching (fn ...) form`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fileName,
|
||||
relativePath,
|
||||
name: module.name,
|
||||
exports: module.exports,
|
||||
signatures,
|
||||
omittedAliasExports,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function packageVersion() {
|
||||
const cargoToml = fs.readFileSync(cargoTomlPath, "utf8");
|
||||
const versionMatch = cargoToml.match(/^version\s*=\s*"([^"]+)"$/m);
|
||||
function releaseVersion() {
|
||||
const readme = fs.readFileSync(readmePath, "utf8");
|
||||
const versionMatch = readme.match(/^Current release:\s*`([^`]+)`\.$/m);
|
||||
if (!versionMatch) {
|
||||
throw new Error("compiler/Cargo.toml must declare a package version");
|
||||
throw new Error("README.md must declare the current release");
|
||||
}
|
||||
return versionMatch[1];
|
||||
}
|
||||
|
||||
function render(modules, version) {
|
||||
const totalExports = modules.reduce((count, module) => count + module.exports.length, 0);
|
||||
const totalExports = modules.reduce((count, module) => count + module.signatures.length, 0);
|
||||
const totalOmittedAliases = modules.reduce((count, module) => count + module.omittedAliasExports.length, 0);
|
||||
const out = [];
|
||||
out.push("# Slovo Standard Library API Catalog");
|
||||
out.push("");
|
||||
out.push("Generated from `lib/std/*.slo` by `scripts/render-stdlib-api-doc.sh`.");
|
||||
out.push("Generated from `lib/std/*.slo` by `scripts/render-stdlib-api-doc.js`.");
|
||||
out.push("Do not edit this file by hand.");
|
||||
out.push("");
|
||||
out.push("## Stability Tiers");
|
||||
@ -100,12 +246,15 @@ function render(modules, version) {
|
||||
out.push(`- \`experimental\`: not used for exported \`lib/std\` helpers in \`${version}\`; future releases may mark new helpers this way before they graduate.`);
|
||||
out.push("- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.");
|
||||
out.push("");
|
||||
out.push("The catalog is a beta compatibility aid, not a stable `1.0.0` API freeze.");
|
||||
out.push("The catalog is a beta API discovery aid, not a stable `1.0.0` standard-library freeze.");
|
||||
out.push("Module-local concrete aliases are normalized in signatures so names such as `VecI32` and `ResultU64` do not leak into the public catalog.");
|
||||
out.push("Only exported `(fn ...)` helpers are listed; `(type ...)` aliases and non-exported helpers are omitted.");
|
||||
out.push("");
|
||||
out.push("## Summary");
|
||||
out.push("");
|
||||
out.push(`- Modules: ${modules.length}`);
|
||||
out.push(`- Exported helpers: ${totalExports}`);
|
||||
out.push(`- Exported helper signatures: ${totalExports}`);
|
||||
out.push(`- Exported type aliases omitted: ${totalOmittedAliases}`);
|
||||
out.push("- Default tier: `beta-supported`");
|
||||
out.push("");
|
||||
out.push("## Modules");
|
||||
@ -115,14 +264,14 @@ function render(modules, version) {
|
||||
out.push("");
|
||||
out.push(`- Path: \`${module.relativePath}\``);
|
||||
out.push("- Tier: `beta-supported`");
|
||||
out.push(`- Exported helpers: ${module.exports.length}`);
|
||||
out.push(`- Exported helper signatures: ${module.signatures.length}`);
|
||||
out.push("");
|
||||
for (const exported of module.exports) {
|
||||
out.push(`- \`${exported}\``);
|
||||
for (const signature of module.signatures) {
|
||||
out.push(`- \`${signature}\``);
|
||||
}
|
||||
out.push("");
|
||||
}
|
||||
return `${out.join("\n")}\n`;
|
||||
}
|
||||
|
||||
fs.writeFileSync(outputPath, render(stdModules(), packageVersion()));
|
||||
fs.writeFileSync(outputPath, render(stdModules(), releaseVersion()));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user