Skip to content

Implement gawk array sorting and type introspection builtins#504

Merged
bertysentry merged 20 commits into
mainfrom
454-implement-gawk-array-sorting-and-type-introspection-builtins
Jul 15, 2026
Merged

Implement gawk array sorting and type introspection builtins#504
bertysentry merged 20 commits into
mainfrom
454-implement-gawk-array-sorting-and-type-introspection-builtins

Conversation

@bertysentry

Copy link
Copy Markdown
Contributor

Closes #454

What changed

GawkExtension, enabled by default (in new Awk() and the CLI when no -l is given), implementing:

  • asort(source [, dest [, how]]) / asorti(...) — gawk array sorting with the predefined comparison modes (@ind_str_asc, @val_num_desc, @val_type_asc, @unsorted, …), honoring IGNORECASE
  • typeof(x) — gawk type categories (number, string, strnum, array, regexp, number|bool, unassigned, untyped)
  • isarray(x), mkbool(x), and gensub(re, repl, how [, target]) (gawk semantics: any g/G-prefixed selector is global, \N backreferences)
  • PROCINFO["sorted_in"]-controlled for (i in a) traversal, re-read at each loop so mid-execution changes apply
  • Honest SYMTAB (script globals + Jawk special variables, snapshot) and FUNCTAB (user-defined functions + extension keywords) for scripts that reference them

Extension SPI — gawk-specific behavior lives entirely in the extension; the core gained neutral hooks:

  • @JawkBeforeStart — setup method run after globals are allocated, before execution
  • @JawkRawValue — parameter receives the runtime value uncoerced (how typeof/isarray see untyped state); backed by new PEEK_DEREFERENCE / PEEK_ARRAY_ELEMENT_RAW opcodes that observe without autovivifying
  • @JawkRegexp — parameter keeps regexp literals as precompiled patterns
  • Method-level @JawkAssocArray({...}) for array positions consumed through varargs
  • ForInKeyOrder — extension-provided for-in traversal order; the default path is unchanged (single keySet() copy, null-check only)

Interpreter fixes riding along:

  • Regexp literal as an ordinary expression now evaluates as $0 ~ /re/ per POSIX (x = /re/ yields 0/1)
  • Typed regexp literals (@/re/) accepted; rejected under --posix
  • Calling a user function with more arguments than declared is a gawk-style runtime warning instead of a compile error; extra expressions are evaluated for side effects then discarded (bare variables are peeked, preserving untyped state)
  • Runtime warnings go to a dedicated stderr-backed stream — never into the script output an embedding host captures (CLI routes it to its stderr; errorStream(...) captures it in the Java API)
  • AVM.getVariable(name) falls back to -v/API-supplied initial variables, so e.g. -v IGNORECASE=1 is honored even when the script never references it
  • sub()/gsub() replacement escaping is shared with gensub via RegexRuntimeSupport

Reviewer notes

  • Extension state is per-engine: the default set instantiates a fresh GawkExtension per Awk; the singleton INSTANCE pattern was removed because AbstractExtension holds per-engine vm/jrt bindings.
  • Serialization: annotation-derived metadata in ExtensionFunction is transient and recomputed from the resolved Method on deserialization, so .tuples files from other versions stay loadable. VariableManager.getVariable(String) is a default method to keep third-party implementors source- and binary-compatible.
  • One intentional test flip: GawkExtensionIT.test_asortsymtab goes from pass to failure. Its .ok fixture only encodes the entry counts of the gawk build's own symbol table (30 globals / 42 builtins); the previous implementation faked those counts with placeholder entries, which corrupted any script actually reading SYMTAB/FUNCTAB. The honest content can't match gawk's internal table sizes.
  • Behavior changes are documented in index.md.vm (gawk departures list), extensions.md, extensions-writing.md, cli.md, cli-reference.md, and java.md — including that gensub/typeof/asort/… become reserved names by default.

Verification

  • mvn verify: 479 unit tests, 0 failures; checkstyle, PMD, SpotBugs clean; mvn compile -P benchmark passes
  • Gawk compatibility vs main baseline: 29 cases error→pass (incl. all 13 representative cases from Implement gawk array sorting and type-introspection builtins #454), 10 error→failure (now run, output differs — follow-up candidates), 1 pass→failure (asortsymtab, intentional, see above), zero other regressions across POSIX/BWK/gawk suites

🤖 Generated with Claude Code

Add GawkExtension, enabled by default, providing asort(), asorti(),
typeof(), isarray(), mkbool(), and gensub(), with runtime support for
PROCINFO["sorted_in"] traversal order, IGNORECASE-aware sorting, and
gawk's typed/untyped value distinctions.

Extension SPI additions: @JawkBeforeStart startup hook, @JawkRawValue
and @JawkRegexp parameter annotations, method-level @JawkAssocArray
positions, and a ForInKeyOrder hook so extensions control for-in
traversal without gawk-specific code in AVM/JRT/AwkParser.

Also fix regexp literal expression semantics (/re/ evaluates as
$0 ~ /re/ per POSIX), accept extra user-function arguments with a
gawk-style runtime warning (evaluated then discarded), route runtime
warnings to stderr instead of the output sink, expose honest
SYMTAB/FUNCTAB content, and keep extension state per-engine.

Closes #454

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry bertysentry linked an issue Jul 7, 2026 that may be closed by this pull request

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a8e20523b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/frontend/AwkParser.java Outdated
Comment thread src/main/java/io/jawk/Awk.java Outdated
bertysentry and others added 2 commits July 8, 2026 12:44
typeof()/isarray() on a missing array element now bring that element
into existence (observable via `in`, delete, for-in), matching gawk.
Achieved by reading array-element raw values through the normal
element dereference, which already returns the untyped marker while
creating the element; the special PEEK_ARRAY_ELEMENT_RAW opcode and
JRT.peekAwkValue are removed as no longer needed.

An explicit empty extension list (new Awk(emptyList) or empty varargs)
is now honored as "no extensions" instead of silently installing the
default set, giving Java hosts a way to reclaim names such as gensub
or typeof. The no-argument constructors still install the default set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tics

Extension SPI:
- Add @JawkOptional for trailing optional parameters (null when omitted);
  asort/asorti/typeof/gensub now declare real signatures instead of
  varargs. @JawkAssocArray is parameter-level only again: the method-level
  indexed form is gone (indexes were unfriendly), and a pure-runtime Map
  check cannot work because a fresh dest variable must be compiled as an
  array reference.
- Make StrNum public: it is the strnum type class and extensions need it.

Diagnostics:
- Replace the per-tuple runtimeWarning fields (CallFunctionTuple,
  ExtensionTuple) with a single WARNING opcode the parser plants before
  the instruction it describes.
- Move the gensub third-argument warning out of the parser into
  GawkExtension itself, where it now checks the actual runtime value
  (variable selectors warn too, like gawk); AVM exposes the source
  description and current call line for extension diagnostics, and
  extension call sites are stamped with the call-site line.

Core cleanups:
- Rename AwkSettings.setAllowArraysOfArrays to setPosix (logic reversed):
  the flag now gates all POSIX compile-time behavior, including typed
  regexp literals.
- Rename JRT.getAwkValue to getAssocArrayValue.
- Single source of truth for special variable names: AVM exposes
  getSpecialVariableNames() backed by the same map getVariable() uses;
  GawkExtension and execMatch consume it.
- HashAssocArray/SortedAssocArray normalize null values to the untyped
  marker so callers never special-case null.
- VariableManager.getVariable is a plain abstract method (implemented in
  the JMH benchmark); sourceBasename uses File.getName; warnings use
  String.format; comparator helpers documented, including why they cannot
  reuse JRT.compare2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a92bcbfc10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/ext/ExtensionRegistry.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
ExtensionRegistry now stores factories instead of instances: resolve()
and listExtensions() return a fresh instance for factory-registered
extensions, so the stateful built-ins (GawkExtension, StdinExtension)
are never shared across engines by programmatic callers. A new
register(String, Supplier) overload lets third parties register
factories for their own stateful extensions; register(String, instance)
keeps its sharing semantics. The CLI's GawkExtension clone is obsolete.

SYMTAB now observes initialized runtime globals: the SET_NUM_GLOBALS
handler consumes the runtime-managed preamble tuples (ENVIRON, ARGC,
ARGV offsets) before running the beforeStart hooks, so the gawk
extension's SYMTAB snapshot sees their real values instead of untyped
placeholders. The tuple stream stays the source of truth, which keeps
sandboxed programs (which omit the ARGV tuple on purpose) intact.
ARGC/ARGV also join AVM's special-variable map, answered through their
lazy synthetic accessors, so SYMTAB["ARGV"][0] works in embedded runs
where ARGV is never materialized.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6143c74242

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/intermediate/Opcode.java
Comment thread src/main/java/io/jawk/jrt/HashAssocArray.java
Comment thread src/main/java/io/jawk/ext/GawkExtension.java
bertysentry and others added 4 commits July 9, 2026 01:11
Add PEEK_DEREFERENCE and EXTENSION to the opcodes requiring the eval
global frame: an AwkExpression containing a raw-value extension call
such as typeof(x) had its SET_NUM_GLOBALS initializer optimized away,
crashing on the null globals array. Extension calls also read globals
(e.g. IGNORECASE) and run beforeStart hooks, so they keep the frame too.

gensub() now honors IGNORECASE: when it is nonzero, the pattern is
recompiled with CASE_INSENSITIVE, matching gawk's rule that IGNORECASE
affects all regexp operations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ENVIRON and ARGV preamble tuples are now emitted only when the script
references them, or when it references SYMTAB (whose snapshot exposes
them); unreferenced ones are answered by the synthetic accessors, so
plain scripts no longer pay for building the environment map. ARGC
stays always materialized: it is a single cheap assignment and its slot
must remain authoritative for ARGC=n command-line operand assignments
to affect input traversal. ARGV population derives its count from the
argument list so it no longer depends on the ARGC slot.

IGNORECASE is now a JRT-managed special variable like FS or RS: reads
and writes compile to dedicated PUSH_IGNORECASE/ASSIGN_IGNORECASE
opcodes, the JRT precomputes the boolean on assignment, and every
regexp/sorting consumer tests jrt.isIgnoreCase() instead of coercing
the value per match. -v/API assignments flow through the same setter.

Wiring IGNORECASE surfaced a general parser hole: ++/-- on JRT-managed
specials compiled to slot-based INC/DEC opcodes that never touched the
JRT (gawk's aasort fixture loops on IGNORECASE++ and span forever).
Pre/post increment and decrement of specials now compile to a
read/adjust/assign sequence through the special-variable opcodes, with
correct prefix/postfix value semantics. The duplicated special-variable
push/assign switches are consolidated into single parser-level methods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SYMTAB and FUNCTAB move from GawkExtension into the interpreter, where
they belong: the parser emits UPDATE_SYMTAB/UPDATE_FUNCTAB preamble
tuples only when the script references them outside POSIX mode, and the
AVM populates them from its own metadata (script globals, JRT-managed
specials, host-supplied variables, function names, extension keywords).
This fixes gawk parity for the reported case: -v variables without a
compiled slot now appear in SYMTAB, and command-line name=value operand
assignments update the array live between input files.

The extension beforeStart hooks now run from a dedicated
BEFORE_START_HOOKS opcode emitted by the parser at the end of the
preamble, replacing the AVM's nested preamble-consumption loop, and
execute at most once per AVM: reused interpreters (repeated executions,
expression evaluations) no longer reinitialize their extensions.

The special-variables accessor table is now a static
Map<String, Function<AVM, Object>>, so constructing an AVM allocates
nothing for it. GawkExtension loses all SYMTAB/FUNCTAB code (its
beforeStart hook only registers the for-in ordering), which also
retires the shouldMaterialize/getVariable lookup concern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AVM.getVariable(String) is back to a plain string switch (a compiled
jump table, no map allocation and no lookup indirection); the special
variable names live in a static Set that a unit test keeps in sync with
the switch.

@unsorted now performs no sorting at all: asort()/asorti() skip
Collections.sort entirely (the for-in hook already returned keySet()
directly), and the constant-comparator branch is gone.

JRT.toAssignedScalar is renamed untypedToBlank with documentation of
where the untyped marker comes from (missing-array-element reads) and
why assignments must strip it (x = a[missing] must leave x an assigned
blank scalar, as in gawk). The pointless call on a freshly boxed
Integer in populateArgc is removed; the two real call sites remain,
costing one monomorphic instanceof on the assignment paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 19b1689ece

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/jrt/JRT.java Outdated
Comment thread src/main/java/io/jawk/backend/AVM.java
Comment thread src/main/java/io/jawk/frontend/AwkParser.java
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/backend/AVM.java
bertysentry and others added 3 commits July 10, 2026 01:08
IGNORECASE now follows AWK truthiness (nonzero or non-null, strnum
aware) instead of numeric coercion, so IGNORECASE = "yes" activates it,
matching gawk.

Runtime name=value assignments (command-line operands processed between
input files) now reach the JRT for its managed specials: IGNORECASE=1
or FS=: as an operand takes effect for subsequent files instead of
being written to a global slot or dropped. The duplicated special-
variable dispatch chains in JRT are consolidated into a single
applySpecialVariable(name, value) used by initial assignment, override
application, and the new runtime path. ARGC is excluded (its setter
delegates back to the VariableManager and its slot stays
authoritative).

IGNORECASE now applies to precompiled regexp constants at runtime:
pattern rules (/re/ { ... }), match expressions, sub()/gsub(), and
gensub() all consult a per-JRT cache of case-insensitive pattern twins,
compiled once per pattern, so the matching hot path stays allocation
free. Two gawk IGNORECASE fixtures (ignrcase, ignrcas4) now pass.

Unknown @-prefixed sort modes are fatal, as in gawk, instead of
silently applying the default ordering; names of user-defined
comparison functions (unsupported) fall back to the default ordering
with a one-time warning. symtabOffset is reset with the other
runtime-managed offsets so a reused AVM cannot write operand
assignments into a stale array.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RegexRuntimeSupport is gone: sub()/gsub() replacement now lives in JRT
as replaceFirst/replaceAll instance methods that consult IGNORECASE
internally (no flags parameter to thread around), with the reused
result buffer moving along and exposed through getReplaceResult().
prepareReplacement() becomes a JRT static used by gensub() as well.
The AVM's replaceFirst/replaceAll wrapper indirection is deleted; the
sub/gsub opcode handlers call the JRT directly.

regexpFlags() also moves to JRT next to isIgnoreCase(): dynamic-regexp
call sites compile with jrt.regexpFlags(), while boolean consumers
(sorting, pattern-twin cache) keep isIgnoreCase().

setFilelistVariable() now delegates to assignVariable() after parsing,
so the specials-routing/slot/SYMTAB logic exists in exactly one place;
the operand value is already an input scalar, which assignVariable's
normalization passes through unchanged.

The prepareReplacement unit test moves to io.jawk.jrt and gains
backreference-mode coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ated

The AWK match operator and the match() builtin move into JRT next to
the IGNORECASE state and the pattern-twin cache: jrt.matches(text,
regexp) handles both precompiled regexp constants and dynamic
expressions, and jrt.matchPosition(s, ere) locates the match while
updating RSTART and RLENGTH. The AVM's MATCHES case and execMatch are
now thin delegations, and the interpreter no longer references
java.util.regex.Matcher at all.

The three inline copies of the "special name, but not ENVIRON/ARGV"
expression in the parser now call isJrtManagedSpecialName, whose
documentation explains the distinction: ENVIRON and ARGV are special
names backed by plain global slots, while SYMTAB and FUNCTAB are not
special names at all (ordinary globals populated by the runtime), so
they need no exclusion.

The caseAwarePattern documentation now records why the cache exists:
the JDK's Pattern.compile performs no caching of its own, so every
record matched against a regexp constant would otherwise recompile it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e3fb6eea33

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/ext/GawkExtension.java
Comment thread src/main/java/io/jawk/frontend/AwkParser.java
gensub() no longer aborts when the replacement references a capture
group the regexp does not define: prepareReplacement gains a
group-count-aware variant, and \N beyond the pattern's groups expands
to the empty string, as in gawk. The boolean variant keeps its
semantics for sub()/gsub().

split() and FS field splitting honor IGNORECASE. Tokenizer selection is
consolidated into JRT.splitTokenizer(input, separator), which replaces
the three pasted copies in execSplit, JRT.split, and the field-parsing
path. A regexp-literal separator now stays a precompiled Pattern all
the way to the tokenizer (served by the case-aware twin cache instead
of being stringified), a single-letter separator goes through the
regexp path when IGNORECASE is set (letters are regex-safe, so
POSIX literal single-character semantics are preserved otherwise), and
multi-character separators compile with the IGNORECASE flags.
RegexTokenizer gains Pattern and flags constructors. The gawk splitwht
fixture now passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review once more!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2fc8a65c29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
All four findings verified against gawk 5.0.0 and fixed to match:

- @val_str_* compares every scalar as text, with no numbers-before-
  strings type ranking (the number 2 sorts after the string "10").
- @val_num_* coerces every scalar to a number, non-numeric strings
  counting as 0, and breaks numeric ties with a string comparison;
  subarrays still sort last.
- An empty sort mode ("") behaves like an omitted one instead of
  crashing on charAt(0); handled in effectiveSortMode so both the
  asort()/asorti() and for-in paths get it, which also let
  comparator() drop its redundant null/empty guard.
- asorti() defaults to @ind_str_asc, gawk's actual default: indexes
  are strings, so no type ranking applies and "10" sorts before "2".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 400eb21350

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/backend/AVM.java
Comment thread src/main/java/io/jawk/ext/GawkExtension.java
Comment thread src/main/java/io/jawk/backend/AVM.java
- The SYMTAB snapshot no longer contains the SYMTAB and FUNCTAB
  globals themselves; gawk keeps the meta tables out of the symbol
  table ("SYMTAB" in SYMTAB is 0).
- @ind_num_* now breaks numeric ties with a string comparison, like
  @val_num_*: non-numeric indexes all coerce to 0, so without the
  tiebreak their order leaked the backing map's insertion order.
  Verified against gawk 5.0.0 ({"b","a","0","B"} sorts 0 B a b, and
  _desc fully reverses). The shared comparator is renamed
  compareNumericThenText.
- @JawkBeforeStart docs now state the deliberate contract: hooks run
  once per interpreter instance, not once per execution, because
  extension initialization may be heavy and the AVM/JRT pair is stable
  for the engine's lifetime. The example no longer suggests seeding
  per-run globals there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review once more.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a47c61c850

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/jrt/VariableManager.java Outdated
- gensub's occurrence selector now goes through AWK numeric conversion
  (JRT.toDouble) and truncates, matching gawk 5.0.0: " 2" selects the
  2nd occurrence, "1e1" the 10th, and the "treated as 1" warning fires
  exactly when the converted value is below 1 (so non-numeric strings,
  0, and negatives all warn; valid numeric strings no longer do). This
  replaces the integer-prefix JRT.toLong parse and the separate
  isParseableNumber warning check.
- Only the exact "@unsorted" mode skips sorting; "@unsortedx" and
  "@unsorted_asc" are undefined comparison modes and fatal, as in gawk,
  for asort()/asorti() and PROCINFO["sorted_in"] alike.
- VariableManager.getVariable(String) is now a default method returning
  null: it was added to a pre-existing public interface, and a default
  keeps third-party VariableManager implementations source and binary
  compatible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d61f909777

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/frontend/AwkParser.java
Comment thread src/main/java/io/jawk/jrt/JRT.java
- SYMTAB and FUNCTAB are marked array-typed when their global ID is
  created (non-POSIX only), so scalar misuse like "SYMTAB = 1" now
  fails at parse time with the same array/scalar guard as any other
  array, instead of the less precise runtime error. gawk is fatal on
  such use too. In --posix mode both names stay ordinary variables.
- IGNORECASE now applies to string relational operators and index(),
  as in gawk: compare2 gains a case-aware variant fed with
  jrt.isIgnoreCase() by the comparison opcodes (numeric/strnum
  comparisons are untouched), and index() delegates to a case-aware
  JRT.index(). Constant folding of comparisons is restricted to
  numeric operands, because a folded string comparison would bake in
  the case-sensitive result before runtime IGNORECASE can act.
  Verified byte-identical with gawk 5.0.0, including strnum cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e3c62ea597

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/backend/AVM.java Outdated
Comment thread src/main/java/io/jawk/backend/AVM.java Outdated
The SYMTAB snapshot and AVM.getVariable's slotless fallbacks read
baseInitialVariables, so variables supplied per run through
AwkRunBuilder.variable(...) / execute(..., variableOverrides) were
invisible when the script gave them no compiled slot. All three sites
now read executionInitialVariables, which prepareExecutionInputs
already maintains as the base map merged with the current run's
overrides (and aliases the base map when there are none, so
no-override executions are untouched). A host-supplied PROCINFO with
"sorted_in" now drives for-in ordering even when the script never
references PROCINFO; tests cover that and SYMTAB visibility.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex Please review once more.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 65b60c5e7b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
Comment thread src/main/java/io/jawk/ext/GawkExtension.java Outdated
- mkbool() now applies ordinary AWK truthiness via jrt.toBoolean, the
  same conversion IGNORECASE uses: a non-empty string like "abc" (or
  the string constant "0") is true, while a strnum judged numerically
  zero stays false. Numeric coercion via toDouble wrongly returned
  false for truthy non-numeric strings.
- asorti() writes the source indices as string values, as gawk does:
  the internal key object may be a Long for numeric-looking indexes,
  which leaked "number" out of typeof(d[i]) where gawk 5.0.0 says
  "string".

Note: for (k in a) also types numeric-looking keys as "number" where
gawk says "string"; that lives in the interpreter's iteration path,
not the extension, and is left for a separate discussion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry

Copy link
Copy Markdown
Contributor Author

@codex please review again.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ab7186b3d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/main/java/io/jawk/intermediate/AwkTuples.java
No code change: the reported null-frame scenario is not reachable.
setNumGlobals is the sole populator of both the globals array and the
by-name offset map, so when the eval optimizer drops SET_NUM_GLOBALS
(only for expressions with no compiled globals) no name can resolve to
an index into the missing frame. Hooks run once, on the first
execution of a fresh AVM, so no stale layout from a prior run can
exist either; every by-name path a hook can take (getVariable,
assignVariable) falls back to JRT specials or the initial-variables
maps. Treating BEFORE_START_HOOKS as frame-requiring would instead
disable the optimization entirely, since the opcode is emitted in
every eval preamble.

A regression test pins this: an extension whose beforeStart reads and
assigns variables by name runs against a frame-optimized expression
stream and completes safely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GawkExtension.compareStrings folds case with compareToIgnoreCase
  instead of two toLowerCase(Locale.ROOT) allocations per comparison
  inside O(n log n) sorts, and now applies the same folding rule as
  JRT.compare2 on string relational operators.
- JRT.replaceFirst/replaceAll share one private replace(orig, repl,
  ere, global) worker; their bodies differed by a single token.
- GawkExtension members are grouped by design order: constants and
  fields, the before-start hook, the six @JawkFunction builtins, the
  diagnostics helper, for-in and sort-mode plumbing, sort machinery,
  comparators, typeof helpers. Pure block moves, no content changes.
- The sort entry and key lists are pre-sized to the array size.

Remaining observations from the final review are tracked in #511.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bertysentry bertysentry merged commit df9d1cf into main Jul 15, 2026
5 checks passed
@bertysentry bertysentry deleted the 454-implement-gawk-array-sorting-and-type-introspection-builtins branch July 15, 2026 10:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement gawk array sorting and type-introspection builtins

1 participant