Implementation: awk/ crate (~13.7 kloc): main.rs (99), program.rs (335),
compiler.rs (4482), grammar.pest (406), regex.rs (171, wraps plib::regex
with RegexFlags::ere()), interpreter/{mod.rs 1046, format.rs 2696, builtins.rs 412, io.rs 618, record.rs 235, value.rs 251, stack.rs 387, string.rs 142, array.rs 338}.
Tests: awk/interpreter/tests.rs (1711) + awk/tests/integration.rs (832).
Spec: POSIX.1-2024 (IEEE Std 1003.1-2024), Vol. 3 §3, pp. 2653–2691.
Reference slice: ~/tmp/posix.2024/sliced/xcu-shell-and-utilities/3-utilities/awk.md (lines 85158–86827).
Date: 2026-06-05
Method: spec read in full; implementation read paired with spec; every
Critical/Major finding confirmed by building target/release/awk and running it
(behavioral evidence inline). No code was modified.
The language core is broad and largely correct: expressions, numeric-string
comparison, special variables, FS/RS modes (including paragraph mode and
regex/multi-char extensions), field/$0 rebuild with OFS, NF truncate/extend,
print/printf redirection and pipes, getline forms, OFMT/CONVFMT separation, the
arithmetic and most string builtins, gsub/sub & handling, srand-returns-prior-seed,
exit, next/nextfile, user functions/recursion, arrays, ENVIRON/ARGV/ARGC,
and locale/gettext setup are all present and behave per spec. The conformance
gaps are concentrated and concrete: close() crashes the interpreter when used
as an expression (Critical); -F sepstring skips the escape processing that
-v FS= performs so -F '\t' does not mean tab (Major); length/index/
match/RSTART/RLENGTH count bytes, not characters (Major); printf *
dynamic width/precision is unimplemented (Major); -f - does not read the
program from stdin (Major); uninitialized/empty/nonexistent fields do not
compare numerically equal to 0 (Major); and a hard 1024-field cap silently
drops fields (Major).
- #1 —
close()panics ("empty stack") when used as an expression and never returns a status.awk/interpreter/mod.rs:554-562pops the filename but pushes no return value;BuiltinFunction::Closeis otherwise treated as a value-producing call, so any expression use underflows the operand stack. Verified:awk 'BEGIN{print "x">"/tmp/f"; print close("/tmp/f")}'→panicked at awk/interpreter/stack.rs:205:36: empty stack;r=close("/tmp/f")→ panic atstack.rs:237. Bare-statement use (close(f)alone) works. POSIX 85911-85915: close "If the close was successful, the function shall return …" a status (0/non-zero). Fix: push the close result (0 on success, non-zero on error) likefflushdoes atmod.rs:580. ✓ Fixed: the fourclose_*I/O helpers now returnOption<i32>and theCloseopcode pushes 0 (success) / non-zero (e.g. not open); regression testawk/tests/awk/close_returns_status.{awk,out}+test_awk_close_returns_status.
-
#2 —
-F sepstringdoes not apply escape-sequence processing.awk/interpreter/mod.rs:843-847assigns the-Fargument to FS verbatim (AwkString::from(separator)), bypassing theescape_string_contents()used for-vassignments (mod.rs:799) and assignment operands (mod.rs:903). POSIX 85182-85187:-F sepstring"shall be equivalent to-v FS=sepstring". Verified:awk -F '\t' 'BEGIN{print length(FS)}'→2(literal\t), butawk -v 'FS=\t' 'BEGIN{print length(FS)}'→1(a real tab);printf 'a\tb\n' | awk -F '\t' '{print $2}'prints nothing. The ubiquitous-F'\t'idiom is broken. Fix: runseparatorthroughescape_string_contentsbefore assigning FS. ✓ Fixed:interpret()now wraps-Finescape_string_contents; testtest_awk_dash_f_escape_processing. -
#3 —
length,index,match(and RSTART/RLENGTH) count bytes, not characters.lengthusesvalue_str.len()(builtins.rs:288),indexusesstr::findbyte offset (builtins.rs:273-278), andmatchderives RSTART/RLENGTH fromplib::regexbyte offsets (builtins.rs:140-152).substrandsplit("")correctly usechars()(builtins.rs:336,record.rs:64), so the implementation is internally inconsistent. POSIX 85859-85868 and APPLICATION USAGE 86394-86396: "the awk versions deal with characters, while the ISO C standard deals with bytes." Verified on a 3-character / 4-byte recordAÉB:length→4(want 3),index($0,"B")→4(want 3),match($0,/B/)RSTART→4(want 3). Fix: count characters (chars().count(), char indices) in these functions and translate regex byte offsets to char offsets. ✓ Fixed:lengthuseschars().count();index/matchconvert byte offsets viabyte_offset_to_char_count; testtest_awk_multibyte_char_counts. -
#4 — printf/sprintf
*dynamic field width/precision is unimplemented.parse_conversion_specifier_args(awk/interpreter/format.rs) only consumes from the format-string iterator;sprintf(builtins.rs:38) has no path to fetch a width/precision argument from the value list. POSIX EXTENDED DESCRIPTION item 4 (85798-85800): "A field width or precision can be specified as the*character … the next argument … shall be fetched and its numeric value taken as the field width or precision." Verified:awk 'BEGIN{printf "%*d\n",5,42}'→runtime error: unsupported format specifier '*'(same for%.*f,%-*d). Fix: when*is seen in the width/precision position, pull the next expression argument as an integer. ✓ Fixed:FormatArgsrecordswidth_star/precision_star;sprintffetches width then precision then value (negative width → left-justify, negative precision → omitted); testtest_awk_printf_star_width. -
#5 —
-f -does not read the program from standard input.awk/main.rs:64-77opens every-fargument withstd::fs::File::open, which fails on-. POSIX 85188-85192: "A pathname of-shall denote the standard input." Verified:echo 'BEGIN{print 1}' | awk -f -→Error: "could not open file '-'". Fix: special-case-to readstdin(and concatenate in order with other-ffiles). ✓ Fixed: the-floop readsstdinwhen the arg is-; testtest_awk_program_file_from_stdin. -
#6 — Uninitialized / empty / nonexistent fields do not compare numerically equal to 0. Two causes: (a) the compare macro at
awk/interpreter/stack.rs:362-371explicitly excludes field refs (!lhs.ref_type.is_field() && !rhs.ref_type.is_field()) from the "Number vs UninitializedScalar → numeric" rule, dropping uninitialized fields to the string-compare fallback (stack.rs:378-380); (b) empty fields are stored as empty strings (record.rs:119,198, viamaybe_numeric_string("")) rather than the uninitialized value. POSIX 85481 (numeric comparison "if one is numeric and the other has the uninitialized value"), 85506 (nonexistent fields "shall evaluate to the uninitialized value"), 85511 (empty fields from$0/FS "shall have the uninitialized value … considered a numeric string"). Verified:echo 'a b' | awk '{print ($5==0)}'→0(want 1);echo 'a::b' | awk -F: '{print ($2==0)}'→0(want 1); scalar/array uninitialized compares (x==0,a["k"]==0) correctly give 1, confirming the divergence is field-specific. Fix: treat field-ref uninitialized values like scalar uninitialized values in comparison, and create empty fields as the uninitialized value. ✓ Fixed: removed theis_field()guard incompare_op!(and the now-unusedAwkRefType::is_field);record.rsmake_fieldstores empty fields as the uninitialized value; testtest_awk_uninitialized_field_comparison. -
#7 — Hard 1024-field cap silently truncates records and errors on high field assignment.
awk/interpreter/record.rs:103(MAX_FIELDS = 1024); split silently drops fields past 1024 (record.rs:115-117,194-196) andis_valid_record_index(record.rs:229-235) rejects any$nwithn>1024. POSIX places no such low fixed limit and the spec model is dynamic. Verified: a 1100-field record →NFreports1024(76 fields lost with no diagnostic);echo x | awk '{$2000="z"}'→runtime error: invalid field index. Fix: grow the fields vector dynamically (or raise the cap well beyond {LINE_MAX}-implied field counts and never silently drop). ✓ Fixed: field storage is nowRefCell<Vec<Box<…>>>grown on demand to au16::MAXceiling (boxed cells keep stack-held field pointers valid across growth; surplus fields are cleared, not dropped, for soundness). Teststest_awk_record_with_many_fields,test_awk_high_field_assignment.
-
#8 —
substr(s,m,n)withm<1keepsncharacters from position 1 instead of fromm.builtins.rs:332clampsmto1.0thenbuiltins.rs:336takesnchars, so leading positions below 1 are not counted againstn. POSIX 85895-85898 defines the result as "the at most n-character substring … that begins at position m". Verified:substr("hello",-1,3)→helandsubstr("hello",0,2)→he; nawk/gawk givehandh. The code comment acknowledges the<1case is "not specified", so this is a divergence-from-common-behavior rather than a hardshall; remaining substr edges (m/nfractional truncation, negativen→empty, over-longnclamp) are correct. ✓ Fixed: substr now computes the character window[max(m,1), m+n)clamped to the string, so out-of-range leading positions consumen; testtest_awk_substr_edges. -
#9 — Division / modulo by zero yields
inf/-nanwith no diagnostic and exit 0.awk 'BEGIN{print 1/0}'→inf(exit 0);1%0→-nan. POSIX 85426-85428 makes the result undefined (ISO C error case), so this is conforming-but-surprising; most awks emit a fatal "division by zero" diagnostic. Consider emitting a diagnostic and non-zero exit. Left as-is (intentional): current behavior is POSIX-conforming (undefined →inf/-nan); not changed to avoid breaking programs that rely on the IEEE result. -
#10 —
tolower/toupperuse Unicode default case mapping, not theLC_CTYPEmapping.builtins.rs:339-349call Rustto_lowercase()/to_uppercase(). POSIX 85899-85906 ties the mapping to "the LC_CTYPE category of the current locale". Practical impact is small (ASCII identical); flagged for strict-conformance completeness, consistent with theplib::localedirection taken indev/audit.md. ✓ Fixed: addedplib::locale::to_lower/to_upper(libctolower/towlower); awk maps each character through them. Testsplib locale::tests::to_lower_upper_ascii,test_awk_case_mapping. -
#11 —
cmd | getlinedoes not set NR.awk/interpreter/mod.rs:598-622sets neither NR nor FNR for the file and pipe getline forms. The POSIX 2024 text forexpression | getline [var](85922-85933) is silent on NR, so this conforms to the letter, but historical awk and gawk increment NR for the pipe form. Verified:awk 'BEGIN{"echo hi"|getline x; print NR}'→0. Document or align with historical behavior. ✓ Fixed: theGetLineFromPipebranch now advances NR (not FNR) on a successful read, matching gawk;getline < filestill touches neither. Testtest_awk_getline_pipe_advances_nr. -
#12 —
split(s,a,"")performs a per-character split.record.rs:76-77maps an empty FS toNull→ one element per character. POSIX 85876 calls a nullfs"unspecified", so this is conforming; worth a one-line doc note since it is an observable choice (split("abc",a,"")→ 3 elements). Likewise FS="" for record field splitting is the same unspecified-but-defined char split. ✓ Documented: comment added at theFieldSeparator::Nullconstruction noting the gawk-compatible per-character behavior. -
#13 — clap exposes
--help/-h/--version/-V.awk/main.rs:24-45. These are non-POSIX but standard and harmless.--end-of-options and unknown-option rejection are handled by clap (both verified working). No action required beyond noting the extension surface.
- Both synopsis forms supported — program-as-first-operand and
-f progfile—main.rs:64-94. -
-F,-f(repeatable,ArgAction::Append),-v(repeatable) parsed —main.rs:27-42. -
-fconcatenation in order forms one program —main.rs:65-77. -
--end-of-options handled (clap) — verifiedawk -- 'BEGIN{print 1}'→1. -
-f -routed to stdin (#5 ✓ fixed) —main.rs:64-77.
| Opt | Status | Notes (file:line) |
|---|---|---|
-F sepstring |
CONFORMS | (#2 ✓ fixed) escapes processed via escape_string_contents; -F '\t' is a tab. |
-f progfile |
CONFORMS | (#5 ✓ fixed) Multiple/concatenation OK; - reads stdin. |
-v assignment |
CONFORMS | main.rs:37-42; applied before BEGIN (mod.rs:836-841), escapes processed (mod.rs:799), numeric-string tagged. Verified -v x=5 and -v 'x=a\tb'. |
-
programis the first operand when no-f—main.rs:84-93. -
var=valueassignment operand detection (name=[A-Za-z_][A-Za-z0-9_]*then=) —mod.rsparse_assignment. Verifiedawk '{print v,$0}' v=hi file. - Assignment operand escapes processed and numeric-string tagged —
mod.rs:903. Verifiedv=10then(v>5)→1. - Assignment processed "just prior to" the following file; before END if after last file — argv loop
mod.rs:868+. -
-file operand → stdin at that position — argv loop. - STDIN used only when no file operands or
-; empty program (no rules/END) exits 0 without reading —mod.rs:864-866. Verifiedecho data | awk ''→ exit 0. -
{LINE_MAX}-and-beyond records supported (records read fully, no line cap) —io.rsrecord readers.
| Var | Status | Notes |
|---|---|---|
LANG/LC_ALL/LC_COLLATE/LC_CTYPE/LC_MESSAGES |
CONFORMS (setup) | setlocale(LC_ALL,"") at main.rs:58; regex/collation via libc-backed plib::regex. tolower/toupper are the one LC_CTYPE gap (#10). |
LC_NUMERIC |
CONFORMS | Program literals always use . (lexical C format); numeric output radix via format.rs locale decimal_point, matching 85271-85276. |
NLSPATH (XSI) |
CONFORMS (via gettext) | textdomain/bind_textdomain_codeset — main.rs:59-60. |
PATH |
CONFORMS | system() uses libc system(3) (builtins.rs:354-369), inheriting PATH. |
ENVIRON array |
CONFORMS | Populated from std::env::vars(), numeric-string tagged — mod.rs:823-825. Verified ENVIRON["FOO"]. |
- Default — spec says "Default" (85282); no special handling required.
- print/printf write stdout by default —
builtins.rs:371-376. - Diagnostics to stderr, gettext-wrapped —
main.rs:51,68-71,95; pipe-close warningsio.rs.
- Nature depends on program (redirections/pipes) —
mod.rs:520-552,io.rs.
- Full precedence ladder: grouping,
$, pre/post++/--,^(right assoc,pow), unary!/+/-,* / %(fmod),+ -, concatenation, relational,~/!~,in,&&,||,?:, all assignment ops. Verified2^10→1024,7.5%2→1.5,1?"y":"n"→y,"a" "b" 1+2→ab3. - String→number conversion per 85356-85374 (
strtod-style, leading-numeric) —mod.rs:59-66. - Number→string: integers via
%d, others via CONVFMT (85375-85381) —value.rs:88-106. Verified integer bypass and CONVFMT concat. - Numeric-string sources (fields, getline, FILENAME, ARGV, ENVIRON, split, cmdline assign) tagged —
mod.rs:78-82,814-825,record.rs:43. - Comparison rule (numeric if both numeric / numeric+numeric-string / both numeric-strings / numeric+uninitialized) —
stack.rs:345-381. Verified"10"==10→y," 10 "==10→n, field10>9→1. - Field uninitialized comparison (#6 ✓ fixed) —
stack.rscompare_op!,record.rsmake_field. - Boolean context (zero/
""false) — verified viaif,?:, patterns.
- Associative, auto-vivify on reference,
indoes not create — verified("z" in a)→0. - Multi-dim subscript via SUBSEP;
(i,j) in a; subscript uses CONVFMT — verifieda[1,2]+split(k,p,SUBSEP);CONVFMTin subscript →0.12. -
delete a[i]/delete a(whole array) — verifiedlength(a)→0 afterdelete a. -
for (k in a)iterates indices — verified count 2. -
length(array)→ element count —builtins.rs:283-285. Verified →3.
| Var | Status | Notes |
|---|---|---|
ARGC/ARGV |
CONFORMS | ARGV[0]="awk", ARGC=count; modifiable (verified injecting a file via ARGV/ARGC). mod.rs:814-821. |
CONVFMT |
CONFORMS | default %.6g; used for number→string (value.rs). |
ENVIRON |
CONFORMS | see ENV table. |
FILENAME |
CONFORMS | set per file; - for stdin. |
FNR/NR |
CONFORMS | reset per file / cumulative; getline plain & getline var bump both (mod.rs:589-592). |
FS |
CONFORMS (value) | default space; see FS modes. (-F escape gap is #2.) |
NF |
CONFORMS | recomputed on split; assignment truncates/extends (mod.rs/record.rs:135-183). Verified NF=2 truncate, $4= grow. Capped at 1024 (#7). |
OFMT |
CONFORMS | print of non-integers; integers bypass. Verified OFMT="%.2f"→3.14, print 100→100. |
OFS/ORS |
CONFORMS | print separators; OFS on $0 rebuild. Verified OFS="-"; $1=$1. |
RLENGTH/RSTART |
CONFORMS | set by match; character-based (#3 ✓ fixed). No-match → 0/-1. |
RS |
CONFORMS+ext | first-char separator; "" paragraph mode; multi-char/regex accepted (spec leaves multi-char unspecified — conforming extension). Verified all three. |
SUBSEP |
CONFORMS | multi-dim subscript join. |
- FS modes: space-default (strip leading/trailing blanks+newlines, split on runs), single-char, regex/multi-char —
record.rs:44-86. Verified all, incl.FS="\t",FS="[0-9]". - RS modes incl. paragraph (
RS="", newline always a field sep) —io.rs+mod.rseffective-FS. Verified. - Assigning
$nrebuilds$0with OFS; assigning$0re-splits;$(NF+k)grows NF with intervening uninitialized fields —record.rs:135-205. Verified. - Empty fields carry the uninitialized value (#6 ✓ fixed) —
record.rsmake_field. - Dynamic field count (#7 ✓ fixed) —
record.rsgrow-on-demand boxed cells.
- ERE via
plib::regexRegexFlags::ere()— libc-backed POSIX ERE (regex.rs:84). Verified anchors^…$, alternation, intervals{2}, bracket class[[:alpha:]],~/!~, dynamic regex from a string variable. - C-style escapes in STRING/ERE (
\\ \a \b \f \n \r \t \v,\dddoctal) —compiler.rs. Verified octal\101→A. -
\xhex escapes correctly NOT supported (spec RATIONALE 86527; only octal) — rejected at lex. - Bare ERE in expression context ≡
$0 ~ /ere/— grammar/compiler. - Record-separator-not-matchable-in-
$0semantics — record reader strips terminator before fielding.
-
pattern { action }, missing pattern = always, missing action ={ print }—program.rs/compiler. - BEGIN/END (multiple, ordered; END-before-BEGIN allowed; BEGIN-only exits without reading; END forces input read) —
mod.rs:849-866+ end loop. Verified empty-program and exit paths. - Expression patterns (Boolean) and range patterns
e1,e2— compiler +range_pattern_started(mod.rs:833). -
if/else,while,do…while,for,for (k in a),break,continue— verified do-while, recursion. -
next(verified skips record),nextfile(verified abandons file),delete,exit [expr]. -
exitruns END once then terminates; exit code from expr — verifiedexit 2+ END → rc 2;exit 3→ rc 3.
-
printlist joined by OFS, terminated by ORS, numbers via OFMT; empty list =$0—builtins.rs:390-411. Verified bareprint. -
printf/sprintf:%d %i %o %x %X %u %e %E %f %F %g %G %a %A %c %s %%; flags/width/precision —builtins.rs:26-118,format.rs. Verified%i,%x,%o,%e,%8.2f,%dlarge. -
%c: numeric→char-by-value, string→first char (builtins.rs:85-101). Verified65→A,"hello"→h. - Redirection
>(truncate-once-then-append),>>(append),| cmd(popenw) —mod.rs:520-552,io.rs. Verified truncate-then-append, cross-run>>,print|"sort". -
*dynamic width/precision (#4 ✓ fixed) —format.rsFormatArgs,builtins.rssprintf. - Insufficient args → error (spec "undefined"; erroring is acceptable) —
builtins.rs:45-46. Extra args ignored (awk printf does not cycle) — verified.
-
atan2 cos sin exp log sqrt int rand srand—builtins.rs:237-264,mod.rs:623-639.inttruncates toward 0;rand∈[0,1);srandreturns previous seed (verifiedsrand(1); print srand(2)→1), reseeds deterministically.
| Func | Status | Notes |
|---|---|---|
gsub/sub |
CONFORMS | count returned; &/\&/\\ replacement handled (builtins.rs:154-228). Verified gsub(/b/,"[&]")→a[b]c, \\&→a[&]c, count gsub(/a/,…)→3. |
index |
CONFORMS | (#3 ✓ fixed) character position via byte_offset_to_char_count. |
length |
CONFORMS | (#3 ✓ fixed) chars().count(). Bare length = length($0); length(array) works. |
match |
CONFORMS | (#3 ✓ fixed) RSTART/RLENGTH character-based. No-match 0/-1 correct. |
split |
CONFORMS | clears array; default FS / regex / single-char; numeric-string tagging (verified 10>9→1); empty-fs char split (#12). |
sprintf |
CONFORMS | (#4 ✓ fixed) * width/precision supported. |
substr |
CONFORMS | (#8 ✓ fixed) m<1 window math; char-based, fractional truncation, negative-n→empty, over-long-n clamp. |
tolower/toupper |
CONFORMS | (#10 ✓ fixed) plib::locale LC_CTYPE mapping. |
| Func | Status | Notes |
|---|---|---|
close |
CONFORMS | (#1 ✓ fixed) returns 0/non-zero; mod.rs:554-579, io.rs close helpers. |
fflush |
CONFORMS | returns 0/-1; no-arg flushes all (mod.rs:563-581). |
system |
CONFORMS | libc system(3); returns exit status / 128+sig (builtins.rs:354-369). |
getline |
CONFORMS | sets $0,NF,NR,FNR (verified NF=3/NR=2/FNR=2). |
getline var |
CONFORMS | sets var,NR,FNR; not $0/NF (verified). |
getline < file |
CONFORMS | sets $0/NF (or var); not NR/FNR (verified NR=0). |
cmd | getline [var] |
CONFORMS | (#11 ✓ fixed) advances NR (not FNR), like historical awk. |
| getline returns 1/0/-1 | CONFORMS | verified missing-file→-1, EOF→0, success→1 (mod.rs:608-621). |
- Keyword and BUILTIN_FUNC_NAME token sets, NAME vs FUNC_NAME (
(adjacency), two-char tokens,/-vs-ERE ambiguity —grammar.pest+compiler.rs. - String/ERE constant escape handling, comments, newline-continuation, optional newlines after
,/{/&&/||/do/else/)— compiler. - Hex numeric constants optional (85333 "may"); not provided — conforming N/A. Verified
0x1A→0. - User functions: forward reference, by-value scalars / by-reference arrays, excess params as locals, recursion — verified
f(5)→120.
- 0 on success, >0 on error;
exit [expr]overrides —main.rs:47-54,98,mod.rsexit propagation. Verified. - Compile/runtime errors → diagnostic to stderr + exit 1 —
main.rs:47-55. - Division/modulo by zero: no diagnostic, exit 0 (#9) — arithmetic ops.
- Inaccessible file operand → diagnostic + terminate — file readers in
io.rs/mod.rs.
- i18n:
setlocale(LC_ALL,"")+ gettext domain wired (main.rs:58-60); diagnostics gettext-wrapped;tolower/toupperhonor LC_CTYPE (#10 ✓ fixed). - Regex flavor: ERE (correct for awk) via libc-backed
plib::regex. - Signals: Default (non-interactive) — no handlers required.
- Character vs byte:
length/index/matchnow character-based, consistent withsubstr/split(#3 ✓ fixed).
Existing tests (interpreter/tests.rs 1711, tests/integration.rs 832) cover the
golden language paths well (operators, builtins, getline, printf, arrays, regex,
srand-prior-seed). Gaps that map to findings — add tests that:
- assert
print close(f)/r=close(f)return a status without panicking (#1). ✓test_awk_close_returns_status - assert
-F '\t'yields a single-char tab FS (length(FS)==1) (#2). ✓test_awk_dash_f_escape_processing - assert
length/index/match/RSTART/RLENGTH on a multibyte record return character counts (#3). ✓test_awk_multibyte_char_counts - exercise
printf "%*d","%.*f","%-*d"(#4). ✓test_awk_printf_star_width - exercise
awk -f -reading the program from stdin (#5). ✓test_awk_program_file_from_stdin - assert
$5==0and empty$2==0are true (#6). ✓test_awk_uninitialized_field_comparison - assert records with >1024 fields keep all fields and
$2000=…works (#7). ✓test_awk_record_with_many_fields,test_awk_high_field_assignment - pin
substr("hello",-1,3)behavior (#8). ✓test_awk_substr_edges
- PR A — "close() returns a status (no crash)": #1. Push close result; add expression-context tests. Smallest fix that removes a Critical crash.
- PR B — "argv/option conformance": #2 (
-Fescape processing), #5 (-f -stdin). Both inmain.rs/interpret()option wiring. - PR C — "characters, not bytes": #3. Convert
length/index/match/RSTART/RLENGTH to character semantics; align withsubstr/split. - PR D — "printf
*dynamic width/precision": #4.format.rs+sprintfarg fetch. - PR E — "field uninitialized value semantics": #6.
stack.rsfield-ref comparison +record.rsempty-field creation. - PR F — "dynamic field count": #7. Remove/raise
MAX_FIELDS; never silently drop. - PR G — "edge-case polish": #8 (substr
m<1), #9 (divide-by-zero diagnostic), #10 (LC_CTYPE case mapping), #11 (cmd|getlineNR), #12 (document null-FS split).