csv_plus_plus 0.1.2 → 0.2.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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -5
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  6. data/lib/csv_plus_plus/cell.rb +29 -41
  7. data/lib/csv_plus_plus/cli.rb +53 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -71
  9. data/lib/csv_plus_plus/color.rb +32 -7
  10. data/lib/csv_plus_plus/compiler.rb +98 -66
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
  12. data/lib/csv_plus_plus/entities/boolean.rb +26 -10
  13. data/lib/csv_plus_plus/entities/builtins.rb +66 -24
  14. data/lib/csv_plus_plus/entities/date.rb +42 -6
  15. data/lib/csv_plus_plus/entities/entity.rb +17 -69
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
  17. data/lib/csv_plus_plus/entities/function.rb +34 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +49 -10
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +30 -11
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
  23. data/lib/csv_plus_plus/entities/string.rb +23 -7
  24. data/lib/csv_plus_plus/entities.rb +7 -16
  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 +25 -2
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  33. data/lib/csv_plus_plus/error.rb +5 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +25 -10
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
  38. data/lib/csv_plus_plus/lexer.rb +64 -1
  39. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  40. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  41. data/lib/csv_plus_plus/modifier/expand.rb +78 -0
  42. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  43. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  44. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  45. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  46. data/lib/csv_plus_plus/modifier.rb +89 -160
  47. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  48. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  49. data/lib/csv_plus_plus/options/options.rb +97 -0
  50. data/lib/csv_plus_plus/options.rb +34 -77
  51. data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
  52. data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
  53. data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
  54. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  55. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  56. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  57. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  58. data/lib/csv_plus_plus/reader.rb +14 -0
  59. data/lib/csv_plus_plus/row.rb +53 -12
  60. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  61. data/lib/csv_plus_plus/runtime/position.rb +242 -0
  62. data/lib/csv_plus_plus/runtime/references.rb +115 -0
  63. data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
  64. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  65. data/lib/csv_plus_plus/runtime.rb +34 -191
  66. data/lib/csv_plus_plus/source_code.rb +71 -0
  67. data/lib/csv_plus_plus/template.rb +71 -39
  68. data/lib/csv_plus_plus/version.rb +2 -1
  69. data/lib/csv_plus_plus/writer/csv.rb +37 -8
  70. data/lib/csv_plus_plus/writer/excel.rb +25 -5
  71. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
  72. data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
  73. data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
  74. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  75. data/lib/csv_plus_plus/writer/open_document.rb +21 -2
  76. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
  77. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  78. data/lib/csv_plus_plus/writer.rb +79 -10
  79. data/lib/csv_plus_plus.rb +47 -18
  80. metadata +50 -21
  81. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  82. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  83. data/lib/csv_plus_plus/data_validation.rb +0 -138
  84. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
  85. data/lib/csv_plus_plus/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
  87. data/lib/csv_plus_plus/expand.rb +0 -20
  88. data/lib/csv_plus_plus/google_options.rb +0 -27
  89. data/lib/csv_plus_plus/graph.rb +0 -62
  90. data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
  91. data/lib/csv_plus_plus/references.rb +0 -68
  92. data/lib/csv_plus_plus/scope.rb +0 -196
  93. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  94. data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
  95. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
  96. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  97. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -0,0 +1,242 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Runtime
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
15
+ extend ::T::Sig
16
+
17
+ sig { params(cell: ::T.nilable(::CSVPlusPlus::Cell)).returns(::T.nilable(::CSVPlusPlus::Cell)) }
18
+ attr_writer :cell
19
+
20
+ sig { params(cell_index: ::T.nilable(::Integer)).returns(::T.nilable(::Integer)) }
21
+ attr_writer :cell_index
22
+
23
+ sig { params(line_number: ::T.nilable(::Integer)).returns(::T.nilable(::Integer)) }
24
+ attr_writer :line_number
25
+
26
+ sig { params(row_index: ::T.nilable(::Integer)).returns(::T.nilable(::Integer)) }
27
+ attr_writer :row_index
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
+
35
+ sig { returns(::CSVPlusPlus::Cell) }
36
+ # The current cell index. This will only be set when processing the CSV section
37
+ #
38
+ # @return [Cell]
39
+ def cell
40
+ @cell ||= ::T.let(nil, ::T.nilable(::CSVPlusPlus::Cell))
41
+ assert_initted!(@cell)
42
+ end
43
+
44
+ sig { returns(::Integer) }
45
+ # The current CSV cell index.
46
+ #
47
+ # This will only be set when processing the CSV section and will throw an exception otherwise. It is up to the
48
+ # caller (the compiler) to make sure it's called in the context of a compilation stage and/or a
49
+ # +#map_row+/+#map_rows+/+#map_lines+
50
+ #
51
+ # @return [Integer]
52
+ def cell_index
53
+ @cell_index ||= ::T.let(nil, ::T.nilable(::Integer))
54
+ assert_initted!(@cell_index)
55
+ end
56
+
57
+ sig { returns(::Integer) }
58
+ # The current CSV row index. This will only be set when processing the CSV section
59
+ #
60
+ # This will only be set when processing the CSV section and will throw an exception otherwise. It is up to the
61
+ # caller (the compiler) to make sure it's called in the context of a compilation stage and/or a
62
+ # +#map_row+/+#map_rows+/+#map_lines+
63
+ #
64
+ # @return [Integer]
65
+ def row_index
66
+ @row_index ||= ::T.let(nil, ::T.nilable(::Integer))
67
+ assert_initted!(@row_index)
68
+ end
69
+
70
+ sig { returns(::Integer) }
71
+ # The current line number being processed. The line number is based on the entire file, irregardless of if it's
72
+ # parsing the code section or the CSV section
73
+ #
74
+ # This will only be set when processing the csvpp file and will throw an exception otherwise. It is up to the
75
+ # caller (the compiler) to make sure it's called in the context of a compilation stage and/or a
76
+ # +#map_row+/+#map_rows+/+#map_lines+
77
+ #
78
+ # @return [Integer]
79
+ def line_number
80
+ @line_number ||= ::T.let(nil, ::T.nilable(::Integer))
81
+ assert_initted!(@line_number)
82
+ end
83
+
84
+ sig { void }
85
+ # Clean up the Tempfile we're using for parsing
86
+ def cleanup!
87
+ input&.close
88
+ input&.unlink
89
+ end
90
+
91
+ sig { returns(::T.nilable(::Tempfile)) }
92
+ # The currently available input for parsing. The tmp state will be re-written
93
+ # between parsing the code section and the CSV section
94
+ #
95
+ # @return [::Tempfile]
96
+ def input
97
+ @input ||= ::T.let(::Tempfile.new, ::T.nilable(::Tempfile))
98
+ end
99
+
100
+ sig do
101
+ type_parameters(:I, :O).params(
102
+ lines: ::T::Enumerable[::T.type_parameter(:I)],
103
+ block: ::T.proc.params(args0: ::T.type_parameter(:I)).returns(::T.type_parameter(:O))
104
+ ).returns(::T::Array[::T.type_parameter(:O)])
105
+ end
106
+ # Map over a csvpp file and keep track of line_number and row_index
107
+ #
108
+ # @param lines [Array]
109
+ #
110
+ # @return [Array]
111
+ def map_lines(lines, &block)
112
+ self.line_number = 1
113
+ lines.map do |line|
114
+ ret = block.call(line)
115
+ next_line!
116
+ ret
117
+ end
118
+ end
119
+
120
+ sig do
121
+ type_parameters(:I, :O)
122
+ .params(
123
+ row: ::T::Enumerable[::T.all(::T.type_parameter(:I), ::Object)],
124
+ block: ::T.proc.params(
125
+ cell: ::T.all(::T.type_parameter(:I), ::Object),
126
+ index: ::Integer
127
+ ).returns(::T.type_parameter(:O))
128
+ )
129
+ .returns(::T::Array[::T.type_parameter(:O)])
130
+ end
131
+ # Map over a single row and keep track of the cell and it's index
132
+ #
133
+ # @param row [Array<Cell>] The row to map each cell over
134
+ #
135
+ # @return [Array]
136
+ def map_row(row, &block)
137
+ row.map.with_index do |cell, index|
138
+ self.cell_index = index
139
+ self.cell = cell if cell.is_a?(::CSVPlusPlus::Cell)
140
+ block.call(cell, index)
141
+ end
142
+ end
143
+
144
+ sig do
145
+ type_parameters(:O).params(
146
+ rows: ::T::Enumerable[::CSVPlusPlus::Row],
147
+ block: ::T.proc.params(row: ::CSVPlusPlus::Row).returns(::T.type_parameter(:O))
148
+ ).returns(::T::Array[::T.type_parameter(:O)])
149
+ end
150
+ # Map over all rows and keep track of row and line numbers
151
+ #
152
+ # @param rows [Array<Row>] The rows to map over (and keep track of indexes)
153
+ #
154
+ # @return [Array]
155
+ def map_rows(rows, &block)
156
+ self.row_index = 0
157
+ map_lines(rows) do |row|
158
+ block.call(row)
159
+ end
160
+ end
161
+
162
+ sig do
163
+ type_parameters(:R)
164
+ .params(rows: ::T::Enumerable[::CSVPlusPlus::Row],
165
+ block: ::T.proc.params(cell: ::CSVPlusPlus::Cell, index: ::Integer).returns(::T.type_parameter(:R)))
166
+ .returns(::T::Array[::T::Array[::T.type_parameter(:R)]])
167
+ end
168
+ # Map over all +rows+ and over all of their +cells+, calling the +&block+ with each +Cell+
169
+ #
170
+ # @param rows [Array<Row>]
171
+ #
172
+ # @return [Array<Array>]
173
+ # rubocop:disable Naming/BlockForwarding
174
+ def map_all_cells(rows, &block)
175
+ self.row_index = 0
176
+ map_lines(rows) { |row| map_row(row.cells, &block) }
177
+ end
178
+ # rubocop:enable Naming/BlockForwarding
179
+
180
+ sig { returns(::Integer) }
181
+ # Return the current spreadsheet row number. It parallels +@row_index+ but starts at 1.
182
+ #
183
+ # @return [Integer, nil]
184
+ def rownum
185
+ row_index + 1
186
+ end
187
+
188
+ sig do
189
+ type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
190
+ end
191
+ # Each time we run a parse on the input, reset the runtime state starting at the beginning of the file
192
+ def start!(&block)
193
+ @row_index = @cell_index = 0
194
+
195
+ ret = block.call
196
+ finish!
197
+ ret
198
+ end
199
+
200
+ sig { params(data: ::String).void }
201
+ # We mutate the input over and over. It's ok because it's just a Tempfile
202
+ #
203
+ # @param data [::String] The data to rewrite our input file to
204
+ def rewrite_input!(data)
205
+ input&.truncate(0)
206
+ input&.write(data)
207
+ input&.rewind
208
+ end
209
+
210
+ private
211
+
212
+ sig do
213
+ type_parameters(:R).params(runtime_value: ::T.nilable(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
214
+ end
215
+ def assert_initted!(runtime_value)
216
+ ::T.must_because(runtime_value) do
217
+ 'Runtime value accessed without an initialized runtime. Make sure you call Runtime#start! or ' \
218
+ 'Runtime#start_at_csv! first.'
219
+ end
220
+ end
221
+
222
+ sig { void }
223
+ # Called to mark the trackers dirty. It should be an error to use them outside of an initialized context.
224
+ def finish!
225
+ @line_number = nil
226
+ @row_index = nil
227
+ @cell_index = nil
228
+ @cell = nil
229
+ end
230
+
231
+ sig { returns(::Integer) }
232
+ # Increment state to the next line
233
+ #
234
+ # @return [Integer]
235
+ def next_line!
236
+ self.row_index += 1
237
+ self.line_number += 1
238
+ end
239
+ end
240
+ # rubocop:enable Metrics/ClassLength
241
+ end
242
+ end
@@ -0,0 +1,115 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Runtime
6
+ # References in an AST that need to be resolved
7
+ #
8
+ # @attr functions [Array<Entities::Function>] Functions references
9
+ # @attr variables [Array<Entities::Variable>] Variable references
10
+ class References
11
+ extend ::T::Sig
12
+
13
+ sig { returns(::T::Array[::CSVPlusPlus::Entities::FunctionCall]) }
14
+ attr_accessor :functions
15
+
16
+ sig { returns(::T::Array[::CSVPlusPlus::Entities::Reference]) }
17
+ attr_accessor :variables
18
+
19
+ sig do
20
+ params(
21
+ ast: ::CSVPlusPlus::Entities::Entity,
22
+ position: ::CSVPlusPlus::Runtime::Position,
23
+ scope: ::CSVPlusPlus::Runtime::Scope
24
+ ).returns(::CSVPlusPlus::Runtime::References)
25
+ end
26
+ # Extract references from an AST and return them in a new +References+ object
27
+ #
28
+ # @param ast [Entity] An +Entity+ to do a depth first search on for references. Entities can be
29
+ # infinitely deep because they can contain other function calls as params to a function call
30
+ # @param scope [Scope] The current scope
31
+ #
32
+ # @return [References]
33
+ def self.extract(ast, position, scope)
34
+ new.tap do |refs|
35
+ ::CSVPlusPlus::Runtime::Graph.depth_first_search(ast) do |node|
36
+ unless node.is_a?(::CSVPlusPlus::Entities::FunctionCall) || node.is_a?(::CSVPlusPlus::Entities::Reference)
37
+ next
38
+ end
39
+
40
+ refs.functions << node if function_reference?(node, scope)
41
+ refs.variables << node if variable_reference?(node, position, scope)
42
+ end
43
+ end
44
+ end
45
+
46
+ sig do
47
+ params(
48
+ node: ::CSVPlusPlus::Entities::Entity,
49
+ position: ::CSVPlusPlus::Runtime::Position,
50
+ scope: ::CSVPlusPlus::Runtime::Scope
51
+ ).returns(::T::Boolean)
52
+ end
53
+ # Is the node a resolvable variable reference?
54
+ #
55
+ # @param node [Entity] The node to check if it's resolvable
56
+ # @param scope [Scope] The current scope
57
+ #
58
+ # @return [boolean]
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)
64
+
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
71
+ )
72
+ )
73
+ end
74
+ private_class_method :variable_reference?
75
+
76
+ sig do
77
+ params(node: ::CSVPlusPlus::Entities::Entity, scope: ::CSVPlusPlus::Runtime::Scope).returns(::T::Boolean)
78
+ end
79
+ # Is the node a resolvable function reference?
80
+ #
81
+ # @param node [Entity] The node to check if it's resolvable
82
+ # @param scope [Scope] The current scope
83
+ #
84
+ # @return [boolean]
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))
88
+ end
89
+ private_class_method :function_reference?
90
+
91
+ sig { void }
92
+ # Create an object with empty references. The caller will build them up as it depth-first-searches
93
+ def initialize
94
+ @functions = ::T.let([], ::T::Array[::CSVPlusPlus::Entities::FunctionCall])
95
+ @variables = ::T.let([], ::T::Array[::CSVPlusPlus::Entities::Reference])
96
+ end
97
+
98
+ sig { params(other: ::CSVPlusPlus::Runtime::References).returns(::T::Boolean) }
99
+ # @param other [References]
100
+ #
101
+ # @return [boolean]
102
+ def ==(other)
103
+ @functions == other.functions && @variables == other.variables
104
+ end
105
+
106
+ sig { returns(::T::Boolean) }
107
+ # Are there any references to be resolved?
108
+ #
109
+ # @return [::T::Boolean]
110
+ def empty?
111
+ @functions.empty? && @variables.empty?
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,132 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module CSVPlusPlus
5
+ module Runtime
6
+ # The runtime state of the compiler (the current +line_number+/+row_index+, +cell+ being processed, etc) for parsing
7
+ # a given file. We take multiple runs through the input file for parsing so it's really convenient to have a
8
+ # central place for these things to be managed.
9
+ #
10
+ # @attr_reader position [Runtime::Position]
11
+ # @attr_reader scope [Runtime::Scope]
12
+ # @attr_reader source_code [SourceCode]
13
+ class Runtime
14
+ extend ::T::Sig
15
+
16
+ sig { returns(::CSVPlusPlus::Runtime::Position) }
17
+ attr_reader :position
18
+
19
+ sig { returns(::CSVPlusPlus::Runtime::Scope) }
20
+ attr_reader :scope
21
+
22
+ sig { returns(::CSVPlusPlus::SourceCode) }
23
+ attr_reader :source_code
24
+
25
+ sig do
26
+ params(
27
+ source_code: ::CSVPlusPlus::SourceCode,
28
+ position: ::T.nilable(::CSVPlusPlus::Runtime::Position),
29
+ scope: ::T.nilable(::CSVPlusPlus::Runtime::Scope)
30
+ ).void
31
+ end
32
+ # @param position [Position, nil] The (optional) position to start at
33
+ # @param source_code [SourceCode] The source code being compiled
34
+ # @param scope [Runtime::Scope, nil] The (optional) scope if it already exists
35
+ def initialize(source_code:, position: nil, scope: nil)
36
+ @source_code = source_code
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
+ )
42
+ end
43
+
44
+ sig { params(var_id: ::Symbol).returns(::CSVPlusPlus::Entities::Reference) }
45
+ # Bind +var_id+ to the current cell
46
+ #
47
+ # @param var_id [Symbol] The name of the variable to bind the cell reference to
48
+ #
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
56
+ end
57
+
58
+ sig do
59
+ params(var_id: ::Symbol, expand: ::CSVPlusPlus::Modifier::Expand).returns(::CSVPlusPlus::Entities::Reference)
60
+ end
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.
63
+ #
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
74
+ end
75
+
76
+ sig { returns(::T::Boolean) }
77
+ # Is the parser currently inside of the CSV section?
78
+ #
79
+ # @return [T::Boolean]
80
+ def parsing_csv_section?
81
+ source_code.in_csv_section?(position.line_number)
82
+ end
83
+
84
+ sig { params(ast: ::CSVPlusPlus::Entities::Entity).returns(::CSVPlusPlus::Entities::Entity) }
85
+ # Resolve all values in the ast of the current cell being processed
86
+ #
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
106
+ end
107
+ # rubocop:enable Metrics/MethodLength
108
+
109
+ sig do
110
+ type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
111
+ end
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)
117
+ end
118
+ # rubocop:enable Naming/BlockForwarding
119
+
120
+ sig do
121
+ type_parameters(:R).params(block: ::T.proc.returns(::T.type_parameter(:R))).returns(::T.type_parameter(:R))
122
+ end
123
+ # Reset the runtime state starting at the CSV section
124
+ # rubocop:disable Naming/BlockForwarding
125
+ def start_at_csv!(&block)
126
+ position.line_number = source_code.length_of_code_section + 1
127
+ position.start!(&block)
128
+ end
129
+ # rubocop:enable Naming/BlockForwarding
130
+ end
131
+ end
132
+ end