Skip to content

Commit 72446e8

Browse files
committed
fix: alias refs in any() keep set semantics like the legacy DSL
Bare identifiers inside any(...) parsed as :alias_ref, which no transform rule matched — the hash degraded downstream into the debug string "[:alias_ref, \"greek\"]" baked into constraint regexps, so every context-gated rule silently never fired (Greek velar assimilation γ→n, ta-marbuta contexts). Two parts: - transform rules for :alias_ref (list entries and single any() args) - single-alias any() args stay a Set; imported aliases compile to the vacuous fragment exactly as the legacy runtime's Any(nil) does — resolving them to the charset string would bake a 1000-char literal into a lookbehind that never matches
1 parent d2ef5a9 commit 72446e8

3 files changed

Lines changed: 52 additions & 2 deletions

File tree

‎lib/interscript/isc/node_adapter.rb‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,14 @@ def convert_set(set)
218218
when Items::Primitive
219219
convert_primitive(c)
220220
when Items::AliasRef
221-
convert_alias_ref(c)
221+
# Legacy parity: a bare alias inside any(...) compiles to the
222+
# Stdlib value (Any(nil) there for imported aliases). Keeping
223+
# the Alias item resolves to the imported charset *string* at
224+
# build time, baking a 1000-char literal into the constraint
225+
# regexp — a lookbehind that never matches.
226+
name = convert_alias_ref(c).name
227+
val = Interscript::Stdlib::ALIASES[name]
228+
val ? Interscript::Node::Item::Any.new(val) : Interscript::Node::Item::String.new("")
222229
else
223230
convert_item(c)
224231
end

‎lib/interscript/isc/transform.rb‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class Transform < Parslet::Transform
2929
Items::AliasRef.new(q.to_s, map: n.to_s)
3030
}
3131
rule(alias: simple(:n)) { Items::AliasRef.new(n.to_s) }
32+
# Bare identifiers inside any(...) lists parse as :alias_ref (the
33+
# :alias key only covers direct item position). Unmatched, the hash
34+
# degraded downstream into the debug string "[:alias_ref, \"name\"]".
35+
rule(alias_ref: {identifier: simple(:n)}) { Items::AliasRef.new(n.to_s) }
36+
rule(alias_ref: simple(:n)) { Items::AliasRef.new(n.to_s) }
3237
rule(ref: subtree(:h)) { Items::Capture.new(h[:digit].to_s.to_i) }
3338
rule(capture_inner: subtree(:inner)) { Items::CaptureGroup.new(Interscript::Isc::Transform.materialize_item(inner)) }
3439
rule(maybe_inner: subtree(:inner)) { Items::Maybe.new(Interscript::Isc::Transform.materialize_item(inner)) }
@@ -84,7 +89,13 @@ def self.decode_string_parts(parts)
8489
rule(list: sequence(:arr)) do
8590
Items::Set.new(arr)
8691
end
87-
rule(any: subtree(:h)) { h }
92+
rule(any: subtree(:h)) do
93+
# A single alias argument keeps set semantics: the legacy runtime
94+
# builds Any(Alias) which resolves through Stdlib (Any(nil) for
95+
# imported aliases). Unwrapped, the bare Alias resolves to the
96+
# imported charset string and compiles as a literal.
97+
h.is_a?(Items::AliasRef) ? Items::Set.new([h]) : h
98+
end
8899

89100
rule(concatenation: subtree(:parts)) do
90101
Items::Concat.from_parts(Array(parts))

‎spec/interscript/isc/node_adapter_spec.rb‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,35 @@ def parse_and_adapt(src)
326326
expect(tests[1]).to eq(["a\tb\nc", "déjà"])
327327
end
328328
end
329+
330+
RSpec.describe "NodeAdapter alias refs in constraints" do
331+
it "compiles bare alias refs in sets like the legacy DSL — no debug dump, no literal-charset lookbehind" do
332+
src = <<~ISC
333+
system "t:g-c:C-D:1" {
334+
metadata { name "T" }
335+
stage main {
336+
sub {
337+
from "γ"
338+
to "n"
339+
before any(greek)
340+
after any("κΚ") + any(greek)
341+
not_before boundary
342+
}
343+
}
344+
}
345+
ISC
346+
node = Interscript::Isc::NodeAdapter.to_interscript_node(
347+
Interscript::Isc::DocumentBuilder.build(Interscript::Isc::Parser.parse(src))
348+
)
349+
rule = node.stages[:main].children.first
350+
expect(rule.before).to be_a(Interscript::Node::Item::Any)
351+
expect(rule.before.inspect).not_to include("alias_ref")
352+
expect(rule.after.inspect).not_to include("alias_ref")
353+
# The legacy runtime compiles an imported alias in a set to a vacuous
354+
# fragment (Any(nil)); resolving it to the charset *string* would bake
355+
# a 1000-char literal into the lookbehind and never match.
356+
interp = Interscript::Interpreter.new
357+
interp.compile(node)
358+
expect(interp.call("αγκα")).to eq("αnκα")
359+
end
360+
end

0 commit comments

Comments
 (0)