expressir 2.4.1 → 2.4.2
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/TODO.max-perf/01-restore-ci-green.md +29 -0
- data/TODO.max-perf/02-streaming-parse-path.md +31 -0
- data/TODO.max-perf/03-cli-parallel-opt-in.md +27 -0
- data/TODO.max-perf/04-benchmark-harness.md +28 -0
- data/TODO.max-perf/05-parallel-fidelity-specs.md +22 -0
- data/TODO.max-perf/06-builder-cpu-audit.md +41 -0
- data/TODO.max-perf/07-upstream-parsanol-roadmap.md +27 -0
- data/TODO.max-perf/08-builder-build-perf.md +45 -0
- data/TODO.max-perf/09-grammar-cold-start.md +25 -0
- data/TODO.max-perf/10-parser-facade-hygiene.md +23 -0
- data/TODO.max-perf/11-ci-green-closeout.md +25 -0
- data/TODO.max-perf/12-require-boot-profile.md +25 -0
- data/TODO.max-perf/13-key-conversion-specs.md +26 -0
- data/TODO.max-perf/14-builder-call-handler-audit.md +28 -0
- data/benchmark/srl_benchmark.rb +76 -17
- data/expressir.gemspec +1 -1
- data/lib/expressir/cli.rb +3 -0
- data/lib/expressir/commands/coverage.rb +6 -2
- data/lib/expressir/commands/package.rb +4 -1
- data/lib/expressir/express/ast_key_converter.rb +114 -0
- data/lib/expressir/express/builder.rb +8 -119
- data/lib/expressir/express/error.rb +17 -0
- data/lib/expressir/express/parallel_files.rb +229 -0
- data/lib/expressir/express/parser.rb +44 -86
- data/lib/expressir/express/remark_attacher.rb +25 -7
- data/lib/expressir/express/schema_block_scanner.rb +3 -2
- data/lib/expressir/express/scope_resolver.rb +34 -5
- data/lib/expressir/express.rb +2 -0
- data/lib/expressir/model/model_element.rb +6 -1
- data/lib/expressir/model/repository.rb +18 -5
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +18 -0
- metadata +22 -6
|
@@ -35,6 +35,10 @@ module Expressir
|
|
|
35
35
|
# via `child_attributes :foo, :bar, ...`. See TODO.bugs/15.
|
|
36
36
|
EXPRESSION_CHILDREN = Model::ModelElement.child_attributes_registry
|
|
37
37
|
|
|
38
|
+
# `WHERE <label> :` clause headers; the captured label maps to the
|
|
39
|
+
# 1-based line number(s) it appears on.
|
|
40
|
+
WHERE_CLAUSE_PATTERN = /\A\s*WHERE\s+(\w+)\s*:/i
|
|
41
|
+
|
|
38
42
|
def initialize(source)
|
|
39
43
|
@source = source
|
|
40
44
|
@attached_spans = Set.new
|
|
@@ -71,6 +75,8 @@ module Expressir
|
|
|
71
75
|
@line_map = nil
|
|
72
76
|
@owner_map = nil
|
|
73
77
|
@active_scope_map = nil
|
|
78
|
+
@source_lines_for_where_clause = nil
|
|
79
|
+
@where_clause_line_index = nil
|
|
74
80
|
end
|
|
75
81
|
|
|
76
82
|
private
|
|
@@ -658,27 +664,39 @@ module Expressir
|
|
|
658
664
|
where_rules = get_collection(scope, :where_rules)
|
|
659
665
|
return nil unless where_rules&.any?
|
|
660
666
|
|
|
661
|
-
|
|
667
|
+
where_clause_lines = where_clause_line_index
|
|
662
668
|
|
|
663
669
|
where_rules.each do |wr|
|
|
664
670
|
next unless wr.id
|
|
665
671
|
|
|
666
|
-
|
|
667
|
-
line_num = idx + 1
|
|
672
|
+
where_clause_lines.fetch(wr.id, []).each do |line_num|
|
|
668
673
|
next unless line_num < remark_line
|
|
669
674
|
|
|
670
|
-
|
|
671
|
-
return create_remark_item(wr, tag)
|
|
672
|
-
end
|
|
675
|
+
return create_remark_item(wr, tag) if remark_line.between?(line_num, line_num + 5)
|
|
673
676
|
end
|
|
674
677
|
end
|
|
675
678
|
|
|
676
679
|
nil
|
|
677
680
|
end
|
|
678
681
|
|
|
682
|
+
# Single scan over the source lines mapping each `WHERE <id>:` label to
|
|
683
|
+
# its 1-based line number, so per-remark lookups stop re-testing every
|
|
684
|
+
# line against every WHERE rule's regex.
|
|
685
|
+
def where_clause_line_index
|
|
686
|
+
@where_clause_line_index ||= begin
|
|
687
|
+
index = Hash.new { |h, k| h[k] = [] }
|
|
688
|
+
source_lines_for_where_clause.each_with_index do |line, idx|
|
|
689
|
+
if (match = line.match(WHERE_CLAUSE_PATTERN))
|
|
690
|
+
index[match[1]] << (idx + 1)
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
index
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
|
|
679
697
|
def source_lines_for_where_clause
|
|
680
698
|
# @source is set for the duration of `attach`; freed at the end.
|
|
681
|
-
@source.lines
|
|
699
|
+
@source_lines_for_where_clause ||= @source.lines
|
|
682
700
|
end
|
|
683
701
|
|
|
684
702
|
def find_node_in_statement(stmt, tag)
|
|
@@ -6,8 +6,9 @@ module Expressir
|
|
|
6
6
|
#
|
|
7
7
|
# A hand-rolled state machine that skips comments (`(* ... *)`) and
|
|
8
8
|
# string literals, tracks SCHEMA/END_SCHEMA depth, and returns one
|
|
9
|
-
# block per schema declaration.
|
|
10
|
-
#
|
|
9
|
+
# block per schema declaration. Retained for the future streaming
|
|
10
|
+
# parse path (see TODO.max-perf/02) that parses each schema
|
|
11
|
+
# independently with a fresh memory arena.
|
|
11
12
|
#
|
|
12
13
|
# Extracted from the outer Parser class (TODO.bugs/09) so block-
|
|
13
14
|
# scanning logic lives in one focused module behind one interface.
|
|
@@ -50,8 +50,13 @@ module Expressir
|
|
|
50
50
|
@model = model
|
|
51
51
|
@nodes_with_positions = nodes_with_positions
|
|
52
52
|
@scope_map = nil
|
|
53
|
+
@source_lines = nil
|
|
54
|
+
@position_buckets = nil
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# Line-band width for the position fallback index.
|
|
58
|
+
BUCKET_LINES = 1024
|
|
59
|
+
|
|
55
60
|
# Returns the innermost Model::ScopeContainer whose source span contains
|
|
56
61
|
# the given 1-based remark line, or nil if none is found.
|
|
57
62
|
def containing_scope_for(remark_line)
|
|
@@ -69,7 +74,7 @@ module Expressir
|
|
|
69
74
|
type_state = { line: nil, name: nil }
|
|
70
75
|
rule_state = { line: nil, name: nil }
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
source_lines.each_with_index do |line, idx|
|
|
73
78
|
line_num = idx + 1
|
|
74
79
|
|
|
75
80
|
case line
|
|
@@ -131,8 +136,15 @@ module Expressir
|
|
|
131
136
|
@scope_map ||= build_scope_map
|
|
132
137
|
end
|
|
133
138
|
|
|
139
|
+
# Source split into lines, computed once per resolver: both the
|
|
140
|
+
# scope-map build and find_by_source_text need it, and re-splitting the
|
|
141
|
+
# whole source per lookup dominates remark attachment on large files.
|
|
142
|
+
def source_lines
|
|
143
|
+
@source_lines ||= @source.lines
|
|
144
|
+
end
|
|
145
|
+
|
|
134
146
|
def build_scope_map
|
|
135
|
-
lines =
|
|
147
|
+
lines = source_lines
|
|
136
148
|
map = {}
|
|
137
149
|
return map if lines.empty?
|
|
138
150
|
|
|
@@ -160,10 +172,27 @@ module Expressir
|
|
|
160
172
|
|
|
161
173
|
# --- Strategy 2: position-based fallback against the node index ---
|
|
162
174
|
|
|
175
|
+
# Nodes bucketed by 1024-line bands: a node spanning [line, end_line]
|
|
176
|
+
# is registered in every band it overlaps, so a remark-line lookup only
|
|
177
|
+
# scans nodes that can possibly contain it. Bucket order preserves the
|
|
178
|
+
# original index order, so select+reverse_each semantics are unchanged.
|
|
179
|
+
def position_buckets
|
|
180
|
+
@position_buckets ||= begin
|
|
181
|
+
buckets = Hash.new { |h, k| h[k] = [] }
|
|
182
|
+
@nodes_with_positions.each do |n|
|
|
183
|
+
next unless n[:line] && n[:end_line]
|
|
184
|
+
|
|
185
|
+
((n[:line] / BUCKET_LINES)..(n[:end_line] / BUCKET_LINES)).each do |b|
|
|
186
|
+
buckets[b] << n
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
buckets
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
163
193
|
def find_by_position(remark_line)
|
|
164
|
-
containing =
|
|
165
|
-
n[:line]
|
|
166
|
-
remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
194
|
+
containing = position_buckets[remark_line / BUCKET_LINES].select do |n|
|
|
195
|
+
remark_line.between?(n[:line], n[:end_line]) &&
|
|
167
196
|
!n[:node].is_a?(Model::Repository) && !n[:node].is_a?(Model::Cache)
|
|
168
197
|
end
|
|
169
198
|
|
data/lib/expressir/express.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module Expressir
|
|
4
4
|
module Express
|
|
5
|
+
autoload :AstKeyConverter, "#{__dir__}/express/ast_key_converter"
|
|
5
6
|
autoload :Builder, "#{__dir__}/express/builder"
|
|
6
7
|
autoload :BuilderContext, "#{__dir__}/express/builder_context"
|
|
7
8
|
autoload :BuilderRegistry, "#{__dir__}/express/builder_registry"
|
|
@@ -17,6 +18,7 @@ module Expressir
|
|
|
17
18
|
autoload :LineMap, "#{__dir__}/express/line_map"
|
|
18
19
|
autoload :ModelVisitor, "#{__dir__}/express/model_visitor"
|
|
19
20
|
autoload :NodePositionIndex, "#{__dir__}/express/node_position_index"
|
|
21
|
+
autoload :ParallelFiles, "#{__dir__}/express/parallel_files"
|
|
20
22
|
autoload :Parser, "#{__dir__}/express/parser"
|
|
21
23
|
autoload :PrettyFormatter, "#{__dir__}/express/pretty_formatter"
|
|
22
24
|
autoload :RemarkAttacher, "#{__dir__}/express/remark_attacher"
|
|
@@ -155,7 +155,12 @@ module Expressir
|
|
|
155
155
|
end
|
|
156
156
|
|
|
157
157
|
def source
|
|
158
|
-
|
|
158
|
+
# Formatting is not free and callers probe `source` repeatedly while
|
|
159
|
+
# walking the tree (position index, remark attachment), so the text is
|
|
160
|
+
# computed once per node. Deliberately NOT @source: that ivar holds
|
|
161
|
+
# the raw source span lutaml-model's `source=` stores when
|
|
162
|
+
# include_source is on, and readers must keep seeing formatted text.
|
|
163
|
+
@formatted_source ||= Expressir::Express::SourceFormatter.format(self) # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
159
164
|
end
|
|
160
165
|
|
|
161
166
|
# @param [Hash] options
|
|
@@ -291,14 +291,27 @@ reference_index: nil)
|
|
|
291
291
|
# Build repository from list of schema files
|
|
292
292
|
# @param file_paths [Array<String>] Schema file paths
|
|
293
293
|
# @param base_dir [String, nil] Base directory for path resolution
|
|
294
|
+
# @param max_processes [Integer, nil] parse in a fork worker pool when
|
|
295
|
+
# set above 1 on a fork-capable platform (advisory; degrades to
|
|
296
|
+
# sequential otherwise). Unlike Express::Parser.from_files, a file
|
|
297
|
+
# that fails to parse always raises.
|
|
294
298
|
# @return [Repository] Built repository with all schemas
|
|
295
|
-
def self.from_files(file_paths, base_dir: nil)
|
|
299
|
+
def self.from_files(file_paths, base_dir: nil, max_processes: nil)
|
|
296
300
|
repo = new(base_dir: base_dir)
|
|
297
301
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
pool = Expressir::Express::ParallelFiles
|
|
303
|
+
parsed_files = if pool.sequential?(file_paths, max_processes)
|
|
304
|
+
file_paths.map do |path|
|
|
305
|
+
Expressir::Express::Parser.from_file(path)
|
|
306
|
+
end
|
|
307
|
+
else
|
|
308
|
+
pool.run(file_paths,
|
|
309
|
+
parse: ->(path) { Expressir::Express::Parser.from_file(path) },
|
|
310
|
+
max_processes: max_processes,
|
|
311
|
+
strict: true)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
parsed_files.each do |parsed|
|
|
302
315
|
repo.files << parsed if parsed.is_a?(ExpFile)
|
|
303
316
|
end
|
|
304
317
|
|
data/lib/expressir/version.rb
CHANGED
data/lib/expressir.rb
CHANGED
|
@@ -21,6 +21,24 @@ else
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
module Expressir
|
|
24
|
+
# yeptris mingw builds crash once loaded (`uninitialized constant
|
|
25
|
+
# Yeptris::FFI::NODE_SCALAR`, leptris/yeptris#318) and the gem rebinds
|
|
26
|
+
# core YAML on require, so merely having it in the bundle breaks every
|
|
27
|
+
# Hash#to_yaml on Windows. Pinning the portable adapters stops
|
|
28
|
+
# lutaml-model's bundle-based autodetection from loading yeptris there;
|
|
29
|
+
# other platforms keep the native engine. Revisit when yeptris ships a
|
|
30
|
+
# working mingw build.
|
|
31
|
+
def self.select_serialization_engines(windows: Gem.win_platform?)
|
|
32
|
+
return unless windows
|
|
33
|
+
|
|
34
|
+
Lutaml::Model::Config.configure do |config|
|
|
35
|
+
config.yaml_adapter_type = :standard
|
|
36
|
+
config.json_adapter_type = :standard
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
select_serialization_engines
|
|
41
|
+
|
|
24
42
|
# Namespace modules - autoload loads the namespace file which contains
|
|
25
43
|
# autoload definitions for classes within that namespace
|
|
26
44
|
autoload :Config, "#{__dir__}/expressir/config"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: expressir
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.4.
|
|
4
|
+
version: 2.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -174,16 +174,16 @@ dependencies:
|
|
|
174
174
|
name: rubyzip
|
|
175
175
|
requirement: !ruby/object:Gem::Requirement
|
|
176
176
|
requirements:
|
|
177
|
-
- - "
|
|
177
|
+
- - ">="
|
|
178
178
|
- !ruby/object:Gem::Version
|
|
179
|
-
version: '
|
|
179
|
+
version: '0'
|
|
180
180
|
type: :runtime
|
|
181
181
|
prerelease: false
|
|
182
182
|
version_requirements: !ruby/object:Gem::Requirement
|
|
183
183
|
requirements:
|
|
184
|
-
- - "
|
|
184
|
+
- - ">="
|
|
185
185
|
- !ruby/object:Gem::Version
|
|
186
|
-
version: '
|
|
186
|
+
version: '0'
|
|
187
187
|
- !ruby/object:Gem::Dependency
|
|
188
188
|
name: table_tennis
|
|
189
189
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -282,6 +282,20 @@ files:
|
|
|
282
282
|
- TODO.bugs/29-validate-ascii-god-class.md
|
|
283
283
|
- TODO.bugs/30-unicode-map-extraction.md
|
|
284
284
|
- TODO.bugs/README.md
|
|
285
|
+
- TODO.max-perf/01-restore-ci-green.md
|
|
286
|
+
- TODO.max-perf/02-streaming-parse-path.md
|
|
287
|
+
- TODO.max-perf/03-cli-parallel-opt-in.md
|
|
288
|
+
- TODO.max-perf/04-benchmark-harness.md
|
|
289
|
+
- TODO.max-perf/05-parallel-fidelity-specs.md
|
|
290
|
+
- TODO.max-perf/06-builder-cpu-audit.md
|
|
291
|
+
- TODO.max-perf/07-upstream-parsanol-roadmap.md
|
|
292
|
+
- TODO.max-perf/08-builder-build-perf.md
|
|
293
|
+
- TODO.max-perf/09-grammar-cold-start.md
|
|
294
|
+
- TODO.max-perf/10-parser-facade-hygiene.md
|
|
295
|
+
- TODO.max-perf/11-ci-green-closeout.md
|
|
296
|
+
- TODO.max-perf/12-require-boot-profile.md
|
|
297
|
+
- TODO.max-perf/13-key-conversion-specs.md
|
|
298
|
+
- TODO.max-perf/14-builder-call-handler-audit.md
|
|
285
299
|
- benchmark/srl_benchmark.rb
|
|
286
300
|
- benchmark/srl_native_benchmark.rb
|
|
287
301
|
- benchmark/srl_ruby_benchmark.rb
|
|
@@ -397,6 +411,7 @@ files:
|
|
|
397
411
|
- lib/expressir/eengine/modified_object.rb
|
|
398
412
|
- lib/expressir/errors.rb
|
|
399
413
|
- lib/expressir/express.rb
|
|
414
|
+
- lib/expressir/express/ast_key_converter.rb
|
|
400
415
|
- lib/expressir/express/builder.rb
|
|
401
416
|
- lib/expressir/express/builder_context.rb
|
|
402
417
|
- lib/expressir/express/builder_registry.rb
|
|
@@ -453,6 +468,7 @@ files:
|
|
|
453
468
|
- lib/expressir/express/line_map.rb
|
|
454
469
|
- lib/expressir/express/model_visitor.rb
|
|
455
470
|
- lib/expressir/express/node_position_index.rb
|
|
471
|
+
- lib/expressir/express/parallel_files.rb
|
|
456
472
|
- lib/expressir/express/parser.rb
|
|
457
473
|
- lib/expressir/express/pretty_formatter.rb
|
|
458
474
|
- lib/expressir/express/remark_attacher.rb
|