csv_plus_plus 0.1.2 → 0.1.3
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/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
|