csv_plus_plus 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/README.md +1 -0
  4. data/lib/csv_plus_plus/cell.rb +24 -8
  5. data/lib/csv_plus_plus/cli.rb +29 -16
  6. data/lib/csv_plus_plus/cli_flag.rb +10 -2
  7. data/lib/csv_plus_plus/code_section.rb +22 -3
  8. data/lib/csv_plus_plus/color.rb +19 -5
  9. data/lib/csv_plus_plus/google_options.rb +6 -2
  10. data/lib/csv_plus_plus/graph.rb +0 -1
  11. data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
  12. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
  13. data/lib/csv_plus_plus/language/builtins.rb +46 -0
  14. data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
  15. data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
  16. data/lib/csv_plus_plus/language/compiler.rb +74 -86
  17. data/lib/csv_plus_plus/language/entities/boolean.rb +3 -2
  18. data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
  19. data/lib/csv_plus_plus/language/entities/entity.rb +20 -8
  20. data/lib/csv_plus_plus/language/entities/function.rb +6 -4
  21. data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
  22. data/lib/csv_plus_plus/language/entities/number.rb +6 -4
  23. data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
  24. data/lib/csv_plus_plus/language/entities/string.rb +6 -4
  25. data/lib/csv_plus_plus/language/references.rb +22 -5
  26. data/lib/csv_plus_plus/language/runtime.rb +80 -22
  27. data/lib/csv_plus_plus/language/scope.rb +29 -38
  28. data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
  29. data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
  30. data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
  31. data/lib/csv_plus_plus/modifier.rb +38 -13
  32. data/lib/csv_plus_plus/modifier.tab.rb +2 -2
  33. data/lib/csv_plus_plus/options.rb +17 -2
  34. data/lib/csv_plus_plus/row.rb +15 -4
  35. data/lib/csv_plus_plus/template.rb +10 -6
  36. data/lib/csv_plus_plus/version.rb +1 -1
  37. data/lib/csv_plus_plus/writer/excel.rb +2 -9
  38. data/lib/csv_plus_plus/writer/file_backer_upper.rb +22 -20
  39. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
  40. data/lib/csv_plus_plus/writer/google_sheets.rb +4 -10
  41. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
  42. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
  43. data/lib/csv_plus_plus.rb +21 -3
  44. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c4f5c9b5a342102fa8e1c31b319fb05f19ad55278bc633435129254b5b2511a8
4
- data.tar.gz: 0c2656d4d7b22d0b3b3311745ed5a70b789277c622f0f2d09d2b8a144de71a8d
3
+ metadata.gz: 0a93c3ee93f2320dc717ee330c55cd3f300cfa4d155a4502b9a433cdafdcc389
4
+ data.tar.gz: aa95dca43a374d57f2217030e0aefef680341549613b0388cb5df1122cb5f815
5
5
  SHA512:
6
- metadata.gz: 90965e2275cc7988f4054b59e30523cd645b21c1adb89797828e86a767217b09ac174bbc17094604dd608b93defd79f4c9a74ccf7e2f20dd17995f8c1b5dadac
7
- data.tar.gz: 291011896232e88a7bd0e252887459986812d2877c1d0383458250e1c30cf735d8b2bdaf5dd759f44fa5b8b33b08f74d780df7960622ab774e9f6d856628508f
6
+ metadata.gz: 19a827c9a5fc8f0f9fe9d58c4e6b85de821c9e09627465673c2813c2690fadc886c7d8f24e3da5402783b27347a79f7832b13c82abe4e204026d12839d6f4691
7
+ data.tar.gz: b157b531d9ae9e2fda9c93ed466976deacfe639b1fbc1527b60c706be9d97792946341a767f00c32b6d35cbc6e388711980767d9ef6b5c20b4a2f44ebe7fb121
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+
2
+ ## v0.1.0
3
+
4
+ - revamp of builtin functions
5
+
6
+ - docs & tests
7
+
1
8
  ## v0.0.5
2
9
 
3
10
  - Support the --backup/-b option
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Ruby Style Guide](https://img.shields.io/badge/code_style-community-brightgreen.svg)](https://rubystyle.guide)
2
+ [![Gem Version](https://badge.fury.io/rb/csv_plus_plus.svg)](https://badge.fury.io/rb/csv_plus_plus)
2
3
 
3
4
  # csv++
4
5
 
@@ -4,21 +4,33 @@ require_relative './language/cell_value.tab'
4
4
  require_relative './modifier'
5
5
 
6
6
  module CSVPlusPlus
7
- ##
8
7
  # A cell of a template
8
+ #
9
+ # @attr ast [Entity]
10
+ # @attr row_index [Integer] The cell's row index (starts at 0)
11
+ # @attr_reader index [Integer] The cell's index (starts at 0)
12
+ # @attr_reader modifier [Modifier] The modifier for this cell
9
13
  class Cell
10
14
  attr_accessor :ast, :row_index
11
15
  attr_reader :index, :modifier
12
16
 
13
- # Parse a +value+ into a Cell object. The +value+ should already have been through
14
- # a CSV parser
17
+ # Parse a +value+ into a Cell object.
18
+ #
19
+ # @param value [String] A string value which should already have been processed through a CSV parser
20
+ # @param runtime [Runtime]
21
+ # @param modifier [Modifier]
22
+ #
23
+ # @return [Cell]
15
24
  def self.parse(value, runtime:, modifier:)
16
25
  new(value:, row_index: runtime.row_index, index: runtime.cell_index, modifier:).tap do |c|
17
26
  c.ast = ::CSVPlusPlus::Language::CellValueParser.new.parse(value, runtime)
18
27
  end
19
28
  end
20
29
 
21
- # initialize
30
+ # @param row_index [Integer] The cell's row index (starts at 0)
31
+ # @param index [Integer] The cell's index (starts at 0)
32
+ # @param value [String] A string value which should already have been processed through a CSV parser
33
+ # @param modifier [Modifier] A modifier to apply to this cell
22
34
  def initialize(row_index:, index:, value:, modifier:)
23
35
  @value = value
24
36
  @modifier = modifier
@@ -26,25 +38,29 @@ module CSVPlusPlus
26
38
  @row_index = row_index
27
39
  end
28
40
 
29
- # The value (cleaned up some)
41
+ # The +@value+ (cleaned up some)
42
+ #
43
+ # @return [String]
30
44
  def value
31
45
  return if @value.nil? || @value.strip.empty?
32
46
 
33
47
  @value.strip
34
48
  end
35
49
 
36
- # to_s
50
+ # @return [String]
37
51
  def to_s
38
52
  "Cell(index: #{@index}, row_index: #{@row_index}, value: #{@value}, modifier: #{@modifier})"
39
53
  end
40
54
 
41
55
  # A compiled final representation of the cell. This can only happen after all cell have had
42
56
  # variables and functions resolved.
57
+ #
58
+ # @return [String]
43
59
  def to_csv
44
60
  return value unless @ast
45
61
 
46
- # This looks really simple but we're relying on each node of the AST to define #to_s and calling
47
- # this at the top will recursively print the tree
62
+ # This looks really simple but we're relying on each node of the AST to define #to_s such that calling
63
+ # this at the top will recursively print the tree (as a well-formatted spreadsheet formula)
48
64
  "=#{@ast}"
49
65
  end
50
66
  end
@@ -4,27 +4,32 @@ require 'optparse'
4
4
 
5
5
  module CSVPlusPlus
6
6
  # Handle running the application with the given CLI flags
7
+ #
8
+ # @attr options [Options, nil] The parsed CLI options
7
9
  class CLI
8
- # handle any CLI flags and launch the compiler
10
+ attr_accessor :options
11
+
12
+ # Handle CLI flags and launch the compiler
13
+ #
14
+ # @return [CLI]
9
15
  def self.launch_compiler!
10
16
  cli = new
11
- cli.compile!
17
+ cli.parse_options!
18
+ cli.main
12
19
  rescue ::StandardError => e
13
20
  cli.handle_error(e)
14
21
  exit(1)
15
22
  end
16
23
 
17
- # initialize
18
- def initialize
19
- parse_options!
20
- end
21
-
22
- # compile the given template, using the given CLI flags
23
- def compile!
24
+ # Compile the given template using the given CLI flags
25
+ def main
26
+ parse_options! unless @options
24
27
  ::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, @options)
25
28
  end
26
29
 
27
- # (nicely) handle a given error. how it's handled depends on if it's our error and if @options.verbose
30
+ # Nicely handle a given error. How it's handled depends on if it's our error and if @options.verbose
31
+ #
32
+ # @param error [CSVPlusPlus::Error, Google::Apis::ClientError, StandardError]
28
33
  def handle_error(error)
29
34
  case error
30
35
  when ::CSVPlusPlus::Error
@@ -37,6 +42,20 @@ module CSVPlusPlus
37
42
  end
38
43
  end
39
44
 
45
+ # Handle the supplied command line options, setting +@options+ or throw an error if anything is invalid
46
+ def parse_options!
47
+ @options = ::CSVPlusPlus::Options.new
48
+ option_parser.parse!
49
+ validate_options
50
+ rescue ::OptionParser::InvalidOption => e
51
+ raise(::CSVPlusPlus::Error, e.message)
52
+ end
53
+
54
+ # @return [String]
55
+ def to_s
56
+ "CLI(options: #{options})"
57
+ end
58
+
40
59
  private
41
60
 
42
61
  def handle_internal_error(error)
@@ -54,12 +73,6 @@ module CSVPlusPlus
54
73
  warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
55
74
  end
56
75
 
57
- def parse_options!
58
- @options = ::CSVPlusPlus::Options.new
59
- option_parser.parse!
60
- validate_options
61
- end
62
-
63
76
  def validate_options
64
77
  error_message = @options.validate
65
78
  return if error_message.nil?
@@ -4,10 +4,18 @@ require_relative './google_options'
4
4
 
5
5
  module CSVPlusPlus
6
6
  # Individual CLI flags that a user can supply
7
+ #
8
+ # @attr_reader short_flag [String] A definition of the short/single-character flag
9
+ # @attr_reader long_flag [String] A definition of the long/word-based flag
10
+ # @attr_reader description [String] A description of what the flag does
11
+ # @attr_reader handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
7
12
  class CliFlag
8
13
  attr_reader :short_flag, :long_flag, :description, :handler
9
14
 
10
- # initialize
15
+ # @param short_flag [String] A definition of the short/single-character flag
16
+ # @param long_flag [String] A definition of the long/word-based flag
17
+ # @param description [String] A description of what the flag does
18
+ # @param handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
11
19
  def initialize(short_flag, long_flag, description, handler)
12
20
  @short_flag = short_flag
13
21
  @long_flag = long_flag
@@ -15,7 +23,7 @@ module CSVPlusPlus
15
23
  @handler = handler
16
24
  end
17
25
 
18
- # to_s
26
+ # @return [String]
19
27
  def to_s
20
28
  "#{@short_flag}, #{@long_flag} #{@description}"
21
29
  end
@@ -4,44 +4,63 @@ require_relative './language/code_section.tab'
4
4
  require_relative './language/entities'
5
5
 
6
6
  module CSVPlusPlus
7
- ##
8
7
  # A representation of the code section part of a template (the variable and function definitions)
8
+ #
9
+ # @attr variables [Hash<Symbol, Variable>] All defined variables
10
+ # @attr_reader functions [Hash<Symbol, Function>] All defined functions
9
11
  class CodeSection
10
12
  attr_reader :functions
11
13
  attr_accessor :variables
12
14
 
13
- # initialize
15
+ # @param variables [Hash<Symbol, Variable>] Initial variables
16
+ # @param functions [Hash<Symbol, Variable>] Initial functions
14
17
  def initialize(variables: {}, functions: {})
15
18
  @variables = variables
16
19
  @functions = functions
17
20
  end
18
21
 
19
22
  # Define a (or re-define an existing) variable
23
+ #
24
+ # @param id [String, Symbol] The identifier for the variable
25
+ # @param entity [Entity] The value (entity) the variable holds
20
26
  def def_variable(id, entity)
21
27
  @variables[id.to_sym] = entity
22
28
  end
23
29
 
24
30
  # Define (or re-define existing) variables
31
+ #
32
+ # @param variables [Hash<Symbol, Variable>] Variables to define
25
33
  def def_variables(variables)
26
34
  variables.each { |id, entity| def_variable(id, entity) }
27
35
  end
28
36
 
29
37
  # Define a (or re-define an existing) function
38
+ #
39
+ # @param id [String, Symbol] The identifier for the function
40
+ # @param entity [Entities::Function] The defined function
30
41
  def def_function(id, entity)
31
42
  @functions[id.to_sym] = entity
32
43
  end
33
44
 
34
45
  # Is the variable defined?
46
+ #
47
+ # @param var_id [Symbol, String] The identifier of the variable
48
+ #
49
+ # @return [boolean]
35
50
  def defined_variable?(var_id)
36
51
  @variables.key?(var_id.to_sym)
37
52
  end
38
53
 
39
54
  # Is the function defined?
55
+ #
56
+ # @param fn_id [Symbol, String] The identifier of the function
57
+ #
58
+ # @return [boolean]
40
59
  def defined_function?(fn_id)
41
60
  @functions.key?(fn_id.to_sym)
42
61
  end
43
62
 
44
- # to_s
63
+ # @return [String]
45
64
  def to_s
46
65
  "CodeSection(functions: #{@functions}, variables: #{@variables})"
47
66
  end
@@ -1,11 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CSVPlusPlus
4
- # A color value
4
+ # A color value parsed into it's respective components
5
+ #
6
+ # attr_reader blue_hex [String] The blue value in hex ("FF", "00", "AF", etc)
7
+ # attr_reader green_hex [String] The green value in hex ("FF", "00", "AF", etc)
8
+ # attr_reader red_hex [String] The red value in hex ("FF", "00", "AF", etc)
5
9
  class Color
6
10
  attr_reader :red_hex, :green_hex, :blue_hex
7
11
 
8
12
  # create an instance from a string like "#FFF" or "#FFFFFF"
13
+ #
14
+ # @param hex_string [String] The hex string input to parse
9
15
  def initialize(hex_string)
10
16
  @red_hex, @green_hex, @blue_hex = hex_string
11
17
  .gsub(/^#?/, '')
@@ -15,31 +21,39 @@ module CSVPlusPlus
15
21
  end
16
22
 
17
23
  # The percent (decimal between 0-1) of red
24
+ #
25
+ # @return [Numeric]
18
26
  def red_percent
19
27
  hex_to_percent(@red_hex)
20
28
  end
21
29
 
22
30
  # The percent (decimal between 0-1) of green
31
+ #
32
+ # @return [Numeric]
23
33
  def green_percent
24
34
  hex_to_percent(@green_hex)
25
35
  end
26
36
 
27
37
  # The percent (decimal between 0-1) of blue
38
+ #
39
+ # @return [Numeric]
28
40
  def blue_percent
29
41
  hex_to_percent(@blue_hex)
30
42
  end
31
43
 
32
- # to_hex
44
+ # Create a hex representation of the color (without a '#')
45
+ #
46
+ # @return [String]
33
47
  def to_hex
34
48
  [@red_hex, @green_hex, @blue_hex].join
35
49
  end
36
50
 
37
- # to_s
51
+ # @return [String]
38
52
  def to_s
39
53
  "Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
40
54
  end
41
55
 
42
- # ==
56
+ # @return [boolean]
43
57
  def ==(other)
44
58
  other.is_a?(self.class) &&
45
59
  other.red_hex == @red_hex &&
@@ -50,7 +64,7 @@ module CSVPlusPlus
50
64
  private
51
65
 
52
66
  def hex_to_percent(hex)
53
- hex.to_i(16) / 255
67
+ hex.to_i(16) / 255.0
54
68
  end
55
69
  end
56
70
  end
@@ -2,9 +2,13 @@
2
2
 
3
3
  module CSVPlusPlus
4
4
  # The Google-specific options a user can supply
5
+ #
6
+ # attr sheet_id [String] The ID of the Google Sheet to write to
5
7
  GoogleOptions =
6
8
  ::Struct.new(:sheet_id) do
7
- # Return a string with a verbose description of what we're doing with the options
9
+ # Format a string with a verbose description of what we're doing with the options
10
+ #
11
+ # @return [String]
8
12
  def verbose_summary
9
13
  <<~SUMMARY
10
14
  ## Google Sheets Options
@@ -13,7 +17,7 @@ module CSVPlusPlus
13
17
  SUMMARY
14
18
  end
15
19
 
16
- # to_s
20
+ # @return [String]
17
21
  def to_s
18
22
  "GoogleOptions(sheet_id: #{sheet_id})"
19
23
  end
@@ -3,7 +3,6 @@
3
3
  require 'tsort'
4
4
 
5
5
  module CSVPlusPlus
6
- ##
7
6
  # Graph ordering and searching functions
8
7
  module Graph
9
8
  # Get a list of all variables references in a given +ast+
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './entities'
4
+
5
+ module CSVPlusPlus
6
+ module Language
7
+ # Some helpful functions that can be mixed into a class to help building ASTs
8
+ module ASTBuilder
9
+ # Let the current class have functions which can build a given entity by calling it's type. For example
10
+ # +number(1)+, +variable(:foo)+
11
+ #
12
+ # @param method_name [Symbol] The +method_name+ to respond to
13
+ # @param arguments [] The arguments to create the entity with
14
+ #
15
+ # @return [Entity, #super]
16
+ def method_missing(method_name, *arguments)
17
+ entity_class = ::CSVPlusPlus::Language::TYPES[method_name.to_sym]
18
+ return super unless entity_class
19
+
20
+ entity_class.new(*arguments)
21
+ end
22
+
23
+ # Let the current class have functions which can build a given entity by calling it's type. For example
24
+ # +number(1)+, +variable(:foo)+
25
+ #
26
+ # @param method_name [Symbol] The +method_name+ to respond to
27
+ # @param arguments [] The arguments to create the entity with
28
+ #
29
+ # @return [Boolean, #super]
30
+ def respond_to_missing?(method_name, *_arguments)
31
+ ::CSVPlusPlus::Language::TYPES.include?(method_name.to_sym) || super
32
+ end
33
+
34
+ # Turns index-based/X,Y coordinates into a A1 format
35
+ #
36
+ # @param row_index [Integer]
37
+ # @param cell_index [Integer]
38
+ #
39
+ # @return [String]
40
+ def ref(row_index: nil, cell_index: nil)
41
+ return unless row_index || cell_index
42
+
43
+ rowref = row_index ? (row_index + 1).to_s : ''
44
+ cellref = cell_index ? cell_ref(cell_index) : ''
45
+ cell_reference([cellref, rowref].join)
46
+ end
47
+
48
+ private
49
+
50
+ ALPHA = ('A'..'Z').to_a.freeze
51
+ private_constant :ALPHA
52
+
53
+ def cell_ref(cell_index)
54
+ c = cell_index.dup
55
+ ref = ''
56
+
57
+ while c >= 0
58
+ # rubocop:disable Lint/ConstantResolution
59
+ ref += ALPHA[c % 26]
60
+ # rubocop:enable Lint/ConstantResolution
61
+ c = (c / 26).floor - 1
62
+ end
63
+
64
+ ref.reverse
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+
5
+ module CSVPlusPlus
6
+ module Language
7
+ # Extend a +Compiler+ class and add benchmark timings
8
+ # @attr_reader timings [Array<Benchmark::Tms>] +Benchmark+ timings that have been accumulated by each step of
9
+ # compilation
10
+ # @attr_reader benchmark [Benchmark] A +Benchmark+ instance
11
+ module BenchmarkedCompiler
12
+ attr_reader :benchmark, :timings
13
+
14
+ # Wrap a +Compiler+ with our instance methods that add benchmarks
15
+ def self.with_benchmarks(compiler, &block)
16
+ ::Benchmark.benchmark(::Benchmark::CAPTION, 25, ::Benchmark::FORMAT, '> Total') do |x|
17
+ # compiler = new(options:, runtime:, benchmark: x)
18
+ compiler.extend(self)
19
+ compiler.benchmark = x
20
+
21
+ block.call(compiler)
22
+
23
+ [compiler.timings.reduce(:+)]
24
+ end
25
+ end
26
+
27
+ # @param benchmark [Benchmark] A +Benchmark+ instance
28
+ def benchmark=(benchmark)
29
+ @benchmark = benchmark
30
+ @timings = []
31
+ end
32
+
33
+ # Time the Compiler#outputting! stage
34
+ def outputting!
35
+ time_stage('Writing the spreadsheet') { super }
36
+ end
37
+
38
+ protected
39
+
40
+ def parse_code_section!
41
+ time_stage('Parsing code section') { super }
42
+ end
43
+
44
+ def parse_csv_section!
45
+ time_stage('Parsing CSV section') { super }
46
+ end
47
+
48
+ def expanding
49
+ time_stage('Expanding rows') { super }
50
+ end
51
+
52
+ def resolve_all_cells!(template)
53
+ time_stage('Resolving each cell') { super(template) }
54
+ end
55
+
56
+ private
57
+
58
+ def time_stage(stage, &block)
59
+ ret = nil
60
+ @timings << @benchmark.report(stage) { ret = block.call }
61
+ ret
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ast_builder'
4
+
5
+ module CSVPlusPlus
6
+ module Language
7
+ # Provides ASTs for builtin functions and variables
8
+ module Builtins
9
+ extend ::CSVPlusPlus::Language::ASTBuilder
10
+
11
+ VARIABLES = {
12
+ # The number (integer) of the current cell. Starts at 1
13
+ cellnum: runtime_value(->(runtime) { number(runtime.cell_index + 1) }),
14
+
15
+ # A reference to the current cell
16
+ cellref: runtime_value(->(runtime) { ref(row_index: runtime.row_index, cell_index: runtime.cell_index) }),
17
+
18
+ # A reference to the row above
19
+ rowabove: runtime_value(->(runtime) { ref(row_index: [0, (runtime.row_index - 1)].max) }),
20
+
21
+ # A reference to the row below
22
+ rowbelow: runtime_value(->(runtime) { ref(row_index: runtime.row_index + 1) }),
23
+
24
+ # The number (integer) of the current row. Starts at 1
25
+ rownum: runtime_value(->(runtime) { number(runtime.rownum) }),
26
+
27
+ # A reference to the current row
28
+ rowref: runtime_value(->(runtime) { ref(row_index: runtime.row_index) })
29
+ }.freeze
30
+ public_constant :VARIABLES
31
+
32
+ FUNCTIONS = {
33
+ # TODO: A reference to a cell in a given row?
34
+ # A reference to a cell above the current row
35
+ cellabove: runtime_value(->(runtime, args) { cell_reference([args[0], [1, (runtime.rownum - 1)].max].join) }),
36
+
37
+ # A reference to a cell in the current row
38
+ celladjacent: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum].join) }),
39
+
40
+ # A reference to a cell below the current row
41
+ cellbelow: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum + 1].join) })
42
+ }.freeze
43
+ public_constant :FUNCTIONS
44
+ end
45
+ end
46
+ end
@@ -29,11 +29,10 @@ module_eval(<<'...end cell_value.y/module_eval...', 'cell_value.y', 48)
29
29
  @ast
30
30
  end
31
31
 
32
- def tokenizer(input)
32
+ def tokenizer
33
33
  ::CSVPlusPlus::Lexer::Tokenizer.new(
34
34
  catchall: /[\(\)\/\*\+\-,=&]/,
35
35
  ignore: /\s+/,
36
- input:,
37
36
  tokens: [
38
37
  [/true/i, :TRUE],
39
38
  [/false/i, :FALSE],
@@ -33,11 +33,10 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 67)
33
33
  'code section'
34
34
  end
35
35
 
36
- def tokenizer(input)
36
+ def tokenizer
37
37
  ::CSVPlusPlus::Lexer::Tokenizer.new(
38
38
  catchall: /[\(\)\{\}\/\*\+\-,=&]/,
39
39
  ignore: /\s+|\#[^\n]+\n/,
40
- input:,
41
40
  stop_fn: lambda do |scanner|
42
41
  return false unless scanner.scan(/#{::CSVPlusPlus::Lexer::END_OF_CODE_SECTION}/)
43
42