PEGO

80. Inline Eligible Typed Operators Rule Line Matchers

  • The generated typed Go runtime inlines eligible unfinished operand, operator and skip matchers on Operators Rule lines. Each line owns its toperatorsRuleLine.scope, so it does not need a shared Node scope cache. The Operators Rule runtime retains precedence selection, longest-match behavior, frames and action finalization.

  • A generation-time reference control emits per-expression methods for those matchers while keeping the same Operators Rule runtime. It adds no parser-time option or branch. Node generation, recognition and TypeScript output are unchanged.

  • This cut-free route leaves cut-bearing matchers to the framed body emitter in change 81. Recovery bodies have a separate structural route in record 83; recovery layouts the collector cannot represent, action captures absent from the actual general frame and other unmatched layouts remain on the general typed path. Record 82 recovers cases where the earlier structural walk omitted or reordered dead captures. Eligible callees may still use direct bodies.

  • The 14-case typed corpus compares the direct and same-generator reference routes across associativity, longest-match ties, captures, rollback, lookahead, nullable parts, cut/recovery fallback, unreachable captures, mixed routes and error isolation. It also checks Node output is byte-identical and runs conversion parity in both position units. Workload fixtures check accepted and rejected inputs, parser reuse and unchanged returned values.

  • Focused benchmark: Go 1.27.1, darwin/arm64, Apple M3 Max, 16 CPUs; five alternating 300 ms pairs of prebuilt direct/general binaries. Ratios are medians of direct/general time ratios across pairs, with paired min–max ranges:

    Workload n Unit Median ratio Paired range
    Calculator 1 CodePoints 0.953× 0.913–0.998×
    Calculator 1 Bytes 0.972× 0.920–0.994×
    Calculator 128 CodePoints 0.939× 0.884–0.970×
    Calculator 128 Bytes 0.995× 0.954–1.032×
    Actions and captures 1 CodePoints 1.017× 0.971–1.041×
    Actions and captures 1 Bytes 0.987× 0.944–1.040×
    Actions and captures 128 CodePoints 0.891× 0.881–0.932×
    Actions and captures 128 Bytes 0.940× 0.891–0.984×

    Both large action/capture cases improve, with allocation counts unchanged at 12 per operation and cumulative bytes about 44.5 KB. The small action cases show no stable benefit (5 allocations and about 12.3 KB). Calculator allocation counts also stay unchanged (4/14) with about 11.7/61.8 KB; large-input Bytes shows no established gain. Node and recognition control ranges cross parity. These synthetic measurements do not establish a broad parser speedup; generation and compilation are excluded.

To generate the same-generator routes and check that they return identical values and errors:

export PEGO_TYPED_OPERATORS_RULE_DIR=/tmp/pego-typed-operators-rule
go test ./internal/engine -run '^TestGeneratedTypedOperatorsRule(Workload|ActionWorkload)$' -count=1

Build each fixture from the direct and general folders into separate benchmark binaries:

for workload in calculator actions; do
  fixture=$PEGO_TYPED_OPERATORS_RULE_DIR
  if [ "$workload" = actions ]; then fixture="$fixture/actions"; fi
  for variant in direct general; do
    (cd "$fixture/$variant" && go test -c -o "$fixture/$variant.test" .)
  done
done

Alternate run order over five paired samples:

for pair in 1 2 3 4 5; do
  if [ "$((pair % 2))" -eq 1 ]; then
    first=general; second=direct
  else
    first=direct; second=general
  fi
  for workload in calculator actions; do
    fixture=$PEGO_TYPED_OPERATORS_RULE_DIR
    if [ "$workload" = actions ]; then fixture="$fixture/actions"; fi
    for variant in "$first" "$second"; do
      "$fixture/$variant.test" -test.run='^$' \
        -test.bench='^BenchmarkTypedOperatorsRule$' -test.benchtime=300ms -test.benchmem
    done
  done
done

The calculator uses lrInput(n) = "1" + strings.Repeat("+2*3", n). The action/capture fixture uses operatorsRuleInput(n) = "a" + strings.Repeat(" [b,c,d,]", n); n=128 produces 128 postfix groups after the base atom.