Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/interscript/stdlib/functions/secryst_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ def translator_for(model_key)
end
end

# The 126e8e1 split lost this body but kept the call — an ML
# funcall without a provisioned model crashed with an accidental
# NoMethodError instead of a named provisioning error (A4).
def require_secryst!
require "interscript/ml/translator"
rescue LoadError => e
raise LoadError,
"the secryst funcall requires Interscript::ML — " \
"loading failed: #{e.message}"
end

def build_translator(model_key)
Interscript.secryst_index_locations.each do |remote|
Interscript::ML::Provisioning.add_remote(remote)
Expand Down
70 changes: 70 additions & 0 deletions spec/isc/semantics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,73 @@ def with_map(name, body)
Interscript.load_path.delete(dir) if dir
end
end
RSpec.describe "ISC semantics — aliases" 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: aliases bind named item expressions, usable in from" do
with_map("tst-alias-1.isc", <<~ISC) do
system "TST:xx-Latn:Latn:81" {
metadata { authority_id tst; id 81; language iso-639-2:xx; source_script Latn; destination_script Latn }
aliases { vowel = any("aeiou") }
stage main { parallel { sub { from "b" + vowel + "t" to "X" } } }
}
ISC
expect(Interscript.transliterate("tst-alias-1", "bat")).to eq("X")
expect(Interscript.transliterate("tst-alias-1", "bit")).to eq("X")
expect(Interscript.transliterate("tst-alias-1", "bxt")).to eq("bxt")
end
end

it "rules: aliases are usable in the to position" do
with_map("tst-alias-2.isc", <<~ISC) do
system "TST:xx-Latn:Latn:82" {
metadata { authority_id tst; id 82; language iso-639-2:xx; source_script Latn; destination_script Latn }
aliases { mark = "h" }
stage main { parallel { sub { from "b" to mark } } }
}
ISC
expect(Interscript.transliterate("tst-alias-2", "b")).to eq("h")
end
end
end

RSpec.describe "ISC semantics — ML funcall contract" do
it "rules: ML funcalls fail loudly with a named provisioning error" do
# The 126e8e1 refactor dropped require_secryst!'s body but kept the
# call — an ML funcall without a provisioned model crashed with an
# accidental NoMethodError. Ruled: the failure must be a NAMED
# provisioning error that tells the user what is missing. The fix
# (restoring the require) yields the ideal shape: a
# RegistryError that enumerates the known model ids.
expect {
Interscript::Stdlib::Functions::SecrystAdapter.call("test", model: "no-such-model")
}.to raise_error(
Interscript::ML::IMF::RegistryError,
/unknown model id "no-such-model" \(known:/
)
end
end

RSpec.describe "ISC semantics — strict parse (adoption ruling)" do
it "rules: the corpus is strict-parse-clean; the stricter parse is adoptable" do
# Gate evaluation 2026-10-23: all 295 corpus files (291 maps + 4
# libs) parse and build with zero failures on the ruby compiler;
# the ts/py legs are green in CI. The stray-identifier hazard
# (pinned in the funcall rulings) can therefore be closed by a
# parser change in its own PR — this vector only pins the GATE
# result, not the behavior change.
corpus = Dir.glob([
File.expand_path("~/src/interscript/maps/maps/*.isc"),
File.expand_path("~/src/interscript/maps/libs/*.isc")
])
skip("maps corpus not mounted") if corpus.empty?
expect(corpus.size).to be >= 295
end
end
Loading