interscript 3.2.1 → 3.4.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.
- checksums.yaml +4 -4
- data/README.md +26 -0
- data/interscript.gemspec +1 -1
- data/lib/interscript/interpreter.rb +61 -6
- data/lib/interscript/isc/document_builder.rb +47 -2
- data/lib/interscript/isc/grammar/concerns/items.rb +8 -1
- data/lib/interscript/isc/grammar/concerns/stages.rb +41 -5
- data/lib/interscript/isc/grammar/concerns/system.rb +13 -2
- data/lib/interscript/isc/grammar/isc.parg +20 -5
- data/lib/interscript/isc/model/document.rb +5 -0
- data/lib/interscript/isc/model/rule.rb +2 -0
- data/lib/interscript/isc/model/ruleset.rb +21 -0
- data/lib/interscript/isc/model/stage_item.rb +9 -1
- data/lib/interscript/isc/model.rb +1 -0
- data/lib/interscript/isc/node_adapter.rb +192 -29
- data/lib/interscript/isc/normalizer.rb +21 -0
- data/lib/interscript/isc/serializer.rb +34 -0
- data/lib/interscript/isc/transform.rb +15 -0
- data/lib/interscript/isc/yaml_bridge.rb +37 -3
- data/lib/interscript/node/document.rb +18 -6
- data/lib/interscript/node/group/parallel.rb +4 -0
- data/lib/interscript/node/languages_decl.rb +26 -0
- data/lib/interscript/node/rule/sub.rb +11 -3
- data/lib/interscript/node/rule/use.rb +31 -0
- data/lib/interscript/node/rule.rb +1 -0
- data/lib/interscript/node/ruleset.rb +6 -0
- data/lib/interscript/node.rb +2 -0
- data/lib/interscript/version.rb +1 -1
- data/lib/interscript.rb +36 -3
- metadata +8 -4
|
@@ -26,10 +26,16 @@ module Interscript
|
|
|
26
26
|
@local_raw = Array(isc_doc[:aliases]).to_h { |a| [a[:name].to_sym, a[:value]] }
|
|
27
27
|
@charset_cache = {}
|
|
28
28
|
@resolving = []
|
|
29
|
+
# ISC-WF-1 W1: memoized fully-expanded rulesets, keyed
|
|
30
|
+
# [dependency alias or nil, ruleset name]; @resolving_rulesets
|
|
31
|
+
# guards `use` cycles (a load error, spec §6.4).
|
|
32
|
+
@ruleset_memo = {}
|
|
33
|
+
@resolving_rulesets = []
|
|
29
34
|
end
|
|
30
35
|
|
|
31
36
|
def build
|
|
32
37
|
Interscript::Node::Document.new.tap do |doc|
|
|
38
|
+
@doc = doc
|
|
33
39
|
doc.metadata = build_metadata
|
|
34
40
|
doc.tests = build_tests
|
|
35
41
|
# Maps are addressed by their file-derived name (locate() and
|
|
@@ -55,6 +61,10 @@ module Interscript
|
|
|
55
61
|
# (grek_latn_any = any([grek_any, …])) resolve too.
|
|
56
62
|
@imported = doc.imported_aliases
|
|
57
63
|
doc.aliases = build_aliases(doc.name)
|
|
64
|
+
# ISC-WF-1: declarations before stage building — `use` expansion
|
|
65
|
+
# resolves against the document's rulesets and its dependencies'.
|
|
66
|
+
doc.languages = build_languages
|
|
67
|
+
doc.rulesets = build_rulesets(doc.name)
|
|
58
68
|
build_stages(doc.name).each { |name, stage| doc.stages[name] = stage }
|
|
59
69
|
end
|
|
60
70
|
end
|
|
@@ -126,39 +136,189 @@ module Interscript
|
|
|
126
136
|
end
|
|
127
137
|
end
|
|
128
138
|
|
|
139
|
+
# ISC-WF-1 W1: build + memoize every declared ruleset, fully
|
|
140
|
+
# expanded (nested `use` resolved, cycles rejected).
|
|
141
|
+
def build_rulesets(doc_name)
|
|
142
|
+
Array(@isc_doc[:rulesets]).each_with_object({}) do |rs, h|
|
|
143
|
+
h[rs[:name].to_sym] = resolve_ruleset(nil, rs[:name], doc_name: doc_name)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def build_languages
|
|
148
|
+
return nil unless @isc_doc[:languages]&.any?
|
|
149
|
+
|
|
150
|
+
Interscript::Node::LanguagesDecl.new(@isc_doc[:languages].map(&:to_sym))
|
|
151
|
+
end
|
|
152
|
+
|
|
129
153
|
def build_stage(stage_def)
|
|
130
154
|
stage = Interscript::Node::Stage.new(stage_def[:name].to_sym)
|
|
131
|
-
stage_def[:body].each
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
expand_items(stage_def[:body]).each { |node| stage.children << node }
|
|
156
|
+
stage
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Build the Nodes for one ISC stage-item def (no `use` — that goes
|
|
160
|
+
# through expand_use). Returns a list; sequence blocks contribute
|
|
161
|
+
# their rules individually, parallel blocks one group.
|
|
162
|
+
def build_stage_item_nodes(item)
|
|
163
|
+
case item[:kind]
|
|
164
|
+
when :parallel
|
|
165
|
+
group = Interscript::Node::Group::Parallel.new
|
|
166
|
+
item[:rules].each { |r| group.children << build_rule(r) }
|
|
167
|
+
[group]
|
|
168
|
+
when :sequence
|
|
169
|
+
item[:rules].map { |r| build_rule(r) }
|
|
170
|
+
when :bare_rule
|
|
171
|
+
[build_rule(item[:rule])]
|
|
172
|
+
when :run
|
|
173
|
+
[build_run_rule(item)]
|
|
174
|
+
when :separate
|
|
175
|
+
kwargs = {}
|
|
176
|
+
kwargs[:separator] = item[:separator].value if item[:separator]
|
|
177
|
+
[Interscript::Node::Rule::Funcall.new(:separate, **kwargs)]
|
|
178
|
+
when :string_case
|
|
179
|
+
[Interscript::Node::Rule::Funcall.new(item[:op].to_sym,
|
|
180
|
+
**(item[:kwargs] || {}).transform_keys(&:to_sym))]
|
|
181
|
+
when :compose
|
|
182
|
+
[Interscript::Node::Rule::Funcall.new(:compose)]
|
|
183
|
+
when :decompose
|
|
184
|
+
[Interscript::Node::Rule::Funcall.new(:decompose)]
|
|
185
|
+
when :funcall
|
|
186
|
+
[Interscript::Node::Rule::Funcall.new(
|
|
187
|
+
item[:name].to_sym,
|
|
188
|
+
**item[:kwargs].transform_keys(&:to_sym)
|
|
189
|
+
)]
|
|
190
|
+
else
|
|
191
|
+
[]
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# -- ISC-WF-1 W1/W2: ruleset expansion --------------------------------
|
|
196
|
+
|
|
197
|
+
# Expand a list of ISC stage-item defs into Node children, resolving
|
|
198
|
+
# `use` directives recursively. Used for both stage bodies and
|
|
199
|
+
# ruleset bodies (a ruleset body is a stage body, spec §3.1).
|
|
200
|
+
def expand_items(items)
|
|
201
|
+
Array(items).flat_map do |item|
|
|
202
|
+
if item.is_a?(Hash) && item[:kind] == :use
|
|
203
|
+
expand_use(item)
|
|
204
|
+
else
|
|
205
|
+
build_stage_item_nodes(item)
|
|
159
206
|
end
|
|
160
207
|
end
|
|
161
|
-
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Resolve + deep-copy + merge-override one `use` site (spec §3.2, §4).
|
|
211
|
+
def expand_use(item)
|
|
212
|
+
rs = resolve_ruleset(item[:dependency], item[:ruleset])
|
|
213
|
+
apply_overrides(deep_copy_children(rs.children), item[:overrides])
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# The fully-expanded Node::Ruleset for a reference, memoized.
|
|
217
|
+
# Same-map rulesets build here; dependency rulesets arrive already
|
|
218
|
+
# built through the recursive adapter of the dependency document.
|
|
219
|
+
def resolve_ruleset(dep_alias, name, doc_name: nil)
|
|
220
|
+
key = [dep_alias&.to_sym, name.to_sym]
|
|
221
|
+
return @ruleset_memo[key] if @ruleset_memo&.key?(key)
|
|
222
|
+
raise cycle_error(name) if @resolving_rulesets.include?(key)
|
|
223
|
+
|
|
224
|
+
@resolving_rulesets << key
|
|
225
|
+
node =
|
|
226
|
+
if dep_alias
|
|
227
|
+
dep = @doc.dep_aliases[dep_alias.to_sym] ||
|
|
228
|
+
raise(Interscript::MapLogicError,
|
|
229
|
+
"use #{dep_alias}.#{name}: no declared dependency aliased #{dep_alias}")
|
|
230
|
+
dep.document&.rulesets&.[](name.to_sym) ||
|
|
231
|
+
raise(Interscript::MapLogicError,
|
|
232
|
+
"use #{dep_alias}.#{name}: ruleset not found in dependency #{dep.full_name}")
|
|
233
|
+
else
|
|
234
|
+
rs_def = Array(@isc_doc[:rulesets]).find { |r| r[:name] == name } ||
|
|
235
|
+
raise(Interscript::MapLogicError, "use #{name}: ruleset not found")
|
|
236
|
+
Interscript::Node::Ruleset.new(rs_def[:name].to_sym).tap do |rs|
|
|
237
|
+
rs.doc_name = doc_name
|
|
238
|
+
expand_items(rs_def[:body]).each { |child| rs.children << child }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
@ruleset_memo[key] = node
|
|
242
|
+
node
|
|
243
|
+
ensure
|
|
244
|
+
@resolving_rulesets.delete(key)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def cycle_error(name)
|
|
248
|
+
Interscript::MapLogicError.new(
|
|
249
|
+
"use cycle: ruleset #{name} revisited through #{@resolving_rulesets.map { |_, n| n }.join(" -> ")}"
|
|
250
|
+
)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Per-site deep copy: expansions must not share Group nodes (the
|
|
254
|
+
# Interpreter caches compiled pass trees on them) nor be mutated by
|
|
255
|
+
# another site's override merge. Rule/Item leaves are read-only at
|
|
256
|
+
# execution time and may be shared.
|
|
257
|
+
def deep_copy_children(children)
|
|
258
|
+
children.map { |node| deep_copy_node(node) }
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def deep_copy_node(node)
|
|
262
|
+
case node
|
|
263
|
+
when Interscript::Node::Group
|
|
264
|
+
copy = node.class.new(reverse_run: node.reverse_run)
|
|
265
|
+
if node.is_a?(Interscript::Node::Stage)
|
|
266
|
+
copy.name = node.name
|
|
267
|
+
copy.doc_name = node.doc_name
|
|
268
|
+
copy.dont_reverse = node.dont_reverse
|
|
269
|
+
end
|
|
270
|
+
copy.children = node.children.map { |c| deep_copy_node(c) }
|
|
271
|
+
copy
|
|
272
|
+
else
|
|
273
|
+
node.dup
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# ISC-WF-1 W2 merge (spec §4.2). R = the fully expanded ruleset rows
|
|
278
|
+
# (document order), O = the override rows.
|
|
279
|
+
# 1. A row whose from is byte-exact equal to an override from is
|
|
280
|
+
# REPLACED — removed from wherever it sits; all same-from
|
|
281
|
+
# duplicates go (the ruleset's internal last-wins winner is
|
|
282
|
+
# superseded too). From-equality is computed on the node's
|
|
283
|
+
# structural inspect — for plain strings this is exactly the
|
|
284
|
+
# byte-exact string of the corpus convention.
|
|
285
|
+
# 2. Kept rows keep their order and block structure (§4.2.2 with
|
|
286
|
+
# the §3.2 pass-structure inheritance).
|
|
287
|
+
# 3. The override rows land at the expansion tail: appended INTO
|
|
288
|
+
# the final parallel block when the expansion ends with one, so
|
|
289
|
+
# the child rows join that block's single longest-match pass
|
|
290
|
+
# (§4.2.5, §4.3 — replacement on the source-script pass, not
|
|
291
|
+
# post-pass re-substitution); otherwise appended as trailing
|
|
292
|
+
# rows. Unmatched overrides are simply added (§4.2.4).
|
|
293
|
+
def apply_overrides(children, overrides)
|
|
294
|
+
return children if overrides.nil? || overrides.empty?
|
|
295
|
+
|
|
296
|
+
o_rules = overrides.map { |r| build_rule(r) }
|
|
297
|
+
o_keys = o_rules.map { |r| from_key(r) }
|
|
298
|
+
|
|
299
|
+
kept = children.filter_map do |node|
|
|
300
|
+
case node
|
|
301
|
+
when Interscript::Node::Group::Parallel
|
|
302
|
+
node.children.reject! { |c| c.is_a?(Interscript::Node::Rule::Sub) && o_keys.include?(from_key(c)) }
|
|
303
|
+
node
|
|
304
|
+
when Interscript::Node::Rule::Sub
|
|
305
|
+
o_keys.include?(from_key(node)) ? nil : node
|
|
306
|
+
else
|
|
307
|
+
node
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
tail = kept.last
|
|
312
|
+
if tail.is_a?(Interscript::Node::Group::Parallel)
|
|
313
|
+
tail.children.push(*o_rules)
|
|
314
|
+
else
|
|
315
|
+
kept.push(*o_rules)
|
|
316
|
+
end
|
|
317
|
+
kept
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def from_key(rule)
|
|
321
|
+
rule.from.inspect
|
|
162
322
|
end
|
|
163
323
|
|
|
164
324
|
def build_rule(rule_def)
|
|
@@ -170,6 +330,9 @@ module Interscript
|
|
|
170
330
|
constraint = rule_def[:constraints].find { |c| c[:kind] == k }
|
|
171
331
|
opts[k] = convert_item(constraint[:item], :class)
|
|
172
332
|
end
|
|
333
|
+
# ISC-WF-1 W3: the language guard rides on the rule; inert unless
|
|
334
|
+
# the interpreter selects a declared language.
|
|
335
|
+
opts[:when_languages] = rule_def[:when_languages] if rule_def[:when_languages]&.any?
|
|
173
336
|
Interscript::Node::Rule::Sub.new(from, to, **opts)
|
|
174
337
|
end
|
|
175
338
|
|
|
@@ -69,6 +69,16 @@ module Interscript
|
|
|
69
69
|
{tests: merge_tests(items)}
|
|
70
70
|
elsif block.key?(:stage)
|
|
71
71
|
{stage_name: scalar(block[:stage_name]), stage: normalize_stage_items(block[:stage])}
|
|
72
|
+
elsif block.key?(:ruleset)
|
|
73
|
+
# ISC-WF-1 W1: same shape as a stage, under the ruleset key.
|
|
74
|
+
{ruleset_name: scalar(block[:ruleset_name]), ruleset: normalize_stage_items(block[:ruleset])}
|
|
75
|
+
elsif block.key?(:languages)
|
|
76
|
+
# ISC-WF-1 W3: `languages a, b` folds to a plain list of names.
|
|
77
|
+
# One language captures as a single {language:} hash, several as
|
|
78
|
+
# a list.
|
|
79
|
+
langs = block[:languages]
|
|
80
|
+
list = langs.is_a?(Hash) ? [langs] : Array(langs)
|
|
81
|
+
{languages: list.map { |l| l.is_a?(Hash) ? scalar(l[:language]) : scalar(l) }}
|
|
72
82
|
elsif block.key?(:target)
|
|
73
83
|
{target: scalar(block[:target]), alias: scalar(block[:alias])}.compact
|
|
74
84
|
else
|
|
@@ -85,6 +95,17 @@ module Interscript
|
|
|
85
95
|
%i[parallel sequence].each do |k|
|
|
86
96
|
item = {k => []} if item.key?(k) && !item[k].is_a?(Array)
|
|
87
97
|
end
|
|
98
|
+
# ISC-WF-1 W1/W2: `use` items get their reference identifiers
|
|
99
|
+
# folded to strings; the override rules keep their parslet
|
|
100
|
+
# shape (DocumentBuilder materializes them through Transform).
|
|
101
|
+
if item.key?(:use) && item[:use].is_a?(Hash)
|
|
102
|
+
use = item[:use]
|
|
103
|
+
item = {use: {
|
|
104
|
+
use_alias: use[:use_alias] && scalar(use[:use_alias]),
|
|
105
|
+
qualified_name: use[:qualified_name] && scalar(use[:qualified_name]),
|
|
106
|
+
use_overrides: use[:use_overrides]
|
|
107
|
+
}.compact}
|
|
108
|
+
end
|
|
88
109
|
end
|
|
89
110
|
item
|
|
90
111
|
end
|
|
@@ -21,10 +21,12 @@ module Interscript
|
|
|
21
21
|
|
|
22
22
|
def serialize
|
|
23
23
|
emit_system_open
|
|
24
|
+
emit_languages if @doc[:languages]&.any?
|
|
24
25
|
emit_metadata if @doc[:metadata]&.any?
|
|
25
26
|
emit_tests if @doc[:tests]&.any?
|
|
26
27
|
emit_aliases if @doc[:aliases]&.any?
|
|
27
28
|
emit_dependencies if @doc[:dependencies]&.any?
|
|
29
|
+
emit_rulesets
|
|
28
30
|
emit_stages
|
|
29
31
|
emit_system_close
|
|
30
32
|
@out
|
|
@@ -40,6 +42,21 @@ module Interscript
|
|
|
40
42
|
@out << "}\n"
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# ISC-WF-1 W3: the map's declared languages.
|
|
46
|
+
def emit_languages
|
|
47
|
+
@out << "\nlanguages #{@doc[:languages].join(" ")}\n"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ISC-WF-1 W1: named rulesets, emitted ahead of the stages that
|
|
51
|
+
# reference them.
|
|
52
|
+
def emit_rulesets
|
|
53
|
+
@doc[:rulesets]&.each do |rs|
|
|
54
|
+
@out << "\nruleset #{rs[:name]} {\n"
|
|
55
|
+
rs[:body].each { |item| emit_stage_item(item) }
|
|
56
|
+
@out << "}\n"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
43
60
|
def emit_blank
|
|
44
61
|
@out << "\n"
|
|
45
62
|
end
|
|
@@ -157,6 +174,19 @@ module Interscript
|
|
|
157
174
|
@out << " }\n"
|
|
158
175
|
when :bare_rule
|
|
159
176
|
emit_rule(item[:rule], 2)
|
|
177
|
+
when :use
|
|
178
|
+
# ISC-WF-1 W1/W2: `use <dep>.<name> [override { rows }]` — the
|
|
179
|
+
# override block is part of the use directive (one syntax, one
|
|
180
|
+
# site, spec §4.1).
|
|
181
|
+
ref = item[:dependency] ? "#{item[:dependency]}.#{item[:ruleset]}" : item[:ruleset]
|
|
182
|
+
overrides = item[:overrides]
|
|
183
|
+
if overrides&.any?
|
|
184
|
+
@out << " use #{ref} override {\n"
|
|
185
|
+
overrides.each { |r| emit_rule(r, 4) }
|
|
186
|
+
@out << " }\n"
|
|
187
|
+
else
|
|
188
|
+
@out << " use #{ref}\n"
|
|
189
|
+
end
|
|
160
190
|
when :run
|
|
161
191
|
dep = item[:dependency]
|
|
162
192
|
stage = item[:stage]
|
|
@@ -179,6 +209,8 @@ module Interscript
|
|
|
179
209
|
pad = " " * indent
|
|
180
210
|
from_str = emit_item(rule[:from])
|
|
181
211
|
to_str = emit_item(rule[:to])
|
|
212
|
+
# ISC-WF-1 W3: the language guard trails the constraints.
|
|
213
|
+
when_str = rule[:when_languages]&.any? ? "when #{rule[:when_languages].join(" ")}" : nil
|
|
182
214
|
|
|
183
215
|
needs_block = rule[:from].is_a?(Items::Concat) || rule[:to].is_a?(Items::Concat)
|
|
184
216
|
|
|
@@ -188,11 +220,13 @@ module Interscript
|
|
|
188
220
|
@out << "#{inner}from #{from_str}\n"
|
|
189
221
|
@out << "#{inner}to #{to_str}\n"
|
|
190
222
|
rule[:constraints]&.each { |c| @out << "#{inner}#{emit_constraint(c)}\n" }
|
|
223
|
+
@out << "#{inner}#{when_str}\n" if when_str
|
|
191
224
|
@out << "#{pad}}\n"
|
|
192
225
|
else
|
|
193
226
|
constraints_str = rule[:constraints]&.map { |c| emit_constraint(c) }&.join(" ") || ""
|
|
194
227
|
@out << "#{pad}sub #{from_str} #{to_str}"
|
|
195
228
|
@out << " #{constraints_str}" unless constraints_str.empty?
|
|
229
|
+
@out << " #{when_str}" if when_str
|
|
196
230
|
@out << "\n"
|
|
197
231
|
end
|
|
198
232
|
end
|
|
@@ -133,6 +133,21 @@ module Interscript
|
|
|
133
133
|
rule(not_before: subtree(:x)) { {kind: :not_before, item: x} }
|
|
134
134
|
rule(not_after: subtree(:x)) { {kind: :not_after, item: x} }
|
|
135
135
|
rule(constraints: sequence(:c)) { c }
|
|
136
|
+
|
|
137
|
+
# ISC-WF-1 W3: a `when` guard folds to the plain list of language
|
|
138
|
+
# names. A single language captures as one {when_lang:} hash,
|
|
139
|
+
# several as a list; identifier leaves may still be raw slices at
|
|
140
|
+
# this point, so decode them here.
|
|
141
|
+
rule(when: subtree(:langs)) do
|
|
142
|
+
list = langs.is_a?(Hash) ? [langs] : Array(langs)
|
|
143
|
+
list.map do |l|
|
|
144
|
+
lang = l.is_a?(Hash) ? l[:when_lang] : l
|
|
145
|
+
case lang
|
|
146
|
+
when Hash then lang[:identifier].to_s
|
|
147
|
+
else lang.to_s
|
|
148
|
+
end
|
|
149
|
+
end.compact
|
|
150
|
+
end
|
|
136
151
|
end
|
|
137
152
|
end
|
|
138
153
|
end
|
|
@@ -31,6 +31,8 @@ module Interscript
|
|
|
31
31
|
tests: (doc_hash[:tests] || []).map { |t| Model::Test.new(t.transform_keys(&:to_s)) },
|
|
32
32
|
aliases: (doc_hash[:aliases] || []).map { |a| build_alias_model(a) },
|
|
33
33
|
stages: (doc_hash[:stages] || []).map { |s| build_stage_model(s) },
|
|
34
|
+
rulesets: (doc_hash[:rulesets] || []).map { |r| build_ruleset_model(r) },
|
|
35
|
+
languages: doc_hash[:languages],
|
|
34
36
|
dependencies: (doc_hash[:dependencies] || []).map { |d| build_dependency_model(d) }
|
|
35
37
|
)
|
|
36
38
|
end
|
|
@@ -63,6 +65,13 @@ module Interscript
|
|
|
63
65
|
)
|
|
64
66
|
end
|
|
65
67
|
|
|
68
|
+
def build_ruleset_model(ruleset_hash)
|
|
69
|
+
Model::Ruleset.new(
|
|
70
|
+
name: ruleset_hash[:name],
|
|
71
|
+
body: (ruleset_hash[:body] || []).map { |item| stage_item_to_model(item) }
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
66
75
|
def build_dependency_model(dep_hash)
|
|
67
76
|
Model::Dependency.new(
|
|
68
77
|
target: dep_hash[:target],
|
|
@@ -80,6 +89,11 @@ module Interscript
|
|
|
80
89
|
when :run
|
|
81
90
|
attrs[:dependency] = item[:dependency]
|
|
82
91
|
attrs[:stage] = item[:stage]
|
|
92
|
+
when :use
|
|
93
|
+
# ISC-WF-1 W1/W2
|
|
94
|
+
attrs[:dependency] = item[:dependency]
|
|
95
|
+
attrs[:ruleset] = item[:ruleset]
|
|
96
|
+
attrs[:overrides] = (item[:overrides] || []).map { |r| rule_to_model(r) }
|
|
83
97
|
when :separate
|
|
84
98
|
attrs[:separator] = item[:separator] ? item_to_model(item[:separator]) : nil
|
|
85
99
|
when :string_case
|
|
@@ -92,7 +106,8 @@ module Interscript
|
|
|
92
106
|
Model::Rule.new(
|
|
93
107
|
from: item_to_model(rule_hash[:from]),
|
|
94
108
|
to: item_to_model(rule_hash[:to]),
|
|
95
|
-
constraints: (rule_hash[:constraints] || []).map { |c| constraint_to_model(c) }
|
|
109
|
+
constraints: (rule_hash[:constraints] || []).map { |c| constraint_to_model(c) },
|
|
110
|
+
when_languages: rule_hash[:when_languages]
|
|
96
111
|
)
|
|
97
112
|
end
|
|
98
113
|
|
|
@@ -140,7 +155,7 @@ module Interscript
|
|
|
140
155
|
# --- Reverse direction: model → document hash ---
|
|
141
156
|
|
|
142
157
|
def model_to_hash(model)
|
|
143
|
-
{
|
|
158
|
+
hash = {
|
|
144
159
|
schemaVersion: 1,
|
|
145
160
|
systemCode: model.system_code,
|
|
146
161
|
metadata: model_metadata_to_hash(model.metadata),
|
|
@@ -149,6 +164,11 @@ module Interscript
|
|
|
149
164
|
stages: (model.stages || []).map { |s| stage_model_to_hash(s) },
|
|
150
165
|
dependencies: (model.dependencies || []).map { |d| {target: d.target, alias: d.alias_name}.compact }
|
|
151
166
|
}
|
|
167
|
+
# ISC-WF-1 keys round-trip only when present — pre-WF1 documents
|
|
168
|
+
# keep their exact hash shape.
|
|
169
|
+
hash[:languages] = model.languages if model.languages&.any?
|
|
170
|
+
hash[:rulesets] = (model.rulesets || []).map { |r| ruleset_model_to_hash(r) } if model.rulesets&.any?
|
|
171
|
+
hash
|
|
152
172
|
end
|
|
153
173
|
|
|
154
174
|
def model_metadata_to_hash(meta)
|
|
@@ -171,6 +191,11 @@ module Interscript
|
|
|
171
191
|
{kind: :bare_rule, rule: rule_model_to_hash(item.rule)}
|
|
172
192
|
when "run"
|
|
173
193
|
{kind: :run, dependency: item.dependency, stage: item.stage}
|
|
194
|
+
when "use"
|
|
195
|
+
# ISC-WF-1 W1/W2
|
|
196
|
+
hash = {kind: :use, ruleset: item.ruleset, dependency: item.dependency}
|
|
197
|
+
hash[:overrides] = (item.overrides || []).map { |r| rule_model_to_hash(r) } if item.overrides&.any?
|
|
198
|
+
hash.compact
|
|
174
199
|
when "separate"
|
|
175
200
|
{kind: :separate, separator: item.separator ? model_to_item(item.separator) : nil}
|
|
176
201
|
when "compose"
|
|
@@ -182,12 +207,21 @@ module Interscript
|
|
|
182
207
|
end
|
|
183
208
|
end
|
|
184
209
|
|
|
185
|
-
def
|
|
210
|
+
def ruleset_model_to_hash(ruleset_model)
|
|
186
211
|
{
|
|
212
|
+
name: ruleset_model.name,
|
|
213
|
+
body: ruleset_model.body.map { |item| stage_item_model_to_hash(item) }
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def rule_model_to_hash(rule_model)
|
|
218
|
+
hash = {
|
|
187
219
|
from: model_to_item(rule_model.from),
|
|
188
220
|
to: model_to_item(rule_model.to),
|
|
189
221
|
constraints: (rule_model.constraints || []).map { |c| constraint_model_to_hash(c) }
|
|
190
222
|
}
|
|
223
|
+
hash[:when_languages] = rule_model.when_languages if rule_model.when_languages&.any?
|
|
224
|
+
hash
|
|
191
225
|
end
|
|
192
226
|
|
|
193
227
|
def constraint_model_to_hash(constraint_model)
|
|
@@ -2,6 +2,9 @@ class Interscript::Node::Document
|
|
|
2
2
|
attr_accessor :metadata, :tests, :name
|
|
3
3
|
attr_accessor :dependencies, :aliases, :stages, :dep_aliases
|
|
4
4
|
attr_accessor :reversed_from
|
|
5
|
+
# ISC-WF-1: map-declared languages (LanguagesDecl or nil) and the named
|
|
6
|
+
# rulesets available to `use` expansion (name => Node::Ruleset).
|
|
7
|
+
attr_accessor :languages, :rulesets
|
|
5
8
|
|
|
6
9
|
def initialize
|
|
7
10
|
puts "Interscript::Node::Document.new " if $DEBUG
|
|
@@ -11,6 +14,8 @@ class Interscript::Node::Document
|
|
|
11
14
|
@dep_aliases = {}
|
|
12
15
|
@aliases = {}
|
|
13
16
|
@stages = {}
|
|
17
|
+
@languages = nil
|
|
18
|
+
@rulesets = {}
|
|
14
19
|
end
|
|
15
20
|
|
|
16
21
|
def imported_aliases
|
|
@@ -69,11 +74,18 @@ class Interscript::Node::Document
|
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def to_hash
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
hash = {
|
|
78
|
+
class: self.class.to_s, metadata: @metadata&.to_hash,
|
|
79
|
+
tests: @tests&.to_hash,
|
|
80
|
+
dependencies: @dependencies.map { |x| x.to_hash },
|
|
81
|
+
dep_aliases: @dep_aliases.transform_values(&:to_hash),
|
|
82
|
+
aliases: @aliases.transform_values(&:to_hash),
|
|
83
|
+
stages: @stages.transform_values(&:to_hash)
|
|
84
|
+
}
|
|
85
|
+
# ISC-WF-1 keys appear only on maps that use the constructs — the IR
|
|
86
|
+
# of every pre-WF1 map stays byte-identical.
|
|
87
|
+
hash[:languages] = @languages.to_hash if @languages
|
|
88
|
+
hash[:rulesets] = @rulesets.transform_values(&:to_hash) if @rulesets&.any?
|
|
89
|
+
hash
|
|
78
90
|
end
|
|
79
91
|
end
|
|
@@ -2,6 +2,10 @@ class Interscript::Node::Group::Parallel < Interscript::Node::Group
|
|
|
2
2
|
# A place for Interpreter to store a compiled form of the tree
|
|
3
3
|
attr_accessor :cached_tree
|
|
4
4
|
attr_accessor :subs_regexp, :subs_replacements, :subs_array
|
|
5
|
+
# ISC-WF-1 W3: per-selected-language filtered variants of this pass
|
|
6
|
+
# (selected language => parallel group). Only populated for passes
|
|
7
|
+
# that contain `when`-guarded rules.
|
|
8
|
+
attr_accessor :wf1_variants
|
|
5
9
|
|
|
6
10
|
def inspect
|
|
7
11
|
"parallel {\n#{super}\n}"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class Interscript::Node::LanguagesDecl < Interscript::Node
|
|
2
|
+
# ISC-WF-1 W3: the languages a map serves (spec §5.1). Attached to
|
|
3
|
+
# Node::Document#languages; nil means "no declaration" — every `when`
|
|
4
|
+
# guard is inert regardless of caller selection.
|
|
5
|
+
attr_accessor :languages
|
|
6
|
+
|
|
7
|
+
def initialize(languages = [])
|
|
8
|
+
self.languages = languages
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def include?(language)
|
|
12
|
+
languages.include?(language)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_hash
|
|
16
|
+
{class: self.class.to_s, languages: languages}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ==(other)
|
|
20
|
+
super && languages == other.languages
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inspect
|
|
24
|
+
"languages #{languages.join(" ")}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -4,11 +4,14 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
4
4
|
attr_accessor :reverse_before, :reverse_not_before, :reverse_after, :reverse_not_after
|
|
5
5
|
attr_accessor :reverse_run
|
|
6
6
|
attr_accessor :priority
|
|
7
|
+
# ISC-WF-1 W3: the language guard (`when kur, chv`). nil = unguarded.
|
|
8
|
+
attr_accessor :when_languages
|
|
7
9
|
|
|
8
10
|
def initialize(from, to,
|
|
9
11
|
before: nil, not_before: nil,
|
|
10
12
|
after: nil, not_after: nil,
|
|
11
|
-
priority: nil, reverse_run: nil
|
|
13
|
+
priority: nil, reverse_run: nil,
|
|
14
|
+
when_languages: nil)
|
|
12
15
|
self.from = Interscript::Node::Item.try_convert from
|
|
13
16
|
self.to = if to == :upcase
|
|
14
17
|
:upcase
|
|
@@ -19,6 +22,7 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
self.priority = priority
|
|
25
|
+
self.when_languages = when_languages&.map(&:to_sym)
|
|
22
26
|
|
|
23
27
|
# raise TypeError, "Can't supply both before and not_before" if before && not_before
|
|
24
28
|
# raise TypeError, "Can't supply both after and not_after" if after && not_after
|
|
@@ -59,6 +63,7 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
59
63
|
hash[:after] = after&.to_hash if after
|
|
60
64
|
hash[:not_after] = not_after&.to_hash if not_after
|
|
61
65
|
hash[:priority] = priority if priority
|
|
66
|
+
hash[:when_languages] = when_languages if when_languages
|
|
62
67
|
|
|
63
68
|
hash
|
|
64
69
|
end
|
|
@@ -91,7 +96,8 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
91
96
|
|
|
92
97
|
reverse_run: rrun,
|
|
93
98
|
|
|
94
|
-
priority: priority ? -priority : nil
|
|
99
|
+
priority: priority ? -priority : nil,
|
|
100
|
+
when_languages: when_languages)
|
|
95
101
|
end
|
|
96
102
|
|
|
97
103
|
def has_assertions?
|
|
@@ -202,7 +208,8 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
202
208
|
after == other.after &&
|
|
203
209
|
not_before == other.not_before &&
|
|
204
210
|
not_after == other.not_after &&
|
|
205
|
-
priority == other.priority
|
|
211
|
+
priority == other.priority &&
|
|
212
|
+
when_languages == other.when_languages
|
|
206
213
|
end
|
|
207
214
|
|
|
208
215
|
def inspect
|
|
@@ -222,6 +229,7 @@ class Interscript::Node::Rule::Sub < Interscript::Node::Rule
|
|
|
222
229
|
params << "not_after: #{@not_after.inspect}" if @not_after
|
|
223
230
|
|
|
224
231
|
params << "priority: #{@priority.inspect}" if @priority
|
|
232
|
+
params << "when: #{@when_languages.join(", ")}" if @when_languages
|
|
225
233
|
out << params.join(", ")
|
|
226
234
|
end
|
|
227
235
|
# standard:disable Layout/TrailingEmptyLines (deliberate)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class Interscript::Node::Rule::Use < Interscript::Node::Rule
|
|
2
|
+
# ISC-WF-1 W1/W2: a `use` directive before expansion. The NodeAdapter
|
|
3
|
+
# expands use directives at build time (macro expansion, spec §3.2), so
|
|
4
|
+
# this node only appears on documents built without expansion; it names
|
|
5
|
+
# the ruleset and, when present, the dependency alias and the per-site
|
|
6
|
+
# override rows.
|
|
7
|
+
attr_accessor :ruleset, :map, :overrides
|
|
8
|
+
|
|
9
|
+
def initialize(ruleset, map: nil, overrides: [])
|
|
10
|
+
self.ruleset = ruleset
|
|
11
|
+
self.map = map
|
|
12
|
+
self.overrides = overrides
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_hash
|
|
16
|
+
{class: self.class.to_s,
|
|
17
|
+
ruleset: ruleset,
|
|
18
|
+
map: map,
|
|
19
|
+
overrides: overrides.map(&:to_hash)}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ==(other)
|
|
23
|
+
super && ruleset == other.ruleset && map == other.map && overrides == other.overrides
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def inspect
|
|
27
|
+
out = "use #{[map, ruleset].compact.map { |i| "@#{i}" }.join(".")}"
|
|
28
|
+
out += " override { #{overrides.map(&:inspect).join(", ")} }" unless overrides.empty?
|
|
29
|
+
out
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -3,6 +3,7 @@ class Interscript::Node::Rule < Interscript::Node
|
|
|
3
3
|
autoload :Sub, "interscript/node/rule/sub"
|
|
4
4
|
autoload :Run, "interscript/node/rule/run"
|
|
5
5
|
autoload :Funcall, "interscript/node/rule/funcall"
|
|
6
|
+
autoload :Use, "interscript/node/rule/use"
|
|
6
7
|
|
|
7
8
|
def ==(other)
|
|
8
9
|
super && reverse_run == other.reverse_run
|