expressir 2.3.1 → 2.3.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/lib/expressir/express/remark_attacher.rb +33 -152
- data/lib/expressir/model/declarations/schema.rb +0 -1
- data/lib/expressir/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0cd9487796289d666f64e0d96628efb1e54a7d7b5fe02f051edd7ff886cbd38
|
|
4
|
+
data.tar.gz: 1f4dc35360f53c93c987ca8bab26cbbcf677c421bd05e050451315735dd78039
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf984c0fa68ad04c83d05b3cc4268d2e67baa0a6789545378e7599533cfa8e8332673b303793d485b46a12212c1b2e84087ba4decd4c54dc9fe671c52d13faf4
|
|
7
|
+
data.tar.gz: a2f68e1091a39b336abe009e2cc5b2f576d0a72deb58a79f831c52741345422f4651d22edf133d5c5e1d8d4d83dd6f63fed555d13e62e6bd68a79a84666cf1b0
|
|
@@ -11,6 +11,17 @@ module Expressir
|
|
|
11
11
|
# 2. Proximity-based matching for simple tags
|
|
12
12
|
# 3. NOT creating spurious schema-level items for ambiguous tags
|
|
13
13
|
class RemarkAttacher
|
|
14
|
+
# Child collection attributes to walk when traversing model elements.
|
|
15
|
+
# These are the named collections on model elements (e.g., entity.attributes,
|
|
16
|
+
# schema.entities, function.parameters). Extracted to a constant to avoid
|
|
17
|
+
# duplication between calculate_children_end_line and collect_children.
|
|
18
|
+
CHILD_COLLECTION_ATTRIBUTES = %i[
|
|
19
|
+
schemas types entities functions procedures rules constants
|
|
20
|
+
attributes derived_attributes inverse_attributes
|
|
21
|
+
where_rules unique_rules informal_propositions
|
|
22
|
+
parameters variables statements items remark_items
|
|
23
|
+
].freeze
|
|
24
|
+
|
|
14
25
|
def initialize(source)
|
|
15
26
|
@source = source
|
|
16
27
|
@attached_spans = Set.new
|
|
@@ -23,8 +34,14 @@ module Expressir
|
|
|
23
34
|
def attach(model)
|
|
24
35
|
@model = model
|
|
25
36
|
remarks = extract_all_remarks
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
|
|
38
|
+
# Build nodes_with_positions ONCE for both tagged and untagged remark passes.
|
|
39
|
+
# This avoids double tree walk (381K nodes × 2 = 762K visits) which was
|
|
40
|
+
# the largest memory overhead in remark attachment (~430MB for large files).
|
|
41
|
+
nodes_with_positions = build_sorted_nodes_with_positions(model)
|
|
42
|
+
|
|
43
|
+
attach_tagged_remarks(model, remarks, nodes_with_positions)
|
|
44
|
+
attach_untagged_remarks(remarks, nodes_with_positions)
|
|
28
45
|
|
|
29
46
|
# Free expensive data structures after attachment is complete.
|
|
30
47
|
# These are only needed during the attach process.
|
|
@@ -32,7 +49,6 @@ module Expressir
|
|
|
32
49
|
@source_lines = nil
|
|
33
50
|
@scope_map = nil
|
|
34
51
|
@line_cache = nil
|
|
35
|
-
@remarks_cache = nil
|
|
36
52
|
|
|
37
53
|
model
|
|
38
54
|
end
|
|
@@ -131,9 +147,7 @@ module Expressir
|
|
|
131
147
|
@line_cache[position] ||= @source.byteslice(0...position).count("\n") + 1
|
|
132
148
|
end
|
|
133
149
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def attach_tagged_remarks(model, remarks)
|
|
150
|
+
def attach_tagged_remarks(model, remarks, nodes_with_positions)
|
|
137
151
|
tagged = remarks.select { |r| r[:tag] }
|
|
138
152
|
return if tagged.empty?
|
|
139
153
|
|
|
@@ -143,12 +157,6 @@ module Expressir
|
|
|
143
157
|
# This is the key optimization that makes scope lookup O(1) per remark
|
|
144
158
|
@scope_map ||= build_scope_map
|
|
145
159
|
|
|
146
|
-
# Collect nodes with positions for position-based fallback
|
|
147
|
-
nodes_with_positions = []
|
|
148
|
-
collect_nodes_with_positions(model, nodes_with_positions)
|
|
149
|
-
# Use stable sort to ensure deterministic ordering across Ruby versions
|
|
150
|
-
nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
151
|
-
|
|
152
160
|
tagged.sort_by { |r| r[:position] }.each do |remark|
|
|
153
161
|
next if @attached_spans.include?(remark[:position])
|
|
154
162
|
|
|
@@ -566,102 +574,6 @@ module Expressir
|
|
|
566
574
|
find_containing_scope_position(remark_line, nodes_with_positions)
|
|
567
575
|
end
|
|
568
576
|
|
|
569
|
-
def find_scope_by_text_search(remark_line)
|
|
570
|
-
lines = source_lines
|
|
571
|
-
return nil if remark_line < 1 || remark_line > lines.length
|
|
572
|
-
|
|
573
|
-
# Track nested scopes by searching backwards from remark_line
|
|
574
|
-
scope_stack = []
|
|
575
|
-
|
|
576
|
-
lines.each_with_index do |line, idx|
|
|
577
|
-
line_num = idx + 1
|
|
578
|
-
break if line_num > remark_line
|
|
579
|
-
|
|
580
|
-
# Check for START keywords first
|
|
581
|
-
if line =~ /^\s*SCHEMA\s+(\w+)/i
|
|
582
|
-
scope_stack << { type: :schema, name: $1, line: line_num }
|
|
583
|
-
end
|
|
584
|
-
|
|
585
|
-
if line =~ /^\s*FUNCTION\s+(\w+)/i
|
|
586
|
-
scope_stack << { type: :function, name: $1, line: line_num }
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
if line =~ /^\s*PROCEDURE\s+(\w+)/i
|
|
590
|
-
scope_stack << { type: :procedure, name: $1, line: line_num }
|
|
591
|
-
end
|
|
592
|
-
|
|
593
|
-
if line =~ /^\s*RULE\s+(\w+)/i
|
|
594
|
-
scope_stack << { type: :rule, name: $1, line: line_num }
|
|
595
|
-
end
|
|
596
|
-
|
|
597
|
-
if line =~ /^\s*ENTITY\s+(\w+)/i
|
|
598
|
-
scope_stack << { type: :entity, name: $1, line: line_num }
|
|
599
|
-
end
|
|
600
|
-
|
|
601
|
-
if line =~ /^\s*TYPE\s+(\w+)/i
|
|
602
|
-
scope_stack << { type: :type, name: $1, line: line_num }
|
|
603
|
-
end
|
|
604
|
-
|
|
605
|
-
# Then check for END keywords (to handle inline closures on same line)
|
|
606
|
-
if (line =~ /END_TYPE/i) && (scope_stack.last&.dig(:type) == :type)
|
|
607
|
-
scope_stack.pop
|
|
608
|
-
end
|
|
609
|
-
if (line =~ /END_FUNCTION/i) && (scope_stack.last&.dig(:type) == :function)
|
|
610
|
-
scope_stack.pop
|
|
611
|
-
end
|
|
612
|
-
if (line =~ /END_PROCEDURE/i) && (scope_stack.last&.dig(:type) == :procedure)
|
|
613
|
-
scope_stack.pop
|
|
614
|
-
end
|
|
615
|
-
if (line =~ /END_RULE/i) && (scope_stack.last&.dig(:type) == :rule)
|
|
616
|
-
scope_stack.pop
|
|
617
|
-
end
|
|
618
|
-
if (line =~ /END_ENTITY/i) && (scope_stack.last&.dig(:type) == :entity)
|
|
619
|
-
scope_stack.pop
|
|
620
|
-
end
|
|
621
|
-
if (line =~ /END_SCHEMA/i) && (scope_stack.last&.dig(:type) == :schema)
|
|
622
|
-
scope_stack.pop
|
|
623
|
-
end
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
# Find the innermost scope and get the corresponding model node
|
|
627
|
-
return nil if scope_stack.empty?
|
|
628
|
-
|
|
629
|
-
innermost = scope_stack.last
|
|
630
|
-
find_scope_node(innermost[:type], innermost[:name])
|
|
631
|
-
end
|
|
632
|
-
|
|
633
|
-
def find_scope_node(type, name)
|
|
634
|
-
return nil unless @model && name
|
|
635
|
-
|
|
636
|
-
@model.schemas.each do |schema|
|
|
637
|
-
# Check schema itself
|
|
638
|
-
if type == :schema && schema.id == name
|
|
639
|
-
return schema
|
|
640
|
-
end
|
|
641
|
-
|
|
642
|
-
# Check schema-level declarations
|
|
643
|
-
case type
|
|
644
|
-
when :function
|
|
645
|
-
found = schema.functions&.find { |f| f.id == name }
|
|
646
|
-
return found if found
|
|
647
|
-
when :procedure
|
|
648
|
-
found = schema.procedures&.find { |p| p.id == name }
|
|
649
|
-
return found if found
|
|
650
|
-
when :rule
|
|
651
|
-
found = schema.rules&.find { |r| r.id == name }
|
|
652
|
-
return found if found
|
|
653
|
-
when :entity
|
|
654
|
-
found = schema.entities&.find { |e| e.id == name }
|
|
655
|
-
return found if found
|
|
656
|
-
when :type
|
|
657
|
-
found = schema.types&.find { |t| t.id == name }
|
|
658
|
-
return found if found
|
|
659
|
-
end
|
|
660
|
-
end
|
|
661
|
-
|
|
662
|
-
nil
|
|
663
|
-
end
|
|
664
|
-
|
|
665
577
|
def build_scope_path(node)
|
|
666
578
|
return nil unless node
|
|
667
579
|
|
|
@@ -682,36 +594,6 @@ module Expressir
|
|
|
682
594
|
parts.empty? ? nil : parts.join(".")
|
|
683
595
|
end
|
|
684
596
|
|
|
685
|
-
def find_containing_scope_for_ip(remark_line, nodes_with_positions)
|
|
686
|
-
# First try scope map (O(1))
|
|
687
|
-
scope = find_containing_scope_by_name(remark_line)
|
|
688
|
-
if scope && supports_informal_propositions?(scope)
|
|
689
|
-
return scope
|
|
690
|
-
end
|
|
691
|
-
|
|
692
|
-
# Fallback to position-based detection
|
|
693
|
-
containing_nodes = nodes_with_positions.select do |n|
|
|
694
|
-
n[:line] && n[:end_line] && remark_line >= n[:line] && remark_line <= n[:end_line] &&
|
|
695
|
-
!repository?(n[:node]) && !cache?(n[:node])
|
|
696
|
-
end
|
|
697
|
-
|
|
698
|
-
# Find the innermost node that supports informal propositions
|
|
699
|
-
if containing_nodes.any?
|
|
700
|
-
containing_nodes.reverse_each do |n|
|
|
701
|
-
node = n[:node]
|
|
702
|
-
if supports_informal_propositions?(node)
|
|
703
|
-
return node
|
|
704
|
-
end
|
|
705
|
-
|
|
706
|
-
# Fallback to schema
|
|
707
|
-
return node if node.is_a?(Model::Declarations::Schema)
|
|
708
|
-
end
|
|
709
|
-
end
|
|
710
|
-
|
|
711
|
-
# Fallback: search for containing entity/type/rule by source text
|
|
712
|
-
find_scope_by_source_text(remark_line)
|
|
713
|
-
end
|
|
714
|
-
|
|
715
597
|
def find_scope_by_source_text(remark_line)
|
|
716
598
|
# Search backwards from remark_line for containing scope
|
|
717
599
|
lines = source_lines
|
|
@@ -917,15 +799,10 @@ module Expressir
|
|
|
917
799
|
item
|
|
918
800
|
end
|
|
919
801
|
|
|
920
|
-
def attach_untagged_remarks(
|
|
802
|
+
def attach_untagged_remarks(remarks, nodes_with_positions)
|
|
921
803
|
untagged = remarks.reject { |r| r[:tag] }
|
|
922
804
|
return unless untagged.any?
|
|
923
805
|
|
|
924
|
-
nodes_with_positions = []
|
|
925
|
-
collect_nodes_with_positions(model, nodes_with_positions)
|
|
926
|
-
# Use stable sort to preserve original order for equal keys
|
|
927
|
-
nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
928
|
-
|
|
929
806
|
untagged.each do |remark|
|
|
930
807
|
next if @attached_spans.include?(remark[:position])
|
|
931
808
|
|
|
@@ -1092,10 +969,7 @@ module Expressir
|
|
|
1092
969
|
end
|
|
1093
970
|
|
|
1094
971
|
# Check specific child collections
|
|
1095
|
-
|
|
1096
|
-
attributes derived_attributes inverse_attributes
|
|
1097
|
-
where_rules unique_rules informal_propositions
|
|
1098
|
-
parameters variables statements items remark_items].each do |attr|
|
|
972
|
+
CHILD_COLLECTION_ATTRIBUTES.each do |attr|
|
|
1099
973
|
collection = safe_get_collection(node, attr)
|
|
1100
974
|
collection&.each do |child|
|
|
1101
975
|
if child.is_a?(Model::ModelElement) && child.source_offset && child.source
|
|
@@ -1114,10 +988,7 @@ module Expressir
|
|
|
1114
988
|
collect_nodes_with_positions(child, result, visited)
|
|
1115
989
|
end
|
|
1116
990
|
|
|
1117
|
-
|
|
1118
|
-
attributes derived_attributes inverse_attributes
|
|
1119
|
-
where_rules unique_rules informal_propositions
|
|
1120
|
-
parameters variables statements items remark_items].each do |attr|
|
|
991
|
+
CHILD_COLLECTION_ATTRIBUTES.each do |attr|
|
|
1121
992
|
collection = safe_get_collection(node, attr)
|
|
1122
993
|
collection&.each do |item|
|
|
1123
994
|
collect_nodes_with_positions(item, result, visited)
|
|
@@ -1125,6 +996,16 @@ module Expressir
|
|
|
1125
996
|
end
|
|
1126
997
|
end
|
|
1127
998
|
|
|
999
|
+
# Build sorted nodes_with_positions ONCE for both tagged and untagged remark passes.
|
|
1000
|
+
# This merges the two separate tree walks into one, cutting node visits in half.
|
|
1001
|
+
def build_sorted_nodes_with_positions(model)
|
|
1002
|
+
nodes_with_positions = []
|
|
1003
|
+
collect_nodes_with_positions(model, nodes_with_positions)
|
|
1004
|
+
# Stable sort: nil positions last, ties broken by insertion order
|
|
1005
|
+
nodes_with_positions.sort_by!.with_index { |n, i| [n[:position] || Float::INFINITY, i] }
|
|
1006
|
+
nodes_with_positions
|
|
1007
|
+
end
|
|
1008
|
+
|
|
1128
1009
|
def find_nearest_node(remark, nodes)
|
|
1129
1010
|
remark_line = remark[:line]
|
|
1130
1011
|
|
data/lib/expressir/version.rb
CHANGED
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.3.
|
|
4
|
+
version: 2.3.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-04-
|
|
11
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|