Release 1.0.0-beta.20 string search and trim foundation

This commit is contained in:
sanjin 2026-05-23 01:22:39 +02:00
parent 98f81d2d59
commit c1231fdb5f
23 changed files with 1214 additions and 37 deletions

View File

@ -0,0 +1,89 @@
# 1.0.0-beta.20 String Search And ASCII Trim Foundation
## Scope
`1.0.0-beta.20` is a standard-library and compiler-gate slice. It does not
change source-language syntax, runtime C, compiler-known runtime names, or
ABI/layout policy.
The release adds source-authored `std.string` helpers:
- `contains ((value string) (needle string)) -> bool`
- `index_of_option ((value string) (needle string)) -> (option i32)`
- `last_index_of_option ((value string) (needle string)) -> (option i32)`
- `trim_ascii_start ((value string)) -> string`
- `trim_ascii_end ((value string)) -> string`
- `trim_ascii ((value string)) -> string`
## Contract
Search is byte-oriented over the current runtime string representation.
`index_of_option` returns the first zero-based byte offset for a matching
needle, `last_index_of_option` returns the last zero-based byte offset, and
`contains` is true when `index_of_option` returns `some`.
Empty needles are valid:
- `index_of_option value ""` returns `some 0`
- `last_index_of_option value ""` returns `some (len value)`
- `contains value ""` returns `true`
Missing needles return `none`.
ASCII trim removes only these byte values from the requested edges:
- `9` horizontal tab
- `10` line feed
- `11` vertical tab
- `12` form feed
- `13` carriage return
- `32` space
The helpers compose over already-promoted string primitives:
- `std.string.len`
- `std.string.byte_at_result`
- `std.string.slice_result`
- `std.string.starts_with`
- `std.string.ends_with`
## Non-Scope
This scope does not add:
- compiler-known `std.string.*` runtime names for the new helpers
- runtime C helper implementations
- source-language syntax
- Unicode scalar, grapheme, display-width, or normalization semantics
- case folding or locale-sensitive search
- regular expressions
- tokenizer/parser APIs
- mutable strings
- language slice/view syntax
- stable string ABI/layout
- stable allocation ownership rules
- stable standard-library compatibility
- performance claims
## Acceptance Criteria
- `lib/std/string.slo` exports all six helpers.
- Explicit `std.string` import examples exercise contains, first/last search,
missing needles, empty needles, leading trim, trailing trim, full trim,
all-whitespace trim, and no-trim cases.
- Local `std-layout-local-string` examples mirror the public helper surface.
- Focused compiler coverage verifies the helpers are source-authored and that
direct compiler-known runtime calls for the new names remain unsupported.
- `scripts/release-gate.sh` runs the focused beta20 test.
- Generated standard-library API documentation includes the new signatures.
## Gates
```bash
cargo fmt --check
cargo test --test standard_string_search_trim_beta20
cargo test --test standard_string_source_fallback_helpers_alpha
cargo test --test standard_string_scanning_beta16
cargo test
./scripts/release-gate.sh
```

View File

@ -0,0 +1,95 @@
# 1.0.0-beta.20 Release Review
Scope: String Search And ASCII Trim Foundation
## Findings
No blocking findings.
The beta20 diff matches the documented release contract at the
release-blocking level. `lib/std/string.slo:1` exports all six new helpers, and
the implementations remain ordinary source helpers over the existing string
facade primitives: first search at `lib/std/string.slo:30`, last search at
`lib/std/string.slo:47`, `contains` at `lib/std/string.slo:64`, and ASCII trim
at `lib/std/string.slo:71`. The local mirror in
`examples/projects/std-layout-local-string/src/string.slo:1` exposes the same
surface without changing the public contract shape.
The example coverage is explicit and scoped correctly. The standard import
example covers present, missing, empty-needle, first/last index, leading trim,
trailing trim, full trim, all-whitespace trim, and no-trim cases at
`examples/projects/std-import-string/src/main.slo:204`; the documentation copy
matches at `docs/language/examples/projects/std-import-string/src/main.slo:204`;
the local fixture mirrors the same behavior at
`examples/projects/std-layout-local-string/src/main.slo:204`.
The compiler gate coverage is aligned with the non-scope. The focused beta20
test builds an explicit `std.string` import fixture at
`compiler/tests/standard_string_search_trim_beta20.rs:47`, asserts the helpers
are source-authored at `compiler/tests/standard_string_search_trim_beta20.rs:178`,
and rejects direct compiler-known runtime calls for the new names. The existing
local-string fallback test now expects and inventories the new helpers at
`compiler/tests/standard_string_source_fallback_helpers_alpha.rs:8` and
`compiler/tests/standard_string_source_fallback_helpers_alpha.rs:24`.
The repo-root standard-source import gate and promotion gate are also aligned at
`compiler/tests/standard_core_facade_source_search_alpha.rs:8`,
`compiler/tests/standard_core_facade_source_search_alpha.rs:33`,
`compiler/tests/promotion_gate.rs:1318`, and
`compiler/tests/promotion_gate.rs:7368`.
## Contract Drift
No blocking contract drift found.
The README, language release notes, language roadmap, v1 spec, compiler release
notes, compiler roadmap, post-beta roadmap, stdlib README, and beta20 `.llm`
contract all describe the same narrow surface: byte-oriented search, empty
needle behavior, ASCII-only trimming over bytes `9`, `10`, `11`, `12`, `13`,
and `32`, no new compiler-known runtime names, no runtime C work, no new source
syntax, and no Unicode, regex, tokenizer, mutable-string, stable ABI/layout, or
stable stdlib/API claims. Representative anchors: `README.md:144`,
`docs/language/RELEASE_NOTES.md:39`, `docs/language/ROADMAP.md:80`,
`docs/language/SPEC-v1.md:232`, `docs/compiler/RELEASE_NOTES.md:15`,
`docs/compiler/ROADMAP.md:100`, `docs/POST_BETA_ROADMAP.md:111`,
`lib/std/README.md:233`, and
`.llm/BETA_20_STRING_SEARCH_AND_ASCII_TRIM_FOUNDATION.md:1`.
The version and generated catalog updates are coherent: `compiler/Cargo.toml:3`
and `compiler/Cargo.lock` are bumped to `1.0.0-beta.20`,
`docs/language/STDLIB_API.md:18` reports 596 exported signatures, and
`docs/language/STDLIB_API.md:486` lists 36 `std.string` signatures including
the six beta20 helpers. `scripts/release-gate.sh:73` wires the focused beta20
test into the release gate.
## Verification Notes
Inspected:
- Working tree diff and untracked beta20 contract/test files for std source,
explicit std/local examples, docs, compiler tests, generated catalog, version
bump, and release-gate integration.
- Contract drift against README, language roadmap/spec/release notes, compiler
roadmap/release notes, post-beta roadmap, stdlib README, and the beta20 `.llm`
contract.
- Sibling `glagol` repository status; no sibling worktree changes were present.
Read-only checks run:
- `git diff --check` - passed.
- `git diff --cached --check` - passed.
- `bash -n scripts/release-gate.sh` - passed.
- `cargo fmt --check --manifest-path compiler/Cargo.toml` - passed.
- Local/private publication text scan over source/docs/tests/.llm with build
artifacts excluded - passed.
Not run:
- Focused cargo tests, full `cargo test`, and `./scripts/release-gate.sh`.
Those commands write build/generated artifacts, and this review was
constrained to read-only commands except for the review file.
## Verdict
Release-ready from this review. No blocking beta20 issues remain in the current
working tree diff. The controller should still run the focused beta20 test stack
and full release gate before tagging.

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
documents.
Current release: `1.0.0-beta.19`.
Current release: `1.0.0-beta.20`.
## Repository Layout
@ -24,7 +24,7 @@ scripts/ local release and document tooling
## Beta Scope
`1.0.0-beta.19` keeps the `1.0.0-beta` language baseline, includes the
`1.0.0-beta.20` 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
@ -40,8 +40,9 @@ slice, the `1.0.0-beta.13` diagnostic catalog and schema policy slice, the
`1.0.0-beta.15` reserved generic collection boundary hardening and collection
ledger, the `1.0.0-beta.16` string scanning and token boundary foundation,
the `1.0.0-beta.17` JSON primitive scalar parsing foundation, the
`1.0.0-beta.18` JSON string token parsing foundation, and the
`1.0.0-beta.19` test discovery and user-project conformance foundation.
`1.0.0-beta.18` JSON string token parsing foundation, the
`1.0.0-beta.19` test discovery and user-project conformance foundation, and
the `1.0.0-beta.20` string search and ASCII trim foundation.
The language baseline supports practical local command-line, file, and
loopback-network programs with:
@ -57,6 +58,7 @@ loopback-network programs with:
- beta-scoped loopback TCP handles through `std.net`
- JSON string quoting, compact JSON text construction, primitive scalar token
parsing, and ASCII JSON string-token parsing through `std.json`
- byte-oriented string search and ASCII edge trimming through `std.string`
- hosted native builds through LLVM IR, Clang, and `runtime/runtime.c`
The generated standard-library API catalog is a beta discovery aid: it lists
@ -139,6 +141,14 @@ tests without executing test bodies. The list mode preserves existing file,
project, and workspace test ordering, honors `--filter <substring>`, and
remains beta tooling rather than a stable output schema.
The `1.0.0-beta.20` string search and ASCII trim foundation adds
`std.string.contains`, `std.string.index_of_option`,
`std.string.last_index_of_option`, `std.string.trim_ascii_start`,
`std.string.trim_ascii_end`, and `std.string.trim_ascii` as ordinary source
helpers over the existing byte string primitives. Search is byte-oriented,
empty needles match at the first index and at `(len value)` for last search,
and ASCII trimming removes only bytes `9`, `10`, `11`, `12`, `13`, and `32`.
Still deferred before stable: executable generics, generic aliases, maps/sets,
broad package registry semantics, stable Markdown schema, stable stdlib/API
compatibility freeze, DNS/TLS/async networking, LSP/watch guarantees, SARIF
@ -414,6 +424,17 @@ semantics; full JSON parsing; object/array parsing; tokenizer APIs; a language
slice/view feature; mutable strings; stable ABI/layout; or a stable stdlib/API
freeze.
## 1.0.0-beta.20 String Search And ASCII Trim Foundation
The `1.0.0-beta.20` scope extends the source-authored `std.string` facade
with byte-oriented search and ASCII trim helpers:
`contains`, `index_of_option`, `last_index_of_option`, `trim_ascii_start`,
`trim_ascii_end`, and `trim_ascii`.
This scope adds no compiler-known runtime names. It does not claim Unicode
scalar, grapheme, case-folding, locale, regex, tokenizer, mutable string,
language slice/view, stable ABI/layout, or stable stdlib/API semantics.
## 1.0.0-beta.15 Reserved Generic Collection Boundary Hardening And Collection Ledger
The `1.0.0-beta.15` release documents the current concrete collection and

2
compiler/Cargo.lock generated
View File

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

View File

@ -1,6 +1,6 @@
[package]
name = "glagol"
version = "1.0.0-beta.19"
version = "1.0.0-beta.20"
edition = "2021"
description = "Glagol, the first compiler for the Slovo language"
license = "MIT OR Apache-2.0"

View File

@ -1322,6 +1322,12 @@ const STANDARD_STRING_SOURCE_FACADE_ALPHA: &[&str] = &[
"slice_result",
"starts_with",
"ends_with",
"contains",
"index_of_option",
"last_index_of_option",
"trim_ascii_start",
"trim_ascii_end",
"trim_ascii",
"parse_i32_result",
"parse_i32_option",
"parse_u32_result",
@ -7374,8 +7380,10 @@ fn assert_project_std_import_string_tooling_matches_fixture(project: &Path) {
"test \"explicit std string parse integer fallbacks\" ... ok\n",
"test \"explicit std string parse float bool fallbacks\" ... ok\n",
"test \"explicit std string parse custom fallbacks\" ... ok\n",
"test \"explicit std string search helpers\" ... ok\n",
"test \"explicit std string ascii trim helpers\" ... ok\n",
"test \"explicit std string helpers all\" ... ok\n",
"10 test(s) passed\n",
"12 test(s) passed\n",
),
);
}
@ -8854,8 +8862,10 @@ fn assert_project_std_layout_local_string_tooling_matches_fixture(project: &Path
"test \"explicit local string parse integer fallbacks\" ... ok\n",
"test \"explicit local string parse float bool fallbacks\" ... ok\n",
"test \"explicit local string parse custom fallbacks\" ... ok\n",
"test \"explicit local string search helpers\" ... ok\n",
"test \"explicit local string ascii trim helpers\" ... ok\n",
"test \"explicit local string helpers all\" ... ok\n",
"10 test(s) passed\n",
"12 test(s) passed\n",
),
"std layout local string project test",
);
@ -8896,11 +8906,11 @@ fn assert_standard_string_source_fallback_helpers_alpha(project: &Path) {
"standard string source helper fixture must use only existing std.string runtime names",
);
assert!(
!string.contains("trim")
&& !string.contains("locale")
!string.contains("locale")
&& !string.contains("unicode")
&& !string.contains("bytes")
&& !string.contains("case_insensitive")
&& !string.contains("regex")
&& !string.contains("host_error"),
"standard string source helper fixture must not claim deferred parsing or richer error APIs"
);

View File

@ -15,8 +15,10 @@ const EXPECTED_STD_STRING_OUTPUT: &str = concat!(
"test \"explicit std string parse integer fallbacks\" ... ok\n",
"test \"explicit std string parse float bool fallbacks\" ... ok\n",
"test \"explicit std string parse custom fallbacks\" ... ok\n",
"test \"explicit std string search helpers\" ... ok\n",
"test \"explicit std string ascii trim helpers\" ... ok\n",
"test \"explicit std string helpers all\" ... ok\n",
"10 test(s) passed\n",
"12 test(s) passed\n",
);
const EXPECTED_STD_NUM_OUTPUT: &str = concat!(
@ -39,6 +41,12 @@ fn explicit_std_string_import_loads_repo_root_standard_source() {
"slice_result",
"starts_with",
"ends_with",
"contains",
"index_of_option",
"last_index_of_option",
"trim_ascii_start",
"trim_ascii_end",
"trim_ascii",
"parse_i32_result",
"parse_i32_option",
"parse_u32_result",

View File

@ -0,0 +1,478 @@
use std::{
env,
ffi::OsStr,
fs,
path::{Path, PathBuf},
process::{Command, Output},
sync::atomic::{AtomicUsize, Ordering},
};
static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0);
const EXPECTED_TEST_OUTPUT: &str = concat!(
"test \"explicit std string contains\" ... ok\n",
"test \"explicit std string index_of_option\" ... ok\n",
"test \"explicit std string last_index_of_option\" ... ok\n",
"test \"explicit std string ascii trim\" ... ok\n",
"test \"explicit std string search trim composition\" ... ok\n",
"test \"explicit std string search trim all\" ... ok\n",
"6 test(s) passed\n",
);
const STANDARD_STRING_SEARCH_TRIM_BETA20: &[&str] = &[
"contains",
"index_of_option",
"last_index_of_option",
"trim_ascii_start",
"trim_ascii_end",
"trim_ascii",
];
const ALLOWED_STD_REFERENCES: &[&str] = &[
"std.result",
"std.string.parse_bool_result",
"std.string.parse_f64_result",
"std.string.parse_i64_result",
"std.string.parse_u64_result",
"std.string.parse_i32_result",
"std.string.parse_u32_result",
"std.string.byte_at_result",
"std.string.slice_result",
"std.string.starts_with",
"std.string.ends_with",
"std.string.concat",
"std.string.len",
];
#[test]
fn explicit_std_string_search_and_ascii_trim_helpers_check_and_test() {
let project = write_project(
"std-string-search-trim-beta20",
r#"
(module main)
(import std.string (contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii))
(fn option_i32_eq ((maybe (option i32)) (expected i32)) -> bool
(match maybe
((some value)
(= value expected))
((none)
false)))
(fn option_i32_none ((maybe (option i32))) -> bool
(match maybe
((some value)
false)
((none)
true)))
(fn imported_string_contains_ok () -> bool
(if (contains "slovo compiler" "slo")
(if (contains "slovo compiler" "compiler")
(= (contains "slovo compiler" "missing") false)
false)
false))
(fn imported_string_index_of_ok () -> bool
(if (option_i32_eq (index_of_option "bananana" "ana") 1)
(if (option_i32_eq (index_of_option "slovo" "s") 0)
(if (option_i32_eq (index_of_option "slovo" "vo") 3)
(option_i32_none (index_of_option "slovo" "compiler"))
false)
false)
false))
(fn imported_string_last_index_of_ok () -> bool
(if (option_i32_eq (last_index_of_option "bananana" "ana") 5)
(if (option_i32_eq (last_index_of_option "slovo" "o") 4)
(if (option_i32_eq (last_index_of_option "slovo" "s") 0)
(option_i32_none (last_index_of_option "slovo" "compiler"))
false)
false)
false))
(fn imported_string_ascii_trim_ok () -> bool
(if (= (trim_ascii_start "\n\t slovo \t") "slovo \t")
(if (= (trim_ascii_end "\n\t slovo \t") "\n\t slovo")
(if (= (trim_ascii "\n\t slovo \t") "slovo")
(if (= (trim_ascii "slovo") "slovo")
(= (trim_ascii " ") "")
false)
false)
false)
false))
(fn imported_string_search_trim_composes_ok () -> bool
(if (= (trim_ascii " slovo compiler ") "slovo compiler")
(if (contains (trim_ascii " slovo compiler ") "compiler")
(if (option_i32_eq (index_of_option (trim_ascii_start "\t\tprefix-core") "core") 7)
(option_i32_eq (last_index_of_option (trim_ascii_end "core-core\n") "core") 5)
false)
false)
false))
(fn imported_string_search_trim_all_ok () -> bool
(if (imported_string_contains_ok)
(if (imported_string_index_of_ok)
(if (imported_string_last_index_of_ok)
(if (imported_string_ascii_trim_ok)
(imported_string_search_trim_composes_ok)
false)
false)
false)
false))
(fn main () -> i32
(if (imported_string_search_trim_all_ok)
42
1))
(test "explicit std string contains"
(imported_string_contains_ok))
(test "explicit std string index_of_option"
(imported_string_index_of_ok))
(test "explicit std string last_index_of_option"
(imported_string_last_index_of_ok))
(test "explicit std string ascii trim"
(imported_string_ascii_trim_ok))
(test "explicit std string search trim composition"
(imported_string_search_trim_composes_ok))
(test "explicit std string search trim all"
(= (main) 42))
"#,
);
let source = read(&project.join("src/main.slo"));
let std_string = read(&std_string_path());
assert!(
!project.join("src/string.slo").exists(),
"beta20 fixture must exercise repo-root std.string, not a local module copy"
);
assert!(
source.starts_with("(module main)\n\n(import std.string ("),
"beta20 fixture must use an explicit std.string import"
);
assert_std_string_search_trim_facades(&std_string);
let fmt = run_glagol([
OsStr::new("fmt"),
OsStr::new("--check"),
project.as_os_str(),
]);
assert_success("std string search trim fmt --check", &fmt);
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
assert_success_stdout(check, "", "std string search trim check");
let test = run_glagol([OsStr::new("test"), project.as_os_str()]);
assert_success_stdout(test, EXPECTED_TEST_OUTPUT, "std string search trim test");
}
#[test]
fn string_search_and_ascii_trim_helpers_are_not_compiler_known_runtime_calls() {
let std_string = read(&std_string_path());
assert_std_string_search_trim_facades(&std_string);
for helper in STANDARD_STRING_SEARCH_TRIM_BETA20 {
assert!(
!std_string.contains(&format!("std.string.{}", helper)),
"std.string.{} must remain source-authored, not a compiler-known runtime call",
helper
);
assert!(
!std_string.contains(&format!("__glagol_string_{}", helper)),
"std.string.{} must not introduce a private runtime symbol",
helper
);
}
let cases = [
UnsupportedRuntimeCase {
name: "contains",
symbol: "std.string.contains",
source: r#"
(module main)
(fn main () -> i32
(if (std.string.contains "slovo" "ovo")
0
1))
"#,
},
UnsupportedRuntimeCase {
name: "index-of-option",
symbol: "std.string.index_of_option",
source: r#"
(module main)
(fn main () -> i32
(match (std.string.index_of_option "slovo" "o")
((some value)
value)
((none)
0)))
"#,
},
UnsupportedRuntimeCase {
name: "last-index-of-option",
symbol: "std.string.last_index_of_option",
source: r#"
(module main)
(fn main () -> i32
(match (std.string.last_index_of_option "slovo" "o")
((some value)
value)
((none)
0)))
"#,
},
UnsupportedRuntimeCase {
name: "trim-ascii-start",
symbol: "std.string.trim_ascii_start",
source: r#"
(module main)
(fn main () -> i32
(std.string.len (std.string.trim_ascii_start " slovo")))
"#,
},
UnsupportedRuntimeCase {
name: "trim-ascii-end",
symbol: "std.string.trim_ascii_end",
source: r#"
(module main)
(fn main () -> i32
(std.string.len (std.string.trim_ascii_end "slovo ")))
"#,
},
UnsupportedRuntimeCase {
name: "trim-ascii",
symbol: "std.string.trim_ascii",
source: r#"
(module main)
(fn main () -> i32
(std.string.len (std.string.trim_ascii " slovo ")))
"#,
},
];
for case in cases {
let fixture = write_fixture(case.name, case.source);
let output = run_glagol([fixture.as_os_str()]);
assert_failure_stderr_contains(
&format!("direct {} runtime call", case.symbol),
&output,
&format!("standard library call `{}` is not supported", case.symbol),
);
}
}
fn assert_std_string_search_trim_facades(std_string: &str) {
assert!(
std_string.starts_with("(module string (export "),
"lib/std/string.slo must stay a source-authored module export"
);
let mut non_allowed_std = std_string.to_owned();
for allowed in ALLOWED_STD_REFERENCES {
non_allowed_std = non_allowed_std.replace(allowed, "");
}
assert!(
!non_allowed_std.contains("std."),
"std.string beta20 helpers must use only existing std.result bridges and promoted beta16-or-earlier std.string primitives"
);
for helper in STANDARD_STRING_SEARCH_TRIM_BETA20 {
assert!(
std_string.contains(&format!("(fn {} ", helper)),
"lib/std/string.slo is missing source facade `{}`",
helper
);
}
let search_trim_source = search_trim_source_region(std_string);
for primitive in [
("len", ["std.string.len", "(len "]),
(
"byte_at_result",
["std.string.byte_at_result", "(byte_at_result "],
),
(
"slice_result",
["std.string.slice_result", "(slice_result "],
),
("starts_with", ["std.string.starts_with", "(starts_with "]),
] {
assert!(
primitive
.1
.iter()
.any(|needle| search_trim_source.contains(needle)),
"beta20 search/trim facades must compose over existing beta16 string primitive `{}`",
primitive.0
);
}
assert!(
!std_string.contains("unicode")
&& !std_string.contains("grapheme")
&& !std_string.contains("locale")
&& !std_string.contains("case_insensitive")
&& !std_string.contains("regex"),
"beta20 string helpers must not claim deferred Unicode, locale, case-folding, or regex APIs"
);
}
fn search_trim_source_region(source: &str) -> &str {
let ends_with_end = function_range(source, "ends_with").1;
let parse_start = function_range(source, "parse_i32_result").0;
&source[ends_with_end..parse_start]
}
fn function_range(source: &str, name: &str) -> (usize, usize) {
let needle = format!("(fn {} ", name);
let start = source
.find(&needle)
.unwrap_or_else(|| panic!("missing function `{}`", name));
let mut depth = 0usize;
for (offset, byte) in source.as_bytes()[start..].iter().enumerate() {
match byte {
b'(' => depth += 1,
b')' => {
depth = depth
.checked_sub(1)
.unwrap_or_else(|| panic!("unbalanced function `{}`", name));
if depth == 0 {
return (start, start + offset + 1);
}
}
_ => {}
}
}
panic!("unterminated function `{}`", name);
}
fn run_glagol<I, S>(args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(env!("CARGO_BIN_EXE_glagol"))
.args(args)
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
.output()
.expect("run glagol")
}
fn write_project(name: &str, source: &str) -> PathBuf {
let root = temp_root(name);
let src = root.join("src");
fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err));
fs::write(
root.join("slovo.toml"),
format!(
"[project]\nname = \"{}\"\nsource_root = \"src\"\nentry = \"main\"\n",
name
),
)
.unwrap_or_else(|err| panic!("write project manifest: {}", err));
fs::write(src.join("main.slo"), source.trim_start())
.unwrap_or_else(|err| panic!("write project main.slo: {}", err));
root
}
fn write_fixture(name: &str, source: &str) -> PathBuf {
let mut path = env::temp_dir();
path.push(format!(
"glagol-standard-string-search-trim-beta20-{}-{}-{}.slo",
name,
std::process::id(),
NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
));
fs::write(&path, source.trim_start())
.unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
path
}
fn temp_root(name: &str) -> PathBuf {
let root = env::temp_dir().join(format!(
"glagol-standard-string-search-trim-beta20-{}-{}-{}",
name,
std::process::id(),
NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).unwrap_or_else(|err| panic!("create `{}`: {}", root.display(), err));
root
}
fn std_string_path() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../lib/std/string.slo")
}
fn read(path: &Path) -> String {
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
}
fn assert_success(context: &str, output: &Output) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
context,
output.status.code(),
stdout,
stderr
);
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}
fn assert_success_stdout(output: Output, expected: &str, context: &str) {
assert_success(context, &output);
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout, expected, "{}", context);
}
fn assert_failure_stderr_contains(context: &str, output: &Output, needle: &str) {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"{} unexpectedly passed\nstdout:\n{}\nstderr:\n{}",
context,
stdout,
stderr
);
assert!(
stdout.is_empty(),
"{} rejected compile wrote stdout:\n{}",
context,
stdout
);
assert!(
stderr.contains(needle),
"{} stderr did not contain `{}`:\n{}",
context,
needle,
stderr
);
}
struct UnsupportedRuntimeCase {
name: &'static str,
symbol: &'static str,
source: &'static str,
}

View File

@ -15,8 +15,10 @@ const EXPECTED_TEST_OUTPUT: &str = concat!(
"test \"explicit local string parse integer fallbacks\" ... ok\n",
"test \"explicit local string parse float bool fallbacks\" ... ok\n",
"test \"explicit local string parse custom fallbacks\" ... ok\n",
"test \"explicit local string search helpers\" ... ok\n",
"test \"explicit local string ascii trim helpers\" ... ok\n",
"test \"explicit local string helpers all\" ... ok\n",
"10 test(s) passed\n",
"12 test(s) passed\n",
);
const STANDARD_STRING_SOURCE_FALLBACK_HELPERS_ALPHA: &[&str] = &[
@ -26,6 +28,12 @@ const STANDARD_STRING_SOURCE_FALLBACK_HELPERS_ALPHA: &[&str] = &[
"slice_result",
"starts_with",
"ends_with",
"contains",
"index_of_option",
"last_index_of_option",
"trim_ascii_start",
"trim_ascii_end",
"trim_ascii",
"parse_i32_result",
"parse_i32_option",
"parse_u32_result",
@ -121,11 +129,11 @@ fn assert_local_string_fixture_is_source_authored(project: &Path) {
"string fixture must use only the existing promoted std.string runtime names"
);
assert!(
!string.contains("trim")
&& !string.contains("locale")
!string.contains("locale")
&& !string.contains("unicode")
&& !string.contains("bytes")
&& !string.contains("case_insensitive")
&& !string.contains("regex")
&& !string.contains("host_error"),
"string fixture must not claim deferred parsing or richer error APIs"
);

View File

@ -108,6 +108,16 @@ generated from `lib/std/*.slo` and guarded by `scripts/release-gate.sh`.
that composes `std.fs`, `std.string`, `std.math`, and `std.io` through explicit
standard imports, with focused check/test/doc/run coverage.
Released in `1.0.0-beta.20`: source-authored `std.string` search and ASCII
trim helpers. The scope adds `contains`, `index_of_option`,
`last_index_of_option`, `trim_ascii_start`, `trim_ascii_end`, and
`trim_ascii` over existing byte-oriented string primitives, with explicit
`std.string` import examples and focused compiler gates. Empty needles match
at first index `0` and last index `(len value)`; ASCII trimming removes only
bytes `9`, `10`, `11`, `12`, `13`, and `32`. Unicode/grapheme semantics,
case folding, regexes, tokenizers, mutable strings, slice/view syntax, new
runtime names, and stable stdlib/API promises remain deferred.
Why third: stdlib growth is already broad enough that naming and stability tiers
matter more than adding another isolated helper group.

View File

@ -12,6 +12,41 @@ integration/readiness release, not the first real beta.
No active unreleased compiler scope is documented here yet.
## 1.0.0-beta.20
Release label: `1.0.0-beta.20`
Release date: 2026-05-23
Release state: string search and ASCII trim foundation
### Summary
The beta.20 compiler/tooling slice adds focused coverage for source-authored
`std.string` search and ASCII trim helpers without adding compiler-known
runtime names:
- Bump the `glagol` compiler package version to `1.0.0-beta.20`.
- Add focused `standard_string_search_trim_beta20` coverage for explicit
`std.string` imports of `contains`, `index_of_option`,
`last_index_of_option`, `trim_ascii_start`, `trim_ascii_end`, and
`trim_ascii`.
- Require the new public helpers to remain source facades over beta16-or-earlier
string primitives and existing result/option shapes.
- Gate direct `std.string.contains`, `std.string.index_of_option`,
`std.string.last_index_of_option`, `std.string.trim_ascii_start`,
`std.string.trim_ascii_end`, and `std.string.trim_ascii` runtime calls as
unsupported compiler-known names.
- Add the focused beta20 test to `scripts/release-gate.sh`.
### Explicit Deferrals
This release does not implement new compiler-known `std.*` runtime names,
runtime C changes, source-language syntax, Unicode or grapheme semantics,
locale/case-folded search, regex, tokenizer/parser APIs, stable string
ABI/layout, stable allocation ownership rules, or a stable standard-library
compatibility contract.
## 1.0.0-beta.19
Release label: `1.0.0-beta.19`

View File

@ -22,8 +22,8 @@ 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.19`, released on 2026-05-23 as a test discovery
and user-project conformance foundation. It keeps the
Current stage: `1.0.0-beta.20`, released on 2026-05-23 as a string search
and ASCII trim foundation. 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,
@ -97,6 +97,14 @@ single-file, project, and workspace ordering, honors `--filter <substring>`,
and avoids executing test bodies. It keeps normal test execution output
unchanged when `--list` is absent.
The beta.20 compiler/tooling slice bumps the package version and gates
source-authored `std.string` facades for `contains`, `index_of_option`,
`last_index_of_option`, `trim_ascii_start`, `trim_ascii_end`, and `trim_ascii`
through explicit `std.string` imports. It keeps those helpers composed over
beta16-or-earlier string primitives and existing option/result shapes, and
verifies that direct compiler-known runtime calls for the new helper names
remain unsupported.
Generic vectors, generic collections, maps, sets, generic stdlib dispatch,
runtime collection changes, collection unification, stable human diagnostic
text, stable artifact-manifest or Markdown schema freezes, LSP/watch

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
experimental integration/readiness release, not as a beta maturity claim.
The current release is `1.0.0-beta.19`, published on 2026-05-23. It keeps the
The current release is `1.0.0-beta.20`, published on 2026-05-23. 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
@ -28,13 +28,45 @@ collection alias unification and generic reservation slice from
collection ledger from `1.0.0-beta.15`, plus the string scanning and token
boundary foundation from `1.0.0-beta.16`, and the JSON primitive scalar
parsing foundation from `1.0.0-beta.17`, plus the JSON string token parsing
foundation from `1.0.0-beta.18`, and the test discovery and user-project
conformance foundation from `1.0.0-beta.19`.
foundation from `1.0.0-beta.18`, the test discovery and user-project
conformance foundation from `1.0.0-beta.19`, and the string search and ASCII
trim foundation from `1.0.0-beta.20`.
## Unreleased
No active unreleased language scope is documented here yet.
## 1.0.0-beta.20
Release label: `1.0.0-beta.20`
Release name: String Search And ASCII Trim Foundation
Release date: 2026-05-23
Status: released beta standard-library string helper foundation on the
`1.0.0-beta` language baseline.
The source facade adds these source-authored `std.string` helpers:
- `contains ((value string) (needle string)) -> bool`
- `index_of_option ((value string) (needle string)) -> (option i32)`
- `last_index_of_option ((value string) (needle string)) -> (option i32)`
- `trim_ascii_start ((value string)) -> string`
- `trim_ascii_end ((value string)) -> string`
- `trim_ascii ((value string)) -> string`
Search remains byte-oriented over current runtime strings. Empty needles match:
`index_of_option` returns `some 0`, `last_index_of_option` returns
`some (len value)`, and `contains` returns `true`. Missing needles return
`none`.
The ASCII trim helpers remove only bytes `9`, `10`, `11`, `12`, `13`, and
`32` from the requested edges. This release adds no compiler-known runtime
names, Unicode/grapheme semantics, case folding, locale-sensitive matching,
regular expressions, tokenizer APIs, language slice/view syntax, mutable
strings, stable ABI/layout guarantees, or stable stdlib/API freeze.
## 1.0.0-beta.19
Release label: `1.0.0-beta.19`

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
first real general-purpose beta Slovo contract.
Current stage: `1.0.0-beta.19`, released on 2026-05-23 as a post-beta test
discovery and user-project conformance foundation. It keeps the
Current stage: `1.0.0-beta.20`, released on 2026-05-23 as a post-beta string
search and ASCII trim foundation. 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`
@ -26,8 +26,9 @@ documentation, `1.0.0-beta.12` concrete vector helper parity,
benchmark suite catalog and metadata gate, `1.0.0-beta.15` reserved generic
collection boundary hardening and collection ledger, and `1.0.0-beta.16`
string scanning and token boundary helpers, `1.0.0-beta.17` JSON primitive
scalar token parsing, `1.0.0-beta.18` JSON string token parsing, and
`1.0.0-beta.19` test discovery and user-project conformance tooling.
scalar token parsing, `1.0.0-beta.18` JSON string token parsing,
`1.0.0-beta.19` test discovery and user-project conformance tooling, and
`1.0.0-beta.20` string search and ASCII trim helpers.
`1.0.0-beta.16` adds `std.string` source facades and examples for
`byte_at_result`, `slice_result`, `starts_with`, and `ends_with`. These helpers
@ -76,6 +77,14 @@ freeze, stable standard-library/API compatibility freeze, registry semantics,
semver solving, performance claims, mutable vectors, language slice/view APIs,
additional runtime names, or Unicode/grapheme semantics.
`1.0.0-beta.20` adds source-authored `std.string` helpers for byte-oriented
`contains`, first/last index option search, and ASCII edge trimming over bytes
`9`, `10`, `11`, `12`, `13`, and `32`. Empty needles match at first index `0`
and last index `(len value)`. The scope adds no compiler-known runtime names,
Unicode/grapheme semantics, case folding, regexes, tokenizer APIs, language
slice/view syntax, mutable strings, stable ABI/layout guarantees, or stable
stdlib/API freeze.
The final experimental precursor scope is `exp-125`, defined in
`.llm/EXP_125_UNSIGNED_U32_U64_NUMERIC_AND_STDLIB_BREADTH_ALPHA.md`. Its
unsigned direct-value flow, parse/format runtime lanes, and matching staged

View File

@ -9,8 +9,9 @@ diagnostic catalog and schema policy update, and `1.0.0-beta.14` benchmark
suite catalog and metadata gate, `1.0.0-beta.15` reserved generic collection
boundary hardening and collection ledger, `1.0.0-beta.16` string scanning
and token boundary foundation, `1.0.0-beta.17` JSON primitive scalar parsing
foundation, `1.0.0-beta.18` JSON string token parsing foundation, and
`1.0.0-beta.19` test discovery and user-project conformance tooling. The
foundation, `1.0.0-beta.18` JSON string token parsing foundation,
`1.0.0-beta.19` test discovery and user-project conformance tooling, and
`1.0.0-beta.20` string search and ASCII trim foundation. The
language contract
integrates
promoted language slices through `exp-125` and the historical publication
@ -228,6 +229,17 @@ Current v1 release surface and explicit experimental targets:
coverage/event streams, stable artifact-manifest or Markdown schemas,
LSP/watch behavior, SARIF/daemon protocols, package registries, semver
solving, or performance claims
- `1.0.0-beta.20` string search and ASCII trim target:
source-authored `std.string.contains`, `std.string.index_of_option`,
`std.string.last_index_of_option`, `std.string.trim_ascii_start`,
`std.string.trim_ascii_end`, and `std.string.trim_ascii` compose over the
existing byte-oriented string primitives; search is byte-oriented, missing
needles return `none`, empty needles match at first index `0` and last index
`(len value)`, and trim removes only bytes `9`, `10`, `11`, `12`, `13`, and
`32` from the requested edges. This target does not add compiler-known
runtime names, Unicode/grapheme semantics, case folding, locale-sensitive
matching, regex, tokenizer APIs, language slice/view syntax, mutable
strings, stable ABI/layout, performance claims, or stable stdlib/API 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

View File

@ -6,7 +6,7 @@ Do not edit this file by hand.
## Stability Tiers
- `beta-supported`: exported from `lib/std` and covered by source-search, promotion, or facade gates in the current beta line.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.19`; future releases may mark new helpers this way before they graduate.
- `experimental`: not used for exported `lib/std` helpers in `1.0.0-beta.20`; future releases may mark new helpers this way before they graduate.
- `internal`: helper names that are not exported from their module; they are intentionally omitted from this catalog.
The catalog is a beta API discovery aid, not a stable `1.0.0` standard-library freeze.
@ -16,7 +16,7 @@ Only exported `(fn ...)` helpers are listed; `(type ...)` aliases and non-export
## Summary
- Modules: 19
- Exported helper signatures: 590
- Exported helper signatures: 596
- Exported type aliases omitted: 0
- Default tier: `beta-supported`
@ -487,7 +487,7 @@ Only exported `(fn ...)` helpers are listed; `(type ...)` aliases and non-export
- Path: `lib/std/string.slo`
- Tier: `beta-supported`
- Exported helper signatures: 30
- Exported helper signatures: 36
- `len ((value string)) -> i32`
- `concat ((left string) (right string)) -> string`
@ -495,6 +495,12 @@ Only exported `(fn ...)` helpers are listed; `(type ...)` aliases and non-export
- `slice_result ((value string) (start i32) (count i32)) -> (result string i32)`
- `starts_with ((value string) (prefix string)) -> bool`
- `ends_with ((value string) (suffix string)) -> bool`
- `contains ((value string) (needle string)) -> bool`
- `index_of_option ((value string) (needle string)) -> (option i32)`
- `last_index_of_option ((value string) (needle string)) -> (option i32)`
- `trim_ascii_start ((value string)) -> string`
- `trim_ascii_end ((value string)) -> string`
- `trim_ascii ((value string)) -> string`
- `parse_i32_result ((value string)) -> (result i32 i32)`
- `parse_i32_option ((value string)) -> (option i32)`
- `parse_u32_result ((value string)) -> (result u32 i32)`

View File

@ -1,6 +1,6 @@
(module main)
(import std.string (len concat byte_at_result slice_result starts_with ends_with parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(import std.string (len concat byte_at_result slice_result starts_with ends_with contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(fn imported_string_concat () -> string
(concat "slo" "vo"))
@ -187,6 +187,50 @@
false)
false))
(fn imported_option_i32_is_some_value ((actual (option i32)) (expected i32)) -> bool
(match actual
((some payload)
(= payload expected))
((none)
false)))
(fn imported_option_i32_is_none ((actual (option i32))) -> bool
(match actual
((some payload)
false)
((none)
true)))
(fn imported_string_search_ok () -> bool
(if (contains "alpha beta alpha" "beta")
(if (contains "alpha" "z")
false
(if (contains "alpha" "")
(if (imported_option_i32_is_some_value (index_of_option "alpha beta alpha" "alpha") 0)
(if (imported_option_i32_is_none (index_of_option "alpha" "z"))
(if (imported_option_i32_is_some_value (index_of_option "alpha" "") 0)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha beta alpha" "alpha") 11)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha" "") 5)
(imported_option_i32_is_none (last_index_of_option "alpha" "z"))
false)
false)
false)
false)
false)
false))
false))
(fn imported_string_ascii_trim_ok () -> bool
(if (= (trim_ascii_start " \t\nslovo") "slovo")
(if (= (trim_ascii_end "slovo \t\n") "slovo")
(if (= (trim_ascii " \t\nslovo \t\n") "slovo")
(if (= (trim_ascii " \t\n") "")
(= (trim_ascii "slovo") "slovo")
false)
false)
false)
false))
(fn imported_string_helpers_ok () -> bool
(if (= (imported_string_len_concat_score) 42)
(if (imported_string_byte_at_ok)
@ -196,7 +240,11 @@
(if (imported_string_parse_options_ok)
(if (imported_string_parse_integer_fallbacks_ok)
(if (imported_string_parse_float_bool_fallbacks_ok)
(imported_string_parse_custom_fallbacks_ok)
(if (imported_string_parse_custom_fallbacks_ok)
(if (imported_string_search_ok)
(imported_string_ascii_trim_ok)
false)
false)
false)
false)
false)
@ -238,5 +286,11 @@
(test "explicit std string parse custom fallbacks"
(imported_string_parse_custom_fallbacks_ok))
(test "explicit std string search helpers"
(imported_string_search_ok))
(test "explicit std string ascii trim helpers"
(imported_string_ascii_trim_ok))
(test "explicit std string helpers all"
(= (main) 42))

View File

@ -1,6 +1,6 @@
(module main)
(import std.string (len concat byte_at_result slice_result starts_with ends_with parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(import std.string (len concat byte_at_result slice_result starts_with ends_with contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(fn imported_string_concat () -> string
(concat "slo" "vo"))
@ -187,6 +187,50 @@
false)
false))
(fn imported_option_i32_is_some_value ((actual (option i32)) (expected i32)) -> bool
(match actual
((some payload)
(= payload expected))
((none)
false)))
(fn imported_option_i32_is_none ((actual (option i32))) -> bool
(match actual
((some payload)
false)
((none)
true)))
(fn imported_string_search_ok () -> bool
(if (contains "alpha beta alpha" "beta")
(if (contains "alpha" "z")
false
(if (contains "alpha" "")
(if (imported_option_i32_is_some_value (index_of_option "alpha beta alpha" "alpha") 0)
(if (imported_option_i32_is_none (index_of_option "alpha" "z"))
(if (imported_option_i32_is_some_value (index_of_option "alpha" "") 0)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha beta alpha" "alpha") 11)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha" "") 5)
(imported_option_i32_is_none (last_index_of_option "alpha" "z"))
false)
false)
false)
false)
false)
false))
false))
(fn imported_string_ascii_trim_ok () -> bool
(if (= (trim_ascii_start " \t\nslovo") "slovo")
(if (= (trim_ascii_end "slovo \t\n") "slovo")
(if (= (trim_ascii " \t\nslovo \t\n") "slovo")
(if (= (trim_ascii " \t\n") "")
(= (trim_ascii "slovo") "slovo")
false)
false)
false)
false))
(fn imported_string_helpers_ok () -> bool
(if (= (imported_string_len_concat_score) 42)
(if (imported_string_byte_at_ok)
@ -196,7 +240,11 @@
(if (imported_string_parse_options_ok)
(if (imported_string_parse_integer_fallbacks_ok)
(if (imported_string_parse_float_bool_fallbacks_ok)
(imported_string_parse_custom_fallbacks_ok)
(if (imported_string_parse_custom_fallbacks_ok)
(if (imported_string_search_ok)
(imported_string_ascii_trim_ok)
false)
false)
false)
false)
false)
@ -238,5 +286,11 @@
(test "explicit std string parse custom fallbacks"
(imported_string_parse_custom_fallbacks_ok))
(test "explicit std string search helpers"
(imported_string_search_ok))
(test "explicit std string ascii trim helpers"
(imported_string_ascii_trim_ok))
(test "explicit std string helpers all"
(= (main) 42))

View File

@ -1,6 +1,6 @@
(module main)
(import string (len concat byte_at_result slice_result starts_with ends_with parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(import string (len concat byte_at_result slice_result starts_with ends_with contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(fn imported_string_concat () -> string
(concat "slo" "vo"))
@ -187,6 +187,50 @@
false)
false))
(fn imported_option_i32_is_some_value ((actual (option i32)) (expected i32)) -> bool
(match actual
((some payload)
(= payload expected))
((none)
false)))
(fn imported_option_i32_is_none ((actual (option i32))) -> bool
(match actual
((some payload)
false)
((none)
true)))
(fn imported_string_search_ok () -> bool
(if (contains "alpha beta alpha" "beta")
(if (contains "alpha" "z")
false
(if (contains "alpha" "")
(if (imported_option_i32_is_some_value (index_of_option "alpha beta alpha" "alpha") 0)
(if (imported_option_i32_is_none (index_of_option "alpha" "z"))
(if (imported_option_i32_is_some_value (index_of_option "alpha" "") 0)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha beta alpha" "alpha") 11)
(if (imported_option_i32_is_some_value (last_index_of_option "alpha" "") 5)
(imported_option_i32_is_none (last_index_of_option "alpha" "z"))
false)
false)
false)
false)
false)
false))
false))
(fn imported_string_ascii_trim_ok () -> bool
(if (= (trim_ascii_start " \t\nslovo") "slovo")
(if (= (trim_ascii_end "slovo \t\n") "slovo")
(if (= (trim_ascii " \t\nslovo \t\n") "slovo")
(if (= (trim_ascii " \t\n") "")
(= (trim_ascii "slovo") "slovo")
false)
false)
false)
false))
(fn imported_string_helpers_ok () -> bool
(if (= (imported_string_len_concat_score) 42)
(if (imported_string_byte_at_ok)
@ -196,7 +240,11 @@
(if (imported_string_parse_options_ok)
(if (imported_string_parse_integer_fallbacks_ok)
(if (imported_string_parse_float_bool_fallbacks_ok)
(imported_string_parse_custom_fallbacks_ok)
(if (imported_string_parse_custom_fallbacks_ok)
(if (imported_string_search_ok)
(imported_string_ascii_trim_ok)
false)
false)
false)
false)
false)
@ -238,5 +286,11 @@
(test "explicit local string parse custom fallbacks"
(imported_string_parse_custom_fallbacks_ok))
(test "explicit local string search helpers"
(imported_string_search_ok))
(test "explicit local string ascii trim helpers"
(imported_string_ascii_trim_ok))
(test "explicit local string helpers all"
(= (main) 42))

View File

@ -1,4 +1,4 @@
(module string (export len concat byte_at_result slice_result starts_with ends_with parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(module string (export len concat byte_at_result slice_result starts_with ends_with contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(import result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))
@ -20,6 +20,99 @@
(fn ends_with ((value string) (suffix string)) -> bool
(std.string.ends_with value suffix))
(fn suffix_starts_with ((value string) (needle string) (position i32) (value_len i32)) -> bool
(match (slice_result value position (- value_len position))
((ok text)
(starts_with text needle))
((err code)
false)))
(fn index_of_option ((value string) (needle string)) -> (option i32)
(let value_len i32 (len value))
(let needle_len i32 (len needle))
(let max_start i32 (- value_len needle_len))
(var position i32 0)
(var found_position i32 -1)
(while (and (> needle_len 0) (and (< found_position 0) (<= position max_start)))
(set found_position (if (suffix_starts_with value needle position value_len)
position
found_position))
(set position (+ position 1)))
(if (= needle_len 0)
(some i32 0)
(if (< found_position 0)
(none i32)
(some i32 found_position))))
(fn last_index_of_option ((value string) (needle string)) -> (option i32)
(let value_len i32 (len value))
(let needle_len i32 (len needle))
(let max_start i32 (- value_len needle_len))
(var position i32 0)
(var found_position i32 -1)
(while (and (> needle_len 0) (<= position max_start))
(set found_position (if (suffix_starts_with value needle position value_len)
position
found_position))
(set position (+ position 1)))
(if (= needle_len 0)
(some i32 value_len)
(if (< found_position 0)
(none i32)
(some i32 found_position))))
(fn contains ((value string) (needle string)) -> bool
(match (index_of_option value needle)
((some position)
true)
((none)
false)))
(fn is_ascii_trim_byte ((value i32)) -> bool
(if (= value 9)
true
(if (= value 10)
true
(if (= value 11)
true
(if (= value 12)
true
(if (= value 13)
true
(= value 32)))))))
(fn byte_is_ascii_trim ((value string) (position i32)) -> bool
(match (byte_at_result value position)
((ok byte)
(is_ascii_trim_byte byte))
((err code)
false)))
(fn trim_ascii_start ((value string)) -> string
(let value_len i32 (len value))
(var start i32 0)
(while (and (< start value_len) (byte_is_ascii_trim value start))
(set start (+ start 1)))
(match (slice_result value start (- value_len start))
((ok text)
text)
((err code)
value)))
(fn trim_ascii_end ((value string)) -> string
(let value_len i32 (len value))
(var end i32 value_len)
(while (and (> end 0) (byte_is_ascii_trim value (- end 1)))
(set end (- end 1)))
(match (slice_result value 0 end)
((ok text)
text)
((err code)
value)))
(fn trim_ascii ((value string)) -> string
(trim_ascii_end (trim_ascii_start value)))
(fn parse_i32_result ((value string)) -> (result i32 i32)
(std.string.parse_i32_result value))

View File

@ -22,7 +22,8 @@ helpers updated through `exp-54`, `exp-55`, and `exp-72`; CLI option helpers
updated through `exp-110`; CLI local-source gate
aligned in `exp-78`; string fallback helpers updated through `exp-60` and
`exp-68`; string option helpers updated through `exp-110`; string
byte-scanning and token-boundary helpers updated in `1.0.0-beta.16`; process fallback
byte-scanning and token-boundary helpers updated in `1.0.0-beta.16`; string
search and ASCII trim helpers updated in `1.0.0-beta.20`; process fallback
helpers updated through `exp-61`; process typed helpers updated through
`exp-67` and `exp-71`; process option helpers updated through `exp-110`; env
fallback helpers updated through `exp-62` and `exp-69`; env typed helpers
@ -229,6 +230,12 @@ exp-125 target adds matching `parse_u32_result`, `parse_u32_option`,
`1.0.0-beta.16` adds `byte_at_result`, `slice_result`, `starts_with`, and
`ends_with` as byte-oriented helpers over current runtime strings. Invalid
byte indexes and ranges return `err 1`; empty prefixes and suffixes match.
`1.0.0-beta.20` adds `contains`,
`index_of_option`, `last_index_of_option`, `trim_ascii_start`,
`trim_ascii_end`, and `trim_ascii` as ordinary source helpers over those same
byte primitives. Empty needles match at first index `0` and last index
`(len value)`. ASCII trim removes only bytes `9`, `10`, `11`, `12`, `13`, and
`32` from the requested edges.
This string scanning foundation does not add Unicode scalar or grapheme
semantics, full JSON parsing, tokenizer objects, language-level slice/view
syntax, mutable strings, stable ABI/layout promises, or a stable stdlib/API

View File

@ -1,4 +1,4 @@
(module string (export len concat byte_at_result slice_result starts_with ends_with parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(module string (export len concat byte_at_result slice_result starts_with ends_with contains index_of_option last_index_of_option trim_ascii_start trim_ascii_end trim_ascii parse_i32_result parse_i32_option parse_u32_result parse_u32_option parse_i64_result parse_i64_option parse_u64_result parse_u64_option parse_f64_result parse_f64_option parse_bool_result parse_bool_option parse_i32_or_zero parse_u32_or_zero parse_i64_or_zero parse_u64_or_zero parse_f64_or_zero parse_bool_or_false parse_i32_or parse_u32_or parse_i64_or parse_u64_or parse_f64_or parse_bool_or))
(import std.result (ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))
@ -20,6 +20,99 @@
(fn ends_with ((value string) (suffix string)) -> bool
(std.string.ends_with value suffix))
(fn suffix_starts_with ((value string) (needle string) (position i32) (value_len i32)) -> bool
(match (slice_result value position (- value_len position))
((ok text)
(starts_with text needle))
((err code)
false)))
(fn index_of_option ((value string) (needle string)) -> (option i32)
(let value_len i32 (len value))
(let needle_len i32 (len needle))
(let max_start i32 (- value_len needle_len))
(var position i32 0)
(var found_position i32 -1)
(while (and (> needle_len 0) (and (< found_position 0) (<= position max_start)))
(set found_position (if (suffix_starts_with value needle position value_len)
position
found_position))
(set position (+ position 1)))
(if (= needle_len 0)
(some i32 0)
(if (< found_position 0)
(none i32)
(some i32 found_position))))
(fn last_index_of_option ((value string) (needle string)) -> (option i32)
(let value_len i32 (len value))
(let needle_len i32 (len needle))
(let max_start i32 (- value_len needle_len))
(var position i32 0)
(var found_position i32 -1)
(while (and (> needle_len 0) (<= position max_start))
(set found_position (if (suffix_starts_with value needle position value_len)
position
found_position))
(set position (+ position 1)))
(if (= needle_len 0)
(some i32 value_len)
(if (< found_position 0)
(none i32)
(some i32 found_position))))
(fn contains ((value string) (needle string)) -> bool
(match (index_of_option value needle)
((some position)
true)
((none)
false)))
(fn is_ascii_trim_byte ((value i32)) -> bool
(if (= value 9)
true
(if (= value 10)
true
(if (= value 11)
true
(if (= value 12)
true
(if (= value 13)
true
(= value 32)))))))
(fn byte_is_ascii_trim ((value string) (position i32)) -> bool
(match (byte_at_result value position)
((ok byte)
(is_ascii_trim_byte byte))
((err code)
false)))
(fn trim_ascii_start ((value string)) -> string
(let value_len i32 (len value))
(var start i32 0)
(while (and (< start value_len) (byte_is_ascii_trim value start))
(set start (+ start 1)))
(match (slice_result value start (- value_len start))
((ok text)
text)
((err code)
value)))
(fn trim_ascii_end ((value string)) -> string
(let value_len i32 (len value))
(var end i32 value_len)
(while (and (> end 0) (byte_is_ascii_trim value (- end 1)))
(set end (- end 1)))
(match (slice_result value 0 end)
((ok text)
text)
((err code)
value)))
(fn trim_ascii ((value string)) -> string
(trim_ascii_end (trim_ascii_start value)))
(fn parse_i32_result ((value string)) -> (result i32 i32)
(std.string.parse_i32_result value))

View File

@ -70,6 +70,7 @@ cargo test --test standard_string_scanning_beta16
cargo test --test standard_json_scalar_parsing_beta17
cargo test --test standard_json_string_parsing_beta18
cargo test --test test_discovery_beta19
cargo test --test standard_string_search_trim_beta20
# Full cargo test includes unignored integration gates such as dx_v1_7,
# beta_v2_0_0_beta_1, and beta_1_0_0.
cargo test