csv_plus_plus 0.1.0 → 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 +16 -1
- data/README.md +18 -62
- data/lib/csv_plus_plus/benchmarked_compiler.rb +62 -0
- data/lib/csv_plus_plus/can_define_references.rb +88 -0
- 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 +5 -7
- 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 +19 -11
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +17 -0
- data/lib/csv_plus_plus/modifier.rb +73 -70
- data/lib/csv_plus_plus/options.rb +3 -0
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +305 -0
- data/lib/csv_plus_plus/parser/code_section.tab.rb +410 -0
- 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 +21 -5
- 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 +41 -16
- metadata +34 -24
- data/lib/csv_plus_plus/code_section.rb +0 -68
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
- data/lib/csv_plus_plus/language/cell_value.tab.rb +0 -332
- data/lib/csv_plus_plus/language/code_section.tab.rb +0 -442
- data/lib/csv_plus_plus/language/compiler.rb +0 -157
- 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 -26
- 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 -188
- data/lib/csv_plus_plus/modifier.tab.rb +0 -907
@@ -1,86 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../entities'
|
4
|
-
|
5
|
-
module CSVPlusPlus
|
6
|
-
module Language
|
7
|
-
module Entities
|
8
|
-
# A basic building block of the abstract syntax tree (AST)
|
9
|
-
#
|
10
|
-
# @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
|
11
|
-
# for variables it's the variable name
|
12
|
-
# @attr_reader type [Symbol] The type of the entity. Valid values are defined in +::CSVPlusPlus::Language::Types+
|
13
|
-
class Entity
|
14
|
-
attr_reader :id, :type
|
15
|
-
|
16
|
-
# @param type [::String, Symbol]
|
17
|
-
# @param id [::String, nil]
|
18
|
-
def initialize(type, id: nil)
|
19
|
-
@type = type.to_sym
|
20
|
-
@id = id.downcase.to_sym if id
|
21
|
-
end
|
22
|
-
|
23
|
-
# @return [boolean]
|
24
|
-
def ==(other)
|
25
|
-
self.class == other.class && @type == other.type && @id == other.id
|
26
|
-
end
|
27
|
-
|
28
|
-
# Respond to predicates that correspond to types like #boolean?, #string?, etc
|
29
|
-
#
|
30
|
-
# @param method_name [Symbol] The +method_name+ to respond to
|
31
|
-
def method_missing(method_name, *_arguments)
|
32
|
-
if method_name =~ /^(\w+)\?$/
|
33
|
-
t = ::Regexp.last_match(1)
|
34
|
-
a_type?(t) && @type == t.to_sym
|
35
|
-
else
|
36
|
-
super
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
# Respond to predicates by type (entity.boolean?, entity.string?, etc)
|
41
|
-
#
|
42
|
-
# @param method_name [Symbol] The +method_name+ to respond to
|
43
|
-
#
|
44
|
-
# @return [boolean]
|
45
|
-
def respond_to_missing?(method_name, *_arguments)
|
46
|
-
(method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def a_type?(str)
|
52
|
-
::CSVPlusPlus::Language::TYPES.include?(str.to_sym)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
# An entity that can take other entities as arguments. Current use cases for this
|
57
|
-
# are function calls and function definitions
|
58
|
-
#
|
59
|
-
# @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
|
60
|
-
class EntityWithArguments < Entity
|
61
|
-
attr_reader :arguments
|
62
|
-
|
63
|
-
# @param type [::String, Symbol]
|
64
|
-
# @param id [::String]
|
65
|
-
# @param arguments [Array<Entity>]
|
66
|
-
def initialize(type, id: nil, arguments: [])
|
67
|
-
super(type, id:)
|
68
|
-
@arguments = arguments
|
69
|
-
end
|
70
|
-
|
71
|
-
# @return [boolean]
|
72
|
-
def ==(other)
|
73
|
-
super && @arguments == other.arguments
|
74
|
-
end
|
75
|
-
|
76
|
-
protected
|
77
|
-
|
78
|
-
attr_writer :arguments
|
79
|
-
|
80
|
-
def arguments_to_s
|
81
|
-
@arguments.join(', ')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative './entity'
|
4
|
-
|
5
|
-
module CSVPlusPlus
|
6
|
-
module Language
|
7
|
-
module Entities
|
8
|
-
# A function definition
|
9
|
-
#
|
10
|
-
# @attr_reader body [Entity] The body of the function. +body+ can contain variable references
|
11
|
-
# from +@arguments+
|
12
|
-
class Function < EntityWithArguments
|
13
|
-
attr_reader :body
|
14
|
-
|
15
|
-
# @param id [Symbool, String] the name of the function - what it will be callable by
|
16
|
-
# @param arguments [Array<Symbol>]
|
17
|
-
# @param body [Entity]
|
18
|
-
def initialize(id, arguments, body)
|
19
|
-
super(:function, id:, arguments: arguments.map(&:to_sym))
|
20
|
-
@body = body
|
21
|
-
end
|
22
|
-
|
23
|
-
# @return [String]
|
24
|
-
def to_s
|
25
|
-
"def #{@id.to_s.upcase}(#{arguments_to_s}) #{@body}"
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [boolean]
|
29
|
-
def ==(other)
|
30
|
-
super && @body == other.body
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Language
|
5
|
-
module Entities
|
6
|
-
# A function call
|
7
|
-
class FunctionCall < EntityWithArguments
|
8
|
-
# @param id [String] The name of the function
|
9
|
-
# @param arguments [Array<Entity>] The arguments to the function
|
10
|
-
def initialize(id, arguments)
|
11
|
-
super(:function_call, id:, arguments:)
|
12
|
-
end
|
13
|
-
|
14
|
-
# @return [String]
|
15
|
-
def to_s
|
16
|
-
"#{@id.to_s.upcase}(#{arguments_to_s})"
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [boolean]
|
20
|
-
def ==(other)
|
21
|
-
super && @id == other.id
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Language
|
5
|
-
module Entities
|
6
|
-
# A number value
|
7
|
-
#
|
8
|
-
# @attr_reader value [Numeric] The parsed number value
|
9
|
-
class Number < Entity
|
10
|
-
attr_reader :value
|
11
|
-
|
12
|
-
# @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
|
13
|
-
def initialize(value)
|
14
|
-
super(:number)
|
15
|
-
|
16
|
-
@value =
|
17
|
-
if value.instance_of?(::String)
|
18
|
-
value.include?('.') ? Float(value) : Integer(value, 10)
|
19
|
-
else
|
20
|
-
value
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# @return [String]
|
25
|
-
def to_s
|
26
|
-
@value.to_s
|
27
|
-
end
|
28
|
-
|
29
|
-
# @return [boolean]
|
30
|
-
def ==(other)
|
31
|
-
super && value == other.value
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Language
|
5
|
-
module Entities
|
6
|
-
# A runtime value. These are values which can be materialized at any point via the +resolve_fn+
|
7
|
-
# which takes an ExecutionContext as a param
|
8
|
-
#
|
9
|
-
# @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
|
10
|
-
class RuntimeValue < Entity
|
11
|
-
attr_reader :arguments, :resolve_fn
|
12
|
-
|
13
|
-
# @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
|
14
|
-
def initialize(resolve_fn, arguments: nil)
|
15
|
-
super(:runtime_value)
|
16
|
-
|
17
|
-
@arguments = arguments
|
18
|
-
@resolve_fn = resolve_fn
|
19
|
-
end
|
20
|
-
|
21
|
-
# @return [String]
|
22
|
-
def to_s
|
23
|
-
'(runtime_value)'
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Language
|
5
|
-
module Entities
|
6
|
-
# A string value
|
7
|
-
#
|
8
|
-
# @attr_reader value [String]
|
9
|
-
class String < Entity
|
10
|
-
attr_reader :value
|
11
|
-
|
12
|
-
# @param value [String] The string that has been parsed out of the template
|
13
|
-
def initialize(value)
|
14
|
-
super(:string)
|
15
|
-
|
16
|
-
@value = value.gsub(/^"|"$/, '')
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [String]
|
20
|
-
def to_s
|
21
|
-
"\"#{@value}\""
|
22
|
-
end
|
23
|
-
|
24
|
-
# @return [boolean]
|
25
|
-
def ==(other)
|
26
|
-
super && value == other.value
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,25 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module CSVPlusPlus
|
4
|
-
module Language
|
5
|
-
module Entities
|
6
|
-
# A reference to a variable
|
7
|
-
class Variable < Entity
|
8
|
-
# initialize
|
9
|
-
def initialize(id)
|
10
|
-
super(:variable, id:)
|
11
|
-
end
|
12
|
-
|
13
|
-
# to_s
|
14
|
-
def to_s
|
15
|
-
"$$#{@id}"
|
16
|
-
end
|
17
|
-
|
18
|
-
# ==
|
19
|
-
def ==(other)
|
20
|
-
super && id == other.id
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'entities/boolean'
|
4
|
-
require_relative 'entities/cell_reference'
|
5
|
-
require_relative 'entities/entity'
|
6
|
-
require_relative 'entities/function'
|
7
|
-
require_relative 'entities/function_call'
|
8
|
-
require_relative 'entities/number'
|
9
|
-
require_relative 'entities/runtime_value'
|
10
|
-
require_relative 'entities/string'
|
11
|
-
require_relative 'entities/variable'
|
12
|
-
|
13
|
-
module CSVPlusPlus
|
14
|
-
module Language
|
15
|
-
TYPES = {
|
16
|
-
boolean: ::CSVPlusPlus::Language::Entities::Boolean,
|
17
|
-
cell_reference: ::CSVPlusPlus::Language::Entities::CellReference,
|
18
|
-
function: ::CSVPlusPlus::Language::Entities::Function,
|
19
|
-
function_call: ::CSVPlusPlus::Language::Entities::FunctionCall,
|
20
|
-
number: ::CSVPlusPlus::Language::Entities::Number,
|
21
|
-
runtime_value: ::CSVPlusPlus::Language::Entities::RuntimeValue,
|
22
|
-
string: ::CSVPlusPlus::Language::Entities::String,
|
23
|
-
variable: ::CSVPlusPlus::Language::Entities::Variable
|
24
|
-
}.freeze
|
25
|
-
|
26
|
-
public_constant :TYPES
|
27
|
-
end
|
28
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../graph'
|
4
|
-
require_relative './scope'
|
5
|
-
|
6
|
-
module CSVPlusPlus
|
7
|
-
module Language
|
8
|
-
# References in an AST that need to be resolved
|
9
|
-
#
|
10
|
-
# @attr functions [Array<Entities::Function>] Functions references
|
11
|
-
# @attr variables [Array<Entities::Variable>] Variable references
|
12
|
-
class References
|
13
|
-
attr_accessor :functions, :variables
|
14
|
-
|
15
|
-
# Extract references from an AST and return them in a new +References+ object
|
16
|
-
#
|
17
|
-
# @param ast [Entity] An +Entity+ to do a depth first search on for references. Entities can be
|
18
|
-
# infinitely deep because they can contain other function calls as params to a function call
|
19
|
-
# @param code_section [CodeSection] The +CodeSection+ containing all currently defined functions
|
20
|
-
#
|
21
|
-
# @return [References]
|
22
|
-
def self.extract(ast, code_section)
|
23
|
-
new.tap do |refs|
|
24
|
-
::CSVPlusPlus::Graph.depth_first_search(ast) do |node|
|
25
|
-
next unless node.function_call? || node.variable?
|
26
|
-
|
27
|
-
refs.functions << node if function_reference?(node, code_section)
|
28
|
-
refs.variables << node if node.variable?
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# Is the node a resolvable reference?
|
34
|
-
#
|
35
|
-
# @param node [Entity] The node to check if it's resolvable
|
36
|
-
#
|
37
|
-
# @return [boolean]
|
38
|
-
# TODO: move this into the Entity subclasses
|
39
|
-
def self.function_reference?(node, code_section)
|
40
|
-
node.function_call? && (code_section.defined_function?(node.id) \
|
41
|
-
|| ::CSVPlusPlus::Language::Builtins::FUNCTIONS.key?(node.id))
|
42
|
-
end
|
43
|
-
|
44
|
-
private_class_method :function_reference?
|
45
|
-
|
46
|
-
# Create an object with empty references. The caller will build them up as it depth-first-searches
|
47
|
-
def initialize
|
48
|
-
@functions = []
|
49
|
-
@variables = []
|
50
|
-
end
|
51
|
-
|
52
|
-
# Are there any references to be resolved?
|
53
|
-
#
|
54
|
-
# @return [boolean]
|
55
|
-
def empty?
|
56
|
-
@functions.empty? && @variables.empty?
|
57
|
-
end
|
58
|
-
|
59
|
-
# @return [String]
|
60
|
-
def to_s
|
61
|
-
"References(functions: #{@functions}, variables: #{@variables})"
|
62
|
-
end
|
63
|
-
|
64
|
-
# @return [boolean]
|
65
|
-
def ==(other)
|
66
|
-
@functions == other.functions && @variables == other.variables
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,205 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'entities'
|
4
|
-
require_relative 'syntax_error'
|
5
|
-
require 'tempfile'
|
6
|
-
|
7
|
-
module CSVPlusPlus
|
8
|
-
module Language
|
9
|
-
# The runtime state of the compiler (the current +line_number+/+row_index+, +cell+ being processed, etc). We take
|
10
|
-
# multiple runs through the input file for parsing so it's really convenient to have a central place for these
|
11
|
-
# things to be managed.
|
12
|
-
#
|
13
|
-
# @attr_reader filename [String, nil] The filename that the input came from (mostly used for debugging since
|
14
|
-
# +filename+ can be +nil+ if it's read from stdin.
|
15
|
-
# @attr_reader length_of_code_section [Integer] The length (count of lines) of the code section part of the original
|
16
|
-
# input.
|
17
|
-
# @attr_reader length_of_csv_section [Integer] The length (count of lines) of the CSV part of the original csvpp
|
18
|
-
# input.
|
19
|
-
# @attr_reader length_of_original_file [Integer] The length (count of lines) of the original csvpp input.
|
20
|
-
#
|
21
|
-
# @attr cell [Cell] The current cell being processed
|
22
|
-
# @attr cell_index [Integer] The index of the current cell being processed (starts at 0)
|
23
|
-
# @attr row_index [Integer] The index of the current row being processed (starts at 0)
|
24
|
-
# @attr line_number [Integer] The line number of the original csvpp template (starts at 1)
|
25
|
-
class Runtime
|
26
|
-
attr_reader :filename, :length_of_code_section, :length_of_csv_section, :length_of_original_file
|
27
|
-
|
28
|
-
attr_accessor :cell, :cell_index, :row_index, :line_number
|
29
|
-
|
30
|
-
# @param input [String] The input to be parsed
|
31
|
-
# @param filename [String, nil] The filename that the input came from (mostly used for debugging since +filename+
|
32
|
-
# can be +nil+ if it's read from stdin
|
33
|
-
def initialize(input:, filename:)
|
34
|
-
@filename = filename || 'stdin'
|
35
|
-
|
36
|
-
init_input!(input)
|
37
|
-
start!
|
38
|
-
end
|
39
|
-
|
40
|
-
# Map over an a csvpp file and keep track of line_number and row_index
|
41
|
-
#
|
42
|
-
# @param lines [Array]
|
43
|
-
#
|
44
|
-
# @return [Array]
|
45
|
-
def map_lines(lines, &block)
|
46
|
-
@line_number = 1
|
47
|
-
lines.map do |line|
|
48
|
-
block.call(line).tap { next_line! }
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Map over a single row and keep track of the cell and it's index
|
53
|
-
#
|
54
|
-
# @param row [Array<Cell>] The row to map each cell over
|
55
|
-
#
|
56
|
-
# @return [Array]
|
57
|
-
def map_row(row, &block)
|
58
|
-
@cell_index = 0
|
59
|
-
row.map.with_index do |cell, index|
|
60
|
-
set_cell!(cell, index)
|
61
|
-
block.call(cell, index)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Map over all rows and keep track of row and line numbers
|
66
|
-
#
|
67
|
-
# @param rows [Array<Row>] The rows to map over (and keep track of indexes)
|
68
|
-
# @param cells_too [boolean] If the cells of each +row+ should be iterated over also.
|
69
|
-
#
|
70
|
-
# @return [Array]
|
71
|
-
def map_rows(rows, cells_too: false, &block)
|
72
|
-
@row_index = 0
|
73
|
-
map_lines(rows) do |row|
|
74
|
-
if cells_too
|
75
|
-
# it's either CSV or a Row object
|
76
|
-
map_row((row.is_a?(::CSVPlusPlus::Row) ? row.cells : row), &block)
|
77
|
-
else
|
78
|
-
block.call(row)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Increment state to the next line
|
84
|
-
#
|
85
|
-
# @return [Integer]
|
86
|
-
def next_line!
|
87
|
-
@row_index += 1 unless @row_index.nil?
|
88
|
-
@line_number += 1
|
89
|
-
end
|
90
|
-
|
91
|
-
# Return the current spreadsheet row number. It parallels +@row_index+ but starts at 1.
|
92
|
-
#
|
93
|
-
# @return [Integer, nil]
|
94
|
-
def rownum
|
95
|
-
return if @row_index.nil?
|
96
|
-
|
97
|
-
@row_index + 1
|
98
|
-
end
|
99
|
-
|
100
|
-
# Set the current cell and index
|
101
|
-
#
|
102
|
-
# @param cell [Cell] The current cell
|
103
|
-
# @param cell_index [Integer] The index of the cell
|
104
|
-
def set_cell!(cell, cell_index)
|
105
|
-
@cell = cell
|
106
|
-
@cell_index = cell_index
|
107
|
-
end
|
108
|
-
|
109
|
-
# Each time we run a parse on the input, reset the runtime state starting at the beginning of the file
|
110
|
-
def start!
|
111
|
-
@row_index = @cell_index = nil
|
112
|
-
@line_number = 1
|
113
|
-
end
|
114
|
-
|
115
|
-
# Reset the runtime state starting at the CSV section
|
116
|
-
def start_at_csv!
|
117
|
-
# TODO: isn't the input re-written anyway without the code section? why do we need this?
|
118
|
-
start!
|
119
|
-
@line_number = @length_of_code_section || 1
|
120
|
-
end
|
121
|
-
|
122
|
-
# @return [String]
|
123
|
-
def to_s
|
124
|
-
"Runtime(cell: #{@cell}, row_index: #{@row_index}, cell_index: #{@cell_index})"
|
125
|
-
end
|
126
|
-
|
127
|
-
# Get the current (entity) value of a runtime value
|
128
|
-
#
|
129
|
-
# @param var_id [String, Symbol] The Variable#id of the variable being resolved.
|
130
|
-
#
|
131
|
-
# @return [Entity]
|
132
|
-
def runtime_value(var_id)
|
133
|
-
if runtime_variable?(var_id)
|
134
|
-
::CSVPlusPlus::Language::Builtins::VARIABLES[var_id.to_sym].resolve_fn.call(self)
|
135
|
-
else
|
136
|
-
raise_syntax_error('Undefined variable', var_id)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Is +var_id+ a runtime variable? (it's a static variable otherwise)
|
141
|
-
#
|
142
|
-
# @param var_id [String, Symbol] The Variable#id to check if it's a runtime variable
|
143
|
-
#
|
144
|
-
# @return [boolean]
|
145
|
-
def runtime_variable?(var_id)
|
146
|
-
::CSVPlusPlus::Language::Builtins::VARIABLES.key?(var_id.to_sym)
|
147
|
-
end
|
148
|
-
|
149
|
-
# Called when an error is encoutered during parsing. It will construct a useful
|
150
|
-
# error with the current +@row/@cell_index+, +@line_number+ and +@filename+
|
151
|
-
#
|
152
|
-
# @param message [String] A message relevant to why this error is being raised.
|
153
|
-
# @param bad_input [String] The offending input that caused this error to be thrown.
|
154
|
-
# @param wrapped_error [StandardError, nil] The underlying error that was raised (if it's not from our own logic)
|
155
|
-
def raise_syntax_error(message, bad_input, wrapped_error: nil)
|
156
|
-
raise(::CSVPlusPlus::Language::SyntaxError.new(message, bad_input, self, wrapped_error:))
|
157
|
-
end
|
158
|
-
|
159
|
-
# The currently available input for parsing. The tmp state will be re-written
|
160
|
-
# between parsing the code section and the CSV section
|
161
|
-
#
|
162
|
-
# @return [String]
|
163
|
-
def input
|
164
|
-
@tmp
|
165
|
-
end
|
166
|
-
|
167
|
-
# We mutate the input over and over. It's ok because it's just a Tempfile
|
168
|
-
#
|
169
|
-
# @param data [String] The data to rewrite our input file to
|
170
|
-
def rewrite_input!(data)
|
171
|
-
@tmp.truncate(0)
|
172
|
-
@tmp.write(data)
|
173
|
-
@tmp.rewind
|
174
|
-
end
|
175
|
-
|
176
|
-
# Clean up the Tempfile we're using for parsing
|
177
|
-
def cleanup!
|
178
|
-
return unless @tmp
|
179
|
-
|
180
|
-
@tmp.close
|
181
|
-
@tmp.unlink
|
182
|
-
@tmp = nil
|
183
|
-
end
|
184
|
-
|
185
|
-
private
|
186
|
-
|
187
|
-
def count_code_section_lines(lines)
|
188
|
-
eoc = ::CSVPlusPlus::Lexer::END_OF_CODE_SECTION
|
189
|
-
lines.include?(eoc) ? (lines.take_while { |l| l != eoc }).length + 1 : 0
|
190
|
-
end
|
191
|
-
|
192
|
-
def init_input!(input)
|
193
|
-
lines = (input || '').split(/\s*\n\s*/)
|
194
|
-
@length_of_original_file = lines.length
|
195
|
-
@length_of_code_section = count_code_section_lines(lines)
|
196
|
-
@length_of_csv_section = @length_of_original_file - @length_of_code_section
|
197
|
-
|
198
|
-
# we're gonna take our input file, write it to a tmp file then each
|
199
|
-
# step is gonna mutate that tmp file
|
200
|
-
@tmp = ::Tempfile.new
|
201
|
-
rewrite_input!(input)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
end
|