interscript 2.4.5 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -0
  3. data/docs/demo/20191118-interscript-demo-cast.gif +0 -0
  4. data/exe/codemod-imp-to-isc +5 -0
  5. data/exe/diagnose_parse_failures +47 -0
  6. data/exe/interscript +2 -1
  7. data/exe/verify_isc_deep +211 -0
  8. data/exe/verify_isc_equivalence +128 -0
  9. data/interscript.gemspec +27 -20
  10. data/lib/interscript/command.rb +21 -12
  11. data/lib/interscript/compiler/javascript.rb +34 -35
  12. data/lib/interscript/compiler/json_ir.rb +214 -0
  13. data/lib/interscript/compiler/python.rb +354 -0
  14. data/lib/interscript/compiler/ruby.rb +23 -22
  15. data/lib/interscript/compiler.rb +20 -3
  16. data/lib/interscript/detector.rb +14 -7
  17. data/lib/interscript/dsl/aliases.rb +1 -1
  18. data/lib/interscript/dsl/document.rb +6 -3
  19. data/lib/interscript/dsl/group/parallel.rb +1 -1
  20. data/lib/interscript/dsl/group.rb +18 -9
  21. data/lib/interscript/dsl/items.rb +28 -16
  22. data/lib/interscript/dsl/metadata.rb +19 -18
  23. data/lib/interscript/dsl/stage.rb +1 -1
  24. data/lib/interscript/dsl/symbol_mm.rb +4 -2
  25. data/lib/interscript/dsl/tests.rb +1 -1
  26. data/lib/interscript/dsl.rb +21 -13
  27. data/lib/interscript/interpreter.rb +32 -21
  28. data/lib/interscript/isc/codemod.rb +791 -0
  29. data/lib/interscript/isc/document_builder.rb +354 -0
  30. data/lib/interscript/isc/generator.rb +191 -0
  31. data/lib/interscript/isc/grammar/concerns/aliases.rb +29 -0
  32. data/lib/interscript/isc/grammar/concerns/dependencies.rb +23 -0
  33. data/lib/interscript/isc/grammar/concerns/items.rb +176 -0
  34. data/lib/interscript/isc/grammar/concerns/metadata.rb +142 -0
  35. data/lib/interscript/isc/grammar/concerns/primitives.rb +112 -0
  36. data/lib/interscript/isc/grammar/concerns/stages.rb +129 -0
  37. data/lib/interscript/isc/grammar/concerns/system.rb +37 -0
  38. data/lib/interscript/isc/grammar/concerns/tests.rb +31 -0
  39. data/lib/interscript/isc/grammar/concerns.rb +18 -0
  40. data/lib/interscript/isc/grammar/core.rb +23 -0
  41. data/lib/interscript/isc/grammar/isc.artifact.json +1 -0
  42. data/lib/interscript/isc/grammar/isc.parg +196 -0
  43. data/lib/interscript/isc/grammar.rb +12 -0
  44. data/lib/interscript/isc/items.rb +185 -0
  45. data/lib/interscript/isc/model/alias.rb +20 -0
  46. data/lib/interscript/isc/model/constraint.rb +20 -0
  47. data/lib/interscript/isc/model/dependency.rb +19 -0
  48. data/lib/interscript/isc/model/document.rb +31 -0
  49. data/lib/interscript/isc/model/item.rb +50 -0
  50. data/lib/interscript/isc/model/rule.rb +23 -0
  51. data/lib/interscript/isc/model/stage.rb +20 -0
  52. data/lib/interscript/isc/model/stage_item.rb +33 -0
  53. data/lib/interscript/isc/model/test.rb +21 -0
  54. data/lib/interscript/isc/model.rb +19 -0
  55. data/lib/interscript/isc/node_adapter.rb +310 -0
  56. data/lib/interscript/isc/normalizer.rb +127 -0
  57. data/lib/interscript/isc/parser.rb +78 -0
  58. data/lib/interscript/isc/serializer.rb +248 -0
  59. data/lib/interscript/isc/transform.rb +138 -0
  60. data/lib/interscript/isc/yaml_bridge.rb +236 -0
  61. data/lib/interscript/isc.rb +30 -0
  62. data/lib/interscript/ml/byt5_onnx.rb +102 -0
  63. data/lib/interscript/ml/imf.rb +182 -0
  64. data/lib/interscript/ml/model.rb +52 -0
  65. data/lib/interscript/ml/provisioning.rb +189 -0
  66. data/lib/interscript/ml/translator.rb +38 -0
  67. data/lib/interscript/ml/vocab.rb +48 -0
  68. data/lib/interscript/ml.rb +25 -0
  69. data/lib/interscript/node/alias_def.rb +5 -5
  70. data/lib/interscript/node/dependency.rb +7 -7
  71. data/lib/interscript/node/document.rb +10 -10
  72. data/lib/interscript/node/group.rb +11 -9
  73. data/lib/interscript/node/item/alias.rb +14 -10
  74. data/lib/interscript/node/item/any.rb +14 -7
  75. data/lib/interscript/node/item/capture.rb +14 -9
  76. data/lib/interscript/node/item/group.rb +22 -17
  77. data/lib/interscript/node/item/repeat.rb +3 -3
  78. data/lib/interscript/node/item/stage.rb +4 -5
  79. data/lib/interscript/node/item/string.rb +18 -13
  80. data/lib/interscript/node/item.rb +27 -17
  81. data/lib/interscript/node/metadata.rb +6 -5
  82. data/lib/interscript/node/rule/funcall.rb +4 -5
  83. data/lib/interscript/node/rule/run.rb +4 -5
  84. data/lib/interscript/node/rule/sub.rb +55 -55
  85. data/lib/interscript/node/rule.rb +6 -5
  86. data/lib/interscript/node/stage.rb +11 -14
  87. data/lib/interscript/node/tests.rb +6 -6
  88. data/lib/interscript/node.rb +14 -17
  89. data/lib/interscript/stdlib/functions/rababa_adapter.rb +56 -0
  90. data/lib/interscript/stdlib/functions/secryst_adapter.rb +37 -0
  91. data/lib/interscript/stdlib/functions.rb +49 -0
  92. data/lib/interscript/stdlib.rb +25 -95
  93. data/lib/interscript/utils/helpers.rb +8 -5
  94. data/lib/interscript/utils/regexp_converter.rb +143 -144
  95. data/lib/interscript/version.rb +1 -1
  96. data/lib/interscript/visualize/json.rb +12 -13
  97. data/lib/interscript/visualize/nodes.rb +13 -13
  98. data/lib/interscript/visualize.rb +18 -18
  99. data/lib/interscript.rb +59 -45
  100. metadata +88 -31
  101. data/.github/workflows/assets.yml +0 -91
  102. data/.github/workflows/rake.yml +0 -52
  103. data/.github/workflows/release.yml +0 -68
  104. data/.gitignore +0 -79
  105. data/.rspec +0 -3
  106. data/Gemfile +0 -38
  107. data/Rakefile +0 -139
  108. data/bin/console +0 -10
  109. data/bin/interscript +0 -5
  110. data/bin/maps_analyze_staging +0 -168
  111. data/bin/maps_debug_compilers +0 -58
  112. data/bin/maps_debug_ordering +0 -88
  113. data/bin/maps_debug_ruby_compile +0 -24
  114. data/bin/maps_debug_step_by_step +0 -44
  115. data/bin/maps_optimize_order +0 -112
  116. data/bin/maps_v1_analyze_regexps +0 -45
  117. data/bin/maps_v1_to_v2 +0 -426
  118. data/bin/set_version +0 -16
  119. data/bin/setup +0 -8
  120. data/requirements.txt +0 -1
@@ -1,9 +1,9 @@
1
1
  begin
2
- require 'mini_racer'
2
+ require "mini_racer"
3
3
  rescue LoadError
4
4
  # Ignore loading error
5
5
  end
6
- require 'json'
6
+ require "json"
7
7
 
8
8
  class Interscript::Compiler::Javascript < Interscript::Compiler
9
9
  def compile(map, debug: false)
@@ -12,24 +12,23 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
12
12
  @parallel_regexps = {}
13
13
  @debug = debug
14
14
  c = "var map = function(Interscript) {"
15
- c << "Interscript.define_map(#{map.name.inspect}, function(Interscript, map) {\n";
15
+ c << "Interscript.define_map(#{map.name.inspect}, function(Interscript, map) {\n"
16
16
  c << "map.dependencies = #{map.dependencies.map(&:full_name).to_json};\n"
17
- c
18
17
 
19
18
  map.aliases.each do |name, value|
20
19
  val = compile_item(value.data, map, :str)
21
20
  c << "map.aliases.#{name} = #{val};\n"
22
- val = '"'+compile_item(value.data, map, :re)+'"'
21
+ val = '"' + compile_item(value.data, map, :re) + '"'
23
22
  c << "map.aliases_re.#{name} = #{val};\n"
24
23
  end
25
24
 
26
25
  map.stages.each do |_, stage|
27
26
  c << compile_rule(stage, @map, true)
28
27
  end
29
- @parallel_trees.each do |k,v|
28
+ @parallel_trees.each do |k, v|
30
29
  c << "map.cache.PTREE_#{k} = #{v.to_json};\n"
31
30
  end
32
- @parallel_regexps.each do |k,v|
31
+ @parallel_regexps.each do |k, v|
33
32
  v = "[\"#{v[0]}\", #{v[1].to_json}]"
34
33
  c << "map.cache.PRE_#{k} = #{v};\n"
35
34
  end
@@ -44,10 +43,10 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
44
43
 
45
44
  def parallel_regexp_compile(subs_hash)
46
45
  # puts subs_hash.inspect
47
- regexp = subs_hash.each_with_index.map do |p,i|
48
- "(?<_%d>%s)" % [i,p[0]]
46
+ subs_hash.each_with_index.map do |p, i|
47
+ "(?<_%d>%s)" % [i, p[0]]
49
48
  end.join("|")
50
- subs_regexp = regexp
49
+
51
50
  # puts subs_regexp.inspect
52
51
  end
53
52
 
@@ -90,7 +89,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
90
89
  a = []
91
90
  Interscript::Stdlib.deterministic_sort_by_max_length(r.children).each do |i|
92
91
  raise Interscript::SystemConversionError, "Can't parallelize #{i.class}" unless Interscript::Node::Rule::Sub === i
93
-
92
+
94
93
  next if i.reverse_run == true
95
94
  a << [build_regexp(i, map), compile_item(i.to, map, :parstr)]
96
95
  end
@@ -102,13 +101,13 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
102
101
  c += "s = Interscript.parallel_regexp_gsub(s, map.cache.PRE_#{ah});\n"
103
102
  end
104
103
  when Interscript::Node::Rule::Sub
105
- from = %{"#{build_regexp(r, map).gsub("/", "\\\\/")}"}
106
- if r.to == :upcase
107
- to = 'function(a){return a.toUpperCase();}'
104
+ from = %("#{build_regexp(r, map).gsub("/", "\\\\/")}")
105
+ to = if r.to == :upcase
106
+ "function(a){return a.toUpperCase();}"
108
107
  elsif r.to == :downcase
109
- to = 'function(a){return a.toLowerCase();}'
108
+ "function(a){return a.toLowerCase();}"
110
109
  else
111
- to = compile_item(r.to, map, :str)
110
+ compile_item(r.to, map, :str)
112
111
  end
113
112
  c += "s = Interscript.gsub(s, #{from}, #{to});\n"
114
113
  when Interscript::Node::Rule::Funcall
@@ -127,7 +126,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
127
126
  c
128
127
  end
129
128
 
130
- def build_regexp(r, map=@map)
129
+ def build_regexp(r, map = @map)
131
130
  from = compile_item(r.from, map, :re)
132
131
  before = compile_item(r.before, map, :re) if r.before
133
132
  after = compile_item(r.after, map, :re) if r.after
@@ -144,7 +143,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
144
143
  re
145
144
  end
146
145
 
147
- def compile_item i, doc=@map, target=nil
146
+ def compile_item i, doc = @map, target = nil
148
147
  i = i.first_string if %i[str parstr].include? target
149
148
  i = Interscript::Node::Item.try_convert(i)
150
149
  if target == :parstr
@@ -152,7 +151,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
152
151
  target = :par
153
152
  end
154
153
 
155
- out = case i
154
+ case i
156
155
  when Interscript::Node::Item::Alias
157
156
  astr = if i.map
158
157
  d = doc.dep_aliases[i.map].document
@@ -173,14 +172,14 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
173
172
  end
174
173
 
175
174
  if target == :str
176
- astr = astr.sub("_ALIASTYPE(", "(")
175
+ astr.sub("_ALIASTYPE(", "(")
177
176
  elsif target == :re
178
- astr = %{"+#{astr.sub("_ALIASTYPE(", "_re(")}+"}
177
+ %("+#{astr.sub("_ALIASTYPE(", "_re(")}+")
179
178
  elsif parstr && stdlib_alias
180
- astr = Interscript::Stdlib::ALIASES[i.name]
179
+ Interscript::Stdlib::ALIASES[i.name]
181
180
  elsif target == :par
182
181
  # raise NotImplementedError, "Can't use aliases in parallel mode yet"
183
- astr = Interscript::Stdlib::ALIASES[i.name]
182
+ Interscript::Stdlib::ALIASES[i.name]
184
183
  end
185
184
  when Interscript::Node::Item::String
186
185
  if target == :str
@@ -195,7 +194,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
195
194
  if target == :par
196
195
  i.children.map do |j|
197
196
  compile_item(j, doc, target)
198
- end.reduce([""]) do |j,k|
197
+ end.reduce([""]) do |j, k|
199
198
  Array(j).product(Array(k)).map(&:join)
200
199
  end
201
200
  elsif target == :str
@@ -212,9 +211,9 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
212
211
  Interscript::Node::Item::MaybeSome,
213
212
  Interscript::Node::Item::Some
214
213
 
215
- resuffix = { Interscript::Node::Item::Maybe => "?" ,
216
- Interscript::Node::Item::Some => "+" ,
217
- Interscript::Node::Item::MaybeSome => "*" }[i.class]
214
+ resuffix = {Interscript::Node::Item::Maybe => "?",
215
+ Interscript::Node::Item::Some => "+",
216
+ Interscript::Node::Item::MaybeSome => "*"}[i.class]
218
217
 
219
218
  if target == :par
220
219
  raise Interscript::SystemConversionError, "Can't use a MaybeSome in a #{target} context"
@@ -241,7 +240,7 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
241
240
  case i.value
242
241
  when Array
243
242
  data = i.data.map { |j| compile_item(j, doc, target) }
244
- "(?:"+data.join("|")+")"
243
+ "(?:" + data.join("|") + ")"
245
244
  when String
246
245
  "[#{Regexp.escape(i.value)}]"
247
246
  when Range
@@ -270,28 +269,28 @@ class Interscript::Compiler::Javascript < Interscript::Compiler
270
269
  ctx = self.class.ctx
271
270
  unless ctx
272
271
  ctx = MiniRacer::Context.new
273
- ctx.eval File.read(__dir__+"/../../../../js/test-compiler/xregexp.js")
272
+ ctx.eval File.read(__dir__ + "/../../../../js/test-compiler/xregexp.js")
274
273
  # Compatibility with Safari: will come later
275
- #ctx.eval File.read(__dir__+"/../../../js/xregexp-oniguruma.js")
276
- ctx.eval File.read(__dir__+"/../../../../js/src/stdlib.js")
274
+ # ctx.eval File.read(__dir__+"/../../../js/xregexp-oniguruma.js")
275
+ ctx.eval File.read(__dir__ + "/../../../../js/src/stdlib.js")
277
276
  self.class.ctx = ctx
278
277
  end
279
- #puts @code
278
+ # puts @code
280
279
  ctx.eval @code
281
280
  self.class.maps_loaded[@map.name] = true
282
281
  end
283
282
  end
284
283
 
285
- def call(str, stage=:main)
284
+ def call(str, stage = :main)
286
285
  load
287
286
  self.class.ctx.eval "Interscript.transliterate(#{@map.name.to_json}, #{str.to_json}, #{stage.to_json})"
288
287
  end
289
288
 
290
289
  def self.read_debug_data
291
- self.ctx.eval "globalThis.map_debug || []"
290
+ ctx.eval "globalThis.map_debug || []"
292
291
  end
293
292
 
294
293
  def self.reset_debug_data
295
- self.ctx.eval "globalThis.map_debug = []"
294
+ ctx.eval "globalThis.map_debug = []"
296
295
  end
297
296
  end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ # JSON IR compiler — emits a data-only intermediate representation consumable
6
+ # by non-Ruby runtimes (e.g. interscript-ts). Unlike Compiler::Javascript,
7
+ # which emits imperative JS that calls into a runtime, this emits a pure-data
8
+ # JSON document describing the AST.
9
+ #
10
+ # The shape is versioned via SCHEMA_VERSION. Runtimes should refuse to load
11
+ # IR with an unknown schema.
12
+ class Interscript::Compiler::JsonIR < Interscript::Compiler
13
+ SCHEMA_VERSION = 1
14
+
15
+ CompiledResult = Struct.new(:code) do
16
+ # JsonIR output is already JSON. The runtime treats .code as opaque
17
+ # text; consumers parse via JSON.parse.
18
+ def to_json(*)
19
+ code
20
+ end
21
+ end
22
+
23
+ def compile(map, debug: false)
24
+ @map = map
25
+ @c = JSON.pretty_generate(serialise_document(map))
26
+ self
27
+ end
28
+
29
+ def code
30
+ @c
31
+ end
32
+
33
+ private
34
+
35
+ def serialise_document(doc)
36
+ # Merge aliases from the document AND from dependency documents so
37
+ # that consumers (interscript-ts) can resolve every alias reference
38
+ # without re-implementing the Ruby dep_aliases indirection.
39
+ all_aliases = {}
40
+
41
+ # Only the document's own aliases are serialised. Library and
42
+ # dependency aliases resolve at runtime via the alias `map:` qualifier,
43
+ # matching the production corpus shape.
44
+ doc.aliases.each do |name, defn|
45
+ all_aliases[name.to_s] = serialise_item(defn.data)
46
+ end
47
+
48
+ {
49
+ schemaVersion: SCHEMA_VERSION,
50
+ systemCode: doc.name.to_s,
51
+ dependencies: doc.dependencies.map(&:full_name),
52
+ metadata: serialise_metadata(doc.metadata),
53
+ stages: serialise_stages(doc.stages),
54
+ aliases: all_aliases,
55
+ functions: {}
56
+ }
57
+ end
58
+
59
+ def serialise_metadata(metadata)
60
+ return {} unless metadata
61
+ out = {}
62
+ metadata.data.each do |k, v|
63
+ out[k.to_s] = case v
64
+ when Symbol then v.to_s
65
+ else v
66
+ end
67
+ end
68
+ out
69
+ end
70
+
71
+ def serialise_stages(stages)
72
+ # Document#stages returns Hash{name => Stage} for the document's own stages,
73
+ # but may also include imported stages. Iterate values and skip anything
74
+ # that isn't an Interscript::Node::Stage (e.g. imported Hash entries).
75
+ stages.values.map { |s| serialise_stage(s) if s.is_a?(Interscript::Node::Stage) }.compact
76
+ end
77
+
78
+ def serialise_stage(stage)
79
+ {
80
+ kind: "stage",
81
+ name: stage.name.to_s,
82
+ rules: stage.children.map { |r| serialise_rule(r) }
83
+ }
84
+ end
85
+
86
+ def serialise_rule(rule)
87
+ case rule
88
+ when Interscript::Node::Rule::Sub
89
+ serialise_sub_rule(rule)
90
+ when Interscript::Node::Rule::Run
91
+ serialise_run_rule(rule)
92
+ when Interscript::Node::Rule::Funcall
93
+ serialise_funcall_rule(rule)
94
+ when Interscript::Node::Group::Parallel
95
+ # Parallel rule groups contain sub-rules that are applied simultaneously.
96
+ # In IR, we emit them as a marker rule so the runtime can decide.
97
+ {
98
+ kind: "parallel",
99
+ rules: rule.children.map { |r| serialise_rule(r) }
100
+ }
101
+ when Interscript::Node::Group::Sequential
102
+ {
103
+ kind: "sequential",
104
+ rules: rule.children.map { |r| serialise_rule(r) }
105
+ }
106
+ else
107
+ raise Interscript::MapLogicError, "Cannot serialise rule of type #{rule.class}"
108
+ end
109
+ end
110
+
111
+ def serialise_sub_rule(rule)
112
+ out = {kind: "sub"}
113
+ out[:from] = serialise_item(rule.from) if rule.from
114
+ out[:to] = serialise_to(rule.to)
115
+ out[:before] = serialise_item(rule.before) if rule.before
116
+ out[:after] = serialise_item(rule.after) if rule.after
117
+ out[:notBefore] = serialise_item(rule.not_before) if rule.not_before
118
+ out[:notAfter] = serialise_item(rule.not_after) if rule.not_after
119
+ out[:priority] = rule.priority if rule.priority
120
+ out
121
+ end
122
+
123
+ def serialise_run_rule(rule)
124
+ stage = rule.stage
125
+ doc_name = stage.map
126
+ if doc_name && @map.respond_to?(:dep_aliases) && @map.dep_aliases[doc_name.to_sym]
127
+ resolved = @map.dep_aliases[doc_name.to_sym].document
128
+ doc_name = resolved.name.to_s if resolved.respond_to?(:name)
129
+ end
130
+ {
131
+ kind: "run",
132
+ stage: stage.name.to_s,
133
+ docName: doc_name&.to_s
134
+ }
135
+ end
136
+
137
+ def serialise_funcall_rule(rule)
138
+ {
139
+ kind: "funcall",
140
+ name: rule.name.to_s,
141
+ kwargs: symbolise_keys(rule.kwargs)
142
+ }
143
+ end
144
+
145
+ def serialise_to(to)
146
+ case to
147
+ when Symbol
148
+ {kind: "funcall_inline", name: to.to_s}
149
+ else
150
+ serialise_item(to)
151
+ end
152
+ end
153
+
154
+ def serialise_item(item)
155
+ return {kind: "any", of: item.map { |i| i.is_a?(String) ? {kind: "string", value: i} : serialise_item(i) }} if item.is_a?(::Array)
156
+ case item
157
+ when Interscript::Node::Item::String
158
+ {kind: "string", value: item.data}
159
+ when Interscript::Node::Item::CaptureGroup
160
+ {kind: "capture_group", data: serialise_item(item.data)}
161
+ when Interscript::Node::Item::CaptureRef
162
+ {kind: "capture_ref", id: item.id}
163
+ when Interscript::Node::Item::Alias
164
+ out = {kind: "alias", name: item.name.to_s}
165
+ out[:map] = item.map if item.map
166
+ out
167
+ when Interscript::Node::Item::Any
168
+ # Any with a String payload becomes a character class in Ruby's
169
+ # regex compilation. Range payloads become `[first-last]`. Both
170
+ # must survive IR serialisation as char-class form, NOT expanded
171
+ # into alternatives — Ruby's String#succ expansion of "a".."ᖵ"
172
+ # produces nonsense like "zzz" and misses most of the BMP.
173
+ case item.value
174
+ when ::Range
175
+ {kind: "any_char_class", range: [item.value.first, item.value.last]}
176
+ when ::String
177
+ {kind: "any_char_class", chars: item.value.chars}
178
+ else
179
+ data = item.data || []
180
+ {kind: "any", of: data.map { |i| serialise_item(i) }}
181
+ end
182
+ when Interscript::Node::Item::Group
183
+ {kind: "group", items: item.children.map { |i| serialise_item(i) }}
184
+ when Interscript::Node::Item::Repeat
185
+ {kind: "repeat", item: serialise_item(item.data), min: 0, max: nil}
186
+ when Interscript::Node::Item::Stage
187
+ {kind: "stage_ref", name: item.name.to_s}
188
+ when nil
189
+ nil
190
+ else
191
+ raise Interscript::MapLogicError, "Cannot serialise item of type #{item.class}"
192
+ end
193
+ end
194
+
195
+ def capture_index(_item)
196
+ # CaptureGroup index is determined by position in pattern compilation.
197
+ # Runtimes should treat as a placeholder; the actual index is computed
198
+ # during pattern compilation based on capture-group ordering.
199
+ 0
200
+ end
201
+
202
+ def serialise_aliases(aliases)
203
+ out = {}
204
+ aliases.each do |name, defn|
205
+ out[name.to_s] = serialise_item(defn.data)
206
+ end
207
+ out
208
+ end
209
+
210
+ def symbolise_keys(hash)
211
+ return {} if hash.nil?
212
+ hash.transform_keys(&:to_s)
213
+ end
214
+ end