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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c855bf4d946e55fcd6e67cbe9e7116c4bae1e5fb3b1078b71661f45e2fa595d3
4
- data.tar.gz: 6973d40c8ec148024102cbffcadfd5719dbf7d0ceba002caf33e55c0ad02acda
3
+ metadata.gz: e0cd9487796289d666f64e0d96628efb1e54a7d7b5fe02f051edd7ff886cbd38
4
+ data.tar.gz: 1f4dc35360f53c93c987ca8bab26cbbcf677c421bd05e050451315735dd78039
5
5
  SHA512:
6
- metadata.gz: 543ea137c1a7705e9aa899db477d7bf947ebe678d105197d1545355156a07f2d5dd66f9a9a7e812c64c9b4a446f05adede91ebbe51632c827607c429fd1d148d
7
- data.tar.gz: 3f44737379fbf50a0990e42bd711ddfb23765f7648c12340617f38d7cab2a57a69647ef7763c8f9e1607861e778e64a25db29f0d257901d58df59bd9bbe1718f
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
- attach_tagged_remarks(model, remarks)
27
- attach_untagged_remarks(model, remarks)
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
- alias get_line_number_from_offset get_line_number
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(model, 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
- %i[schemas types entities functions procedures rules constants
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
- %i[schemas types entities functions procedures rules constants
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
 
@@ -21,7 +21,6 @@ module Expressir
21
21
  attribute :selected, :boolean, default: false
22
22
  attribute :formatted, :string
23
23
  attribute :file_basename, :string
24
- attribute :full_source, :string
25
24
 
26
25
  key_value do
27
26
  map "_class", to: :_class, render_default: true
@@ -1,3 +1,3 @@
1
1
  module Expressir
2
- VERSION = "2.3.1".freeze
2
+ VERSION = "2.3.2".freeze
3
3
  end
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.1
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-01 00:00:00.000000000 Z
11
+ date: 2026-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64