elkrb 1.0.2 → 2.0.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/CHANGELOG.adoc +364 -0
- data/LICENSE +25 -0
- data/README.adoc +695 -557
- data/exe/elkrb +36 -2
- data/lib/elkrb/atomic_destination.rb +53 -0
- data/lib/elkrb/best_effort_write.rb +49 -0
- data/lib/elkrb/cli/layout_flag_options.rb +54 -0
- data/lib/elkrb/cli.rb +155 -82
- data/lib/elkrb/command_resolver.rb +186 -0
- data/lib/elkrb/commands/batch_command.rb +28 -5
- data/lib/elkrb/commands/convert_command.rb +22 -52
- data/lib/elkrb/commands/diagram_command.rb +234 -102
- data/lib/elkrb/commands/options_command.rb +87 -0
- data/lib/elkrb/commands/render_command.rb +6 -1
- data/lib/elkrb/commands/validate_command.rb +146 -91
- data/lib/elkrb/errors.rb +20 -1
- data/lib/elkrb/format_sniffer.rb +362 -0
- data/lib/elkrb/geometry/bezier.rb +14 -4
- data/lib/elkrb/geometry/point.rb +5 -6
- data/lib/elkrb/geometry/rectangle.rb +2 -2
- data/lib/elkrb/graph/deep_stringify_keys.rb +30 -0
- data/lib/elkrb/graph/edge.rb +89 -3
- data/lib/elkrb/graph/graph.rb +43 -15
- data/lib/elkrb/graph/label.rb +16 -2
- data/lib/elkrb/graph/node.rb +13 -2
- data/lib/elkrb/graph/node_constraints.rb +139 -8
- data/lib/elkrb/graph/normalize_option_keys.rb +39 -0
- data/lib/elkrb/graph/port.rb +29 -21
- data/lib/elkrb/graph/read_only_mapping.rb +21 -0
- data/lib/elkrb/graphviz_wrapper.rb +152 -44
- data/lib/elkrb/layout/algorithm_registry.rb +65 -8
- data/lib/elkrb/layout/algorithms/base_algorithm.rb +74 -74
- data/lib/elkrb/layout/algorithms/box.rb +47 -20
- data/lib/elkrb/layout/algorithms/disco.rb +150 -61
- data/lib/elkrb/layout/algorithms/fixed.rb +70 -10
- data/lib/elkrb/layout/algorithms/force.rb +145 -86
- data/lib/elkrb/layout/algorithms/layered/bk/aligner.rb +104 -0
- data/lib/elkrb/layout/algorithms/layered/bk/compactor.rb +87 -0
- data/lib/elkrb/layout/algorithms/layered/bk/view.rb +103 -0
- data/lib/elkrb/layout/algorithms/layered/bk_node_placer.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/components.rb +170 -0
- data/lib/elkrb/layout/algorithms/layered/crossing_minimizer.rb +306 -0
- data/lib/elkrb/layout/algorithms/layered/cycle_breaker.rb +118 -49
- data/lib/elkrb/layout/algorithms/layered/discovery_order.rb +75 -0
- data/lib/elkrb/layout/algorithms/layered/dummy_slot.rb +31 -0
- data/lib/elkrb/layout/algorithms/layered/layer_assigner.rb +188 -48
- data/lib/elkrb/layout/algorithms/layered/layer_constraints.rb +82 -0
- data/lib/elkrb/layout/algorithms/layered/layer_sweep.rb +195 -0
- data/lib/elkrb/layout/algorithms/layered/node_placer.rb +214 -36
- data/lib/elkrb/layout/algorithms/layered/orthogonal/gap_solver.rb +219 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/hyper_edge_segment.rb +152 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/junction_points.rb +58 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal/segment_order.rb +134 -0
- data/lib/elkrb/layout/algorithms/layered/orthogonal_router.rb +253 -0
- data/lib/elkrb/layout/algorithms/layered/port_order.rb +188 -0
- data/lib/elkrb/layout/algorithms/layered/port_spread.rb +42 -0
- data/lib/elkrb/layout/algorithms/layered.rb +548 -13
- data/lib/elkrb/layout/algorithms/libavoid.rb +951 -187
- data/lib/elkrb/layout/algorithms/mrtree.rb +585 -65
- data/lib/elkrb/layout/algorithms/radial.rb +188 -29
- data/lib/elkrb/layout/algorithms/random.rb +131 -17
- data/lib/elkrb/layout/algorithms/rectpacking.rb +28 -40
- data/lib/elkrb/layout/algorithms/spore_compaction.rb +57 -100
- data/lib/elkrb/layout/algorithms/spore_overlap.rb +78 -84
- data/lib/elkrb/layout/algorithms/stress.rb +391 -84
- data/lib/elkrb/layout/algorithms/topdown_packing.rb +95 -31
- data/lib/elkrb/layout/algorithms/vertiflex.rb +12 -47
- data/lib/elkrb/layout/constraints/alignment_constraint.rb +49 -12
- data/lib/elkrb/layout/constraints/base_constraint.rb +3 -15
- data/lib/elkrb/layout/constraints/constraint_processor.rb +9 -23
- data/lib/elkrb/layout/constraints/fixed_position_constraint.rb +22 -13
- data/lib/elkrb/layout/constraints/layer_constraint.rb +10 -33
- data/lib/elkrb/layout/constraints/relative_position_constraint.rb +94 -50
- data/lib/elkrb/layout/edge_router.rb +533 -337
- data/lib/elkrb/layout/hierarchical_processor.rb +19 -276
- data/lib/elkrb/layout/java_random.rb +59 -0
- data/lib/elkrb/layout/label_placer.rb +165 -157
- data/lib/elkrb/layout/layout_engine.rb +46 -23
- data/lib/elkrb/layout/node_index.rb +149 -0
- data/lib/elkrb/layout/polyomino/component_compactor.rb +138 -0
- data/lib/elkrb/layout/polyomino/footprint.rb +94 -0
- data/lib/elkrb/layout/polyomino/grid.rb +106 -0
- data/lib/elkrb/layout/polyomino/packer.rb +95 -0
- data/lib/elkrb/layout/polyomino/piece.rb +52 -0
- data/lib/elkrb/layout/port_constraint_processor.rb +131 -44
- data/lib/elkrb/layout/spore/body.rb +65 -0
- data/lib/elkrb/layout/spore/depth_first_compaction.rb +127 -0
- data/lib/elkrb/layout/spore/edge_cost.rb +69 -0
- data/lib/elkrb/layout/spore/gap.rb +76 -0
- data/lib/elkrb/layout/spore/geometry.rb +104 -0
- data/lib/elkrb/layout/spore/overlap_sweep.rb +90 -0
- data/lib/elkrb/layout/spore/placement.rb +57 -0
- data/lib/elkrb/layout/spore/rays.rb +119 -0
- data/lib/elkrb/layout/spore/spanning_tree.rb +53 -0
- data/lib/elkrb/layout/spore/triangulation.rb +159 -0
- data/lib/elkrb/layout_flags.rb +69 -0
- data/lib/elkrb/options/decimal.rb +50 -0
- data/lib/elkrb/options/element_options.rb +43 -0
- data/lib/elkrb/options/elk_padding.rb +20 -14
- data/lib/elkrb/options/k_vector.rb +6 -7
- data/lib/elkrb/options/k_vector_chain.rb +12 -20
- data/lib/elkrb/options/option_map.rb +85 -0
- data/lib/elkrb/options/registry.rb +335 -0
- data/lib/elkrb/options/resolver.rb +151 -0
- data/lib/elkrb/options/spellings.rb +62 -0
- data/lib/elkrb/options/unhonoured_report.rb +194 -0
- data/lib/elkrb/parsers/elkt/lexer.rb +309 -0
- data/lib/elkrb/parsers/elkt/parser.rb +424 -0
- data/lib/elkrb/parsers/elkt/resolver.rb +142 -0
- data/lib/elkrb/parsers/elkt/token.rb +40 -0
- data/lib/elkrb/parsers/elkt_parser.rb +32 -230
- data/lib/elkrb/serializers/dot_serializer.rb +342 -54
- data/lib/elkrb/serializers/elkt_serializer.rb +386 -155
- data/lib/elkrb/version.rb +1 -1
- data/lib/elkrb.rb +60 -61
- metadata +71 -49
- data/.rspec +0 -3
- data/.rubocop.yml +0 -11
- data/Gemfile +0 -13
- data/Rakefile +0 -64
- data/benchmarks/README.md +0 -172
- data/benchmarks/elkjs_benchmark.js +0 -140
- data/benchmarks/elkrb_benchmark.rb +0 -145
- data/benchmarks/fixtures/graphs.json +0 -10777
- data/benchmarks/generate_report.rb +0 -241
- data/benchmarks/generate_test_graphs.rb +0 -154
- data/benchmarks/results/elkrb_results.json +0 -280
- data/benchmarks/results/elkrb_summary.json +0 -285
- data/elkrb.gemspec +0 -39
- data/examples/dot_export_demo.rb +0 -133
- data/examples/hierarchical_graph.rb +0 -19
- data/examples/layout_constraints_demo.rb +0 -272
- data/examples/port_constraints_demo.rb +0 -291
- data/examples/self_loop_demo.rb +0 -391
- data/examples/simple_graph.rb +0 -50
- data/examples/spline_routing_demo.rb +0 -235
- data/lib/elkrb/graph/layout_options.rb +0 -247
- data/sig/elkrb/constraints.rbs +0 -114
- data/sig/elkrb/geometry.rbs +0 -61
- data/sig/elkrb/graph.rbs +0 -112
- data/sig/elkrb/layout.rbs +0 -107
- data/sig/elkrb/options.rbs +0 -81
- data/sig/elkrb.rbs +0 -32
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../errors"
|
|
4
|
+
require_relative "token"
|
|
5
|
+
|
|
6
|
+
module Elkrb
|
|
7
|
+
module Parsers
|
|
8
|
+
module Elkt
|
|
9
|
+
# Turns ELKT source into a flat Array of Token, ending in :eof.
|
|
10
|
+
#
|
|
11
|
+
# Scan order is load-bearing. Strings are consumed ATOMICALLY before any
|
|
12
|
+
# comment is recognised, so `/*` and `//` inside a label are ordinary
|
|
13
|
+
# characters; the reverse order deletes everything between a label's `/*`
|
|
14
|
+
# and the next real block comment.
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
class Lexer
|
|
18
|
+
BOM = "\uFEFF"
|
|
19
|
+
WS = /\G[ \t\r\n]+/
|
|
20
|
+
NEWLINE = /\r\n|\r|\n/
|
|
21
|
+
# The exponent alternative MUST precede the plain-decimal one: Ruby
|
|
22
|
+
# alternation is ordered, so `\d+\.\d+` first matches "1.5" of
|
|
23
|
+
# "1.5e-3" and leaves "e-3" to lex as an identifier.
|
|
24
|
+
NUMBER = /\G[+-]?(?:\d+(?:\.\d+)?[eE][+-]?\d+|\d+\.\d+|\d+)/
|
|
25
|
+
IDENT = /\G\^?[A-Za-z_]\w*(?:\.\^?[A-Za-z_]\w*)*/
|
|
26
|
+
PUNCT = {
|
|
27
|
+
"{" => :lbrace, "}" => :rbrace, "[" => :lbracket,
|
|
28
|
+
"]" => :rbracket, ":" => :colon, "," => :comma, "." => :dot
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def initialize(source)
|
|
32
|
+
unless source.is_a?(String)
|
|
33
|
+
raise TypeError,
|
|
34
|
+
"ELKT source must be a String, got #{source.class}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@src = normalize(source)
|
|
38
|
+
@pos = 0
|
|
39
|
+
@line = 1
|
|
40
|
+
@col = 1
|
|
41
|
+
@tokens = []
|
|
42
|
+
@scanners = build_scanners
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def tokenize
|
|
46
|
+
@pos += BOM.length if @src.start_with?(BOM)
|
|
47
|
+
scan_one while @pos < @src.length
|
|
48
|
+
emit(:eof, "", nil)
|
|
49
|
+
@tokens
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
# The scan order described above, written out. Keep the string
|
|
55
|
+
# scanner before the two comment scanners.
|
|
56
|
+
def build_scanners
|
|
57
|
+
[
|
|
58
|
+
method(:take_whitespace), method(:take_string),
|
|
59
|
+
method(:take_line_comment), method(:take_block_comment),
|
|
60
|
+
method(:take_arrow), method(:take_number),
|
|
61
|
+
method(:take_identifier), method(:take_pipe),
|
|
62
|
+
method(:take_punctuation)
|
|
63
|
+
].freeze
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Skipped by moving @pos alone: a byte-order mark is a zero-width
|
|
67
|
+
# marker, so the first real character is still at column 1. Routing it
|
|
68
|
+
# through `advance` shifted every first-line column by one.
|
|
69
|
+
#
|
|
70
|
+
# UTF-8 and ASCII-8BIT carry no real source encoding to transcode
|
|
71
|
+
# FROM -- UTF-8 is already the target, and ASCII-8BIT is Ruby's "no
|
|
72
|
+
# encoding is known" tag -- so both are reinterpreted in place.
|
|
73
|
+
# Anything else is a real, named encoding (e.g. a file read with
|
|
74
|
+
# `encoding: "ISO-8859-1"`) and is converted with String#encode
|
|
75
|
+
# instead, which is what turns a Latin-1 0xE9 into the accented
|
|
76
|
+
# character it actually names rather than a byte sequence that
|
|
77
|
+
# merely happens to be invalid UTF-8.
|
|
78
|
+
NO_SOURCE_ENCODING_TO_TRANSCODE_FROM =
|
|
79
|
+
[Encoding::UTF_8, Encoding::ASCII_8BIT].freeze
|
|
80
|
+
private_constant :NO_SOURCE_ENCODING_TO_TRANSCODE_FROM
|
|
81
|
+
|
|
82
|
+
def normalize(text)
|
|
83
|
+
if NO_SOURCE_ENCODING_TO_TRANSCODE_FROM.include?(text.encoding)
|
|
84
|
+
reinterpret(text)
|
|
85
|
+
else
|
|
86
|
+
transcode(text)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @return [String] transcoded to UTF-8, or reinterpreted as UTF-8
|
|
91
|
+
# when the source is invalid in its own declared encoding.
|
|
92
|
+
def transcode(text)
|
|
93
|
+
text.encode(Encoding::UTF_8)
|
|
94
|
+
rescue EncodingError
|
|
95
|
+
reinterpret(text)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @return [String] the same bytes, tagged UTF-8.
|
|
99
|
+
# @raise [Elkrb::ParseError] when those bytes are not valid UTF-8.
|
|
100
|
+
def reinterpret(text)
|
|
101
|
+
text = text.dup.force_encoding(Encoding::UTF_8)
|
|
102
|
+
return text if text.valid_encoding?
|
|
103
|
+
|
|
104
|
+
line, col = first_invalid_location(text)
|
|
105
|
+
raise Elkrb::ParseError.new(
|
|
106
|
+
"Input is not valid UTF-8 at line #{line}, column #{col}",
|
|
107
|
+
line: line, column: col,
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Walks up to the first broken character and counts the lines before
|
|
112
|
+
# it. Reporting line 1, column 1 for every file pointed the user at
|
|
113
|
+
# the top even when the bad bytes were far down.
|
|
114
|
+
def first_invalid_location(text)
|
|
115
|
+
line = 1
|
|
116
|
+
col = 1
|
|
117
|
+
previous = nil
|
|
118
|
+
text.each_char do |char|
|
|
119
|
+
return [line, col] unless char.valid_encoding?
|
|
120
|
+
|
|
121
|
+
line, col = step_location(char, previous, line, col)
|
|
122
|
+
previous = char
|
|
123
|
+
end
|
|
124
|
+
[line, col]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# CRLF is one line break, so the LF after a CR must not count again.
|
|
128
|
+
def step_location(char, previous, line, col)
|
|
129
|
+
return [line + 1, 1] if char == "\r" ||
|
|
130
|
+
(char == "\n" && previous != "\r")
|
|
131
|
+
return [line, col] if char == "\n"
|
|
132
|
+
|
|
133
|
+
[line, col + 1]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def scan_one
|
|
137
|
+
return if @scanners.any?(&:call)
|
|
138
|
+
|
|
139
|
+
raise_at(@line, @col,
|
|
140
|
+
"Unexpected character #{@src[@pos].inspect}")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def take_whitespace
|
|
144
|
+
text = match(WS) or return nil
|
|
145
|
+
|
|
146
|
+
advance(text)
|
|
147
|
+
text
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Xtext's SL_COMMENT is `'//' !('\n'|'\r')*`, so a lone CR ends it too.
|
|
151
|
+
# Stopping only at LF swallowed the rest of a CR-delimited file.
|
|
152
|
+
def take_line_comment
|
|
153
|
+
return unless @src[@pos, 2] == "//"
|
|
154
|
+
|
|
155
|
+
stop = @src.index(/[\r\n]/, @pos)
|
|
156
|
+
text = stop ? @src[@pos...stop] : @src[@pos..]
|
|
157
|
+
advance(text)
|
|
158
|
+
text
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def take_block_comment
|
|
162
|
+
return unless @src[@pos, 2] == "/*"
|
|
163
|
+
|
|
164
|
+
line = @line
|
|
165
|
+
col = @col
|
|
166
|
+
close = @src.index("*/", @pos + 2)
|
|
167
|
+
raise_at(line, col, "Unterminated block comment") unless close
|
|
168
|
+
|
|
169
|
+
text = @src[@pos...(close + 2)]
|
|
170
|
+
advance(text)
|
|
171
|
+
text
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def take_arrow
|
|
175
|
+
return unless @src[@pos, 2] == "->"
|
|
176
|
+
|
|
177
|
+
emit(:arrow, "->", nil)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def take_number
|
|
181
|
+
text = match(NUMBER) or return nil
|
|
182
|
+
|
|
183
|
+
emit(:number, text, cast_number(text))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def take_identifier
|
|
187
|
+
text = match(IDENT) or return nil
|
|
188
|
+
|
|
189
|
+
segments = build_segments(text)
|
|
190
|
+
emit(:identifier, text, segments.map(&:name).join("."), segments)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def take_pipe
|
|
194
|
+
return unless @src[@pos] == "|"
|
|
195
|
+
|
|
196
|
+
emit(:pipe, "|", "|")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def take_punctuation
|
|
200
|
+
type = PUNCT[@src[@pos]] or return nil
|
|
201
|
+
|
|
202
|
+
emit(type, @src[@pos], @src[@pos])
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Consumes the whole literal and keeps the RAW inner lexeme: label text
|
|
206
|
+
# and property values decode differently upstream, so decoding belongs
|
|
207
|
+
# at the parse site.
|
|
208
|
+
def take_string
|
|
209
|
+
quote = @src[@pos]
|
|
210
|
+
return unless ['"', "'"].include?(quote)
|
|
211
|
+
|
|
212
|
+
line = @line
|
|
213
|
+
col = @col
|
|
214
|
+
inner = read_string_body(quote, line, col)
|
|
215
|
+
@tokens << Token.new(type: :string, value: inner, segments: nil,
|
|
216
|
+
line: line, column: col)
|
|
217
|
+
inner
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def read_string_body(quote, line, col)
|
|
221
|
+
advance(quote)
|
|
222
|
+
inner = +""
|
|
223
|
+
inner << take_string_char while @pos < @src.length &&
|
|
224
|
+
@src[@pos] != quote
|
|
225
|
+
raise_at(line, col, "Unterminated string") if @pos >= @src.length
|
|
226
|
+
|
|
227
|
+
advance(quote)
|
|
228
|
+
inner
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Raw newlines are legal inside an Xtext STRING, and this walks the
|
|
232
|
+
# body one character at a time -- so CRLF has to be consumed as a pair
|
|
233
|
+
# here too, or the two halves reach the counter separately and every
|
|
234
|
+
# later location is a line out.
|
|
235
|
+
def take_string_char
|
|
236
|
+
return advance_with("\r\n") if crlf?(@pos)
|
|
237
|
+
return take_escape if @src[@pos] == "\\"
|
|
238
|
+
|
|
239
|
+
advance_with(@src[@pos])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# The escape branch consumed the backslash and the CR together, which
|
|
243
|
+
# left the LF for the next advance and counted the pair as two lines.
|
|
244
|
+
def take_escape
|
|
245
|
+
nxt = @src[@pos + 1]
|
|
246
|
+
raise_at(@line, @col, "Unterminated escape") if nxt.nil?
|
|
247
|
+
return advance_with("\\\r\n") if crlf?(@pos + 1)
|
|
248
|
+
|
|
249
|
+
advance_with("\\#{nxt}")
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def crlf?(pos)
|
|
253
|
+
@src[pos] == "\r" && @src[pos + 1] == "\n"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def advance_with(text)
|
|
257
|
+
advance(text)
|
|
258
|
+
text
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def build_segments(text)
|
|
262
|
+
text.split(".").map do |segment|
|
|
263
|
+
if segment.start_with?("^")
|
|
264
|
+
Segment.new(name: segment[1..], escaped: true)
|
|
265
|
+
else
|
|
266
|
+
Segment.new(name: segment, escaped: false)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def cast_number(text)
|
|
272
|
+
text.match?(/[.eE]/) ? text.to_f : text.to_i
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def match(regexp)
|
|
276
|
+
regexp.match(@src, @pos)&.[](0)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def emit(type, text, value, segments = nil)
|
|
280
|
+
@tokens << Token.new(type: type, value: value, segments: segments,
|
|
281
|
+
line: @line, column: @col)
|
|
282
|
+
advance(text)
|
|
283
|
+
@tokens.last
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# CRLF, lone CR and lone LF each end one line. Counting only LF left
|
|
287
|
+
# every location wrong in the CR-delimited files the comment fix
|
|
288
|
+
# started accepting.
|
|
289
|
+
def advance(text)
|
|
290
|
+
parts = text.split(NEWLINE, -1)
|
|
291
|
+
if parts.length <= 1
|
|
292
|
+
@col += text.length
|
|
293
|
+
else
|
|
294
|
+
@line += parts.length - 1
|
|
295
|
+
@col = parts.last.length + 1
|
|
296
|
+
end
|
|
297
|
+
@pos += text.length
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def raise_at(line, col, message)
|
|
301
|
+
raise Elkrb::ParseError.new(
|
|
302
|
+
"#{message} at line #{line}, column #{col}",
|
|
303
|
+
line: line, column: col,
|
|
304
|
+
)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|