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
2 changes: 1 addition & 1 deletion isc.artifact.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion lib/interscript/isc/grammar/concerns/primitives.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ module Primitives
# The corpus converter emits \U with 4 hex digits too
# (gki-bel "\U040E") — accept 4..8, don't be stricter than
# the corpus.
(str("U") >> match(/[0-9a-fA-F]/).repeat(4, 8).as(:unicode))
(str("U") >> match(/[0-9a-fA-F]/).repeat(4, 8).as(:unicode)) |
# Converter-corrupted files (un-mar) contain a bare \\U with no
# hex digits at all — parse it as a literal U so the map loads.
str("U").as(:u_lone)
)
end

Expand Down
3 changes: 2 additions & 1 deletion lib/interscript/isc/grammar/isc.parg
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ grammar InterscriptIsc version "0.1.0" {
esc_bs = "\\" as backslash
esc_uni4 = "u" (4hexd) as unicode
esc_uni8 = "U" (4*8hexd) as unicode
escape_sequence = "\\" (esc_nl / esc_cr / esc_tab / esc_dq / esc_bs / esc_uni4 / esc_uni8)
esc_u_lone = "U" as u_lone
escape_sequence = "\\" (esc_nl / esc_cr / esc_tab / esc_dq / esc_bs / esc_uni4 / esc_uni8 / esc_u_lone)

raw_run = 1*(!("\\" / "\"") chr)
dq_parts = *(escape_sequence / (raw_run as run))
Expand Down
79 changes: 65 additions & 14 deletions lib/interscript/isc/node_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def self.to_interscript_node(isc_doc)

def initialize(isc_doc)
@isc_doc = isc_doc
@imported = {}
# Local alias name -> raw value fragment, for set/constraint
# charset resolution (grek_latn_any = any([grek_any, …])).
@local_raw = Array(isc_doc[:aliases]).to_h { |a| [a[:name].to_sym, a[:value]] }
@charset_cache = {}
@resolving = []
end

def build
Expand All @@ -39,9 +45,17 @@ def build
# Ruby/JS compilers emit run directives and alias lookups as
# Maps.transliterate(stage.doc_name, ...) — without it every
# aliased-dependency run compiled to transliterate(nil).
build_dependencies(doc)
# Imported aliases (charset libraries) resolve NOW, at adapter
# time: their data is a charset that must compile as a character
# class in constraints. Left to the runtime, they degrade to
# vacuous fragments and context-gated rules fire wrongly
# everywhere (din-grc's word-initial γκ). Dependencies load
# FIRST so local aliases composed from imported charsets
# (grek_latn_any = any([grek_any, …])) resolve too.
@imported = doc.imported_aliases
doc.aliases = build_aliases(doc.name)
build_stages(doc.name).each { |name, stage| doc.stages[name] = stage }
build_dependencies(doc)
end
end

Expand Down Expand Up @@ -154,7 +168,7 @@ def build_rule(rule_def)
%i[before after not_before not_after].each do |k|
next unless rule_def[:constraints]&.any? { |c| c[:kind] == k }
constraint = rule_def[:constraints].find { |c| c[:kind] == k }
opts[k] = convert_item(constraint[:item])
opts[k] = convert_item(constraint[:item], :class)
end
Interscript::Node::Rule::Sub.new(from, to, **opts)
end
Expand All @@ -167,7 +181,7 @@ def build_run_rule(item)
Interscript::Node::Rule::Run.new(stage_ref)
end

def convert_item(item)
def convert_item(item, ctx = :word)
case item
when Items::StringValue
Interscript::Node::Item::String.new(item.value)
Expand All @@ -176,7 +190,7 @@ def convert_item(item)
when Items::Primitive
convert_primitive(item)
when Items::AliasRef
convert_alias_ref(item)
convert_alias_ref(item, ctx)
when Items::Capture
Interscript::Node::Item::CaptureRef.new(item.index)
when Items::Function
Expand Down Expand Up @@ -210,8 +224,42 @@ def convert_primitive(item)
Interscript::Node::Item::Alias.new(item.name.to_sym)
end

def convert_alias_ref(item)
Interscript::Node::Item::Alias.new(item.name.to_sym, map: item.map&.to_sym)
def convert_alias_ref(item, ctx = :word)
# An alias in class context (constraint position or inside a set)
# compiles with class semantics when it names a charset — imported
# library sets, or local aliases composed of them. Word-position
# aliases stay Alias nodes and resolve at runtime.
if ctx == :class
# Unresolvable names stay vacuous here — legacy Any(nil) parity.
resolve_charset(item.name.to_sym) || Interscript::Node::Item::String.new("")
else
Interscript::Node::Item::Alias.new(item.name.to_sym, map: item.map&.to_sym)
end
end

# The class-semantics node for a charset alias — imported library
# sets (Any or charset String) or local aliases composed of them —
# or nil. Memoized; a resolving set guards alias cycles.
def resolve_charset(name)
return @charset_cache[name] if @charset_cache.key?(name)
return nil if @resolving.include?(name)

@resolving << name
node =
if (imp = @imported[name])
data = imp.data
case data
when Interscript::Node::Item::Any
data
when Interscript::Node::Item::String
Interscript::Node::Item::Any.new(data.data)
end
elsif (raw = @local_raw[name])
converted = convert_item(raw)
converted if converted.is_a?(Interscript::Node::Item::Any) || converted.is_a?(Interscript::Node::Item::String)
end
@resolving.delete(name)
@charset_cache[name] = node
end

def convert_concat(concat)
Expand All @@ -234,14 +282,17 @@ def convert_set(set)
when Items::Primitive
convert_primitive(c)
when Items::AliasRef
# Legacy parity: a bare alias inside any(...) compiles to the
# Stdlib value (Any(nil) there for imported aliases). Keeping
# the Alias item resolves to the imported charset *string* at
# build time, baking a 1000-char literal into the constraint
# regexp — a lookbehind that never matches.
name = convert_alias_ref(c).name
val = Interscript::Stdlib::ALIASES[name]
val ? Interscript::Node::Item::Any.new(val) : Interscript::Node::Item::String.new("")
convert_alias_ref(c, :class) ||
begin
# Legacy parity: a bare alias inside any(...) compiles to the
# Stdlib value (Any(nil) there for imported aliases). Keeping
# the Alias item resolves to the imported charset *string* at
# build time, baking a 1000-char literal into the constraint
# regexp — a lookbehind that never matches.
name = convert_alias_ref(c).name
val = Interscript::Stdlib::ALIASES[name]
val ? Interscript::Node::Item::Any.new(val) : Interscript::Node::Item::String.new("")
end
else
convert_item(c)
end
Expand Down
3 changes: 3 additions & 0 deletions lib/interscript/isc/transform.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class Transform < Parsanol::Transform
rule(newline: simple(:_)) { "\n" }
rule(carriage_return: simple(:_)) { "\r" }
rule(tab: simple(:_)) { "\t" }
rule(u_lone: simple(:_)) { "U" }
rule(unicode: simple(:hex)) do
[hex.to_s.to_i(16)].pack("U")
rescue
Expand Down Expand Up @@ -71,6 +72,8 @@ def self.decode_string_parts(parts)
"\\"
elsif p.key?(:unicode)
[p[:unicode].to_s.to_i(16)].pack("U")
elsif p.key?(:u_lone)
"U"
else
p.to_s
end
Expand Down
Loading