Release 1.0.0-beta.10 developer experience api discovery

This commit is contained in:
sanjin 2026-05-22 20:00:20 +02:00
parent 5a3ed0c41e
commit f8f0862ee3
22 changed files with 2087 additions and 693 deletions

View 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`

View 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.

View File

@ -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.9`. Current release: `1.0.0-beta.10`.
## Repository Layout ## Repository Layout
@ -24,7 +24,7 @@ scripts/ local release and document tooling
## Beta Scope ## 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` `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 runtime/resource foundation bundle, the `1.0.0-beta.3` standard-library
stabilization bundle, the `1.0.0-beta.4` language-usability diagnostics 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` `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 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 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: 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`, and `build` - `new`, `check`, `fmt`, `test`, `doc`, `symbols`, `build`, `run`, and
`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`
@ -47,15 +49,24 @@ command-line, file, and loopback-network programs with:
- JSON string quoting and compact JSON text construction through `std.json` - JSON string quoting and compact JSON text construction through `std.json`
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c` - hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
Still deferred before stable: generics, maps/sets, broad package registry The generated standard-library API catalog is a beta discovery aid: it lists
semantics, DNS/TLS/async networking, LSP/watch/debug-adapter guarantees, exported helper signatures from `lib/std/*.slo`, normalizes module-local
stable ABI and layout, and a stable standard-library compatibility freeze. 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 Still deferred before stable: executable generics, maps/sets, broad package
reserved generic and collection diagnostics without claiming executable registry semantics, DNS/TLS/async networking, LSP/watch/debug-adapter
generics, maps, sets, traits, inference, monomorphization, iterators, ABI guarantees, stable ABI and layout, runtime changes for generic collections,
stability, or a standard-library API freeze until the contract and gates are and a stable standard-library compatibility freeze.
explicit.
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 ## 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 traits, inference, monomorphization, iterators, stable ABI/layout promises, or
a stable standard-library API freeze. 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 ## Documentation
- [Language Manifest](docs/language/MANIFEST.md) - [Language Manifest](docs/language/MANIFEST.md)

2
compiler/Cargo.lock generated
View File

@ -4,4 +4,4 @@ version = 3
[[package]] [[package]]
name = "glagol" name = "glagol"
version = "1.0.0-beta.9" version = "1.0.0-beta.10"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "glagol" name = "glagol"
version = "1.0.0-beta.9" version = "1.0.0-beta.10"
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"

View File

@ -11,6 +11,7 @@ mod project;
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;
@ -94,19 +95,26 @@ fn run_invocation_inner(invocation: 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!(invocation.mode, Mode::Check | Mode::Build | Mode::Run) let project_capable_mode = matches!(
|| (invocation.mode == Mode::RunTests && invocation.manifest_mode_name == "test"); 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) { 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), 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()); 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,
@ -233,11 +260,52 @@ 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),
@ -247,6 +315,7 @@ 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::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) -> ! { fn run_new(invocation: Invocation) -> ! {
match scaffold::create_project( match scaffold::create_project(
&invocation.path, &invocation.path,
@ -1210,7 +1321,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
command_line: command_line.clone(), 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() => if path.is_none() =>
{ {
let next = match arg.as_str() { let next = match arg.as_str() {
@ -1222,6 +1333,7 @@ fn parse_args(raw_args: &[String]) -> Result<Args, ParseError> {
"clean" => Mode::Clean, "clean" => Mode::Clean,
"new" => Mode::New, "new" => Mode::New,
"doc" => Mode::Doc, "doc" => Mode::Doc,
"symbols" => Mode::Symbols,
_ => unreachable!(), _ => unreachable!(),
}; };
set_mode( set_mode(
@ -1489,6 +1601,7 @@ enum Mode {
Clean, Clean,
New, New,
Doc, Doc,
Symbols,
} }
impl Mode { impl Mode {
@ -1507,6 +1620,7 @@ impl Mode {
Self::Clean => "clean", Self::Clean => "clean",
Self::New => "new", Self::New => "new",
Self::Doc => "doc", Self::Doc => "doc",
Self::Symbols => "symbols",
} }
} }
@ -1523,6 +1637,7 @@ impl Mode {
Self::Clean => "no-output", Self::Clean => "no-output",
Self::New => "no-output", Self::New => "no-output",
Self::Doc => "documentation", Self::Doc => "documentation",
Self::Symbols => "symbols",
} }
} }
@ -2425,6 +2540,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] [--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
View 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(&param.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,
}

View 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)
);
}

View File

@ -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 compiler, docs, tests, and stdlib have enough real pressure to validate the
design. 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: 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 - language-server diagnostics and document symbols
- editor-facing symbol metadata for files, projects, and workspaces
- project watch mode - project watch mode
- generated API documentation for local packages and `lib/std` - generated API documentation for local packages
- clearer benchmark harness output - clearer benchmark harness output
- machine-readable diagnostics stability policy - 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, Why tenth: editor support matters, but it should follow a stable enough syntax,
project model, and diagnostic model. project model, and diagnostic model.

View File

@ -10,11 +10,47 @@ integration/readiness release, not the first real beta.
## Unreleased ## Unreleased
Next scoped Glagol work is expected to continue after the `1.0.0-beta.9` Next scoped Glagol work is expected to continue after the `1.0.0-beta.10`
reservation update. developer-experience API discovery and symbol-metadata update.
No unreleased compiler scope is committed here yet. 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 ## 1.0.0-beta.9
Release label: `1.0.0-beta.9` Release label: `1.0.0-beta.9`

View File

@ -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. 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 Current stage: `1.0.0-beta.10`, released on 2026-05-22 as a
reservation update for generic-shaped collection and stdlib syntax. It keeps developer-experience API discovery and symbol-metadata update. It keeps the `1.0.0-beta`
the `1.0.0-beta` language/compiler support baseline and includes the language/compiler support baseline and includes the
`1.0.0-beta.1` tooling hardening release, the `1.0.0-beta.2` runtime/resource `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, foundation release, the `1.0.0-beta.3` standard-library stabilization release,
the `1.0.0-beta.4` language-usability diagnostics release, the 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 coverage, the `1.0.0-beta.8` concrete type alias foundation update, and
focused diagnostics/formatter/project-mode gates for reserved generic focused diagnostics/formatter/project-mode gates for reserved generic
functions, parameterized aliases, generic type parameters, maps, sets, and 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 Next stage target: post-`1.0.0-beta.10` developer-experience and
vectors, maps, sets, generic stdlib dispatch, and collection unification remain 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. unimplemented until a later scoped contract promotes them explicitly.
The final experimental precursor scope is `exp-125`. Its unsigned direct-value The final experimental precursor scope is `exp-125`. Its unsigned direct-value

View File

@ -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 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. 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 `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 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 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 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 first concrete type alias foundation from `1.0.0-beta.8`, plus the first
collection alias unification and generic reservation slice from 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 ## Unreleased
No unreleased language scope is committed here yet. 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 ## 1.0.0-beta.9
Release label: `1.0.0-beta.9` Release label: `1.0.0-beta.9`

View File

@ -10,8 +10,8 @@ Long-horizon planning lives in
release train from the historical `v2.0.0-beta.1` tag toward and beyond the release train from the historical `v2.0.0-beta.1` tag toward and beyond the
first real general-purpose beta Slovo contract. first real general-purpose beta Slovo contract.
Current stage: `1.0.0-beta.9`, released on 2026-05-22 as the first post-beta Current stage: `1.0.0-beta.10`, 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 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` 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 runtime/resource foundation release, the `1.0.0-beta.3` standard-library
stabilization release, the `1.0.0-beta.4` language-usability diagnostics 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` opaque `i32` handles and concrete `result` families, plus a narrow `std.json`
source facade for compact JSON text construction, plus top-level module-local source facade for compact JSON text construction, plus top-level module-local
transparent aliases for supported concrete target types, plus local alias use transparent aliases for supported concrete target types, plus local alias use
inside the current concrete vector, option, and result facades. JSON parsing, inside the current concrete vector, option, and result facades, plus a
recursive JSON values, executable generics, generic aliases, parameterized generated standard-library API catalog that lists exact exported helper
aliases, cross-module alias visibility, maps/sets, traits, inference, signatures with module-local aliases normalized to concrete public types, plus
monomorphization, iterators, DNS, TLS, UDP, async IO, non-loopback binding, `glagol symbols` deterministic source metadata for files, projects, and
HTTP frameworks, rich host-error ADTs, stable ABI/layout, and a stable workspaces. JSON
standard-library API freeze remain deferred. 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 Next stage target: continue from developer-experience and reserved
collection-unification design pressure without claiming executable generics generic/map/set diagnostics without claiming executable generics, an LSP/watch
until the exact scope is frozen from the manifest and roadmap. 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 The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its `.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its

View File

@ -1,8 +1,9 @@
# Slovo v1 Specification # Slovo v1 Specification
Status: living beta contract for `1.0.0-beta`, with the post-beta 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 `1.0.0-beta.1` tooling/install update, `1.0.0-beta.2` runtime/resource
foundation update. The language contract integrates 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 promoted language slices through `exp-125` and the historical publication
baseline through `exp-123`. `1.0.0-beta` is the first real general-purpose 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 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 existing concrete vector, option, and result source facades may use
module-local concrete aliases internally while exported helper behavior and module-local concrete aliases internally while exported helper behavior and
cross-module types remain concrete; executable generics, maps, sets, traits, 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 - `exp-1` owned runtime strings: compiler-known `std.string.concat` accepts two
`string` values and returns an immutable runtime-owned `string`; existing `string` values and returns an immutable runtime-owned `string`; existing
string equality, length, printing, locals, parameters, returns, and calls work 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, reflection API, and it does not expose stable typed-core, debug metadata,
source-map, ABI, layout, runtime reflection, or compiler-internal contracts. 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 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 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. 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`, Recommended v1 modes are `check`, `format`, `emit-llvm`,
`inspect-lowering`, and `test`. Recommended output and artifact kinds are `inspect-lowering`, and `test`. Recommended output and artifact kinds are
`diagnostics`, `formatted-source`, `llvm-ir`, `lowering-inspector`, `stdout`, `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 A failed invocation still writes a manifest when the tool mode supports
manifests. In that case `(success false)` is paired with a diagnostics artifact manifests. In that case `(success false)` is paired with a diagnostics artifact

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
Sanjin Gumbarevic<br> Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com 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 Technical behavior baseline: compiler and language support through
`1.0.0-beta`; tooling and install workflow through `1.0.0-beta.1`; `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`; loopback networking foundation through `1.0.0-beta.6`;
serialization/data-interchange foundation through `1.0.0-beta.7`; serialization/data-interchange foundation through `1.0.0-beta.7`;
concrete type alias foundation through `1.0.0-beta.8`; 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 Date: 2026-05-22
Evidence source: paired Slovo/Glagol monorepo verification and benchmark 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 public monorepo
Maturity: beta 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, S-expression tree, AST, typed AST, LLVM IR, hosted native executable, tests,
diagnostics, and release documents should agree. 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 general-purpose beta toolchain baseline from `1.0.0-beta` and records the
first post-beta tooling/install hardening update plus the first first post-beta tooling/install hardening update plus the first
runtime/resource foundation update plus the first standard-library 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 the first local package/workspace discipline update plus the first loopback
networking foundation update plus the first serialization/data-interchange networking foundation update plus the first serialization/data-interchange
foundation update plus the first concrete type alias foundation and the first 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 baseline includes the completed `u32` / `u64` unsigned compiler and stdlib
breadth scope, the narrow `std.net` loopback TCP runtime family, the narrow breadth scope, the narrow `std.net` loopback TCP runtime family, the narrow
`std.json.quote_string` runtime family, transparent concrete alias parsing and `std.json.quote_string` runtime family, transparent concrete alias parsing and
erasure, explicit beta9 diagnostics for reserved generic/map/set shapes, and erasure, explicit beta9 diagnostics for reserved generic/map/set shapes, and
the current ten-scaffold benchmark suite. This paper records the the current ten-scaffold benchmark suite. It also includes alias-normalized
current beta 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 implementation surface, the benchmark method and results, the distinction
between Glagol and Lisp-family implementations, the beta.1 tooling update, the between Glagol and Lisp-family implementations, the beta.1 tooling update, the
beta.2 runtime/resource foundation, the beta.3 standard-library stabilization 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.4 diagnostics usability slice, the beta.5 package discipline
slice, the beta.6 networking foundation slice, the beta.7 serialization slice, the beta.6 networking foundation slice, the beta.7 serialization
foundation slice, the beta.8 concrete alias foundation slice, the beta.9 foundation slice, the beta.8 concrete alias foundation slice, the beta.9
generic/collection reservation slice, and the compiler path from beta to generic/collection reservation slice, the beta.10 API-discovery and
stable. symbol-metadata slice, and the compiler path from beta to stable.
## 1. Compiler Thesis ## 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.7` adds a narrow JSON construction foundation, and
`1.0.0-beta.8` adds transparent concrete type aliases, and `1.0.0-beta.9` `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 adds focused diagnostics for reserved generic, map, set, and future generic
stdlib forms. None of these post-beta slices claims changed benchmark stdlib forms, and `1.0.0-beta.10` adds alias-normalized stdlib signature
performance. The beta.7 `json-quote-loop` 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 scaffold is present for local follow-up timing and is not part of the exp-123
nine-row result table below. nine-row result table below.
@ -407,7 +412,7 @@ coverage and compatibility:
- package behavior becoming stable before dependency, manifest, and versioning - package behavior becoming stable before dependency, manifest, and versioning
rules are precise 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 Glagol now implements the first real beta Slovo contract, the first
post-beta tooling/install hardening release, the first runtime/resource 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 release, the first loopback networking foundation release, and the first
serialization/data-interchange foundation release, the first concrete type serialization/data-interchange foundation release, the first concrete type
alias foundation release, and the first generic/collection reservation 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: Recommended compiler sequence:

Binary file not shown.

Binary file not shown.

View File

@ -5,7 +5,7 @@
Sanjin Gumbarevic<br> Sanjin Gumbarevic<br>
hermeticum_lab@protonmail.com 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 Technical behavior baseline: language surface through `1.0.0-beta`; tooling
and install workflow through `1.0.0-beta.1`; runtime/resource foundation through 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.6`; serialization/data-interchange foundation through
`1.0.0-beta.7`; concrete type alias foundation through `1.0.0-beta.8`; `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` 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 Date: 2026-05-22
Evidence source: paired Slovo/Glagol monorepo verification and benchmark 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 public monorepo
Maturity: beta 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 `unsafe`, and native compilation through the Glagol compiler to LLVM IR and
hosted executables. 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 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 post-beta tooling/install hardening update plus the first runtime/resource
foundation update, the first standard-library stabilization update, and the 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 package/workspace discipline update, the first loopback networking foundation
update, the first serialization/data-interchange foundation update, and the update, the first serialization/data-interchange foundation update, and the
first concrete type alias foundation plus the first collection alias 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` completed `u32` / `u64`
unsigned scope, the staged stdlib breadth that makes ordinary command-line unsigned scope, the staged stdlib breadth that makes ordinary command-line
programs practical, the current narrow `std.net` loopback TCP surface, the programs practical, the current narrow `std.net` loopback TCP surface, the
current narrow `std.json` text-construction surface, module-local transparent current narrow `std.json` text-construction surface, module-local transparent
aliases for supported concrete types, and the current ten-scaffold benchmark aliases for supported concrete types, and the current ten-scaffold benchmark
suite. This paper records the current beta suite. It also includes an alias-normalized generated standard-library
technical state, the difference between Slovo and Lisp-family languages, the signature catalog and deterministic `glagol symbols` source metadata for
benchmark methodology, the beta.1 tooling update, the beta.2 runtime/resource 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 foundation, the beta.3 standard-library stabilization slice, the beta.4
diagnostics usability slice, the beta.5 package discipline slice, the beta.6 diagnostics usability slice, the beta.5 package discipline slice, the beta.6
networking foundation slice, the beta.7 serialization foundation slice, and networking foundation slice, the beta.7 serialization foundation slice, the
the beta.8 concrete alias foundation slice, the beta.9 collection alias and beta.8 concrete alias foundation slice, the beta.9 collection alias and
generic-reservation slice, and the remaining path from beta to stable. generic-reservation slice, the beta.10 API-discovery and symbol-metadata
slice, and the remaining path from beta to stable.
## 1. Scope ## 1. Scope
This document is a technical state paper for the current beta baseline. It This document is a technical state paper for the current beta baseline. It
summarizes the behavior represented by the paired Slovo and Glagol source summarizes the behavior represented by the paired Slovo and Glagol source
tree, with `1.0.0-beta` as the current language-surface baseline and 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: 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 - partial parser recognition or speculative examples do not count as support
Historical `exp-*` releases remain experimental alpha maturity. The current 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 ## 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.7` adds a narrow JSON construction foundation, and
`1.0.0-beta.8` adds transparent concrete type aliases, and `1.0.0-beta.9` `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 applies those aliases inside current collection/value-family facades while
reserving generic, map, and set syntax through diagnostics. None of these reserving generic, map, and set syntax through diagnostics, and
post-beta slices claims changed benchmark performance. The beta.7 `json-quote-loop` `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 scaffold is present for local follow-up timing and is not part of the exp-123
nine-row result table below. nine-row result table below.
@ -512,7 +519,7 @@ Major remaining gaps before `1.0.0`:
- semantic versioning and deprecation policy - semantic versioning and deprecation policy
- a clear separation between stable and experimental features - 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 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 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.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.8` as the first concrete type alias foundation point, and
`1.0.0-beta.9` as the first collection alias unification and generic `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: Recommended sequence:

Binary file not shown.

View File

@ -7,15 +7,67 @@ const path = require("path");
const repoRoot = path.resolve(__dirname, ".."); const repoRoot = path.resolve(__dirname, "..");
const stdDir = path.join(repoRoot, "lib", "std"); const stdDir = path.join(repoRoot, "lib", "std");
const outputPath = path.join(repoRoot, "docs", "language", "STDLIB_API.md"); 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) { function tokenize(source) {
const tokens = []; const tokens = [];
const pattern = /\(|\)|[^\s()]+/g; let index = 0;
let match;
while ((match = pattern.exec(source)) !== null) { while (index < source.length) {
tokens.push(match[0]); 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; return tokens;
} }
@ -40,12 +92,24 @@ function parseList(tokens, cursor) {
return list; return list;
} }
function moduleForm(source, file) { function topLevelForms(source, file) {
const tokens = tokenize(source); 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`); throw new Error(`${file}: expected module form`);
} }
const form = parseList(tokens, { index: 0 });
if (form[0] !== "module" || typeof form[1] !== "string") { if (form[0] !== "module" || typeof form[1] !== "string") {
throw new Error(`${file}: expected (module name ...) form`); 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() { function stdModules() {
return fs return fs
.readdirSync(stdDir) .readdirSync(stdDir)
@ -67,31 +196,48 @@ function stdModules() {
.map((fileName) => { .map((fileName) => {
const relativePath = path.join("lib", "std", fileName); const relativePath = path.join("lib", "std", fileName);
const source = fs.readFileSync(path.join(stdDir, fileName), "utf8"); 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 { return {
fileName, fileName,
relativePath, relativePath,
name: module.name, name: module.name,
exports: module.exports, signatures,
omittedAliasExports,
}; };
}); });
} }
function packageVersion() { function releaseVersion() {
const cargoToml = fs.readFileSync(cargoTomlPath, "utf8"); const readme = fs.readFileSync(readmePath, "utf8");
const versionMatch = cargoToml.match(/^version\s*=\s*"([^"]+)"$/m); const versionMatch = readme.match(/^Current release:\s*`([^`]+)`\.$/m);
if (!versionMatch) { 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]; return versionMatch[1];
} }
function render(modules, version) { 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 = []; const out = [];
out.push("# Slovo Standard Library API Catalog"); out.push("# Slovo Standard Library API Catalog");
out.push(""); 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("Do not edit this file by hand.");
out.push(""); out.push("");
out.push("## Stability Tiers"); 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(`- \`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("- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.");
out.push(""); 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("");
out.push("## Summary"); out.push("## Summary");
out.push(""); out.push("");
out.push(`- Modules: ${modules.length}`); 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("- Default tier: `beta-supported`");
out.push(""); out.push("");
out.push("## Modules"); out.push("## Modules");
@ -115,14 +264,14 @@ function render(modules, version) {
out.push(""); out.push("");
out.push(`- Path: \`${module.relativePath}\``); out.push(`- Path: \`${module.relativePath}\``);
out.push("- Tier: `beta-supported`"); out.push("- Tier: `beta-supported`");
out.push(`- Exported helpers: ${module.exports.length}`); out.push(`- Exported helper signatures: ${module.signatures.length}`);
out.push(""); out.push("");
for (const exported of module.exports) { for (const signature of module.signatures) {
out.push(`- \`${exported}\``); out.push(`- \`${signature}\``);
} }
out.push(""); out.push("");
} }
return `${out.join("\n")}\n`; return `${out.join("\n")}\n`;
} }
fs.writeFileSync(outputPath, render(stdModules(), packageVersion())); fs.writeFileSync(outputPath, render(stdModules(), releaseVersion()));