HHYHHY
LearnExamplesExtensionsSpecGitHub中文
中文
HHY

HHY LANGUAGE

HHY Language Manual

V1.3.10Flow-first system scripting language · Complete manualhhylang.dev

CONTENTS

Table of contents

  1. 01Quick StartGuide
  2. 02Language BasicsGuide
  3. 03Flow and StreamsGuide
  4. 04Files and PathsGuide
  5. 05Text, JSON, and CSVGuide
  6. 06Processes and SystemGuide
  7. 07HTTPGuide
  8. 08Parallel and WatchGuide
  9. 09Modules and ErrorsGuide
  10. 10Practical Automation RecipesGuide
  11. 11Project: FlowGuardProjects
  12. 12Project: DataFlow ETLProjects
  13. 13Project: Asset GovernanceProjects
  14. 14Project: Hong Kong Film CompaniesProjects
  15. 15Project: Multi-API Data CollectorProjects
  16. 16Project: SiteGraph AuditorProjects
  17. 17Complete Syntax ReferenceReference
  18. 18Standard Library Function IndexReference
  19. 19CLI ReferenceReference
  20. 20Extension SystemExtensions
  21. 21Database Extension GuideExtensions
  22. 22HTML Extension and Crawler FrameworkExtensions
  23. 23Language and VM Evolution RoadmapRoadmap
  24. 24Editor Language SupportTooling
  25. 25HHY Language Status Report · 2026-09-01Language Reports

Guide · 01

Quick Start

Install HHY, run your first Flow, and learn the daily commands in five minutes.

1.1Minute 1: one-command install (recommended)

The installer supports macOS arm64, Linux x86_64, and Linux arm64. It detects the platform, downloads the V1.3.10 archive and matching .sha256, and installs only after verification. sudo is not required by default.

sh
curl -fsSL https://hhylang.dev/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
hhy --version
The default version directory is ~/.local/share/hhy/1.3.10 and the command entry point is ~/.local/bin/hhy. The installer resolves the latest stable GitHub release by default; use HHY_VERSION to pin or roll back, and HHY_INSTALL_ROOT or HHY_BIN_DIR to override locations.

1.2macOS: install from the Homebrew tap

Apple Silicon Macs can use the Formula maintained in this repository. The explicit Git URL lets the repository act as a tap before a separate homebrew-tap repository is created.

sh
brew tap hh696-wq/hhy https://github.com/hh696-wq/hhy-vm.git
brew install hhy
hhy --version
The current Formula supports macOS arm64 only and pins the official archive and SHA-256. Use the installer or a Release archive on Linux.

1.3Option 2: download a Release

If you are not modifying the HHY Runtime, an official V1.3.10 archive is the fastest path. Choose darwin-arm64, linux-x86_64, or linux-arm64 for your OS and CPU. Archives include the executable, official sample and database extensions, required non-system runtime libraries, docs, licenses, and build metadata.

Open HHY GitHub Releases ↗
Download the latest stable archive and its matching .sha256 file or SHA256SUMS.
https://github.com/hh696-wq/hhy-vm/releases

sh
tar -xzf hhy-1.3.10-PLATFORM-ARCH.tar.gz
cd hhy-1.3.10-PLATFORM-ARCH
./bin/hhy --version
./bin/hhy run examples/07-language-basics.hhy
Keep bin/ and lib/ in their original relative positions so the portable executable can find bundled libraries. Replace PLATFORM-ARCH with darwin-arm64, linux-x86_64, or linux-arm64.

1.4Verify the download and add it to PATH

Before running a download, verify it with the matching .sha256 file or SHA256SUMS. macOS includes shasum; Linux commonly provides sha256sum.

sh
# macOS
shasum -a 256 -c hhy-1.3.10-darwin-arm64.tar.gz.sha256

# Linux
sha256sum -c hhy-1.3.10-linux-x86_64.tar.gz.sha256

# Add to PATH for this terminal (use the real absolute path)
export PATH="/absolute/path/hhy-1.3.10-PLATFORM-ARCH/bin:$PATH"
hhy --version

For permanent access, put the export PATH line in your shell profile. You may also keep invoking ./bin/hhy from the extracted directory without a system-wide install.

1.5Option 3: build from source

Build from source when developing the Runtime, validating current source, or choosing a custom installation prefix. HHY V1.3.10 supports macOS arm64, Linux arm64, and Linux x86_64 and requires a C11 compiler, make, libcurl, PCRE2, and BDWGC. The database extension additionally needs the corresponding PostgreSQL libpq or MySQL client development library.

sh
brew install curl pcre2 bdw-gc
git clone https://github.com/hh696-wq/hhy-vm.git
cd hhy-vm
make
make test
./build/hhy --version
The brew command applies only to macOS. Linux package names vary by distribution; see INSTALL.md for the full dependency matrix.

1.6Install the source build

sh
make install PREFIX="$(brew --prefix)"
hhy --version

PREFIX may be a custom absolute path. Once PREFIX/bin is on PATH, execute any .hhy file with hhy run.

1.7Your first script

hello.hhy
let language = "HHY"

["Flow", "Pipe", "System"]
    |> map { word -> "{language}: {word}" }
    |> print
sh
hhy check hello.hhy
hhy run hello.hhy

let creates a binding; the List literal stores three Strings; |> injects the left value into the next call; the map closure creates one new String per item; print consumes the result. check validates lexical syntax, scope, modules, and known standard-library calls without effects.

1.8Running scripts and the development workflow

TaskCommandPurpose
Formathhy fmt script.hhyWrite canonical HHY formatting
Check formathhy fmt --check script.hhyVerify in CI without changing files
Check scripthhy check script.hhyValidate syntax, scope, and known APIs
Runhhy run script.hhyExecute the script
Pass argumentshhy run script.hhy input.csv output.jsonArguments enter read-only args
Preview planhhy run --dry-run script.hhyInspect a redacted plan without external effects

HHY source files use the .hhy suffix. View complete command help:

sh
hhy --help

Guide · 02

Language Basics

Variables, values, functions, conditions, loops, and scope.

2.1What dynamic typing means

HHY declarations omit types; each value carries its logical type at runtime. Dynamic does not mean coercive: conditions require Bool, String never automatically becomes Number, Bool, or Path, and invalid arity or operations raise structured errors. Use type(value) to inspect a type and is_type(value, name) to test it.

hhy
let nothing = null
let enabled = true
let count = 42
let ratio = 0.75
let title = "HHY"
let pattern = /ERROR|WARN/i
let names = ["Ada", "Linus"]
let user = { name: "Ada", active: true }
let indexes = 0..3
let size_limit = 10mib
let timeout_limit = 5s
let completion = 80%

print(type(user))
print(is_type(title, "String"))

2.2Scalar and unit types

TypeExampleUse
NullnullAbsence
BooltrueConditions and predicates
Int42Integer arithmetic
Float3.14Floating-point arithmetic
String"hello"UTF-8 text
Regex/ERROR/iText matching
Bytes10mibFile or memory size
Duration5sTimeouts and intervals
Percent80%Ratios
DateTimenow()Zoned time
Pathpath("logs")Filesystem paths

Precise String, number, unit, and Path edge cases belong in Reference. For ordinary scripts, remember that HHY never implicitly converts among String, Number, Bool, and Path.

Open the type and syntax reference →
Look up exact UTF-8, overflow, operator, and literal behavior.
/en/learn/syntax-reference

2.3List, Map, and Range

List indices start at zero and out-of-range access raises IndexError. Map keys are Strings and preserve insertion order; map.key equals map["key"]. A missing key normally returns null, while require distinguishes a missing key from a present key whose value is null. Range a..b includes a and excludes b without allocating a List.

hhy
let original = ["Flow", "System"]
let extended = append(original, "Pipe")
let shortened = remove_at(extended, 1)

let config = { retries: 3, label: null }
let updated = put(config, "timeout", 5s)
let selected = pick(updated, ["retries", "timeout"])

print(original)
print(shortened)
print(get(config, "missing"))
print(require(config, "label"))
print(selected)

Lists and Maps are not mutated in place. append, remove_at, put, remove_key, and pick return new collections, leaving original and config unchanged. Lists and Maps support deep equality; Functions, Streams, and system resources do not support value equality.

2.4Result, Stream, and system objects

TypeUsed for
ResultExplicit success values or Errors from one operation
StreamLazy files, lines, processes, responses, and events
ErrorFailures with category, location, and Flow stage
FunctionUser functions and closures
System objectDedicated values such as File, Process, and HttpResponse

System objects are not Maps. Map or pick ordinary fields before JSON encoding. Flow explains Stream laziness and consumption in detail.

2.5Bindings, scope, and immutability

hhy
let service = "api"
let mut retries = 0
retries = retries + 1

let creates a binding that cannot be reassigned; use let mut when reassignment is required. List and Map update functions return new collections rather than mutating originals. Names follow block lexical scope and must be declared before use.

Closures may capture outer values. A closure that captures let mut cannot be sent to a parallel worker; Parallel and Watch covers this concurrency boundary.

2.6Conditions, loops, and functions

hhy
fn classify(score) {
    if score >= 90 { return "excellent" }
    else if score >= 60 { return "pass" }
    else { return "retry" }
}

let mut total = 0
for score in [98, 72, 55] {
    if score < 60 { continue }
    total = total + score
}

let mut attempts = 0
while attempts < 3 {
    attempts = attempts + 1
}

print(classify(98))
print(total)

HHY supports if / else if / else, for item in iterable, while, break, and continue. for iterates Lists, Map entries, Ranges, or Streams; iterating a Stream consumes it. Functions use positional arguments checked at call time and return null without an explicit return.

hhy
fn summarize(items) {
    let mut total = 0

    for item in items {
        if item.enabled {
            total = total + item.score
        }
    }

    return total
}

let users = [
    { name: "Ada", enabled: true, score: 98 },
    { name: "Linus", enabled: false, score: 86 }
]

summarize(users) |> print

A closure is { item -> expression }; a multi-statement closure must name its parameter and use return. A one-argument closure in an unambiguous Flow context may use { it * 2 }. V1.3.10 has no overloading, generics, or default arguments.

Guide · 03

Flow and Streams

Understand pipe injection, lazy streams, and single-consumption semantics.

3.1How Pipe passes values

Pipe is a composition rule for ordinary calls: x |> f means f(x), x |> f(a) means f(x, a), and x |> obj.f(a) means obj.f(x, a). It does not turn scalars into Streams, flatten nested Streams, access it fields, ignore errors, stringify values, or invoke a shell.

hhy
[1, 2, 3, 4, 5]
    |> stream
    |> map { number -> number * 2 }
    |> where { number -> number > 5 }
    |> take(2)
    |> print

3.2Stream lifecycle

A Stream is a lazy, pull-based, single-consumption sequence. Building a pipeline only composes operators; upstream produces items when a terminal starts pulling. Its lifecycle is open → next* → close, and normal completion, early take, errors, and cancellation all close resources upstream.

StageWhat happens
CreateA Source returns a Stream without reading data
ComposeOperators such as map and where form a Pipeline
ConsumeA terminal such as print, collect, or save starts pulling
CloseCompletion, early stop, Error, or cancellation releases upstream resources
A Stream is consumed once. Do not save one Stream and feed two Pipelines; recreate the Source, or explicitly collect finite input.

3.3Item, filter, and observation operators

hhy
[5, 2, 5, 1, 3]
    |> stream
    |> skip(1)
    |> take(4)
    |> inspect { number -> print("seen {number}") }
    |> where { number -> number >= 3 }
    |> map { number -> number * 10 }
    |> distinct
    |> collect
    |> print

map

map(Stream<T>, Function(T -> U)) -> Stream<U>

Lazily transform each item one-to-one without automatic flattening.

where

where(Stream<T>, Function(T -> Bool)) -> Stream<T>

Lazily retain items whose predicate returns Bool true.

take

take(Stream<T>, Int) -> Stream<T>

Lazily retain the first n items and close upstream early.

skip

skip(Stream<T>, Int) -> Stream<T>

Lazily discard the first n items and pass the remainder.

inspect

inspect(Stream<T>, Function(T -> Value)) -> Stream<T>

Run an observation closure for each item and pass the item unchanged.

distinct

distinct(Stream<Hashable>) -> Stream<Hashable>

Lazily remove duplicate hashable scalars while retaining a seen set.

3.4map versus flat_map

map sends exactly the closure result downstream. Returning a Stream therefore creates Stream<Stream<T>>. flat_map requires a Stream result and concatenates each child stream into one Stream.

hhy
let batches = [[1, 2], [3, 4]]

batches
    |> stream
    |> flat_map { batch -> batch |> stream }
    |> print

3.5What barriers and terminals actually do

Item operators retain only the current item. A barrier must inspect or retain substantial input before producing a correct result: sort_by stores all input before sorting, group_by stores every group's values, collect builds a List, and terminals such as reduce/count/sum read to completion before returning a scalar. All obey memory, collection-size, and runtime limits.

hhy
let ordered = [5, 1, 3, 2, 4]
    |> stream
    |> sort_by({ order: "asc" }) { number -> number }
    |> collect

let grouped = [
    { team: "core", name: "Ada" },
    { team: "web", name: "Linus" },
    { team: "core", name: "Grace" }
]
    |> stream
    |> group_by { person -> person.team }
    |> collect

print(ordered)
print(grouped)

sort_by

sort_by(Stream<T>, Map, Function(T -> Comparable)) -> Stream<T>

Materialize finite input and stably sort by closure key and asc/desc option.

group_by

group_by(Stream<T>, Function(T -> Hashable)) -> Stream<Group<T>>

Materialize finite input into Groups containing key and values.

collect

collect(Stream<T>) -> List<T>

Consume a finite Stream and materialize it as a List.

reduce

reduce(Stream<T>, U, Function(State<T,U> -> U)) -> U

Fold a Stream from initial; the closure receives state with acc/item/index.

count

count(Stream<T>) -> Int

Consume a Stream and return its item count.

sum

sum(Stream<Number>) -> Number

Consume and sum a numeric Stream, respecting Int overflow rules.

min

min(Stream<Number>) -> Number | Null

Consume a numeric Stream and return its minimum or null for empty input.

max

max(Stream<Number>) -> Number | Null

Consume a numeric Stream and return its maximum or null for empty input.

first

first(Stream<T>) -> T | Null

Return the first item or null and close upstream early.

last

last(Stream<T>) -> T | Null

Consume a Stream and return its last item or null.

any

any(Stream<T>, Function(T -> Bool)) -> Bool

Return true on the first matching item and short-circuit upstream.

all

all(Stream<T>, Function(T -> Bool)) -> Bool

Return true only if every item matches; short-circuit on the first false.

Do not feed watch, every, or otherwise unbounded input directly into sort_by, group_by, or collect. Apply take, a time window, or another business bound first, or the Runtime raises PlanError.

3.6Effects, errors, and parallelism

Only a terminal or Action consumes a Pipeline. print, for_each, save_*, run, and send perform output, filesystem, process, or network work. Ordinary Errors terminate the Pipeline; use attempt for per-item Results and on_error to replace a failed upstream Stream.

parallel(n) uses isolated workers and preserves output order. Parallel and Watch covers concurrency limits, Sendable values, and cancellation.

Guide · 04

Files and Paths

Walk directories, read text, and write results safely.

4.1Path is not String

Every filesystem API requires Path. path(text) normalizes lexically: it collapses repeated separators and ., and resolves removable .. segments without accessing the filesystem or resolving symlinks. A relative Path is always based on the process startup directory, not the importing file.

hhy
let source = path("./src/../src/main.c")
let target = path_join(source.parent, "runtime.c")

print(source)
print(source.name)
print(source.extension)
print(source.parent)
print(target)

name, extension, and parent are read-only Path fields—not path_name(), path_extension(), or path_parent() functions. extension includes the leading dot and is empty when absent; path_join(base, child) returns a new combined Path.

4.2files: traversal, globs, and metadata

hhy
path("./logs")
    |> files("**/*.log")
    |> where { file -> file.size > 1mib }
    |> flat_map { file -> read_lines(file.path) }
    |> where { line -> contains(line, "ERROR") }
    |> save_lines(path("errors.txt"))

files(root, pattern, options?) returns a lazy Stream<File | Directory> and excludes the traversal root itself. Patterns support *, ?, and **. Directory symlinks are not followed by default; { follow_symlinks: true } opts in with cycle detection.

FieldMeaning
pathFull Path
nameFile or directory name
extensionExtension including its leading dot
sizeSize as Bytes
createdCreation time, or null when unavailable
modifiedModification time
is_file / is_dir / is_symlinkObject-kind flags

File and Directory are system objects, not Maps. Map required fields into an ordinary Map before JSON encoding.

4.3Reading text and binary data

read_text

read_text(Path) -> String

Read an entire UTF-8 file as String.

read_lines

read_lines(Path) -> Stream<String>

Lazily read UTF-8 lines with terminators removed.

read_bytes

read_bytes(Path) -> BytesBuffer

Read an entire binary file as BytesBuffer.

Text APIs validate UTF-8. Use read_bytes and write_bytes for images, archives, and arbitrary binary data rather than storing it in String.

4.4Writing, appending, and atomic saves

hhy
let input = path("notes.txt")
let backup = path("backup/notes.txt")

write_text(input, "first line
", { overwrite: true })
append_text(input, "second line
")
copy(input, backup, { overwrite: false, create_parents: true })

read_lines(backup)
    |> map { line -> upper(line) }
    |> save_lines(path("backup/upper.txt"), { create_parents: true })

write_text

write_text(Path, String, Map?) -> Path

Atomically replace with String, supporting overwrite/create_parents.

append_text

append_text(Path, String) -> Path

Append String to the end of a file.

write_bytes

write_bytes(Path, BytesBuffer, Map?) -> Path

Atomically replace a file with BytesBuffer.

save_text

save_text(String | Stream<String>, Path, Map?) -> Path

Atomically save a String or pull a text Stream directly to disk.

save_lines

save_lines(Stream<String>, Path, Map?) -> Path

Write a String Stream with LF per item and atomically replace the target.

write_text, write_bytes, save_text, and save_lines accept overwrite (default true) and create_parents (default false). They commit through a same-directory temporary file plus rename; overwrite: false uses atomic no-replace to prevent check-then-write races.

4.5Copy, move, remove, and dry-run

copy

copy(Path, Path, Map?) -> Path

Copy a file with atomic no-replace and parent creation options.

move

move(Path, Path, Map?) -> Path

Move or rename a file while respecting overwrite options.

remove

remove(Path) -> Path

Remove an explicit Path and return it.

sh
hhy run --dry-run backup.hhy
hhy run backup.hhy

Review the dry-run plan before executing a script that copies, moves, or removes files.

Filesystem reads, traversal, writes, processes, and networking may stop because of RuntimeLimits, cancellation, or host permissions. The Runtime explicitly cleans resources on completion, errors, return, exit, and cancel; it does not rely on GC finalizers.

4.6Look up the complete API

Paths and files API Reference →
Look up every signature, parameter form, and stable function anchor.
/en/learn/standard-library#fn-path

Guide · 05

Text, JSON, and CSV

Process UTF-8 text, regular expressions, and structured data.

5.1String and UTF-8

String is an immutable UTF-8 byte sequence. length counts Unicode code points, byte_length counts encoded bytes, and indexing returns a one-code-point String. Text functions return new values and never mutate the original.

hhy
let line = "  ERROR: timeout  "

line
    |> trim
    |> replace("ERROR", "WARN")
    |> lower
    |> print

trim

trim(String) -> String

Remove whitespace from both ends of a String.

split

split(String, String) -> List<String>

Split a String by delimiter text into List<String>.

join

join(List<String>, String) -> String

Join List<String> with delimiter text.

replace

replace(String, String, String) -> String

Return a new String with matching text replaced.

contains

contains(String | List, Value) -> Bool

Test whether a String contains a substring or a List contains an equal value.

starts_with

starts_with(String, String) -> Bool

Test whether a String starts with the given text.

ends_with

ends_with(String, String) -> Bool

Test whether a String ends with the given text.

lower

lower(String) -> String

Return a new String converted to Unicode lowercase.

upper

upper(String) -> String

Return a new String converted to Unicode uppercase.

5.2Regex

A Regex literal is /pattern/flags with i (case-insensitive), m (multiline), s (dot matches newline), and u. regex_match returns Bool; regex_captures returns the full match, byte positions, numbered groups, and named captures, or null when unmatched.

V1.3.10 uses PCRE2 8-bit with pattern, subject, match, depth, heap, and capture limits. Exceeding them raises ResourceLimitError.

5.3JSON type mapping and errors

hhy
read_text(path("users.json"))
    |> parse_json
    |> get("users")
    |> stream
    |> where { user -> user.active == true }
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path("active-users.json"))
JSONHHY
objectMap
arrayList
stringString
integerInt
decimalFloat
true / falseBool
nullNull

parse_json reports line and column on failure. encode_json accepts { pretty: true } for readable output. Function, Stream, and system objects require mapping to ordinary fields first.

5.4CSV is a record stream

parse_csv accepts a complete String or Stream<String> and returns Stream<Map>; encode_csv accepts Stream<Map> and returns Stream<String> records without terminators. Neither requires a whole-file buffer.

hhy
read_lines(path("employees.csv"))
    |> parse_csv({ header: true })
    |> where { row -> row.active == "true" }
    |> encode_csv({ header: true })
    |> save_lines(path("active-employees.csv"))

header controls field names; delimiter and quote must be single characters. CSV performs no schema inference, so numbers and Bools require explicit conversion. encode_csv omits terminators and composes with save_lines.

5.5Look up the complete API

Text and structured data API Reference →
Look up complete signatures for text, Regex, JSON, and CSV functions.
/en/learn/standard-library#fn-contains

Guide · 06

Processes and System

Run commands, consume output, and inspect system state.

6.1The safety boundary between run and shell

hhy
run(["git", "log", "--oneline"], { timeout: 5s })
    |> stdout_lines
    |> take(10)
    |> print

run(argv, options?) passes List<String> directly to the OS without a shell, so spaces, globs, $, redirects, and pipes are not reinterpreted. Use shell(command, options?) only when shell syntax is intentional; the Checker emits a safety hint.

OptionPurpose
cwdChild working-directory Path
envOverrides only the child environment
stdinText supplied on standard input
timeoutMaximum command duration
max_outputstdout and stderr capture limit
Prefer run when values include user input. shell interprets redirects, pipes, and expansion and should be reserved for intentional shell syntax.

6.2CommandResult

run and shell wait by default and return CommandResult. A nonzero child exit code is not automatically an HHY Error; inspect exit_code for business success.

FieldContents
exit_codeChild exit status
stdoutStandard-output String
stderrStandard-error String
durationCommand Duration

A nonzero exit_code does not automatically become an HHY Error; interpret it according to the command. stdout_lines(result) exposes captured output as a line Stream.

6.3Process snapshots and fields

processes() returns a current Stream<Process> snapshot. Process is not a Map and exposes read-only pid, name, cpu, memory, status, and command fields. Explicitly map ordinary fields before JSON encoding.

sort_by({ order: "desc" }) accepts only asc or desc (default asc). It is a stable barrier that materializes the finite snapshot.

6.4args, env, system, and stdin

ValueContents
argsList<String> excluding the script path
envRead-only environment view
systemOS, architecture, host, CPU, memory, and directories
stdin_lines()Standard-input line Stream
hhy
if length(args) != 1 {
    print_error("usage: script.hhy <input>")
    exit(3)
}

let input = path(args[0])

6.5Look up the complete API

Processes and system API Reference →
Look up complete signatures for run, shell, processes, stdin_lines, and every.
/en/learn/standard-library#fn-run

Guide · 07

HTTP

Build requests, configure timeout and retry, and process responses.

7.1Request → Policy → Send → Response

hhy
http.get("https://example.com/users")
    |> timeout(5s)
    |> retry({ count: 3, backoff: 200ms })
    |> send
    |> response_body
    |> parse_json
    |> print

http.get/post/put/delete(url, options?) only build immutable HttpRequests without network access. timeout(request, duration) and retry(request, options) return new policy-adjusted Requests; send(request) performs the network effect and returns HttpResponse. Dry-run can therefore inspect the full plan without execution.

7.2Request options and safe defaults

OptionPurpose
queryURL query parameters
headersRequest headers
bodyRequest content
proxyProxy address
follow_redirectsRedirect policy
allow_private_networksWhen false, reject private, loopback, and link-local resolved connection addresses

TLS verification is enabled by default. Sensitive Authorization and Cookie headers are redacted in plans, logs, and Errors. Response bodies obey max_http_body.

7.3Timeout, retries, and idempotency

retry({ count, backoff }) defaults to connection errors, timeouts, 429, and selected 5xx statuses. GET, PUT, and DELETE may retry by policy; POST does not retry automatically to avoid duplicate creation or charges. Timeout and Ctrl+C cancel libcurl work and release response resources.

Retries do not erase failure. Give every request a timeout, evaluate POST idempotency, and preserve method, redacted URL, attempts, and Flow stage in the final Error.

7.4HttpResponse and response bodies

send returns an in-memory HttpResponse. Use response_body for UTF-8 text and response_bytes for binary data; send_to(request, path) writes directly from curl into a sibling temporary file and atomically publishes it, returning only path and size.

hhy
http.get("https://api.example.com/status")
    |> timeout(3s)
    |> send
    |> response_body
    |> parse_json
    |> print

7.5Look up the complete API

HTTP API Reference →
Look up request builders, timeout, retry, send, and response readers.
/en/learn/standard-library#fn-http-get

Guide · 08

Parallel and Watch

Bounded parallel work, cancellation, and filesystem event streams.

8.1parallel is a bounded concurrent map

hhy
let urls = [
    "https://example.com",
    "https://example.org"
]

urls
    |> parallel(2) { url ->
    http.get(url)
        |> timeout(5s)
        |> send
}
    |> print
BehaviorGuarantee
ConcurrencyAt most n workers, subject to RuntimeLimits
Output orderMatches input order
BackpressureInput and result buffers are bounded
ErrorsThe first unhandled Error cancels remaining work
ReturnConcurrent map; child Streams are not flattened

If a closure returns Stream, apply flat_map after parallel. Dry-run creates no workers but still checks closure Effects in order.

8.2Sendable values and isolation

Workers receive frozen snapshots of input and captured values, never shared mutable objects. Null, Bool, numbers, String, units, Path, and ordinary Lists/Maps/system snapshots whose fields are sendable can be copied.

Capturing a let mut Cell, Stream, open File handle, request body stream, or other process-local resource raises CheckError. V1.3.10 exposes no threads, locks, or async/await.

8.3watch and FileEvent

hhy
watch(path("./src"))
    |> where { event -> event.kind == "write" }
    |> debounce(300ms)
    |> for_each { event ->
    print(event.path)
}

watch(path, { recursive? }) returns an infinite Stream<FileEvent>. FileEvent exposes read-only kind, path, old_path, and timestamp; kind is created, modified, removed, or renamed, and old_path exists only for renamed.

FieldContents
kindcreated, modified, removed, or renamed
pathTarget Path
old_pathOriginal Path for renamed; otherwise null
timestampEvent DateTime

watch is an infinite Stream. End it with Ctrl+C, timeout, or cancel. Recursive watch obeys max_open_files, and filesystems may merge rapid duplicate events.

8.4debounce and every

debounce(window) is leading-edge: the first scalar or kind + path FileEvent emits immediately; duplicates inside the window are coalesced and reset the window. Different event keys do not block each other.

every(duration) returns an infinite tick Stream. If downstream is busy, backpressure prevents overlapping the same tick. Apply take or a business window before collect/sort/group.

8.5Cancellation and cleanup

Ctrl+C, timeout, cancel(), and unhandled errors trigger one root CancellationToken. Watchers, sleep, HTTP, child processes, and workers poll it; cancellation closes queues and handles, terminates children, and propagates Stream close upstream.

Guide · 09

Modules and Errors

Organize code, propagate structured errors, and unwind resources reliably.

9.1Import forms and path resolution

hhy
import { add } from "./math.hhy"

add(20, 22) |> print
FormUse
import "./lib/report.hhy" as reportImport a local module namespace
import { parse } from "./lib/data.hhy"Named import
import { validate as check } from "./lib/data.hhy"Named import with alias
import httpImport a standard-library module

Relative paths resolve from the current source file. Standard modules use bare names; local files explicitly use ./, ../, or an absolute Path.

9.2Exports, scope, and execution

hhy
export let version = "1.0"

export fn normalize_name(name) {
    return name |> trim |> lower
}

fn internal_helper() {
    return null
}

Only exported names are visible to importers. A module owns its top-level scope, executes once on first import, and is then cached. Import cycles raise CheckError before execution. V1.3.10 supports standard and local modules plus process-extension modules installed from local packages.

9.3Error fields and categories

Every failure uses Error rather than null or printed text. Error exposes kind, code, message, source, stage, cause, stack, and context. Sensitive headers, credentials, and full file contents are excluded from context by default.

Built-in categories include SyntaxError, CheckError, TypeError, ValueError, IndexError, KeyError, EncodingError, IoError, ProcessError, HttpError, HttpStatusError, TimeoutError, CancelledError, ResourceLimitError, and PlanError.

9.4try/catch and rethrowing

hhy
try {
    read_text(path("config.json"))
        |> parse_json
        |> print
} catch err {
    print_error(err)
    exit(1)
}

catch receives the first Error propagated from try. Execution continues after a normally completed catch; use throw(err) to preserve the error chain when it cannot be handled. An unhandled Error exits nonzero.

9.5Flow errors and per-item Result

hhy
path("./configs")
    |> files("**/*.json")
    |> map { file -> attempt { read_text(file.path) } }
    |> where { result -> result.ok }
    |> map { result -> result.value }
    |> print

An unhandled Error in a Stream terminates the Pipeline. attempt converts one operation to Result for batches that explicitly retain successes and failures. on_error replaces an entirely failed upstream Stream and never silently skips an item.

9.6Resource cleanup guarantees

Error, return, exit, timeout, Ctrl+C, and cancel share one unwind path. Stream close is idempotent; failed atomic saves delete temporary files and preserve the old file; child processes, HTTP responses, watchers, and workers respond to cancellation.

Guide · 10

Practical Automation Recipes

Every example from examples/00–08, plus release gates, security audits, reconciliation, tenant snapshots, and asset governance.

10.100 · Hello HHY and Flow

The smallest runnable example: turn a List into a Stream, map and filter values, then print the result. Corresponds to examples/00-hello.hhy.

00-hello.hhy
let language = "HHY"

["Flow", "Pipe", "System"]
    |> map { word -> "{language}: {word}" }
    |> print
$ hhy run examples/00-hello.hhy
HHY: Flow
HHY: Pipe
HHY: System

✓ exit 0 · Flow pipeline completed

10.2Extract log alerts concurrently

Recursively scan large log files with four workers, extract ERROR/WARN lines, and retain each source path. Useful for incident response and scheduled log jobs.

log-errors.hhy
if length(args) != 2 {
    print_error("usage: hhy run log-errors.hhy <log-dir> <output-file>")
    exit(3)
}

let log_dir = path(args[0])
let output_file = path(args[1])

log_dir
    |> files("**/*.log")
    |> where { file -> file.size > 1mib }
    |> parallel(4) { file ->
    read_lines(file.path)
        |> where { line -> regex_match(line, /ERROR|WARN/) }
        |> map { line -> "{file.path}: {line}" }
        |> collect
}
    |> flat_map { lines -> lines |> stream }
    |> save_lines(output_file)
    |> on_error { err ->
    print_error(err)
    throw(err)
}
sh
hhy run log-errors.hhy ./logs ./output/errors.txt
$ hhy run log-errors.hhy ./logs ./output/errors.txt && head -3 ./output/errors.txt
logs/api.log: 2026-08-25T09:18:42Z ERROR database timeout after 3000ms
logs/worker.log: 2026-08-25T09:18:44Z WARN retrying job #1842
logs/api.log: 2026-08-25T09:18:47Z ERROR upstream returned 502

✓ exit 0 · 3 alerts written to output/errors.txt

10.3Sync active users from an API

Fetch users with timeout and retry, parse JSON, select fields, and atomically save only active users. Corresponds to examples/02-active-users.hhy.

active-users.hhy
if length(args) != 2 {
    print_error("usage: hhy run active-users.hhy <url> <output-file>")
    exit(3)
}

http.get(args[0])
    |> timeout(5s)
    |> retry({ count: 3, backoff: 200ms })
    |> send
    |> response_body
    |> parse_json
    |> get("users")
    |> stream
    |> where { user -> user.active == true }
    |> map { user ->
    { id: user.id, name: user.name, email: user.email }
}
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path(args[1]))
    |> on_error { err ->
    print_error(err)
    throw(err)
}
sh
hhy run active-users.hhy https://api.example.com/users active-users.json
$ hhy run active-users.hhy http://127.0.0.1:9000/users active-users.json && cat active-users.json
[
  { "id": 101, "name": "Ada", "email": "ada@example.com" },
  { "id": 108, "name": "Linus", "email": "linus@example.com" }
]

✓ exit 0 · 2 active users written to active-users.json

10.4Monitor process CPU and memory

Sample processes every five seconds, keep CPU-heavy or memory-heavy entries, and print the top ten by memory. Corresponds to examples/03-process-monitor.hhy.

03-process-monitor.hhy
every(5s)
    |> for_each { tick ->
    processes
        |> where { process ->
        process.cpu > 70% or process.memory > 1gib
    }
        |> sort_by({ order: "desc" }) { process -> process.memory }
        |> take(10)
        |> map { process ->
        {
            pid: process.pid,
            name: process.name,
            cpu: process.cpu,
            memory: process.memory
        }
    }
        |> print
}
$ hhy run examples/03-process-monitor.hhy
[{ pid: 8421, name: "node", cpu: 82.4%, memory: 1.42 GiB },
 { pid: 9107, name: "hhy",  cpu: 74.1%, memory: 86.3 MiB }]

next sample in 5s… · Ctrl+C exits safely

10.5Check service health in batches

Probe multiple services concurrently with consistent timeouts and retries. A failed endpoint is recorded without terminating the whole batch.

health-check.hhy
let services = [
    { name: "users", url: "https://api.example.com/users/health" },
    { name: "orders", url: "https://api.example.com/orders/health" },
    { name: "billing", url: "https://api.example.com/billing/health" }
]

services
    |> stream
    |> parallel(3) { service ->
    let response = attempt {
        http.get(service.url)
            |> timeout(3s)
            |> retry({ count: 2, backoff: 100ms })
            |> send
            |> response_body
            |> parse_json
    }

    let mut status = "unreachable"
    let mut error_message = null

    if response.ok {
        status = response.value.status
    } else {
        error_message = response.error.message
    }

    return {
        name: service.name,
        ok: response.ok,
        status: status,
        error: error_message
    }
}
    |> collect
    |> encode_json({ pretty: true })
    |> print
$ hhy run health-check.hhy
[
  { "name": "users",   "ok": true,  "status": "healthy", "error": null },
  { "name": "orders",  "ok": true,  "status": "healthy", "error": null },
  { "name": "billing", "ok": false, "status": "unreachable", "error": "request timed out" }
]

✓ exit 0 · all 3 checks completed despite one endpoint failure

10.6Business 01 · Release quality gate

Run tests, lint, and production builds in parallel, save a machine-readable report, and block a release with a stable exit code when any check fails.

release-gate.hhy
let checks = [
    { name: "unit-tests", command: ["make", "test"] },
    { name: "lint", command: ["npm", "run", "lint"] },
    { name: "production-build", command: ["npm", "run", "build"] }
]

let report = checks
    |> stream
    |> parallel(3) { check ->
    let result = run(check.command, { timeout: 10min, max_output: 8mib })
    return {
        name: check.name,
        passed: result.exit_code == 0,
        exit_code: result.exit_code,
        output: result.stdout
    }
}
    |> collect

report |> encode_json({ pretty: true }) |> save_text(path("release-gate.json"))

if report |> any { check -> check.passed == false } {
    print_error("release blocked: one or more checks failed")
    exit(1)
}

print("release gate passed")
$ hhy run release-gate.hhy
unit-tests       PASS  4.28s
lint             PASS  1.14s
production-build PASS  6.72s
release-gate.json written

✓ exit 0 · release gate passed

10.7Business 02 · Source secret audit

Scan configuration and source files concurrently for suspected API keys, passwords, and private keys, then produce a security review report.

secret-audit.hhy
if length(args) != 2 {
    print_error("usage: hhy run secret-audit.hhy <source-dir> <report-file>")
    exit(3)
}

path(args[0])
    |> files("**/*")
    |> where { file ->
    file.extension == ".env" or
    file.extension == ".yml" or
    file.extension == ".json" or
    file.extension == ".ts"
}
    |> parallel(4) { file ->
    read_lines(file.path)
        |> where { line ->
        regex_match(line, /API_KEY|SECRET|PASSWORD|BEGIN PRIVATE KEY/)
    }
        |> map { line -> "{file.path}: {line}" }
        |> collect
}
    |> flat_map { matches -> matches |> stream }
    |> save_lines(path(args[1]))
$ hhy run secret-audit.hhy ./services secret-findings.txt
services/billing/.env: PAYMENT_API_KEY=***
services/auth/config.yml: PASSWORD: ***

✓ exit 0 · 2 suspected secrets require review
The example output is redacted. Restrict report access and avoid printing raw secrets in production pipelines.

10.8Business 03 · Order and payment reconciliation

Merge order and payment CSV files into one flow, group records by order_id, and report missing records or mismatched amounts.

reconcile.hhy
if length(args) != 3 {
    print_error("usage: hhy run reconcile.hhy <orders.csv> <payments.csv> <report.json>")
    exit(3)
}

[path(args[0]), path(args[1])]
    |> stream
    |> flat_map { input ->
    read_lines(input) |> parse_csv({ header: true })
}
    |> group_by { record -> record.order_id }
    |> where { group ->
    (group.values |> count) != 2 or
    (group.values |> map { record -> record.amount } |> distinct |> count) != 1
}
    |> map { group ->
    { order_id: group.key, records: group.values, issue: "missing_or_amount_mismatch" }
}
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path(args[2]))
$ hhy run reconcile.hhy orders.csv payments.csv exceptions.json
orders: 12,480 · payments: 12,472
matched: 12,461
exceptions: 19 → exceptions.json

✓ exit 0 · reconciliation report written atomically

10.9Business 04 · Multi-tenant usage snapshot

Fetch tenant usage with bounded concurrency, consistent retries, and failure isolation. Useful for billing, capacity analysis, and customer success reports.

tenant-snapshot.hhy
let tenants = [
    { id: "acme", url: "https://api.example.com/acme/usage" },
    { id: "nova", url: "https://api.example.com/nova/usage" },
    { id: "orbit", url: "https://api.example.com/orbit/usage" }
]

tenants
    |> stream
    |> parallel(3) { tenant ->
    let result = attempt {
        http.get(tenant.url)
            |> timeout(5s)
            |> retry({ count: 3, backoff: 200ms })
            |> send
            |> response_body
            |> parse_json
    }

    if result.ok {
        return { tenant: tenant.id, ok: true, usage: result.value, error: null }
    }

    return { tenant: tenant.id, ok: false, usage: null, error: result.error.message }
}
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path("tenant-usage-snapshot.json"))
$ hhy run tenant-snapshot.hhy
acme  ✓ requests=184203 storage_gb=82.4
nova  ✓ requests=99102  storage_gb=41.8
orbit ✗ request timed out

✓ exit 0 · snapshot contains both data and failure reasons

10.10Business 05 · Oversized asset governance

Find large image and video assets, sort them by size, and produce a JSON inventory for compression or storage migration work.

asset-audit.hhy
if length(args) != 2 {
    print_error("usage: hhy run asset-audit.hhy <asset-dir> <report.json>")
    exit(3)
}

path(args[0])
    |> files("**/*")
    |> where { file ->
    file.is_file and
    (file.extension == ".png" or file.extension == ".jpg" or file.extension == ".mp4")
}
    |> where { file -> file.size > 5mib }
    |> sort_by({ order: "desc" }) { file -> file.size }
    |> map { file ->
    { path: file.path, bytes: file.size, extension: file.extension }
}
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path(args[1]))
$ hhy run asset-audit.hhy ./public asset-report.json
scanned 1,842 assets
large assets: 27
largest: public/video/launch.mp4 · 184.2 MiB

✓ exit 0 · asset-report.json generated

10.11Watch sources and rebuild

Watch C sources, debounce rapid saves, and run make. Build failures are reported while the watcher stays alive.

watch-build.hhy
if length(args) != 1 {
    print_error("usage: hhy run watch-build.hhy <source-dir>")
    exit(3)
}

let source_dir = path(args[0])

watch(source_dir, { recursive: true })
    |> where { event ->
    event.kind != "removed" and
    (event.path.extension == ".c" or event.path.extension == ".h")
}
    |> debounce(300ms)
    |> for_each { event ->
    print("changed: {event.path}")

    let result = run(["make"], { timeout: 2min, cwd: system.cwd })

    if result.exit_code != 0 {
        print_error(result.stderr)
    } else {
        print(result.stdout)
    }
}
sh
hhy run watch-build.hhy ./src
$ hhy run watch-build.hhy ./src
watching ./src recursively…
changed: src/runtime/flow.c
cc -std=c11 -O2 -c src/runtime/flow.c
cc build/*.o -lcurl -lpcre2-8 -lgc -o build/hhy
Build complete: build/hhy

✓ watcher remains active · waiting for the next change

10.12Build a department report from CSV

Read employee CSV records, keep active employees, aggregate headcount and salary by department, and atomically save formatted JSON.

csv-report.hhy
if length(args) != 2 {
    print_error("usage: hhy run csv-report.hhy <input.csv> <output.json>")
    exit(3)
}

read_lines(path(args[0]))
    |> parse_csv({ header: true })
    |> where { employee -> employee.active == "true" }
    |> group_by { employee -> employee.department }
    |> map { group ->
    {
        department: group.key,
        employees: group.values |> count,
        total_salary: group.values
            |> map { employee -> employee.salary |> to_float }
            |> sum
    }
}
    |> collect
    |> encode_json({ pretty: true })
    |> save_text(path(args[1]))
sh
hhy run csv-report.hhy employees.csv department-report.json
$ hhy run csv-report.hhy employees.csv department-report.json && cat department-report.json
[
  { "department": "Engineering", "employees": 12, "total_salary": 2160000 },
  { "department": "Product", "employees": 5, "total_salary": 810000 }
]

✓ exit 0 · department-report.json written atomically

10.13Back up large files with dry-run

Find files over 100 MiB and copy them into a backup directory. Inspect the plan with dry-run before performing real writes.

backup-large.hhy
if length(args) != 2 {
    print_error("usage: hhy run backup-large.hhy <source-dir> <backup-dir>")
    exit(3)
}

let source_dir = path(args[0])
let backup_dir = path(args[1])

source_dir
    |> files("**/*")
    |> where { file -> file.is_file and file.size > 100mib }
    |> for_each { file ->
    let target = path_join(backup_dir, file.name)
    print("copy {file.path} -> {target}")
    copy(file.path, target, { overwrite: false, create_parents: true })
}
    |> on_error { err ->
    print_error(err)
    throw(err)
}
sh
hhy run --dry-run backup-large.hhy ./downloads ./backup
hhy run backup-large.hhy ./downloads ./backup
$ hhy run --dry-run backup-large.hhy ./downloads ./backup
copy downloads/archive.tar -> backup/archive.tar
copy downloads/database.dump -> backup/database.dump
[dry-run] copy downloads/archive.tar → backup/archive.tar
[dry-run] copy downloads/database.dump → backup/database.dump

✓ exit 0 · plan generated without writing files
The recipe refuses to overwrite files and creates parent directories, but you should still inspect the dry-run plan first.

10.1407 · Language basics in one task

A small aggregation task combining variables, Lists, Maps, functions, conditions, loops, scopes, and error handling. Corresponds to examples/07-language-basics.hhy.

07-language-basics.hhy
fn summarize(items) {
    let mut total = 0

    for item in items {
        if item.enabled {
            total = total + item.score
        }
    }

    return total
}

let users = [
    { name: "Ada", enabled: true, score: 98 },
    { name: "Linus", enabled: false, score: 86 }
]

summarize(users) |> print
$ hhy run examples/07-language-basics.hhy
{
  "count": 2,
  "total": 40,
  "average": 20
}

✓ exit 0 · summary generated

Projects · 11

Project: FlowGuard

Build a complete repository health and quality-gate application with HHY v1.3.10, including real fixtures, concurrent checks, JSON reports, and end-to-end tests.

11.1More than a syntax demo

FlowGuard is a complete application run and self-tested with HHY v1.3.10. It accepts a project directory and JSON configuration, checks required files, scans files and possible credentials, runs quality commands and HTTP health checks concurrently, atomically writes a structured report, and uses a stable exit code to enforce the quality gate.

Application capabilityHHY capabilities used
Project structurePath, read_text, attempt, and List
File and security scanfiles, Stream, Regex, and Bytes
Quality commandsrun, parallel, Duration, and CommandResult
Service healthhttp.get, timeout, retry, and parallel
Report and gateMap, encode_json, atomic save_text, and exit

View the complete FlowGuard source on GitHub ↗
Includes the HHY entry point, six business modules, configurations, fixtures, HTTP server, and report assertions.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/flowguard

11.2Project layout

The entry script focuses on orchestration while lib contains each check. Config holds two scenarios, and fixtures provides repeatable project data. output and __pycache__ are ignored and never committed.

FlowGuard project tree showing config, fixtures, lib, entry script, and test utilities
The real FlowGuard layout. output and __pycache__ are local test artifacts and are not tracked by Git.
PathResponsibility
flowguard.hhyRead arguments and configuration, combine checks, write the report, and set the exit code
lib/*.hhyStructure, file, security, command, health, and reporting modules
config/*.jsonHealthy and risky scenario configurations
fixtures/*Deterministic projects under inspection
self-test.shStart the test service and verify both end-to-end scenarios

11.3Run the complete self-test

Run one command from the repository root. The test starts a temporary HTTP service bound only to 127.0.0.1:18991, checks the HHY modules, runs both scenarios, and uses Python assertions to validate the generated JSON reports.

sh
cd hhy-vm
sh practical-projects/flowguard/self-test.sh
FlowGuard end-to-end terminal output with the healthy scenario passing and five expected failures in the risky scenario
Actual output: healthy-service passes all eight checks; risky-service finds five failures; the run ends with FlowGuard self-test passed.

11.4Healthy and risky scenarios

ScenarioInput dataExpected result
healthy-serviceREADME, LICENSE, package.json, source, two successful commands, and a 2xx health endpoint8 passed and exit code 0
risky-serviceMissing LICENSE, fake DEMO_TOKEN, failed command, and a 404 endpoint5 failed and exit code 1; the harness treats this nonzero status as correct
The credential in the risky fixture is explicitly fake. FlowGuard stores only the file name and content_redacted: true; matching content never enters the report.

11.5Configure your own project

text
{
  "project": { "name": "my-service" },
  "required_files": ["README.md", "LICENSE"],
  "limits": { "large_file": "4kib" },
  "commands": [
    { "name": "tests", "argv": ["npm", "test"] }
  ],
  "health_checks": [
    { "name": "api", "url": "http://127.0.0.1:8080/health" }
  ]
}

Commands are passed directly to run as argv arrays and are never assembled through shell. Each command is limited to 15 seconds and 1 MiB of output. The current example accepts 256b, 1kib, 4kib, or 1mib file thresholds.

sh
hhy run \
  --limit max_runtime=2min \
  --limit max_memory=256mib \
  --limit max_processes=8 \
  practical-projects/flowguard/flowguard.hhy \
  /path/to/project \
  practical-projects/flowguard/config/my-project.json \
  report.json

11.6Why it represents HHY

FlowGuard brings filesystem, process, HTTP, and data processing into one reliable workflow. attempt turns an individual failure into a structured check without preventing other checks from completing; parallel provides bounded concurrency; and CI/CD can consume the final report directly. This is where HHY is most distinct from a large shell script.

Read the FlowGuard guide ↗
See configuration fields, manual commands, test design, and instructions for checking a real project.
https://github.com/hh696-wq/hhy-vm/blob/main/practical-projects/flowguard/README.md

Projects · 12

Project: DataFlow ETL

Synchronize CSV, JSON-directory, and HTTP API data through cleaning, filtering, concurrent enrichment, grouping, and JSON/CSV outputs.

12.1A complete synchronization pipeline

DataFlow ETL runs entirely on HHY v1.3.10 and is verified end to end. It reads customer CSV and an event JSON directory, normalizes names and email addresses, filters inactive and low-spend customers, calls a profile API concurrently, aggregates departments with group_by, and atomically writes JSON and CSV outputs.

StageImplementation
Ingestread_lines + parse_csv; files + parse_json
Cleantrim, lower, to_int, and structured Maps
Enrichparallel(4) + http.get + timeout + retry
Aggregatewhere, sort_by, group_by, and sum
Outputencode_json/save_text and encode_csv/save_lines

View the complete DataFlow ETL source on GitHub ↗
Includes HHY modules, CSV/JSON fixtures, profile API, report assertions, and one-command self-test.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/dataflow-etl

12.2Real layout and data flow

DataFlow ETL project tree
The real project layout: entry point, four HHY modules, CSV/JSON fixtures, configuration, and test utilities.
text
customers.csv + events/*.json + HTTP profiles
                    ↓
            parse / trim / lower
                    ↓
          active + minimum spend filter
                    ↓
          parallel HTTP enrichment
                    ↓
          group_by department + sum
                    ↓
              report.json + customers.csv

12.3Actual self-test result

sh
cd hhy-vm
sh practical-projects/dataflow-etl/self-test.sh
Actual DataFlow ETL end-to-end result
Actual run: three qualified customers, two event files, two department summaries, and all HTTP enrichment plus JSON/CSV assertions passed.
The test API binds only to 127.0.0.1:18992. Assertions cover filtering and ordering, email cleanup, remote region/tier fields, department spend totals, and both output formats.

12.4Run your own synchronization

Copy config/test.json, replace the project name, API base, and minimum-spend threshold, then provide customers.csv and events/*.json. An individual HTTP failure becomes a structured error, sets report ok=false, and returns exit code 1.

sh
hhy run practical-projects/dataflow-etl/etl.hhy \
  ./input \
  ./config.json \
  ./output/report.json \
  ./output/customers.csv

Projects · 13

Project: Asset Governance

Audit project assets, generate a governance report, and safely execute copy, move, and remove remediations with Runtime-native dry-run.

13.1Separate audit from remediation

Asset Governance consists of audit.hhy and cleanup.hhy. The auditor scans source, configuration, images, video, and build outputs for oversized, stale, badly named, duplicate-text, and possibly sensitive files. The cleaner accepts only allow-listed report actions and never assembles shell commands.

Check or actionHHY implementation
Inventory and sizefiles, File.size, and Bytes
Stale filesFile.modified, now, and Duration
Naming and secretsRegex, read_text, and redacted findings
Duplicate contentgroup_by text content without storing source text in the report
Remediationcopy, move, remove, and --dry-run EffectDispatcher

View the complete Asset Governance source on GitHub ↗
Includes auditor, cleaner, four HHY modules, risky fixtures, and dry-run plus applied-remediation assertions.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/asset-governance

13.2Project layout

Asset Governance project tree
The real layout contains audit and cleanup entry points, governance modules, and intentionally large, stale, duplicate, and sensitive fixtures.
ProgramResponsibility
audit.hhyScan and atomically write report.json; return 1 when a critical finding exists
cleanup.hhyRead report.actions and execute controlled copy/move/remove operations
self-test.shCreate an isolated mktemp workspace, dry-run first, then apply and assert every action

13.3Actual self-test and dry-run

sh
cd hhy-vm
sh practical-projects/asset-governance/self-test.sh
Actual Asset Governance audit, dry-run, and applied-remediation terminal output
Actual run: detects large/naming/stale/sensitive/duplicate findings; dry-run prints the effect plan without changes; all assertions pass after three real actions.
Processed means that program control reached the action; Runtime intercepts its effect during dry-run. The test then proves that the workspace is unchanged, and verifies copy, move, and remove only after the real run.

13.4Two-phase operation

Run the audit and inspect report.json first. audit returns 1 for a critical finding but still writes the complete report. After approving actions, run dry-run, inspect Runtime's effect plan, and only then apply remediation.

sh
hhy run practical-projects/asset-governance/audit.hhy ./project ./config.json ./report.json
hhy run --dry-run practical-projects/asset-governance/cleanup.hhy ./project ./report.json
hhy run practical-projects/asset-governance/cleanup.hhy ./project ./report.json

Projects · 14

Project: Hong Kong Film Companies

Search MediaWiki for Hong Kong film-company pages, fetch details concurrently, filter the results, and export CSV plus JSON.

14.1Real network research with HHY

This project uses HHY v1.3.10 with the official MediaWiki API. It searches for ten pages related to “香港電影公司”, fetches page IDs, canonical URLs, timestamps, and bounded introductions with parallel(3), retains entries that mention Hong Kong, film, and a company, sorts by title, and atomically writes CSV and JSON.

StageHHY implementation
Searchhttp.get + timeout + retry + parse_json
Deduplicategroup_by(pageid) + Map
Detailsparallel(3) + attempt so one page cannot stop peers
FilterStream + where + contains + sort_by
Outputencode_csv/encode_json + atomic save

View the complete source on GitHub ↗
Includes the HHY crawler, configuration, modules, local MediaWiki fixture, CSV/JSON assertions, and bilingual guides.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/hong-kong-film-companies

14.2Project layout

Hong Kong film companies HHY Wikipedia research project tree
The real layout: crawl.hhy orchestrates, api.hhy handles search and concurrent details, and transform.hhy handles grouping, filtering, sorting, and report structures.
FileResponsibility
crawl.hhyRead configuration, compose the flow, and atomically write CSV/JSON
lib/api.hhyMediaWiki search, HTTP policies, and bounded parallel work
lib/transform.hhySemantic filtering, stable sorting, and output fields
self-test.shStart a local fixture and verify the deterministic 5→3 result

14.3Actual Wikipedia result

On 2026-08-26, the actual run fetched all ten candidate details and retained seven company-related results: China Star Entertainment Group, Cathay Organisation, One Cool Film, Mandarin Films, Shaw Brothers, Film Workshop, and the Hong Kong film-company list.

sh
./build/hhy run --limit max_runtime=2min --limit max_parallelism=8 \
  practical-projects/hong-kong-film-companies/crawl.hhy \
  practical-projects/hong-kong-film-companies/config/wikipedia.json \
  practical-projects/hong-kong-film-companies/output/wikipedia-report.json \
  practical-projects/hong-kong-film-companies/output/wikipedia-hong-kong-film-companies.csv
Actual terminal result of HHY concurrently collecting Hong Kong film company data from Wikipedia
Actual network run: ten candidates, ten fetched pages, seven retained companies, with CSV and JSON written successfully.
Wikipedia search results and content change, so this is a reproducible research sample rather than an exhaustive business registry. The self-test uses a local API fixture so external network changes do not become code regressions.

14.4Concurrency, limits, and repeatability

The default parallelism is three and each introduction is capped at 600 characters, bounding worker results. Requests use a ten-second timeout and two retries; attempt isolates each detail task. The Chinese keyword is percent-encoded in search_url to accommodate HHY 1.1.1's current non-ASCII query Map limitation.

sh
sh practical-projects/hong-kong-film-companies/self-test.sh

Read the English guide ↗
See output fields, the real crawl command, configuration limits, and deterministic test design.
https://github.com/hh696-wq/hhy-vm/blob/main/practical-projects/hong-kong-film-companies/README.md

Projects · 15

Project: Multi-API Data Collector

Collect paginated OpenAlex, Crossref, and GitHub data concurrently, normalize it, record failures, and incrementally merge CSV.

15.1One reliable collection flow

Built with HHY v1.3.10, this project collects two pages each from OpenAlex, Crossref, and GitHub. parallel(3) bounds concurrent HTTP downloads before heterogeneous JSON is normalized into one seven-column schema.

CapabilityImplementation
Pagination and concurrencySix jobs with parallel(3)
Network resilience250ms pacing, 10s timeout, two retries, and attempt
Data governanceNormalization, composite-key deduplication, stable sorting
Incremental outputRead existing CSV, replace matching keys, atomic save
Failure auditSource, page, and error in failures.json

View the complete source on GitHub ↗
Includes the HHY entry point, pagination jobs, three adapters, incremental merge, configuration, and deterministic self-test.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/multi-api-data-collector

15.2Project layout

HHY Multi-API Data Collector project tree
collector.hhy orchestrates; jobs.hhy creates pages; sources.hhy downloads and normalizes; merge.hhy deduplicates and incrementally merges.
FileResponsibility
collector.hhyCompose collection, statistics, and atomic output
lib/jobs.hhyCreate OpenAlex, Crossref, and GitHub page jobs
lib/sources.hhyPacing, timeout, retry, concurrent download, normalization
lib/merge.hhyRead old CSV, deduplicate, sort, and incrementally replace

15.3Run and incremental result

sh
./build/hhy run practical-projects/multi-api-data-collector/collector.hhy \
  practical-projects/multi-api-data-collector/config/public-apis.json \
  practical-projects/multi-api-data-collector/output/records.csv \
  practical-projects/multi-api-data-collector/output/report.json \
  practical-projects/multi-api-data-collector/output/failures.json
HHY Multi-API Data Collector terminal verification
Deterministic end-to-end verification: six pages, twelve incoming records, and nine unique records after both the initial and incremental runs.
Public APIs and quotas change. Source URLs remain in the output; local fixtures exist only for regression testing and never enter the production output directory.

15.4Verification

sh
sh practical-projects/multi-api-data-collector/self-test.sh

The test runs twice to verify pagination, concurrency, normalization, cross-page deduplication, stable sorting, the failure list, and incremental replacement.

Projects · 16

Project: SiteGraph Auditor

Recursively inventory a documentation site, build a normalized link graph, and enforce metadata, failure, and security gates.

16.1A quality gate that uses the safe spider end to end

SiteGraph Auditor builds on HHY v1.3.10 and the safe recursive my-crawler engine. Starting from seeds, it discovers pages by depth and writes an inventory, normalized graph, report, and failures. A stable exit status blocks sites with missing metadata or crawl failures.

New capabilityUse in this project
URL normalizationUnify relative links, dot segments, fragments, host case, and default ports
Link discoverymain a[href] continuously feeds the next frontier
FrontierConcurrent depth batches retain page, depth, and source context
Hard boundariesDomain, path, depth, pages, frontier, and links
Fingerprint deduplicationDeduplicate before frontier admission; count duplicate graph edges separately
SSRFProduction configuration rejects private resolved socket addresses across DNS and redirects

View the complete SiteGraph Auditor source ↗
Includes three HHY modules, a four-level healthy site, a risky site, report assertions, and a negative SSRF test.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/sitegraph-auditor

16.2Healthy and risky scenarios

sh
make
./practical-projects/sitegraph-auditor/self-test.sh
$ ./practical-projects/sitegraph-auditor/self-test.sh
SiteGraph Auditor healthy
Pages 4 / 4 Edges 5
Duplicates 3 Rejected 1 Findings 0
SiteGraph Auditor risky
Pages 1 / 2 Edges 2
Duplicates 0 Rejected 1 Findings 3
SiteGraph Auditor self-test passed

The healthy site has four levels, relative URLs, dot segments, fragment duplicates, and an external reference. The risky site has missing description/canonical metadata, a 404, and an out-of-scope path. A final request proves safe mode rejects loopback.

16.3Outputs and boundary

OutputContent
inventory.jsontitle, description, canonical, heading, and source_url
graph.jsonsource, raw href, normalized target, fingerprint, allowed state, and rejection reason
report.jsonPages, edges, duplicates, rejections, limits, errors, warnings, and findings
failures.jsonURL, depth, and stable error
The default mode remains a static-site audit and does not bypass authentication, CAPTCHAs, robots.txt, or anti-bot controls. v1.1.5 can use an atomic checkpoint frontier with strict resume, plus an isolated Playwright renderer when JavaScript execution is required.

Reference · 17

Complete Syntax Reference

V1.3.10 lexical rules, literals, operators, statements, closures, and module syntax.

17.1Source files and lexical rules

ItemRule
File.hhy, UTF-8, LF or CRLF
IdentifierCase-sensitive ASCII letters, digits, and underscores; cannot start with a digit
Statement endNewline or optional semicolon
ContinuationOpen delimiters or leading/trailing |>
Comment# line comment; first line may be a shebang
/Regex at expression start, division after a left operand

17.2Literals and native units

hhy
let nothing = null
let flags = [true, false]
let numbers = [42, -10, 0xff, 0b1010, 1.5, 1e6]
let name = "HHY"
let strings = ["hello", "Hello, {name}"]
let pattern = /ERROR|WARN/i
let list = [1, 2, 3]
let record = { name: "Tom", age: 20 }
let interval = 1..10
let units = [10mib, 5s, 80%]

Ranges include the start and exclude the end. Bytes support b/kb/mb/gb/kib/mib/gib; Duration supports ns/us/ms/s/min/h; % attached to a number creates Percent. Strings support interpolation and \, ", \n, \r, \t, \b, \f, and \0 escapes.

17.3Operator precedence (highest to lowest)

text
()  []  .
not  -  +
*  /  %
+  -
<  <=  >  >=
==  !=
and
or
??
|>
=

and, or, and ?? short-circuit; = may assign only to a let mut binding; |> is left-associative. Conditions require Bool—0, empty strings, and null are not implicitly false.

17.4Declarations, control flow, functions, and modules

hhy
let name = "HHY"
let mut count = 0
count = count + 1
let enabled = true
let items = ["Flow", "Pipe"]

if enabled { print("yes") } else { print("no") }
for item in items { print(item) }
while count < 3 { count = count + 1 }

fn add(a, b) { return a + b }
let doubled = [1, 2] |> stream |> map { number -> number * 2 } |> collect

try { read_text(path("config.json")) } catch err { print_error(err) }
let result = attempt { read_text(path("config.json")) }

import { add as sum_two } from "./math.hhy"
export fn public_api(value) { return value }
ConstructForm
Callname(args)
Pipex |> f(a) equals f(x, a)
Closure{ param -> expression }
Map{ key: value }
Control flowif, for, while, break, continue, return
Moduleimport, as, export

17.5Core value types

text
Null Bool Int Float String Regex BytesBuffer
List Map Range Function Error Result Stream
Bytes Duration Percent DateTime Path
File Directory FileEvent Process CommandResult
HttpRequest HttpResponse
HHY is dynamically typed but does not perform dangerous String/Number or String/Bool coercions. Int is signed 64-bit and Float is IEEE 754 double.

Reference · 18

Standard Library Function Index

Signatures and purposes for all 96 V1.3.10 core callables in the runtime Registry.

18.1Reading the signatures

This page is sourced from the V1.3.10 Runtime Callable Contract Registry and contains all 96 core callables; dynamically registered callables are documented by their extensions. T/U are generic placeholders, ? marks an optional argument or nullable result, and Map? is an optional options Map. Every function supports ordinary calls; a pipe injects its left value as the first argument.

This is the complete callable list. It excludes read-only special values such as args, env, and system, and does not mislabel read-only fields such as File.path or HttpResponse.status as functions.

18.2Core values, collections, environment, and control (22)

print

print(Value...) -> Null

Write values to stdout; a Stream is consumed and printed item by item.

print_error

print_error(Value...) -> Null

Write values to stderr for diagnostics.

exit

exit(Int?) -> Never

End the script with an optional status code (default 0) and unwind resources.

length

length(String | List | Map) -> Int

Return String code points or List/Map elements; use count for a Stream.

byte_length

byte_length(String | BytesBuffer) -> Int

Return the UTF-8 byte count of String or size of BytesBuffer.

type

type(Value) -> String

Return a value's logical type name.

is_type

is_type(Value, String) -> Bool

Test whether a value has the named logical type.

to_int

to_int(Int | Float | String) -> Int

Explicitly convert Int/Float/String to Int; invalid or overflowing input raises ValueError.

to_float

to_float(Int | Float | String) -> Float

Explicitly convert Int/Float/String to Float; invalid input raises ValueError.

get

get(List | Map | Record, Int | String) -> Value | Null

Safely read a List index, Map key, or record field; missing values return null.

require

require(Map, String) -> Value

Read a required Map key; missing raises KeyError, while a present null stays null.

pick

pick(Map, List<String>) -> Map

Return a new Map containing selected keys, preserving present null fields.

put

put(Map, String, Value) -> Map

Return a new Map with one key inserted or replaced; the original is unchanged.

remove_key

remove_key(Map, String) -> Map

Return a new Map without the named key.

append

append(List<T>, T) -> List<T>

Return a new List with one item appended.

remove_at

remove_at(List<T>, Int) -> List<T>

Return a new List without the indexed item; out of range raises IndexError.

now

now() -> DateTime

Return the current zoned DateTime.

datetime.parse

datetime.parse(String, String, String) -> DateTime

Parse DateTime using an explicit format and timezone; invalid input raises ValueError.

require_env

require_env(String) -> String

Read a required environment variable; missing raises KeyError.

sleep

sleep(Duration) -> Null

Wait for a Duration while remaining cancellable.

cancel

cancel() -> Never

Trigger the execution's root cancellation token and begin cleanup.

throw

throw(Error) -> Never

Throw an Error through the call stack or Flow.

18.3Flow and Stream (25)

Transformations such as map, where, and take stay lazy; terminals such as collect, count, and reduce consume the Stream. sort_by and group_by materialize input within resource limits. parallel uses bounded isolated workers and preserves output order.

stream

stream(List<T> | Map | Range) -> Stream<T>

Convert a List, Map entries, or Range into a lazy single-use Stream.

range

range(Int, Int) -> Stream<Int>

Create an Int Stream from start up to but excluding end.

map

map(Stream<T>, Function(T -> U)) -> Stream<U>

Lazily transform each item one-to-one without automatic flattening.

flat_map

flat_map(Stream<T>, Function(T -> Stream<U>)) -> Stream<U>

Return a child Stream per item and lazily concatenate child streams.

where

where(Stream<T>, Function(T -> Bool)) -> Stream<T>

Lazily retain items whose predicate returns Bool true.

take

take(Stream<T>, Int) -> Stream<T>

Lazily retain the first n items and close upstream early.

skip

skip(Stream<T>, Int) -> Stream<T>

Lazily discard the first n items and pass the remainder.

inspect

inspect(Stream<T>, Function(T -> Value)) -> Stream<T>

Run an observation closure for each item and pass the item unchanged.

distinct

distinct(Stream<Hashable>) -> Stream<Hashable>

Lazily remove duplicate hashable scalars while retaining a seen set.

sort_by

sort_by(Stream<T>, Map, Function(T -> Comparable)) -> Stream<T>

Materialize finite input and stably sort by closure key and asc/desc option.

group_by

group_by(Stream<T>, Function(T -> Hashable)) -> Stream<Group<T>>

Materialize finite input into Groups containing key and values.

debounce

debounce(Stream<T>, Duration) -> Stream<T>

Coalesce rapid events within a Duration, commonly for watch streams.

on_error

on_error(Stream<T>, Function(Error -> Stream<T>)) -> Stream<T>

On Stream failure, invoke a closure whose returned Stream supplies recovery output.

parallel

parallel(Stream<T>, Int, Function(T -> U)) -> Stream<U>

Process with at most n isolated workers, ordered output, bounded buffering, and fail-fast errors.

collect

collect(Stream<T>) -> List<T>

Consume a finite Stream and materialize it as a List.

count

count(Stream<T>) -> Int

Consume a Stream and return its item count.

first

first(Stream<T>) -> T | Null

Return the first item or null and close upstream early.

last

last(Stream<T>) -> T | Null

Consume a Stream and return its last item or null.

min

min(Stream<Number>) -> Number | Null

Consume a numeric Stream and return its minimum or null for empty input.

max

max(Stream<Number>) -> Number | Null

Consume a numeric Stream and return its maximum or null for empty input.

sum

sum(Stream<Number>) -> Number

Consume and sum a numeric Stream, respecting Int overflow rules.

reduce

reduce(Stream<T>, U, Function(State<T,U> -> U)) -> U

Fold a Stream from initial; the closure receives state with acc/item/index.

any

any(Stream<T>, Function(T -> Bool)) -> Bool

Return true on the first matching item and short-circuit upstream.

all

all(Stream<T>, Function(T -> Bool)) -> Bool

Return true only if every item matches; short-circuit on the first false.

for_each

for_each(Stream<T>, Function(T -> Value)) -> Null

Consume a Stream, execute a closure for each item, and return null.

18.4Text, Regex, JSON, and CSV (18)

contains

contains(String | List, Value) -> Bool

Test whether a String contains a substring or a List contains an equal value.

upper

upper(String) -> String

Return a new String converted to Unicode uppercase.

lower

lower(String) -> String

Return a new String converted to Unicode lowercase.

trim

trim(String) -> String

Remove whitespace from both ends of a String.

trim_start

trim_start(String) -> String

Remove leading whitespace from a String.

trim_end

trim_end(String) -> String

Remove trailing whitespace from a String.

starts_with

starts_with(String, String) -> Bool

Test whether a String starts with the given text.

ends_with

ends_with(String, String) -> Bool

Test whether a String ends with the given text.

replace

replace(String, String, String) -> String

Return a new String with matching text replaced.

split

split(String, String) -> List<String>

Split a String by delimiter text into List<String>.

join

join(List<String>, String) -> String

Join List<String> with delimiter text.

regex_match

regex_match(String, Regex) -> Bool

Test a String against a PCRE2 Regex under regex resource limits.

regex_captures

regex_captures(String, Regex) -> Map | Null

Return full match, byte positions, numbered and named captures; null when unmatched.

url_resolve

url_resolve(String, String?) -> Map

Resolve an absolute or relative HTTP(S) URL, remove fragments, default ports, and dot segments, and return host, path, and a stable fingerprint.

parse_json

parse_json(String) -> JsonValue

Strictly parse JSON String into ordinary HHY values with line/column errors.

encode_json

encode_json(JsonValue, Map?) -> String

Encode supported ordinary values as JSON; options may enable pretty output.

parse_csv

parse_csv(String | Stream<String>, Map?) -> Stream<Map>

Stream-parse a String or line Stream into Stream<Map>.

encode_csv

encode_csv(Stream<Map>, Map?) -> Stream<String>

Stream-encode Stream<Map> into CSV records without line terminators.

18.5Paths, files, and watch (15)

read_* functions read data, write_* functions write directly, and save_* functions use a temporary file plus atomic replacement. Dry-run intercepts filesystem actions.

path

path(String) -> Path

Lexically normalize String into Path without filesystem access.

path_join

path_join(Path, String | Path) -> Path

Combine a Path with a child path and return a normalized Path.

files

files(Path, String, Map?) -> Stream<File | Directory>

Lazily walk a root with a glob and return a File/Directory Stream.

read_text

read_text(Path) -> String

Read an entire UTF-8 file as String.

read_lines

read_lines(Path) -> Stream<String>

Lazily read UTF-8 lines with terminators removed.

read_bytes

read_bytes(Path) -> BytesBuffer

Read an entire binary file as BytesBuffer.

write_text

write_text(Path, String, Map?) -> Path

Atomically replace with String, supporting overwrite/create_parents.

append_text

append_text(Path, String) -> Path

Append String to the end of a file.

write_bytes

write_bytes(Path, BytesBuffer, Map?) -> Path

Atomically replace a file with BytesBuffer.

save_text

save_text(String | Stream<String>, Path, Map?) -> Path

Atomically save a String or pull a text Stream directly to disk.

save_lines

save_lines(Stream<String>, Path, Map?) -> Path

Write a String Stream with LF per item and atomically replace the target.

copy

copy(Path, Path, Map?) -> Path

Copy a file with atomic no-replace and parent creation options.

move

move(Path, Path, Map?) -> Path

Move or rename a file while respecting overwrite options.

remove

remove(Path) -> Path

Remove an explicit Path and return it.

watch

watch(Path, Map?) -> Stream<FileEvent>

Return an infinite FileEvent Stream with recursive option and cancellation.

18.6Processes, standard input, and timers (6)

run passes argv directly without a shell; only shell explicitly uses shell parsing. Process launches obey timeout, output, and process-count limits.

run

run(List<String>, Map?) -> CommandResult

Execute argv directly without a shell and return CommandResult.

shell

shell(String, Map?) -> CommandResult

Explicitly execute a String through a shell for redirects, pipes, and shell syntax.

stdout_lines

stdout_lines(CommandResult) -> Stream<String>

Expose CommandResult.stdout as a lazy line Stream.

processes

processes() -> Stream<Process>

Return a Stream<Process> snapshot of current processes.

stdin_lines

stdin_lines() -> Stream<String>

Lazily read stdin lines until EOF or cancellation.

every

every(Duration) -> Stream<Int>

Produce an infinite timer tick Stream at a Duration interval.

18.7HTTP (10)

http.* only builds immutable request plans, timeout/retry transform a plan, and only send performs a network effect. response_body returns UTF-8 text; use response_bytes for binary data.

http.get

http.get(String, Map?) -> HttpRequest

Build a GET HttpRequest plan without network I/O.

http.post

http.post(String, Map?) -> HttpRequest

Build a POST HttpRequest plan without network I/O.

http.put

http.put(String, Map?) -> HttpRequest

Build a PUT HttpRequest plan without network I/O.

http.delete

http.delete(String, Map?) -> HttpRequest

Build a DELETE HttpRequest plan without network I/O.

timeout

timeout(HttpRequest, Duration) -> HttpRequest

Return a new HttpRequest with its timeout configured.

retry

retry(HttpRequest, Map) -> HttpRequest

Return a new HttpRequest configured with retry count and backoff.

send

send(HttpRequest) -> HttpResponse

Perform the HttpRequest network effect and return HttpResponse.

send_to

send_to(HttpRequest, Path) -> HttpResponse

Stream the HTTP body into an atomic file and return an HttpResponse with path and size.

response_body

response_body(HttpResponse) -> String

Validate response status and decode the bounded body as UTF-8 String.

response_bytes

response_bytes(HttpResponse) -> BytesBuffer

Validate response status and return the bounded binary BytesBuffer.

Reference · 19

CLI Reference

Run, check, format, use the REPL, inspect dry-run plans, and profile performance.

19.1Version and release identity

Use --version to confirm the binary version, project author, open-source license, and official contact details. Source builds use ./build/hhy; release archives or PATH installations can invoke hhy directly.

HHY · Version information · $ ./build/hhy --version
hhy 1.3.10
© 2026 HHY Language contributors
Author: houhuiyang
License: Apache License 2.0
https://hhylang.dev/
huiyang.hou@qq.com

Actual HHY 1.3.10 command output. The CLI reports the version, author, license, website, and contact address directly.

From an official archive, run ./bin/hhy --version in the extracted directory. After make install or adding HHY to PATH, run hhy --version.

19.2Complete command set

sh
hhy script.hhy [args...]
hhy run script.hhy [args...]
hhy repl
hhy check script.hhy...
hhy fmt script.hhy...
hhy fmt --check script.hhy...
hhy ast script.hhy
hhy bytecode script.hhy
hhy bytecode --metrics script.hhy
hhy tokens script.hhy
hhy run --dry-run script.hhy
hhy run --limit max_runtime=30s --limit max_memory=256mib script.hhy
hhy profile script.hhy [args...]
hhy profile --cpu script.hhy
hhy profile --heap --format json --output profile.json script.hhy
hhy --version
hhy --help
CommandPurpose
hhy runRun a script and pass args
hhy profileAnalyze CPU hotspots, call counts, and managed-Heap allocations
hhy replStart the interactive environment
hhy checkCheck syntax and core semantics
hhy fmtWrite canonical formatting
hhy fmt --checkCheck formatting only
hhy astPrint the AST
hhy bytecodeCompile, verify, and disassemble Bytecode
hhy bytecode --metricsEmit cache-admission compile/verify/prepare metrics as JSON
hhy tokensPrint Lexer tokens
hhy run --dry-runPreview a redacted execution plan

hhy script.hhy is shorthand for hhy run script.hhy. Use -- after Runtime options when script arguments may begin with a dash.

19.3Bytecode cache admission evidence

v1.3.10 adds read-only --metrics output for measuring real compile+verify and execution-plan verification cost. Across five fixed workloads and 21 paired fresh-process samples, compile+verify medians were 0.004–0.012 ms, only 0.0078%–0.1341% of cold-run wall time, below the joint 1 ms and 20% thresholds.

$ hhy bytecode --metrics examples/00-hello.hhy
{"bytecode_format_version":1,"compile_verify_ns":9000,"constants":21,"instructions":40,"schema_version":1,"source_bytes":167,"stream_kernel_version":1,"stream_kernels":1,"tool":"hhy bytecode --metrics","verify_prepare_ns":4000}
No process or disk Bytecode cache is enabled, and third-party precompiled Bytecode remains rejected. Any future admission requires new performance evidence plus a complete source/dependency/version/feature/target/security fingerprint, checksum, bounded decoding, the full Verifier, and execution-plan verification.

19.4CPU and Heap profiling

profile executes the script and collects CPU and managed-Heap data in the same run by default. Reports go to stderr, leaving script stdout unchanged, and the command preserves the script's exit code.

sh
hhy profile examples/09-profile-algorithms.hhy -- fibonacci 20
hhy profile --cpu examples/09-profile-algorithms.hhy fibonacci 20
hhy profile --heap --format json --output profile.json examples/09-profile-algorithms.hhy fibonacci 20
OptionBehavior
--cpuCollect only 1ms process-CPU samples and call counts
--heapCollect only cumulative allocations, allocation count, Heap peak, and post-GC usage
--format text|jsonSelect a human- or machine-readable report; default: text
--output <path>Write the report to a file instead of stderr
--limit NAME=VALUEOverride Runtime resource limits, as with run
--dry-runBlock external effects, as with run, and profile plan execution
$ hhy profile examples/09-profile-algorithms.hhy -- fibonacci 20
HHY profile: examples/09-profile-algorithms.hhy

Summary
  Wall time        0.006 s
  CPU time         0.004 s
  CPU utilization  64.5%
  CPU samples      2
  Heap peak        755.9 KiB
  Heap after GC    4.0 KiB
  Allocated        523.9 KiB
  Allocations      11107

CPU hotspots
  CPU%    Samples      Calls  Function
  100.0%        2      21891  fibonacci  examples/09-profile-algorithms.hhy:5:1

Allocation hotspots
  Bytes          Objects  Function
  515.6 KiB        10966  fibonacci  examples/09-profile-algorithms.hhy:5:1

fibonacci 6765
CPU profiling samples process CPU time, so file, HTTP, and process waits are not misreported as CPU hotspots. Scripts that finish in a few milliseconds may need a larger or repeated workload. Heap metrics cover memory managed by HHY's Boehm GC, not extension subprocesses or memory owned directly by native libraries.

19.5Interpreter performance evolution

v1.3.10 defaults to the Compiler/Verifier-validated Bytecode VM. Native Opcode dispatch, static slots, reusable call frames, and low-allocation closure fast paths reduce CPU cost; the AST Interpreter remains the permanent semantic oracle and explicit fallback.

AST and Bytecode VM performance evolution

Compiler/Verifier → native Opcode dispatch → slots/call frames → low-allocation closures → performance gate; v1.3.5 defaults to Bytecode with AST fallback.

v1.3.5 defaults run, profile, and script shorthand to Bytecode. Use --engine ast or HHY_ENGINE=ast for immediate rollback. The measured ratios are 0.6805 for the 1M CPU workload, 0.9258 for short scripts, and 0.9976 for JSON/I/O; all machine-readable gates pass.

19.6Runtime resource limits

The run command accepts repeatable --limit NAME=VALUE options. Sizes require b/kb/mb/gb/kib/mib/gib, durations require ns/us/ms/s/min/h, and counts have no unit.

sh
hhy run --limit max_runtime=30s --limit max_memory=256mib script.hhy
LimitDefault
max_memory512mib
max_open_files256
max_processes16
max_parallelism16
max_http_body16mib
max_regex_steps1000000
max_recursion256
max_runtime0 (no total CLI limit)

19.7Stable exit codes

text
0  success
1  unhandled runtime error
2  syntax or static-check error
3  invalid CLI usage
4  file I/O, process, or network error
5  timeout or cancellation

Automation should branch on stable exit codes rather than error text.

Extensions · 20

Extension System

The v1.3.10 process-extension model: the signed official Registry, source builds, manifests, capabilities, Protocol 1, and callable registration.

20.1The current extension boundary

v1.3.10 implements local install/list/remove, an Ed25519-signed Registry, manifest and SHA-256 validation, isolated-process handshakes, dynamic callable registration, synchronous calls, structured errors, and shutdown. Scripts can directly import installed packages.
CapabilityCurrent statusBoundary
Extension distributionImplementedSigned official Registry or local source builds; identity, target, signatures, and file hashes are verified before installation
Process protocolImplementedhandshake, register, call, call_result, error, shutdown
Value transportImplementedJSON protocol mapping for Null, Bool, numbers, String, List, and Map
Stream / handle / cancelNot implementedReserved for a future protocol extension
Public Native ABINot committedEvaluate only if measurements show the process model is insufficient

A package name is its top-level namespace: package_name may register only package_name.* and cannot replace hhy.*, std.*, core callables, or another package. Importing an uninstalled package raises ModuleNotFoundError.

20.2Available official extensions

ExtensionVersionPublishedStatusCapability
database0.2.02026-08-26ReleasedMySQL/PostgreSQL queries, writes, and transactions
html0.1.02026-08-27ReleasedLexbor CSS selectors, text/attribute reads, and structured extraction

Database Extension Guide
Install database 0.2.0 and perform MySQL/PostgreSQL queries, writes, and transactions.
/en/learn/database-extension

HTML Extension and Crawler Framework
Use html 0.2.0 with observable batch extraction, URL normalization, a safe frontier, deduplication, and SSRF protection.
/en/learn/html-crawler-framework

20.3Where to get extensions

SourceBest forAddress or action
Official HHY RegistryDownloading signed official extensions for a specific platformhttps://registry.hhylang.dev (index: /index.json; trust root: /root.json)
GitHub sourceReviewing code, auditing changes, or building from sourcehttps://github.com/hh696-wq/hhy-vm/tree/main/extensions
Local source buildExtension development or a custom local dependency combinationmake -C extensions/<name>, then install the local directory
sh
# Build and install from source (html example)
git clone https://github.com/hh696-wq/hhy-vm.git
cd hhy-vm
make
make -C extensions/html
./build/hhy install ./extensions/html

# Inspect installed extensions
./build/hhy list

Open the official extension index ↗
One extension version may contain darwin-arm64, linux-x86_64, linux-arm64, and windows-x86_64 targets; the installer selects only its native target.
https://registry.hhylang.dev/index.json

Browse extension source on GitHub ↗
Source, hhy.toml manifests, tests, and build scripts for sample, html, and database.
https://github.com/hh696-wq/hhy-vm/tree/main/extensions

GitHub Releases are not currently an extension download channel. The official Registry provides signed distribution; GitHub provides auditable source. Build on the destination operating system and architecture—renaming a macOS binary does not make it usable on Linux or Windows.

20.4Install, list, and remove

sh
./build/hhy install ./path/to/extension
./build/hhy list
./build/hhy remove package-name
StepActual behavior
installRead hhy.toml; validate package name, author, requires_hhy, protocol, command, and integrity; display capabilities and ask the user to confirm
import / loadRecheck installed SHA-256 data, start the extension process, handshake, and register callables
listDisplay each installed package's name, version, author, protocol, and declared capabilities
removeDelete the local package record and installation directory; subsequent imports fail
The default extension home is ~/.hhy/extensions; set HHY_EXTENSION_HOME for isolated CI or tests. Capabilities are reviewable declarations, not a general operating-system sandbox. Treat third-party extensions as native executables.

20.5Generic hhy.toml manifest

text
[package]
name = "package-name"
version = "0.1.0"
author = "Your Organization"
requires_hhy = ">=1.1,<2.0"

[extension]
kind = "process"
command = "bin/hhy-package"
protocol = "1"

[capabilities]
read = []
write = []
network = []
process = false
FieldDeveloper constraint
package.nameUnique top-level namespace using lowercase letters, digits, and hyphens
package.authorShown during install and list to identify official or third-party provenance
requires_hhyRuntime version range checked by the installer
extension.commandMust be an executable under the package bin/ directory and cannot escape its root
extension.protocolCurrently accepts Protocol 1
capabilitiesDeclares file, network, and subprocess access for review

20.6How an extension loads

Extension loading flow

HHY script → Runtime validation → isolated extension process → protocol registration → call execution → structured result or error.

StageRuntime and extension responsibility
resolveRuntime resolves import package_name to an installed package, parses its manifest, and validates command integrity
spawnRuntime starts a separate process with --protocol 1 and opens stdin/stdout protocol pipes
handshakeBoth sides confirm extension_id and protocol_version=1.0
registerThe extension sends one registration message; Runtime validates its package namespace and contracts
callRuntime sends serializable arguments; request_id correlates each call and call_result
shutdownRuntime sends shutdown and reaps protocol streams and the child process
Protocol 1 is synchronous and one-call-at-a-time. It does not provide Stream transport, opaque handles, or protocol-level cancellation.

20.7What an extension author must implement

PartRequirement
PackageProvide hhy.toml, an in-package executable command, and SHA-256 integrity data verifiable by the installer
StartupAccept only --protocol 1; write protocol messages only to stdout and logs to stderr
HandshakeValidate extension_id and protocol_version and return matching identity
RegisterSend exactly one initial registration; every name must stay in the package namespace and provide a valid contract
CallReturn call_result or structured error for each request_id without exposing credentials or sensitive diagnostics
ShutdownIdempotently release connections, memory, and other extension resources
TestsCover identity mismatch, invalid arguments, extension exit, protocol errors, and resource cleanup

The extension process does not receive the complete host environment; Runtime passes only arguments explicitly supplied by the script. Errors should remain actionable without including passwords, tokens, complete connection addresses, or other sensitive information.

Extensions · 21

Database Extension Guide

Install the official database 0.2.0 extension, configure MySQL/PostgreSQL with JSON, and run queries, writes, and transactions.

21.1What the database extension is

database 0.2.0 ships with HHY v1.1.0 and is a real C11 process extension in the repository. It currently supports MySQL, PostgreSQL, parameterized reads, parameterized writes, and the first transaction API. Connection handles, pooling, streaming queries, and complete database type mapping are future work.
CallablePurposeCurrent boundary
database.ping(url)Validate connectivity and return database informationCreates a short-lived connection per call
database.query(url, sql, params, max_rows?)Run a bounded parameterized queryResult contains columns and rows
database.execute(url, sql, params)Run a parameterized write or controlled DDLReturns affected-row information
database.transaction(url, statements)Atomically run 1–100 writesINSERT/UPDATE/DELETE only; rolls back on failure

21.2Install the extension and its four callables

sh
make -C extensions/database
./build/hhy install ./extensions/database
./build/hhy list

install validates hhy.toml, the HHY version range, the extension command, and SHA-256 integrity, then displays network capabilities before confirmation. After installation, import database starts the isolated extension process, completes the Protocol 1 handshake, and registers all four callables.

21.3Connection configuration and credential safety

sh
cd extensions/database/examples/hhy_extension_test
cp config.example.json config.local.json
chmod 600 config.local.json
config.local.json
{
  "url": "mysql://root:CHANGE_ME@127.0.0.1:3306/hhy_extension_test",
  "database": "hhy_extension_test",
  "max_rows": 1000
}
DriverConnection URL exampleParameter placeholder
MySQLmysql://user:password@127.0.0.1:3306/hhy_extension_test?
PostgreSQLpostgresql://user:password@127.0.0.1:5432/hhy_extension_test$1, $2, …
Replace CHANGE_ME with the local password. config.local.json is ignored by Git in the example directory; never put real credentials in .hhy source, documentation, or Git. The examples also require the database field to equal hhy_extension_test so they cannot target an application database accidentally.

21.4Project one: inspect the test database read-only

sh
./build/hhy run \
  extensions/database/examples/hhy_extension_test/read.hhy \
  extensions/database/examples/hhy_extension_test/config.local.json
read.hhy
import database
import { load_database_config } from "./lib/config.hhy"

let config = load_database_config(args[0])
let result = database.query(
    config.url,
    "SELECT COUNT(*) AS table_count FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'",
    [config.database],
    1
)

print("Database", config.database)
print("Table count", result.rows[0].table_count)

The complete read.hhy in the repository also lists every table's name, storage engine, and estimated row count. SQL values go through the driver's prepared-statement parameter API and are never concatenated into the query text.

21.5Project two: controlled writes and transactions

sh
./build/hhy run extensions/database/examples/hhy_extension_test/write-demo.hhy \
  extensions/database/examples/hhy_extension_test/config.local.json --write

./build/hhy run extensions/database/examples/hhy_extension_test/transaction.hhy \
  extensions/database/examples/hhy_extension_test/config.local.json --write
transaction-example.hhy
database.transaction(config.url, [
    { sql: "INSERT INTO _hhy_transaction_test (id, message) VALUES (?, ?)", params: [1, "created"] },
    { sql: "UPDATE _hhy_transaction_test SET message = ? WHERE id = ?", params: ["committed", 1] }
]) |> print
Both write examples require an explicit --write flag and touch only dedicated temporary tables inside hhy_extension_test. transaction rejects SELECT and DDL; if any statement fails, the extension rolls back the entire transaction.

21.6Current boundaries and troubleshooting

SymptomCheck
ModuleNotFoundErrorRun install first and use hhy list to confirm database 0.2.0 is installed
cannot open .../read.hhyRun the complete path from the repository root; the directory is named hhy_extension_test
Connection failureCheck the service, port, user, password, database name, and local network scope declared by hhy.toml
SQL parameter errorMySQL uses ?; PostgreSQL uses $1, $2, …; identifiers cannot be value parameters

Continue with the extension system internals
Learn about manifest validation, capability declarations, process loading, the Protocol 1 handshake, and extension-author constraints.
/en/learn/extensions-roadmap

Extensions · 22

HTML Extension and Crawler Framework

Combine the official HTML extension with a safe frontier for URL normalization, discovery, recursive deduplication, and bounded static crawling.

22.1What the HTML extension is

The official html 0.2.0 package is a process extension with no network or filesystem effects. Lexbor parses untrusted HTML, evaluates CSS selectors, and extract_report returns batch rows with explicit truncation metadata.

The extension parses and extracts only. It does not fetch URLs, schedule pages, or return DOM handles. HHY Runtime and the crawler layer own HTTP, TLS, timeouts, retries, and security policy.

22.2Build and install the HTML extension

sh
brew install jansson lexbor
make -C extensions/html
./build/hhy install ./extensions/html
./build/hhy list
Complete signaturePurpose
html.text(String html, String selector, Map?) -> String?Read normalized text from the first matching node
html.text_all(String html, String selector, Map?) -> List<String>Read text from every matching node
html.attr(String html, String selector, String name, Map?) -> String?Read an attribute from the first matching node
html.attr_all(String html, String selector, String name, Map?) -> List<String>Read an attribute from every matching node
html.exists(String html, String selector) -> BoolTest whether a selector matches
html.extract(String html, String selector, Map schema, Map?) -> List<Map>Parse once and project repeated records through a schema
OptionCallablesBehavior
trim: Booltext, text_all, attr, attr_allTrim surrounding whitespace; enabled by default
max_results: Inttext_all, attr_all, extractDefaults to 1000; hard limit 10000

The extension limits input to 768 KiB, keeping protocol messages bounded. extract schema fields use { selector, value: "text" } or { selector, value: "attr", name }; an empty selector reads the current root. It does not return DOM handles: Protocol 1 transports only JSON-shaped values, so html.extract performs parsing and projection inside one extension call.

22.3From HTML extraction to a safe crawler

The static spider composes separate responsibilities: Runtime url_resolve normalizes relative references; the HTML extension discovers links; a depth frontier checks domains, paths, page and queue budgets, and fingerprints before admission; the HTTP layer blocks SSRF at the resolved connection address.

CapabilityCurrent implementation
url_resolve(url, base?)Returns url, scheme, host, port, path, query, and a stable fingerprint
Link discoveryfollow_selector extracts href values and feeds the next frontier
Frontier and deduplicationDepth batches with bounded concurrency and pre-admission fingerprint deduplication
Hard boundariesDomains, path prefixes, depth, pages, frontier size, and links per page
SSRF protectionReject loopback, private, and link-local socket addresses, including redirects

22.4Project one: the my-crawler foundation

sh
make
./practical-projects/my-crawler/init.sh
./practical-projects/my-crawler/self-test.sh
./practical-projects/my-crawler/run.sh

my-crawler is the smallest runnable spider: it reads JSON configuration, recursively discovers authorized links, extracts records, and writes report and failure artifacts. Normal runs reuse ~/.hhy/extensions; CI and self-tests use a temporary HHY_EXTENSION_HOME.

config/hhylang.json
{
  "seeds": ["https://hhylang.dev/zh/learn/cli-reference"],
  "allowed_domains": ["hhylang.dev"],
  "allowed_path_prefixes": ["/zh/learn/"],
  "follow_selector": "main article a[href]",
  "parallelism": 2,
  "max_depth": 2,
  "max_pages": 50,
  "max_frontier": 100,
  "max_links_per_page": 200,
  "allow_private_networks": false,
  "root_selector": "main article h2",
  "max_results": 100,
  "schema": { "title": { "selector": "", "value": "text" } }
}
$ ./practical-projects/my-crawler/self-test.sh
HHY Collector Framework Crawler Fixture
Pages 3 / 3 Records 3 Failures 0
HHY Collector Framework self-test passed

Read the complete my-crawler source
Start here for configuration, recursive crawling, structured extraction, failure archives, and deterministic tests.
https://github.com/hh696-wq/hhy-vm/tree/main/practical-projects/my-crawler

22.5Project two: the SiteGraph Auditor challenge

SiteGraph Auditor adds a page inventory, normalized graph, metadata audit, and CI quality gate on top of the foundational spider, with both healthy and risky fixtures.

OutputContent
inventory.jsonPage metadata, primary heading, and source URL
graph.jsonNormalized edges, fingerprints, allowed state, and rejection reason
report.jsonPages, edges, duplicates, limits, errors, warnings, and findings
failures.jsonFailed URL, depth, and stable error

Open the SiteGraph Auditor challenge
Continue with a site graph, content-quality audit, negative SSRF test, and CI gate.
/en/learn/sitegraph-auditor-project

22.6Scope and explicit boundaries

The crawler supports an in-memory or atomically checkpointed frontier, strict configuration-matched resume, and send_to response files. An optional Playwright renderer executes JavaScript separately from the side-effect-free Lexbor extension and applies domain plus DNS private-network checks to documents, redirects, and subresources.

Do not use it to bypass robots.txt, authentication, CAPTCHAs, or anti-bot controls. Crawl only authorized sites with an identifiable User-Agent, conservative concurrency, and explicit page budgets.

Roadmap · 23

Language and VM Evolution Roadmap

v1.3.10 completes post-default Bytecode hardening across specialization, IR, profiling, and cache governance while permanently retaining AST as the semantic oracle and fallback.

23.1Current release and two future stages

v1.3.3–v1.3.10 complete native Opcode execution, the Bytecode default switch, Stream Int fusion, named specialization metadata, Compiler/Verifier Stream Kernel IR, Profiler/resource consistency, and cache governance. Final 1M CPU, short-script, JSON/I/O, and Profiler CI gates pass; measured data did not admit a cache. AST remains the semantic oracle and --engine ast fallback.
Language and VM evolution

After core semantics freeze: performance hardening, official extension tooling, and an ABI decision driven by real ecosystem evidence.

23.2Release lineage, timing, and acceptance gates

ReleaseRecommended windowPrimary deliveryRequired before the next stage
v1.0.0 · Released2026-08-25Core language and VM semantics frozenPipe, Value, Stream, Error, the core standard library, and three-platform release evidence completed
v1.1.0 · Released2026-08-26Local process extensions and the official database extensionInstall/load integrity, synchronous Protocol 1 calls, database 0.2.0, and three-platform release evidence completed
v1.1.1 · Released2026-08-27Performance optimization and resource-boundary stabilityhhy profile, interpreter hotspot baselines, and Runtime resource boundaries completed
v1.1.2 · Released2026-08-27HTML extension and static collector frameworkThree-platform CI, protocol tests, local fixtures, and the real hhylang.dev crawl completed
v1.1.3 · Released2026-08-28Runtime correctness and performance hardeningGC pressure regression, sanitizers, hash indexes, stable diagnostics, and three-platform release evidence completed
v1.1.4 · Released2026-08-28Safe static spiderURL normalization, discovery, frontier, limits, fingerprint deduplication, and connection-level SSRF protection
v1.1.5 · Released2026-08-30Resumable spider and browser renderingPersistent frontier, resume, streamed files, optional Playwright, and Windows MSYS2 build evidence
v1.1.6 · Completed2026-08-31Stable engineering baselineHost capability probes, layered CI, machine-readable performance baselines, and release consistency gates
v1.1.7 · Completed2026-08-31Diagnostics and editor baselineVersioned JSON diagnostics, Contract Registry JSON, a minimal LSP, and a VS Code editing loop
v1.1.8 · Completed2026-08-31Gradual Runtime governanceFirst module boundary, internal ownership API, sanitizer/GC stress, and a blocking performance-regression gate
v1.2.0 · Released2026-08-31Official extension distribution and signingNamespaced identities, Ed25519-signed index and package descriptors, deterministic resolution, dry runs, and transaction-safe installs
v1.2.1 · Released2026-09-01Locking, offline installs, and safe rollbackThe same lock produces the same graph; offline rebuilds work; failed upgrades preserve the old environment
v1.2.2 · Released2026-09-01Official HTML complex-extension validationReal fixtures, observable truncation, structured errors, and four-platform distribution all pass
v1.3.0-alpha · Prereleased2026-09-01Bytecode compiler skeletonCore syntax compiles, invalid Bytecode is rejected, and AST remains the default engine
v1.3.0-beta · Stage gate completeMerged into v1.3.0Bytecode VM execution coreThe execution bridge, verifier, resource boundaries, and full dual-engine fixtures passed acceptance
v1.3.0-rc · Stage gate completeMerged into v1.3.0Performance, profiler, stack trace, and default-switch gatesThree-platform and failure evidence is complete; the CPU benefit gate failed, so AST remains the default
v1.3.0 · Released2026-09-01Opt-in production Bytecode pathThe full suite passes both engines; the performance decision retains AST as default with explicit fallback
v1.3.1 · Released2026-09-01Real-workload compatibility hardeningThe official workload dual-engine matrix and capability evidence passed three-platform CI
v1.3.2 · Released2026-09-01VM internal-boundary stabilizationVersioned Bytecode Runtime boundary, static governance, and continuous AST oracle
v1.3.3 · CompletedMerged into v1.3.5Native Opcode executionNormal Bytecode execution no longer calls the AST evaluator; full dual-engine semantics pass
v1.3.4 · CompletedMerged into v1.3.5VM data-path optimization1M CPU workload ratio is 0.6805 with no material short-script or JSON/I/O regression
v1.3.5 · Released2026-09-01Bytecode default enginerun, profile, and shorthand default to Bytecode; AST remains the permanent oracle and explicit fallback
v1.3.6 · Released2026-09-01Stream Int fusion performance closureProvably safe shapes fuse conservatively; unknown shapes fall back losslessly; dual-engine and cross-platform gates pass
v1.3.7 · Released2026-09-01Specialization hardeningNamed metadata, unified stack/error rules, fallback reasons, and three-path differential gates pass
v1.3.8 · Released2026-09-01Compiler/Verifier optimization IRRuntime does not inspect AST shapes; Stream Kernels verify independently and fall back safely
v1.3.9 · Released2026-09-01Profiler and resource consistencyNormal and observed execution share decisions; overhead, cancellation, and Heap-attribution gates pass
v1.3.10 · Released2026-09-01Bytecode cache governanceFive-workload evidence misses admission thresholds; no cache and no unverified external Bytecode
v1.4 · PlannedAfter v1.3 stabilizesFlagship scenarios and external adoptionTemplates, CI, operations documentation, and 3–5 real external cases
v2.0 · ConditionalAfter sufficient ecosystem evidenceEcosystem opening and ABI decisionAt least two real integrations prove the process protocol insufficient; otherwise retain the process protocol and do not publish a Native ABI

Note: these dates are recommended windows, not release commitments.

23.3Evolution principles

PrincipleConstraint
Freeze semantics firstStabilize Pipe, Value, Stream, Error, and cancellation semantics before broadening the ecosystem surface
Usable and measurable before fastEvery capability needs deterministic errors, resource bounds, and cross-platform tests before optimization
Protocol firstIntegrate third-party capability through the Process Extension Protocol instead of inventing a second language model
ABI only when justifiedEvaluate a Native ABI only after Runtime stabilization and measured need; choosing not to publish one is a valid result

Review the roadmap once per quarter. Only unfrozen releases may move; scheduling changes must not weaken published semantics, compatibility commitments, or migration paths.

23.4Explicit non-commitments

  • No second Pipe, Stream, or Error model merely to justify a release number.
  • No public exposure of internal Runtime C structures without a compatibility strategy.
  • No use of recommended windows as a reason to skip testing, security, or cross-platform validation.
  • No simultaneous rush into a remote registry, Native ABI, and multiple official extensions before stage gates pass.

Tooling · 24

Editor Language Support

Install HHY language packages for VS Code and Sublime Text, generated from one syntax source.

24.1HHY Language Support 0.1.0

The editor packages recognize .hhy files and provide HHY syntax highlighting for # comments, shebangs, strings and escapes, Regex, numbers and units, keywords, and operators, plus bracket auto-closing, indentation, and common snippets. VS Code uses a TextMate grammar; Sublime Text uses .sublime-syntax.

Version 0.1.0 is lightweight, process-free language support. It does not yet provide format-on-save, diagnostics, go-to-definition, or an LSP. The repository-owned editors/syntax/hhy-syntax.json file is the single source of truth.

Open the editor language-support source ↗
Includes the shared syntax source, generator, VS Code and Sublime Text packages, and real .hhy regression fixtures.
https://github.com/hh696-wq/hhy-vm/tree/main/editors

24.2Generate and verify the packages

sh
git clone https://github.com/hh696-wq/hhy-vm.git
cd hhy-vm/editors
npm install
npm run generate
npm run check
npm run package

The package command creates dist/hhy-language-support-0.1.0.vsix and dist/HHY-0.1.0.sublime-package. The check command compares Lexer keywords and literal suffixes, validates plugin metadata and generated-file freshness, and checks every fixture with the real HHY binary.

24.3Install in VS Code

sh
code --install-extension editors/dist/hhy-language-support-0.1.0.vsix

You can also open Extensions in VS Code and choose Install from VSIX from the top-right menu. After installation, any .hhy file is automatically recognized as HHY.

24.4Install in Sublime Text

Copy editors/dist/HHY-0.1.0.sublime-package into Sublime Text's Installed Packages directory. For development, copy editors/sublime into Packages/HHY. Opening a .hhy file then enables HHY syntax automatically.

The HHY Lexer distinguishes Regex from division using the previous token. Editor grammars conservatively recognize Regex only in expression-start contexts, preferring a missed Regex highlight over mis-highlighting the remainder of a division expression.

Language Reports · 25

HHY Language Status Report · 2026-09-01

Published status of HHY semantics, Runtime, performance, and engineering quality with reproducible CI measurements.

25.1Release summary

HHY v1.3.10 is formally released. v1.3.7–v1.3.10 sequentially delivered specialization-metadata hardening, Compiler/Verifier Stream Kernel IR, Profiler/resource consistency, and evidence-gated cache governance. Bytecode remains default; AST remains the permanent semantic oracle and emergency fallback. Four-platform Actions, release assets, and SHA256SUMS are verified.
DimensionQuestionCurrent conclusion
Language baselineAre core semantics stable?Pipe, Value, Stream, Error, and core callable contracts are frozen
Runtime healthAre resource, memory, and cancellation boundaries reliable?Resource limits, GC stress, sanitizers, fuzzing, and explicit ownership governance are present
PerformanceIs performance measurable and controlled?Fixed workloads, five-sample medians, machine-readable evidence, and blocking budgets are established
Engineering governanceAre changes auditable?Four-platform CI, layered gates, version consistency, and release evidence form a closed loop

25.2Data at a glance

SignalResultEvidence basis
Current formal releasev1.3.10Four platform archives, per-asset SHA-256, and SHA256SUMS
Execution enginesBytecode default / AST fallbackFull dual-engine suite and machine-readable decision
Core callables96Runtime Callable Contract Registry
Continuous-verification platforms4macOS arm64, Linux arm64, Linux x86_64, Windows x86_64
Final CI engine gatesAll pass1M CPU 0.3695; short task 1.0088; sustained JSON 1.0207
Profiler overhead1.0269× / +2.786 msNine samples; limits 1.35× and 12 ms
Bytecode cacheNot admittedFive workloads × 21 samples; compile+verify is not a major cost
Complete practical projects6AST/Bytecode end-to-end acceptance with stable exit status

25.3Overall baseline and compatibility

BaselineStable commitmentVerification
Language semanticsNo second Pipe, Stream, or Error modelSpecification examples, Parser/Checker fixtures, and valid-program regression
Callable contractsNames, arity, effect, lazy, cancellable, and threading metadata are machine-readableContract Registry JSON and 96-contract consistency checks
DiagnosticsCLI text and JSON/LSP share the Core checking pathDiagnostic schema and LSP protocol tests
Extension boundaryThird-party capabilities prefer the Process Extension ProtocolManifest integrity, Protocol 1, and official-extension acceptance
C ABIRuntime internals are not currently a public ABIReconsider only when real integrations prove the process protocol insufficient

The current formal baseline is v1.3.10. Bytecode is default; the AST evaluator remains the semantic oracle and is selectable through --engine ast or HHY_ENGINE=ast. Compiler-produced Stream Kernels must pass their independent Verifier; dynamic and unknown shapes fall back losslessly to general Bytecode.

25.4v1.2.2 release and extension status

CapabilityCurrent stateAcceptance result
Extension distributionEd25519-signed Registry and deterministic resolutionTampering, unknown sources, and dependency conflicts fail closed
Reproducible environmentLockfile and content-addressed offline cacheThe same lock yields the same graph and rebuilds offline
Safe changeTransactional install, upgrade, and explicit rollbackFailed upgrades preserve the old environment
HTML 0.2.0Lexbor, CSS selectors, and single-parse multi-field projectionMalformed HTML, hard limits, truncation, and structured errors pass on four platforms
Protocol decisionRetain the bounded synchronous batch APINo real evidence requires stream credit, cross-call cancellation, or opaque handles
The v1.2.2 release contains macOS arm64, Linux x86_64, Linux arm64, and Windows x86_64 archives, per-package SHA-256 files, and a combined SHA256SUMS. The HTML extension remains effect = none and performs no independent file, network, or subprocess access.

Open the HHY Language v1.2.2 release
Download all four platform archives and checksums and read the release notes.
https://github.com/hh696-wq/hhy-vm/releases/tag/v1.2.2

25.5v1.3.7–v1.3.10 Bytecode hardening

VersionCore deliveryVerified conclusion
v1.3.7Named specialization metadata, unified stack/error rules, fallback reasonsNo magic kinds; three-path differential and metamorphic gates pass
v1.3.8Compiler-produced versioned Stream Kernel IRRuntime does not inspect AST shapes; independent verification; safe dynamic fallback
v1.3.9Shared optimization decisions for normal and profiled executionKernel/opcode, cancellation, CPU/Heap attribution, and machine reports remain consistent
v1.3.10Performance-triggered cache governanceEvidence did not admit a cache; no process/disk cache; unverified external Bytecode rejected
Each version completed implementation, Release/Debug tests, sanitizers, fuzzing, measured performance, four-platform Actions, formal Release, and Homebrew Formula verification before the next version began.

Open the HHY Language v1.3.10 release
Four platform archives, per-asset SHA-256, SHA256SUMS, and cache-governance release notes.
https://github.com/hh696-wq/hhy-vm/releases/tag/v1.3.10

25.6Measured performance

Final v1.3.10 CI evidence comes from commit 4ddc8c3 on GitHub Actions Ubuntu 24.04: schema-2 paired/interleaved engine benchmarks plus independent Profiler and cache-decision artifacts. Ratios are Bytecode/AST wall time; lower than one means Bytecode is faster.

GateMeasuredLimitResult
1M CPU0.3695×0.90×Pass
Short task1.0088×1.25×Pass
Sustained JSON/I/O1.0207×1.10×Pass
Profiler overhead1.0269× / +2.786 ms1.35× / +12 msPass
Cache-admission workloadCompile+verify medianCold-run medianShare
Hello0.0097 ms5.860 ms0.1649%
Advanced Flow0.0269 ms5.916 ms0.4546%
Stdlib0.0286 ms7.003 ms0.4086%
Sustained JSON0.0089 ms33.518 ms0.0266%
Core Flow 1M0.0105 ms166.285 ms0.0063%
The joint cache threshold is compile+verify ≥ 1 ms and ≥ 20% of cold-run wall time. Every workload remains far below it. Even assuming zero cache-read cost, reproducible benefit is absent, so v1.3.10 implements no process or disk cache and continues to reject unverified external Bytecode.

25.7v1.3.10 six-runtime same-machine rerun

Rerun on 2026-09-01 on macOS 26.6.2 arm64 with HHY 1.3.10, PHP 8.5.10, Go 1.27.0, Python 3.14.7, Lua 5.5.1, and OpenJDK 26.0.2.1. The fixed task maps, filters, stable-distincts, materializes, and counts one million integers; all six implementations validate output 333334. After two warmups, two independent rounds use seven deterministically shuffled and interleaved fresh processes per runtime. Wall time includes process startup; Go and Java are precompiled and compile time is excluded.

ImplementationVersionRound 1 medianRound 2 medianCombined range
Go1.27.07.995 ms7.969 ms7.622–15.325 ms
Lua5.5.118.368 ms17.957 ms17.582–19.822 ms
PHP8.5.1043.174 ms43.549 ms42.588–49.033 ms
JavaOpenJDK 26.0.2.149.394 ms48.153 ms46.995–53.066 ms
HHY Bytecode1.3.1055.297 ms53.404 ms51.027–78.702 ms
Python3.14.781.747 ms86.459 ms79.993–87.774 ms
ComparisonRound 1Round 2Interpretation
HHY / PHP1.28×1.23×HHY uses about 23%–28% more wall time on this task
HHY / Java1.12×1.11×Java is slightly faster including fresh-JVM startup
HHY / Python0.68×0.62×HHY takes less wall time on this task
HHY / Lua3.01×2.97×Lua is faster on this integer loop
HHY / Go6.92×6.70×Precompiled Go remains substantially faster
This is one CPU/materialization workload, not a general language ranking. Each implementation uses an idiomatic loop and distinct container; Java uses HashSet/ArrayList and includes fresh-JVM startup. All 84 timed samples, versions, orders, and sources are retained locally under performance-analysis/2026-09-01-v1.3.10-language-comparison/, which project policy excludes from GitHub.

25.8Governance conclusion and watch list

  • Overall status: v1.3.10 is formally released; v1.3.7–v1.3.10 semantic, three-path, Profiler, resource, and cache-governance gates are complete.
  • Engine policy: Bytecode is default; AST remains permanently available as the semantic oracle, differential-test engine, and --engine ast emergency fallback.
  • Performance conclusion: final CI reports 0.3695 for the 1M CPU ratio, 1.0088 for short tasks, 1.0207 for sustained JSON, and 1.0269× Profiler overhead; all gates pass.
  • Cache conclusion: compile+verify is not a major cold-run cost; no cache is introduced until new data and a complete threat model trigger review.
  • Cross-language conclusion: this same-machine fixed task has Go/Lua/PHP/Java ahead of HHY and HHY ahead of Python; it cannot be generalized to all workloads.
  • Update rule: synchronize this report whenever the release baseline, measurement method, engine/cache decision, or overall risk conclusion changes.

Open final v1.3.10 continuous-verification evidence
Four-platform builds, sanitizers, fuzzing, Profiler, cache decision, performance gates, and practical-project acceptance.
https://github.com/hh696-wq/hhy-vm/actions/runs/33497617218

HHYHHY Language

A flow-first scripting language for system automation.

© 2026 HHY Language contributors
LearnQuick StartComplete ManualCLI Reference
ProjectAbout HHYGitHubSpecApache 2.0
Contact hhylang.dev huiyang.hou@qq.comhouhuiyang.com