csv_plus_plus 0.1.2 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -2
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  5. data/lib/csv_plus_plus/cell.rb +46 -24
  6. data/lib/csv_plus_plus/cli.rb +23 -13
  7. data/lib/csv_plus_plus/cli_flag.rb +1 -2
  8. data/lib/csv_plus_plus/color.rb +32 -7
  9. data/lib/csv_plus_plus/compiler.rb +82 -60
  10. data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
  11. data/lib/csv_plus_plus/entities/boolean.rb +18 -9
  12. data/lib/csv_plus_plus/entities/builtins.rb +23 -9
  13. data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
  14. data/lib/csv_plus_plus/entities/date.rb +38 -5
  15. data/lib/csv_plus_plus/entities/entity.rb +27 -61
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
  17. data/lib/csv_plus_plus/entities/function.rb +23 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +24 -9
  19. data/lib/csv_plus_plus/entities/number.rb +24 -10
  20. data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
  21. data/lib/csv_plus_plus/entities/string.rb +19 -6
  22. data/lib/csv_plus_plus/entities/variable.rb +16 -4
  23. data/lib/csv_plus_plus/entities.rb +20 -13
  24. data/lib/csv_plus_plus/error/error.rb +11 -1
  25. data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
  26. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
  27. data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
  28. data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
  29. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  30. data/lib/csv_plus_plus/error.rb +1 -0
  31. data/lib/csv_plus_plus/google_api_client.rb +7 -2
  32. data/lib/csv_plus_plus/google_options.rb +23 -18
  33. data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
  34. data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
  35. data/lib/csv_plus_plus/lexer.rb +24 -0
  36. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  37. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  38. data/lib/csv_plus_plus/modifier/expand.rb +61 -0
  39. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  40. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  41. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  42. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  43. data/lib/csv_plus_plus/modifier.rb +82 -158
  44. data/lib/csv_plus_plus/options.rb +64 -19
  45. data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
  46. data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
  47. data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
  48. data/lib/csv_plus_plus/row.rb +53 -12
  49. data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
  50. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
  51. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  52. data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
  53. data/lib/csv_plus_plus/runtime/references.rb +110 -0
  54. data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
  55. data/lib/csv_plus_plus/runtime.rb +34 -191
  56. data/lib/csv_plus_plus/source_code.rb +66 -0
  57. data/lib/csv_plus_plus/template.rb +62 -35
  58. data/lib/csv_plus_plus/version.rb +2 -1
  59. data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
  60. data/lib/csv_plus_plus/writer/csv.rb +11 -9
  61. data/lib/csv_plus_plus/writer/excel.rb +9 -2
  62. data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
  63. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
  64. data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
  65. data/lib/csv_plus_plus/writer/open_document.rb +6 -1
  66. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
  67. data/lib/csv_plus_plus/writer.rb +39 -9
  68. data/lib/csv_plus_plus.rb +29 -12
  69. metadata +18 -14
  70. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  71. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  72. data/lib/csv_plus_plus/data_validation.rb +0 -138
  73. data/lib/csv_plus_plus/expand.rb +0 -20
  74. data/lib/csv_plus_plus/graph.rb +0 -62
  75. data/lib/csv_plus_plus/references.rb +0 -68
  76. data/lib/csv_plus_plus/scope.rb +0 -196
  77. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  78. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  79. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,59 +1,69 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative 'benchmarked_compiler'
4
- require_relative 'entities'
5
- require_relative 'parser/code_section.tab'
6
- require_relative 'runtime'
7
- require_relative 'scope'
8
-
9
4
  module CSVPlusPlus
10
5
  # Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+). Variable resolution is delegated
11
6
  # to the +Scope+
12
7
  #
13
8
  # @attr_reader options [Options] The +Options+ to compile with
14
9
  # @attr_reader runtime [Runtime] The runtime execution
15
- # @attr_reader scope [Scope] +Scope+ for variable resolution
10
+ # rubocop:disable Metrics/ClassLength
16
11
  class Compiler
17
- attr_reader :options, :runtime, :scope
12
+ extend ::T::Sig
13
+
14
+ sig { returns(::CSVPlusPlus::Options) }
15
+ attr_reader :options
16
+
17
+ sig { returns(::CSVPlusPlus::Runtime::Runtime) }
18
+ attr_reader :runtime
18
19
 
20
+ sig do
21
+ params(
22
+ options: ::CSVPlusPlus::Options,
23
+ runtime: ::CSVPlusPlus::Runtime::Runtime,
24
+ block: ::T.proc.params(arg0: ::CSVPlusPlus::Compiler).void
25
+ ).void
26
+ end
19
27
  # Create a compiler and make sure it gets cleaned up
20
28
  #
21
- # @param runtime [Runtime] The initial +Runtime+ for the compiler
22
29
  # @param options [Options]
23
- def self.with_compiler(runtime:, options:, &block)
24
- compiler = new(options:, runtime:)
30
+ # @param runtime [Runtime] The initial +Runtime+ for the compiler
31
+ def self.with_compiler(options:, runtime:, &block)
25
32
  if options.verbose
26
- ::CSVPlusPlus::BenchmarkedCompiler.with_benchmarks(compiler) do |c|
33
+ ::CSVPlusPlus::BenchmarkedCompiler.with_benchmarks(options:, runtime:) do |c|
27
34
  block.call(c)
28
35
  end
29
36
  else
30
- yield(compiler)
37
+ block.call(new(options:, runtime:))
31
38
  end
32
39
  ensure
33
40
  runtime.cleanup!
34
41
  end
35
42
 
36
- # @param runtime [Runtime]
43
+ sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
37
44
  # @param options [Options]
38
- # @param scope [Scope, nil]
39
- def initialize(runtime:, options:, scope: nil)
45
+ # @param runtime [Runtime]
46
+ def initialize(options:, runtime:)
40
47
  @options = options
41
48
  @runtime = runtime
42
- @scope = scope || ::CSVPlusPlus::Scope.new(runtime:)
43
49
 
44
50
  # TODO: infer a type
45
51
  # allow user-supplied key/values to override anything global or from the code section
46
- @scope.def_variables(
52
+ @runtime.def_variables(
47
53
  options.key_values.transform_values { |v| ::CSVPlusPlus::Entities::String.new(v.to_s) }
48
54
  )
49
55
  end
50
56
 
51
- # Write the compiled results
52
- def outputting!
53
- @runtime.start_at_csv!
54
- yield
57
+ sig { params(benchmark: ::Benchmark::Report).void }
58
+ # Attach a +Benchmark+ and a place to store timings to the compiler class.
59
+ #
60
+ # @param benchmark [Benchmark] A +Benchmark+ instance
61
+ def benchmark=(benchmark)
62
+ @benchmark = ::T.let(benchmark, ::T.nilable(::Benchmark::Report))
63
+ @timings = ::T.let([], ::T.nilable(::T::Array[::Benchmark::Tms]))
55
64
  end
56
65
 
66
+ sig { returns(::CSVPlusPlus::Template) }
57
67
  # Compile a template and return a +::CSVPlusPlus::Template+ instance ready to be written with a +Writer+
58
68
  #
59
69
  # @return [Template]
@@ -61,96 +71,108 @@ module CSVPlusPlus
61
71
  parse_code_section!
62
72
  rows = parse_csv_section!
63
73
 
64
- ::CSVPlusPlus::Template.new(rows:, scope: @scope).tap do |t|
74
+ ::CSVPlusPlus::Template.new(rows:, runtime: @runtime).tap do |t|
65
75
  t.validate_infinite_expands(@runtime)
66
- expanding { t.expand_rows! }
76
+ expanding! { t.expand_rows! }
77
+ bind_all_vars! { t.bind_all_vars!(@runtime) }
67
78
  resolve_all_cells!(t)
68
79
  end
69
80
  end
70
81
 
71
- # @return [String]
72
- def to_s
73
- "Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
82
+ sig { params(block: ::T.proc.params(runtime: ::CSVPlusPlus::Runtime::Runtime).void).void }
83
+ # Write the compiled results
84
+ def outputting!(&block)
85
+ @runtime.start_at_csv! { block.call(@runtime) }
74
86
  end
75
87
 
76
88
  protected
77
89
 
78
- # Parses the input file and returns a +CodeSection+
79
- #
80
- # @return [CodeSection]
90
+ sig { void }
91
+ # Parses the input file and sets variables on +@runtime+ as necessary
81
92
  def parse_code_section!
82
- @runtime.start!
83
-
84
- # TODO: this flow can probably be refactored, it used to have more needs back when we had to
85
- # parse and save the code_section
86
- parsing_code_section do |input|
87
- csv_section = ::CSVPlusPlus::Parser::CodeSection.new(@scope).parse(input, @runtime)
88
- # TODO: call scope.resolve_static_variables?? or maybe it doesn't matter
89
-
90
- # return the csv_section to the caller because they're gonna re-write input with it
91
- next csv_section
93
+ @runtime.start! do
94
+ # TODO: this flow can probably be refactored, it used to have more needs back when we had to
95
+ # parse and save the code_section
96
+ parsing_code_section do |input|
97
+ csv_section = ::CSVPlusPlus::Parser::CodeSection.new.parse(input, @runtime)
98
+
99
+ # return the csv_section to the caller because they're gonna re-write input with it
100
+ next csv_section
101
+ end
92
102
  end
93
- # @scope.code_section
94
103
  end
95
104
 
105
+ sig { returns(::T::Array[::CSVPlusPlus::Row]) }
96
106
  # Parse the CSV section and return an array of +Row+s
97
107
  #
98
108
  # @return [Array<Row>]
99
109
  def parse_csv_section!
100
- @runtime.start_at_csv!
101
- @runtime.map_rows(::CSV.new(runtime.input)) do |csv_row|
102
- parse_row(csv_row)
110
+ @runtime.start_at_csv! do
111
+ @runtime.map_lines(::CSV.new(::T.unsafe(@runtime.input))) do |csv_row|
112
+ parse_row(::T.cast(csv_row, ::T::Array[::String]))
113
+ end
103
114
  end
104
115
  ensure
105
116
  # we're done with the file and everything is in memory
106
117
  @runtime.cleanup!
107
118
  end
108
119
 
120
+ sig { params(template: ::CSVPlusPlus::Template).returns(::T::Array[::T::Array[::CSVPlusPlus::Entities::Entity]]) }
109
121
  # Iterates through each cell of each row and resolves it's variable and function references.
110
122
  #
111
123
  # @param template [Template]
124
+ #
112
125
  # @return [Array<Entity>]
113
126
  def resolve_all_cells!(template)
114
- @runtime.start_at_csv!
115
- @runtime.map_rows(template.rows, cells_too: true) do |cell|
116
- cell.ast = @scope.resolve_cell_value if cell.ast
127
+ @runtime.start_at_csv! do
128
+ @runtime.map_all_cells(template.rows) do |cell|
129
+ cell.ast = @runtime.resolve_cell_value if cell.ast
130
+ end
117
131
  end
118
132
  end
119
133
 
134
+ sig { params(block: ::T.proc.void).void }
120
135
  # Expanding rows
121
- def expanding
122
- @runtime.start_at_csv!
123
- yield
136
+ def expanding!(&block)
137
+ @runtime.start_at_csv! { block.call }
138
+ end
139
+
140
+ sig { params(block: ::T.proc.void).void }
141
+ # Binding all [[var=]] directives
142
+ def bind_all_vars!(&block)
143
+ @runtime.start_at_csv! { block.call }
124
144
  end
125
145
 
126
146
  private
127
147
 
128
- def parsing_code_section
129
- csv_section = yield(@runtime.input.read)
148
+ sig { params(block: ::T.proc.params(arg0: ::String).returns(::String)).void }
149
+ def parsing_code_section(&block)
150
+ csv_section = block.call(::T.must(::T.must(@runtime.input).read))
130
151
  @runtime.rewrite_input!(csv_section)
131
152
  end
132
153
 
154
+ sig { params(csv_row: ::T::Array[::T.nilable(::String)]).returns(::CSVPlusPlus::Row) }
133
155
  # Using the current +@runtime+ and the given +csv_row+ parse it into a +Row+ of +Cell+s
134
156
  # +csv_row+ should have already been run through a CSV parser and is an array of strings
135
157
  #
136
158
  # @param csv_row [Array<Array<String>>]
159
+ #
137
160
  # @return [Row]
138
161
  def parse_row(csv_row)
139
- row_modifier = ::CSVPlusPlus::ValidatedModifier.new(row_level: true)
162
+ row_modifier = ::CSVPlusPlus::Modifier.new(@options, row_level: true)
140
163
 
141
- cells = @runtime.map_row(csv_row) { |value, _cell_index| parse_cell(value, row_modifier) }
164
+ cells = @runtime.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
142
165
 
143
- ::CSVPlusPlus::Row.new(@runtime.row_index, cells, row_modifier)
166
+ ::CSVPlusPlus::Row.new(cells:, index: @runtime.row_index, modifier: row_modifier)
144
167
  end
145
168
 
169
+ sig { params(value: ::String, row_modifier: ::CSVPlusPlus::Modifier::Modifier).returns(::CSVPlusPlus::Cell) }
146
170
  def parse_cell(value, row_modifier)
147
- cell_modifier = ::CSVPlusPlus::ValidatedModifier.new
148
- parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:, scope: @scope).parse(
149
- value,
150
- @runtime
151
- )
171
+ cell_modifier = ::CSVPlusPlus::Modifier.new(@options)
172
+ parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value, @runtime)
152
173
 
153
174
  ::CSVPlusPlus::Cell.parse(parsed_value, runtime:, modifier: cell_modifier)
154
175
  end
155
176
  end
177
+ # rubocop:enable Metrics/ClassLength
156
178
  end
@@ -1,65 +1,49 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
4
5
  module Entities
5
6
  # Some helpful functions that can be mixed into a class to help building ASTs
6
7
  module ASTBuilder
8
+ extend ::T::Sig
9
+
10
+ sig do
11
+ params(
12
+ method_name: ::Symbol,
13
+ args: ::T.untyped,
14
+ kwargs: ::T.untyped,
15
+ block: ::T.untyped
16
+ ).returns(::CSVPlusPlus::Entities::Entity)
17
+ end
7
18
  # Let the current class have functions which can build a given entity by calling it's type. For example
8
19
  # +number(1)+, +variable(:foo)+
9
20
  #
10
21
  # @param method_name [Symbol] The +method_name+ to respond to
11
- # @param arguments [] The arguments to create the entity with
22
+ # @param args [Array] The arguments to create the entity with
23
+ # @param kwargs [Hash] The arguments to create the entity with
12
24
  #
13
25
  # @return [Entity, #super]
14
- def method_missing(method_name, *args, **kwargs, &)
15
- entity_class = ::CSVPlusPlus::Entities::TYPES[method_name.to_sym]
16
- return super unless entity_class
17
-
18
- entity_class.new(*args, **kwargs, &)
26
+ # rubocop:disable Naming/BlockForwarding
27
+ def method_missing(method_name, *args, **kwargs, &block)
28
+ entity_class_name = method_name.to_s.split('_').map(&:capitalize).join.to_sym
29
+ ::CSVPlusPlus::Entities.const_get(entity_class_name).new(*args, **kwargs, &block)
30
+ rescue ::NameError
31
+ super
19
32
  end
33
+ # rubocop:enable Naming/BlockForwarding
20
34
 
35
+ sig { params(method_name: ::Symbol, _args: ::T.untyped).returns(::T::Boolean) }
21
36
  # Let the current class have functions which can build a given entity by calling it's type. For example
22
37
  # +number(1)+, +variable(:foo)+
23
38
  #
24
39
  # @param method_name [Symbol] The +method_name+ to respond to
25
- # @param arguments [] The arguments to create the entity with
40
+ # @param _args [::T.Untyped] The arguments to create the entity with
26
41
  #
27
- # @return [Boolean, #super]
28
- def respond_to_missing?(method_name, *_arguments)
29
- ::CSVPlusPlus::Entities::TYPES.include?(method_name.to_sym) || super
30
- end
31
-
32
- # Turns index-based/X,Y coordinates into a A1 format
33
- #
34
- # @param row_index [Integer]
35
- # @param cell_index [Integer]
36
- #
37
- # @return [String]
38
- def ref(row_index: nil, cell_index: nil)
39
- return unless row_index || cell_index
40
-
41
- rowref = row_index ? (row_index + 1).to_s : ''
42
- cellref = cell_index ? cell_ref(cell_index) : ''
43
- cell_reference([cellref, rowref].join)
44
- end
45
-
46
- private
47
-
48
- ALPHA = ('A'..'Z').to_a.freeze
49
- private_constant :ALPHA
50
-
51
- def cell_ref(cell_index)
52
- c = cell_index.dup
53
- ref = ''
54
-
55
- while c >= 0
56
- # rubocop:disable Lint/ConstantResolution
57
- ref += ALPHA[c % 26]
58
- # rubocop:enable Lint/ConstantResolution
59
- c = (c / 26).floor - 1
60
- end
61
-
62
- ref.reverse
42
+ # @return [::T::Boolean, #super]
43
+ def respond_to_missing?(method_name, *_args)
44
+ !::CSVPlusPlus::Entities::Type.deserialize(method_name.to_s.gsub('_', '')).nil?
45
+ rescue ::KeyError
46
+ super
63
47
  end
64
48
  end
65
49
  end
@@ -1,30 +1,39 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
- require_relative './entity'
4
-
5
4
  module CSVPlusPlus
6
5
  module Entities
7
6
  # A boolean value
8
7
  #
9
8
  # @attr_reader value [true, false]
10
9
  class Boolean < Entity
10
+ sig { returns(::T::Boolean) }
11
11
  attr_reader :value
12
12
 
13
- # @param value [String, Boolean]
13
+ sig { params(value: ::T.any(::String, ::T::Boolean)).void }
14
+ # @param value [::String, boolean]
14
15
  def initialize(value)
15
- super(:boolean)
16
+ super(::CSVPlusPlus::Entities::Type::Boolean)
16
17
  # TODO: probably can do a lot better in general on type validation
17
- @value = value.is_a?(::String) ? (value.downcase == 'true') : value
18
+ @value = ::T.let(value.is_a?(::String) ? (value.downcase == 'true') : value, ::T::Boolean)
18
19
  end
19
20
 
20
- # @return [String]
21
- def to_s
21
+ sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
22
+ # @param _runtime [Runtime]
23
+ #
24
+ # @return [::String]
25
+ def evaluate(_runtime)
22
26
  @value.to_s.upcase
23
27
  end
24
28
 
25
- # @return [boolean]
29
+ sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
30
+ # @param other [Entity]
31
+ #
32
+ # @return [::T::Boolean]
26
33
  def ==(other)
27
- super && value == other.value
34
+ return false unless super
35
+
36
+ other.is_a?(self.class) && value == other.value
28
37
  end
29
38
  end
30
39
  end
@@ -1,3 +1,4 @@
1
+ # typed: false
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module CSVPlusPlus
@@ -8,35 +9,48 @@ module CSVPlusPlus
8
9
 
9
10
  VARIABLES = {
10
11
  # The number (integer) of the current cell. Starts at 1
11
- cellnum: runtime_value(->(runtime) { number(runtime.cell_index + 1) }),
12
+ cellnum: runtime_value(->(r) { number(r.cell_index + 1) }),
12
13
 
13
14
  # A reference to the current cell
14
- cellref: runtime_value(->(runtime) { ref(row_index: runtime.row_index, cell_index: runtime.cell_index) }),
15
+ cellref: runtime_value(->(r) { cell_reference(row_index: r.row_index, cell_index: r.cell_index) }),
15
16
 
16
17
  # A reference to the row above
17
- rowabove: runtime_value(->(runtime) { ref(row_index: [0, (runtime.row_index - 1)].max) }),
18
+ rowabove: runtime_value(->(r) { cell_reference(row_index: [0, (r.row_index - 1)].max) }),
18
19
 
19
20
  # A reference to the row below
20
- rowbelow: runtime_value(->(runtime) { ref(row_index: runtime.row_index + 1) }),
21
+ rowbelow: runtime_value(->(r) { cell_reference(row_index: r.row_index + 1) }),
21
22
 
22
23
  # The number (integer) of the current row. Starts at 1
23
- rownum: runtime_value(->(runtime) { number(runtime.rownum) }),
24
+ rownum: runtime_value(->(r) { number(r.rownum) }),
24
25
 
25
26
  # A reference to the current row
26
- rowref: runtime_value(->(runtime) { ref(row_index: runtime.row_index) })
27
+ rowref: runtime_value(->(r) { cell_reference(row_index: r.row_index) })
27
28
  }.freeze
28
29
  public_constant :VARIABLES
29
30
 
30
31
  FUNCTIONS = {
31
32
  # TODO: A reference to a cell in a given row?
32
33
  # A reference to a cell above the current row
33
- cellabove: runtime_value(->(runtime, args) { cell_reference([args[0], [1, (runtime.rownum - 1)].max].join) }),
34
+ # cellabove: runtime_value(->(r, args) { cell_reference(ref: [args[0], [1, (r.rownum - 1)].max].join) }),
35
+ cellabove: runtime_value(
36
+ lambda { |r, args|
37
+ cell_reference(cell_index: args[0].cell_index, row_index: [0, (r.row_index - 1)].max)
38
+ }
39
+ ),
34
40
 
35
41
  # A reference to a cell in the current row
36
- celladjacent: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum].join) }),
42
+ celladjacent: runtime_value(
43
+ lambda { |r, args|
44
+ cell_reference(cell_index: args[0].cell_index, row_index: r.row_index)
45
+ }
46
+ ),
37
47
 
38
48
  # A reference to a cell below the current row
39
- cellbelow: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum + 1].join) })
49
+ cellbelow: runtime_value(
50
+ lambda { |r, args|
51
+ cell_reference(cell_index: args[0].cell_index, row_index: r.row_index + 1)
52
+ }
53
+ )
40
54
  }.freeze
41
55
  public_constant :FUNCTIONS
42
56
  end