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.
- checksums.yaml +4 -4
- data/README.md +15 -0
- data/docs/demo/20191118-interscript-demo-cast.gif +0 -0
- data/exe/codemod-imp-to-isc +5 -0
- data/exe/diagnose_parse_failures +47 -0
- data/exe/interscript +2 -1
- data/exe/verify_isc_deep +211 -0
- data/exe/verify_isc_equivalence +128 -0
- data/interscript.gemspec +27 -20
- data/lib/interscript/command.rb +21 -12
- data/lib/interscript/compiler/javascript.rb +34 -35
- data/lib/interscript/compiler/json_ir.rb +214 -0
- data/lib/interscript/compiler/python.rb +354 -0
- data/lib/interscript/compiler/ruby.rb +23 -22
- data/lib/interscript/compiler.rb +20 -3
- data/lib/interscript/detector.rb +14 -7
- data/lib/interscript/dsl/aliases.rb +1 -1
- data/lib/interscript/dsl/document.rb +6 -3
- data/lib/interscript/dsl/group/parallel.rb +1 -1
- data/lib/interscript/dsl/group.rb +18 -9
- data/lib/interscript/dsl/items.rb +28 -16
- data/lib/interscript/dsl/metadata.rb +19 -18
- data/lib/interscript/dsl/stage.rb +1 -1
- data/lib/interscript/dsl/symbol_mm.rb +4 -2
- data/lib/interscript/dsl/tests.rb +1 -1
- data/lib/interscript/dsl.rb +21 -13
- data/lib/interscript/interpreter.rb +32 -21
- data/lib/interscript/isc/codemod.rb +791 -0
- data/lib/interscript/isc/document_builder.rb +354 -0
- data/lib/interscript/isc/generator.rb +191 -0
- data/lib/interscript/isc/grammar/concerns/aliases.rb +29 -0
- data/lib/interscript/isc/grammar/concerns/dependencies.rb +23 -0
- data/lib/interscript/isc/grammar/concerns/items.rb +176 -0
- data/lib/interscript/isc/grammar/concerns/metadata.rb +142 -0
- data/lib/interscript/isc/grammar/concerns/primitives.rb +112 -0
- data/lib/interscript/isc/grammar/concerns/stages.rb +129 -0
- data/lib/interscript/isc/grammar/concerns/system.rb +37 -0
- data/lib/interscript/isc/grammar/concerns/tests.rb +31 -0
- data/lib/interscript/isc/grammar/concerns.rb +18 -0
- data/lib/interscript/isc/grammar/core.rb +23 -0
- data/lib/interscript/isc/grammar/isc.artifact.json +1 -0
- data/lib/interscript/isc/grammar/isc.parg +196 -0
- data/lib/interscript/isc/grammar.rb +12 -0
- data/lib/interscript/isc/items.rb +185 -0
- data/lib/interscript/isc/model/alias.rb +20 -0
- data/lib/interscript/isc/model/constraint.rb +20 -0
- data/lib/interscript/isc/model/dependency.rb +19 -0
- data/lib/interscript/isc/model/document.rb +31 -0
- data/lib/interscript/isc/model/item.rb +50 -0
- data/lib/interscript/isc/model/rule.rb +23 -0
- data/lib/interscript/isc/model/stage.rb +20 -0
- data/lib/interscript/isc/model/stage_item.rb +33 -0
- data/lib/interscript/isc/model/test.rb +21 -0
- data/lib/interscript/isc/model.rb +19 -0
- data/lib/interscript/isc/node_adapter.rb +310 -0
- data/lib/interscript/isc/normalizer.rb +127 -0
- data/lib/interscript/isc/parser.rb +78 -0
- data/lib/interscript/isc/serializer.rb +248 -0
- data/lib/interscript/isc/transform.rb +138 -0
- data/lib/interscript/isc/yaml_bridge.rb +236 -0
- data/lib/interscript/isc.rb +30 -0
- data/lib/interscript/ml/byt5_onnx.rb +102 -0
- data/lib/interscript/ml/imf.rb +182 -0
- data/lib/interscript/ml/model.rb +52 -0
- data/lib/interscript/ml/provisioning.rb +189 -0
- data/lib/interscript/ml/translator.rb +38 -0
- data/lib/interscript/ml/vocab.rb +48 -0
- data/lib/interscript/ml.rb +25 -0
- data/lib/interscript/node/alias_def.rb +5 -5
- data/lib/interscript/node/dependency.rb +7 -7
- data/lib/interscript/node/document.rb +10 -10
- data/lib/interscript/node/group.rb +11 -9
- data/lib/interscript/node/item/alias.rb +14 -10
- data/lib/interscript/node/item/any.rb +14 -7
- data/lib/interscript/node/item/capture.rb +14 -9
- data/lib/interscript/node/item/group.rb +22 -17
- data/lib/interscript/node/item/repeat.rb +3 -3
- data/lib/interscript/node/item/stage.rb +4 -5
- data/lib/interscript/node/item/string.rb +18 -13
- data/lib/interscript/node/item.rb +27 -17
- data/lib/interscript/node/metadata.rb +6 -5
- data/lib/interscript/node/rule/funcall.rb +4 -5
- data/lib/interscript/node/rule/run.rb +4 -5
- data/lib/interscript/node/rule/sub.rb +55 -55
- data/lib/interscript/node/rule.rb +6 -5
- data/lib/interscript/node/stage.rb +11 -14
- data/lib/interscript/node/tests.rb +6 -6
- data/lib/interscript/node.rb +14 -17
- data/lib/interscript/stdlib/functions/rababa_adapter.rb +56 -0
- data/lib/interscript/stdlib/functions/secryst_adapter.rb +37 -0
- data/lib/interscript/stdlib/functions.rb +49 -0
- data/lib/interscript/stdlib.rb +25 -95
- data/lib/interscript/utils/helpers.rb +8 -5
- data/lib/interscript/utils/regexp_converter.rb +143 -144
- data/lib/interscript/version.rb +1 -1
- data/lib/interscript/visualize/json.rb +12 -13
- data/lib/interscript/visualize/nodes.rb +13 -13
- data/lib/interscript/visualize.rb +18 -18
- data/lib/interscript.rb +59 -45
- metadata +88 -31
- data/.github/workflows/assets.yml +0 -91
- data/.github/workflows/rake.yml +0 -52
- data/.github/workflows/release.yml +0 -68
- data/.gitignore +0 -79
- data/.rspec +0 -3
- data/Gemfile +0 -38
- data/Rakefile +0 -139
- data/bin/console +0 -10
- data/bin/interscript +0 -5
- data/bin/maps_analyze_staging +0 -168
- data/bin/maps_debug_compilers +0 -58
- data/bin/maps_debug_ordering +0 -88
- data/bin/maps_debug_ruby_compile +0 -24
- data/bin/maps_debug_step_by_step +0 -44
- data/bin/maps_optimize_order +0 -112
- data/bin/maps_v1_analyze_regexps +0 -45
- data/bin/maps_v1_to_v2 +0 -426
- data/bin/set_version +0 -16
- data/bin/setup +0 -8
- data/requirements.txt +0 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parsanol"
|
|
4
|
+
|
|
5
|
+
module Interscript
|
|
6
|
+
module Isc
|
|
7
|
+
# Entry point for parsing ISC source files.
|
|
8
|
+
#
|
|
9
|
+
# tree = Interscript::Isc::Parser.parse(source)
|
|
10
|
+
# # => { system: { system_code: "...", body: [...] } }
|
|
11
|
+
#
|
|
12
|
+
# The returned tree is a parslet-shaped hash — lists of hashes with
|
|
13
|
+
# symbol keys. Convert to a domain object via Interscript::Isc::DocumentBuilder.
|
|
14
|
+
class Parser < Parsanol::Parser
|
|
15
|
+
include Grammar::Core
|
|
16
|
+
|
|
17
|
+
root :isc_source
|
|
18
|
+
|
|
19
|
+
# The compiled grammar, shipped alongside the .parg source; the
|
|
20
|
+
# runtime parses through it when parsanol carries PARG (>= 1.3.61)
|
|
21
|
+
# and falls back to the Ruby-DSL grammar otherwise. Trees are
|
|
22
|
+
# byte-identical by the corpus gate in the parg_diff spec.
|
|
23
|
+
ARTIFACT_PATH = File.expand_path("grammar/isc.artifact.json", __dir__)
|
|
24
|
+
|
|
25
|
+
class ArtifactUnavailable < StandardError; end
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def parse(source, filename: nil)
|
|
29
|
+
parse_with_callbacks(source, filename: filename)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_with_callbacks(source, filename: nil)
|
|
33
|
+
tree = begin
|
|
34
|
+
parse_with_artifact(source)
|
|
35
|
+
rescue ArtifactUnavailable
|
|
36
|
+
parse_with_dsl(source)
|
|
37
|
+
end
|
|
38
|
+
Normalizer.normalize(tree)
|
|
39
|
+
rescue Parsanol::ParseFailed => e
|
|
40
|
+
raise ParseError.new(e.message, filename: filename, source: source, cause: e)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_with_artifact(source)
|
|
44
|
+
artifact.parse("isc", source)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_with_dsl(source)
|
|
48
|
+
new.parse(source)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def artifact
|
|
52
|
+
@artifact ||= begin
|
|
53
|
+
require "parsanol/parg"
|
|
54
|
+
Parsanol::PARG::Artifact.load(ARTIFACT_PATH)
|
|
55
|
+
rescue LoadError, StandardError => e
|
|
56
|
+
raise ArtifactUnavailable, "parg artifact unavailable: #{e.class}: #{e.message}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def reset_artifact
|
|
61
|
+
@artifact = nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class ParseError < StandardError
|
|
67
|
+
attr_reader :filename, :source
|
|
68
|
+
|
|
69
|
+
def initialize(message, filename:, source:, cause: nil)
|
|
70
|
+
@filename = filename
|
|
71
|
+
@source = source
|
|
72
|
+
@cause = cause
|
|
73
|
+
loc = filename ? "#{filename}: " : ""
|
|
74
|
+
super("#{loc}#{message}")
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "interscript/isc/items"
|
|
4
|
+
|
|
5
|
+
module Interscript
|
|
6
|
+
module Isc
|
|
7
|
+
# Serializes a document hash back to ISC source text.
|
|
8
|
+
# This is the reverse of Parser → DocumentBuilder.
|
|
9
|
+
#
|
|
10
|
+
# isc_source = Serializer.serialize(doc_hash)
|
|
11
|
+
#
|
|
12
|
+
class Serializer
|
|
13
|
+
def self.serialize(doc_hash)
|
|
14
|
+
new(doc_hash).serialize
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(doc_hash)
|
|
18
|
+
@doc = doc_hash
|
|
19
|
+
@out = +""
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def serialize
|
|
23
|
+
emit_system_open
|
|
24
|
+
emit_metadata if @doc[:metadata]&.any?
|
|
25
|
+
emit_tests if @doc[:tests]&.any?
|
|
26
|
+
emit_aliases if @doc[:aliases]&.any?
|
|
27
|
+
emit_dependencies if @doc[:dependencies]&.any?
|
|
28
|
+
emit_stages
|
|
29
|
+
emit_system_close
|
|
30
|
+
@out
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def emit_system_open
|
|
36
|
+
@out << %(system "#{@doc[:systemCode]}" {\n)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def emit_system_close
|
|
40
|
+
@out << "}\n"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def emit_blank
|
|
44
|
+
@out << "\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def emit_metadata
|
|
48
|
+
@out << "\nmetadata {\n"
|
|
49
|
+
@doc[:metadata].each do |key, val|
|
|
50
|
+
emit_metadata_field(key, val)
|
|
51
|
+
end
|
|
52
|
+
@out << "}\n"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def emit_metadata_field(key, val)
|
|
56
|
+
k = key.to_s
|
|
57
|
+
case k
|
|
58
|
+
when "description"
|
|
59
|
+
emit_description(val)
|
|
60
|
+
when "notes", "implementation_notes", "original_notes"
|
|
61
|
+
emit_notes(k, val)
|
|
62
|
+
else
|
|
63
|
+
case val
|
|
64
|
+
when Array
|
|
65
|
+
val.each { |v| @out << " #{k} #{emit_meta_value(v)}\n" }
|
|
66
|
+
when String
|
|
67
|
+
@out << " #{k} #{emit_meta_value(val)}\n"
|
|
68
|
+
when NilClass
|
|
69
|
+
@out << " #{k}\n"
|
|
70
|
+
else
|
|
71
|
+
@out << " #{k} #{val}\n"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def emit_description(val)
|
|
77
|
+
s = val.is_a?(Array) ? val.join(" ") : val.to_s
|
|
78
|
+
s = s.strip
|
|
79
|
+
if s.empty?
|
|
80
|
+
@out << " description { }\n"
|
|
81
|
+
elsif s.include?("\n") || s.length > 60
|
|
82
|
+
escaped = s.gsub("\\", "\\\\\\\\").gsub("{", "\\{").gsub("}", "\\}")
|
|
83
|
+
@out << " description {\n #{escaped.split("\n").join("\n ")}\n }\n"
|
|
84
|
+
else
|
|
85
|
+
@out << " description { #{s.gsub("\\", "\\\\\\\\").gsub("{", "\\{").gsub("}", "\\}")} }\n"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def emit_notes(key, val)
|
|
90
|
+
arr = Array(val).compact.reject(&:empty?)
|
|
91
|
+
if arr.empty?
|
|
92
|
+
@out << " #{key} { }\n"
|
|
93
|
+
else
|
|
94
|
+
@out << " #{key} {\n"
|
|
95
|
+
arr.each do |note|
|
|
96
|
+
escaped = note.to_s.gsub("\\", "\\\\\\\\").gsub('"', '\\"')
|
|
97
|
+
@out << " note \"#{escaped}\"\n"
|
|
98
|
+
end
|
|
99
|
+
@out << " }\n"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def emit_meta_value(val)
|
|
104
|
+
s = val.to_s
|
|
105
|
+
# Quote values that contain spaces, special chars, or look like numbers
|
|
106
|
+
if s.match?(/[\s"{}#]/) || s.empty?
|
|
107
|
+
s.inspect
|
|
108
|
+
else
|
|
109
|
+
s
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def emit_tests
|
|
114
|
+
@out << "\ntests {\n"
|
|
115
|
+
@doc[:tests].each do |t|
|
|
116
|
+
@out << %( "#{escape(t[:input])}" -> "#{escape(t[:expected])}")
|
|
117
|
+
@out << %( note "#{escape(t[:note])}") if t[:note]
|
|
118
|
+
@out << "\n"
|
|
119
|
+
end
|
|
120
|
+
@out << "}\n"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def emit_aliases
|
|
124
|
+
@out << "\naliases {\n"
|
|
125
|
+
@doc[:aliases].each do |a|
|
|
126
|
+
@out << " #{a[:name]} = #{emit_item(a[:value])}\n"
|
|
127
|
+
end
|
|
128
|
+
@out << "}\n"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def emit_dependencies
|
|
132
|
+
@doc[:dependencies].each do |d|
|
|
133
|
+
dep = %(dependency "#{d[:target]}")
|
|
134
|
+
dep << %( as #{d[:alias]}) if d[:alias]
|
|
135
|
+
@out << dep << "\n"
|
|
136
|
+
end
|
|
137
|
+
@out << "\n" if @doc[:dependencies].any?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def emit_stages
|
|
141
|
+
@doc[:stages].each do |stage|
|
|
142
|
+
@out << "\nstage #{stage[:name]} {\n"
|
|
143
|
+
stage[:body].each { |item| emit_stage_item(item) }
|
|
144
|
+
@out << "}\n"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def emit_stage_item(item)
|
|
149
|
+
case item[:kind]
|
|
150
|
+
when :parallel
|
|
151
|
+
@out << " parallel {\n"
|
|
152
|
+
item[:rules].each { |r| emit_rule(r, 4) }
|
|
153
|
+
@out << " }\n"
|
|
154
|
+
when :sequence
|
|
155
|
+
@out << " sequence {\n"
|
|
156
|
+
item[:rules].each { |r| emit_rule(r, 4) }
|
|
157
|
+
@out << " }\n"
|
|
158
|
+
when :bare_rule
|
|
159
|
+
emit_rule(item[:rule], 2)
|
|
160
|
+
when :run
|
|
161
|
+
dep = item[:dependency]
|
|
162
|
+
stage = item[:stage]
|
|
163
|
+
@out << if dep
|
|
164
|
+
" run map.#{dep}.stage.#{stage}\n"
|
|
165
|
+
else
|
|
166
|
+
" run stage.#{stage}\n"
|
|
167
|
+
end
|
|
168
|
+
when :separate
|
|
169
|
+
sep = item[:separator]
|
|
170
|
+
@out << " separate separator #{emit_item(sep)}\n"
|
|
171
|
+
when :compose
|
|
172
|
+
@out << " compose\n"
|
|
173
|
+
when :string_case
|
|
174
|
+
@out << " #{item[:op]}\n"
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def emit_rule(rule, indent)
|
|
179
|
+
pad = " " * indent
|
|
180
|
+
from_str = emit_item(rule[:from])
|
|
181
|
+
to_str = emit_item(rule[:to])
|
|
182
|
+
|
|
183
|
+
needs_block = rule[:from].is_a?(Items::Concat) || rule[:to].is_a?(Items::Concat)
|
|
184
|
+
|
|
185
|
+
if needs_block
|
|
186
|
+
inner = " " * (indent + 2)
|
|
187
|
+
@out << "#{pad}sub {\n"
|
|
188
|
+
@out << "#{inner}from #{from_str}\n"
|
|
189
|
+
@out << "#{inner}to #{to_str}\n"
|
|
190
|
+
rule[:constraints]&.each { |c| @out << "#{inner}#{emit_constraint(c)}\n" }
|
|
191
|
+
@out << "#{pad}}\n"
|
|
192
|
+
else
|
|
193
|
+
constraints_str = rule[:constraints]&.map { |c| emit_constraint(c) }&.join(" ") || ""
|
|
194
|
+
@out << "#{pad}sub #{from_str} #{to_str}"
|
|
195
|
+
@out << " #{constraints_str}" unless constraints_str.empty?
|
|
196
|
+
@out << "\n"
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def emit_constraint(constraint)
|
|
201
|
+
"#{constraint[:kind]} #{emit_item(constraint[:item])}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def emit_item(item)
|
|
205
|
+
return "none" unless item
|
|
206
|
+
|
|
207
|
+
case item
|
|
208
|
+
when Items::StringValue
|
|
209
|
+
escape_string(item.value)
|
|
210
|
+
when Items::None
|
|
211
|
+
"none"
|
|
212
|
+
when Items::Primitive
|
|
213
|
+
item.name
|
|
214
|
+
when Items::Function
|
|
215
|
+
item.name
|
|
216
|
+
when Items::AliasRef
|
|
217
|
+
item.name
|
|
218
|
+
when Items::Capture
|
|
219
|
+
"ref(#{item.index})"
|
|
220
|
+
when Items::CaptureGroup
|
|
221
|
+
"capture(#{emit_item(item.inner)})"
|
|
222
|
+
when Items::Concat
|
|
223
|
+
item.parts.map { |p| emit_item(p) }.join(" + ")
|
|
224
|
+
when Items::Set
|
|
225
|
+
%(any("#{item.chars.map { |c| escape(c) }.join}"))
|
|
226
|
+
when Items::Range
|
|
227
|
+
%(any("#{escape(item.lo)}".."#{escape(item.hi)}"))
|
|
228
|
+
when Items::Maybe
|
|
229
|
+
"maybe(#{emit_item(item.inner)})"
|
|
230
|
+
when Items::Some
|
|
231
|
+
"some(#{emit_item(item.inner)})"
|
|
232
|
+
else
|
|
233
|
+
item.to_s
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def escape_string(str)
|
|
238
|
+
return '""' if str.nil?
|
|
239
|
+
escaped = str.gsub("\\", "\\\\\\\\").gsub('"', '\\"')
|
|
240
|
+
"\"#{escaped}\""
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def escape(str)
|
|
244
|
+
str.to_s.gsub("\\", "\\\\\\\\").gsub('"', '\\"')
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parsanol"
|
|
4
|
+
|
|
5
|
+
module Interscript
|
|
6
|
+
module Isc
|
|
7
|
+
# Transforms the raw parslet tree into a clean intermediate hash with
|
|
8
|
+
# stable shape for the DocumentBuilder to consume.
|
|
9
|
+
#
|
|
10
|
+
# String escapes are unescaped here. Items are flattened. Constraints
|
|
11
|
+
# are tagged by kind.
|
|
12
|
+
class Transform < Parsanol::Transform
|
|
13
|
+
# String atom: parslet gives us a parslet slice for simple strings,
|
|
14
|
+
# or an array of pieces (escape-sequence fragments interleaved with
|
|
15
|
+
# raw chars) for strings containing escapes. Flatten both to a single
|
|
16
|
+
# StringValue.
|
|
17
|
+
rule(string: simple(:s)) { Items::StringValue.new(s.to_s) }
|
|
18
|
+
rule(string: sequence(:parts)) do
|
|
19
|
+
Items::StringValue.new(Transform.decode_string_parts(parts))
|
|
20
|
+
end
|
|
21
|
+
rule(run: simple(:s)) { s.to_s }
|
|
22
|
+
|
|
23
|
+
rule(identifier: simple(:i)) { i.to_s }
|
|
24
|
+
|
|
25
|
+
rule(none: simple(:_)) { Items::None.new }
|
|
26
|
+
# any_character is the Stdlib any-char alias (".") — without this
|
|
27
|
+
# rule the fragment stringified to "any_charany_character".
|
|
28
|
+
rule(any_char: simple(:_)) { Items::AliasRef.new("any_character") }
|
|
29
|
+
rule(primitive: simple(:p)) { Items::Primitive.new(p.to_s) }
|
|
30
|
+
rule(function: simple(:f)) { Items::Function.new(f.to_s) }
|
|
31
|
+
rule(alias: {identifier: simple(:n), qualified_name: simple(:q)}) {
|
|
32
|
+
Items::AliasRef.new(q.to_s, map: n.to_s)
|
|
33
|
+
}
|
|
34
|
+
rule(alias: simple(:n)) { Items::AliasRef.new(n.to_s) }
|
|
35
|
+
# Bare identifiers inside any(...) lists parse as :alias_ref (the
|
|
36
|
+
# :alias key only covers direct item position). Unmatched, the hash
|
|
37
|
+
# degraded downstream into the debug string "[:alias_ref, \"name\"]".
|
|
38
|
+
rule(alias_ref: {identifier: simple(:n)}) { Items::AliasRef.new(n.to_s) }
|
|
39
|
+
rule(alias_ref: simple(:n)) { Items::AliasRef.new(n.to_s) }
|
|
40
|
+
rule(ref: subtree(:h)) { Items::Capture.new(h[:digit].to_s.to_i) }
|
|
41
|
+
rule(capture_inner: subtree(:inner)) { Items::CaptureGroup.new(Interscript::Isc::Transform.materialize_item(inner)) }
|
|
42
|
+
rule(maybe_inner: subtree(:inner)) { Items::Maybe.new(Interscript::Isc::Transform.materialize_item(inner)) }
|
|
43
|
+
rule(some_inner: subtree(:inner)) { Items::Some.new(Interscript::Isc::Transform.materialize_item(inner)) }
|
|
44
|
+
|
|
45
|
+
rule(dquote: simple(:_)) { '"' }
|
|
46
|
+
rule(backslash: simple(:_)) { "\\" }
|
|
47
|
+
rule(newline: simple(:_)) { "\n" }
|
|
48
|
+
rule(carriage_return: simple(:_)) { "\r" }
|
|
49
|
+
rule(tab: simple(:_)) { "\t" }
|
|
50
|
+
rule(u_lone: simple(:_)) { "U" }
|
|
51
|
+
rule(unicode: simple(:hex)) do
|
|
52
|
+
[hex.to_s.to_i(16)].pack("U")
|
|
53
|
+
rescue
|
|
54
|
+
hex.to_s
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Shared decoder for the pieces of a :string capture — used by the
|
|
58
|
+
# transform rules above and by the Normalizer's scalar folding. A
|
|
59
|
+
# fragment without an escape key is a raw run slice.
|
|
60
|
+
def self.decode_string_parts(parts)
|
|
61
|
+
Array(parts).map do |p|
|
|
62
|
+
case p
|
|
63
|
+
when Hash
|
|
64
|
+
if p.key?(:run)
|
|
65
|
+
p[:run].to_s
|
|
66
|
+
elsif p.key?(:newline)
|
|
67
|
+
"\n"
|
|
68
|
+
elsif p.key?(:carriage_return)
|
|
69
|
+
"\r"
|
|
70
|
+
elsif p.key?(:tab)
|
|
71
|
+
"\t"
|
|
72
|
+
elsif p.key?(:dquote)
|
|
73
|
+
'"'
|
|
74
|
+
elsif p.key?(:backslash)
|
|
75
|
+
"\\"
|
|
76
|
+
elsif p.key?(:unicode)
|
|
77
|
+
[p[:unicode].to_s.to_i(16)].pack("U")
|
|
78
|
+
elsif p.key?(:u_lone)
|
|
79
|
+
"U"
|
|
80
|
+
else
|
|
81
|
+
p.to_s
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
p.to_s
|
|
85
|
+
end
|
|
86
|
+
end.join
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
rule(lo: simple(:lo), hi: simple(:hi)) do
|
|
90
|
+
Items::Range.new(lo.to_s, hi.to_s)
|
|
91
|
+
end
|
|
92
|
+
rule(single: simple(:s)) { Items::Set.from_string(s.to_s) }
|
|
93
|
+
# List entries are already Items (strings, primitives, alias refs).
|
|
94
|
+
# Stringifying here baked an object inspect into the char set.
|
|
95
|
+
rule(list: sequence(:arr)) do
|
|
96
|
+
Items::Set.new(arr)
|
|
97
|
+
end
|
|
98
|
+
rule(any: subtree(:h)) do
|
|
99
|
+
# A single alias argument keeps set semantics: the legacy runtime
|
|
100
|
+
# builds Any(Alias) which resolves through Stdlib (Any(nil) for
|
|
101
|
+
# imported aliases). Unwrapped, the bare Alias resolves to the
|
|
102
|
+
# imported charset string and compiles as a literal.
|
|
103
|
+
h.is_a?(Items::AliasRef) ? Items::Set.new([h]) : h
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
rule(concatenation: subtree(:parts)) do
|
|
107
|
+
Items::Concat.from_parts(Array(parts))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.materialize_item(fragment)
|
|
111
|
+
case fragment
|
|
112
|
+
when Hash
|
|
113
|
+
if fragment.key?(:concatenation)
|
|
114
|
+
new.apply(fragment)
|
|
115
|
+
else
|
|
116
|
+
new.apply(concatenation: [fragment])
|
|
117
|
+
end
|
|
118
|
+
when Array
|
|
119
|
+
new.apply(concatenation: fragment)
|
|
120
|
+
when NilClass
|
|
121
|
+
Items::None.new
|
|
122
|
+
else
|
|
123
|
+
fragment
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def materialize_item(fragment)
|
|
128
|
+
self.class.materialize_item(fragment)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
rule(before: subtree(:x)) { {kind: :before, item: x} }
|
|
132
|
+
rule(after: subtree(:x)) { {kind: :after, item: x} }
|
|
133
|
+
rule(not_before: subtree(:x)) { {kind: :not_before, item: x} }
|
|
134
|
+
rule(not_after: subtree(:x)) { {kind: :not_after, item: x} }
|
|
135
|
+
rule(constraints: sequence(:c)) { c }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model"
|
|
5
|
+
require "interscript/isc/items"
|
|
6
|
+
|
|
7
|
+
module Interscript
|
|
8
|
+
module Isc
|
|
9
|
+
# Bridges the ISC document hash (from DocumentBuilder) and lutaml-model
|
|
10
|
+
# objects for YAML serialization.
|
|
11
|
+
#
|
|
12
|
+
# yaml_str = YamlBridge.to_yaml(doc_hash)
|
|
13
|
+
# doc_hash = YamlBridge.from_yaml(yaml_str)
|
|
14
|
+
#
|
|
15
|
+
module YamlBridge
|
|
16
|
+
class << self
|
|
17
|
+
def to_yaml(doc_hash)
|
|
18
|
+
hash_to_model(doc_hash).to_yaml
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def from_yaml(yaml_str)
|
|
22
|
+
model_to_hash(Model::Document.from_yaml(yaml_str))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def hash_to_model(doc_hash)
|
|
28
|
+
Model::Document.new(
|
|
29
|
+
system_code: doc_hash[:systemCode],
|
|
30
|
+
metadata: normalize_metadata(doc_hash[:metadata]),
|
|
31
|
+
tests: (doc_hash[:tests] || []).map { |t| Model::Test.new(t.transform_keys(&:to_s)) },
|
|
32
|
+
aliases: (doc_hash[:aliases] || []).map { |a| build_alias_model(a) },
|
|
33
|
+
stages: (doc_hash[:stages] || []).map { |s| build_stage_model(s) },
|
|
34
|
+
dependencies: (doc_hash[:dependencies] || []).map { |d| build_dependency_model(d) }
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def normalize_metadata(meta)
|
|
39
|
+
return {} unless meta.is_a?(Hash)
|
|
40
|
+
meta.transform_values do |v|
|
|
41
|
+
case v
|
|
42
|
+
when Array then v.map { |i| normalize_meta_value(i) }
|
|
43
|
+
else normalize_meta_value(v)
|
|
44
|
+
end
|
|
45
|
+
end.transform_keys(&:to_s)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def normalize_meta_value(v)
|
|
49
|
+
v.is_a?(Symbol) ? v.to_s : v
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_alias_model(alias_hash)
|
|
53
|
+
Model::Alias.new(
|
|
54
|
+
name: alias_hash[:name],
|
|
55
|
+
value: item_to_model(alias_hash[:value])
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_stage_model(stage_hash)
|
|
60
|
+
Model::Stage.new(
|
|
61
|
+
name: stage_hash[:name],
|
|
62
|
+
body: (stage_hash[:body] || []).map { |item| stage_item_to_model(item) }
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def build_dependency_model(dep_hash)
|
|
67
|
+
Model::Dependency.new(
|
|
68
|
+
target: dep_hash[:target],
|
|
69
|
+
alias_name: dep_hash[:alias]
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stage_item_to_model(item)
|
|
74
|
+
attrs = {kind: item[:kind].to_s}
|
|
75
|
+
case item[:kind]
|
|
76
|
+
when :parallel, :sequence
|
|
77
|
+
attrs[:rules] = item[:rules].map { |r| rule_to_model(r) }
|
|
78
|
+
when :bare_rule
|
|
79
|
+
attrs[:rule] = rule_to_model(item[:rule])
|
|
80
|
+
when :run
|
|
81
|
+
attrs[:dependency] = item[:dependency]
|
|
82
|
+
attrs[:stage] = item[:stage]
|
|
83
|
+
when :separate
|
|
84
|
+
attrs[:separator] = item[:separator] ? item_to_model(item[:separator]) : nil
|
|
85
|
+
when :string_case
|
|
86
|
+
attrs[:op] = item[:op]
|
|
87
|
+
end
|
|
88
|
+
Model::StageItem.new(attrs.compact)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def rule_to_model(rule_hash)
|
|
92
|
+
Model::Rule.new(
|
|
93
|
+
from: item_to_model(rule_hash[:from]),
|
|
94
|
+
to: item_to_model(rule_hash[:to]),
|
|
95
|
+
constraints: (rule_hash[:constraints] || []).map { |c| constraint_to_model(c) }
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def constraint_to_model(constraint_hash)
|
|
100
|
+
Model::Constraint.new(
|
|
101
|
+
kind: constraint_hash[:kind]&.to_s,
|
|
102
|
+
item: constraint_hash[:item] ? item_to_model(constraint_hash[:item]) : nil
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Convert an Items::* object to a Model::Item
|
|
107
|
+
def item_to_model(item)
|
|
108
|
+
return nil unless item
|
|
109
|
+
|
|
110
|
+
case item
|
|
111
|
+
when Items::StringValue
|
|
112
|
+
Model::Item.new(type: "string", value: item.value)
|
|
113
|
+
when Items::None
|
|
114
|
+
Model::Item.new(type: "none")
|
|
115
|
+
when Items::Primitive
|
|
116
|
+
Model::Item.new(type: "primitive", name: item.name)
|
|
117
|
+
when Items::Function
|
|
118
|
+
Model::Item.new(type: "function", name: item.name)
|
|
119
|
+
when Items::AliasRef
|
|
120
|
+
Model::Item.new(type: "alias_ref", name: item.name)
|
|
121
|
+
when Items::Capture
|
|
122
|
+
Model::Item.new(type: "capture", index: item.index)
|
|
123
|
+
when Items::CaptureGroup
|
|
124
|
+
Model::Item.new(type: "capture_group", inner: item_to_model(item.inner))
|
|
125
|
+
when Items::Maybe
|
|
126
|
+
Model::Item.new(type: "maybe", inner: item_to_model(item.inner))
|
|
127
|
+
when Items::Some
|
|
128
|
+
Model::Item.new(type: "some", inner: item_to_model(item.inner))
|
|
129
|
+
when Items::Range
|
|
130
|
+
Model::Item.new(type: "range", lo: item.lo, hi: item.hi)
|
|
131
|
+
when Items::Set
|
|
132
|
+
Model::Item.new(type: "set", chars: item.chars)
|
|
133
|
+
when Items::Concat
|
|
134
|
+
Model::Item.new(type: "concat", parts: item.parts.map { |p| item_to_model(p) })
|
|
135
|
+
else
|
|
136
|
+
Model::Item.new(type: "unknown", value: item.to_s)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# --- Reverse direction: model → document hash ---
|
|
141
|
+
|
|
142
|
+
def model_to_hash(model)
|
|
143
|
+
{
|
|
144
|
+
schemaVersion: 1,
|
|
145
|
+
systemCode: model.system_code,
|
|
146
|
+
metadata: model_metadata_to_hash(model.metadata),
|
|
147
|
+
tests: (model.tests || []).map { |t| {input: t.input || "", expected: t.expected || "", note: t.note}.compact },
|
|
148
|
+
aliases: (model.aliases || []).map { |a| {name: a.name, value: model_to_item(a.value)} },
|
|
149
|
+
stages: (model.stages || []).map { |s| stage_model_to_hash(s) },
|
|
150
|
+
dependencies: (model.dependencies || []).map { |d| {target: d.target, alias: d.alias_name}.compact }
|
|
151
|
+
}
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def model_metadata_to_hash(meta)
|
|
155
|
+
return {} unless meta.is_a?(Hash)
|
|
156
|
+
meta.transform_keys(&:to_sym)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def stage_model_to_hash(stage_model)
|
|
160
|
+
{
|
|
161
|
+
name: stage_model.name,
|
|
162
|
+
body: stage_model.body.map { |item| stage_item_model_to_hash(item) }
|
|
163
|
+
}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def stage_item_model_to_hash(item)
|
|
167
|
+
case item.kind
|
|
168
|
+
when "parallel", "sequence"
|
|
169
|
+
{kind: item.kind.to_sym, rules: item.rules.map { |r| rule_model_to_hash(r) }}
|
|
170
|
+
when "bare_rule"
|
|
171
|
+
{kind: :bare_rule, rule: rule_model_to_hash(item.rule)}
|
|
172
|
+
when "run"
|
|
173
|
+
{kind: :run, dependency: item.dependency, stage: item.stage}
|
|
174
|
+
when "separate"
|
|
175
|
+
{kind: :separate, separator: item.separator ? model_to_item(item.separator) : nil}
|
|
176
|
+
when "compose"
|
|
177
|
+
{kind: :compose}
|
|
178
|
+
when "string_case"
|
|
179
|
+
{kind: :string_case, op: item.op}
|
|
180
|
+
else
|
|
181
|
+
{kind: item.kind&.to_sym}
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def rule_model_to_hash(rule_model)
|
|
186
|
+
{
|
|
187
|
+
from: model_to_item(rule_model.from),
|
|
188
|
+
to: model_to_item(rule_model.to),
|
|
189
|
+
constraints: (rule_model.constraints || []).map { |c| constraint_model_to_hash(c) }
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def constraint_model_to_hash(constraint_model)
|
|
194
|
+
{
|
|
195
|
+
kind: constraint_model.kind&.to_sym,
|
|
196
|
+
item: constraint_model.item ? model_to_item(constraint_model.item) : nil
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Convert a Model::Item back to an Items::* object
|
|
201
|
+
def model_to_item(item_model)
|
|
202
|
+
return nil unless item_model
|
|
203
|
+
|
|
204
|
+
case item_model.type
|
|
205
|
+
when "string"
|
|
206
|
+
Items::StringValue.new(item_model.value || "")
|
|
207
|
+
when "none"
|
|
208
|
+
Items::None.new
|
|
209
|
+
when "primitive"
|
|
210
|
+
Items::Primitive.new(item_model.name)
|
|
211
|
+
when "function"
|
|
212
|
+
Items::Function.new(item_model.name)
|
|
213
|
+
when "alias_ref"
|
|
214
|
+
Items::AliasRef.new(item_model.name)
|
|
215
|
+
when "capture"
|
|
216
|
+
Items::Capture.new(item_model.index)
|
|
217
|
+
when "capture_group"
|
|
218
|
+
Items::CaptureGroup.new(model_to_item(item_model.inner))
|
|
219
|
+
when "maybe"
|
|
220
|
+
Items::Maybe.new(model_to_item(item_model.inner))
|
|
221
|
+
when "some"
|
|
222
|
+
Items::Some.new(model_to_item(item_model.inner))
|
|
223
|
+
when "range"
|
|
224
|
+
Items::Range.new(item_model.lo, item_model.hi)
|
|
225
|
+
when "set"
|
|
226
|
+
Items::Set.new(item_model.chars || [])
|
|
227
|
+
when "concat"
|
|
228
|
+
Items::Concat.new((item_model.parts || []).map { |p| model_to_item(p) })
|
|
229
|
+
else
|
|
230
|
+
Items::StringValue.new(item_model.value.to_s)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|