From 669c0ac6fe6c573d88d1ff077708a30b1817187d Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Thu, 1 Oct 2026 20:13:26 +0800 Subject: [PATCH] fix: render bridge-emitted ISC shapes the corpus actually produces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Through the Ruby bridge the engine sees generated ISC, whose shapes differ from the hand-written corpus in two ways: - any() is a character class β€” the generator inlines the unicode library's greek inside any() β€” while a bare alias reference stays a literal sequence (the qualified 404 pins). alalc-ell's gamma nasalization guards had compiled to the literal 300-character alphabet through the bridge. - a capture group in a result position renders as the parenthesised inner text, like Ruby's build_item (bgnpcgn-zho writes to capture("me, yao") for 么). The renderer rejected it and the whole map failed to load through the bridge. Bridge sweep (Compiler::Python over the full corpus): 8,238 examples, 0 failures. --- src/interscript/isc.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/interscript/isc.py b/src/interscript/isc.py index ea7ed84..6c0bac4 100644 --- a/src/interscript/isc.py +++ b/src/interscript/isc.py @@ -22,7 +22,7 @@ import re from pathlib import Path -from .expr import _BOUNDARY, _WORD, expr_lookbehind, expr_neg_lookbehind, expr_to_regex +from .expr import _BOUNDARY, _WORD, expr_lookbehind, expr_neg_lookbehind, expr_to_regex, is_plain_string # Ruby \w is ASCII-only; the same divergence the expression layer fixes. _WORD_BOUNDARY = _BOUNDARY @@ -671,6 +671,18 @@ def _render_item(item: dict, aliases: dict[str, str]) -> str: len(sub["value"]) == 1 for sub in items ): return f'any("{_escape("".join(sub["value"] for sub in items))}")' + # any() is a character + # class (measured against Ruby: the unicode library's greek); + # a bare alias reference stays a literal sequence. + if ( + len(items) == 1 + and items[0]["type"] == "alias_ref" + and not items[0].get("map") + and items[0]["name"] in aliases + ): + resolved = aliases[items[0]["name"]] + if is_plain_string(resolved): + return f"any({resolved})" return "any([" + ", ".join(_render_item(sub, aliases) for sub in items) + "])" if kind == "range": return f'any("{_escape(item["lo"])}".."{_escape(item["hi"])}")' @@ -792,6 +804,10 @@ def _repl_of(item: dict, aliases: dict[str, str]) -> str: return "".join(_repl_of(part, aliases) for part in item["parts"]) if kind == "capture": return f"${item['index']}" + if kind == "capture_group": + # Ruby renders a capture group in a result position as the + # parenthesised inner text (bgnpcgn-zho: to capture("me, yao")). + return "(" + _repl_of(item["inner"], aliases) + ")" if kind == "set": # A to-position any() lists alternative spellings; the Ruby # runtime picks the first in non-iterating mode.