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,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model/test"
|
|
5
|
+
require "interscript/isc/model/alias"
|
|
6
|
+
require "interscript/isc/model/stage"
|
|
7
|
+
require "interscript/isc/model/dependency"
|
|
8
|
+
|
|
9
|
+
module Interscript
|
|
10
|
+
module Isc
|
|
11
|
+
module Model
|
|
12
|
+
class Document < Lutaml::Model::Serializable
|
|
13
|
+
attribute :system_code, :string
|
|
14
|
+
attribute :metadata, :hash
|
|
15
|
+
attribute :tests, Test, collection: true
|
|
16
|
+
attribute :aliases, Alias, collection: true
|
|
17
|
+
attribute :stages, Stage, collection: true
|
|
18
|
+
attribute :dependencies, Dependency, collection: true
|
|
19
|
+
|
|
20
|
+
yaml do
|
|
21
|
+
map "system_code", to: :system_code
|
|
22
|
+
map "metadata", to: :metadata
|
|
23
|
+
map "tests", to: :tests
|
|
24
|
+
map "aliases", to: :aliases
|
|
25
|
+
map "stages", to: :stages
|
|
26
|
+
map "dependencies", to: :dependencies
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model"
|
|
5
|
+
|
|
6
|
+
module Interscript
|
|
7
|
+
module Isc
|
|
8
|
+
module Model
|
|
9
|
+
# Polymorphic representation of an ISC item (StringValue, AliasRef, etc.)
|
|
10
|
+
# Serialized as a discriminated union keyed by the `type` field.
|
|
11
|
+
#
|
|
12
|
+
# YAML shape:
|
|
13
|
+
# type: string
|
|
14
|
+
# value: "hello"
|
|
15
|
+
# ---
|
|
16
|
+
# type: alias_ref
|
|
17
|
+
# name: my_alias
|
|
18
|
+
# ---
|
|
19
|
+
# type: concat
|
|
20
|
+
# parts:
|
|
21
|
+
# - type: string
|
|
22
|
+
# value: "a"
|
|
23
|
+
# - type: alias_ref
|
|
24
|
+
# name: consonants
|
|
25
|
+
class Item < Lutaml::Model::Serializable
|
|
26
|
+
attribute :type, :string
|
|
27
|
+
attribute :value, :string
|
|
28
|
+
attribute :name, :string
|
|
29
|
+
attribute :index, :integer
|
|
30
|
+
attribute :lo, :string
|
|
31
|
+
attribute :hi, :string
|
|
32
|
+
attribute :chars, :string, collection: true
|
|
33
|
+
attribute :parts, Item, collection: true
|
|
34
|
+
attribute :inner, Item
|
|
35
|
+
|
|
36
|
+
yaml do
|
|
37
|
+
map "type", to: :type
|
|
38
|
+
map "value", to: :value
|
|
39
|
+
map "name", to: :name
|
|
40
|
+
map "index", to: :index
|
|
41
|
+
map "lo", to: :lo
|
|
42
|
+
map "hi", to: :hi
|
|
43
|
+
map "chars", to: :chars
|
|
44
|
+
map "parts", to: :parts
|
|
45
|
+
map "inner", to: :inner
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model/item"
|
|
5
|
+
require "interscript/isc/model/constraint"
|
|
6
|
+
|
|
7
|
+
module Interscript
|
|
8
|
+
module Isc
|
|
9
|
+
module Model
|
|
10
|
+
class Rule < Lutaml::Model::Serializable
|
|
11
|
+
attribute :from, Item
|
|
12
|
+
attribute :to, Item
|
|
13
|
+
attribute :constraints, Constraint, collection: true
|
|
14
|
+
|
|
15
|
+
yaml do
|
|
16
|
+
map "from", to: :from
|
|
17
|
+
map "to", to: :to
|
|
18
|
+
map "constraints", to: :constraints
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model/stage_item"
|
|
5
|
+
|
|
6
|
+
module Interscript
|
|
7
|
+
module Isc
|
|
8
|
+
module Model
|
|
9
|
+
class Stage < Lutaml::Model::Serializable
|
|
10
|
+
attribute :name, :string
|
|
11
|
+
attribute :body, StageItem, collection: true
|
|
12
|
+
|
|
13
|
+
yaml do
|
|
14
|
+
map "name", to: :name
|
|
15
|
+
map "body", to: :body
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
require "interscript/isc/model/rule"
|
|
5
|
+
require "interscript/isc/model/item"
|
|
6
|
+
|
|
7
|
+
module Interscript
|
|
8
|
+
module Isc
|
|
9
|
+
module Model
|
|
10
|
+
# A single item in a stage body. Discriminated by `kind`:
|
|
11
|
+
# parallel, sequence, bare_rule, run, separate, compose, string_case
|
|
12
|
+
class StageItem < Lutaml::Model::Serializable
|
|
13
|
+
attribute :kind, :string
|
|
14
|
+
attribute :rules, Rule, collection: true
|
|
15
|
+
attribute :rule, Rule
|
|
16
|
+
attribute :dependency, :string
|
|
17
|
+
attribute :stage, :string
|
|
18
|
+
attribute :separator, Item
|
|
19
|
+
attribute :op, :string
|
|
20
|
+
|
|
21
|
+
yaml do
|
|
22
|
+
map "kind", to: :kind
|
|
23
|
+
map "rules", to: :rules
|
|
24
|
+
map "rule", to: :rule
|
|
25
|
+
map "dependency", to: :dependency
|
|
26
|
+
map "stage", to: :stage
|
|
27
|
+
map "separator", to: :separator
|
|
28
|
+
map "op", to: :op
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Interscript
|
|
6
|
+
module Isc
|
|
7
|
+
module Model
|
|
8
|
+
class Test < Lutaml::Model::Serializable
|
|
9
|
+
attribute :input, :string
|
|
10
|
+
attribute :expected, :string
|
|
11
|
+
attribute :note, :string
|
|
12
|
+
|
|
13
|
+
yaml do
|
|
14
|
+
map "input", to: :input
|
|
15
|
+
map "expected", to: :expected
|
|
16
|
+
map "note", to: :note
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "lutaml/model"
|
|
4
|
+
|
|
5
|
+
module Interscript
|
|
6
|
+
module Isc
|
|
7
|
+
module Model
|
|
8
|
+
autoload :Item, "interscript/isc/model/item"
|
|
9
|
+
autoload :Constraint, "interscript/isc/model/constraint"
|
|
10
|
+
autoload :Rule, "interscript/isc/model/rule"
|
|
11
|
+
autoload :StageItem, "interscript/isc/model/stage_item"
|
|
12
|
+
autoload :Stage, "interscript/isc/model/stage"
|
|
13
|
+
autoload :Alias, "interscript/isc/model/alias"
|
|
14
|
+
autoload :Test, "interscript/isc/model/test"
|
|
15
|
+
autoload :Dependency, "interscript/isc/model/dependency"
|
|
16
|
+
autoload :Document, "interscript/isc/model/document"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interscript
|
|
4
|
+
module Isc
|
|
5
|
+
# Bridges the ISC document hash (from DocumentBuilder) to the existing
|
|
6
|
+
# Interscript::Node::Document object model, enabling .isc files to be
|
|
7
|
+
# used for actual transliteration via the standard runtime.
|
|
8
|
+
#
|
|
9
|
+
# This is the critical integration point: without it, ISC files can be
|
|
10
|
+
# parsed but cannot transliterate.
|
|
11
|
+
#
|
|
12
|
+
# doc_hash = Interscript::Isc::DocumentBuilder.build(tree)
|
|
13
|
+
# node_doc = Interscript::Isc::NodeAdapter.to_interscript_node(doc_hash)
|
|
14
|
+
# Interscript.transliterate_node(node_doc, "main", "hello")
|
|
15
|
+
#
|
|
16
|
+
class NodeAdapter
|
|
17
|
+
def self.to_interscript_node(isc_doc)
|
|
18
|
+
new(isc_doc).build
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(isc_doc)
|
|
22
|
+
@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 = []
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build
|
|
32
|
+
Interscript::Node::Document.new.tap do |doc|
|
|
33
|
+
doc.metadata = build_metadata
|
|
34
|
+
doc.tests = build_tests
|
|
35
|
+
# Maps are addressed by their file-derived name (locate() and
|
|
36
|
+
# Maps.transliterate use it); registering under the system code
|
|
37
|
+
# made every map whose code differs from its file name resolve
|
|
38
|
+
# to an auto-created empty entry — stages[:main].call crashed.
|
|
39
|
+
doc.name = if @isc_doc[:filename]
|
|
40
|
+
File.basename(@isc_doc[:filename].to_s, ".isc")
|
|
41
|
+
else
|
|
42
|
+
@isc_doc[:systemCode]
|
|
43
|
+
end
|
|
44
|
+
# The legacy DSL stamps doc_name on every alias and stage; the
|
|
45
|
+
# Ruby/JS compilers emit run directives and alias lookups as
|
|
46
|
+
# Maps.transliterate(stage.doc_name, ...) — without it every
|
|
47
|
+
# 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
|
|
57
|
+
doc.aliases = build_aliases(doc.name)
|
|
58
|
+
build_stages(doc.name).each { |name, stage| doc.stages[name] = stage }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def build_metadata
|
|
65
|
+
meta = Interscript::Node::MetaData.new
|
|
66
|
+
@isc_doc[:metadata].each do |key, value|
|
|
67
|
+
meta[key.to_sym] = value
|
|
68
|
+
end
|
|
69
|
+
meta
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def build_tests
|
|
73
|
+
return nil if @isc_doc[:tests].empty?
|
|
74
|
+
|
|
75
|
+
tests = Interscript::Node::Tests.new
|
|
76
|
+
@isc_doc[:tests].each do |t|
|
|
77
|
+
tests.data << [t[:input], t[:expected]]
|
|
78
|
+
end
|
|
79
|
+
tests
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_aliases(doc_name)
|
|
83
|
+
@isc_doc[:aliases].each_with_object({}) do |a, h|
|
|
84
|
+
# The runtime resolves aliases through AliasDef#data (see
|
|
85
|
+
# Interpreter::Stage#build_item); a bare item here hands it a
|
|
86
|
+
# raw Array once Any#data unrolls.
|
|
87
|
+
alias_def = Interscript::Node::AliasDef.new(a[:name].to_sym, convert_item(a[:value]))
|
|
88
|
+
alias_def.doc_name = doc_name
|
|
89
|
+
h[a[:name].to_sym] = alias_def
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# ISC v1 has no import marker; aliased dependencies are reached
|
|
94
|
+
# through dep_aliases, which `import` does not gate. Documents
|
|
95
|
+
# load through the same dispatch the compiler uses, so chains of
|
|
96
|
+
# .isc/.imp dependencies resolve recursively.
|
|
97
|
+
def build_dependencies(doc)
|
|
98
|
+
Array(@isc_doc[:dependencies]).each do |dep_hash|
|
|
99
|
+
dep = Interscript::Node::Dependency.new
|
|
100
|
+
dep.full_name = dep_hash[:target]
|
|
101
|
+
dep.name = dep_hash[:alias]&.to_sym
|
|
102
|
+
# Aliased dependencies are stage-run targets; unaliased ones are
|
|
103
|
+
# library imports (the ISC form of `dependency "posix", import:
|
|
104
|
+
# true` from the .imp corpus) — their aliases merge into scope.
|
|
105
|
+
dep.import = dep.name.nil?
|
|
106
|
+
dep.document = load_dependency_document(dep.full_name)
|
|
107
|
+
doc.dependencies << dep
|
|
108
|
+
doc.dep_aliases[dep.name] = dep if dep.name
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def load_dependency_document(full_name)
|
|
113
|
+
path = Interscript.locate(full_name)
|
|
114
|
+
if path&.end_with?(".isc")
|
|
115
|
+
Interscript::Compiler.parse_isc(path)
|
|
116
|
+
else
|
|
117
|
+
Interscript::DSL.parse(full_name)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def build_stages(doc_name)
|
|
122
|
+
@isc_doc[:stages].each_with_object({}) do |stage, h|
|
|
123
|
+
built = build_stage(stage)
|
|
124
|
+
built.doc_name = doc_name
|
|
125
|
+
h[stage[:name].to_sym] = built
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def build_stage(stage_def)
|
|
130
|
+
stage = Interscript::Node::Stage.new(stage_def[:name].to_sym)
|
|
131
|
+
stage_def[:body].each do |item|
|
|
132
|
+
case item[:kind]
|
|
133
|
+
when :parallel
|
|
134
|
+
group = Interscript::Node::Group::Parallel.new
|
|
135
|
+
item[:rules].each { |r| group.children << build_rule(r) }
|
|
136
|
+
stage.children << group
|
|
137
|
+
when :sequence
|
|
138
|
+
item[:rules].each { |r| stage.children << build_rule(r) }
|
|
139
|
+
when :bare_rule
|
|
140
|
+
stage.children << build_rule(item[:rule])
|
|
141
|
+
when :run
|
|
142
|
+
stage.children << build_run_rule(item)
|
|
143
|
+
when :separate
|
|
144
|
+
kwargs = {}
|
|
145
|
+
kwargs[:separator] = item[:separator].value if item[:separator]
|
|
146
|
+
stage.children << Interscript::Node::Rule::Funcall.new(:separate, **kwargs)
|
|
147
|
+
when :string_case
|
|
148
|
+
stage.children << Interscript::Node::Rule::Funcall.new(item[:op].to_sym,
|
|
149
|
+
**(item[:kwargs] || {}).transform_keys(&:to_sym))
|
|
150
|
+
when :compose
|
|
151
|
+
stage.children << Interscript::Node::Rule::Funcall.new(:compose)
|
|
152
|
+
when :decompose
|
|
153
|
+
stage.children << Interscript::Node::Rule::Funcall.new(:decompose)
|
|
154
|
+
when :funcall
|
|
155
|
+
stage.children << Interscript::Node::Rule::Funcall.new(
|
|
156
|
+
item[:name].to_sym,
|
|
157
|
+
**item[:kwargs].transform_keys(&:to_sym)
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
stage
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def build_rule(rule_def)
|
|
165
|
+
from = convert_item(rule_def[:from])
|
|
166
|
+
to = convert_item(rule_def[:to])
|
|
167
|
+
opts = {}
|
|
168
|
+
%i[before after not_before not_after].each do |k|
|
|
169
|
+
next unless rule_def[:constraints]&.any? { |c| c[:kind] == k }
|
|
170
|
+
constraint = rule_def[:constraints].find { |c| c[:kind] == k }
|
|
171
|
+
opts[k] = convert_item(constraint[:item], :class)
|
|
172
|
+
end
|
|
173
|
+
Interscript::Node::Rule::Sub.new(from, to, **opts)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def build_run_rule(item)
|
|
177
|
+
stage_ref = Interscript::Node::Item::Stage.new(
|
|
178
|
+
item[:stage].to_sym,
|
|
179
|
+
map: item[:dependency]&.to_sym
|
|
180
|
+
)
|
|
181
|
+
Interscript::Node::Rule::Run.new(stage_ref)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def convert_item(item, ctx = :word)
|
|
185
|
+
case item
|
|
186
|
+
when Items::StringValue
|
|
187
|
+
Interscript::Node::Item::String.new(item.value)
|
|
188
|
+
when Items::None
|
|
189
|
+
Interscript::Node::Item::String.new("")
|
|
190
|
+
when Items::Primitive
|
|
191
|
+
convert_primitive(item)
|
|
192
|
+
when Items::AliasRef
|
|
193
|
+
convert_alias_ref(item, ctx)
|
|
194
|
+
when Items::Capture
|
|
195
|
+
Interscript::Node::Item::CaptureRef.new(item.index)
|
|
196
|
+
when Items::Function
|
|
197
|
+
item.name.to_sym
|
|
198
|
+
when Items::Concat
|
|
199
|
+
convert_concat(item)
|
|
200
|
+
when Items::CaptureGroup
|
|
201
|
+
Interscript::Node::Item::CaptureGroup.new(convert_item(item.inner))
|
|
202
|
+
when Items::Maybe
|
|
203
|
+
Interscript::Node::Item::Maybe.new(convert_item(item.inner))
|
|
204
|
+
when Items::Some
|
|
205
|
+
Interscript::Node::Item::Some.new(convert_item(item.inner))
|
|
206
|
+
when Items::Range
|
|
207
|
+
# Keep the range native: both runtimes compile Any(Range) to a
|
|
208
|
+
# codepoint character class [lo-hi]. Expanding it to an Array
|
|
209
|
+
# iterates Ruby String#succ (a, b, … z, aa, ab …), which never
|
|
210
|
+
# reaches non-ASCII codepoints and blows up the node size.
|
|
211
|
+
lo = item.lo.is_a?(Items::StringValue) ? item.lo.value : item.lo
|
|
212
|
+
hi = item.hi.is_a?(Items::StringValue) ? item.hi.value : item.hi
|
|
213
|
+
Interscript::Node::Item::Any.new(lo..hi)
|
|
214
|
+
when Items::Set
|
|
215
|
+
convert_set(item)
|
|
216
|
+
else
|
|
217
|
+
Interscript::Node::Item::String.new(item.to_s)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def convert_primitive(item)
|
|
222
|
+
# In the Ruby runtime, zero-width primitives are represented as
|
|
223
|
+
# Alias nodes referencing Stdlib symbols. See Stdlib::ALIASES.
|
|
224
|
+
Interscript::Node::Item::Alias.new(item.name.to_sym)
|
|
225
|
+
end
|
|
226
|
+
|
|
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
|
+
# Stdlib alias values are regex fragments ("\d", "\b") —
|
|
250
|
+
# keep them as Alias nodes so the runtimes interpolate them raw;
|
|
251
|
+
# wrapping in Any escapes the backslashes ([\\d] matches a
|
|
252
|
+
# literal backslash or d).
|
|
253
|
+
if Interscript::Stdlib::ALIASES.key?(name)
|
|
254
|
+
Interscript::Node::Item::Alias.new(name)
|
|
255
|
+
elsif (imp = @imported[name])
|
|
256
|
+
data = imp.data
|
|
257
|
+
case data
|
|
258
|
+
when Interscript::Node::Item::Any
|
|
259
|
+
data
|
|
260
|
+
when Interscript::Node::Item::String
|
|
261
|
+
Interscript::Node::Item::Any.new(data.data)
|
|
262
|
+
end
|
|
263
|
+
elsif (raw = @local_raw[name])
|
|
264
|
+
converted = convert_item(raw)
|
|
265
|
+
converted if converted.is_a?(Interscript::Node::Item::Any) || converted.is_a?(Interscript::Node::Item::String)
|
|
266
|
+
end
|
|
267
|
+
@resolving.delete(name)
|
|
268
|
+
@charset_cache[name] = node
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def convert_concat(concat)
|
|
272
|
+
parts = concat.parts.map { |p| convert_item(p) }
|
|
273
|
+
return parts.first if parts.size == 1
|
|
274
|
+
parts.reduce { |acc, part| acc + part }
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def convert_set(set)
|
|
278
|
+
Interscript::Node::Item::Any.new(
|
|
279
|
+
set.chars.map do |c|
|
|
280
|
+
# Entries may be primitives/alias refs (e.g. any([boundary,
|
|
281
|
+
# "\u200c", "\u200d"])) — stringifying them baked an object
|
|
282
|
+
# inspect into the compiled character class.
|
|
283
|
+
case c
|
|
284
|
+
when ::String
|
|
285
|
+
Interscript::Node::Item::String.new(c)
|
|
286
|
+
when Items::StringValue
|
|
287
|
+
Interscript::Node::Item::String.new(c.value)
|
|
288
|
+
when Items::Primitive
|
|
289
|
+
convert_primitive(c)
|
|
290
|
+
when Items::AliasRef
|
|
291
|
+
convert_alias_ref(c, :class) ||
|
|
292
|
+
begin
|
|
293
|
+
# Legacy parity: a bare alias inside any(...) compiles to the
|
|
294
|
+
# Stdlib value (Any(nil) there for imported aliases). Keeping
|
|
295
|
+
# the Alias item resolves to the imported charset *string* at
|
|
296
|
+
# build time, baking a 1000-char literal into the constraint
|
|
297
|
+
# regexp — a lookbehind that never matches.
|
|
298
|
+
name = convert_alias_ref(c).name
|
|
299
|
+
val = Interscript::Stdlib::ALIASES[name]
|
|
300
|
+
val ? Interscript::Node::Item::Any.new(val) : Interscript::Node::Item::String.new("")
|
|
301
|
+
end
|
|
302
|
+
else
|
|
303
|
+
convert_item(c)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
)
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Interscript
|
|
4
|
+
module Isc
|
|
5
|
+
# Normalizes the raw parslet tree into the documented Parser.parse
|
|
6
|
+
# shape: scalar leaves (string/identifier nodes) become plain
|
|
7
|
+
# strings, metadata field pairs become keyed hashes, and arrow-form
|
|
8
|
+
# test pairs merge into single {input:, expected:} entries. Deeper
|
|
9
|
+
# item trees (rule from/to, constraints, kwargs) keep their parslet
|
|
10
|
+
# shape — DocumentBuilder materializes those through Transform.
|
|
11
|
+
class Normalizer
|
|
12
|
+
def self.normalize(tree)
|
|
13
|
+
new(tree).normalize
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(tree)
|
|
17
|
+
@tree = tree
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def normalize
|
|
21
|
+
system = @tree[:system]
|
|
22
|
+
return @tree unless system
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
system: {
|
|
26
|
+
system_code: scalar(system[:system_code]),
|
|
27
|
+
body: Array(system[:body]).map { |block| normalize_block(block) }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# {string: [{char: "B"@8}, ...]} and {identifier: "main"@31}
|
|
35
|
+
# become plain strings.
|
|
36
|
+
def scalar(node)
|
|
37
|
+
return node if node.is_a?(::String) || node.nil?
|
|
38
|
+
if node.key?(:string)
|
|
39
|
+
# Parts are {char:} hashes and escape fragments ({dquote:},
|
|
40
|
+
# {unicode:}, …) in the general grammar; some constructions
|
|
41
|
+
# (e.g. the rababa directive) capture bare slices instead.
|
|
42
|
+
# Escapes decode via the shared fold — folding only :char here
|
|
43
|
+
# silently dropped every escaped character.
|
|
44
|
+
Interscript::Isc::Transform.decode_string_parts(node[:string])
|
|
45
|
+
elsif node.key?(:identifier)
|
|
46
|
+
node[:identifier].to_s
|
|
47
|
+
elsif node.key?(:raw)
|
|
48
|
+
node[:raw].to_s
|
|
49
|
+
else
|
|
50
|
+
node
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def normalize_block(block)
|
|
55
|
+
return block unless block.is_a?(Hash)
|
|
56
|
+
if block.key?(:metadata)
|
|
57
|
+
{metadata: Array(block[:metadata]).map { |field| normalize_meta_field(field) }}
|
|
58
|
+
elsif block.key?(:aliases)
|
|
59
|
+
{
|
|
60
|
+
aliases: Array(block[:aliases]).map do |alias_def|
|
|
61
|
+
{name: scalar(alias_def[:name]), value: alias_def[:value]}.compact
|
|
62
|
+
end
|
|
63
|
+
}
|
|
64
|
+
elsif block.key?(:tests)
|
|
65
|
+
# A comment-only or empty tests block captures as its raw
|
|
66
|
+
# text through the artifact; the DSL grammar folds it. Both
|
|
67
|
+
# normalize to no tests.
|
|
68
|
+
items = block[:tests].is_a?(Array) ? block[:tests] : []
|
|
69
|
+
{tests: merge_tests(items)}
|
|
70
|
+
elsif block.key?(:stage)
|
|
71
|
+
{stage_name: scalar(block[:stage_name]), stage: normalize_stage_items(block[:stage])}
|
|
72
|
+
elsif block.key?(:target)
|
|
73
|
+
{target: scalar(block[:target]), alias: scalar(block[:alias])}.compact
|
|
74
|
+
else
|
|
75
|
+
block
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A capture-free block (empty or comment-only body — comments are
|
|
80
|
+
# consumed as whitespace) folds to a raw slice under .as; the .parg
|
|
81
|
+
# artifact's empty capture is an array. Match it.
|
|
82
|
+
def normalize_stage_items(items)
|
|
83
|
+
Array(items).map do |item|
|
|
84
|
+
if item.is_a?(Hash)
|
|
85
|
+
%i[parallel sequence].each do |k|
|
|
86
|
+
item = {k => []} if item.key?(k) && !item[k].is_a?(Array)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
item
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# {field_name: {identifier: "authority"}, field_value: {string: [...]}}
|
|
94
|
+
# becomes {authority: "BGN-PCGN"}.
|
|
95
|
+
def normalize_meta_field(field)
|
|
96
|
+
return field unless field.is_a?(Hash) && field.key?(:field_name)
|
|
97
|
+
|
|
98
|
+
name = scalar(field[:field_name]).to_sym
|
|
99
|
+
value = field.key?(:field_block) ? field[:field_block] : scalar(field[:field_value])
|
|
100
|
+
{name => value}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The arrow form usually parses as one {input:, expected:} hash
|
|
104
|
+
# per test; tolerate adjacent single-key entries as well.
|
|
105
|
+
def merge_tests(entries)
|
|
106
|
+
merged = []
|
|
107
|
+
pending_input = nil
|
|
108
|
+
entries.each do |entry|
|
|
109
|
+
next unless entry.is_a?(Hash)
|
|
110
|
+
if entry.key?(:input) && entry.key?(:expected)
|
|
111
|
+
merged << {input: scalar(entry[:input]), expected: scalar(entry[:expected])}
|
|
112
|
+
pending_input = nil
|
|
113
|
+
elsif entry.key?(:input)
|
|
114
|
+
pending_input = scalar(entry[:input])
|
|
115
|
+
elsif entry.key?(:expected)
|
|
116
|
+
merged << {input: pending_input, expected: scalar(entry[:expected])}
|
|
117
|
+
pending_input = nil
|
|
118
|
+
else
|
|
119
|
+
merged << entry
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
merged << {input: pending_input, expected: ""} if pending_input
|
|
123
|
+
merged
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|