Skip to content

Commit f2d29c1

Browse files
authored
Merge pull request #783 from interscript/feat/parg-complete
fix: charset alias resolution in class context — din-grc green (sweep 395→132)
2 parents 85673a3 + 2c3eab2 commit f2d29c1

5 files changed

Lines changed: 75 additions & 17 deletions

File tree

‎isc.artifact.json‎

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

‎lib/interscript/isc/grammar/concerns/primitives.rb‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ module Primitives
6565
# The corpus converter emits \U with 4 hex digits too
6666
# (gki-bel "\U040E") — accept 4..8, don't be stricter than
6767
# the corpus.
68-
(str("U") >> match(/[0-9a-fA-F]/).repeat(4, 8).as(:unicode))
68+
(str("U") >> match(/[0-9a-fA-F]/).repeat(4, 8).as(:unicode)) |
69+
# Converter-corrupted files (un-mar) contain a bare \\U with no
70+
# hex digits at all — parse it as a literal U so the map loads.
71+
str("U").as(:u_lone)
6972
)
7073
end
7174

‎lib/interscript/isc/grammar/isc.parg‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ grammar InterscriptIsc version "0.1.0" {
3939
esc_bs = "\\" as backslash
4040
esc_uni4 = "u" (4hexd) as unicode
4141
esc_uni8 = "U" (4*8hexd) as unicode
42-
escape_sequence = "\\" (esc_nl / esc_cr / esc_tab / esc_dq / esc_bs / esc_uni4 / esc_uni8)
42+
esc_u_lone = "U" as u_lone
43+
escape_sequence = "\\" (esc_nl / esc_cr / esc_tab / esc_dq / esc_bs / esc_uni4 / esc_uni8 / esc_u_lone)
4344

4445
raw_run = 1*(!("\\" / "\"") chr)
4546
dq_parts = *(escape_sequence / (raw_run as run))

‎lib/interscript/isc/node_adapter.rb‎

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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

‎lib/interscript/isc/transform.rb‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class Transform < Parsanol::Transform
4444
rule(newline: simple(:_)) { "\n" }
4545
rule(carriage_return: simple(:_)) { "\r" }
4646
rule(tab: simple(:_)) { "\t" }
47+
rule(u_lone: simple(:_)) { "U" }
4748
rule(unicode: simple(:hex)) do
4849
[hex.to_s.to_i(16)].pack("U")
4950
rescue
@@ -71,6 +72,8 @@ def self.decode_string_parts(parts)
7172
"\\"
7273
elsif p.key?(:unicode)
7374
[p[:unicode].to_s.to_i(16)].pack("U")
75+
elsif p.key?(:u_lone)
76+
"U"
7477
else
7578
p.to_s
7679
end

0 commit comments

Comments
 (0)