csv_plus_plus 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -1
  3. data/README.md +18 -62
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +62 -0
  5. data/lib/csv_plus_plus/can_define_references.rb +88 -0
  6. data/lib/csv_plus_plus/can_resolve_references.rb +8 -0
  7. data/lib/csv_plus_plus/cell.rb +3 -3
  8. data/lib/csv_plus_plus/cli.rb +24 -7
  9. data/lib/csv_plus_plus/color.rb +12 -6
  10. data/lib/csv_plus_plus/compiler.rb +156 -0
  11. data/lib/csv_plus_plus/data_validation.rb +138 -0
  12. data/lib/csv_plus_plus/{language → entities}/ast_builder.rb +5 -7
  13. data/lib/csv_plus_plus/entities/boolean.rb +31 -0
  14. data/lib/csv_plus_plus/{language → entities}/builtins.rb +2 -4
  15. data/lib/csv_plus_plus/entities/cell_reference.rb +60 -0
  16. data/lib/csv_plus_plus/entities/date.rb +30 -0
  17. data/lib/csv_plus_plus/entities/entity.rb +84 -0
  18. data/lib/csv_plus_plus/entities/function.rb +33 -0
  19. data/lib/csv_plus_plus/entities/function_call.rb +35 -0
  20. data/lib/csv_plus_plus/entities/number.rb +34 -0
  21. data/lib/csv_plus_plus/entities/runtime_value.rb +26 -0
  22. data/lib/csv_plus_plus/entities/string.rb +29 -0
  23. data/lib/csv_plus_plus/entities/variable.rb +25 -0
  24. data/lib/csv_plus_plus/entities.rb +33 -0
  25. data/lib/csv_plus_plus/error/error.rb +10 -0
  26. data/lib/csv_plus_plus/error/formula_syntax_error.rb +36 -0
  27. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +27 -0
  28. data/lib/csv_plus_plus/error/modifier_validation_error.rb +49 -0
  29. data/lib/csv_plus_plus/{language → error}/syntax_error.rb +6 -14
  30. data/lib/csv_plus_plus/error/writer_error.rb +9 -0
  31. data/lib/csv_plus_plus/error.rb +9 -2
  32. data/lib/csv_plus_plus/expand.rb +3 -1
  33. data/lib/csv_plus_plus/google_api_client.rb +4 -0
  34. data/lib/csv_plus_plus/lexer/lexer.rb +19 -11
  35. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +17 -0
  36. data/lib/csv_plus_plus/modifier.rb +73 -70
  37. data/lib/csv_plus_plus/options.rb +3 -0
  38. data/lib/csv_plus_plus/parser/cell_value.tab.rb +305 -0
  39. data/lib/csv_plus_plus/parser/code_section.tab.rb +410 -0
  40. data/lib/csv_plus_plus/parser/modifier.tab.rb +484 -0
  41. data/lib/csv_plus_plus/references.rb +68 -0
  42. data/lib/csv_plus_plus/row.rb +0 -3
  43. data/lib/csv_plus_plus/runtime.rb +199 -0
  44. data/lib/csv_plus_plus/scope.rb +196 -0
  45. data/lib/csv_plus_plus/template.rb +21 -5
  46. data/lib/csv_plus_plus/validated_modifier.rb +164 -0
  47. data/lib/csv_plus_plus/version.rb +1 -1
  48. data/lib/csv_plus_plus/writer/file_backer_upper.rb +6 -4
  49. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +24 -29
  50. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +33 -12
  51. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +3 -6
  52. data/lib/csv_plus_plus.rb +41 -16
  53. metadata +34 -24
  54. data/lib/csv_plus_plus/code_section.rb +0 -68
  55. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
  56. data/lib/csv_plus_plus/language/cell_value.tab.rb +0 -332
  57. data/lib/csv_plus_plus/language/code_section.tab.rb +0 -442
  58. data/lib/csv_plus_plus/language/compiler.rb +0 -157
  59. data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
  60. data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
  61. data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
  62. data/lib/csv_plus_plus/language/entities/function.rb +0 -35
  63. data/lib/csv_plus_plus/language/entities/function_call.rb +0 -26
  64. data/lib/csv_plus_plus/language/entities/number.rb +0 -36
  65. data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
  66. data/lib/csv_plus_plus/language/entities/string.rb +0 -31
  67. data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
  68. data/lib/csv_plus_plus/language/entities.rb +0 -28
  69. data/lib/csv_plus_plus/language/references.rb +0 -70
  70. data/lib/csv_plus_plus/language/runtime.rb +0 -205
  71. data/lib/csv_plus_plus/language/scope.rb +0 -188
  72. data/lib/csv_plus_plus/modifier.tab.rb +0 -907
@@ -1,188 +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
- def function_replace(node, fn_id, replacement)
95
- if node.function_call? && node.id == fn_id
96
- call_function_or_runtime_value(replacement, node)
97
- elsif node.function_call?
98
- # not our function, but continue our depth first search on it
99
- ::CSVPlusPlus::Language::Entities::FunctionCall.new(
100
- node.id,
101
- node.arguments.map { |n| function_replace(n, fn_id, replacement) }
102
- )
103
- else
104
- node
105
- end
106
- end
107
-
108
- def resolve_function(fn_id)
109
- id = fn_id.to_sym
110
- return @code_section.functions[id] if @code_section.defined_function?(id)
111
-
112
- ::CSVPlusPlus::Language::Builtins::FUNCTIONS[id]
113
- end
114
-
115
- def call_function_or_runtime_value(function_or_runtime_value, function_call)
116
- if function_or_runtime_value.function?
117
- call_function(function_or_runtime_value, function_call)
118
- else
119
- function_or_runtime_value.resolve_fn.call(@runtime, function_call.arguments)
120
- end
121
- end
122
-
123
- def call_function(function, function_call)
124
- i = 0
125
- function.arguments.reduce(function.body.dup) do |ast, argument|
126
- variable_replace(ast, argument, function_call.arguments[i]).tap do
127
- i += 1
128
- end
129
- end
130
- end
131
-
132
- # Make a copy of the AST represented by +node+ and replace +var_id+ with +replacement+ throughout
133
- def variable_replace(node, var_id, replacement)
134
- if node.function_call?
135
- arguments = node.arguments.map { |n| variable_replace(n, var_id, replacement) }
136
- ::CSVPlusPlus::Language::Entities::FunctionCall.new(node.id, arguments)
137
- elsif node.variable? && node.id == var_id
138
- replacement
139
- else
140
- node
141
- end
142
- end
143
-
144
- def resolve_variable(var_id)
145
- id = var_id.to_sym
146
- return @code_section.variables[id] if @code_section.defined_variable?(id)
147
-
148
- # this will throw a syntax error if it doesn't exist (which is what we want)
149
- @runtime.runtime_value(id)
150
- end
151
-
152
- def check_unbound_vars(dependencies, variables)
153
- unbound_vars = dependencies.values.flatten - variables.keys
154
- return if unbound_vars.empty?
155
-
156
- @runtime.raise_syntax_error('Undefined variables', unbound_vars.map(&:to_s).join(', '))
157
- end
158
-
159
- def variable_resolution_order(variables)
160
- # we have a hash of variables => ASTs but they might have references to each other, so
161
- # we need to interpolate them first (before interpolating the cell values)
162
- var_dependencies = ::CSVPlusPlus::Graph.dependency_graph(variables, @runtime)
163
- # are there any references that we don't have variables for? (undefined variable)
164
- check_unbound_vars(var_dependencies, variables)
165
-
166
- # a topological sort will give us the order of dependencies
167
- [var_dependencies, ::CSVPlusPlus::Graph.topological_sort(var_dependencies)]
168
- # TODO: don't expose this exception directly to the caller
169
- rescue ::TSort::Cyclic
170
- @runtime.raise_syntax_error('Cyclic variable dependency detected', var_refs.keys)
171
- end
172
-
173
- def resolve_dependencies(var_dependencies, resolution_order, variables)
174
- {}.tap do |resolved_vars|
175
- # for each var and each dependency it has, build up and mutate resolved_vars
176
- resolution_order.each do |var|
177
- resolved_vars[var] = variables[var].dup
178
-
179
- var_dependencies[var].each do |dependency|
180
- resolved_vars[var] = variable_replace(resolved_vars[var], dependency, variables[dependency])
181
- end
182
- end
183
- end
184
- end
185
- end
186
- # rubocop:enable Metrics/ClassLength
187
- end
188
- end