csv_plus_plus 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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