From e7d3cc3d68ac1419e2e339e2fa0309248f1ae272 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Wed, 30 Sep 2026 21:41:53 +0800 Subject: [PATCH] fix: keep decompose distinct from compose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The grammar captures both directives under :compose; the builder dropped the value and compiled every decompose as NFC compose — var-kor's author-intended NFD output composed o+U+0306 into ŏ (U+014F) and all 94 of its sweep tests failed on invisible byte diffs. Read the captured value. var-kor: 282 examples, 0 failures (was 94). --- lib/interscript/isc/document_builder.rb | 5 ++++- spec/interscript/isc/node_adapter_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/interscript/isc/document_builder.rb b/lib/interscript/isc/document_builder.rb index 232ef42b..f711ece8 100644 --- a/lib/interscript/isc/document_builder.rb +++ b/lib/interscript/isc/document_builder.rb @@ -263,7 +263,10 @@ def extract_stage_items(n) elsif n[:separate] [{kind: :separate, separator: n[:separator] ? materialize(n[:separator]) : nil}] elsif n[:compose] - [{kind: :compose}] + # The grammar captures both `compose` and `decompose` under this + # key — the value decides the directive. Dropping it compiled + # every decompose as NFC compose. + [{kind: n[:compose].to_s.to_sym}] elsif n[:case] item = {kind: :string_case, op: n[:case].to_s} item[:kwargs] = extract_kwargs(n[:case_kwargs]) if n[:case_kwargs] diff --git a/spec/interscript/isc/node_adapter_spec.rb b/spec/interscript/isc/node_adapter_spec.rb index a92944b6..c787ffe9 100644 --- a/spec/interscript/isc/node_adapter_spec.rb +++ b/spec/interscript/isc/node_adapter_spec.rb @@ -373,3 +373,25 @@ def parse_and_adapt(src) expect(node.name).to eq("test-map") end end + +RSpec.describe "NodeAdapter compose vs decompose" do + it "keeps decompose distinct from compose" do + src = <<~ISC + system "t:a-b:C-D:1" { + metadata { name "T" } + stage main { + sub "a" "b" + decompose + compose + } + } + ISC + node = Interscript::Isc::NodeAdapter.to_interscript_node( + Interscript::Isc::DocumentBuilder.build(Interscript::Isc::Parser.parse(src)) + ) + calls = node.stages[:main].children.select { |c| c.is_a?(Interscript::Node::Rule::Funcall) }.map(&:name) + # The grammar captures both directives under :compose; the builder must + # read the value or decompose compiles as NFC compose. + expect(calls).to eq(%i[decompose compose]) + end +end