csv_plus_plus 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +9 -3
- data/bin/csv++ +1 -37
- data/bin/csvpp +6 -0
- data/lib/csv_plus_plus/cell.rb +24 -8
- data/lib/csv_plus_plus/cli.rb +97 -0
- data/lib/csv_plus_plus/cli_flag.rb +10 -2
- data/lib/csv_plus_plus/code_section.rb +22 -3
- data/lib/csv_plus_plus/color.rb +19 -5
- data/lib/csv_plus_plus/google_api_client.rb +20 -0
- data/lib/csv_plus_plus/google_options.rb +6 -2
- data/lib/csv_plus_plus/graph.rb +0 -1
- data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
- data/lib/csv_plus_plus/language/builtins.rb +46 -0
- data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
- data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
- data/lib/csv_plus_plus/language/compiler.rb +74 -86
- data/lib/csv_plus_plus/language/entities/boolean.rb +5 -4
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
- data/lib/csv_plus_plus/language/entities/entity.rb +22 -6
- data/lib/csv_plus_plus/language/entities/function.rb +6 -4
- data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
- data/lib/csv_plus_plus/language/entities/number.rb +6 -4
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
- data/lib/csv_plus_plus/language/entities/string.rb +6 -4
- data/lib/csv_plus_plus/language/references.rb +22 -5
- data/lib/csv_plus_plus/language/runtime.rb +80 -22
- data/lib/csv_plus_plus/language/scope.rb +29 -38
- data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
- data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
- data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
- data/lib/csv_plus_plus/modifier.rb +71 -8
- data/lib/csv_plus_plus/modifier.tab.rb +2 -2
- data/lib/csv_plus_plus/options.rb +17 -3
- data/lib/csv_plus_plus/row.rb +15 -4
- data/lib/csv_plus_plus/template.rb +10 -6
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -1
- data/lib/csv_plus_plus/writer/csv.rb +4 -1
- data/lib/csv_plus_plus/writer/excel.rb +5 -9
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +58 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
- data/lib/csv_plus_plus/writer/google_sheets.rb +22 -41
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
- data/lib/csv_plus_plus.rb +26 -4
- metadata +29 -7
@@ -29,11 +29,10 @@ module_eval(<<'...end cell_value.y/module_eval...', 'cell_value.y', 48)
|
|
29
29
|
@ast
|
30
30
|
end
|
31
31
|
|
32
|
-
def tokenizer
|
32
|
+
def tokenizer
|
33
33
|
::CSVPlusPlus::Lexer::Tokenizer.new(
|
34
34
|
catchall: /[\(\)\/\*\+\-,=&]/,
|
35
35
|
ignore: /\s+/,
|
36
|
-
input:,
|
37
36
|
tokens: [
|
38
37
|
[/true/i, :TRUE],
|
39
38
|
[/false/i, :FALSE],
|
@@ -33,11 +33,10 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 67)
|
|
33
33
|
'code section'
|
34
34
|
end
|
35
35
|
|
36
|
-
def tokenizer
|
36
|
+
def tokenizer
|
37
37
|
::CSVPlusPlus::Lexer::Tokenizer.new(
|
38
38
|
catchall: /[\(\)\{\}\/\*\+\-,=&]/,
|
39
39
|
ignore: /\s+|\#[^\n]+\n/,
|
40
|
-
input:,
|
41
40
|
stop_fn: lambda do |scanner|
|
42
41
|
return false unless scanner.scan(/#{::CSVPlusPlus::Lexer::END_OF_CODE_SECTION}/)
|
43
42
|
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'benchmark'
|
4
3
|
require 'csv'
|
4
|
+
# TODO: move some of these out to csv_plus_plus.rb
|
5
5
|
require_relative '../cell'
|
6
6
|
require_relative '../modifier'
|
7
7
|
require_relative '../modifier.tab'
|
8
8
|
require_relative '../row'
|
9
9
|
require_relative '../template'
|
10
|
+
require_relative 'benchmarked_compiler'
|
10
11
|
require_relative 'code_section.tab'
|
11
12
|
require_relative 'entities'
|
12
13
|
require_relative 'runtime'
|
@@ -14,59 +15,73 @@ require_relative 'scope'
|
|
14
15
|
|
15
16
|
module CSVPlusPlus
|
16
17
|
module Language
|
17
|
-
# Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+).
|
18
|
-
#
|
19
|
-
#
|
18
|
+
# Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+). Variable resolution is delegated
|
19
|
+
# to the +Scope+
|
20
|
+
#
|
21
|
+
# @attr_reader options [Options] The +Options+ to compile with
|
22
|
+
# @attr_reader runtime [Runtime] The runtime execution
|
23
|
+
# @attr_reader scope [Scope] +Scope+ for variable resolution
|
20
24
|
class Compiler
|
21
25
|
attr_reader :timings, :benchmark, :options, :runtime, :scope
|
22
26
|
|
23
27
|
# Create a compiler and make sure it gets cleaned up
|
24
|
-
|
25
|
-
|
26
|
-
|
28
|
+
#
|
29
|
+
# @param runtime [Runtime] The initial +Runtime+ for the compiler
|
30
|
+
# @param options [Options]
|
31
|
+
def self.with_compiler(runtime:, options:, &block)
|
32
|
+
compiler = new(options:, runtime:)
|
27
33
|
if options.verbose
|
28
|
-
|
34
|
+
::CSVPlusPlus::Language::BenchmarkedCompiler.with_benchmarks(compiler) do |c|
|
29
35
|
block.call(c)
|
30
36
|
end
|
31
37
|
else
|
32
|
-
yield(
|
38
|
+
yield(compiler)
|
33
39
|
end
|
34
40
|
ensure
|
35
41
|
runtime.cleanup!
|
36
42
|
end
|
37
43
|
|
38
|
-
#
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
block.call(compiler)
|
43
|
-
[compiler.timings.reduce(:+)]
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
# initialize
|
48
|
-
def initialize(runtime:, options:, scope: nil, benchmark: nil)
|
44
|
+
# @param runtime [Runtime]
|
45
|
+
# @param options [Options]
|
46
|
+
# @param scope [Scope, nil]
|
47
|
+
def initialize(runtime:, options:, scope: nil)
|
49
48
|
@options = options
|
50
49
|
@runtime = runtime
|
51
50
|
@scope = scope || ::CSVPlusPlus::Language::Scope.new(runtime:)
|
52
|
-
@benchmark = benchmark
|
53
|
-
@timings = [] if benchmark
|
54
51
|
end
|
55
52
|
|
56
|
-
#
|
57
|
-
def
|
53
|
+
# Write the compiled results
|
54
|
+
def outputting!
|
55
|
+
@runtime.start_at_csv!
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
|
59
|
+
# Compile a template and return a +::CSVPlusPlus::Template+ instance ready to be written with a +Writer+
|
60
|
+
#
|
61
|
+
# @return [Template]
|
62
|
+
def compile_template
|
58
63
|
parse_code_section!
|
59
64
|
rows = parse_csv_section!
|
60
65
|
|
61
|
-
::CSVPlusPlus::Template.new(rows
|
66
|
+
::CSVPlusPlus::Template.new(rows:).tap do |t|
|
62
67
|
t.validate_infinite_expands(@runtime)
|
63
68
|
expanding { t.expand_rows! }
|
64
69
|
resolve_all_cells!(t)
|
65
70
|
end
|
66
71
|
end
|
67
72
|
|
68
|
-
#
|
73
|
+
# @return [String]
|
74
|
+
def to_s
|
75
|
+
"Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
# Parses the input file and returns a +CodeSection+
|
81
|
+
#
|
82
|
+
# @return [CodeSection]
|
69
83
|
def parse_code_section!
|
84
|
+
@runtime.start!
|
70
85
|
parsing_code_section do |input|
|
71
86
|
code_section, csv_section = ::CSVPlusPlus::Language::CodeSectionParser.new.parse(input, self)
|
72
87
|
# TODO: infer a type
|
@@ -82,20 +97,48 @@ module CSVPlusPlus
|
|
82
97
|
@scope.code_section
|
83
98
|
end
|
84
99
|
|
85
|
-
#
|
100
|
+
# Parse the CSV section and return an array of +Row+s
|
101
|
+
#
|
102
|
+
# @return [Array<Row>]
|
86
103
|
def parse_csv_section!
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
104
|
+
@runtime.start_at_csv!
|
105
|
+
@runtime.map_rows(::CSV.new(runtime.input)) do |csv_row|
|
106
|
+
parse_row(csv_row)
|
91
107
|
end
|
92
108
|
ensure
|
93
109
|
# we're done with the file and everything is in memory
|
94
110
|
@runtime.cleanup!
|
95
111
|
end
|
96
112
|
|
113
|
+
# Iterates through each cell of each row and resolves it's variable and function references.
|
114
|
+
#
|
115
|
+
# @param template [Template]
|
116
|
+
# @return [Array<Entity>]
|
117
|
+
def resolve_all_cells!(template)
|
118
|
+
@runtime.start_at_csv!
|
119
|
+
@runtime.map_rows(template.rows, cells_too: true) do |cell|
|
120
|
+
cell.ast = @scope.resolve_cell_value if cell.ast
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Expanding rows
|
125
|
+
def expanding
|
126
|
+
@runtime.start_at_csv!
|
127
|
+
yield
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def parsing_code_section
|
133
|
+
csv_section = yield(@runtime.input.read)
|
134
|
+
@runtime.rewrite_input!(csv_section)
|
135
|
+
end
|
136
|
+
|
97
137
|
# Using the current +@runtime+ and the given +csv_row+ parse it into a +Row+ of +Cell+s
|
98
138
|
# +csv_row+ should have already been run through a CSV parser and is an array of strings
|
139
|
+
#
|
140
|
+
# @param csv_row [Array<Array<String>>]
|
141
|
+
# @return [Row]
|
99
142
|
def parse_row(csv_row)
|
100
143
|
row_modifier = ::CSVPlusPlus::Modifier.new(row_level: true)
|
101
144
|
|
@@ -109,61 +152,6 @@ module CSVPlusPlus
|
|
109
152
|
|
110
153
|
::CSVPlusPlus::Row.new(@runtime.row_index, cells, row_modifier)
|
111
154
|
end
|
112
|
-
|
113
|
-
# workflow when resolving the values of all cells
|
114
|
-
def resolve_all_cells!(template)
|
115
|
-
workflow(stage: 'Resolving each cell') do
|
116
|
-
@runtime.map_rows(template.rows, cells_too: true) do |cell|
|
117
|
-
cell.ast = @scope.resolve_cell_value if cell.ast
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# workflow when writing results
|
123
|
-
def outputting!(&block)
|
124
|
-
workflow(stage: 'Writing the spreadsheet') do
|
125
|
-
block.call
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# to_s
|
130
|
-
def to_s
|
131
|
-
"Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
|
132
|
-
end
|
133
|
-
|
134
|
-
private
|
135
|
-
|
136
|
-
# workflow when parsing the code section
|
137
|
-
def parsing_code_section(&block)
|
138
|
-
workflow(
|
139
|
-
stage: 'Parsing code section',
|
140
|
-
processing_code_section: true
|
141
|
-
) do
|
142
|
-
csv_section = block.call(@runtime.input.read)
|
143
|
-
@runtime.rewrite_input!(csv_section)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# workflow when expanding rows
|
148
|
-
def expanding(&block)
|
149
|
-
workflow(stage: 'Expanding rows') do
|
150
|
-
block.call
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def workflow(stage:, processing_code_section: false, &block)
|
155
|
-
@runtime.init!(processing_code_section ? 1 : (@runtime.length_of_code_section || 1))
|
156
|
-
|
157
|
-
ret = nil
|
158
|
-
if @benchmark
|
159
|
-
@timings << @benchmark.report(stage) { ret = block.call }
|
160
|
-
else
|
161
|
-
ret = block.call
|
162
|
-
end
|
163
|
-
|
164
|
-
ret
|
165
|
-
end
|
166
155
|
end
|
167
|
-
# rubocop:enable Metrics/ClassLength
|
168
156
|
end
|
169
157
|
end
|
@@ -5,24 +5,25 @@ require_relative './entity'
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
module Language
|
7
7
|
module Entities
|
8
|
-
##
|
9
8
|
# A boolean value
|
9
|
+
#
|
10
|
+
# @attr_reader value [true, false]
|
10
11
|
class Boolean < Entity
|
11
12
|
attr_reader :value
|
12
13
|
|
13
|
-
#
|
14
|
+
# @param value [String, Boolean]
|
14
15
|
def initialize(value)
|
15
16
|
super(:boolean)
|
16
17
|
# TODO: probably can do a lot better in general on type validation
|
17
18
|
@value = value.is_a?(::String) ? (value.downcase == 'true') : value
|
18
19
|
end
|
19
20
|
|
20
|
-
#
|
21
|
+
# @return [String]
|
21
22
|
def to_s
|
22
23
|
@value.to_s.upcase
|
23
24
|
end
|
24
25
|
|
25
|
-
#
|
26
|
+
# @return [boolean]
|
26
27
|
def ==(other)
|
27
28
|
super && value == other.value
|
28
29
|
end
|
@@ -5,21 +5,28 @@ require_relative './entity'
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
module Language
|
7
7
|
module Entities
|
8
|
-
##
|
9
8
|
# A reference to a cell
|
9
|
+
#
|
10
|
+
# @attr_reader cell_reference [String] The cell reference in A1 format
|
10
11
|
class CellReference < Entity
|
11
12
|
attr_reader :cell_reference
|
12
13
|
|
13
|
-
#
|
14
|
+
# @param cell_reference [String] The cell reference in A1 format
|
14
15
|
def initialize(cell_reference)
|
15
16
|
super(:cell_reference)
|
17
|
+
|
16
18
|
@cell_reference = cell_reference
|
17
19
|
end
|
18
20
|
|
19
|
-
#
|
21
|
+
# @return [String]
|
20
22
|
def to_s
|
21
23
|
@cell_reference
|
22
24
|
end
|
25
|
+
|
26
|
+
# @return [Boolean]
|
27
|
+
def ==(other)
|
28
|
+
super && @cell_reference == other.cell_reference
|
29
|
+
end
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -6,21 +6,28 @@ module CSVPlusPlus
|
|
6
6
|
module Language
|
7
7
|
module Entities
|
8
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+
|
9
13
|
class Entity
|
10
14
|
attr_reader :id, :type
|
11
15
|
|
12
|
-
#
|
16
|
+
# @param type [::String, Symbol]
|
17
|
+
# @param id [::String, nil]
|
13
18
|
def initialize(type, id: nil)
|
14
19
|
@type = type.to_sym
|
15
20
|
@id = id.downcase.to_sym if id
|
16
21
|
end
|
17
22
|
|
18
|
-
#
|
23
|
+
# @return [boolean]
|
19
24
|
def ==(other)
|
20
25
|
self.class == other.class && @type == other.type && @id == other.id
|
21
26
|
end
|
22
27
|
|
23
28
|
# Respond to predicates that correspond to types like #boolean?, #string?, etc
|
29
|
+
#
|
30
|
+
# @param method_name [Symbol] The +method_name+ to respond to
|
24
31
|
def method_missing(method_name, *_arguments)
|
25
32
|
if method_name =~ /^(\w+)\?$/
|
26
33
|
t = ::Regexp.last_match(1)
|
@@ -30,7 +37,11 @@ module CSVPlusPlus
|
|
30
37
|
end
|
31
38
|
end
|
32
39
|
|
33
|
-
#
|
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]
|
34
45
|
def respond_to_missing?(method_name, *_arguments)
|
35
46
|
(method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
|
36
47
|
end
|
@@ -42,17 +53,22 @@ module CSVPlusPlus
|
|
42
53
|
end
|
43
54
|
end
|
44
55
|
|
45
|
-
# An entity that can take arguments
|
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.
|
46
60
|
class EntityWithArguments < Entity
|
47
61
|
attr_reader :arguments
|
48
62
|
|
49
|
-
#
|
63
|
+
# @param type [::String, Symbol]
|
64
|
+
# @param id [::String]
|
65
|
+
# @param arguments [Array<Entity>]
|
50
66
|
def initialize(type, id: nil, arguments: [])
|
51
67
|
super(type, id:)
|
52
68
|
@arguments = arguments
|
53
69
|
end
|
54
70
|
|
55
|
-
#
|
71
|
+
# @return [boolean]
|
56
72
|
def ==(other)
|
57
73
|
super && @arguments == other.arguments
|
58
74
|
end
|
@@ -6,24 +6,26 @@ module CSVPlusPlus
|
|
6
6
|
module Language
|
7
7
|
module Entities
|
8
8
|
# A function definition
|
9
|
+
#
|
10
|
+
# @attr_reader body [Entity] The body of the function. +body+ can contain variable references
|
11
|
+
# from +@arguments+
|
9
12
|
class Function < EntityWithArguments
|
10
13
|
attr_reader :body
|
11
14
|
|
12
|
-
# Create a function
|
13
15
|
# @param id [Symbool, String] the name of the function - what it will be callable by
|
14
|
-
# @param arguments [Array
|
16
|
+
# @param arguments [Array<Symbol>]
|
15
17
|
# @param body [Entity]
|
16
18
|
def initialize(id, arguments, body)
|
17
19
|
super(:function, id:, arguments: arguments.map(&:to_sym))
|
18
20
|
@body = body
|
19
21
|
end
|
20
22
|
|
21
|
-
#
|
23
|
+
# @return [String]
|
22
24
|
def to_s
|
23
25
|
"def #{@id.to_s.upcase}(#{arguments_to_s}) #{@body}"
|
24
26
|
end
|
25
27
|
|
26
|
-
#
|
28
|
+
# @return [boolean]
|
27
29
|
def ==(other)
|
28
30
|
super && @body == other.body
|
29
31
|
end
|
@@ -5,17 +5,18 @@ module CSVPlusPlus
|
|
5
5
|
module Entities
|
6
6
|
# A function call
|
7
7
|
class FunctionCall < EntityWithArguments
|
8
|
-
#
|
8
|
+
# @param id [String] The name of the function
|
9
|
+
# @param arguments [Array<Entity>] The arguments to the function
|
9
10
|
def initialize(id, arguments)
|
10
11
|
super(:function_call, id:, arguments:)
|
11
12
|
end
|
12
13
|
|
13
|
-
#
|
14
|
+
# @return [String]
|
14
15
|
def to_s
|
15
16
|
"#{@id.to_s.upcase}(#{arguments_to_s})"
|
16
17
|
end
|
17
18
|
|
18
|
-
#
|
19
|
+
# @return [boolean]
|
19
20
|
def ==(other)
|
20
21
|
super && @id == other.id
|
21
22
|
end
|
@@ -3,14 +3,16 @@
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
module Language
|
5
5
|
module Entities
|
6
|
-
##
|
7
6
|
# A number value
|
7
|
+
#
|
8
|
+
# @attr_reader value [Numeric] The parsed number value
|
8
9
|
class Number < Entity
|
9
10
|
attr_reader :value
|
10
11
|
|
11
|
-
#
|
12
|
+
# @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
|
12
13
|
def initialize(value)
|
13
14
|
super(:number)
|
15
|
+
|
14
16
|
@value =
|
15
17
|
if value.instance_of?(::String)
|
16
18
|
value.include?('.') ? Float(value) : Integer(value, 10)
|
@@ -19,12 +21,12 @@ module CSVPlusPlus
|
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
|
-
#
|
24
|
+
# @return [String]
|
23
25
|
def to_s
|
24
26
|
@value.to_s
|
25
27
|
end
|
26
28
|
|
27
|
-
#
|
29
|
+
# @return [boolean]
|
28
30
|
def ==(other)
|
29
31
|
super && value == other.value
|
30
32
|
end
|
@@ -3,21 +3,22 @@
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
module Language
|
5
5
|
module Entities
|
6
|
-
|
7
|
-
# A runtime value
|
8
|
-
#
|
9
|
-
# These are values which can be materialized at any point via the +resolve_fn+
|
6
|
+
# A runtime value. These are values which can be materialized at any point via the +resolve_fn+
|
10
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
|
11
10
|
class RuntimeValue < Entity
|
12
|
-
attr_reader :resolve_fn
|
11
|
+
attr_reader :arguments, :resolve_fn
|
13
12
|
|
14
|
-
#
|
15
|
-
def initialize(resolve_fn)
|
13
|
+
# @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
|
14
|
+
def initialize(resolve_fn, arguments: nil)
|
16
15
|
super(:runtime_value)
|
16
|
+
|
17
|
+
@arguments = arguments
|
17
18
|
@resolve_fn = resolve_fn
|
18
19
|
end
|
19
20
|
|
20
|
-
#
|
21
|
+
# @return [String]
|
21
22
|
def to_s
|
22
23
|
'(runtime_value)'
|
23
24
|
end
|
@@ -3,23 +3,25 @@
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
module Language
|
5
5
|
module Entities
|
6
|
-
##
|
7
6
|
# A string value
|
7
|
+
#
|
8
|
+
# @attr_reader value [String]
|
8
9
|
class String < Entity
|
9
10
|
attr_reader :value
|
10
11
|
|
11
|
-
#
|
12
|
+
# @param value [String] The string that has been parsed out of the template
|
12
13
|
def initialize(value)
|
13
14
|
super(:string)
|
15
|
+
|
14
16
|
@value = value.gsub(/^"|"$/, '')
|
15
17
|
end
|
16
18
|
|
17
|
-
#
|
19
|
+
# @return [String]
|
18
20
|
def to_s
|
19
21
|
"\"#{@value}\""
|
20
22
|
end
|
21
23
|
|
22
|
-
#
|
24
|
+
# @return [boolean]
|
23
25
|
def ==(other)
|
24
26
|
super && value == other.value
|
25
27
|
end
|
@@ -6,10 +6,19 @@ require_relative './scope'
|
|
6
6
|
module CSVPlusPlus
|
7
7
|
module Language
|
8
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
|
9
12
|
class References
|
10
13
|
attr_accessor :functions, :variables
|
11
14
|
|
12
|
-
# Extract references from an AST
|
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]
|
13
22
|
def self.extract(ast, code_section)
|
14
23
|
new.tap do |refs|
|
15
24
|
::CSVPlusPlus::Graph.depth_first_search(ast) do |node|
|
@@ -22,8 +31,14 @@ module CSVPlusPlus
|
|
22
31
|
end
|
23
32
|
|
24
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
|
25
39
|
def self.function_reference?(node, code_section)
|
26
|
-
node.function_call? && (code_section.defined_function?(node.id)
|
40
|
+
node.function_call? && (code_section.defined_function?(node.id) \
|
41
|
+
|| ::CSVPlusPlus::Language::Builtins::FUNCTIONS.key?(node.id))
|
27
42
|
end
|
28
43
|
|
29
44
|
private_class_method :function_reference?
|
@@ -34,17 +49,19 @@ module CSVPlusPlus
|
|
34
49
|
@variables = []
|
35
50
|
end
|
36
51
|
|
37
|
-
#
|
52
|
+
# Are there any references to be resolved?
|
53
|
+
#
|
54
|
+
# @return [boolean]
|
38
55
|
def empty?
|
39
56
|
@functions.empty? && @variables.empty?
|
40
57
|
end
|
41
58
|
|
42
|
-
#
|
59
|
+
# @return [String]
|
43
60
|
def to_s
|
44
61
|
"References(functions: #{@functions}, variables: #{@variables})"
|
45
62
|
end
|
46
63
|
|
47
|
-
#
|
64
|
+
# @return [boolean]
|
48
65
|
def ==(other)
|
49
66
|
@functions == other.functions && @variables == other.variables
|
50
67
|
end
|