@@ -20,6 +20,12 @@ def self.to_interscript_node(isc_doc)
2020
2121 def initialize ( isc_doc )
2222 @isc_doc = isc_doc
23+ @imported = { }
24+ # Local alias name -> raw value fragment, for set/constraint
25+ # charset resolution (grek_latn_any = any([grek_any, …])).
26+ @local_raw = Array ( isc_doc [ :aliases ] ) . to_h { |a | [ a [ :name ] . to_sym , a [ :value ] ] }
27+ @charset_cache = { }
28+ @resolving = [ ]
2329 end
2430
2531 def build
@@ -39,9 +45,17 @@ def build
3945 # Ruby/JS compilers emit run directives and alias lookups as
4046 # Maps.transliterate(stage.doc_name, ...) — without it every
4147 # aliased-dependency run compiled to transliterate(nil).
48+ build_dependencies ( doc )
49+ # Imported aliases (charset libraries) resolve NOW, at adapter
50+ # time: their data is a charset that must compile as a character
51+ # class in constraints. Left to the runtime, they degrade to
52+ # vacuous fragments and context-gated rules fire wrongly
53+ # everywhere (din-grc's word-initial γκ). Dependencies load
54+ # FIRST so local aliases composed from imported charsets
55+ # (grek_latn_any = any([grek_any, …])) resolve too.
56+ @imported = doc . imported_aliases
4257 doc . aliases = build_aliases ( doc . name )
4358 build_stages ( doc . name ) . each { |name , stage | doc . stages [ name ] = stage }
44- build_dependencies ( doc )
4559 end
4660 end
4761
@@ -154,7 +168,7 @@ def build_rule(rule_def)
154168 %i[ before after not_before not_after ] . each do |k |
155169 next unless rule_def [ :constraints ] &.any? { |c | c [ :kind ] == k }
156170 constraint = rule_def [ :constraints ] . find { |c | c [ :kind ] == k }
157- opts [ k ] = convert_item ( constraint [ :item ] )
171+ opts [ k ] = convert_item ( constraint [ :item ] , :class )
158172 end
159173 Interscript ::Node ::Rule ::Sub . new ( from , to , **opts )
160174 end
@@ -167,7 +181,7 @@ def build_run_rule(item)
167181 Interscript ::Node ::Rule ::Run . new ( stage_ref )
168182 end
169183
170- def convert_item ( item )
184+ def convert_item ( item , ctx = :word )
171185 case item
172186 when Items ::StringValue
173187 Interscript ::Node ::Item ::String . new ( item . value )
@@ -176,7 +190,7 @@ def convert_item(item)
176190 when Items ::Primitive
177191 convert_primitive ( item )
178192 when Items ::AliasRef
179- convert_alias_ref ( item )
193+ convert_alias_ref ( item , ctx )
180194 when Items ::Capture
181195 Interscript ::Node ::Item ::CaptureRef . new ( item . index )
182196 when Items ::Function
@@ -210,8 +224,42 @@ def convert_primitive(item)
210224 Interscript ::Node ::Item ::Alias . new ( item . name . to_sym )
211225 end
212226
213- def convert_alias_ref ( item )
214- Interscript ::Node ::Item ::Alias . new ( item . name . to_sym , map : item . map &.to_sym )
227+ def convert_alias_ref ( item , ctx = :word )
228+ # An alias in class context (constraint position or inside a set)
229+ # compiles with class semantics when it names a charset — imported
230+ # library sets, or local aliases composed of them. Word-position
231+ # aliases stay Alias nodes and resolve at runtime.
232+ if ctx == :class
233+ # Unresolvable names stay vacuous here — legacy Any(nil) parity.
234+ resolve_charset ( item . name . to_sym ) || Interscript ::Node ::Item ::String . new ( "" )
235+ else
236+ Interscript ::Node ::Item ::Alias . new ( item . name . to_sym , map : item . map &.to_sym )
237+ end
238+ end
239+
240+ # The class-semantics node for a charset alias — imported library
241+ # sets (Any or charset String) or local aliases composed of them —
242+ # or nil. Memoized; a resolving set guards alias cycles.
243+ def resolve_charset ( name )
244+ return @charset_cache [ name ] if @charset_cache . key? ( name )
245+ return nil if @resolving . include? ( name )
246+
247+ @resolving << name
248+ node =
249+ if ( imp = @imported [ name ] )
250+ data = imp . data
251+ case data
252+ when Interscript ::Node ::Item ::Any
253+ data
254+ when Interscript ::Node ::Item ::String
255+ Interscript ::Node ::Item ::Any . new ( data . data )
256+ end
257+ elsif ( raw = @local_raw [ name ] )
258+ converted = convert_item ( raw )
259+ converted if converted . is_a? ( Interscript ::Node ::Item ::Any ) || converted . is_a? ( Interscript ::Node ::Item ::String )
260+ end
261+ @resolving . delete ( name )
262+ @charset_cache [ name ] = node
215263 end
216264
217265 def convert_concat ( concat )
@@ -234,14 +282,17 @@ def convert_set(set)
234282 when Items ::Primitive
235283 convert_primitive ( c )
236284 when Items ::AliasRef
237- # Legacy parity: a bare alias inside any(...) compiles to the
238- # Stdlib value (Any(nil) there for imported aliases). Keeping
239- # the Alias item resolves to the imported charset *string* at
240- # build time, baking a 1000-char literal into the constraint
241- # regexp — a lookbehind that never matches.
242- name = convert_alias_ref ( c ) . name
243- val = Interscript ::Stdlib ::ALIASES [ name ]
244- val ? Interscript ::Node ::Item ::Any . new ( val ) : Interscript ::Node ::Item ::String . new ( "" )
285+ convert_alias_ref ( c , :class ) ||
286+ begin
287+ # Legacy parity: a bare alias inside any(...) compiles to the
288+ # Stdlib value (Any(nil) there for imported aliases). Keeping
289+ # the Alias item resolves to the imported charset *string* at
290+ # build time, baking a 1000-char literal into the constraint
291+ # regexp — a lookbehind that never matches.
292+ name = convert_alias_ref ( c ) . name
293+ val = Interscript ::Stdlib ::ALIASES [ name ]
294+ val ? Interscript ::Node ::Item ::Any . new ( val ) : Interscript ::Node ::Item ::String . new ( "" )
295+ end
245296 else
246297 convert_item ( c )
247298 end
0 commit comments