furud 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +101 -0
- data/lib/furud/engine.rb +756 -0
- data/lib/furud/format.rb +244 -0
- data/lib/furud/formula.rb +538 -0
- data/lib/furud/functions.rb +794 -0
- data/lib/furud/types.rb +70 -0
- data/lib/furud/version.rb +5 -0
- data/lib/furud.rb +15 -0
- data/sig/furud.rbs +128 -0
- metadata +57 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
|
|
5
|
+
module Furud
|
|
6
|
+
module Formula
|
|
7
|
+
Token = Data.define(:type, :value, :position)
|
|
8
|
+
COLUMNS = (1..16_384).to_h do |number|
|
|
9
|
+
value = number
|
|
10
|
+
name = +""
|
|
11
|
+
while value.positive?
|
|
12
|
+
value, remainder = (value - 1).divmod(26)
|
|
13
|
+
name.prepend((65 + remainder).chr)
|
|
14
|
+
end
|
|
15
|
+
[name, number]
|
|
16
|
+
end.freeze
|
|
17
|
+
COLUMN_NAMES = COLUMNS.invert.freeze
|
|
18
|
+
ERROR_CODES = Furud::ERROR_CODES.to_h { |code, text| [text.upcase, code] }.freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
def parse(source, origin: nil)
|
|
23
|
+
Parser.new(source.to_s, origin: coerce_reference(origin)).parse
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render(ast, origin: nil)
|
|
27
|
+
"=#{render_node(ast, coerce_reference(origin))}"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def references(ast)
|
|
31
|
+
result = []
|
|
32
|
+
collect = lambda do |node|
|
|
33
|
+
case node.type
|
|
34
|
+
when :reference, :qualified_reference
|
|
35
|
+
result << node.value
|
|
36
|
+
when :range
|
|
37
|
+
first, last = node.children.map(&:value)
|
|
38
|
+
result << Area.new(sheet: first.sheet || last.sheet,
|
|
39
|
+
top: first.row, left: first.column,
|
|
40
|
+
bottom: last.row, right: last.column)
|
|
41
|
+
else
|
|
42
|
+
node.children.each { |child| collect.call(child) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
collect.call(ast)
|
|
46
|
+
result.uniq
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def translate(ast, from:, to:)
|
|
50
|
+
from = coerce_reference(from)
|
|
51
|
+
to = coerce_reference(to)
|
|
52
|
+
transform(ast) do |node|
|
|
53
|
+
case node.type
|
|
54
|
+
when :range
|
|
55
|
+
children = node.children.map { |child| translate_reference(child, from, to) }
|
|
56
|
+
children.any? { |child| child.type == :error } ? ref_error_node : Node.new(type: :range, children: children)
|
|
57
|
+
when :reference, :qualified_reference
|
|
58
|
+
translate_reference(node, from, to)
|
|
59
|
+
else
|
|
60
|
+
node
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def adjust(ast, operation)
|
|
66
|
+
operation = normalize_adjustment(operation)
|
|
67
|
+
adjust_node(ast, operation)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def coerce_reference(reference)
|
|
71
|
+
case reference
|
|
72
|
+
when Reference then reference
|
|
73
|
+
when Hash
|
|
74
|
+
Reference.new(sheet: reference[:sheet] || reference["sheet"],
|
|
75
|
+
row: reference[:row] || reference["row"] || 1,
|
|
76
|
+
column: reference[:column] || reference["column"] || 1,
|
|
77
|
+
absolute_row: reference[:absolute_row] || reference["absolute_row"],
|
|
78
|
+
absolute_column: reference[:absolute_column] || reference["absolute_column"])
|
|
79
|
+
when nil then Reference.new(sheet: nil, row: 1, column: 1)
|
|
80
|
+
else raise ArgumentError, "origin must be a Furud::Reference, Hash, or nil"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def column_number(name)
|
|
85
|
+
COLUMNS[name.upcase]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def column_name(number)
|
|
89
|
+
COLUMN_NAMES.fetch(number) { raise ParseError, "column outside A:XFD" }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def render_reference(reference)
|
|
93
|
+
prefix = reference.sheet ? "#{render_sheet(reference.sheet)}!" : ""
|
|
94
|
+
row = reference.absolute_row ? "$#{reference.row}" : reference.row.to_s
|
|
95
|
+
column = reference.absolute_column ? "$#{column_name(reference.column)}" : column_name(reference.column)
|
|
96
|
+
"#{prefix}#{column}#{row}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def render_reference_for(reference, origin)
|
|
100
|
+
render_reference(reference.sheet == origin.sheet ? reference.with(sheet: nil) : reference)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def render_node(node, origin, parent_precedence = 0)
|
|
104
|
+
case node.type
|
|
105
|
+
when :literal then render_literal(node.value)
|
|
106
|
+
when :reference then render_reference_for(node.value, origin)
|
|
107
|
+
when :qualified_reference then render_reference(node.value)
|
|
108
|
+
when :range
|
|
109
|
+
first, last = node.children
|
|
110
|
+
if last.type == :reference && first.value.sheet == last.value.sheet
|
|
111
|
+
last = Node.new(type: :reference, value: last.value.with(sheet: nil))
|
|
112
|
+
end
|
|
113
|
+
"#{render_node(first, origin, 100)}:#{render_node(last, origin, 100)}"
|
|
114
|
+
when :name then node.value
|
|
115
|
+
when :error then node.value.to_s
|
|
116
|
+
when :array
|
|
117
|
+
"{" + node.children.map { |row| row.children.map { |child| render_node(child, origin) }.join(",") }.join(";") + "}"
|
|
118
|
+
when :call
|
|
119
|
+
"#{node.value}(#{node.children.map { |child| render_node(child, origin) }.join(",")})"
|
|
120
|
+
when :unary
|
|
121
|
+
op = node.value
|
|
122
|
+
child = render_node(node.children.first, origin, 60)
|
|
123
|
+
wrap("#{op}#{child}", 60, parent_precedence)
|
|
124
|
+
when :postfix
|
|
125
|
+
wrap("#{render_node(node.children.first, origin, 70)}%", 70, parent_precedence)
|
|
126
|
+
when :binary
|
|
127
|
+
precedence = PRECEDENCE.fetch(node.value)
|
|
128
|
+
left, right = node.children
|
|
129
|
+
right_precedence = precedence + (node.value == "^" ? 0 : 1)
|
|
130
|
+
wrap("#{render_node(left, origin, precedence)}#{node.value}#{render_node(right, origin, right_precedence)}",
|
|
131
|
+
precedence, parent_precedence)
|
|
132
|
+
else raise Error, "unknown formula node: #{node.type}"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
PRECEDENCE = { "=" => 10, "<>" => 10, "<" => 10, ">" => 10, "<=" => 10, ">=" => 10,
|
|
137
|
+
"&" => 20, "+" => 30, "-" => 30, "*" => 40, "/" => 40, "^" => 50 }.freeze
|
|
138
|
+
|
|
139
|
+
def visit(node, &block)
|
|
140
|
+
yield node
|
|
141
|
+
node.children.each { |child| visit(child, &block) }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def transform(node, &block)
|
|
145
|
+
replaced = block.call(node)
|
|
146
|
+
return replaced unless replaced.equal?(node)
|
|
147
|
+
|
|
148
|
+
children = node.children.map { |child| transform(child, &block) }
|
|
149
|
+
children == node.children ? node : Node.new(type: node.type, value: node.value, children: children)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def translate_reference(node, from, to)
|
|
153
|
+
reference = node.value
|
|
154
|
+
row = reference.absolute_row ? reference.row : to.row + reference.row - from.row
|
|
155
|
+
column = reference.absolute_column ? reference.column : to.column + reference.column - from.column
|
|
156
|
+
return ref_error_node if row < 1 || column < 1
|
|
157
|
+
|
|
158
|
+
sheet = node.type == :reference && reference.sheet == from.sheet ? to.sheet : reference.sheet
|
|
159
|
+
ref_node(reference.with(row: row, column: column, sheet: sheet), node.type)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def ref_error_node
|
|
163
|
+
Node.new(type: :error, value: ErrorValue.new(code: :ref))
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def adjust_node(node, operation)
|
|
167
|
+
case node.type
|
|
168
|
+
when :reference, :qualified_reference
|
|
169
|
+
adjust_reference(node.value, operation, node.type)
|
|
170
|
+
when :range
|
|
171
|
+
first_node, last_node = node.children
|
|
172
|
+
first, last = first_node.value, last_node.value
|
|
173
|
+
adjusted = adjust_range(first, last, operation)
|
|
174
|
+
adjusted.is_a?(ErrorValue) ? Node.new(type: :error, value: adjusted) :
|
|
175
|
+
Node.new(type: :range, value: nil, children: [ref_node(adjusted[0], first_node.type), ref_node(adjusted[1], last_node.type)])
|
|
176
|
+
else
|
|
177
|
+
children = node.children.map { |child| adjust_node(child, operation) }
|
|
178
|
+
children == node.children ? node : Node.new(type: node.type, value: node.value, children: children)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def adjust_reference(reference, operation, node_type)
|
|
183
|
+
axis = operation.type.to_s.end_with?("rows") ? :row : :column
|
|
184
|
+
kind = operation.type.to_s.start_with?("insert") ? :insert : :delete
|
|
185
|
+
return ref_node(reference, node_type) unless operation.sheet.nil? || reference.sheet == operation.sheet
|
|
186
|
+
|
|
187
|
+
coordinate = reference.public_send(axis)
|
|
188
|
+
if kind == :insert
|
|
189
|
+
coordinate += operation.count if coordinate >= operation.at
|
|
190
|
+
elsif coordinate >= operation.at && coordinate < operation.at + operation.count
|
|
191
|
+
return Node.new(type: :error, value: ErrorValue.new(code: :ref))
|
|
192
|
+
elsif coordinate >= operation.at + operation.count
|
|
193
|
+
coordinate -= operation.count
|
|
194
|
+
end
|
|
195
|
+
ref_node(reference.with(axis => coordinate), node_type)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def adjust_range(first, last, operation)
|
|
199
|
+
axis = operation.type.to_s.end_with?("rows") ? :row : :column
|
|
200
|
+
kind = operation.type.to_s.start_with?("insert") ? :insert : :delete
|
|
201
|
+
sheet = first.sheet || last.sheet
|
|
202
|
+
return [first, last] unless operation.sheet.nil? || sheet == operation.sheet
|
|
203
|
+
|
|
204
|
+
start = [first.public_send(axis), last.public_send(axis)].min
|
|
205
|
+
finish = [first.public_send(axis), last.public_send(axis)].max
|
|
206
|
+
if kind == :insert
|
|
207
|
+
if operation.at <= start
|
|
208
|
+
start += operation.count
|
|
209
|
+
finish += operation.count
|
|
210
|
+
elsif operation.at <= finish
|
|
211
|
+
finish += operation.count
|
|
212
|
+
end
|
|
213
|
+
else
|
|
214
|
+
deleted_end = operation.at + operation.count - 1
|
|
215
|
+
overlap = [finish, deleted_end].min - [start, operation.at].max + 1
|
|
216
|
+
if overlap >= finish - start + 1
|
|
217
|
+
return ErrorValue.new(code: :ref)
|
|
218
|
+
elsif overlap.positive?
|
|
219
|
+
finish -= overlap
|
|
220
|
+
start = operation.at if start >= operation.at
|
|
221
|
+
elsif start > deleted_end
|
|
222
|
+
start -= operation.count
|
|
223
|
+
finish -= operation.count
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
first_value = first.public_send(axis)
|
|
227
|
+
last_value = last.public_send(axis)
|
|
228
|
+
reversed = first_value > last_value
|
|
229
|
+
if axis == :row
|
|
230
|
+
new_first = first.with(row: reversed ? finish : start)
|
|
231
|
+
new_last = last.with(row: reversed ? start : finish)
|
|
232
|
+
else
|
|
233
|
+
new_first = first.with(column: reversed ? finish : start)
|
|
234
|
+
new_last = last.with(column: reversed ? start : finish)
|
|
235
|
+
end
|
|
236
|
+
[new_first, new_last]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def normalize_adjustment(operation)
|
|
240
|
+
return operation if operation.is_a?(Adjustment)
|
|
241
|
+
|
|
242
|
+
values = operation.transform_keys(&:to_sym)
|
|
243
|
+
Adjustment.new(type: values.fetch(:type), sheet: values[:sheet],
|
|
244
|
+
at: values.fetch(:at), count: values.fetch(:count, 1))
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def ref_node(reference, node_type = :reference)
|
|
248
|
+
return Node.new(type: :error, value: ErrorValue.new(code: :ref)) unless reference.row.between?(1, 1_048_576) && reference.column.between?(1, 16_384)
|
|
249
|
+
|
|
250
|
+
Node.new(type: node_type, value: reference)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def render_literal(value)
|
|
254
|
+
case value
|
|
255
|
+
when String then "\"#{value.gsub('"', '""')}\""
|
|
256
|
+
when true then "TRUE"
|
|
257
|
+
when false then "FALSE"
|
|
258
|
+
else value.to_s
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def render_sheet(sheet)
|
|
263
|
+
return sheet if sheet.match?(/\A[A-Za-z_][A-Za-z0-9_.]*\z/)
|
|
264
|
+
|
|
265
|
+
"'#{sheet.gsub("'", "''")}'"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def wrap(text, precedence, parent_precedence)
|
|
269
|
+
precedence < parent_precedence ? "(#{text})" : text
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
class Lexer
|
|
273
|
+
def initialize(source, origin)
|
|
274
|
+
@source = source
|
|
275
|
+
@origin = origin
|
|
276
|
+
@scanner = StringScanner.new(source)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def tokens
|
|
280
|
+
result = []
|
|
281
|
+
until @scanner.eos?
|
|
282
|
+
@scanner.skip(/\s+/)
|
|
283
|
+
break if @scanner.eos?
|
|
284
|
+
|
|
285
|
+
position = @scanner.pos
|
|
286
|
+
result << Token.new(*next_token(position))
|
|
287
|
+
end
|
|
288
|
+
result << Token.new(:eof, nil, @scanner.pos)
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
def next_token(position)
|
|
294
|
+
if (raw = @scanner.scan(/'(?:[^']|'')+'!\$?[A-Za-z]{1,3}\$?\d+/))
|
|
295
|
+
sheet, address = raw.split("!", 2)
|
|
296
|
+
return [:qualified_reference, parse_a1(address, sheet[1...-1].gsub("''", "'")), position]
|
|
297
|
+
end
|
|
298
|
+
if (raw = @scanner.scan(/[A-Za-z_][A-Za-z0-9_.]*!\$?[A-Za-z]{1,3}\$?\d+/))
|
|
299
|
+
sheet, address = raw.split("!", 2)
|
|
300
|
+
return [:qualified_reference, parse_a1(address, sheet), position]
|
|
301
|
+
end
|
|
302
|
+
if (raw = @scanner.scan(/R(?:\d+|\[-?\d+\])?C(?:\d+|\[-?\d+\])?/i))
|
|
303
|
+
return [:reference, parse_r1c1(raw), position]
|
|
304
|
+
end
|
|
305
|
+
if (raw = @scanner.scan(/\$?[A-Za-z]{1,3}\$?\d+/))
|
|
306
|
+
if @scanner.rest.match?(/\A\s*\(/)
|
|
307
|
+
return [:identifier, raw, position]
|
|
308
|
+
end
|
|
309
|
+
return [:reference, parse_a1(raw, nil), position]
|
|
310
|
+
end
|
|
311
|
+
if (raw = @scanner.scan(/#(?:DIV\/0!|VALUE!|REF!|NAME\?|N\/A|NUM!|CYCLE!|SPILL!|CALC!)/i))
|
|
312
|
+
code = ERROR_CODES[raw.upcase]
|
|
313
|
+
return [:error, ErrorValue.new(code: code), position]
|
|
314
|
+
end
|
|
315
|
+
if @scanner.scan(/"/)
|
|
316
|
+
value = +""
|
|
317
|
+
until @scanner.eos?
|
|
318
|
+
part = @scanner.scan(/[^\"]+/)
|
|
319
|
+
value << part if part
|
|
320
|
+
break unless @scanner.scan(/"/)
|
|
321
|
+
if @scanner.scan(/"/)
|
|
322
|
+
value << '"'
|
|
323
|
+
else
|
|
324
|
+
return [:string, value, position]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
raise ParseError, "unterminated string at #{position}"
|
|
328
|
+
end
|
|
329
|
+
if (raw = @scanner.scan(/(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?/))
|
|
330
|
+
value = raw.match?(/[.Ee]/) ? Float(raw) : Integer(raw)
|
|
331
|
+
return [:number, value, position]
|
|
332
|
+
end
|
|
333
|
+
if (operator = @scanner.scan(/<=|>=|<>|[+\-*\/^&%=<>:(),;{}]/))
|
|
334
|
+
type = { "(" => :lparen, ")" => :rparen, "," => :separator, ";" => :row_separator,
|
|
335
|
+
"{" => :lbrace, "}" => :rbrace, ":" => :colon }[operator] || :operator
|
|
336
|
+
return [type, operator, position]
|
|
337
|
+
end
|
|
338
|
+
if (identifier = @scanner.scan(/[A-Za-z_\\][A-Za-z0-9_.\\]*/))
|
|
339
|
+
value = identifier.upcase
|
|
340
|
+
return [:boolean, value == "TRUE", position] if %w[TRUE FALSE].include?(value)
|
|
341
|
+
|
|
342
|
+
return [:identifier, identifier, position]
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
raise ParseError, "unexpected character #{@scanner.peek(1).inspect} at #{position}"
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def parse_a1(raw, sheet)
|
|
349
|
+
match = /\A(\$?)([A-Za-z]{1,3})(\$?)(\d+)\z/.match(raw)
|
|
350
|
+
column = Formula.column_number(match[2])
|
|
351
|
+
row = Integer(match[4])
|
|
352
|
+
raise ParseError, "cell reference outside A1:XFD1048576: #{raw}" unless column && row.between?(1, 1_048_576)
|
|
353
|
+
|
|
354
|
+
Reference.new(sheet: sheet, row: row, column: column,
|
|
355
|
+
absolute_row: !match[3].empty?, absolute_column: !match[1].empty?)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def parse_r1c1(raw)
|
|
359
|
+
match = /\AR(?:(\d+)|\[(-?\d+)\])?C(?:(\d+)|\[(-?\d+)\])?\z/i.match(raw)
|
|
360
|
+
row = match[1] ? Integer(match[1]) : @origin.row + (match[2] ? Integer(match[2]) : 0)
|
|
361
|
+
column = match[3] ? Integer(match[3]) : @origin.column + (match[4] ? Integer(match[4]) : 0)
|
|
362
|
+
Reference.new(sheet: @origin.sheet, row: row, column: column,
|
|
363
|
+
absolute_row: !match[1].nil?, absolute_column: !match[3].nil?)
|
|
364
|
+
rescue ArgumentError
|
|
365
|
+
raise ParseError, "invalid R1C1 reference: #{raw}"
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
class Parser
|
|
370
|
+
def initialize(source, origin:)
|
|
371
|
+
@origin = origin
|
|
372
|
+
source = source.sub(/\A\s*=/, "")
|
|
373
|
+
@tokens = Lexer.new(source, origin).tokens
|
|
374
|
+
@index = 0
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def parse
|
|
378
|
+
raise ParseError, "formula is empty" if current.type == :eof
|
|
379
|
+
|
|
380
|
+
node = comparison
|
|
381
|
+
raise ParseError, "unexpected token #{current.value.inspect} at #{current.position}" unless current.type == :eof
|
|
382
|
+
|
|
383
|
+
qualify_sheets(node, @origin.sheet)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
private
|
|
387
|
+
|
|
388
|
+
def comparison
|
|
389
|
+
binary_chain(:concat, %w[= <> < > <= >=])
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def concat
|
|
393
|
+
binary_chain(:additive, ["&"])
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def additive
|
|
397
|
+
binary_chain(:multiplicative, %w[+ -])
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def multiplicative
|
|
401
|
+
binary_chain(:unary, %w[* /])
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def unary
|
|
405
|
+
if current.type == :operator && %w[+ -].include?(current.value)
|
|
406
|
+
op = advance.value
|
|
407
|
+
return Node.new(type: :unary, value: op, children: [unary])
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
exponent
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def exponent
|
|
414
|
+
node = postfix
|
|
415
|
+
if accept_operator("^")
|
|
416
|
+
node = Node.new(type: :binary, value: "^", children: [node, unary])
|
|
417
|
+
end
|
|
418
|
+
node
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def postfix
|
|
422
|
+
node = primary
|
|
423
|
+
node = Node.new(type: :range, value: nil, children: [node, reference_primary]) if current.type == :colon && advance
|
|
424
|
+
node = Node.new(type: :postfix, value: "%", children: [node]) while accept_operator("%")
|
|
425
|
+
node
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def primary
|
|
429
|
+
token = advance
|
|
430
|
+
case token.type
|
|
431
|
+
when :number then Node.new(type: :literal, value: token.value)
|
|
432
|
+
when :string then Node.new(type: :literal, value: token.value)
|
|
433
|
+
when :boolean then Node.new(type: :literal, value: token.value)
|
|
434
|
+
when :error then Node.new(type: :error, value: token.value)
|
|
435
|
+
when :reference then Node.new(type: :reference, value: token.value)
|
|
436
|
+
when :qualified_reference then Node.new(type: :qualified_reference, value: token.value)
|
|
437
|
+
when :identifier then identifier(token)
|
|
438
|
+
when :lparen
|
|
439
|
+
node = comparison
|
|
440
|
+
expect(:rparen)
|
|
441
|
+
node
|
|
442
|
+
when :lbrace then array_literal
|
|
443
|
+
else raise ParseError, "expected a value at #{token.position}"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def reference_primary
|
|
448
|
+
token = advance
|
|
449
|
+
raise ParseError, "range endpoint must be a cell reference at #{token.position}" unless %i[reference qualified_reference].include?(token.type)
|
|
450
|
+
|
|
451
|
+
Node.new(type: token.type, value: token.value)
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def qualify_sheets(node, default_sheet)
|
|
455
|
+
if %i[reference qualified_reference].include?(node.type)
|
|
456
|
+
reference = node.value
|
|
457
|
+
return node if node.type == :qualified_reference || reference.sheet
|
|
458
|
+
return Node.new(type: :reference, value: reference.with(sheet: default_sheet))
|
|
459
|
+
end
|
|
460
|
+
if node.type == :range
|
|
461
|
+
first_node, last_node = node.children
|
|
462
|
+
first, last = first_node.value, last_node.value
|
|
463
|
+
first_sheet = first.sheet || default_sheet
|
|
464
|
+
last_sheet = last.sheet || (first_node.type == :qualified_reference ? first_sheet : default_sheet)
|
|
465
|
+
return Node.new(type: :range, value: nil, children: [
|
|
466
|
+
Node.new(type: first_node.type, value: first.with(sheet: first_sheet)),
|
|
467
|
+
Node.new(type: last_node.type, value: last.with(sheet: last_sheet))
|
|
468
|
+
])
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
children = node.children.map { |child| qualify_sheets(child, default_sheet) }
|
|
472
|
+
children == node.children ? node : Node.new(type: node.type, value: node.value, children: children)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def identifier(token)
|
|
476
|
+
if current.type == :lparen
|
|
477
|
+
advance
|
|
478
|
+
args = []
|
|
479
|
+
unless current.type == :rparen
|
|
480
|
+
loop do
|
|
481
|
+
args << comparison
|
|
482
|
+
break unless current.type == :separator
|
|
483
|
+
|
|
484
|
+
advance
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
expect(:rparen)
|
|
488
|
+
Node.new(type: :call, value: token.value.delete_prefix("_xlfn.").upcase, children: args)
|
|
489
|
+
else
|
|
490
|
+
Node.new(type: :name, value: token.value)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def array_literal
|
|
495
|
+
rows = [[]]
|
|
496
|
+
until current.type == :rbrace
|
|
497
|
+
rows.last << comparison
|
|
498
|
+
case current.type
|
|
499
|
+
when :separator then advance
|
|
500
|
+
when :row_separator then advance; rows << []
|
|
501
|
+
when :rbrace then break
|
|
502
|
+
else raise ParseError, "expected array separator at #{current.position}"
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
expect(:rbrace)
|
|
506
|
+
raise ParseError, "empty array" if rows.any?(&:empty?)
|
|
507
|
+
raise ParseError, "array rows must have equal length" unless rows.map(&:length).uniq.one?
|
|
508
|
+
|
|
509
|
+
Node.new(type: :array, children: rows.map { |row| Node.new(type: :row, children: row) })
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def binary_chain(next_method, operators)
|
|
513
|
+
node = send(next_method)
|
|
514
|
+
while current.type == :operator && operators.include?(current.value)
|
|
515
|
+
operator = advance.value
|
|
516
|
+
node = Node.new(type: :binary, value: operator, children: [node, send(next_method)])
|
|
517
|
+
end
|
|
518
|
+
node
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def accept_operator(value)
|
|
522
|
+
return false unless current.type == :operator && current.value == value
|
|
523
|
+
|
|
524
|
+
advance
|
|
525
|
+
true
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def expect(type)
|
|
529
|
+
return advance if current.type == type
|
|
530
|
+
|
|
531
|
+
raise ParseError, "expected #{type}, got #{current.type} at #{current.position}"
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
def current = @tokens[@index]
|
|
535
|
+
def advance = @tokens[@index].tap { @index += 1 }
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|