Read a file line by line with a stream
Count the lines of a log file in fixed-size reads, with every bound stated.
Generated by cargo run -p agent_docs from compiler/examples/. Edit the example files, then regenerate.
Count the lines of a log file in fixed-size reads, with every bound stated.
Fs.file_source(file, 4_096) is a view over the open File: each pull reads up to 4 KiB.
Encoding.decode_source turns those bytes into text, holding a UTF-8 sequence that a read
split until the next chunk completes it, and lines(1_024) splits the text on LF wherever
the chunks break, dropping a CR before an LF. fold walks the lines once. Memory is bounded
by the chunk and the line limit, not the file: a line over 1,024 bytes fails
StreamError(MaxLine) instead of growing, and a malformed byte fails EncodingError.
A source is a value. summarize_text takes any Source<String, e>, so a test hands it a
pure source built with Source.from_list, which reads the same items every time it is
pulled. The file view advances the file and is valid only inside the Fs.with_file
callback: returning it from the callback is V6003. The tests use Fs.memory, so they
need no permissions.
Source project · harness mode: test.
From the repository root, with this checkout's sakura on PATH:
cd compiler/examples/stream_lines
sakura check --json
sakura test --jsonManifest and permissions
[package]
name = "stream_lines"
version = "0.1.0"
edition = "2026"
[permissions]Source and tests
src/main.sak includes all required imports and tests.
import std/encoding.{Encoding, EncodingError, Errors, Malformed}
import std/fs.{Fs, FsError, Read}
import std/path.{Path}
import std/stream.{MaxLine, Source, StreamError}
pub type Summary = { lines: Int, errors: Int, longest: Int }
/// Counts the lines of a UTF-8 log and those that start with `ERROR`, reading the file in
/// 4 KiB chunks. A line over 1,024 bytes fails `StreamError(MaxLine)`; a malformed byte fails
/// `EncodingError`.
pub fn summarize(path: Path) -> Summary ! {Fs, Fail<EncodingError | FsError | StreamError>} {
Fs.with_file(path, Read, fn(file) {
summarize_text(Encoding.decode_source(Fs.file_source(file, 4_096), Encoding.Utf8, Errors.Fatal))
})
}
fn count(s: Summary, line: String) -> Summary {
Summary {
lines: s.lines + 1,
errors: s.errors + (if String.starts_with(line, "ERROR") { 1 } else { 0 }),
longest: Int.max(s.longest, String.len(line)),
}
}
/// The same count over any source of text: a pure source in a test, a view in `summarize`.
pub fn summarize_text<e>(text: Source<String, e>) -> Summary ! {Fail<StreamError> | e} {
text.lines(1_024).fold(Summary { lines: 0, errors: 0, longest: 0 }, count)
}
test "counts lines and errors across chunk boundaries" {
let log = Path.of("app.log")
let body = "INFO start\r\nERROR disk full\nINFO retry\nERROR gave up"
with Fs.memory([(log, String.to_bytes(body))]) {
expect_eq(try { summarize(log) }, Ok(Summary { lines: 4, errors: 2, longest: 15 }))
}
}
test "a pure source gives the same answer without a file" {
let text: Source<String, {}> = Source.from_list(["INFO a\nERR", "OR b\n", "INFO c"])
expect_eq(try { summarize_text(text) }, Ok(Summary { lines: 3, errors: 1, longest: 7 }))
}
test "a line over the bound fails instead of growing" {
let text: Source<String, {}> = Source.from_list([
String.repeat("x", 600),
String.repeat("y", 600),
])
expect_eq(try { summarize_text(text) }, Err(StreamError(MaxLine)))
}
test "a malformed byte fails the read" {
let log = Path.of("bad.log")
with Fs.memory([(log, Bytes.from_list([0x6F, 0x6B, 0x0A, 0xFF]))]) {
expect_eq(try { summarize(log) }, Err(EncodingError(Malformed(3))))
}
}Compiler provenance and regeneration
Finish only when no holes remain, independent tests pass, the requested target builds, and contracts/tests/permissions have not been weakened. Repeat the frozen baseline check after the final edits. Report missing prerequisites and failing checks explicitly; do not claim a skipped test passed.
These ten projects teach supported Sakura APIs on the JavaScript backend. Each
project is exercised offline by cargo test -p sakura_cli --test examples;
HTTP and LLM exchanges are synthetic committed cassettes, with no network or API keys.
Use Node 26 for execution, including its built-in SQLite implementation.
The initial API baseline is compiler revision
5cae349516c21714d90d36e87f1cfd6debe980c6.
CI reruns these projects against the compiler in the same checkout. Build that
checkout's CLI and run sakura --version and sakura prompt before adapting a recipe.
Sakura Lang is pre-1.0: read STABILITY.md before depending on an API. An installed
skill's frontmatter records its compiler version and build revision. These teaching examples are separate from the agent
evaluation tasks.
The source of each page is its example's recipe.md, sakura.toml,
src/main.sak and fixture files. Run cargo run -p agent_docs from compiler/
to regenerate the cookbook and skill reference. Edit those sources, not the rendered pages.
Last updated