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
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module CSVPlusPlus
|
|
5
|
-
# The Google-specific options a user can supply.
|
|
6
|
-
#
|
|
7
|
-
# @attr sheet_id [String] The ID of the Google Sheet to write to.
|
|
8
|
-
class GoogleOptions
|
|
9
|
-
extend ::T::Sig
|
|
10
|
-
|
|
11
|
-
sig { returns(::String) }
|
|
12
|
-
attr_reader :sheet_id
|
|
13
|
-
|
|
14
|
-
sig { params(sheet_id: ::String).void }
|
|
15
|
-
# @param sheet_id [String] The unique ID Google uses to reference the sheet
|
|
16
|
-
def initialize(sheet_id)
|
|
17
|
-
@sheet_id = sheet_id
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
sig { returns(::String) }
|
|
21
|
-
# Format a string with a verbose description of what we're doing with the options
|
|
22
|
-
#
|
|
23
|
-
# @return [String]
|
|
24
|
-
def verbose_summary
|
|
25
|
-
<<~SUMMARY
|
|
26
|
-
## Google Sheets Options
|
|
27
|
-
|
|
28
|
-
> Sheet ID | #{@sheet_id}
|
|
29
|
-
SUMMARY
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# typed: false
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module CSVPlusPlus
|
|
5
|
-
# Common methods to be mixed into the Racc parsers
|
|
6
|
-
#
|
|
7
|
-
# @attr_reader tokens [Array]
|
|
8
|
-
module Lexer
|
|
9
|
-
attr_reader :tokens
|
|
10
|
-
|
|
11
|
-
# Initialize a lexer instance with an empty +@tokens+
|
|
12
|
-
def initialize(tokens: [])
|
|
13
|
-
@tokens = tokens
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Used by racc to iterate each token
|
|
17
|
-
#
|
|
18
|
-
# @return [Array<(String, String)>]
|
|
19
|
-
def next_token
|
|
20
|
-
@tokens.shift
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Orchestate the tokenizing, parsing and error handling of parsing input. Each instance will implement their own
|
|
24
|
-
# #tokenizer method
|
|
25
|
-
#
|
|
26
|
-
# @return [Lexer#return_value] Each instance will define it's own +return_value+ with the result of parsing
|
|
27
|
-
def parse(input, runtime)
|
|
28
|
-
return if input.nil?
|
|
29
|
-
|
|
30
|
-
return return_value unless anything_to_parse?(input)
|
|
31
|
-
|
|
32
|
-
@runtime = runtime
|
|
33
|
-
|
|
34
|
-
tokenize(input, runtime)
|
|
35
|
-
do_parse
|
|
36
|
-
return_value
|
|
37
|
-
rescue ::Racc::ParseError => e
|
|
38
|
-
runtime.raise_formula_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
|
|
39
|
-
rescue ::CSVPlusPlus::Error::ModifierValidationError => e
|
|
40
|
-
raise(::CSVPlusPlus::Error::ModifierSyntaxError.from_validation_error(runtime, e))
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
TOKEN_LIBRARY = {
|
|
44
|
-
A1_NOTATION: [::CSVPlusPlus::Entities::CellReference::A1_NOTATION_REGEXP, :A1_NOTATION],
|
|
45
|
-
FALSE: [/false/i, :FALSE],
|
|
46
|
-
HEX_COLOR: [::CSVPlusPlus::Color::HEX_STRING_REGEXP, :HEX_COLOR],
|
|
47
|
-
ID: [/[$!\w:]+/, :ID],
|
|
48
|
-
INFIX_OP: [%r{\^|\+|-|\*|/|&|<|>|<=|>=|<>}, :INFIX_OP],
|
|
49
|
-
NUMBER: [/-?[\d.]+/, :NUMBER],
|
|
50
|
-
STRING: [%r{"(?:[^"\\]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4}))*"}, :STRING],
|
|
51
|
-
TRUE: [/true/i, :TRUE],
|
|
52
|
-
VAR_REF: [/\$\$/, :VAR_REF]
|
|
53
|
-
}.freeze
|
|
54
|
-
public_constant :TOKEN_LIBRARY
|
|
55
|
-
|
|
56
|
-
private
|
|
57
|
-
|
|
58
|
-
def tokenize(input, runtime)
|
|
59
|
-
return if input.nil?
|
|
60
|
-
|
|
61
|
-
t = tokenizer.scan(input)
|
|
62
|
-
|
|
63
|
-
until t.scanner.empty?
|
|
64
|
-
next if t.matches_ignore?
|
|
65
|
-
|
|
66
|
-
return if t.stop?
|
|
67
|
-
|
|
68
|
-
t.scan_tokens!
|
|
69
|
-
consume_token(t, runtime)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
@tokens << %i[EOL EOL]
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def consume_token(tokenizer, runtime)
|
|
76
|
-
if tokenizer.last_token
|
|
77
|
-
@tokens << [tokenizer.last_token, tokenizer.last_match]
|
|
78
|
-
elsif tokenizer.scan_catchall
|
|
79
|
-
@tokens << [tokenizer.last_match, tokenizer.last_match]
|
|
80
|
-
# TODO: checking the +parse_subject+ like this is a little hacky... but we need to know if we're parsing
|
|
81
|
-
# modifiers or code_section (or formulas in a cell)
|
|
82
|
-
elsif parse_subject == 'modifier'
|
|
83
|
-
runtime.raise_modifier_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
|
|
84
|
-
else
|
|
85
|
-
runtime.raise_formula_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
# typed: true
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module CSVPlusPlus
|
|
5
|
-
module Runtime
|
|
6
|
-
# Methods for classes that need to manage +@variables+ and +@functions+
|
|
7
|
-
module CanDefineReferences
|
|
8
|
-
# Define a (or re-define an existing) variable
|
|
9
|
-
#
|
|
10
|
-
# @param id [String, Symbol] The identifier for the variable
|
|
11
|
-
# @param entity [Entity] The value (entity) the variable holds
|
|
12
|
-
#
|
|
13
|
-
# @return [Entity] The value of the variable (+entity+)
|
|
14
|
-
def def_variable(id, entity)
|
|
15
|
-
@variables[id.to_sym] = entity
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Define (or re-define existing) variables
|
|
19
|
-
#
|
|
20
|
-
# @param vars [Hash<Symbol, Variable>] Variables to define
|
|
21
|
-
def def_variables(vars)
|
|
22
|
-
vars.each { |id, entity| def_variable(id, entity) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# Define a (or re-define an existing) function
|
|
26
|
-
#
|
|
27
|
-
# @param id [String, Symbol] The identifier for the function
|
|
28
|
-
# @param entity [Entities::Function] The defined function
|
|
29
|
-
#
|
|
30
|
-
# @return [Entities::Function] The defined function
|
|
31
|
-
def def_function(id, entity)
|
|
32
|
-
@functions[id.to_sym] = entity
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Is the variable defined?
|
|
36
|
-
#
|
|
37
|
-
# @param var_id [Symbol, String] The identifier of the variable
|
|
38
|
-
#
|
|
39
|
-
# @return [boolean]
|
|
40
|
-
def defined_variable?(var_id)
|
|
41
|
-
@variables.key?(var_id.to_sym)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# Is the function defined?
|
|
45
|
-
#
|
|
46
|
-
# @param fn_id [Symbol, String] The identifier of the function
|
|
47
|
-
#
|
|
48
|
-
# @return [boolean]
|
|
49
|
-
def defined_function?(fn_id)
|
|
50
|
-
@functions.key?(fn_id.to_sym)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Provide a summary of the functions and variables compiled (to show in verbose mode)
|
|
54
|
-
#
|
|
55
|
-
# @return [String]
|
|
56
|
-
def verbose_summary
|
|
57
|
-
<<~SUMMARY
|
|
58
|
-
# Code Section Summary
|
|
59
|
-
|
|
60
|
-
## Resolved Variables
|
|
61
|
-
|
|
62
|
-
#{variable_summary}
|
|
63
|
-
|
|
64
|
-
## Functions
|
|
65
|
-
|
|
66
|
-
#{function_summary}
|
|
67
|
-
SUMMARY
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def variable_summary
|
|
73
|
-
return '(no variables defined)' if @variables.empty?
|
|
74
|
-
|
|
75
|
-
@variables.map { |k, v| "#{k} := #{v}" }
|
|
76
|
-
.join("\n")
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def function_summary
|
|
80
|
-
return '(no functions defined)' if @functions.empty?
|
|
81
|
-
|
|
82
|
-
@functions.map { |k, f| "#{k}: #{f}" }
|
|
83
|
-
.join("\n")
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
end
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# typed: false
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module CSVPlusPlus
|
|
5
|
-
module Runtime
|
|
6
|
-
# Methods for resolving functions and variables. These should be included onto a class that has +@variables+ and
|
|
7
|
-
# +@functions+ instance variables.
|
|
8
|
-
module CanResolveReferences
|
|
9
|
-
# Resolve all values in the ast of the current cell being processed
|
|
10
|
-
#
|
|
11
|
-
# @return [Entity]
|
|
12
|
-
def resolve_cell_value
|
|
13
|
-
return unless (ast = @cell&.ast)
|
|
14
|
-
|
|
15
|
-
last_round = nil
|
|
16
|
-
loop do
|
|
17
|
-
refs = ::CSVPlusPlus::Runtime::References.extract(ast, self)
|
|
18
|
-
return ast if refs.empty?
|
|
19
|
-
|
|
20
|
-
# TODO: throw an error here instead I think - basically we did a round and didn't make progress
|
|
21
|
-
return ast if last_round == refs
|
|
22
|
-
|
|
23
|
-
ast = resolve_functions(resolve_variables(ast, refs.variables), refs.functions)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Bind +var_id+ to the current cell
|
|
28
|
-
#
|
|
29
|
-
# @param var_id [Symbol] The name of the variable to bind the cell reference to
|
|
30
|
-
#
|
|
31
|
-
# @return [CellReference]
|
|
32
|
-
def bind_variable_to_cell(var_id)
|
|
33
|
-
def_variable(
|
|
34
|
-
var_id,
|
|
35
|
-
::CSVPlusPlus::Entities::CellReference.new(
|
|
36
|
-
cell_index: @cell_index,
|
|
37
|
-
row_index: @row_index
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Bind +var_id+ relative to an ![[expand]] modifier.
|
|
43
|
-
#
|
|
44
|
-
# @param var_id [Symbol] The name of the variable to bind the cell reference to
|
|
45
|
-
# @param expand [Expand] The expand where the variable is accessible (where it will be bound relative to)
|
|
46
|
-
#
|
|
47
|
-
# @return [CellReference]
|
|
48
|
-
def bind_variable_in_expand(var_id, expand)
|
|
49
|
-
def_variable(
|
|
50
|
-
var_id,
|
|
51
|
-
::CSVPlusPlus::Entities::CellReference.new(
|
|
52
|
-
scoped_to_expand: expand,
|
|
53
|
-
cell_index: @cell_index
|
|
54
|
-
)
|
|
55
|
-
)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Variables outside of an ![[expand=...] are always in scope. If it's defined within an expand then things
|
|
59
|
-
# get trickier because the variable is only in scope while we're processing cells within that expand.
|
|
60
|
-
#
|
|
61
|
-
# @param var_id [Symbol] The variable's identifier that we are checking if it's in scope
|
|
62
|
-
#
|
|
63
|
-
# @return [boolean]
|
|
64
|
-
def in_scope?(var_id)
|
|
65
|
-
value = @variables[var_id]
|
|
66
|
-
|
|
67
|
-
raise_modifier_syntax_error('Undefined variable reference', var_id.to_s) if value.nil?
|
|
68
|
-
|
|
69
|
-
expand = value.type == ::CSVPlusPlus::Entities::Type::CellReference && value.scoped_to_expand
|
|
70
|
-
return true unless expand
|
|
71
|
-
|
|
72
|
-
unless expand.starts_at
|
|
73
|
-
raise(::CSVPlusPlus::Error::Error, 'Must call Template.expand_rows! before checking the scope of expands.')
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
@row_index >= expand.starts_at && (expand.ends_at.nil? || row_index <= expand.ends_at)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
private
|
|
80
|
-
|
|
81
|
-
# Resolve all variable references defined statically in the code section
|
|
82
|
-
# def resolve_static_variables!
|
|
83
|
-
# last_var_dependencies = {}
|
|
84
|
-
# loop do
|
|
85
|
-
# var_dependencies, resolution_order = variable_resolution_order(only_static_vars(variables))
|
|
86
|
-
# return if var_dependencies == last_var_dependencies
|
|
87
|
-
#
|
|
88
|
-
# # TODO: make the contract better here
|
|
89
|
-
# @variables = resolve_dependencies(var_dependencies, resolution_order, variables)
|
|
90
|
-
# last_var_dependencies = var_dependencies.clone
|
|
91
|
-
# end
|
|
92
|
-
# end
|
|
93
|
-
#
|
|
94
|
-
# def only_static_vars(var_dependencies)
|
|
95
|
-
# var_dependencies.reject { |k| @runtime.builtin_variable?(k) }
|
|
96
|
-
# end
|
|
97
|
-
|
|
98
|
-
def resolve_functions(ast, refs)
|
|
99
|
-
refs.reduce(ast.dup) do |acc, elem|
|
|
100
|
-
function_replace(acc, elem.id, resolve_function(elem.id))
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def resolve_variables(ast, refs)
|
|
105
|
-
refs.reduce(ast.dup) do |acc, elem|
|
|
106
|
-
variable_replace(acc, elem.id, resolve_variable(elem.id))
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Make a copy of the AST represented by +node+ and replace +fn_id+ with +replacement+ throughout
|
|
111
|
-
# rubocop:disable Metrics/MethodLength
|
|
112
|
-
def function_replace(node, fn_id, replacement)
|
|
113
|
-
if node.type == ::CSVPlusPlus::Entities::Type::FunctionCall && node.id == fn_id
|
|
114
|
-
call_function_or_builtin(replacement, node)
|
|
115
|
-
elsif node.type == ::CSVPlusPlus::Entities::Type::FunctionCall
|
|
116
|
-
# not our function, but continue our depth first search on it
|
|
117
|
-
::CSVPlusPlus::Entities::FunctionCall.new(
|
|
118
|
-
node.id,
|
|
119
|
-
node.arguments.map { |n| function_replace(n, fn_id, replacement) },
|
|
120
|
-
infix: node.infix
|
|
121
|
-
)
|
|
122
|
-
else
|
|
123
|
-
node
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
# rubocop:enable Metrics/MethodLength
|
|
127
|
-
|
|
128
|
-
def resolve_function(fn_id)
|
|
129
|
-
id = fn_id.to_sym
|
|
130
|
-
return @functions[id] if defined_function?(id)
|
|
131
|
-
|
|
132
|
-
::CSVPlusPlus::Entities::Builtins::FUNCTIONS[id]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def call_function_or_builtin(function_or_builtin, function_call)
|
|
136
|
-
if function_or_builtin.type == ::CSVPlusPlus::Entities::Type::Function
|
|
137
|
-
call_function(function_or_builtin, function_call)
|
|
138
|
-
else
|
|
139
|
-
function_or_builtin.resolve_fn.call(self, function_call.arguments)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def call_function(function, function_call)
|
|
144
|
-
i = 0
|
|
145
|
-
function.arguments.reduce(function.body.dup) do |ast, argument|
|
|
146
|
-
variable_replace(ast, argument, function_call.arguments[i]).tap do
|
|
147
|
-
i += 1
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Make a copy of the AST represented by +node+ and replace +var_id+ with +replacement+ throughout
|
|
153
|
-
def variable_replace(node, var_id, replacement)
|
|
154
|
-
if node.type == ::CSVPlusPlus::Entities::Type::FunctionCall
|
|
155
|
-
arguments = node.arguments.map { |n| variable_replace(n, var_id, replacement) }
|
|
156
|
-
# TODO: refactor these places where we copy functions... it's brittle with the kwargs
|
|
157
|
-
::CSVPlusPlus::Entities::FunctionCall.new(node.id, arguments, infix: node.infix)
|
|
158
|
-
elsif node.type == ::CSVPlusPlus::Entities::Type::Variable && node.id == var_id
|
|
159
|
-
replacement
|
|
160
|
-
else
|
|
161
|
-
node
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def resolve_variable(var_id)
|
|
166
|
-
id = var_id.to_sym
|
|
167
|
-
return @variables[id] if defined_variable?(id)
|
|
168
|
-
|
|
169
|
-
raise_formula_syntax_error('Undefined variable', var_id) unless builtin_variable?(var_id)
|
|
170
|
-
|
|
171
|
-
::CSVPlusPlus::Entities::Builtins::VARIABLES[var_id.to_sym].resolve_fn.call(self)
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# def check_unbound_vars(dependencies, variables)
|
|
175
|
-
# unbound_vars = dependencies.values.flatten - variables.keys
|
|
176
|
-
# return if unbound_vars.empty?
|
|
177
|
-
#
|
|
178
|
-
# raise_formula_syntax_error('Undefined variables', unbound_vars.map(&:to_s).join(', '))
|
|
179
|
-
# end
|
|
180
|
-
|
|
181
|
-
# def variable_resolution_order(variables)
|
|
182
|
-
# # we have a hash of variables => ASTs but they might have references to each other, so
|
|
183
|
-
# # we need to interpolate them first (before interpolating the cell values)
|
|
184
|
-
# var_dependencies = ::CSVPlusPlus::Graph.dependency_graph(variables, @runtime)
|
|
185
|
-
# # are there any references that we don't have variables for? (undefined variable)
|
|
186
|
-
# check_unbound_vars(var_dependencies, variables)
|
|
187
|
-
#
|
|
188
|
-
# # a topological sort will give us the order of dependencies
|
|
189
|
-
# [var_dependencies, ::CSVPlusPlus::Graph.topological_sort(var_dependencies)]
|
|
190
|
-
# # TODO: don't expose this exception directly to the caller
|
|
191
|
-
# rescue ::TSort::Cyclic
|
|
192
|
-
# @runtime.raise_formula_syntax_error('Cyclic variable dependency detected', var_refs.keys)
|
|
193
|
-
# end
|
|
194
|
-
|
|
195
|
-
# def resolve_dependencies(var_dependencies, resolution_order, variables)
|
|
196
|
-
# {}.tap do |resolved_vars|
|
|
197
|
-
# # for each var and each dependency it has, build up and mutate resolved_vars
|
|
198
|
-
# resolution_order.each do |var|
|
|
199
|
-
# resolved_vars[var] = variables[var].dup
|
|
200
|
-
#
|
|
201
|
-
# var_dependencies[var].each do |dependency|
|
|
202
|
-
# resolved_vars[var] = variable_replace(resolved_vars[var], dependency, variables[dependency])
|
|
203
|
-
# end
|
|
204
|
-
# end
|
|
205
|
-
# end
|
|
206
|
-
# end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
module CSVPlusPlus
|
|
5
|
-
module Writer
|
|
6
|
-
# Some shared functionality that all Writers should build on
|
|
7
|
-
#
|
|
8
|
-
# @attr_reader options [Options] The supplied options - some of which are relevant for our writer instance
|
|
9
|
-
# @attr_reader runtime [Runtime] The current runtime - needed to resolve variables and display useful error messages
|
|
10
|
-
class BaseWriter
|
|
11
|
-
extend ::T::Sig
|
|
12
|
-
extend ::T::Helpers
|
|
13
|
-
|
|
14
|
-
abstract!
|
|
15
|
-
|
|
16
|
-
sig { returns(::CSVPlusPlus::Options) }
|
|
17
|
-
attr_reader :options
|
|
18
|
-
|
|
19
|
-
sig { returns(::CSVPlusPlus::Runtime::Runtime) }
|
|
20
|
-
attr_reader :runtime
|
|
21
|
-
|
|
22
|
-
protected
|
|
23
|
-
|
|
24
|
-
sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
|
|
25
|
-
# Open a CSV outputter to the +output_filename+ specified by the +Options+
|
|
26
|
-
#
|
|
27
|
-
# @param options [Options] The supplied options.
|
|
28
|
-
# @param runtime [Runtime] The current runtime.
|
|
29
|
-
def initialize(options, runtime)
|
|
30
|
-
@options = options
|
|
31
|
-
@runtime = runtime
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
sig { abstract.params(template: ::CSVPlusPlus::Template).void }
|
|
35
|
-
# Write the given +template+.
|
|
36
|
-
#
|
|
37
|
-
# @param template [Template]
|
|
38
|
-
def write(template); end
|
|
39
|
-
|
|
40
|
-
sig { abstract.void }
|
|
41
|
-
# Write a backup of the current spreadsheet.
|
|
42
|
-
def write_backup; end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
end
|