From 23971b60baf94fb2bcded66af8608233c519a365 Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Thu, 22 Oct 2026 15:53:38 +0800 Subject: [PATCH] =?UTF-8?q?test(WO80/A4):=20rulings=20batch=204=20?= =?UTF-8?q?=E2=80=94=20funcall,=20item=20algebra,=20boundary,=20embedded?= =?UTF-8?q?=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empirically pinned against the interpreter, normative as IS-1 v2 sections: - run dispatches Stdlib::Functions; run takes NO kwargs (kwargs belong to declared funcall directives — rababa); parse error otherwise - stage-level case directives execute at their rule position on the whole string (downcase-then-sub ordering pinned) - item algebra: any() set, any(lo..hi) inclusive range, capture+ref source substitution - boundary is a zero-width word edge (string edges match, mid-word never) - embedded tests are normative data — never executed by transliterate; the verification layer runs them - HAZARD pinned: stray identifiers (incl. unknown run targets) are consumed as parse no-ops — a typo'd map is silently wrong. Ruled tolerable for the legacy corpus; stricter parsing is an IS-1 v2 follow-up gated on a clean 293-map parse across all three compilers --- spec/isc/semantics_spec.rb | 132 +++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/spec/isc/semantics_spec.rb b/spec/isc/semantics_spec.rb index 3dc01e09..4252a6cb 100644 --- a/spec/isc/semantics_spec.rb +++ b/spec/isc/semantics_spec.rb @@ -208,3 +208,135 @@ def with_map(name, body) end end end + +RSpec.describe "ISC semantics — funcall and stdlib" do + def with_map(name, body) + dir = Dir.mktmpdir + File.write(File.join(dir, name), body) + Interscript.load_path.unshift(dir) + yield + ensure + Interscript.load_path.delete(dir) if dir + end + + def system_map(id, body) + <<~ISC + system "TST:xx-Latn:Latn:#{id}" { + metadata { authority_id tst; id #{id}; language iso-639-2:xx; source_script Latn; destination_script Latn } + #{body} + } + ISC + end + + it "rules: run dispatches the stdlib function" do + with_map("tst-fn-1.isc", system_map(51, "stage main { run downcase }")) do + expect(Interscript.transliterate("tst-fn-1", "AbC")).to eq("abc") + end + end + + it "rules: stage-level case directives run at their position, on the whole string" do + with_map("tst-fn-2.isc", system_map(52, + "stage main { downcase parallel { sub { from \"b\" to \"B\" } } }")) do + # downcase applies before the sub (rule order), so b -> B still fires + expect(Interscript.transliterate("tst-fn-2", "AB")).to eq("aB") + end + end + + it "rules: run accepts no kwargs — kwargs belong to declared funcall directives (rababa)" do + with_map("tst-fn-3.isc", system_map(53, "stage main { run separate separator: \"-\" }")) do + expect { Interscript.transliterate("tst-fn-3", "abc") } + .to raise_error(Interscript::Isc::ParseError) + end + end + + it "rules: stray identifiers are consumed as no-ops — pinned as a hazard" do + # HAZARD (A4 follow-up): `run nosuchfunction` drops BOTH identifiers + # at parse time and the stage becomes empty — a typo'd map is + # silently wrong. Ruled normative-for-now (legacy .imp corpus + # tolerance, grammar/concerns/stages.rb comment_item); the stricter + # parse is an open item tracked in the IS-1 v2 text, gated on a + # clean parse of the full 293-map corpus across all three compilers. + with_map("tst-fn-4.isc", system_map(54, "stage main { run nosuchfunction parallel { sub { from \"a\" to \"b\" } } }")) do + expect(Interscript.transliterate("tst-fn-4", "a")).to eq("b") + end + end +end + +RSpec.describe "ISC semantics — item algebra and boundary" do + def with_map(name, body) + dir = Dir.mktmpdir + File.write(File.join(dir, name), body) + Interscript.load_path.unshift(dir) + yield + ensure + Interscript.load_path.delete(dir) if dir + end + + def system_map(id, body) + <<~ISC + system "TST:xx-Latn:Latn:#{id}" { + metadata { authority_id tst; id #{id}; language iso-639-2:xx; source_script Latn; destination_script Latn } + #{body} + } + ISC + end + + it "rules: any() is a character set of length 1" do + with_map("tst-item-1.isc", system_map(61, + "stage main { parallel { sub { from any(\"bc\") to \"X\" } } }")) do + expect(Interscript.transliterate("tst-item-1", "abcd")).to eq("aXXd") + end + end + + it "rules: any(..) is an inclusive codepoint range" do + with_map("tst-item-2.isc", system_map(62, + "stage main { parallel { sub { from any(\"0\"..\"9\") to \"N\" } } }")) do + expect(Interscript.transliterate("tst-item-2", "a7b")).to eq("aNb") + end + end + + it "rules: capture + ref substitutes the captured source text" do + with_map("tst-item-3.isc", system_map(63, + "stage main { sequence { sub { from capture(\"a\") + \"b\" to ref(1) + \"Y\" } } }")) do + expect(Interscript.transliterate("tst-item-3", "ab")).to eq("aY") + end + end + + it "rules: boundary is a zero-width word-edge, not string-edge-only" do + with_map("tst-item-4.isc", system_map(64, + "stage main { parallel { sub { from \"a\" to \"X\" after boundary } } }")) do + # string end IS a word edge + expect(Interscript.transliterate("tst-item-4", "xa")).to eq("xX") + # mid-word: no edge after a + expect(Interscript.transliterate("tst-item-4", "ab")).to eq("ab") + end + end +end + +RSpec.describe "ISC semantics — metadata contract and embedded tests" do + def with_map(name, body) + dir = Dir.mktmpdir + File.write(File.join(dir, name), body) + Interscript.load_path.unshift(dir) + yield + ensure + Interscript.load_path.delete(dir) if dir + end + + it "rules: embedded tests are normative data — never executed by transliterate" do + dir = Dir.mktmpdir + File.write(File.join(dir, "tst-test-1.isc"), <<~ISC) + system "TST:xx-Latn:Latn:71" { + metadata { authority_id tst; id 71; language iso-639-2:xx; source_script Latn; destination_script Latn } + stage main { parallel { sub { from "a" to "b" } } } + tests { "zzz" -> "wrong" } + } + ISC + Interscript.load_path.unshift(dir) + # a deliberately wrong expectation does not fire; the verification + # layer (conformance runner / artifact build) executes tests + expect(Interscript.transliterate("tst-test-1", "a")).to eq("b") + ensure + Interscript.load_path.delete(dir) if dir + end +end