csv_plus_plus 0.1.3 → 0.2.1

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -3
  3. data/docs/CHANGELOG.md +18 -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 +77 -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 +102 -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 +56 -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 +43 -18
  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