expressir 2.3.7 → 2.4.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/lib/expressir/express/line_map.rb +48 -0
- data/lib/expressir/express/remark_attacher.rb +26 -110
- data/lib/expressir/express/remark_scanner.rb +180 -0
- data/lib/expressir/express.rb +2 -0
- data/lib/expressir/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 738451ad45c2f5e9268ef7562cbb2c667b90648f0e2df9ac409c79ceeeb6b879
|
|
4
|
+
data.tar.gz: e4e04ff5ede476581d65882a5e8d6fefba98ced3345337673bf61366e5b96392
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e9c0b2be28d8fa52f4c64f151e73c345701b2e6a118ee771f1442459c9c97bd5539bf1d799692ad065317849f9e794fb4cdc03a7d55efec1ddf627408f710de
|
|
7
|
+
data.tar.gz: 44100a53632c094d8cd7d5da781a8f2ab63274f4fcdcad44a0aafaf2e6c94821a5eefa6263c9f4c982876f55aeb6e23f5858cb7ca78decce3489a4c00b50abea
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# Maps byte positions in a source string to 1-based line numbers.
|
|
6
|
+
#
|
|
7
|
+
# Single source of truth for byte→line lookup across the remark pipeline
|
|
8
|
+
# (RemarkScanner and RemarkAttacher both rely on it). Builds an offset
|
|
9
|
+
# table once and answers queries in O(log n) via binary search.
|
|
10
|
+
class LineMap
|
|
11
|
+
# Newline byte (ASCII 0x0A).
|
|
12
|
+
NEWLINE = "\n"
|
|
13
|
+
|
|
14
|
+
# @param source_bytes [String] BINARY / ASCII-8BIT encoded source.
|
|
15
|
+
def initialize(source_bytes)
|
|
16
|
+
@offsets = build_offsets(source_bytes)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns the 1-based line number that contains the given byte position.
|
|
20
|
+
# Returns 1 for position 0 (or any position before the first newline).
|
|
21
|
+
# Returns the last line number for positions past the final newline.
|
|
22
|
+
#
|
|
23
|
+
# @param byte_pos [Integer, nil]
|
|
24
|
+
# @return [Integer] 1-based line number; nil input yields 1.
|
|
25
|
+
def line_number(byte_pos)
|
|
26
|
+
return 1 if byte_pos.nil? || byte_pos <= 0
|
|
27
|
+
|
|
28
|
+
idx = @offsets.bsearch_index { |offset| offset > byte_pos }
|
|
29
|
+
idx || @offsets.size
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
# Builds the sorted array of byte positions where each line begins.
|
|
35
|
+
# offsets[0] = 0 (line 1 begins at byte 0).
|
|
36
|
+
# offsets[i] = (byte position of the i-th newline) + 1 (line i+1 begins).
|
|
37
|
+
def build_offsets(source_bytes)
|
|
38
|
+
offsets = [0]
|
|
39
|
+
i = 0
|
|
40
|
+
while (i = source_bytes.index(NEWLINE, i))
|
|
41
|
+
offsets << (i + 1)
|
|
42
|
+
i += 1
|
|
43
|
+
end
|
|
44
|
+
offsets
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -48,7 +48,7 @@ module Expressir
|
|
|
48
48
|
def initialize(source)
|
|
49
49
|
@source = source
|
|
50
50
|
@attached_spans = Set.new
|
|
51
|
-
@
|
|
51
|
+
@line_map = LineMap.new(source.b)
|
|
52
52
|
@model = nil
|
|
53
53
|
@source_lines = nil # cached @source.lines
|
|
54
54
|
@scope_map = nil # cached scope at each line number
|
|
@@ -56,7 +56,7 @@ module Expressir
|
|
|
56
56
|
|
|
57
57
|
def attach(model)
|
|
58
58
|
@model = model
|
|
59
|
-
remarks =
|
|
59
|
+
remarks = RemarkScanner.new(@source).scan
|
|
60
60
|
|
|
61
61
|
# Build nodes_with_positions ONCE for both tagged and untagged remark passes.
|
|
62
62
|
# This avoids double tree walk (381K nodes × 2 = 762K visits) which was
|
|
@@ -71,103 +71,22 @@ module Expressir
|
|
|
71
71
|
@source = nil
|
|
72
72
|
@source_lines = nil
|
|
73
73
|
@scope_map = nil
|
|
74
|
-
@
|
|
74
|
+
@line_map = nil
|
|
75
75
|
|
|
76
76
|
model
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
private
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
byte_position = 0
|
|
84
|
-
|
|
85
|
-
@source.each_line.with_index do |line, line_idx|
|
|
86
|
-
line_bytesize = line.bytesize
|
|
87
|
-
line_bytes = line.b # Get byte string for indexing
|
|
88
|
-
if (dash_byte_idx = line_bytes.index("--"))
|
|
89
|
-
remark_text = line.byteslice((dash_byte_idx + 2)..).strip
|
|
90
|
-
|
|
91
|
-
# Check for special patterns like --IP1: content (informal proposition)
|
|
92
|
-
if remark_text.match?(/^IP\d+:\s*(.*)$/)
|
|
93
|
-
tag = remark_text[/^(IP\d+):/, 1]
|
|
94
|
-
content = remark_text[/^IP\d+:\s*(.*)$/, 1]
|
|
95
|
-
remarks << {
|
|
96
|
-
position: byte_position + dash_byte_idx,
|
|
97
|
-
line: line_idx + 1,
|
|
98
|
-
text: content,
|
|
99
|
-
tag: tag,
|
|
100
|
-
format: "tail",
|
|
101
|
-
}
|
|
102
|
-
else
|
|
103
|
-
tag, content = parse_tagged_remark(remark_text)
|
|
104
|
-
remarks << {
|
|
105
|
-
position: byte_position + dash_byte_idx,
|
|
106
|
-
line: line_idx + 1,
|
|
107
|
-
text: content || remark_text,
|
|
108
|
-
tag: tag,
|
|
109
|
-
format: "tail",
|
|
110
|
-
}
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
byte_position += line_bytesize
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
extract_embedded_remarks(remarks)
|
|
117
|
-
# Sort by position to ensure remarks are processed in source order
|
|
118
|
-
remarks.sort_by! { |r| r[:position] }
|
|
119
|
-
remarks
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def extract_embedded_remarks(remarks)
|
|
123
|
-
source_bytes = @source.b
|
|
124
|
-
start_pos = 0
|
|
125
|
-
while (start_idx = source_bytes.index("(*", start_pos))
|
|
126
|
-
end_idx = source_bytes.index("*)", start_idx + 2)
|
|
127
|
-
break unless end_idx
|
|
128
|
-
|
|
129
|
-
content = @source.byteslice((start_idx + 2)...end_idx)
|
|
130
|
-
line_num = get_line_number(start_idx)
|
|
131
|
-
|
|
132
|
-
tag, text = parse_tagged_embedded_remark(content)
|
|
133
|
-
|
|
134
|
-
remarks << {
|
|
135
|
-
position: start_idx,
|
|
136
|
-
line: line_num,
|
|
137
|
-
text: text,
|
|
138
|
-
tag: tag,
|
|
139
|
-
format: "embedded",
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
start_pos = end_idx + 2
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def parse_tagged_remark(text)
|
|
147
|
-
if text.start_with?('"') && (end_quote = text.index('"', 1))
|
|
148
|
-
[text[1...end_quote], text[(end_quote + 1)..].strip]
|
|
149
|
-
else
|
|
150
|
-
[nil, text]
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def parse_tagged_embedded_remark(content)
|
|
155
|
-
stripped = content.strip
|
|
156
|
-
if stripped.start_with?('"') && (end_quote = stripped.index('"', 1))
|
|
157
|
-
[stripped[1...end_quote], stripped[(end_quote + 1)..].strip]
|
|
158
|
-
else
|
|
159
|
-
[nil, stripped]
|
|
160
|
-
end
|
|
161
|
-
end
|
|
81
|
+
# Remark extraction lives in {RemarkScanner}; this class only attaches.
|
|
82
|
+
# Line-number lookup is delegated to {LineMap} for O(log n) queries.
|
|
162
83
|
|
|
163
84
|
def source_lines
|
|
164
85
|
@source_lines ||= @source.lines
|
|
165
86
|
end
|
|
166
87
|
|
|
167
88
|
def get_line_number(position)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@line_cache[position] ||= @source.byteslice(0...position).count("\n") + 1
|
|
89
|
+
@line_map.line_number(position)
|
|
171
90
|
end
|
|
172
91
|
|
|
173
92
|
def attach_tagged_remarks(model, remarks, nodes_with_positions)
|
|
@@ -180,23 +99,23 @@ module Expressir
|
|
|
180
99
|
# This is the key optimization that makes scope lookup O(1) per remark
|
|
181
100
|
@scope_map ||= build_scope_map
|
|
182
101
|
|
|
183
|
-
tagged.sort_by
|
|
184
|
-
next if @attached_spans.include?(remark
|
|
102
|
+
tagged.sort_by(&:position).each do |remark|
|
|
103
|
+
next if @attached_spans.include?(remark.position)
|
|
185
104
|
|
|
186
|
-
tag = remark
|
|
105
|
+
tag = remark.tag
|
|
187
106
|
target = nil
|
|
188
107
|
|
|
189
108
|
# Find containing scope using pre-computed scope map (O(1))
|
|
190
109
|
# Falls back to position-based lookup if scope map doesn't have the line
|
|
191
|
-
containing_scope = find_containing_scope_by_name(remark
|
|
192
|
-
containing_scope ||= find_containing_scope_position(remark
|
|
110
|
+
containing_scope = find_containing_scope_by_name(remark.line)
|
|
111
|
+
containing_scope ||= find_containing_scope_position(remark.line,
|
|
193
112
|
nodes_with_positions)
|
|
194
113
|
|
|
195
114
|
# Check if this is an informal proposition tag (IP\d+)
|
|
196
115
|
if tag.match?(/^IP\d+$/)
|
|
197
116
|
scope = containing_scope
|
|
198
117
|
if scope.nil?
|
|
199
|
-
scope = find_scope_by_source_text(remark
|
|
118
|
+
scope = find_scope_by_source_text(remark.line)
|
|
200
119
|
end
|
|
201
120
|
if scope && supports_informal_propositions?(scope)
|
|
202
121
|
target = create_or_find_informal_proposition(scope, tag)
|
|
@@ -246,7 +165,7 @@ module Expressir
|
|
|
246
165
|
# Special handling for remarks inside WHERE clauses
|
|
247
166
|
if target.nil? && supports_where_rules?(containing_scope)
|
|
248
167
|
target = find_target_in_where_clause(containing_scope, tag,
|
|
249
|
-
remark
|
|
168
|
+
remark.line)
|
|
250
169
|
end
|
|
251
170
|
|
|
252
171
|
# Only fall back to schema prefix if NOT inside a function/rule/procedure
|
|
@@ -296,9 +215,8 @@ module Expressir
|
|
|
296
215
|
end
|
|
297
216
|
|
|
298
217
|
if target
|
|
299
|
-
add_remark(target, remark
|
|
300
|
-
|
|
301
|
-
@attached_spans << remark[:position]
|
|
218
|
+
add_remark(target, remark.text, format: remark.format, tag: remark.tag)
|
|
219
|
+
@attached_spans << remark.position
|
|
302
220
|
end
|
|
303
221
|
end
|
|
304
222
|
end
|
|
@@ -829,28 +747,26 @@ module Expressir
|
|
|
829
747
|
end
|
|
830
748
|
|
|
831
749
|
def attach_untagged_remarks(remarks, nodes_with_positions)
|
|
832
|
-
untagged = remarks.reject
|
|
750
|
+
untagged = remarks.reject(&:tag)
|
|
833
751
|
return unless untagged.any?
|
|
834
752
|
|
|
835
753
|
untagged.each do |remark|
|
|
836
|
-
next if @attached_spans.include?(remark
|
|
754
|
+
next if @attached_spans.include?(remark.position)
|
|
837
755
|
|
|
838
|
-
if end_scope_line?(remark
|
|
756
|
+
if end_scope_line?(remark.line)
|
|
839
757
|
matched_node = find_node_for_end_scope_remark(remark,
|
|
840
758
|
nodes_with_positions)
|
|
841
759
|
if matched_node
|
|
842
|
-
add_remark(matched_node, remark
|
|
843
|
-
|
|
844
|
-
@attached_spans << remark[:position]
|
|
760
|
+
add_remark(matched_node, remark.text, format: remark.format, tag: nil)
|
|
761
|
+
@attached_spans << remark.position
|
|
845
762
|
next
|
|
846
763
|
end
|
|
847
764
|
end
|
|
848
765
|
|
|
849
766
|
matched_node = find_nearest_node(remark, nodes_with_positions)
|
|
850
767
|
if matched_node
|
|
851
|
-
add_remark(matched_node, remark
|
|
852
|
-
|
|
853
|
-
@attached_spans << remark[:position]
|
|
768
|
+
add_remark(matched_node, remark.text, format: remark.format, tag: nil)
|
|
769
|
+
@attached_spans << remark.position
|
|
854
770
|
end
|
|
855
771
|
end
|
|
856
772
|
end
|
|
@@ -868,7 +784,7 @@ module Expressir
|
|
|
868
784
|
end
|
|
869
785
|
|
|
870
786
|
def find_node_for_end_scope_remark(remark, nodes)
|
|
871
|
-
line_content = get_line_content(remark
|
|
787
|
+
line_content = get_line_content(remark.line)
|
|
872
788
|
|
|
873
789
|
node_type = case line_content
|
|
874
790
|
when /END_SCHEMA/i then Model::Declarations::Schema
|
|
@@ -883,8 +799,8 @@ module Expressir
|
|
|
883
799
|
|
|
884
800
|
matching_nodes = nodes.select do |n|
|
|
885
801
|
n[:node].is_a?(node_type) &&
|
|
886
|
-
(n[:end_line] == remark
|
|
887
|
-
(n[:end_line] && n[:end_line] <= remark
|
|
802
|
+
(n[:end_line] == remark.line ||
|
|
803
|
+
(n[:end_line] && n[:end_line] <= remark.line && n[:end_line] >= remark.line - 2))
|
|
888
804
|
end
|
|
889
805
|
|
|
890
806
|
matching_nodes.first&.dig(:node) || find_node_by_type(nodes, node_type)
|
|
@@ -1038,7 +954,7 @@ module Expressir
|
|
|
1038
954
|
end
|
|
1039
955
|
|
|
1040
956
|
def find_nearest_node(remark, nodes)
|
|
1041
|
-
remark_line = remark
|
|
957
|
+
remark_line = remark.line
|
|
1042
958
|
|
|
1043
959
|
# For tail remarks, prefer nodes that START on the same line
|
|
1044
960
|
# This handles cases like: "attr : STRING; -- tail remark"
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Expressir
|
|
4
|
+
module Express
|
|
5
|
+
# Scans EXPRESS source text and extracts all remarks (tail and embedded),
|
|
6
|
+
# correctly tracking embedded-remark nesting so that inline `--` inside
|
|
7
|
+
# a `(* ... *)` documentation block is NOT mistaken for a tail remark.
|
|
8
|
+
#
|
|
9
|
+
# Single responsibility: source string -> Array<Remark>. The scanner has
|
|
10
|
+
# no knowledge of the model; attaching remarks to model elements is the
|
|
11
|
+
# job of {RemarkAttacher}.
|
|
12
|
+
#
|
|
13
|
+
# Reference: ISO 10303-11 §7.1.6.
|
|
14
|
+
# tail_remark = '--' [ remark_tag ] { <any char except newline> } '\n'
|
|
15
|
+
# embedded_remark = '(*' [ remark_tag ]
|
|
16
|
+
# { <any char> | nested embedded_remark } '*)'
|
|
17
|
+
#
|
|
18
|
+
# The scanner is a single-pass state machine with three states:
|
|
19
|
+
# :top — outside any remark; look for `--` or `(*`.
|
|
20
|
+
# :in_tail — inside a tail remark; look only for the next newline.
|
|
21
|
+
# :in_embedded — inside one or more nested embedded remarks; look
|
|
22
|
+
# for the matching `*)` or a nested `(*`, ignoring `--`.
|
|
23
|
+
class RemarkScanner
|
|
24
|
+
# Immutable value object describing a single extracted remark.
|
|
25
|
+
Remark = Struct.new(:position, :line, :text, :tag, :format, keyword_init: true) do
|
|
26
|
+
def tail?
|
|
27
|
+
format == TAIL_FORMAT
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def embedded?
|
|
31
|
+
format == EMBEDDED_FORMAT
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tagged?
|
|
35
|
+
!tag.nil? && !tag.empty?
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Format discriminator values.
|
|
40
|
+
TAIL_FORMAT = "tail"
|
|
41
|
+
EMBEDDED_FORMAT = "embedded"
|
|
42
|
+
|
|
43
|
+
# Lexical markers recognised by EXPRESS.
|
|
44
|
+
TAIL_MARKER = "--"
|
|
45
|
+
EMBEDDED_OPEN = "(*"
|
|
46
|
+
EMBEDDED_CLOSE = "*)"
|
|
47
|
+
|
|
48
|
+
# Tag forms (see ISO 10303-11 §7.1.6.3 Remark tag).
|
|
49
|
+
# IP-style informal-proposition tags are recognised only in tail remarks
|
|
50
|
+
# (matching historic behaviour); embedded remarks use the quoted form.
|
|
51
|
+
INFORMAL_PROPOSITION_TAG = /\A(IP\d+):\s*(.*)\z/m
|
|
52
|
+
QUOTED_TAG = /\A"([^"]*)"\s*(.*)\z/m
|
|
53
|
+
|
|
54
|
+
# @param source [String] EXPRESS source text (any encoding).
|
|
55
|
+
def initialize(source)
|
|
56
|
+
@source = source
|
|
57
|
+
@source_bytes = source.b
|
|
58
|
+
@line_map = LineMap.new(@source_bytes)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns an Array of {Remark}, in source order (by byte position).
|
|
62
|
+
# @return [Array<Remark>]
|
|
63
|
+
def scan
|
|
64
|
+
run_state_machine
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
alias call scan
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def run_state_machine
|
|
72
|
+
remarks = []
|
|
73
|
+
src = @source_bytes
|
|
74
|
+
pos = 0
|
|
75
|
+
|
|
76
|
+
state = :top
|
|
77
|
+
embedded_depth = 0
|
|
78
|
+
tail_start = nil # byte position of first content char after "--"
|
|
79
|
+
tail_line = nil
|
|
80
|
+
embedded_start = nil # byte position of first content char after "(*"
|
|
81
|
+
embedded_line = nil
|
|
82
|
+
|
|
83
|
+
while pos < src.bytesize
|
|
84
|
+
case state
|
|
85
|
+
when :in_tail
|
|
86
|
+
nl = src.index("\n", pos)
|
|
87
|
+
if nl
|
|
88
|
+
emit_tail(remarks, tail_start, nl, tail_line)
|
|
89
|
+
pos = nl + 1
|
|
90
|
+
state = :top
|
|
91
|
+
else
|
|
92
|
+
emit_tail(remarks, tail_start, src.bytesize, tail_line)
|
|
93
|
+
return remarks
|
|
94
|
+
end
|
|
95
|
+
when :in_embedded
|
|
96
|
+
closing = src.index(EMBEDDED_CLOSE, pos)
|
|
97
|
+
nesting = src.index(EMBEDDED_OPEN, pos)
|
|
98
|
+
if nesting && (!closing || nesting < closing)
|
|
99
|
+
embedded_depth += 1
|
|
100
|
+
pos = nesting + EMBEDDED_OPEN.bytesize
|
|
101
|
+
elsif closing
|
|
102
|
+
embedded_depth -= 1
|
|
103
|
+
pos = closing + EMBEDDED_CLOSE.bytesize
|
|
104
|
+
if embedded_depth.zero?
|
|
105
|
+
emit_embedded(remarks, embedded_start, closing, embedded_line)
|
|
106
|
+
state = :top
|
|
107
|
+
end
|
|
108
|
+
else
|
|
109
|
+
# Unclosed embedded remark — terminate scan gracefully.
|
|
110
|
+
return remarks
|
|
111
|
+
end
|
|
112
|
+
else # :top
|
|
113
|
+
next_embed = src.index(EMBEDDED_OPEN, pos)
|
|
114
|
+
next_tail = src.index(TAIL_MARKER, pos)
|
|
115
|
+
if next_embed.nil? && next_tail.nil?
|
|
116
|
+
return remarks
|
|
117
|
+
elsif next_embed && (next_tail.nil? || next_embed <= next_tail)
|
|
118
|
+
embedded_start = next_embed + EMBEDDED_OPEN.bytesize
|
|
119
|
+
embedded_line = @line_map.line_number(next_embed)
|
|
120
|
+
embedded_depth = 1
|
|
121
|
+
state = :in_embedded
|
|
122
|
+
pos = next_embed + EMBEDDED_OPEN.bytesize
|
|
123
|
+
else
|
|
124
|
+
tail_start = next_tail + TAIL_MARKER.bytesize
|
|
125
|
+
tail_line = @line_map.line_number(next_tail)
|
|
126
|
+
state = :in_tail
|
|
127
|
+
pos = next_tail + TAIL_MARKER.bytesize
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
remarks
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def emit_tail(remarks, content_start, content_end, line)
|
|
136
|
+
# Slice content from the original (UTF-8) source so the returned text
|
|
137
|
+
# preserves the source encoding. Byte offsets come from the BINARY copy.
|
|
138
|
+
raw = @source.byteslice(content_start...content_end)
|
|
139
|
+
tag, content = parse_tail(raw.strip)
|
|
140
|
+
remarks << Remark.new(
|
|
141
|
+
position: content_start - TAIL_MARKER.bytesize,
|
|
142
|
+
line: line,
|
|
143
|
+
text: content,
|
|
144
|
+
tag: tag,
|
|
145
|
+
format: TAIL_FORMAT,
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def emit_embedded(remarks, content_start, content_end, line)
|
|
150
|
+
raw = @source.byteslice(content_start...content_end)
|
|
151
|
+
tag, content = parse_embedded(raw)
|
|
152
|
+
remarks << Remark.new(
|
|
153
|
+
position: content_start - EMBEDDED_OPEN.bytesize,
|
|
154
|
+
line: line,
|
|
155
|
+
text: content,
|
|
156
|
+
tag: tag,
|
|
157
|
+
format: EMBEDDED_FORMAT,
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Tail remarks support both quoted tags ("schema.entity" ...) and the
|
|
162
|
+
# shorthand IP-tag form (IP1: ...). Returns [tag, content]; for untagged
|
|
163
|
+
# remarks tag is nil and content is the stripped text.
|
|
164
|
+
def parse_tail(text)
|
|
165
|
+
match = text.match(INFORMAL_PROPOSITION_TAG) || text.match(QUOTED_TAG)
|
|
166
|
+
match ? match.captures : [nil, text]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Embedded remarks only use the quoted tag form.
|
|
170
|
+
def parse_embedded(content)
|
|
171
|
+
stripped = content.strip
|
|
172
|
+
if (m = stripped.match(QUOTED_TAG))
|
|
173
|
+
[m[1], m[2]]
|
|
174
|
+
else
|
|
175
|
+
[nil, stripped]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
data/lib/expressir/express.rb
CHANGED
|
@@ -12,10 +12,12 @@ module Expressir
|
|
|
12
12
|
autoload :Formatter, "#{__dir__}/express/formatter"
|
|
13
13
|
autoload :Formatters, "#{__dir__}/express/formatters"
|
|
14
14
|
autoload :HyperlinkFormatter, "#{__dir__}/express/hyperlink_formatter"
|
|
15
|
+
autoload :LineMap, "#{__dir__}/express/line_map"
|
|
15
16
|
autoload :ModelVisitor, "#{__dir__}/express/model_visitor"
|
|
16
17
|
autoload :Parser, "#{__dir__}/express/parser"
|
|
17
18
|
autoload :PrettyFormatter, "#{__dir__}/express/pretty_formatter"
|
|
18
19
|
autoload :RemarkAttacher, "#{__dir__}/express/remark_attacher"
|
|
20
|
+
autoload :RemarkScanner, "#{__dir__}/express/remark_scanner"
|
|
19
21
|
autoload :ResolveReferencesModelVisitor,
|
|
20
22
|
"#{__dir__}/express/resolve_references_model_visitor"
|
|
21
23
|
autoload :SchemaHeadFormatter, "#{__dir__}/express/schema_head_formatter"
|
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.
|
|
4
|
+
version: 2.4.0
|
|
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-
|
|
11
|
+
date: 2026-07-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -398,10 +398,12 @@ files:
|
|
|
398
398
|
- lib/expressir/express/formatters/statements_formatter.rb
|
|
399
399
|
- lib/expressir/express/formatters/supertype_expressions_formatter.rb
|
|
400
400
|
- lib/expressir/express/hyperlink_formatter.rb
|
|
401
|
+
- lib/expressir/express/line_map.rb
|
|
401
402
|
- lib/expressir/express/model_visitor.rb
|
|
402
403
|
- lib/expressir/express/parser.rb
|
|
403
404
|
- lib/expressir/express/pretty_formatter.rb
|
|
404
405
|
- lib/expressir/express/remark_attacher.rb
|
|
406
|
+
- lib/expressir/express/remark_scanner.rb
|
|
405
407
|
- lib/expressir/express/resolve_references_model_visitor.rb
|
|
406
408
|
- lib/expressir/express/schema_head_formatter.rb
|
|
407
409
|
- lib/expressir/express/streaming_builder.rb
|