csv_plus_plus 0.1.1 → 0.1.2
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/CHANGELOG.md +8 -0
- data/README.md +18 -62
- data/lib/csv_plus_plus/benchmarked_compiler.rb +62 -0
- data/lib/csv_plus_plus/{code_section.rb → can_define_references.rb} +22 -35
- data/lib/csv_plus_plus/can_resolve_references.rb +8 -0
- data/lib/csv_plus_plus/cell.rb +3 -3
- data/lib/csv_plus_plus/cli.rb +24 -7
- data/lib/csv_plus_plus/color.rb +12 -6
- data/lib/csv_plus_plus/compiler.rb +156 -0
- data/lib/csv_plus_plus/data_validation.rb +138 -0
- data/lib/csv_plus_plus/{language → entities}/ast_builder.rb +3 -5
- data/lib/csv_plus_plus/entities/boolean.rb +31 -0
- data/lib/csv_plus_plus/{language → entities}/builtins.rb +2 -4
- data/lib/csv_plus_plus/entities/cell_reference.rb +60 -0
- data/lib/csv_plus_plus/entities/date.rb +30 -0
- data/lib/csv_plus_plus/entities/entity.rb +84 -0
- data/lib/csv_plus_plus/entities/function.rb +33 -0
- data/lib/csv_plus_plus/entities/function_call.rb +35 -0
- data/lib/csv_plus_plus/entities/number.rb +34 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +26 -0
- data/lib/csv_plus_plus/entities/string.rb +29 -0
- data/lib/csv_plus_plus/entities/variable.rb +25 -0
- data/lib/csv_plus_plus/entities.rb +33 -0
- data/lib/csv_plus_plus/error/error.rb +10 -0
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +36 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +27 -0
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +49 -0
- data/lib/csv_plus_plus/{language → error}/syntax_error.rb +6 -14
- data/lib/csv_plus_plus/error/writer_error.rb +9 -0
- data/lib/csv_plus_plus/error.rb +9 -2
- data/lib/csv_plus_plus/expand.rb +3 -1
- data/lib/csv_plus_plus/google_api_client.rb +4 -0
- data/lib/csv_plus_plus/lexer/lexer.rb +13 -6
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +73 -65
- data/lib/csv_plus_plus/{language → parser}/cell_value.tab.rb +20 -20
- data/lib/csv_plus_plus/{language → parser}/code_section.tab.rb +83 -87
- data/lib/csv_plus_plus/parser/modifier.tab.rb +484 -0
- data/lib/csv_plus_plus/references.rb +68 -0
- data/lib/csv_plus_plus/row.rb +0 -3
- data/lib/csv_plus_plus/runtime.rb +199 -0
- data/lib/csv_plus_plus/scope.rb +196 -0
- data/lib/csv_plus_plus/template.rb +10 -10
- data/lib/csv_plus_plus/validated_modifier.rb +164 -0
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +6 -4
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +24 -29
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +33 -12
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +3 -6
- data/lib/csv_plus_plus.rb +19 -10
- metadata +34 -24
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
- data/lib/csv_plus_plus/language/compiler.rb +0 -152
- data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
- data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
- data/lib/csv_plus_plus/language/entities/function.rb +0 -35
- data/lib/csv_plus_plus/language/entities/function_call.rb +0 -37
- data/lib/csv_plus_plus/language/entities/number.rb +0 -36
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
- data/lib/csv_plus_plus/language/entities/string.rb +0 -31
- data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/language/entities.rb +0 -28
- data/lib/csv_plus_plus/language/references.rb +0 -70
- data/lib/csv_plus_plus/language/runtime.rb +0 -205
- data/lib/csv_plus_plus/language/scope.rb +0 -192
- data/lib/csv_plus_plus/modifier.tab.rb +0 -907
@@ -1,192 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../code_section'
|
4
|
-
require_relative '../graph'
|
5
|
-
require_relative './entities'
|
6
|
-
require_relative './references'
|
7
|
-
require_relative './syntax_error'
|
8
|
-
|
9
|
-
module CSVPlusPlus
|
10
|
-
module Language
|
11
|
-
# A class representing the scope of the current Template and responsible for resolving variables
|
12
|
-
#
|
13
|
-
# @attr_reader code_section [CodeSection] The CodeSection containing variables and functions to be resolved
|
14
|
-
# @attr_reader runtime [Runtime] The compiler's current runtime
|
15
|
-
#
|
16
|
-
# rubocop:disable Metrics/ClassLength
|
17
|
-
class Scope
|
18
|
-
attr_reader :code_section, :runtime
|
19
|
-
|
20
|
-
# initialize with a +Runtime+ and optional +CodeSection+
|
21
|
-
#
|
22
|
-
# @param runtime [Runtime]
|
23
|
-
# @param code_section [Runtime, nil]
|
24
|
-
def initialize(runtime:, code_section: nil)
|
25
|
-
@code_section = code_section if code_section
|
26
|
-
@runtime = runtime
|
27
|
-
end
|
28
|
-
|
29
|
-
# Resolve all values in the ast of the current cell being processed
|
30
|
-
#
|
31
|
-
# @return [Entity]
|
32
|
-
def resolve_cell_value
|
33
|
-
return unless (ast = @runtime.cell&.ast)
|
34
|
-
|
35
|
-
last_round = nil
|
36
|
-
loop do
|
37
|
-
refs = ::CSVPlusPlus::Language::References.extract(ast, @code_section)
|
38
|
-
return ast if refs.empty?
|
39
|
-
|
40
|
-
# TODO: throw an error here instead I think - basically we did a round and didn't make progress
|
41
|
-
return ast if last_round == refs
|
42
|
-
|
43
|
-
ast = resolve_functions(resolve_variables(ast, refs.variables), refs.functions)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# Set the +code_section+ and resolve all inner dependencies in it's variables and functions.
|
48
|
-
#
|
49
|
-
# @param code_section [CodeSection] The code_section to be resolved
|
50
|
-
def code_section=(code_section)
|
51
|
-
@code_section = code_section
|
52
|
-
resolve_static_variables!
|
53
|
-
end
|
54
|
-
|
55
|
-
# @return [String]
|
56
|
-
def to_s
|
57
|
-
"Scope(code_section: #{@code_section}, runtime: #{@runtime})"
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
# Resolve all variable references defined statically in the code section
|
63
|
-
# TODO: experiment with getting rid of this - does it even play correctly with runtime vars?
|
64
|
-
def resolve_static_variables!
|
65
|
-
variables = @code_section.variables
|
66
|
-
last_var_dependencies = {}
|
67
|
-
loop do
|
68
|
-
var_dependencies, resolution_order = variable_resolution_order(only_static_vars(variables))
|
69
|
-
return if var_dependencies == last_var_dependencies
|
70
|
-
|
71
|
-
# TODO: make the contract better here where we're not seting the variables of another class
|
72
|
-
@code_section.variables = resolve_dependencies(var_dependencies, resolution_order, variables)
|
73
|
-
last_var_dependencies = var_dependencies.clone
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def only_static_vars(var_dependencies)
|
78
|
-
var_dependencies.reject { |k| @runtime.runtime_variable?(k) }
|
79
|
-
end
|
80
|
-
|
81
|
-
def resolve_functions(ast, refs)
|
82
|
-
refs.reduce(ast.dup) do |acc, elem|
|
83
|
-
function_replace(acc, elem.id, resolve_function(elem.id))
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def resolve_variables(ast, refs)
|
88
|
-
refs.reduce(ast.dup) do |acc, elem|
|
89
|
-
variable_replace(acc, elem.id, resolve_variable(elem.id))
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
# Make a copy of the AST represented by +node+ and replace +fn_id+ with +replacement+ throughout
|
94
|
-
# rubocop:disable Metrics/MethodLength
|
95
|
-
def function_replace(node, fn_id, replacement)
|
96
|
-
if node.function_call? && node.id == fn_id
|
97
|
-
call_function_or_runtime_value(replacement, node)
|
98
|
-
elsif node.function_call?
|
99
|
-
# not our function, but continue our depth first search on it
|
100
|
-
::CSVPlusPlus::Language::Entities::FunctionCall.new(
|
101
|
-
node.id,
|
102
|
-
node.arguments.map { |n| function_replace(n, fn_id, replacement) },
|
103
|
-
infix: node.infix
|
104
|
-
)
|
105
|
-
else
|
106
|
-
node
|
107
|
-
end
|
108
|
-
end
|
109
|
-
# rubocop:enable Metrics/MethodLength
|
110
|
-
|
111
|
-
def resolve_function(fn_id)
|
112
|
-
id = fn_id.to_sym
|
113
|
-
return @code_section.functions[id] if @code_section.defined_function?(id)
|
114
|
-
|
115
|
-
::CSVPlusPlus::Language::Builtins::FUNCTIONS[id]
|
116
|
-
end
|
117
|
-
|
118
|
-
def call_function_or_runtime_value(function_or_runtime_value, function_call)
|
119
|
-
if function_or_runtime_value.function?
|
120
|
-
call_function(function_or_runtime_value, function_call)
|
121
|
-
else
|
122
|
-
function_or_runtime_value.resolve_fn.call(@runtime, function_call.arguments)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def call_function(function, function_call)
|
127
|
-
i = 0
|
128
|
-
function.arguments.reduce(function.body.dup) do |ast, argument|
|
129
|
-
variable_replace(ast, argument, function_call.arguments[i]).tap do
|
130
|
-
i += 1
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Make a copy of the AST represented by +node+ and replace +var_id+ with +replacement+ throughout
|
136
|
-
def variable_replace(node, var_id, replacement)
|
137
|
-
if node.function_call?
|
138
|
-
arguments = node.arguments.map { |n| variable_replace(n, var_id, replacement) }
|
139
|
-
# TODO: refactor these places where we copy functions... it's brittle with the kwargs
|
140
|
-
::CSVPlusPlus::Language::Entities::FunctionCall.new(node.id, arguments, infix: node.infix)
|
141
|
-
elsif node.variable? && node.id == var_id
|
142
|
-
replacement
|
143
|
-
else
|
144
|
-
node
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def resolve_variable(var_id)
|
149
|
-
id = var_id.to_sym
|
150
|
-
return @code_section.variables[id] if @code_section.defined_variable?(id)
|
151
|
-
|
152
|
-
# this will throw a syntax error if it doesn't exist (which is what we want)
|
153
|
-
@runtime.runtime_value(id)
|
154
|
-
end
|
155
|
-
|
156
|
-
def check_unbound_vars(dependencies, variables)
|
157
|
-
unbound_vars = dependencies.values.flatten - variables.keys
|
158
|
-
return if unbound_vars.empty?
|
159
|
-
|
160
|
-
@runtime.raise_syntax_error('Undefined variables', unbound_vars.map(&:to_s).join(', '))
|
161
|
-
end
|
162
|
-
|
163
|
-
def variable_resolution_order(variables)
|
164
|
-
# we have a hash of variables => ASTs but they might have references to each other, so
|
165
|
-
# we need to interpolate them first (before interpolating the cell values)
|
166
|
-
var_dependencies = ::CSVPlusPlus::Graph.dependency_graph(variables, @runtime)
|
167
|
-
# are there any references that we don't have variables for? (undefined variable)
|
168
|
-
check_unbound_vars(var_dependencies, variables)
|
169
|
-
|
170
|
-
# a topological sort will give us the order of dependencies
|
171
|
-
[var_dependencies, ::CSVPlusPlus::Graph.topological_sort(var_dependencies)]
|
172
|
-
# TODO: don't expose this exception directly to the caller
|
173
|
-
rescue ::TSort::Cyclic
|
174
|
-
@runtime.raise_syntax_error('Cyclic variable dependency detected', var_refs.keys)
|
175
|
-
end
|
176
|
-
|
177
|
-
def resolve_dependencies(var_dependencies, resolution_order, variables)
|
178
|
-
{}.tap do |resolved_vars|
|
179
|
-
# for each var and each dependency it has, build up and mutate resolved_vars
|
180
|
-
resolution_order.each do |var|
|
181
|
-
resolved_vars[var] = variables[var].dup
|
182
|
-
|
183
|
-
var_dependencies[var].each do |dependency|
|
184
|
-
resolved_vars[var] = variable_replace(resolved_vars[var], dependency, variables[dependency])
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
# rubocop:enable Metrics/ClassLength
|
191
|
-
end
|
192
|
-
end
|