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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a04dd91ecaf1ba6e202f6d61ddae77a1fa97afe3ae2f40874dac43142e09baa3
|
|
4
|
+
data.tar.gz: 7315cdfb66bfae1620920ae97f3cc363ace436d8506c227dbb449a48cb7fac10
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df34de55722a54f9942b0d24eef322bc1b2e54fa4cb587249e98da56fb6bad21e839038922ca9555edbe0ccdb0f8a278dcbd15026dff3160e91ce5fd334c3220
|
|
7
|
+
data.tar.gz: a88b209689466f418cd6af6c31cca544e656c4df98215120cdc2d1f5131aa8781c67803c3729da1aa3c7e7d8da47f438ccfee84203e3568f6c44c88843def7c2
|
data/README.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
image:https://github.com/interscript/interscript-ruby/actions/workflows/rake.yml/badge.svg["CI status", link="https://github.com/interscript/interscript-ruby/actions/workflows/rake.yml"]
|
|
1
2
|
# Interscript
|
|
2
3
|
|
|
3
4
|
Documentation is available directory higher in file README.adoc.
|
|
5
|
+
|
|
6
|
+
## Phonological layer (optional)
|
|
7
|
+
|
|
8
|
+
Some transliteration systems need their input vocalized before the map
|
|
9
|
+
can apply (undiacritized Arabic, nikud-less Hebrew, unsegmented Thai).
|
|
10
|
+
The stdlib ships adapters for this: the `secryst` function dispatches
|
|
11
|
+
to a [secryst crystal](https://www.secryst.org) —
|
|
12
|
+
`gem 'secryst'` (Ruby), `pip install secryst`, or `npm i secryst` —
|
|
13
|
+
which resolves a model id through the
|
|
14
|
+
[interscript-ml `models.yaml` index](https://github.com/interscript/interscript-ml)
|
|
15
|
+
and runs sha256-verified ONNX inference locally. The gem has no
|
|
16
|
+
interscript dependency: a crystal can be used standalone for any G2P
|
|
17
|
+
need, and an engine without a crystal simply cannot execute maps that
|
|
18
|
+
declare a vocalization step.
|
|
Binary file
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Find first failing line in each failing .isc file
|
|
3
|
+
# Usage: diagnose_parse_failures.rb [N] # show first N failures with context
|
|
4
|
+
|
|
5
|
+
require "interscript"
|
|
6
|
+
require "interscript/isc"
|
|
7
|
+
|
|
8
|
+
n = (ARGV.shift || "5").to_i
|
|
9
|
+
fails = []
|
|
10
|
+
Dir.glob("/tmp/isc-verify/*.isc").sort.each do |path|
|
|
11
|
+
src = File.read(path)
|
|
12
|
+
begin
|
|
13
|
+
Interscript::Isc::Parser.parse(src, filename: File.basename(path))
|
|
14
|
+
rescue Interscript::Isc::ParseError
|
|
15
|
+
fails << path
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
puts "Total fails: #{fails.size}"
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
fails.first(n).each do |path|
|
|
23
|
+
src = File.read(path)
|
|
24
|
+
lines = src.lines
|
|
25
|
+
# Binary search for first failing prefix
|
|
26
|
+
lo = 1
|
|
27
|
+
hi = lines.size
|
|
28
|
+
while lo < hi
|
|
29
|
+
mid = (lo + hi) / 2
|
|
30
|
+
snippet = lines[0...mid].join
|
|
31
|
+
open = snippet.count("{") - snippet.count("}")
|
|
32
|
+
snippet += "}" * open
|
|
33
|
+
begin
|
|
34
|
+
Interscript::Isc::Parser.parse(snippet, filename: path)
|
|
35
|
+
lo = mid + 1
|
|
36
|
+
rescue
|
|
37
|
+
hi = mid
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
puts "=== #{File.basename(path)} (first fail at line #{lo}) ==="
|
|
41
|
+
start = [1, lo - 2].max
|
|
42
|
+
lines[(start - 1)...(lo + 1)].each_with_index do |line, i|
|
|
43
|
+
marker = (start + i == lo) ? ">>" : " "
|
|
44
|
+
puts " #{marker} #{start + i}: #{line.chomp[0..100]}"
|
|
45
|
+
end
|
|
46
|
+
puts
|
|
47
|
+
end
|
data/exe/interscript
CHANGED
data/exe/verify_isc_deep
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Deep equivalence checker: compares ISC parser output against Ruby DSL
|
|
3
|
+
# at the rule level (from/to/constraints), not just test counts.
|
|
4
|
+
|
|
5
|
+
require "interscript"
|
|
6
|
+
require "interscript/isc"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
class DeepVerifier
|
|
10
|
+
Result = Struct.new(:status, :details, :imp_data, :isc_data)
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@results = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def verify(imp_path, isc_path)
|
|
17
|
+
imp_data = extract_imp(imp_path)
|
|
18
|
+
isc_data = extract_isc(isc_path)
|
|
19
|
+
|
|
20
|
+
return Result.new(:both_fail, "Both parsers failed", nil, nil) if imp_data.nil? && isc_data.nil?
|
|
21
|
+
return Result.new(:imp_fail, "Ruby DSL parse failed", nil, isc_data) if imp_data.nil?
|
|
22
|
+
return Result.new(:isc_fail, "ISC parse failed", imp_data, nil) if isc_data.nil?
|
|
23
|
+
|
|
24
|
+
details = []
|
|
25
|
+
|
|
26
|
+
# Compare metadata — skip DSL-internal fields only.
|
|
27
|
+
# STANDARD_ARRAY_KEYS bug is fixed, so url/notes/etc. are now stored.
|
|
28
|
+
skip_fields = [:nonstandard, :tests, :stages, :aliases, :dependencies]
|
|
29
|
+
imp_meta = imp_data[:metadata].reject { |k, _| skip_fields.include?(k.to_sym) }
|
|
30
|
+
isc_meta = isc_data[:metadata].reject { |k, _| skip_fields.include?(k.to_sym) }
|
|
31
|
+
(imp_meta.keys | isc_meta.keys).each do |key|
|
|
32
|
+
imp_val = normalize_meta(imp_meta[key])
|
|
33
|
+
isc_val = normalize_meta(isc_meta[key])
|
|
34
|
+
next if imp_val == isc_val
|
|
35
|
+
next if imp_val.nil? && isc_val.to_s.strip == ""
|
|
36
|
+
next if isc_val.nil? && imp_val.to_s.strip == ""
|
|
37
|
+
details << "metadata[#{key}]: imp=#{imp_val.inspect[0..60]} isc=#{isc_val.inspect[0..60]}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Compare tests
|
|
41
|
+
Set.new(imp_data[:tests])
|
|
42
|
+
isc_tests = Set.new(isc_data[:tests])
|
|
43
|
+
missing = imp_data[:tests].reject { |t| isc_tests.include?(t) }
|
|
44
|
+
details << "missing #{missing.size} tests from ISC" if missing.any?
|
|
45
|
+
|
|
46
|
+
# Compare aliases
|
|
47
|
+
imp_aliases = imp_data[:aliases].to_h
|
|
48
|
+
isc_aliases = isc_data[:aliases].to_h
|
|
49
|
+
if imp_aliases.keys.sort != isc_aliases.keys.sort
|
|
50
|
+
details << "alias names differ: imp=#{imp_aliases.keys.sort} isc=#{isc_aliases.keys.sort}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Compare rule counts per stage
|
|
54
|
+
imp_rules = imp_data[:rule_counts]
|
|
55
|
+
isc_rules = isc_data[:rule_counts]
|
|
56
|
+
if imp_rules != isc_rules
|
|
57
|
+
details << "rule counts: imp=#{imp_rules} isc=#{isc_rules}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
status = details.empty? ? :equivalent : :differ
|
|
61
|
+
Result.new(status, details.join("; "), imp_data, isc_data)
|
|
62
|
+
rescue => e
|
|
63
|
+
Result.new(:error, "#{e.class}: #{e.message[0..100]}", nil, nil)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def extract_imp(path)
|
|
69
|
+
dsl = Interscript::DSL.parse(File.basename(path, ".imp"))
|
|
70
|
+
hash = dsl.to_hash
|
|
71
|
+
|
|
72
|
+
metadata = hash[:metadata]&.fetch(:data, {}) || {}
|
|
73
|
+
tests = (dsl.tests&.data || []).map { |t| [t[0], t[1]] }
|
|
74
|
+
aliases = (hash[:aliases] || {}).map { |k, v| [k.to_s, rule_to_s(v)] }
|
|
75
|
+
|
|
76
|
+
rule_counts = {}
|
|
77
|
+
hash[:stages]&.each do |name, stage|
|
|
78
|
+
rule_counts[name] = count_rules(stage[:children])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
metadata: metadata,
|
|
83
|
+
tests: tests,
|
|
84
|
+
aliases: aliases,
|
|
85
|
+
rule_counts: rule_counts
|
|
86
|
+
}
|
|
87
|
+
rescue => e
|
|
88
|
+
warn "IMP fail #{path}: #{e.message[0..80]}"
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def extract_isc(path)
|
|
93
|
+
src = File.read(path)
|
|
94
|
+
tree = Interscript::Isc::Parser.parse(src, filename: File.basename(path))
|
|
95
|
+
doc = Interscript::Isc::DocumentBuilder.build(tree, filename: File.basename(path))
|
|
96
|
+
|
|
97
|
+
metadata = doc[:metadata] || {}
|
|
98
|
+
tests = doc[:tests].map { |t| [t[:input], t[:expected]] }
|
|
99
|
+
aliases = (doc[:aliases] || []).map { |a| [a[:name], item_to_s(a[:value])] }
|
|
100
|
+
|
|
101
|
+
rule_counts = {}
|
|
102
|
+
doc[:stages].each do |stage|
|
|
103
|
+
rule_counts[stage[:name].to_sym] = count_stage_rules(stage[:body])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
{
|
|
107
|
+
metadata: metadata,
|
|
108
|
+
tests: tests,
|
|
109
|
+
aliases: aliases,
|
|
110
|
+
rule_counts: rule_counts
|
|
111
|
+
}
|
|
112
|
+
rescue => e
|
|
113
|
+
warn "ISC fail #{path}: #{e.message[0..80]}"
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def normalize_meta(val)
|
|
118
|
+
case val
|
|
119
|
+
when String then val.gsub(/\s+/, " ").strip
|
|
120
|
+
when Array then val.map { |v| normalize_meta(v) }.reject { |v| v == "" }
|
|
121
|
+
when Hash then val.transform_values { |v| normalize_meta(v) }
|
|
122
|
+
else val
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def count_rules(children)
|
|
127
|
+
return 0 unless children.is_a?(Array)
|
|
128
|
+
count = 0
|
|
129
|
+
children.each do |child|
|
|
130
|
+
case child[:class].to_s
|
|
131
|
+
when /Group/
|
|
132
|
+
count += count_rules(child[:children])
|
|
133
|
+
when /Rule/
|
|
134
|
+
count += 1
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
count
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def count_stage_rules(body)
|
|
141
|
+
return 0 unless body.is_a?(Array)
|
|
142
|
+
count = 0
|
|
143
|
+
body.each do |item|
|
|
144
|
+
case item[:kind]
|
|
145
|
+
when :sequence, :parallel
|
|
146
|
+
count += item[:rules].size
|
|
147
|
+
when :bare_rule, :sub, :run, :separate, :compose, :string_case
|
|
148
|
+
count += 1
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
count
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def rule_to_s(node)
|
|
155
|
+
return "" unless node.is_a?(Hash)
|
|
156
|
+
data = node[:data]
|
|
157
|
+
data ? data.to_s : node.to_s
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def item_to_s(item)
|
|
161
|
+
item.respond_to?(:value) ? item.value : item.to_s
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
if $PROGRAM_NAME == __FILE__
|
|
166
|
+
maps_dir = "/Users/mulgogi/src/interscript/maps/maps"
|
|
167
|
+
isc_dir = "/tmp/isc-verify"
|
|
168
|
+
|
|
169
|
+
v = DeepVerifier.new
|
|
170
|
+
results = {}
|
|
171
|
+
counts = Hash.new(0)
|
|
172
|
+
|
|
173
|
+
Dir.glob("#{maps_dir}/*.imp").sort.each do |imp_path|
|
|
174
|
+
base = File.basename(imp_path, ".imp")
|
|
175
|
+
isc_path = "#{isc_dir}/#{base}.isc"
|
|
176
|
+
next unless File.exist?(isc_path)
|
|
177
|
+
|
|
178
|
+
r = v.verify(imp_path, isc_path)
|
|
179
|
+
results[base] = r
|
|
180
|
+
counts[r.status] += 1
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
puts "=" * 60
|
|
184
|
+
puts "DEEP EQUIVALENCE REPORT"
|
|
185
|
+
puts "=" * 60
|
|
186
|
+
puts "Total maps: #{results.size}"
|
|
187
|
+
puts "Equivalent: #{counts[:equivalent]}"
|
|
188
|
+
puts "Differ: #{counts[:differ]}"
|
|
189
|
+
puts "IMP fail: #{counts[:imp_fail]}"
|
|
190
|
+
puts "ISC fail: #{counts[:isc_fail]}"
|
|
191
|
+
puts "Both fail: #{counts[:both_fail]}"
|
|
192
|
+
puts "Errors: #{counts[:error]}"
|
|
193
|
+
puts
|
|
194
|
+
|
|
195
|
+
if counts[:differ] > 0
|
|
196
|
+
puts "Differences:"
|
|
197
|
+
results.select { |_, r| r.status == :differ }.each do |base, r|
|
|
198
|
+
puts " #{base}: #{r.details}"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
File.write("/tmp/deep_report.json", JSON.pretty_generate(
|
|
203
|
+
results.transform_values do |r|
|
|
204
|
+
{
|
|
205
|
+
status: r.status,
|
|
206
|
+
details: r.details
|
|
207
|
+
}
|
|
208
|
+
end
|
|
209
|
+
))
|
|
210
|
+
puts "Full report: /tmp/deep_report.json"
|
|
211
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Verification harness: for each .imp file, compare Ruby DSL parse vs ISC parse.
|
|
3
|
+
# Reports per-file equivalence status.
|
|
4
|
+
|
|
5
|
+
require "interscript"
|
|
6
|
+
require "interscript/isc"
|
|
7
|
+
require "json"
|
|
8
|
+
|
|
9
|
+
class Verifier
|
|
10
|
+
Result = Struct.new(:status, :details, :imp_data, :isc_data)
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@results = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def verify(imp_path, isc_path)
|
|
17
|
+
imp_data = parse_imp(imp_path)
|
|
18
|
+
isc_data = parse_isc(isc_path)
|
|
19
|
+
|
|
20
|
+
return Result.new(:both_fail, "Both parsers failed", nil, nil) if imp_data.nil? && isc_data.nil?
|
|
21
|
+
return Result.new(:imp_fail, "Ruby DSL parse failed", nil, isc_data) if imp_data.nil?
|
|
22
|
+
return Result.new(:isc_fail, "ISC parse failed", imp_data, nil) if isc_data.nil?
|
|
23
|
+
|
|
24
|
+
# Compare semantic equivalence on the dimensions we can compare:
|
|
25
|
+
# - If ISC tests are a SUPERSET of IMP tests, count as equivalent
|
|
26
|
+
# (ISC parser is more capable; extra tests are legitimately parsed)
|
|
27
|
+
# - If counts differ and tests don't match, it's a real difference
|
|
28
|
+
details = []
|
|
29
|
+
imp_set = Set.new(imp_data[:tests])
|
|
30
|
+
isc_set = Set.new(isc_data[:tests])
|
|
31
|
+
|
|
32
|
+
missing_from_isc = imp_data[:tests].reject { |t| isc_set.include?(t) }
|
|
33
|
+
isc_data[:tests].reject { |t| imp_set.include?(t) }
|
|
34
|
+
|
|
35
|
+
if missing_from_isc.any?
|
|
36
|
+
details << "missing #{missing_from_isc.size} tests from ISC"
|
|
37
|
+
end
|
|
38
|
+
if imp_data[:stages_count] != isc_data[:stages_count]
|
|
39
|
+
details << "stages: imp=#{imp_data[:stages_count]} isc=#{isc_data[:stages_count]}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
status = details.empty? ? :equivalent : :differ
|
|
43
|
+
Result.new(status, details.join("; "), imp_data, isc_data)
|
|
44
|
+
rescue => e
|
|
45
|
+
Result.new(:error, "#{e.class}: #{e.message[0..100]}", nil, nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def parse_imp(path)
|
|
51
|
+
dsl = Interscript::DSL.parse(File.basename(path, ".imp"))
|
|
52
|
+
tests_data = (dsl.tests&.data if dsl.tests.respond_to?(:data)) || []
|
|
53
|
+
tests = tests_data.map { |t| [t[0], t[1]] }
|
|
54
|
+
{
|
|
55
|
+
tests_count: tests.size,
|
|
56
|
+
tests: tests,
|
|
57
|
+
stages_count: dsl.stages&.size || 0
|
|
58
|
+
}
|
|
59
|
+
rescue => e
|
|
60
|
+
warn "IMP parse fail #{path}: #{e.message[0..80]}"
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def parse_isc(path)
|
|
65
|
+
src = File.read(path)
|
|
66
|
+
tree = Interscript::Isc::Parser.parse(src, filename: File.basename(path))
|
|
67
|
+
doc = Interscript::Isc::DocumentBuilder.build(tree, filename: File.basename(path))
|
|
68
|
+
{
|
|
69
|
+
tests_count: doc[:tests].size,
|
|
70
|
+
tests: doc[:tests].map { |t| [t[:input], t[:expected]] },
|
|
71
|
+
stages_count: doc[:stages].size
|
|
72
|
+
}
|
|
73
|
+
rescue => e
|
|
74
|
+
warn "ISC parse fail #{path}: #{e.message[0..80]}"
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if $PROGRAM_NAME == __FILE__
|
|
80
|
+
maps_dir = "/Users/mulgogi/src/interscript/maps/maps"
|
|
81
|
+
isc_dir = "/tmp/isc-verify"
|
|
82
|
+
|
|
83
|
+
v = Verifier.new
|
|
84
|
+
results = {}
|
|
85
|
+
counts = Hash.new(0)
|
|
86
|
+
|
|
87
|
+
Dir.glob("#{maps_dir}/*.imp").sort.each do |imp_path|
|
|
88
|
+
base = File.basename(imp_path, ".imp")
|
|
89
|
+
isc_path = "#{isc_dir}/#{base}.isc"
|
|
90
|
+
next unless File.exist?(isc_path)
|
|
91
|
+
|
|
92
|
+
r = v.verify(imp_path, isc_path)
|
|
93
|
+
results[base] = r
|
|
94
|
+
counts[r.status] += 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
puts "=" * 60
|
|
98
|
+
puts "VERIFICATION REPORT"
|
|
99
|
+
puts "=" * 60
|
|
100
|
+
puts "Total maps: #{results.size}"
|
|
101
|
+
puts "Equivalent: #{counts[:equivalent]}"
|
|
102
|
+
puts "Differ: #{counts[:differ]}"
|
|
103
|
+
puts "IMP fail: #{counts[:imp_fail]}"
|
|
104
|
+
puts "ISC fail: #{counts[:isc_fail]}"
|
|
105
|
+
puts "Both fail: #{counts[:both_fail]}"
|
|
106
|
+
puts "Errors: #{counts[:error]}"
|
|
107
|
+
puts
|
|
108
|
+
|
|
109
|
+
if counts[:differ] > 0
|
|
110
|
+
puts "First 10 differences:"
|
|
111
|
+
results.select { |_, r| r.status == :differ }.first(10).each do |base, r|
|
|
112
|
+
puts " #{base}: #{r.details}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Save full report
|
|
117
|
+
File.write("/tmp/verification_report.json", JSON.pretty_generate(
|
|
118
|
+
results.transform_values do |r|
|
|
119
|
+
{
|
|
120
|
+
status: r.status,
|
|
121
|
+
details: r.details,
|
|
122
|
+
imp: r.imp_data,
|
|
123
|
+
isc: r.isc_data
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
))
|
|
127
|
+
puts "Full report: /tmp/verification_report.json"
|
|
128
|
+
end
|
data/interscript.gemspec
CHANGED
|
@@ -1,32 +1,39 @@
|
|
|
1
|
-
require_relative
|
|
1
|
+
require_relative "lib/interscript/version"
|
|
2
2
|
|
|
3
3
|
Gem::Specification.new do |spec|
|
|
4
|
-
spec.name
|
|
5
|
-
spec.version
|
|
6
|
-
spec.summary
|
|
7
|
-
spec.description
|
|
8
|
-
spec.authors
|
|
9
|
-
spec.email
|
|
4
|
+
spec.name = "interscript"
|
|
5
|
+
spec.version = Interscript::VERSION
|
|
6
|
+
spec.summary = "Interoperable script conversion systems"
|
|
7
|
+
spec.description = "Interoperable script conversion systems."
|
|
8
|
+
spec.authors = ["Ribose Inc."]
|
|
9
|
+
spec.email = ["open.source@ribose.com"]
|
|
10
10
|
|
|
11
|
-
spec.
|
|
12
|
-
spec.
|
|
13
|
-
spec.
|
|
14
|
-
|
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
|
11
|
+
spec.homepage = "https://www.interscript.com"
|
|
12
|
+
spec.license = "BSD-2-Clause"
|
|
13
|
+
spec.required_ruby_version = ">= 3.3.0"
|
|
16
14
|
|
|
17
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
-
spec.metadata["source_code_uri"] = "https://github.com/interscript/interscript"
|
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/interscript/interscript-ruby"
|
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/interscript/interscript-ruby/releases"
|
|
18
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/interscript/interscript-ruby/issues"
|
|
19
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
spec.files = Dir.chdir(__dir__) do
|
|
22
|
+
Dir[
|
|
23
|
+
"lib/**/*",
|
|
24
|
+
"exe/**/*",
|
|
25
|
+
"docs/**/*",
|
|
26
|
+
"README*",
|
|
27
|
+
"LICENSE*",
|
|
28
|
+
"*.gemspec"
|
|
29
|
+
].select { |f| File.file?(f) }
|
|
24
30
|
end
|
|
25
|
-
spec.bindir
|
|
26
|
-
spec.executables
|
|
31
|
+
spec.bindir = "exe"
|
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
27
33
|
spec.require_paths = ["lib"]
|
|
28
34
|
|
|
29
35
|
spec.add_dependency "thor"
|
|
30
|
-
spec.add_dependency "interscript-maps", "~>
|
|
36
|
+
spec.add_dependency "interscript-maps", "~> 2.5"
|
|
31
37
|
spec.add_dependency "text"
|
|
38
|
+
spec.add_dependency "parsanol", ">= 1.3.61", "< 2"
|
|
32
39
|
end
|
data/lib/interscript/command.rb
CHANGED
|
@@ -1,32 +1,41 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "thor"
|
|
2
|
+
require "interscript"
|
|
3
|
+
require "json"
|
|
4
4
|
|
|
5
5
|
module Interscript
|
|
6
6
|
# Command line interface
|
|
7
7
|
class Command < Thor
|
|
8
|
-
desc
|
|
9
|
-
option :system, aliases:
|
|
10
|
-
option :output, aliases:
|
|
8
|
+
desc "<file>", "Transliterate text"
|
|
9
|
+
option :system, aliases: "-s", required: true, desc: "Transliteration system"
|
|
10
|
+
option :output, aliases: "-o", required: false, desc: "Output file"
|
|
11
|
+
option :compiler, aliases: "-c", required: false, desc: "Compiler (eg. Interscript::Compiler::Python)"
|
|
11
12
|
# Was this option really well thought out? The last parameter is a cache, isn't it?
|
|
12
|
-
#option :map, aliases: '-m', required: false, default: "{}", desc: 'Transliteration mapping json'
|
|
13
|
+
# option :map, aliases: '-m', required: false, default: "{}", desc: 'Transliteration mapping json'
|
|
13
14
|
|
|
14
15
|
def translit(input)
|
|
16
|
+
compiler = if options[:compiler]
|
|
17
|
+
compiler = options[:compiler].split("::").last.downcase
|
|
18
|
+
require "interscript/compiler/#{compiler}"
|
|
19
|
+
Object.const_get(options[:compiler])
|
|
20
|
+
else
|
|
21
|
+
Interscript::Interpreter
|
|
22
|
+
end
|
|
23
|
+
|
|
15
24
|
if options[:output]
|
|
16
|
-
Interscript.transliterate_file(options[:system], input, options[:output]
|
|
25
|
+
Interscript.transliterate_file(options[:system], input, options[:output], compiler: compiler)
|
|
17
26
|
else
|
|
18
|
-
puts Interscript.transliterate(options[:system], IO.read(input))
|
|
27
|
+
puts Interscript.transliterate(options[:system], IO.read(input), compiler: compiler)
|
|
19
28
|
end
|
|
20
29
|
end
|
|
21
30
|
|
|
22
|
-
desc
|
|
31
|
+
desc "list", "Prints allowed transliteration systems"
|
|
23
32
|
def list
|
|
24
33
|
Interscript.maps(load_path: true).each do |path|
|
|
25
34
|
puts path
|
|
26
35
|
end
|
|
27
36
|
end
|
|
28
37
|
|
|
29
|
-
desc
|
|
38
|
+
desc "stats", "Prints statistics about the maps we have"
|
|
30
39
|
def stats
|
|
31
40
|
maps = Interscript.maps(load_path: true)
|
|
32
41
|
parsed_maps = maps.map { |i| [i, Interscript.parse(i)] }.to_h
|
|
@@ -35,7 +44,7 @@ module Interscript
|
|
|
35
44
|
end
|
|
36
45
|
|
|
37
46
|
authorities, languages, source_scripts, target_scripts = 4.times.map do |i|
|
|
38
|
-
maps.group_by { |map| map.split(
|
|
47
|
+
maps.group_by { |map| map.split("-")[i] }
|
|
39
48
|
end
|
|
40
49
|
|
|
41
50
|
puts <<~END
|