expressir 2.3.0 → 2.3.1
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/.rubocop_todo.yml +480 -49
- data/benchmark/srl_benchmark.rb +47 -34
- data/benchmark/srl_native_benchmark.rb +20 -16
- data/benchmark/srl_ruby_benchmark.rb +14 -12
- data/expressir.gemspec +2 -2
- data/lib/expressir/changes/item_change.rb +0 -1
- data/lib/expressir/changes/mapping_change.rb +0 -1
- data/lib/expressir/changes/schema_change.rb +0 -1
- data/lib/expressir/changes/version_change.rb +0 -1
- data/lib/expressir/commands/changes_import_eengine.rb +2 -2
- data/lib/expressir/commands/validate_ascii.rb +0 -1
- data/lib/expressir/eengine/arm_compare_report.rb +0 -1
- data/lib/expressir/eengine/changes_section.rb +0 -1
- data/lib/expressir/eengine/mim_compare_report.rb +0 -1
- data/lib/expressir/eengine/modified_object.rb +0 -1
- data/lib/expressir/express/builder.rb +64 -21
- data/lib/expressir/express/builders/built_in_builder.rb +4 -2
- data/lib/expressir/express/builders/entity_decl_builder.rb +8 -4
- data/lib/expressir/express/builders/expression_builder.rb +0 -6
- data/lib/expressir/express/builders/function_decl_builder.rb +8 -8
- data/lib/expressir/express/builders/procedure_decl_builder.rb +8 -8
- data/lib/expressir/express/builders/rule_decl_builder.rb +8 -8
- data/lib/expressir/express/builders/syntax_builder.rb +2 -44
- data/lib/expressir/express/formatters/remark_formatter.rb +1 -3
- data/lib/expressir/express/parser.rb +234 -14
- data/lib/expressir/express/remark_attacher.rb +47 -18
- data/lib/expressir/express/transformer/remark_handling.rb +0 -1
- data/lib/expressir/model/exp_file.rb +2 -1
- data/lib/expressir/model/model_element.rb +1 -1
- data/lib/expressir/model/repository.rb +8 -9
- data/lib/expressir/model/search_engine.rb +7 -6
- data/lib/expressir/package/builder.rb +3 -1
- data/lib/expressir/package/metadata.rb +0 -1
- data/lib/expressir/schema_manifest.rb +0 -1
- data/lib/expressir/schema_manifest_entry.rb +0 -1
- data/lib/expressir/version.rb +1 -1
- metadata +15 -15
data/benchmark/srl_benchmark.rb
CHANGED
|
@@ -5,21 +5,21 @@
|
|
|
5
5
|
# Compares Parsanol Ruby vs Native performance on full STEPmod Resource Library
|
|
6
6
|
# Features: Live progress, emojis, colors, per-schema stats
|
|
7
7
|
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require
|
|
8
|
+
require "bundler/setup"
|
|
9
|
+
require "benchmark"
|
|
10
|
+
require "fileutils"
|
|
11
11
|
|
|
12
12
|
# Force loading of native extension
|
|
13
|
-
require
|
|
14
|
-
require
|
|
13
|
+
require "parsanol"
|
|
14
|
+
require "parsanol/native"
|
|
15
15
|
|
|
16
16
|
# Now require expressir
|
|
17
|
-
require
|
|
17
|
+
require "expressir"
|
|
18
18
|
|
|
19
19
|
# Configuration
|
|
20
|
-
SRL_PATH =
|
|
21
|
-
ITERATIONS = (ENV[
|
|
22
|
-
TIMEOUT_SECONDS = (ENV[
|
|
20
|
+
SRL_PATH = "/Users/mulgogi/src/mn/iso-10303/schemas/resources"
|
|
21
|
+
ITERATIONS = (ENV["ITERATIONS"] || 1).to_i
|
|
22
|
+
TIMEOUT_SECONDS = (ENV["TIMEOUT"] || 30).to_i # Timeout per file
|
|
23
23
|
|
|
24
24
|
# Check if we're running in an interactive terminal
|
|
25
25
|
INTERACTIVE = $stdout.tty?
|
|
@@ -64,7 +64,9 @@ include Colors
|
|
|
64
64
|
module Terminal
|
|
65
65
|
class << self
|
|
66
66
|
def width
|
|
67
|
-
IO.console.winsize[1]
|
|
67
|
+
IO.console.winsize[1]
|
|
68
|
+
rescue StandardError
|
|
69
|
+
80
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def clear_line
|
|
@@ -85,7 +87,7 @@ class ProgressBar
|
|
|
85
87
|
@current = 0
|
|
86
88
|
end
|
|
87
89
|
|
|
88
|
-
def render(current, label =
|
|
90
|
+
def render(current, label = "")
|
|
89
91
|
@current = current
|
|
90
92
|
percentage = (@current.to_f / @total * 100).round(1)
|
|
91
93
|
filled = (@width * @current / @total.to_f).round
|
|
@@ -97,7 +99,9 @@ class ProgressBar
|
|
|
97
99
|
end
|
|
98
100
|
|
|
99
101
|
def find_exp_files
|
|
100
|
-
Dir.glob("#{SRL_PATH}/*/*.exp").
|
|
102
|
+
Dir.glob("#{SRL_PATH}/*/*.exp").reject do |f|
|
|
103
|
+
f.include?("quantities_and_units")
|
|
104
|
+
end
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
def count_lines(files)
|
|
@@ -158,50 +162,56 @@ class ParserBenchmark
|
|
|
158
162
|
@results = []
|
|
159
163
|
end
|
|
160
164
|
|
|
161
|
-
def run(files,
|
|
165
|
+
def run(files, _total_lines)
|
|
162
166
|
puts "#{@color}#{@emoji} #{@name}#{@RESET}"
|
|
163
167
|
puts "#{DIM}┌──────────────────────────────────────────────────────────┐#{RESET}"
|
|
164
168
|
|
|
165
169
|
progress_bar = ProgressBar.new(files.size, 25)
|
|
166
|
-
iteration_results = { success: 0, failed: 0, errors: [], time: 0,
|
|
170
|
+
iteration_results = { success: 0, failed: 0, errors: [], time: 0,
|
|
171
|
+
schema_times: [] }
|
|
167
172
|
start_time = Time.now
|
|
168
173
|
|
|
169
174
|
files.each_with_index do |file, idx|
|
|
170
|
-
schema_name = File.basename(file,
|
|
175
|
+
schema_name = File.basename(file, ".exp")
|
|
171
176
|
file_start = Time.now
|
|
172
177
|
schema_lines = File.read(file).lines.count
|
|
173
178
|
|
|
174
179
|
begin
|
|
175
|
-
require
|
|
180
|
+
require "timeout"
|
|
176
181
|
Timeout.timeout(TIMEOUT_SECONDS) do
|
|
177
182
|
if @use_native
|
|
178
183
|
content = File.read(file)
|
|
179
|
-
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
184
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
185
|
+
use_native: true)
|
|
180
186
|
else
|
|
181
187
|
Expressir::Express::Parser.from_file(file, skip_references: true)
|
|
182
188
|
end
|
|
183
189
|
end
|
|
184
190
|
iteration_results[:success] += 1
|
|
185
191
|
status = "#{BRIGHT_GREEN}✓#{RESET}"
|
|
186
|
-
rescue Timeout::Error
|
|
192
|
+
rescue Timeout::Error
|
|
187
193
|
iteration_results[:failed] += 1
|
|
188
|
-
iteration_results[:errors] << { file: File.basename(file),
|
|
194
|
+
iteration_results[:errors] << { file: File.basename(file),
|
|
195
|
+
error: "Timeout after #{TIMEOUT_SECONDS}s" }
|
|
189
196
|
status = "#{BRIGHT_YELLOW}⏱#{RESET}"
|
|
190
|
-
rescue => e
|
|
197
|
+
rescue StandardError => e
|
|
191
198
|
iteration_results[:failed] += 1
|
|
192
|
-
iteration_results[:errors] << { file: File.basename(file),
|
|
199
|
+
iteration_results[:errors] << { file: File.basename(file),
|
|
200
|
+
error: e.message[0..60] }
|
|
193
201
|
status = "#{BRIGHT_RED}✗#{RESET}"
|
|
194
202
|
end
|
|
195
203
|
|
|
196
204
|
elapsed = Time.now - file_start
|
|
197
|
-
iteration_results[:schema_times] << { name: schema_name, time: elapsed,
|
|
205
|
+
iteration_results[:schema_times] << { name: schema_name, time: elapsed,
|
|
206
|
+
lines: schema_lines }
|
|
198
207
|
|
|
199
208
|
# Live progress update
|
|
200
209
|
if INTERACTIVE
|
|
201
210
|
progress_label = "#{status} #{schema_name[0..20].ljust(21)}"
|
|
202
|
-
print "#{Terminal.clear_line} #{progress_bar.render(idx + 1,
|
|
211
|
+
print "#{Terminal.clear_line} #{progress_bar.render(idx + 1,
|
|
212
|
+
progress_label)}"
|
|
203
213
|
$stdout.flush
|
|
204
|
-
elsif (idx + 1) % 10
|
|
214
|
+
elsif ((idx + 1) % 10).zero? || idx.zero?
|
|
205
215
|
# Print progress every 10 files when not interactive
|
|
206
216
|
pct = ((idx + 1).to_f / files.size * 100).round(1)
|
|
207
217
|
puts " #{progress_bar.render(idx + 1, "#{pct}% complete")}"
|
|
@@ -230,7 +240,7 @@ class ParserBenchmark
|
|
|
230
240
|
puts "#{DIM}│#{RESET} #{BRIGHT_CYAN}⏱️ Time:#{RESET} #{BRIGHT_WHITE}#{format_time(avg_time).rjust(5)}#{RESET}"
|
|
231
241
|
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speed:#{RESET} #{BRIGHT_WHITE}#{format_number(lines_per_sec).rjust(5)}#{RESET} lines/sec"
|
|
232
242
|
|
|
233
|
-
if result[:failed]
|
|
243
|
+
if result[:failed].positive? && result[:failed] <= 5
|
|
234
244
|
puts "#{DIM}├──────────────────────────────────────────────────────────┤#{RESET}"
|
|
235
245
|
puts "#{DIM}│#{RESET} #{BRIGHT_RED}⚠️ Errors:#{RESET}"
|
|
236
246
|
result[:errors].first(3).each do |err|
|
|
@@ -241,7 +251,8 @@ class ParserBenchmark
|
|
|
241
251
|
puts "#{DIM}└──────────────────────────────────────────────────────────┘#{RESET}"
|
|
242
252
|
puts
|
|
243
253
|
|
|
244
|
-
{ avg_time: avg_time, lines_per_sec: lines_per_sec,
|
|
254
|
+
{ avg_time: avg_time, lines_per_sec: lines_per_sec,
|
|
255
|
+
files_per_sec: files_per_sec }
|
|
245
256
|
end
|
|
246
257
|
|
|
247
258
|
def print_slowest_schemas(result, count = 5)
|
|
@@ -290,7 +301,7 @@ def print_comparison(ruby_stats, native_stats, files, total_lines)
|
|
|
290
301
|
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⏱️ Time Saved:#{RESET} #{BRIGHT_GREEN}#{format_time(time_saved)} per run#{RESET} #{DIM}│#{RESET}"
|
|
291
302
|
else
|
|
292
303
|
puts "#{DIM}│#{RESET} #{BOLD}#{BRIGHT_BLUE}🏆 WINNER: Ruby Parser#{RESET} #{DIM}│#{RESET}"
|
|
293
|
-
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speedup:#{RESET} #{BOLD}#{BRIGHT_BLUE}#{(1/speedup).round(1)}x FASTER#{RESET} #{DIM}│#{RESET}"
|
|
304
|
+
puts "#{DIM}│#{RESET} #{BRIGHT_YELLOW}⚡ Speedup:#{RESET} #{BOLD}#{BRIGHT_BLUE}#{(1 / speedup).round(1)}x FASTER#{RESET} #{DIM}│#{RESET}"
|
|
294
305
|
end
|
|
295
306
|
|
|
296
307
|
puts "#{DIM}│#{RESET} #{DIM}│#{RESET}"
|
|
@@ -336,15 +347,16 @@ warmup_file = files.first
|
|
|
336
347
|
|
|
337
348
|
begin
|
|
338
349
|
Expressir::Express::Parser.from_file(warmup_file, skip_references: true)
|
|
339
|
-
rescue => e
|
|
350
|
+
rescue StandardError => e
|
|
340
351
|
puts "#{BRIGHT_YELLOW}⚠️ Ruby warmup warning: #{e.message[0..40]}#{RESET}"
|
|
341
352
|
end
|
|
342
353
|
|
|
343
354
|
if Parsanol::Native.available?
|
|
344
355
|
begin
|
|
345
356
|
content = File.read(warmup_file)
|
|
346
|
-
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
347
|
-
|
|
357
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
358
|
+
use_native: true)
|
|
359
|
+
rescue StandardError => e
|
|
348
360
|
puts "#{BRIGHT_YELLOW}⚠️ Native warmup warning: #{e.message[0..40]}#{RESET}"
|
|
349
361
|
end
|
|
350
362
|
end
|
|
@@ -356,7 +368,7 @@ ruby_benchmark = ParserBenchmark.new(
|
|
|
356
368
|
name: "Ruby Parser",
|
|
357
369
|
emoji: "💎",
|
|
358
370
|
color: BRIGHT_BLUE,
|
|
359
|
-
use_native: false
|
|
371
|
+
use_native: false,
|
|
360
372
|
)
|
|
361
373
|
|
|
362
374
|
ruby_result = ruby_benchmark.run(files, total_lines)
|
|
@@ -369,17 +381,18 @@ if Parsanol::Native.available?
|
|
|
369
381
|
name: "Native Parser (Rust)",
|
|
370
382
|
emoji: "🦀",
|
|
371
383
|
color: BRIGHT_CYAN,
|
|
372
|
-
use_native: true
|
|
384
|
+
use_native: true,
|
|
373
385
|
)
|
|
374
386
|
|
|
375
387
|
native_result = native_benchmark.run(files, total_lines)
|
|
376
|
-
native_stats = native_benchmark.print_summary(files, total_lines,
|
|
388
|
+
native_stats = native_benchmark.print_summary(files, total_lines,
|
|
389
|
+
native_result)
|
|
377
390
|
native_benchmark.print_slowest_schemas(native_result)
|
|
378
391
|
|
|
379
392
|
# Print comparison
|
|
380
393
|
print_comparison(ruby_stats, native_stats, files, total_lines)
|
|
381
394
|
else
|
|
382
|
-
puts "#{BRIGHT_YELLOW}⚠️ Native parser not available. Run
|
|
395
|
+
puts "#{BRIGHT_YELLOW}⚠️ Native parser not available. Run `rake compile` in parsanol-ruby to build.#{RESET}"
|
|
383
396
|
puts
|
|
384
397
|
end
|
|
385
398
|
|
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
# SRL Benchmark Script for Expressir - Native Parser Only
|
|
5
5
|
# Tests Rust native parser performance on full STEPmod Resource Library
|
|
6
6
|
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
7
|
+
require "bundler/setup"
|
|
8
|
+
require "benchmark"
|
|
9
|
+
require "fileutils"
|
|
10
10
|
|
|
11
11
|
# Force loading of native extension first
|
|
12
|
-
require
|
|
13
|
-
require
|
|
12
|
+
require "parsanol"
|
|
13
|
+
require "parsanol/native"
|
|
14
14
|
|
|
15
15
|
# Now require expressir
|
|
16
|
-
require
|
|
16
|
+
require "expressir"
|
|
17
17
|
|
|
18
18
|
# Configuration
|
|
19
|
-
SRL_PATH =
|
|
19
|
+
SRL_PATH = "/Users/mulgogi/src/mn/iso-10303/schemas/resources"
|
|
20
20
|
|
|
21
21
|
def find_exp_files
|
|
22
|
-
Dir.glob("#{SRL_PATH}/*/*.exp")
|
|
22
|
+
Dir.glob("#{SRL_PATH}/*/*.exp")
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def count_lines(files)
|
|
@@ -59,9 +59,10 @@ puts "Warming up..."
|
|
|
59
59
|
warmup_file = files.first
|
|
60
60
|
begin
|
|
61
61
|
content = File.read(warmup_file)
|
|
62
|
-
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
62
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
63
|
+
use_native: true)
|
|
63
64
|
puts "Warmup complete."
|
|
64
|
-
rescue => e
|
|
65
|
+
rescue StandardError => e
|
|
65
66
|
puts "Warning: Warmup failed: #{e.message}"
|
|
66
67
|
puts e.backtrace.first(5)
|
|
67
68
|
end
|
|
@@ -80,19 +81,22 @@ time = Benchmark.realtime do
|
|
|
80
81
|
file_start = Time.now
|
|
81
82
|
begin
|
|
82
83
|
content = File.read(file)
|
|
83
|
-
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
84
|
+
Expressir::Express::Parser.from_exp(content, skip_references: true,
|
|
85
|
+
use_native: true)
|
|
84
86
|
results[:success] += 1
|
|
85
87
|
file_time = Time.now - file_start
|
|
86
|
-
file_times << { file: File.basename(file), time: file_time,
|
|
88
|
+
file_times << { file: File.basename(file), time: file_time,
|
|
89
|
+
lines: content.lines.count }
|
|
87
90
|
|
|
88
91
|
# Progress every 10 files
|
|
89
|
-
if (idx + 1) % 10
|
|
92
|
+
if ((idx + 1) % 10).zero?
|
|
90
93
|
print "."
|
|
91
94
|
$stdout.flush
|
|
92
95
|
end
|
|
93
|
-
rescue => e
|
|
96
|
+
rescue StandardError => e
|
|
94
97
|
results[:failed] += 1
|
|
95
|
-
results[:errors] << { file: File.basename(file),
|
|
98
|
+
results[:errors] << { file: File.basename(file),
|
|
99
|
+
error: e.message[0..100] }
|
|
96
100
|
end
|
|
97
101
|
end
|
|
98
102
|
end
|
|
@@ -112,7 +116,7 @@ puts " Time: #{results[:total_time].round(2)}s"
|
|
|
112
116
|
puts " Speed: #{lines_per_sec} lines/sec"
|
|
113
117
|
puts " Speed: #{files_per_sec} files/sec"
|
|
114
118
|
|
|
115
|
-
if results[:failed]
|
|
119
|
+
if results[:failed].positive?
|
|
116
120
|
puts
|
|
117
121
|
puts "Errors (first 10):"
|
|
118
122
|
results[:errors].first(10).each do |err|
|
|
@@ -4,18 +4,18 @@
|
|
|
4
4
|
# SRL Benchmark Script for Expressir - Ruby Parser Only
|
|
5
5
|
# Tests pure Ruby parser performance on full STEPmod Resource Library
|
|
6
6
|
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
7
|
+
require "bundler/setup"
|
|
8
|
+
require "benchmark"
|
|
9
|
+
require "fileutils"
|
|
10
10
|
|
|
11
11
|
# Load expressir WITHOUT native extension
|
|
12
|
-
require
|
|
12
|
+
require "expressir"
|
|
13
13
|
|
|
14
14
|
# Configuration
|
|
15
|
-
SRL_PATH =
|
|
15
|
+
SRL_PATH = "/Users/mulgogi/src/mn/iso-10303/schemas/resources"
|
|
16
16
|
|
|
17
17
|
def find_exp_files
|
|
18
|
-
Dir.glob("#{SRL_PATH}/*/*.exp")
|
|
18
|
+
Dir.glob("#{SRL_PATH}/*/*.exp")
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def count_lines(files)
|
|
@@ -46,7 +46,7 @@ warmup_file = files.first
|
|
|
46
46
|
begin
|
|
47
47
|
Expressir::Express::Parser.from_file(warmup_file, skip_references: true)
|
|
48
48
|
puts "Warmup complete."
|
|
49
|
-
rescue => e
|
|
49
|
+
rescue StandardError => e
|
|
50
50
|
puts "Warning: Warmup failed: #{e.message}"
|
|
51
51
|
end
|
|
52
52
|
puts
|
|
@@ -67,16 +67,18 @@ time = Benchmark.realtime do
|
|
|
67
67
|
results[:success] += 1
|
|
68
68
|
file_time = Time.now - file_start
|
|
69
69
|
content = File.read(file)
|
|
70
|
-
file_times << { file: File.basename(file), time: file_time,
|
|
70
|
+
file_times << { file: File.basename(file), time: file_time,
|
|
71
|
+
lines: content.lines.count }
|
|
71
72
|
|
|
72
73
|
# Progress every 10 files
|
|
73
|
-
if (idx + 1) % 10
|
|
74
|
+
if ((idx + 1) % 10).zero?
|
|
74
75
|
print "."
|
|
75
76
|
$stdout.flush
|
|
76
77
|
end
|
|
77
|
-
rescue => e
|
|
78
|
+
rescue StandardError => e
|
|
78
79
|
results[:failed] += 1
|
|
79
|
-
results[:errors] << { file: File.basename(file),
|
|
80
|
+
results[:errors] << { file: File.basename(file),
|
|
81
|
+
error: e.message[0..100] }
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
end
|
|
@@ -96,7 +98,7 @@ puts " Time: #{results[:total_time].round(2)}s"
|
|
|
96
98
|
puts " Speed: #{lines_per_sec} lines/sec"
|
|
97
99
|
puts " Speed: #{files_per_sec} files/sec"
|
|
98
100
|
|
|
99
|
-
if results[:failed]
|
|
101
|
+
if results[:failed].positive?
|
|
100
102
|
puts
|
|
101
103
|
puts "Errors (first 10):"
|
|
102
104
|
results[:errors].first(10).each do |err|
|
data/expressir.gemspec
CHANGED
|
@@ -38,9 +38,9 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
spec.add_dependency "liquid"
|
|
39
39
|
spec.add_dependency "lutaml-model", "~> 0.8.0"
|
|
40
40
|
spec.add_dependency "moxml"
|
|
41
|
-
spec.add_dependency "paint"
|
|
42
|
-
spec.add_dependency "parsanol", "~> 1.3.7", ">= 1.3.7"
|
|
43
41
|
spec.add_dependency "nokogiri"
|
|
42
|
+
spec.add_dependency "paint"
|
|
43
|
+
spec.add_dependency "parsanol", "~> 1.3.9", ">= 1.3.9"
|
|
44
44
|
spec.add_dependency "ruby-progressbar", "~> 1.11"
|
|
45
45
|
spec.add_dependency "rubyzip", "~> 2.3"
|
|
46
46
|
spec.add_dependency "table_tennis"
|
|
@@ -11,7 +11,7 @@ module Expressir
|
|
|
11
11
|
# @param version [String] Version identifier
|
|
12
12
|
# @param options [Hash] Additional options
|
|
13
13
|
# @return [Expressir::Changes::SchemaChange]
|
|
14
|
-
def self.from_xml(xml_content, schema_name, version, **
|
|
14
|
+
def self.from_xml(xml_content, schema_name, version, **)
|
|
15
15
|
require "expressir/changes"
|
|
16
16
|
|
|
17
17
|
# Parse into CompareReport using Lutaml::Model
|
|
@@ -19,7 +19,7 @@ module Expressir
|
|
|
19
19
|
|
|
20
20
|
# Convert to SchemaChange
|
|
21
21
|
convert_to_schema_change(compare_report, schema_name, version,
|
|
22
|
-
xml_content: xml_content, **
|
|
22
|
+
xml_content: xml_content, **)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# File-based workflow (backward compatible)
|
|
@@ -26,6 +26,21 @@ module Expressir
|
|
|
26
26
|
# @param source [String, nil] The original source code
|
|
27
27
|
# @param include_source [Boolean, nil] Whether to include source
|
|
28
28
|
# @return [Model::ModelElement] The built model object
|
|
29
|
+
# Operator tokens that return nil (separators, punctuation)
|
|
30
|
+
# When these appear as the first key in a multi-key hash, they should be
|
|
31
|
+
# skipped in favor of the content key. This handles grammar patterns like
|
|
32
|
+
# `element >> (op_comma >> element).repeat` which produce
|
|
33
|
+
# {:op_comma => ..., :element => {...}}.
|
|
34
|
+
OPERATOR_TOKENS = Set.new(%i[
|
|
35
|
+
op_comma op_colon op_decl op_delim op_leftparen op_rightparen
|
|
36
|
+
op_leftbracket op_rightbracket op_left_curly_brace op_right_curly_brace
|
|
37
|
+
op_period op_pipe op_double_backslash op_double_pipe op_double_asterisk
|
|
38
|
+
op_asterisk op_slash op_plus op_minus op_less_equal op_greater_equal
|
|
39
|
+
op_less_greater op_less_than op_greater_than op_equals
|
|
40
|
+
op_colon_less_greater_colon op_colon_equals_colon
|
|
41
|
+
op_query_begin op_query_end op_question_mark
|
|
42
|
+
]).freeze
|
|
43
|
+
|
|
29
44
|
def build(ast, source: nil, include_source: nil)
|
|
30
45
|
return nil unless ast
|
|
31
46
|
|
|
@@ -44,25 +59,41 @@ module Expressir
|
|
|
44
59
|
snake_data = fast_convert_keys(node_data)
|
|
45
60
|
|
|
46
61
|
builder = @register[handler_key]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
if builder
|
|
63
|
+
result = builder.call(snake_data)
|
|
64
|
+
|
|
65
|
+
# Fast path: single-key hash or non-nil result
|
|
66
|
+
if !result.nil? || ast.keys.length <= 1
|
|
67
|
+
attach_source_info(result, node_data)
|
|
68
|
+
return result
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Slow path: operator token returned nil in multi-key hash.
|
|
72
|
+
# Try other keys for actual content. This handles
|
|
73
|
+
# {:op_comma => ..., :element => {...}} where the first key
|
|
74
|
+
# is an operator separator rather than a content key.
|
|
75
|
+
if OPERATOR_TOKENS.include?(handler_key)
|
|
76
|
+
ast.each_key do |key|
|
|
77
|
+
next if key == node_type
|
|
78
|
+
|
|
79
|
+
h_key = cached_snake_case(key)
|
|
80
|
+
h_builder = @register[h_key]
|
|
81
|
+
next unless h_builder
|
|
82
|
+
|
|
83
|
+
n_data = ast[key]
|
|
84
|
+
s_data = fast_convert_keys(n_data)
|
|
85
|
+
result = h_builder.call(s_data)
|
|
86
|
+
|
|
87
|
+
unless result.nil?
|
|
88
|
+
attach_source_info(result, n_data)
|
|
89
|
+
return result
|
|
90
|
+
end
|
|
61
91
|
end
|
|
62
92
|
end
|
|
93
|
+
else
|
|
94
|
+
raise Error::UnknownNodeTypeError, node_type
|
|
63
95
|
end
|
|
64
|
-
|
|
65
|
-
result
|
|
96
|
+
nil
|
|
66
97
|
when Array
|
|
67
98
|
ast.map do |item|
|
|
68
99
|
build(item)
|
|
@@ -83,7 +114,9 @@ module Expressir
|
|
|
83
114
|
|
|
84
115
|
result = build(ast)
|
|
85
116
|
|
|
86
|
-
if
|
|
117
|
+
# Only attach remarks if include_source is explicitly true
|
|
118
|
+
# (nil means use default behavior - attach remarks)
|
|
119
|
+
if source && result && include_source != false
|
|
87
120
|
attacher = RemarkAttacher.new(source)
|
|
88
121
|
attacher.attach(result)
|
|
89
122
|
end
|
|
@@ -113,6 +146,7 @@ module Expressir
|
|
|
113
146
|
def ensure_array(value)
|
|
114
147
|
return [] if value.nil?
|
|
115
148
|
return [] if value.is_a?(Parsanol::Slice)
|
|
149
|
+
|
|
116
150
|
value.is_a?(Array) ? value : [value]
|
|
117
151
|
end
|
|
118
152
|
|
|
@@ -121,9 +155,8 @@ module Expressir
|
|
|
121
155
|
def build_children(ast_array)
|
|
122
156
|
return [] if ast_array.nil?
|
|
123
157
|
|
|
124
|
-
# Handle Parsanol::Slice (
|
|
125
|
-
# Convert to empty Array
|
|
126
|
-
# Native parser returns empty slices where Ruby parser returns empty arrays
|
|
158
|
+
# Handle Parsanol::Slice (empty Slices from optional rules)
|
|
159
|
+
# Convert to empty Array
|
|
127
160
|
if ast_array.is_a?(Parsanol::Slice)
|
|
128
161
|
return []
|
|
129
162
|
end
|
|
@@ -138,7 +171,7 @@ module Expressir
|
|
|
138
171
|
ast_array.each do |item|
|
|
139
172
|
next if item.nil?
|
|
140
173
|
|
|
141
|
-
# Empty Slices from
|
|
174
|
+
# Empty Slices from optional rules should be treated as empty arrays
|
|
142
175
|
if item.is_a?(Parsanol::Slice)
|
|
143
176
|
next
|
|
144
177
|
end
|
|
@@ -328,6 +361,16 @@ module Expressir
|
|
|
328
361
|
}
|
|
329
362
|
end
|
|
330
363
|
|
|
364
|
+
def attach_source_info(result, data)
|
|
365
|
+
return unless @source && result.is_a?(Model::ModelElement)
|
|
366
|
+
|
|
367
|
+
source_info = extract_source_info(data)
|
|
368
|
+
return unless source_info
|
|
369
|
+
|
|
370
|
+
result.source_offset = source_info[:offset]
|
|
371
|
+
result.source = source_info[:text] if @include_source
|
|
372
|
+
end
|
|
373
|
+
|
|
331
374
|
def find_slice(data, depth = 0)
|
|
332
375
|
return nil if depth > 10
|
|
333
376
|
|
|
@@ -13,12 +13,14 @@ module Expressir
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def build_built_in_function(ast_data)
|
|
16
|
-
id = extract_text(ast_data[:str])
|
|
16
|
+
id = extract_text(ast_data[:str]) ||
|
|
17
|
+
extract_nested_text(ast_data)
|
|
17
18
|
Expressir::Model::References::SimpleReference.new(id: id)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def build_built_in_procedure(ast_data)
|
|
21
|
-
id = extract_text(ast_data[:str])
|
|
22
|
+
id = extract_text(ast_data[:str]) ||
|
|
23
|
+
extract_nested_text(ast_data)
|
|
22
24
|
Expressir::Model::References::SimpleReference.new(id: id)
|
|
23
25
|
end
|
|
24
26
|
|
|
@@ -36,7 +36,9 @@ module Expressir
|
|
|
36
36
|
if subtype_constraint[:supertype_expression]
|
|
37
37
|
supertype_expression = Builder.build({ supertype_expression: subtype_constraint[:supertype_expression] })
|
|
38
38
|
elsif subtype_constraint[:list_of_entity_ref]
|
|
39
|
-
entity_refs = Builder.build_children(Builder.ensure_array(subtype_constraint[:list_of_entity_ref][:entity_ref]).map
|
|
39
|
+
entity_refs = Builder.build_children(Builder.ensure_array(subtype_constraint[:list_of_entity_ref][:entity_ref]).map do |d|
|
|
40
|
+
{ entity_ref: d }
|
|
41
|
+
end)
|
|
40
42
|
supertype_expression = entity_refs.first if entity_refs.length == 1
|
|
41
43
|
end
|
|
42
44
|
end
|
|
@@ -47,7 +49,9 @@ module Expressir
|
|
|
47
49
|
if list_ref.is_a?(Hash)
|
|
48
50
|
entity_ref_data = list_ref[:entity_ref]
|
|
49
51
|
if entity_ref_data
|
|
50
|
-
subtype_of = Builder.build_children(Builder.ensure_array(entity_ref_data).map
|
|
52
|
+
subtype_of = Builder.build_children(Builder.ensure_array(entity_ref_data).map do |d|
|
|
53
|
+
{ entity_ref: d }
|
|
54
|
+
end)
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
end
|
|
@@ -65,12 +69,12 @@ module Expressir
|
|
|
65
69
|
|
|
66
70
|
if entity_body[:derive_clause]
|
|
67
71
|
derived = Builder.build({ derive_clause: entity_body[:derive_clause] })
|
|
68
|
-
attributes.
|
|
72
|
+
attributes.push(derived) if derived
|
|
69
73
|
end
|
|
70
74
|
|
|
71
75
|
if entity_body[:inverse_clause]
|
|
72
76
|
inverse = Builder.build({ inverse_clause: entity_body[:inverse_clause] })
|
|
73
|
-
attributes.
|
|
77
|
+
attributes.push(inverse) if inverse
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
if entity_body[:unique_clause]
|