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.
- checksums.yaml +4 -4
- data/README.md +13 -3
- data/docs/CHANGELOG.md +18 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
- data/lib/csv_plus_plus/cell.rb +1 -35
- data/lib/csv_plus_plus/cli.rb +43 -80
- data/lib/csv_plus_plus/cli_flag.rb +77 -70
- data/lib/csv_plus_plus/color.rb +1 -1
- data/lib/csv_plus_plus/compiler.rb +31 -21
- data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
- data/lib/csv_plus_plus/entities/boolean.rb +16 -9
- data/lib/csv_plus_plus/entities/builtins.rb +68 -40
- data/lib/csv_plus_plus/entities/date.rb +14 -11
- data/lib/csv_plus_plus/entities/entity.rb +11 -29
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
- data/lib/csv_plus_plus/entities/function.rb +22 -11
- data/lib/csv_plus_plus/entities/function_call.rb +35 -11
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +15 -10
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
- data/lib/csv_plus_plus/entities/string.rb +13 -10
- data/lib/csv_plus_plus/entities.rb +2 -18
- data/lib/csv_plus_plus/error/cli_error.rb +17 -0
- data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
- data/lib/csv_plus_plus/error/error.rb +18 -5
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +1 -1
- data/lib/csv_plus_plus/error.rb +4 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +18 -8
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
- data/lib/csv_plus_plus/lexer.rb +40 -1
- data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
- data/lib/csv_plus_plus/modifier/expand.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +6 -1
- data/lib/csv_plus_plus/options/file_options.rb +49 -0
- data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
- data/lib/csv_plus_plus/options/options.rb +102 -0
- data/lib/csv_plus_plus/options.rb +22 -110
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
- data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
- data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
- data/lib/csv_plus_plus/reader/csv.rb +50 -0
- data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
- data/lib/csv_plus_plus/reader/reader.rb +27 -0
- data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
- data/lib/csv_plus_plus/reader.rb +14 -0
- data/lib/csv_plus_plus/runtime/graph.rb +6 -6
- data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
- data/lib/csv_plus_plus/runtime/references.rb +32 -27
- data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +9 -9
- data/lib/csv_plus_plus/source_code.rb +14 -9
- data/lib/csv_plus_plus/template.rb +17 -12
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/csv.rb +32 -5
- data/lib/csv_plus_plus/writer/excel.rb +19 -6
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
- data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
- data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
- data/lib/csv_plus_plus/writer/merger.rb +56 -0
- data/lib/csv_plus_plus/writer/open_document.rb +16 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +58 -19
- data/lib/csv_plus_plus.rb +26 -14
- metadata +43 -18
- data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
- data/lib/csv_plus_plus/entities/variable.rb +0 -37
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
- data/lib/csv_plus_plus/google_options.rb +0 -32
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
- data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
- 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,
|
|
12
|
+
def self.variable_references(ast, include_runtime_variables: false)
|
|
13
13
|
depth_first_search(ast) do |node|
|
|
14
|
-
next unless node.
|
|
14
|
+
next unless node.is_a?(::CSVPlusPlus::Entities::Reference)
|
|
15
15
|
|
|
16
|
-
node.id if
|
|
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
|
|
21
|
+
def self.dependency_graph(variables)
|
|
22
22
|
::CSVPlusPlus::Runtime::Graph::DependencyGraph[
|
|
23
|
-
variables.map { |var_id, ast| [var_id, variable_references(ast
|
|
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.
|
|
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
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
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/
|
|
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::
|
|
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
|
-
|
|
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
|
|
30
|
+
# @param scope [Scope] The current scope
|
|
31
31
|
#
|
|
32
32
|
# @return [References]
|
|
33
|
-
def self.extract(ast,
|
|
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.
|
|
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,
|
|
43
|
-
refs.variables << node if variable_reference?(node,
|
|
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(
|
|
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
|
|
56
|
+
# @param scope [Scope] The current scope
|
|
55
57
|
#
|
|
56
58
|
# @return [boolean]
|
|
57
|
-
def self.variable_reference?(node,
|
|
58
|
-
return false unless node.
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"#{node.
|
|
65
|
-
node.
|
|
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
|
-
|
|
72
|
+
)
|
|
68
73
|
end
|
|
69
74
|
private_class_method :variable_reference?
|
|
70
75
|
|
|
71
76
|
sig do
|
|
72
|
-
params(node: ::CSVPlusPlus::Entities::Entity,
|
|
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
|
|
82
|
+
# @param scope [Scope] The current scope
|
|
78
83
|
#
|
|
79
84
|
# @return [boolean]
|
|
80
|
-
def self.function_reference?(node,
|
|
81
|
-
node.
|
|
82
|
-
&& (
|
|
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::
|
|
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
|
|
11
|
-
#
|
|
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
|
-
|
|
21
|
-
|
|
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(::
|
|
28
|
-
attr_reader :
|
|
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
|
-
|
|
37
|
-
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
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(
|
|
52
|
-
#
|
|
44
|
+
sig { params(var_id: ::Symbol).returns(::CSVPlusPlus::Entities::Reference) }
|
|
45
|
+
# Bind +var_id+ to the current cell
|
|
53
46
|
#
|
|
54
|
-
# @param
|
|
47
|
+
# @param var_id [Symbol] The name of the variable to bind the cell reference to
|
|
55
48
|
#
|
|
56
|
-
# @return [
|
|
57
|
-
def
|
|
58
|
-
::CSVPlusPlus::Entities::
|
|
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
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
# @
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
88
|
-
|
|
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
|
|
95
|
-
#
|
|
96
|
-
# @
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
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
|
-
#
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
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
|