Skip to content

Commit dcfc954

Browse files
committed
feat(isc): add NodeAdapter — ISC document to Interscript::Node bridge
The NodeAdapter converts ISC document hashes (from DocumentBuilder) to Interscript::Node::Document objects, enabling .isc files for actual transliteration through the existing Interpreter runtime. This closes the critical gap: ISC files can now be parsed AND used for transliteration, not just parsed. doc = Isc::DocumentBuilder.build(tree) node = Isc::NodeAdapter.to_interscript_node(doc) Interscript::Interpreter.new.compile(node).call("hello") Verified: ISC transliteration output matches DSL output for alalc-amh.
1 parent ff0b193 commit dcfc954

2 files changed

Lines changed: 182 additions & 0 deletions

File tree

lib/interscript/isc.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Isc
1010
autoload :Grammar, "interscript/isc/grammar"
1111
autoload :Items, "interscript/isc/items"
1212
autoload :Codemod, "interscript/isc/codemod"
13+
autoload :NodeAdapter, "interscript/isc/node_adapter"
1314

1415
SCHEMA_VERSION = 1
1516

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# frozen_string_literal: true
2+
3+
module Interscript
4+
module Isc
5+
# Bridges the ISC document hash (from DocumentBuilder) to the existing
6+
# Interscript::Node::Document object model, enabling .isc files to be
7+
# used for actual transliteration via the standard runtime.
8+
#
9+
# This is the critical integration point: without it, ISC files can be
10+
# parsed but cannot transliterate.
11+
#
12+
# doc_hash = Interscript::Isc::DocumentBuilder.build(tree)
13+
# node_doc = Interscript::Isc::NodeAdapter.to_interscript_node(doc_hash)
14+
# Interscript.transliterate_node(node_doc, "main", "hello")
15+
#
16+
class NodeAdapter
17+
def self.to_interscript_node(isc_doc)
18+
new(isc_doc).build
19+
end
20+
21+
def initialize(isc_doc)
22+
@isc_doc = isc_doc
23+
end
24+
25+
def build
26+
Interscript::Node::Document.new.tap do |doc|
27+
doc.metadata = build_metadata
28+
doc.tests = build_tests
29+
doc.aliases = build_aliases
30+
build_stages.each { |name, stage| doc.stages[name] = stage }
31+
doc.name = @isc_doc[:system_code]
32+
end
33+
end
34+
35+
private
36+
37+
def build_metadata
38+
meta = Interscript::Node::MetaData.new
39+
@isc_doc[:metadata].each do |key, value|
40+
meta[key.to_sym] = value
41+
end
42+
meta
43+
end
44+
45+
def build_tests
46+
return nil if @isc_doc[:tests].empty?
47+
48+
tests = Interscript::Node::Tests.new
49+
@isc_doc[:tests].each do |t|
50+
tests.data << [t[:input], t[:expected]]
51+
end
52+
tests
53+
end
54+
55+
def build_aliases
56+
@isc_doc[:aliases].each_with_object({}) do |a, h|
57+
h[a[:name].to_sym] = convert_item(a[:value])
58+
end
59+
end
60+
61+
def build_stages
62+
@isc_doc[:stages].each_with_object({}) do |stage, h|
63+
h[stage[:name].to_sym] = build_stage(stage)
64+
end
65+
end
66+
67+
def build_stage(stage_def)
68+
stage = Interscript::Node::Stage.new(stage_def[:name].to_sym)
69+
stage_def[:body].each do |item|
70+
case item[:kind]
71+
when :parallel
72+
group = Interscript::Node::Group::Parallel.new
73+
item[:rules].each { |r| group.children << build_rule(r) }
74+
stage.children << group
75+
when :sequence
76+
item[:rules].each { |r| stage.children << build_rule(r) }
77+
when :bare_rule
78+
stage.children << build_rule(item[:rule])
79+
when :run
80+
stage.children << build_run_rule(item)
81+
when :separate
82+
stage.children << Interscript::Node::Rule::Sub.new(
83+
Interscript::Node::Item::String.new(" "),
84+
Interscript::Node::Item::String.new(item[:separator]&.value || "-"),
85+
)
86+
when :string_case
87+
sym = item[:op] == "title_case" ? :title_case : item[:op].to_sym
88+
stage.children << sym
89+
when :compose
90+
stage.children << :compose
91+
end
92+
end
93+
stage
94+
end
95+
96+
def build_rule(rule_def)
97+
from = convert_item(rule_def[:from])
98+
to = convert_item(rule_def[:to])
99+
opts = {}
100+
%i[before after not_before not_after].each do |k|
101+
next unless rule_def[:constraints]&.any? { |c| c[:kind] == k }
102+
constraint = rule_def[:constraints].find { |c| c[:kind] == k }
103+
opts[k] = convert_item(constraint[:value])
104+
end
105+
Interscript::Node::Rule::Sub.new(from, to, **opts)
106+
end
107+
108+
def build_run_rule(item)
109+
dep = item[:dependency]
110+
stage = item[:stage]
111+
Interscript::Node::Rule::Run.new(
112+
Interscript::Node::Rule::Run::Map.new(
113+
(dep ? "#{dep}:" : "") + stage.to_s,
114+
),
115+
)
116+
end
117+
118+
def convert_item(item)
119+
case item
120+
when Items::StringValue
121+
Interscript::Node::Item::String.new(item.value)
122+
when Items::None
123+
Interscript::Node::Item::String.new("")
124+
when Items::Primitive
125+
convert_primitive(item)
126+
when Items::AliasRef
127+
Interscript::Node::Item::Alias.new(item.name.to_sym)
128+
when Items::Capture
129+
Interscript::Node::Item::Capture.new(item.index)
130+
when Items::Function
131+
item.name.to_sym
132+
when Items::Concat
133+
convert_concat(item)
134+
when Items::CaptureGroup
135+
convert_item(item.inner)
136+
when Items::Maybe
137+
Interscript::Node::Item::Maybe.new(convert_item(item.inner))
138+
when Items::Some
139+
Interscript::Node::Item::Some.new(convert_item(item.inner))
140+
when Items::Range
141+
Interscript::Node::Item::Any.new(
142+
(item.lo..item.hi).map { |c| Interscript::Node::Item::String.new(c) },
143+
)
144+
when Items::Set
145+
convert_set(item)
146+
else
147+
Interscript::Node::Item::String.new(item.to_s)
148+
end
149+
end
150+
151+
def convert_primitive(item)
152+
case item.name
153+
when "boundary"
154+
Interscript::Node::Item::BeginBoundary.new
155+
when "line_start"
156+
Interscript::Node::Item::StartBoundary.new
157+
when "line_end"
158+
Interscript::Node::Item::EndBoundary.new
159+
when "word_boundary"
160+
Interscript::Node::Item::WordBoundary.new
161+
when "space"
162+
Interscript::Node::Item::String.new(" ")
163+
when "non_boundary"
164+
Interscript::Node::Item::NonBoundary.new
165+
end
166+
end
167+
168+
def convert_concat(concat)
169+
parts = concat.parts.map { |p| convert_item(p) }
170+
return parts.first if parts.size == 1
171+
parts.reduce { |acc, part| acc + part }
172+
end
173+
174+
def convert_set(set)
175+
Interscript::Node::Item::Any.new(
176+
set.chars.map { |c| Interscript::Node::Item::String.new(c) },
177+
)
178+
end
179+
end
180+
end
181+
end

0 commit comments

Comments
 (0)