Refactor dialect front ends behind a PlxSurface.parse_body vtable#2
Merged
jdatcmd merged 1 commit intoJul 17, 2026
Merged
Conversation
The transpiler put every dialect's lexer, parser, and emitter inside the single ~11k-line src/plx_transpile.c and dispatched them with a hardcoded `block_style` if-ladder. This inverts SOLID responsibility: the "general" transpiler owned each language. This commit restructures it to mirror the precomp design — a dialect-neutral engine plus per-dialect front ends selected through a function pointer on the per-dialect descriptor. Architecture: - PlxSurface (the per-dialect descriptor already threaded as cx->surf) gains a vtable method `void (*parse_body)(struct PlxCtx *cx)` and a `self_contained_block` flag. plx_transpile() now calls `cx.surf->parse_body(&cx)` and runs one assemble tail (conditional on self_contained_block for PL/SQL, which emits its own DECLARE/BEGIN/END) — the block_style if-ladder is gone. block_style survives only as a lexer hint. - New src/plx_engine.h exposes the shared engine: the Ctx (now `struct PlxCtx`), Tok/PlxLocal2 types, PLX_MAX_DEPTH/PLX_DIAG_* macros, and the plx_*-prefixed entry points (plx_lex, plx_rewrite_expr, plx_emit_core/leaf, plx_span_text, plx_local_add/find, plx_skip_seps, plx_is_ident[_start], ...). The engine (lexer, expression rewriter, leaf emitter, symbol table, interpolation, assemble) stays in plx_transpile.c, now ~2.6k lines. - Each dialect's tokenizer/parser/emitter moves into its own translation unit: COBOL/PL-SQL/T-SQL/Go/Ruby/Python into their plx_dialect_*.c, and the shared PHP/JS/TS brace parser (+ TypeScript preprocessing) into a new src/plx_parse_brace.c. php/js/ts become thin config stubs whose parse_body points at plx_brace_parse_body. No functional change. The generated plpgsql (pg_proc.prosrc) is byte-identical across all 209 regression functions before and after; the move was verified at every step against that golden set. All 13 installcheck tests pass and the tree builds with zero compiler warnings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jdatcmd
approved these changes
Jul 17, 2026
jdatcmd
left a comment
Contributor
There was a problem hiding this comment.
Approved — verified locally
Reviewed as a pure architectural refactor and verified the claims empirically (build + full regression suite against unchanged goldens), not just by reading the diff.
Verification
- Clean rebuild of all 14 translation units: zero warnings.
installcheck(13 tests, all dialects): 13/13 pass against the unchangedtest/expectedgoldens — consistent with the "no functional change / byte-identical output" claim.- The one genuinely new logic surface — the
plx_transpile()driver — checks out as a 1:1 behavioral equivalent of the old dispatch: the newself_contained_blockpath reproduces the oldPLX_BLK_PLSQLbranch exactly, and theelsepath reproduces the old DECLARE/BEGIN/END assemble exactly. Theblock_styleif-ladder is correctly reduced to a lexer hint. - Diff touches only
src/*+Makefile; notest/expectededits, as expected for a no-op refactor.
Assessment
- Mirrors the existing
precompengine+front-end design, so it's consistent with the established architecture. - Real maintainability win:
plx_transpile.c11,135 → 2,655 lines; adding a dialect no longer touches shared code. - Low risk: mechanical relocation with a clean, verified dispatch equivalence.
LGTM. 🤖 verified with Claude Code.
jdatcmd
pushed a commit
that referenced
this pull request
Jul 17, 2026
Follow-on cleanup from the post-merge audit of #2, in the same spirit as the plx_diag_prefix relocation: - Linkage (#2): the six single-dialect front ends (ruby/python/cobol/plsql/ tsql/go plx_*_parse_body) are only ever referenced by their own surface's .parse_body in their own translation unit, so make them static and drop their prototypes from plx_engine.h. Only plx_brace_parse_body is genuinely shared (php/js/ts) and keeps its prototype. - Dead code (#3): drop the write-only Ctx.nt field (set once in plx_lex, never read; the standalone tokenizers track their own token counts on their own structs). - Comment (#4): COBOL's surface carries no keyword table (.kws = NULL); fix the header comment that claimed a table was "kept for consistency". No functional change. Clean rebuild (zero warnings), 13/13 installcheck pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
The transpiler kept every dialect's lexer, parser, and emitter inside the single ~11k-line
src/plx_transpile.cand dispatched them with a hardcodedblock_styleif-ladder — the "general" transpiler owned each language's implementation. This restructures it to mirror the precomp design: a dialect-neutral engine plus per-dialect front ends selected through a function pointer on the per-dialect descriptor.This is a pure architectural change. The generated plpgsql is byte-identical.
Architecture
PlxSurfacebecomes the vtable. It already rides oncx->surf; it gainsvoid (*parse_body)(struct PlxCtx *cx)and aself_contained_blockflag.plx_transpile()now callscx.surf->parse_body(&cx)and runs a single assemble tail (conditional onself_contained_blockfor PL/SQL, which emits its ownDECLARE/BEGIN/END). Theblock_styleif-ladder is gone;block_stylesurvives only as a lexer hint (indent vs. newline tokenizing).src/plx_engine.hexposes the shared engine: the context (struct PlxCtx),Tok/PlxLocal2types,PLX_MAX_DEPTH/PLX_DIAG_*, and theplx_*-prefixed entry points (plx_lex,plx_rewrite_expr,plx_emit_core/leaf,plx_span_text,plx_local_add/find,plx_skip_seps,plx_is_ident[_start], …). The engine — lexer, expression rewriter, leaf emitter, symbol table, interpolation, assemble — stays inplx_transpile.c, now ~2.6k lines.plx_dialect_*.c; the shared PHP/JS/TS brace parser (plus TypeScript preprocessing) moves into a newsrc/plx_parse_brace.c.php/js/tsbecome thin config stubs whoseparse_bodypoints atplx_brace_parse_body.Result
plx_transpile.cplx_engine.h,plx_parse_brace.cAdding a dialect is now: new
plx_dialect_X.cwith aPlxSurface+parse_body, register it — no edit to the shared engine.Verification
md5(pg_proc.prosrc)captured for all 209 functions the regression suite creates, and diffed after every relocation step — identical throughout.installchecktests pass at each step and at the end.🤖 Generated with Claude Code