rust-awk#
A GNU awk-compatible text-processing tool written in Rust.
Status#
242/242 tests passing (100%) — BASIC_TESTS from the upstream GNU gawk 5.3.2 test suite. Each test runs both rust-awk and the reference gawk against the same script/input in a sandbox and diffs the output byte-for-byte.
Usage#
Run a single upstream test:
nix build .#checks.x86_64-linux.rust-awk-test-{name}
View a failing test's log:
nix log .#checks.x86_64-linux.rust-awk-test-{name}
Batch-run every test in a single evaluator (much faster than looping):
nix build .#checks.x86_64-linux.rust-awk-test-* --keep-going --no-link
The binary is available as awk from pkgs.rust-awk (release build) or
pkgs.rust-awk-dev (debug build, faster compile). It installs gawk as
an alias.
Architecture#
Seven source modules:
ast— expression and statement node types.lexer— tokenizer with regex-literal bracket-class state and context-sensitive/=disambiguation.parser— expression/statement parser with gawk-compatible operator precedence and getline grammar.value—Valueenum (Num,Str,StrNum,Uninitialized) with comparison and coercion rules.format— printf/sprintf engine: width, precision, flags, all conversion specifiers,OFMT/CONVFMT, char-count%swidth.interpreter— execution engine: rules, statements, builtins, I/O, array-aliasing call frames, record splitting, BSD random port.main— argv parsing, source loading, pipeline wiring.
Features#
Expression semantics#
- Full expression precedence matching gawk, including
?:,in, match/not-match, concatenation-by-juxtaposition, pre/post-inc on fields. StrNumvalue type for input strings and fields: numeric comparison when both sides are numeric-looking, string comparison otherwise.CONVFMT/OFMTapplied during string coercion andprint.$field-ref with unary/prefix operators ($+i++,$-x).
Arrays#
- Reference semantics via per-frame alias map. Multiple function parameters sharing the same caller array all see each other's writes. Uninitialized variables passed by reference promote to arrays in the caller when the callee uses them as arrays.
- Scalar/array conflict detection with provenance chains — when a function parameter is used as scalar while a sibling alias uses it as array, the error names the origin variable.
asort/asorti,delete array,delete array[key],inmembership, for-in iteration with deterministic ordering.
Regex#
- awk-style literal lexing:
/inside[...]doesn't terminate; a leading](or]after^) stays literal;/=ambiguous-case look-ahead picks regex vs compound-assign based on surrounding tokens. - Compatibility fixups for the Rust regex crate: octal
\ddd→ hex,\xHHpre-expansion,[inside a class auto-escaped (unless introducing[[:class:]]), POSIX collating[.c.]rewritten, leading--range preserved via hex, quantifier-after-anchor treated as literal. - POSIX named character classes (
[:alpha:], etc.). - Used by
~/!~,match,sub/gsub/gensub,split,FS/RS.
Record and field handling#
RSas literal, single char, empty (paragraph mode), or multi-char regex. Zero-width regex match treated as no-split.RTpopulated with the actual matched separator per record.FSas single char, literal, or regex;FS=" "default whitespace mode.NFread/write rebuilds$0viaOFS.- Paragraph mode skips leading newlines and preserves trailing
newlines as the last record's
RT.
Input / output#
- Byte-preserving I/O. Input decodes bytes 0–255 to Unicode chars U+0000–U+00FF so non-UTF-8 data round-trips exactly. Output writes chars U+0000–U+00FF as single bytes and higher code points as UTF-8, matching gawk's C-locale byte-level semantics in the test sandbox.
getlineforms:getline,getline var,getline < file,getline var < file,cmd | getline,cmd | getline var. Pipe reads honor the currentRSby caching the full output and splitting on first access, so paragraph-mode pipe reads see every paragraph.print/printfwith redirection (>,>>,|).close(),system(), pipe lifecycle tied to child processes.
printf / sprintf#
- All standard specifiers:
%d,%i,%o,%x,%X,%e,%E,%f,%F,%g,%G,%s,%c,%%. - Width, precision, flags (
-,+,0,#, space), dynamic*width/precision. %swidth uses character count (not byte count) so U+FFFD from lossy-decoded input pads to the same visual width as gawk's byte count on the pre-decode bytes.%cprefers first char of a string argument over numeric conversion.- Infinity/NaN formatting,
%#gkeeping trailing zeros,%#fforcing decimal point.
Random numbers#
srand()/rand()use a port of gawk's bundled BSDrandom()(support/random.c): TYPE_4 with 63-element LFSR, Park–Miller seed-expand, 512-slot shuffle buffer. Produces bit-identical output to gawk for any seed.
Errors and diagnostics#
- gawk-format syntax errors with source line and caret position.
- Runtime regex errors for dynamic patterns include
(FILENAME=.. FNR=..) fatal: invalid regexp: Trailing backslash: /regex/wording, matching gawk's per-record reporting. - Lvalue checks: assignment to post-increment field (
$i++ = 3) is rejected with the caret at the end of the rhs expression. - Array-scalar conflicts, function-as-variable misuse, space between
function name and
(,NF = negative,NULin source code, compile-time division by zero.
gawk-specific grammar corners#
- Adjacent-paren function calls (
callparam). getline $target++—opt_incdecabsorbed into the getline target, matching the$ non_post_simp_exp opt_incdecproduction.print a /regex/concatenation with a regex literal right after an identifier or other value-like token.$/= b/parses as$(regex)thanks to the/=look-ahead.