csv_plus_plus 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/docs/CHANGELOG.md +16 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
  6. data/lib/csv_plus_plus/cell.rb +1 -35
  7. data/lib/csv_plus_plus/cli.rb +43 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -70
  9. data/lib/csv_plus_plus/color.rb +1 -1
  10. data/lib/csv_plus_plus/compiler.rb +31 -21
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
  12. data/lib/csv_plus_plus/entities/boolean.rb +16 -9
  13. data/lib/csv_plus_plus/entities/builtins.rb +68 -40
  14. data/lib/csv_plus_plus/entities/date.rb +14 -11
  15. data/lib/csv_plus_plus/entities/entity.rb +11 -29
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
  17. data/lib/csv_plus_plus/entities/function.rb +22 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +35 -11
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +15 -10
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
  23. data/lib/csv_plus_plus/entities/string.rb +13 -10
  24. data/lib/csv_plus_plus/entities.rb +2 -18
  25. data/lib/csv_plus_plus/error/cli_error.rb +17 -0
  26. data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
  27. data/lib/csv_plus_plus/error/error.rb +18 -5
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +1 -1
  33. data/lib/csv_plus_plus/error.rb +4 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +18 -8
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
  38. data/lib/csv_plus_plus/lexer.rb +40 -1
  39. data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
  40. data/lib/csv_plus_plus/modifier/expand.rb +17 -0
  41. data/lib/csv_plus_plus/modifier.rb +6 -1
  42. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  43. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  44. data/lib/csv_plus_plus/options/options.rb +97 -0
  45. data/lib/csv_plus_plus/options.rb +22 -110
  46. data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
  47. data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
  48. data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
  49. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  50. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  51. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  52. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  53. data/lib/csv_plus_plus/reader.rb +14 -0
  54. data/lib/csv_plus_plus/runtime/graph.rb +6 -6
  55. data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
  56. data/lib/csv_plus_plus/runtime/references.rb +32 -27
  57. data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
  58. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  59. data/lib/csv_plus_plus/runtime.rb +9 -9
  60. data/lib/csv_plus_plus/source_code.rb +14 -9
  61. data/lib/csv_plus_plus/template.rb +17 -12
  62. data/lib/csv_plus_plus/version.rb +1 -1
  63. data/lib/csv_plus_plus/writer/csv.rb +32 -5
  64. data/lib/csv_plus_plus/writer/excel.rb +19 -6
  65. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
  66. data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
  67. data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
  68. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  69. data/lib/csv_plus_plus/writer/open_document.rb +16 -2
  70. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
  71. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  72. data/lib/csv_plus_plus/writer.rb +58 -19
  73. data/lib/csv_plus_plus.rb +26 -14
  74. metadata +37 -12
  75. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
  76. data/lib/csv_plus_plus/entities/variable.rb +0 -37
  77. data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
  78. data/lib/csv_plus_plus/google_options.rb +0 -32
  79. data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
  80. data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
  81. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
  82. data/lib/csv_plus_plus/writer/base_writer.rb +0 -45
@@ -0,0 +1,37 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Reader
6
+ # Reads an Excel file
7
+ class RubyXL < ::CSVPlusPlus::Reader::Reader
8
+ extend ::T::Sig
9
+ extend ::T::Generic
10
+
11
+ CellValue = type_member { { fixed: ::RubyXL::Cell } }
12
+ public_constant :CellValue
13
+
14
+ sig { params(options: ::CSVPlusPlus::Options::FileOptions, worksheet: ::RubyXL::Worksheet).void }
15
+ # Open an excel outputter to the +output_filename+ specified by the +Options+
16
+ #
17
+ # @param options [Options] The supplied options.
18
+ # @param worksheet [RubyXL::Worksheet] The already-opened RubyXL worksheet
19
+ def initialize(options, worksheet)
20
+ super()
21
+
22
+ @options = options
23
+ @worksheet = worksheet
24
+ end
25
+
26
+ sig { override.params(cell: ::CSVPlusPlus::Cell).returns(::T.nilable(::CSVPlusPlus::Reader::RubyXL::CellValue)) }
27
+ # Get the current value at the +cell+'s position
28
+ #
29
+ # @param cell [Cell]
30
+ #
31
+ # @return [RubyXL::Cell, nil]
32
+ def value_at(cell)
33
+ @worksheet.sheet_data[cell.row_index]&.[](cell.index)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,14 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ # Classes which can read spreadsheets in our various formats.
6
+ module Reader
7
+ end
8
+ end
9
+
10
+ require_relative './reader/reader'
11
+
12
+ require_relative './reader/csv'
13
+ require_relative './reader/google_sheets'
14
+ require_relative './reader/rubyxl'
@@ -9,18 +9,18 @@ module CSVPlusPlus
9
9
  module Graph
10
10
  # Get a list of all variables references in a given +ast+
11
11
  # TODO: this is only used in one place - refactor it
12
- def self.variable_references(ast, runtime, include_runtime_variables: false)
12
+ def self.variable_references(ast, include_runtime_variables: false)
13
13
  depth_first_search(ast) do |node|
14
- next unless node.type == ::CSVPlusPlus::Entities::Type::Variable
14
+ next unless node.is_a?(::CSVPlusPlus::Entities::Reference)
15
15
 
16
- node.id if !runtime.builtin_variable?(node.id) || include_runtime_variables
16
+ node.id if !::CSVPlusPlus::Entities::Builtins.builtin_variable?(node.id) || include_runtime_variables
17
17
  end
18
18
  end
19
19
 
20
20
  # Create a dependency graph of +variables+
21
- def self.dependency_graph(variables, runtime)
21
+ def self.dependency_graph(variables)
22
22
  ::CSVPlusPlus::Runtime::Graph::DependencyGraph[
23
- variables.map { |var_id, ast| [var_id, variable_references(ast, runtime)] }
23
+ variables.map { |var_id, ast| [var_id, variable_references(ast)] }
24
24
  ]
25
25
  end
26
26
 
@@ -47,7 +47,7 @@ module CSVPlusPlus
47
47
  ret = yield(node)
48
48
  accum << ret unless ret.nil?
49
49
 
50
- return accum unless node.type == ::CSVPlusPlus::Entities::Type::FunctionCall
50
+ return accum unless node.is_a?(::CSVPlusPlus::Entities::FunctionCall)
51
51
 
52
52
  node.arguments.each { |n| depth_first_search(n, accum, &) }
53
53
  accum
@@ -3,9 +3,15 @@
3
3
 
4
4
  module CSVPlusPlus
5
5
  module Runtime
6
- # Functions needed to track all of the runtime pointers: current line number, current row number, current cell, etc.
7
- # rubocop:disable Metrics/ModuleLength
8
- module PositionTracker
6
+ # Keeps track of the position in a file where the parser is. The parser makes various passes over the input but
7
+ # it always needs to track the same things (line number, cell/row index, current cell)
8
+ #
9
+ # @attr cell [Cell] The current cell being processed
10
+ # @attr cell_index [Integer] The index of the current cell being processed (starts at 0)
11
+ # @attr row_index [Integer] The index of the current row being processed (starts at 0)
12
+ # @attr line_number [Integer] The line number of the original csvpp template (starts at 1)
13
+ # rubocop:disable Metrics/ClassLength
14
+ class Position
9
15
  extend ::T::Sig
10
16
 
11
17
  sig { params(cell: ::T.nilable(::CSVPlusPlus::Cell)).returns(::T.nilable(::CSVPlusPlus::Cell)) }
@@ -20,6 +26,12 @@ module CSVPlusPlus
20
26
  sig { params(row_index: ::T.nilable(::Integer)).returns(::T.nilable(::Integer)) }
21
27
  attr_writer :row_index
22
28
 
29
+ sig { params(input: ::String).void }
30
+ # @param input [String]
31
+ def initialize(input)
32
+ rewrite_input!(::CSVPlusPlus::Lexer.preprocess(input))
33
+ end
34
+
23
35
  sig { returns(::CSVPlusPlus::Cell) }
24
36
  # The current cell index. This will only be set when processing the CSV section
25
37
  #
@@ -179,7 +191,6 @@ module CSVPlusPlus
179
191
  # Each time we run a parse on the input, reset the runtime state starting at the beginning of the file
180
192
  def start!(&block)
181
193
  @row_index = @cell_index = 0
182
- self.line_number = 1
183
194
 
184
195
  ret = block.call
185
196
  finish!
@@ -226,6 +237,6 @@ module CSVPlusPlus
226
237
  self.line_number += 1
227
238
  end
228
239
  end
229
- # rubocop:enable Metrics/ModuleLength
240
+ # rubocop:enable Metrics/ClassLength
230
241
  end
231
242
  end
@@ -7,79 +7,84 @@ module CSVPlusPlus
7
7
  #
8
8
  # @attr functions [Array<Entities::Function>] Functions references
9
9
  # @attr variables [Array<Entities::Variable>] Variable references
10
- # TODO: turn this into a CanExtractReferences?
11
10
  class References
12
11
  extend ::T::Sig
13
12
 
14
13
  sig { returns(::T::Array[::CSVPlusPlus::Entities::FunctionCall]) }
15
14
  attr_accessor :functions
16
15
 
17
- sig { returns(::T::Array[::CSVPlusPlus::Entities::Variable]) }
16
+ sig { returns(::T::Array[::CSVPlusPlus::Entities::Reference]) }
18
17
  attr_accessor :variables
19
18
 
20
19
  sig do
21
20
  params(
22
21
  ast: ::CSVPlusPlus::Entities::Entity,
23
- runtime: ::CSVPlusPlus::Runtime::Runtime
22
+ position: ::CSVPlusPlus::Runtime::Position,
23
+ scope: ::CSVPlusPlus::Runtime::Scope
24
24
  ).returns(::CSVPlusPlus::Runtime::References)
25
25
  end
26
26
  # Extract references from an AST and return them in a new +References+ object
27
27
  #
28
28
  # @param ast [Entity] An +Entity+ to do a depth first search on for references. Entities can be
29
29
  # infinitely deep because they can contain other function calls as params to a function call
30
- # @param runtime [Runtime] The current runtime
30
+ # @param scope [Scope] The current scope
31
31
  #
32
32
  # @return [References]
33
- def self.extract(ast, runtime)
33
+ def self.extract(ast, position, scope)
34
34
  new.tap do |refs|
35
35
  ::CSVPlusPlus::Runtime::Graph.depth_first_search(ast) do |node|
36
- unless node.type == ::CSVPlusPlus::Entities::Type::FunctionCall \
37
- || node.type == ::CSVPlusPlus::Entities::Type::Variable
38
-
36
+ unless node.is_a?(::CSVPlusPlus::Entities::FunctionCall) || node.is_a?(::CSVPlusPlus::Entities::Reference)
39
37
  next
40
38
  end
41
39
 
42
- refs.functions << node if function_reference?(node, runtime)
43
- refs.variables << node if variable_reference?(node, runtime)
40
+ refs.functions << node if function_reference?(node, scope)
41
+ refs.variables << node if variable_reference?(node, position, scope)
44
42
  end
45
43
  end
46
44
  end
47
45
 
48
46
  sig do
49
- params(node: ::CSVPlusPlus::Entities::Entity, runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T::Boolean)
47
+ params(
48
+ node: ::CSVPlusPlus::Entities::Entity,
49
+ position: ::CSVPlusPlus::Runtime::Position,
50
+ scope: ::CSVPlusPlus::Runtime::Scope
51
+ ).returns(::T::Boolean)
50
52
  end
51
53
  # Is the node a resolvable variable reference?
52
54
  #
53
55
  # @param node [Entity] The node to check if it's resolvable
54
- # @param runtime [Runtime] The current runtime
56
+ # @param scope [Scope] The current scope
55
57
  #
56
58
  # @return [boolean]
57
- def self.variable_reference?(node, runtime)
58
- return false unless node.type == ::CSVPlusPlus::Entities::Type::Variable
59
+ def self.variable_reference?(node, position, scope)
60
+ return false unless node.is_a?(::CSVPlusPlus::Entities::Reference)
61
+
62
+ id = node.id
63
+ return false unless id && scope.variables.key?(id)
59
64
 
60
- if runtime.in_scope?(node.id)
61
- true
62
- else
63
- runtime.raise_modifier_syntax_error(
64
- "#{node.id} can only be referenced within the ![[expand]] where it was defined.",
65
- node.id.to_s
65
+ return true if scope.in_scope?(id, position)
66
+
67
+ raise(
68
+ ::CSVPlusPlus::Error::ModifierSyntaxError.new(
69
+ "Reference #{node.ref} can only be referenced within the ![[expand]] where it was defined.",
70
+ bad_input: node.ref.to_s
66
71
  )
67
- end
72
+ )
68
73
  end
69
74
  private_class_method :variable_reference?
70
75
 
71
76
  sig do
72
- params(node: ::CSVPlusPlus::Entities::Entity, runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T::Boolean)
77
+ params(node: ::CSVPlusPlus::Entities::Entity, scope: ::CSVPlusPlus::Runtime::Scope).returns(::T::Boolean)
73
78
  end
74
79
  # Is the node a resolvable function reference?
75
80
  #
76
81
  # @param node [Entity] The node to check if it's resolvable
77
- # @param runtime [Runtime] The current runtime
82
+ # @param scope [Scope] The current scope
78
83
  #
79
84
  # @return [boolean]
80
- def self.function_reference?(node, runtime)
81
- node.type == ::CSVPlusPlus::Entities::Type::FunctionCall \
82
- && (runtime.defined_function?(node.id) || runtime.builtin_function?(::T.must(node.id)))
85
+ def self.function_reference?(node, scope)
86
+ node.is_a?(::CSVPlusPlus::Entities::FunctionCall) \
87
+ && (scope.functions.key?(node.id) || ::CSVPlusPlus::Entities::Builtins.builtin_function?(node.id))
83
88
  end
84
89
  private_class_method :function_reference?
85
90
 
@@ -87,7 +92,7 @@ module CSVPlusPlus
87
92
  # Create an object with empty references. The caller will build them up as it depth-first-searches
88
93
  def initialize
89
94
  @functions = ::T.let([], ::T::Array[::CSVPlusPlus::Entities::FunctionCall])
90
- @variables = ::T.let([], ::T::Array[::CSVPlusPlus::Entities::Variable])
95
+ @variables = ::T.let([], ::T::Array[::CSVPlusPlus::Entities::Reference])
91
96
  end
92
97
 
93
98
  sig { params(other: ::CSVPlusPlus::Runtime::References).returns(::T::Boolean) }
@@ -7,25 +7,17 @@ module CSVPlusPlus
7
7
  # a given file. We take multiple runs through the input file for parsing so it's really convenient to have a
8
8
  # central place for these things to be managed.
9
9
  #
10
- # @attr_reader filename [String, nil] The filename that the input came from (mostly used for debugging since
11
- # +filename+ can be +nil+ if it's read from stdin.
12
- #
13
- # @attr cell [Cell] The current cell being processed
14
- # @attr cell_index [Integer] The index of the current cell being processed (starts at 0)
15
- # @attr row_index [Integer] The index of the current row being processed (starts at 0)
16
- # @attr line_number [Integer] The line number of the original csvpp template (starts at 1)
10
+ # @attr_reader position [Runtime::Position]
11
+ # @attr_reader scope [Runtime::Scope]
12
+ # @attr_reader source_code [SourceCode]
17
13
  class Runtime
18
14
  extend ::T::Sig
19
15
 
20
- include ::CSVPlusPlus::Runtime::CanDefineReferences
21
- include ::CSVPlusPlus::Runtime::CanResolveReferences
22
- include ::CSVPlusPlus::Runtime::PositionTracker
23
-
24
- sig { returns(::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Function]) }
25
- attr_reader :functions
16
+ sig { returns(::CSVPlusPlus::Runtime::Position) }
17
+ attr_reader :position
26
18
 
27
- sig { returns(::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Entity]) }
28
- attr_reader :variables
19
+ sig { returns(::CSVPlusPlus::Runtime::Scope) }
20
+ attr_reader :scope
29
21
 
30
22
  sig { returns(::CSVPlusPlus::SourceCode) }
31
23
  attr_reader :source_code
@@ -33,47 +25,52 @@ module CSVPlusPlus
33
25
  sig do
34
26
  params(
35
27
  source_code: ::CSVPlusPlus::SourceCode,
36
- functions: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Function],
37
- variables: ::T::Hash[::Symbol, ::CSVPlusPlus::Entities::Entity]
28
+ position: ::T.nilable(::CSVPlusPlus::Runtime::Position),
29
+ scope: ::T.nilable(::CSVPlusPlus::Runtime::Scope)
38
30
  ).void
39
31
  end
32
+ # @param position [Position, nil] The (optional) position to start at
40
33
  # @param source_code [SourceCode] The source code being compiled
41
- # @param functions [Hash<Symbol, Function>] Pre-defined functions
42
- # @param variables [Hash<Symbol, Entity>] Pre-defined variables
43
- def initialize(source_code:, functions: {}, variables: {})
44
- @functions = functions
45
- @variables = variables
34
+ # @param scope [Runtime::Scope, nil] The (optional) scope if it already exists
35
+ def initialize(source_code:, position: nil, scope: nil)
46
36
  @source_code = source_code
47
-
48
- rewrite_input!(source_code.input)
37
+ @scope = ::T.let(scope || ::CSVPlusPlus::Runtime::Scope.new, ::CSVPlusPlus::Runtime::Scope)
38
+ @position = ::T.let(
39
+ position || ::CSVPlusPlus::Runtime::Position.new(source_code.input),
40
+ ::CSVPlusPlus::Runtime::Position
41
+ )
49
42
  end
50
43
 
51
- sig { params(fn_id: ::Symbol).returns(::T::Boolean) }
52
- # Is +fn_id+ a builtin function?
44
+ sig { params(var_id: ::Symbol).returns(::CSVPlusPlus::Entities::Reference) }
45
+ # Bind +var_id+ to the current cell
53
46
  #
54
- # @param fn_id [Symbol] The Function#id to check if it's a runtime variable
47
+ # @param var_id [Symbol] The name of the variable to bind the cell reference to
55
48
  #
56
- # @return [T::Boolean]
57
- def builtin_function?(fn_id)
58
- ::CSVPlusPlus::Entities::Builtins::FUNCTIONS.key?(fn_id)
49
+ # @return [Entities::Reference]
50
+ def bind_variable_to_cell(var_id)
51
+ ::CSVPlusPlus::Entities::Reference.new(
52
+ a1_ref: ::CSVPlusPlus::A1Reference.new(cell_index: position.cell_index, row_index: position.row_index)
53
+ ).tap do |var|
54
+ scope.def_variable(var_id, var)
55
+ end
59
56
  end
60
57
 
61
- sig { params(var_id: ::Symbol).returns(::T::Boolean) }
62
- # Is +var_id+ a builtin variable?
63
- #
64
- # @param var_id [Symbol] The Variable#id to check if it's a runtime variable
65
- #
66
- # @return [T::Boolean]
67
- def builtin_variable?(var_id)
68
- ::CSVPlusPlus::Entities::Builtins::VARIABLES.key?(var_id)
58
+ sig do
59
+ params(var_id: ::Symbol, expand: ::CSVPlusPlus::Modifier::Expand).returns(::CSVPlusPlus::Entities::Reference)
69
60
  end
70
-
71
- sig { returns(::T::Boolean) }
72
- # Is the parser currently inside of the code section? (includes the `---`)
61
+ # Bind +var_id+ relative to a cell relative to an ![[expand]] modifier. The variable can only be referenced
62
+ # inside rows of that expand.
73
63
  #
74
- # @return [T::Boolean]
75
- def parsing_code_section?
76
- source_code.in_code_section?(line_number)
64
+ # @param var_id [Symbol] The name of the variable to bind the cell reference to
65
+ # @param expand [Expand] The expand where the variable is accessible (where it will be bound relative to)
66
+ #
67
+ # @return [Entities::Reference]
68
+ def bind_variable_in_expand(var_id, expand)
69
+ ::CSVPlusPlus::Entities::Reference.new(
70
+ a1_ref: ::CSVPlusPlus::A1Reference.new(scoped_to_expand: expand, cell_index: position.cell_index)
71
+ ).tap do |var|
72
+ scope.def_variable(var_id, var)
73
+ end
77
74
  end
78
75
 
79
76
  sig { returns(::T::Boolean) }
@@ -81,35 +78,44 @@ module CSVPlusPlus
81
78
  #
82
79
  # @return [T::Boolean]
83
80
  def parsing_csv_section?
84
- source_code.in_csv_section?(line_number)
81
+ source_code.in_csv_section?(position.line_number)
85
82
  end
86
83
 
87
- sig do
88
- params(message: ::String, bad_input: ::String, wrapped_error: ::T.nilable(::StandardError))
89
- .returns(::T.noreturn)
90
- end
91
- # Called when an error is encoutered during parsing formulas (whether in the code section or a cell). It will
92
- # construct a useful error with the current +@row/@cell_index+, +@line_number+ and +@filename+
84
+ sig { params(ast: ::CSVPlusPlus::Entities::Entity).returns(::CSVPlusPlus::Entities::Entity) }
85
+ # Resolve all values in the ast of the current cell being processed
93
86
  #
94
- # @param message [::String] A message relevant to why this error is being raised.
95
- # @param bad_input [::String] The offending input that caused this error to be thrown.
96
- # @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
97
- def raise_formula_syntax_error(message, bad_input, wrapped_error: nil)
98
- raise(::CSVPlusPlus::Error::FormulaSyntaxError.new(message, bad_input, self, wrapped_error:))
87
+ # @param ast [Entities::Entity] The AST to replace references within
88
+ #
89
+ # @return [Entity] The AST with all references replaced
90
+ # rubocop:disable Metrics/MethodLength
91
+ def resolve_cell_value(ast)
92
+ last_round = nil
93
+ ::Kernel.loop do
94
+ refs = ::CSVPlusPlus::Runtime::References.extract(ast, position, scope)
95
+ return ast if refs.empty?
96
+
97
+ # TODO: throw a +CompilerError+ here instead I think - basically we did a round and didn't make progress
98
+ return ast if last_round == refs
99
+
100
+ ast = scope.resolve_functions(
101
+ position,
102
+ scope.resolve_variables(position, ast, refs.variables),
103
+ refs.functions
104
+ )
105
+ end
99
106
  end
107
+ # rubocop:enable Metrics/MethodLength
100
108
 
101
109
  sig do
102
- params(message: ::String, bad_input: ::String, wrapped_error: ::T.nilable(::StandardError))
103
- .returns(::T.noreturn)
110
+ type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
104
111
  end
105
- # Called when an error is encountered while parsing a modifier.
106
- #
107
- # @param message [::String] A message relevant to why this error is being raised.
108
- # @param bad_input [::String] The offending input that caused this error to be thrown.
109
- # @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
110
- def raise_modifier_syntax_error(message, bad_input, wrapped_error: nil)
111
- raise(::CSVPlusPlus::Error::ModifierSyntaxError.new(self, bad_input:, message:, wrapped_error:))
112
+ # Each time we run a parse on the input, reset the runtime state starting at the beginning of the file
113
+ # rubocop:disable Naming/BlockForwarding
114
+ def start!(&block)
115
+ position.line_number = 1
116
+ position.start!(&block)
112
117
  end
118
+ # rubocop:enable Naming/BlockForwarding
113
119
 
114
120
  sig do
115
121
  type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
@@ -117,8 +123,8 @@ module CSVPlusPlus
117
123
  # Reset the runtime state starting at the CSV section
118
124
  # rubocop:disable Naming/BlockForwarding
119
125
  def start_at_csv!(&block)
120
- self.line_number = source_code.length_of_code_section + 1
121
- start!(&block)
126
+ position.line_number = source_code.length_of_code_section + 1
127
+ position.start!(&block)
122
128
  end
123
129
  # rubocop:enable Naming/BlockForwarding
124
130
  end