|
| 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