csv_plus_plus 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +9 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +46 -24
- data/lib/csv_plus_plus/cli.rb +23 -13
- data/lib/csv_plus_plus/cli_flag.rb +1 -2
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +82 -60
- data/lib/csv_plus_plus/entities/ast_builder.rb +27 -43
- data/lib/csv_plus_plus/entities/boolean.rb +18 -9
- data/lib/csv_plus_plus/entities/builtins.rb +23 -9
- data/lib/csv_plus_plus/entities/cell_reference.rb +200 -29
- data/lib/csv_plus_plus/entities/date.rb +38 -5
- data/lib/csv_plus_plus/entities/entity.rb +27 -61
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +57 -0
- data/lib/csv_plus_plus/entities/function.rb +23 -11
- data/lib/csv_plus_plus/entities/function_call.rb +24 -9
- data/lib/csv_plus_plus/entities/number.rb +24 -10
- data/lib/csv_plus_plus/entities/runtime_value.rb +22 -5
- data/lib/csv_plus_plus/entities/string.rb +19 -6
- data/lib/csv_plus_plus/entities/variable.rb +16 -4
- data/lib/csv_plus_plus/entities.rb +20 -13
- data/lib/csv_plus_plus/error/error.rb +11 -1
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +1 -0
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +53 -5
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +34 -14
- data/lib/csv_plus_plus/error/syntax_error.rb +22 -9
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +1 -0
- data/lib/csv_plus_plus/google_api_client.rb +7 -2
- data/lib/csv_plus_plus/google_options.rb +23 -18
- data/lib/csv_plus_plus/lexer/lexer.rb +8 -4
- data/lib/csv_plus_plus/lexer/tokenizer.rb +6 -1
- data/lib/csv_plus_plus/lexer.rb +24 -0
- data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
- data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
- data/lib/csv_plus_plus/modifier/expand.rb +61 -0
- data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
- data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
- data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
- data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
- data/lib/csv_plus_plus/modifier.rb +82 -158
- data/lib/csv_plus_plus/options.rb +64 -19
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +5 -5
- data/lib/csv_plus_plus/parser/code_section.tab.rb +8 -13
- data/lib/csv_plus_plus/parser/modifier.tab.rb +17 -23
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/can_define_references.rb +87 -0
- data/lib/csv_plus_plus/runtime/can_resolve_references.rb +209 -0
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position_tracker.rb +231 -0
- data/lib/csv_plus_plus/runtime/references.rb +110 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +126 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +66 -0
- data/lib/csv_plus_plus/template.rb +62 -35
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +30 -5
- data/lib/csv_plus_plus/writer/csv.rb +11 -9
- data/lib/csv_plus_plus/writer/excel.rb +9 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +1 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +71 -23
- data/lib/csv_plus_plus/writer/google_sheets.rb +79 -29
- data/lib/csv_plus_plus/writer/open_document.rb +6 -1
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +103 -30
- data/lib/csv_plus_plus/writer.rb +39 -9
- data/lib/csv_plus_plus.rb +29 -12
- metadata +18 -14
- data/lib/csv_plus_plus/can_define_references.rb +0 -88
- data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
- data/lib/csv_plus_plus/data_validation.rb +0 -138
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/references.rb +0 -68
- data/lib/csv_plus_plus/scope.rb +0 -196
- data/lib/csv_plus_plus/validated_modifier.rb +0 -164
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
@@ -1,32 +1,69 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require_relative './ast_builder'
|
4
|
-
require_relative './entity'
|
5
|
-
|
6
4
|
module CSVPlusPlus
|
7
5
|
module Entities
|
8
|
-
# A reference to a cell
|
6
|
+
# A reference to a cell. Internally it is represented by a simple +cell_index+ and +row_index+ but there are
|
7
|
+
# functions for converting to and from A1-style formats. Supported formats are:
|
8
|
+
#
|
9
|
+
# * `1` - A reference to the entire first row
|
10
|
+
# * `A` - A reference to the entire first column
|
11
|
+
# * `A1` - A reference to the first cell (top left)
|
12
|
+
# * `A1:D10` - The range defined between A1 and D10
|
13
|
+
# * `Sheet1!B2` - Cell B2 on the sheet "Sheet1"
|
9
14
|
#
|
10
|
-
# @
|
15
|
+
# @attr sheet_name [String, nil] The name of the sheet reference
|
16
|
+
# @attr_reader cell_index [Integer, nil] The cell index of the cell being referenced
|
17
|
+
# @attr_reader row_index [Integer, nil] The row index of the cell being referenced
|
18
|
+
# @attr_reader scoped_to_expand [Expand, nil] If set, the expand in which this variable is scoped to. It cannot be
|
19
|
+
# resolved outside of the given expand.
|
20
|
+
# @attr_reader upper_cell_index [Integer, nil] If set, the cell reference is a range and this is the upper cell
|
21
|
+
# index of it
|
22
|
+
# @attr_reader upper_row_index [Integer, nil] If set, the cell reference is a range and this is the upper row index
|
23
|
+
# of it
|
24
|
+
# rubocop:disable Metrics/ClassLength
|
11
25
|
class CellReference < Entity
|
12
|
-
|
26
|
+
extend ::T::Sig
|
13
27
|
|
14
|
-
|
15
|
-
|
28
|
+
sig { returns(::T.nilable(::String)) }
|
29
|
+
attr_accessor :sheet_name
|
16
30
|
|
17
|
-
|
18
|
-
|
19
|
-
# @param cell_index [Integer] The current cell index
|
20
|
-
# @param row_index [Integer] The current row index
|
21
|
-
#
|
22
|
-
# @return [CellReference]
|
23
|
-
def self.from_index(cell_index:, row_index:)
|
24
|
-
return unless row_index || cell_index
|
31
|
+
sig { returns(::T.nilable(::Integer)) }
|
32
|
+
attr_reader :cell_index
|
25
33
|
|
26
|
-
|
27
|
-
|
28
|
-
|
34
|
+
sig { returns(::T.nilable(::Integer)) }
|
35
|
+
attr_reader :row_index
|
36
|
+
|
37
|
+
sig { returns(::T.nilable(::CSVPlusPlus::Modifier::Expand)) }
|
38
|
+
attr_reader :scoped_to_expand
|
29
39
|
|
40
|
+
sig { returns(::T.nilable(::Integer)) }
|
41
|
+
attr_reader :upper_cell_index
|
42
|
+
|
43
|
+
sig { returns(::T.nilable(::Integer)) }
|
44
|
+
attr_reader :upper_row_index
|
45
|
+
|
46
|
+
# TODO: this is getting gross, maybe define an actual parser
|
47
|
+
A1_NOTATION_REGEXP = /
|
48
|
+
^
|
49
|
+
(?:
|
50
|
+
(?:
|
51
|
+
(?:'([^'\\]|\\.)*') # allow for a single-quoted sheet name
|
52
|
+
|
|
53
|
+
(\w+) # or if it's not quoted, just allow \w+
|
54
|
+
)
|
55
|
+
! # if a sheet name is specified, it's always followed by a !
|
56
|
+
)?
|
57
|
+
([a-zA-Z0-9]+) # the only part required - something alphanumeric
|
58
|
+
(?: :([a-zA-Z0-9]+))? # and they might make it a range
|
59
|
+
$
|
60
|
+
/x
|
61
|
+
public_constant :A1_NOTATION_REGEXP
|
62
|
+
|
63
|
+
ALPHA = ::T.let(('A'..'Z').to_a.freeze, ::T::Array[::String])
|
64
|
+
private_constant :ALPHA
|
65
|
+
|
66
|
+
sig { params(cell_reference_string: ::String).returns(::T::Boolean) }
|
30
67
|
# Does the given +cell_reference_string+ conform to a valid cell reference?
|
31
68
|
#
|
32
69
|
# {https://developers.google.com/sheets/api/guides/concepts}
|
@@ -34,27 +71,161 @@ module CSVPlusPlus
|
|
34
71
|
# @param cell_reference_string [::String] The string to check if it is a valid cell reference (we assume it's in
|
35
72
|
# A1 notation but maybe can support R1C1)
|
36
73
|
#
|
37
|
-
# @return [
|
74
|
+
# @return [::T::Boolean]
|
38
75
|
def self.valid_cell_reference?(cell_reference_string)
|
39
76
|
!(cell_reference_string =~ ::CSVPlusPlus::Entities::CellReference::A1_NOTATION_REGEXP).nil?
|
40
77
|
end
|
41
78
|
|
42
|
-
|
43
|
-
|
44
|
-
|
79
|
+
sig do
|
80
|
+
params(
|
81
|
+
cell_index: ::T.nilable(::Integer),
|
82
|
+
ref: ::T.nilable(::String),
|
83
|
+
row_index: ::T.nilable(::Integer),
|
84
|
+
scoped_to_expand: ::T.nilable(::CSVPlusPlus::Modifier::Expand)
|
85
|
+
).void
|
86
|
+
end
|
87
|
+
# Either +ref+, +cell_index+ or +row_index+ must be specified.
|
88
|
+
#
|
89
|
+
# @param cell_index [Integer, nil] The index of the cell being referenced.
|
90
|
+
# @param ref [Integer, nil] An A1-style cell reference (that will be parsed into it's row/cell indexes).
|
91
|
+
# @param row_index [Integer, nil] The index of the row being referenced.
|
92
|
+
# @param scoped_to_expand [Expand] The [[expand]] that this cell reference will be scoped to. In other words, it
|
93
|
+
# will only be able to be resolved if the runtime is within the bounds of the expand (it can't be referenced
|
94
|
+
# outside of the expand.)
|
95
|
+
# rubocop:disable Metrics/MethodLength
|
96
|
+
def initialize(cell_index: nil, ref: nil, row_index: nil, scoped_to_expand: nil)
|
97
|
+
raise(::ArgumentError, 'Must specify :ref, :cell_index or :row_index') unless ref || cell_index || row_index
|
98
|
+
|
99
|
+
super(::CSVPlusPlus::Entities::Type::CellReference)
|
100
|
+
|
101
|
+
if ref
|
102
|
+
from_a1_ref!(ref)
|
103
|
+
else
|
104
|
+
@cell_index = ::T.let(cell_index, ::T.nilable(::Integer))
|
105
|
+
@row_index = ::T.let(row_index, ::T.nilable(::Integer))
|
106
|
+
|
107
|
+
@upper_cell_index = ::T.let(nil, ::T.nilable(::Integer))
|
108
|
+
@upper_row_index = ::T.let(nil, ::T.nilable(::Integer))
|
109
|
+
end
|
45
110
|
|
46
|
-
@
|
111
|
+
@scoped_to_expand = scoped_to_expand
|
47
112
|
end
|
113
|
+
# rubocop:enable Metrics/MethodLength
|
114
|
+
|
115
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
116
|
+
# @param other [Entity]
|
117
|
+
#
|
118
|
+
# @return [boolean]
|
119
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
120
|
+
def ==(other)
|
121
|
+
return false unless super
|
122
|
+
|
123
|
+
other.is_a?(self.class) && @cell_index == other.cell_index && @row_index == other.row_index \
|
124
|
+
&& @sheet_name == other.sheet_name && @scoped_to_expand == other.scoped_to_expand \
|
125
|
+
&& @upper_cell_index == other.upper_cell_index && @upper_row_index == other.upper_row_index
|
126
|
+
end
|
127
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
128
|
+
|
129
|
+
sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
130
|
+
# Get the A1-style cell reference
|
131
|
+
#
|
132
|
+
# @param runtime [Runtime] The current runtime
|
133
|
+
#
|
134
|
+
# @return [::String] An A1-style reference
|
135
|
+
def evaluate(runtime)
|
136
|
+
# unless in_scope?(runtime)
|
137
|
+
# runtime.raise_modifier_syntax_error(message: 'Reference is out of scope', bad_input: runtime.cell.value)
|
138
|
+
# end
|
48
139
|
|
140
|
+
to_a1_ref(runtime) || ''
|
141
|
+
end
|
142
|
+
|
143
|
+
sig { returns(::T::Boolean) }
|
144
|
+
# Is the cell_reference a range? - something like A1:D10
|
145
|
+
#
|
146
|
+
# @return [boolean]
|
147
|
+
def range?
|
148
|
+
!upper_row_index.nil? || !upper_cell_index.nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T.nilable(::String)) }
|
154
|
+
# Turns index-based/X,Y coordinates into a A1 format
|
155
|
+
#
|
156
|
+
# @param runtime [Runtime]
|
157
|
+
#
|
158
|
+
# @return [::String, nil]
|
159
|
+
def to_a1_ref(runtime)
|
160
|
+
row_index = runtime_row_index(runtime)
|
161
|
+
return unless row_index || @cell_index
|
162
|
+
|
163
|
+
rowref = row_index ? (row_index + 1).to_s : ''
|
164
|
+
cellref = @cell_index ? to_a1_cell_ref : ''
|
165
|
+
[cellref, rowref].join
|
166
|
+
end
|
167
|
+
|
168
|
+
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T.nilable(::Integer)) }
|
169
|
+
def runtime_row_index(runtime)
|
170
|
+
@scoped_to_expand ? runtime.row_index : @row_index
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { returns(::String) }
|
174
|
+
# Turns a cell index into an A1 reference (just the "A" part - for example 0 == 'A', 1 == 'B', 2 == 'C', etc.)
|
175
|
+
#
|
49
176
|
# @return [::String]
|
50
|
-
def
|
51
|
-
@
|
177
|
+
def to_a1_cell_ref
|
178
|
+
c = @cell_index.dup
|
179
|
+
ref = ''
|
180
|
+
|
181
|
+
while c >= 0
|
182
|
+
# rubocop:disable Lint/ConstantResolution
|
183
|
+
ref += ::T.must(ALPHA[c % 26])
|
184
|
+
# rubocop:enable Lint/ConstantResolution
|
185
|
+
c = (c / 26).floor - 1
|
186
|
+
end
|
187
|
+
|
188
|
+
ref.reverse
|
52
189
|
end
|
53
190
|
|
54
|
-
|
55
|
-
def
|
56
|
-
|
191
|
+
sig { params(ref: ::String).void }
|
192
|
+
def from_a1_ref!(ref)
|
193
|
+
quoted_sheet_name, unquoted_sheet_name, lower_range, upper_range = ::T.must(
|
194
|
+
ref.strip.match(
|
195
|
+
::CSVPlusPlus::Entities::CellReference::A1_NOTATION_REGEXP
|
196
|
+
)
|
197
|
+
).captures
|
198
|
+
|
199
|
+
@sheet_name = quoted_sheet_name || unquoted_sheet_name
|
200
|
+
|
201
|
+
parse_lower_range!(lower_range) if lower_range
|
202
|
+
parse_upper_range!(upper_range) if upper_range
|
203
|
+
end
|
204
|
+
|
205
|
+
sig { params(lower_range: ::String).void }
|
206
|
+
def parse_lower_range!(lower_range)
|
207
|
+
cell_ref, row_ref = ::T.must(lower_range.match(/^([a-zA-Z]+)?(\d+)?$/)).captures
|
208
|
+
@cell_index = from_a1_cell_ref!(cell_ref) if cell_ref
|
209
|
+
@row_index = Integer(row_ref, 10) - 1 if row_ref
|
210
|
+
end
|
211
|
+
|
212
|
+
sig { params(upper_range: ::String).void }
|
213
|
+
# TODO: make this less redundant with the above function
|
214
|
+
def parse_upper_range!(upper_range)
|
215
|
+
cell_ref, row_ref = ::T.must(upper_range.match(/^([a-zA-Z]+)?(\d+)?$/)).captures
|
216
|
+
@upper_cell_index = from_a1_cell_ref!(cell_ref) if cell_ref
|
217
|
+
@upper_row_index = Integer(row_ref, 10) - 1 if row_ref
|
218
|
+
end
|
219
|
+
|
220
|
+
sig { params(cell_ref: ::String).returns(::Integer) }
|
221
|
+
def from_a1_cell_ref!(cell_ref)
|
222
|
+
(cell_ref.upcase.chars.reduce(0) do |cell_index, letter|
|
223
|
+
# rubocop:disable Lint/ConstantResolution
|
224
|
+
(cell_index * 26) + ::T.must(ALPHA.find_index(letter)) + 1
|
225
|
+
# rubocop:enable Lint/ConstantResolution
|
226
|
+
end) - 1
|
57
227
|
end
|
58
228
|
end
|
229
|
+
# rubocop:enable Metrics/ClassLength
|
59
230
|
end
|
60
231
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -6,24 +7,56 @@ module CSVPlusPlus
|
|
6
7
|
#
|
7
8
|
# @attr_reader value [Date] The parsed date
|
8
9
|
class Date < Entity
|
10
|
+
extend ::T::Sig
|
11
|
+
|
12
|
+
sig { returns(::Date) }
|
9
13
|
attr_reader :value
|
10
14
|
|
11
|
-
# TODO: support time?
|
15
|
+
# TODO: support time granularity?
|
12
16
|
DATE_STRING_REGEXP = %r{^\d{1,2}[/-]\d{1,2}[/-]\d{1,4}?$}
|
13
17
|
public_constant :DATE_STRING_REGEXP
|
14
18
|
|
19
|
+
sig { params(date_string: ::String).returns(::T::Boolean) }
|
15
20
|
# Is the given string a valid date?
|
16
21
|
#
|
17
22
|
# @param date_string [::String]
|
18
23
|
def self.valid_date?(date_string)
|
19
|
-
|
24
|
+
new(date_string)
|
25
|
+
true
|
26
|
+
rescue ::Date::Error
|
27
|
+
false
|
20
28
|
end
|
21
29
|
|
22
|
-
|
30
|
+
sig { params(value: ::String).void }
|
31
|
+
# @param value [::String] The user-inputted date value
|
23
32
|
def initialize(value)
|
24
|
-
super(
|
33
|
+
super(::CSVPlusPlus::Entities::Type::Date)
|
34
|
+
|
35
|
+
parsed =
|
36
|
+
begin
|
37
|
+
::Date.parse(value)
|
38
|
+
rescue ::Date::Error
|
39
|
+
::Date.strptime(value, '%d/%m/%yyyy')
|
40
|
+
end
|
41
|
+
@value = ::T.let(parsed, ::Date)
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
45
|
+
# @param _runtime [Runtime]
|
46
|
+
#
|
47
|
+
# @return [::String]
|
48
|
+
def evaluate(_runtime)
|
49
|
+
@value.strftime('%m/%d/%y')
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
53
|
+
# @param other [Entity]
|
54
|
+
#
|
55
|
+
# @return [T::Boolean]
|
56
|
+
def ==(other)
|
57
|
+
return false unless super
|
25
58
|
|
26
|
-
|
59
|
+
other.is_a?(self.class) && other.value == @value
|
27
60
|
end
|
28
61
|
end
|
29
62
|
end
|
@@ -1,84 +1,50 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require_relative '../entities'
|
4
|
-
|
5
4
|
module CSVPlusPlus
|
6
5
|
module Entities
|
7
6
|
# A basic building block of the abstract syntax tree (AST)
|
8
7
|
#
|
9
8
|
# @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
|
10
9
|
# for variables it's the variable name
|
11
|
-
# @attr_reader type [
|
10
|
+
# @attr_reader type [Entities::Type] The type of the entity. Each type should have a corresponding class definition
|
11
|
+
# in CSVPlusPlus::Entities
|
12
12
|
class Entity
|
13
|
-
|
13
|
+
extend ::T::Sig
|
14
|
+
extend ::T::Helpers
|
15
|
+
|
16
|
+
abstract!
|
17
|
+
|
18
|
+
sig { returns(::T.nilable(::Symbol)) }
|
19
|
+
attr_reader :id
|
20
|
+
|
21
|
+
sig { returns(::CSVPlusPlus::Entities::Type) }
|
22
|
+
attr_reader :type
|
14
23
|
|
15
|
-
|
16
|
-
# @param
|
24
|
+
sig { params(type: ::CSVPlusPlus::Entities::Type, id: ::T.nilable(::Symbol)).void }
|
25
|
+
# @param type [Entities::Type]
|
26
|
+
# @param id [Symbol, nil]
|
17
27
|
def initialize(type, id: nil)
|
18
|
-
@type = type
|
19
|
-
@id = id
|
28
|
+
@type = type
|
29
|
+
@id = ::T.let(id&.downcase&.to_sym || nil, ::T.nilable(::Symbol))
|
20
30
|
end
|
21
31
|
|
32
|
+
sig { overridable.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
33
|
+
# Each class should define it's own version of #==
|
34
|
+
# @param other [Entity]
|
35
|
+
#
|
22
36
|
# @return [boolean]
|
23
37
|
def ==(other)
|
24
38
|
self.class == other.class && @type == other.type && @id == other.id
|
25
39
|
end
|
26
40
|
|
27
|
-
|
41
|
+
sig { abstract.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
42
|
+
# Uses the given +runtime+ to evaluate itself in the current context
|
28
43
|
#
|
29
|
-
# @param
|
30
|
-
def method_missing(method_name, *_arguments)
|
31
|
-
if method_name =~ /^(\w+)\?$/
|
32
|
-
t = ::Regexp.last_match(1)
|
33
|
-
a_type?(t) && @type == t.to_sym
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# Respond to predicates by type (entity.boolean?, entity.string?, etc)
|
44
|
+
# @param _runtime [Runtime] The current runtime
|
40
45
|
#
|
41
|
-
# @
|
42
|
-
|
43
|
-
# @return [boolean]
|
44
|
-
def respond_to_missing?(method_name, *_arguments)
|
45
|
-
(method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def a_type?(str)
|
51
|
-
::CSVPlusPlus::Entities::TYPES.include?(str.to_sym)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
# An entity that can take other entities as arguments. Current use cases for this
|
56
|
-
# are function calls and function definitions
|
57
|
-
#
|
58
|
-
# @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
|
59
|
-
class EntityWithArguments < Entity
|
60
|
-
attr_reader :arguments
|
61
|
-
|
62
|
-
# @param type [::String, Symbol]
|
63
|
-
# @param id [::String]
|
64
|
-
# @param arguments [Array<Entity>]
|
65
|
-
def initialize(type, id: nil, arguments: [])
|
66
|
-
super(type, id:)
|
67
|
-
@arguments = arguments
|
68
|
-
end
|
69
|
-
|
70
|
-
# @return [boolean]
|
71
|
-
def ==(other)
|
72
|
-
super && @arguments == other.arguments
|
73
|
-
end
|
74
|
-
|
75
|
-
protected
|
76
|
-
|
77
|
-
attr_writer :arguments
|
78
|
-
|
79
|
-
def arguments_to_s
|
80
|
-
@arguments.join(', ')
|
81
|
-
end
|
46
|
+
# @return [::String]
|
47
|
+
def evaluate(_runtime); end
|
82
48
|
end
|
83
49
|
end
|
84
50
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module CSVPlusPlus
|
5
|
+
module Entities
|
6
|
+
# An entity that can take other entities as arguments. Current use cases for this
|
7
|
+
# are function calls and function definitions
|
8
|
+
#
|
9
|
+
# @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
|
10
|
+
class EntityWithArguments < Entity
|
11
|
+
extend ::T::Sig
|
12
|
+
|
13
|
+
abstract!
|
14
|
+
|
15
|
+
sig { returns(::T::Array[::CSVPlusPlus::Entities::Entity]) }
|
16
|
+
attr_reader :arguments
|
17
|
+
|
18
|
+
sig do
|
19
|
+
params(
|
20
|
+
type: ::CSVPlusPlus::Entities::Type,
|
21
|
+
id: ::T.nilable(::Symbol),
|
22
|
+
arguments: ::T::Array[::CSVPlusPlus::Entities::Entity]
|
23
|
+
).void
|
24
|
+
end
|
25
|
+
# @param type [Entities::Type]
|
26
|
+
# @param id [::String]
|
27
|
+
# @param arguments [Array<Entity>]
|
28
|
+
def initialize(type, id: nil, arguments: [])
|
29
|
+
super(type, id:)
|
30
|
+
@arguments = arguments
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
34
|
+
# @param other [Entity]
|
35
|
+
#
|
36
|
+
# @return [boolean]
|
37
|
+
def ==(other)
|
38
|
+
return false unless other.is_a?(self.class)
|
39
|
+
|
40
|
+
@arguments == other.arguments && super
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
sig do
|
46
|
+
params(arguments: ::T::Array[::CSVPlusPlus::Entities::Entity])
|
47
|
+
.returns(::T::Array[::CSVPlusPlus::Entities::Entity])
|
48
|
+
end
|
49
|
+
attr_writer :arguments
|
50
|
+
|
51
|
+
sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T::Array[::String]) }
|
52
|
+
def evaluate_arguments(runtime)
|
53
|
+
@arguments.map { |arg| arg.evaluate(runtime) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,32 +1,44 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require_relative './entity'
|
4
|
-
|
5
4
|
module CSVPlusPlus
|
6
5
|
module Entities
|
7
6
|
# A function definition
|
8
7
|
#
|
9
8
|
# @attr_reader body [Entity] The body of the function. +body+ can contain variable references
|
10
|
-
#
|
9
|
+
# from +@arguments+
|
11
10
|
class Function < EntityWithArguments
|
11
|
+
extend ::T::Sig
|
12
|
+
|
13
|
+
sig { returns(::CSVPlusPlus::Entities::Entity) }
|
12
14
|
attr_reader :body
|
13
15
|
|
14
|
-
|
16
|
+
sig { params(id: ::Symbol, arguments: ::T::Array[::Symbol], body: ::CSVPlusPlus::Entities::Entity).void }
|
17
|
+
# @param id [Symbol] the name of the function - what it will be callable by
|
15
18
|
# @param arguments [Array<Symbol>]
|
16
19
|
# @param body [Entity]
|
17
20
|
def initialize(id, arguments, body)
|
18
|
-
super(
|
19
|
-
|
21
|
+
super(::CSVPlusPlus::Entities::Type::Function, id:, arguments: arguments.map(&:to_sym))
|
22
|
+
|
23
|
+
@body = ::T.let(body, ::CSVPlusPlus::Entities::Entity)
|
20
24
|
end
|
21
25
|
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
27
|
+
# @param runtime [Runtime]
|
28
|
+
#
|
29
|
+
# @return [::String]
|
30
|
+
def evaluate(runtime)
|
31
|
+
"def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(runtime)}"
|
25
32
|
end
|
26
33
|
|
27
|
-
|
34
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
35
|
+
# @param other [Entity]
|
36
|
+
#
|
37
|
+
# @return [::T::Boolean]
|
28
38
|
def ==(other)
|
29
|
-
|
39
|
+
return false unless super
|
40
|
+
|
41
|
+
other.is_a?(self.class) && @body == other.body
|
30
42
|
end
|
31
43
|
end
|
32
44
|
end
|
@@ -1,34 +1,49 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
4
5
|
module Entities
|
5
|
-
# A function call
|
6
|
+
# A function call that can be either infix (A + B) or prefix (ADD(A, B))
|
6
7
|
#
|
7
8
|
# @attr_reader infix [boolean] Whether or not this function call is infix (X * Y, A + B, etc)
|
8
9
|
class FunctionCall < EntityWithArguments
|
10
|
+
extend ::T::Sig
|
11
|
+
|
12
|
+
sig { returns(::T::Boolean) }
|
9
13
|
attr_reader :infix
|
10
14
|
|
11
|
-
|
15
|
+
sig { params(id: ::Symbol, arguments: ::T::Array[::CSVPlusPlus::Entities::Entity], infix: ::T::Boolean).void }
|
16
|
+
# @param id [::String] The name of the function
|
12
17
|
# @param arguments [Array<Entity>] The arguments to the function
|
13
|
-
# @param infix [
|
18
|
+
# @param infix [T::Boolean] Whether the function is infix
|
14
19
|
def initialize(id, arguments, infix: false)
|
15
|
-
super(
|
20
|
+
super(::CSVPlusPlus::Entities::Type::FunctionCall, id:, arguments:)
|
16
21
|
|
17
22
|
@infix = infix
|
18
23
|
end
|
19
24
|
|
20
|
-
|
21
|
-
|
25
|
+
sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
26
|
+
# @param runtime [Runtime]
|
27
|
+
#
|
28
|
+
# @return [::String]
|
29
|
+
def evaluate(runtime)
|
30
|
+
evaluated_arguments = evaluate_arguments(runtime)
|
31
|
+
|
22
32
|
if @infix
|
23
|
-
"(#{
|
33
|
+
"(#{evaluated_arguments.join(" #{@id} ")})"
|
24
34
|
else
|
25
|
-
"#{@id.to_s.upcase}(#{
|
35
|
+
"#{@id.to_s.upcase}(#{evaluated_arguments.join(', ')})"
|
26
36
|
end
|
27
37
|
end
|
28
38
|
|
39
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
40
|
+
# @param other [Entity]
|
41
|
+
#
|
29
42
|
# @return [boolean]
|
30
43
|
def ==(other)
|
31
|
-
|
44
|
+
return false unless super
|
45
|
+
|
46
|
+
other.is_a?(self.class) && @id == other.id && @infix == other.infix
|
32
47
|
end
|
33
48
|
end
|
34
49
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# typed: strict
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
module CSVPlusPlus
|
@@ -6,28 +7,41 @@ module CSVPlusPlus
|
|
6
7
|
#
|
7
8
|
# @attr_reader value [Numeric] The parsed number value
|
8
9
|
class Number < Entity
|
10
|
+
sig { returns(::Numeric) }
|
9
11
|
attr_reader :value
|
10
12
|
|
13
|
+
sig { params(value: ::T.any(::String, ::Numeric)).void }
|
11
14
|
# @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
|
12
15
|
def initialize(value)
|
13
|
-
super(
|
16
|
+
super(::CSVPlusPlus::Entities::Type::Number)
|
14
17
|
|
15
18
|
@value =
|
16
|
-
|
17
|
-
value.
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
::T.let(
|
20
|
+
(if value.is_a?(::String)
|
21
|
+
value.include?('.') ? Float(value) : Integer(value, 10)
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end),
|
25
|
+
::Numeric
|
26
|
+
)
|
21
27
|
end
|
22
28
|
|
23
|
-
|
24
|
-
|
29
|
+
sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
|
30
|
+
# @param _runtime [Runtime]
|
31
|
+
#
|
32
|
+
# @return [::String]
|
33
|
+
def evaluate(_runtime)
|
25
34
|
@value.to_s
|
26
35
|
end
|
27
36
|
|
28
|
-
|
37
|
+
sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
|
38
|
+
# @param other [Entity]
|
39
|
+
#
|
40
|
+
# @return [::T::Boolean]
|
29
41
|
def ==(other)
|
30
|
-
|
42
|
+
return false unless super
|
43
|
+
|
44
|
+
other.is_a?(self.class) && @value == other.value
|
31
45
|
end
|
32
46
|
end
|
33
47
|
end
|