csv_plus_plus 0.1.2 → 0.2.0
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 +9 -5
- data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
- data/lib/csv_plus_plus/a1_reference.rb +202 -0
- data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
- data/lib/csv_plus_plus/cell.rb +29 -41
- data/lib/csv_plus_plus/cli.rb +53 -80
- data/lib/csv_plus_plus/cli_flag.rb +71 -71
- data/lib/csv_plus_plus/color.rb +32 -7
- data/lib/csv_plus_plus/compiler.rb +98 -66
- data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
- data/lib/csv_plus_plus/entities/boolean.rb +26 -10
- data/lib/csv_plus_plus/entities/builtins.rb +66 -24
- data/lib/csv_plus_plus/entities/date.rb +42 -6
- data/lib/csv_plus_plus/entities/entity.rb +17 -69
- data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
- data/lib/csv_plus_plus/entities/function.rb +34 -11
- data/lib/csv_plus_plus/entities/function_call.rb +49 -10
- data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
- data/lib/csv_plus_plus/entities/number.rb +30 -11
- data/lib/csv_plus_plus/entities/reference.rb +77 -0
- data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
- data/lib/csv_plus_plus/entities/string.rb +23 -7
- data/lib/csv_plus_plus/entities.rb +7 -16
- data/lib/csv_plus_plus/error/cli_error.rb +17 -0
- data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
- data/lib/csv_plus_plus/error/error.rb +25 -2
- data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
- data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
- data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
- data/lib/csv_plus_plus/error/positional_error.rb +15 -0
- data/lib/csv_plus_plus/error/writer_error.rb +8 -0
- data/lib/csv_plus_plus/error.rb +5 -1
- data/lib/csv_plus_plus/error_formatter.rb +111 -0
- data/lib/csv_plus_plus/google_api_client.rb +25 -10
- data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
- data/lib/csv_plus_plus/lexer.rb +64 -1
- 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 +78 -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 +89 -160
- data/lib/csv_plus_plus/options/file_options.rb +49 -0
- data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
- data/lib/csv_plus_plus/options/options.rb +97 -0
- data/lib/csv_plus_plus/options.rb +34 -77
- data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
- data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
- data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
- data/lib/csv_plus_plus/reader/csv.rb +50 -0
- data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
- data/lib/csv_plus_plus/reader/reader.rb +27 -0
- data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
- data/lib/csv_plus_plus/reader.rb +14 -0
- data/lib/csv_plus_plus/row.rb +53 -12
- data/lib/csv_plus_plus/runtime/graph.rb +68 -0
- data/lib/csv_plus_plus/runtime/position.rb +242 -0
- data/lib/csv_plus_plus/runtime/references.rb +115 -0
- data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
- data/lib/csv_plus_plus/runtime/scope.rb +280 -0
- data/lib/csv_plus_plus/runtime.rb +34 -191
- data/lib/csv_plus_plus/source_code.rb +71 -0
- data/lib/csv_plus_plus/template.rb +71 -39
- data/lib/csv_plus_plus/version.rb +2 -1
- data/lib/csv_plus_plus/writer/csv.rb +37 -8
- data/lib/csv_plus_plus/writer/excel.rb +25 -5
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
- data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
- data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
- data/lib/csv_plus_plus/writer/merger.rb +31 -0
- data/lib/csv_plus_plus/writer/open_document.rb +21 -2
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
- data/lib/csv_plus_plus/writer/writer.rb +42 -0
- data/lib/csv_plus_plus/writer.rb +79 -10
- data/lib/csv_plus_plus.rb +47 -18
- metadata +50 -21
- 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/entities/cell_reference.rb +0 -60
- data/lib/csv_plus_plus/entities/variable.rb +0 -25
- data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
- data/lib/csv_plus_plus/expand.rb +0 -20
- data/lib/csv_plus_plus/google_options.rb +0 -27
- data/lib/csv_plus_plus/graph.rb +0 -62
- data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
- 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/base_writer.rb +0 -20
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +0 -59
|
@@ -1,44 +1,86 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module CSVPlusPlus
|
|
4
5
|
module Entities
|
|
5
|
-
# Provides
|
|
6
|
+
# Provides +RuntimeValue+s for builtin functions and variables
|
|
6
7
|
module Builtins
|
|
8
|
+
extend ::T::Sig
|
|
9
|
+
|
|
7
10
|
extend ::CSVPlusPlus::Entities::ASTBuilder
|
|
8
11
|
|
|
9
|
-
VARIABLES =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
VARIABLES = ::T.let(
|
|
13
|
+
{
|
|
14
|
+
# The number (integer) of the current cell. Starts at 1
|
|
15
|
+
cellnum: runtime_value(->(p, _args) { number(p.cell_index + 1) }),
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
# A reference to the current cell
|
|
18
|
+
cellref: runtime_value(->(p, _args) { cell_ref(p.row_index, p.cell_index) }),
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
# A reference to the row above
|
|
21
|
+
rowabove: runtime_value(->(p, _args) { cell_ref([0, (p.row_index - 1)].max) }),
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
# A reference to the row below
|
|
24
|
+
rowbelow: runtime_value(->(p, _args) { cell_ref(p.row_index + 1) }),
|
|
21
25
|
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
# The number (integer) of the current row. Starts at 1
|
|
27
|
+
rownum: runtime_value(->(p, _args) { number(p.rownum) }),
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
# A reference to the current row
|
|
30
|
+
rowref: runtime_value(->(p, _args) { cell_ref(p.row_index) })
|
|
31
|
+
}.freeze,
|
|
32
|
+
::T::Hash[::Symbol, ::CSVPlusPlus::Entities::RuntimeValue]
|
|
33
|
+
)
|
|
28
34
|
public_constant :VARIABLES
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
# TODO: A reference to a cell in a given row?
|
|
37
|
+
# TODO: check types of the args and throw a more friendly error?
|
|
38
|
+
FUNCTIONS = ::T.let(
|
|
39
|
+
{
|
|
40
|
+
# A reference to a cell above the current row
|
|
41
|
+
cellabove: runtime_value(->(p, args) { cell_ref([0, (p.row_index - 1)].max, args[0].a1_ref.cell_index) }),
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
# A reference to a cell in the current row
|
|
44
|
+
celladjacent: runtime_value(->(p, args) { cell_ref(p.row_index, args[0].a1_ref.cell_index) }),
|
|
37
45
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
# A reference to a cell below the current row
|
|
47
|
+
cellbelow: runtime_value(->(p, args) { cell_ref(p.row_index + 1, args[0].a1_ref.cell_index) })
|
|
48
|
+
}.freeze,
|
|
49
|
+
::T::Hash[::Symbol, ::CSVPlusPlus::Entities::RuntimeValue]
|
|
50
|
+
)
|
|
41
51
|
public_constant :FUNCTIONS
|
|
52
|
+
|
|
53
|
+
sig { params(fn_id: ::Symbol).returns(::T::Boolean) }
|
|
54
|
+
# Is +fn_id+ a builtin function?
|
|
55
|
+
#
|
|
56
|
+
# @param fn_id [Symbol] The Function#id to check if it's a runtime variable
|
|
57
|
+
#
|
|
58
|
+
# @return [T::Boolean]
|
|
59
|
+
def self.builtin_function?(fn_id)
|
|
60
|
+
::CSVPlusPlus::Entities::Builtins::FUNCTIONS.key?(fn_id)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
sig { params(var_id: ::Symbol).returns(::T::Boolean) }
|
|
64
|
+
# Is +var_id+ a builtin variable?
|
|
65
|
+
#
|
|
66
|
+
# @param var_id [Symbol] The Variable#id to check if it's a runtime variable
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean]
|
|
69
|
+
def self.builtin_variable?(var_id)
|
|
70
|
+
::CSVPlusPlus::Entities::Builtins::VARIABLES.key?(var_id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig do
|
|
74
|
+
params(row_index: ::Integer, cell_index: ::T.nilable(::Integer)).returns(::CSVPlusPlus::Entities::Reference)
|
|
75
|
+
end
|
|
76
|
+
# @param row_index [Integer]
|
|
77
|
+
# @param cell_index [Integer, nil]
|
|
78
|
+
#
|
|
79
|
+
# @return [Runtime::Reference]
|
|
80
|
+
def self.cell_ref(row_index, cell_index = nil)
|
|
81
|
+
::CSVPlusPlus::Entities::Reference.new(a1_ref: ::CSVPlusPlus::A1Reference.new(row_index:, cell_index:))
|
|
82
|
+
end
|
|
83
|
+
private_class_method :cell_ref
|
|
42
84
|
end
|
|
43
85
|
end
|
|
44
86
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module CSVPlusPlus
|
|
@@ -5,25 +6,60 @@ module CSVPlusPlus
|
|
|
5
6
|
# A date value
|
|
6
7
|
#
|
|
7
8
|
# @attr_reader value [Date] The parsed date
|
|
8
|
-
class Date < Entity
|
|
9
|
+
class Date < ::CSVPlusPlus::Entities::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()
|
|
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
|
|
25
43
|
|
|
26
|
-
|
|
44
|
+
sig { override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
45
|
+
# @param _position [Position]
|
|
46
|
+
#
|
|
47
|
+
# @return [::String]
|
|
48
|
+
def evaluate(_position)
|
|
49
|
+
@value.strftime('%m/%d/%y')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
53
|
+
# @param other [BasicObject]
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def ==(other)
|
|
57
|
+
case other
|
|
58
|
+
when self.class
|
|
59
|
+
other.value == @value
|
|
60
|
+
else
|
|
61
|
+
false
|
|
62
|
+
end
|
|
27
63
|
end
|
|
28
64
|
end
|
|
29
65
|
end
|
|
@@ -1,84 +1,32 @@
|
|
|
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
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
|
|
10
|
-
# for variables it's the variable name
|
|
11
|
-
# @attr_reader type [Symbol] The type of the entity. Valid values are defined in +::CSVPlusPlus::Entities::TYPES+
|
|
6
|
+
# All classes that are a part of an AST must implement this interface
|
|
12
7
|
class Entity
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# @param type [::String, Symbol]
|
|
16
|
-
# @param id [::String, nil]
|
|
17
|
-
def initialize(type, id: nil)
|
|
18
|
-
@type = type.to_sym
|
|
19
|
-
@id = id.downcase.to_sym if id
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# @return [boolean]
|
|
23
|
-
def ==(other)
|
|
24
|
-
self.class == other.class && @type == other.type && @id == other.id
|
|
25
|
-
end
|
|
8
|
+
extend ::T::Sig
|
|
9
|
+
extend ::T::Helpers
|
|
26
10
|
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
# @param method_name [Symbol] The +method_name+ to respond to
|
|
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
|
|
11
|
+
abstract!
|
|
38
12
|
|
|
39
|
-
|
|
13
|
+
sig { abstract.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
14
|
+
# Each node in the AST needs to implement #== so we can compare entities for equality
|
|
40
15
|
#
|
|
41
|
-
# @param
|
|
16
|
+
# @param other [Entity]
|
|
42
17
|
#
|
|
43
18
|
# @return [boolean]
|
|
44
|
-
def
|
|
45
|
-
(method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
|
|
46
|
-
end
|
|
19
|
+
def ==(other); end
|
|
47
20
|
|
|
48
|
-
|
|
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(', ')
|
|
21
|
+
sig do
|
|
22
|
+
abstract.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String)
|
|
81
23
|
end
|
|
24
|
+
# Uses the given +position+ to evaluate itself in the current context
|
|
25
|
+
#
|
|
26
|
+
# @param position [Position] The current runtime
|
|
27
|
+
#
|
|
28
|
+
# @return [::String]
|
|
29
|
+
def evaluate(position); end
|
|
82
30
|
end
|
|
83
31
|
end
|
|
84
32
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
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 < ::CSVPlusPlus::Entities::Entity
|
|
11
|
+
extend ::T::Sig
|
|
12
|
+
extend ::T::Helpers
|
|
13
|
+
extend ::T::Generic
|
|
14
|
+
|
|
15
|
+
abstract!
|
|
16
|
+
|
|
17
|
+
ArgumentsType = type_member
|
|
18
|
+
public_constant :ArgumentsType
|
|
19
|
+
|
|
20
|
+
sig { returns(::T::Array[::CSVPlusPlus::Entities::EntityWithArguments::ArgumentsType]) }
|
|
21
|
+
attr_reader :arguments
|
|
22
|
+
|
|
23
|
+
sig { params(arguments: ::T::Array[::CSVPlusPlus::Entities::EntityWithArguments::ArgumentsType]).void }
|
|
24
|
+
# @param arguments [Array<ArgumentsType>]
|
|
25
|
+
def initialize(arguments: [])
|
|
26
|
+
super()
|
|
27
|
+
@arguments = arguments
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
31
|
+
# @param other [Entity]
|
|
32
|
+
#
|
|
33
|
+
# @return [boolean]
|
|
34
|
+
def ==(other)
|
|
35
|
+
case other
|
|
36
|
+
when self.class
|
|
37
|
+
@arguments == other.arguments
|
|
38
|
+
else
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -1,32 +1,55 @@
|
|
|
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
|
-
#
|
|
11
|
-
class Function < EntityWithArguments
|
|
9
|
+
# from +@arguments+
|
|
10
|
+
class Function < ::CSVPlusPlus::Entities::EntityWithArguments
|
|
11
|
+
extend ::T::Sig
|
|
12
|
+
include ::CSVPlusPlus::Entities::HasIdentifier
|
|
13
|
+
|
|
14
|
+
ArgumentsType = type_member { { fixed: ::Symbol } }
|
|
15
|
+
public_constant :ArgumentsType
|
|
16
|
+
|
|
17
|
+
sig { returns(::Symbol) }
|
|
18
|
+
attr_reader :id
|
|
19
|
+
|
|
20
|
+
sig { returns(::CSVPlusPlus::Entities::Entity) }
|
|
12
21
|
attr_reader :body
|
|
13
22
|
|
|
14
|
-
|
|
23
|
+
sig { params(id: ::Symbol, arguments: ::T::Array[::Symbol], body: ::CSVPlusPlus::Entities::Entity).void }
|
|
24
|
+
# @param id [Symbol] the name of the function - what it will be callable by
|
|
15
25
|
# @param arguments [Array<Symbol>]
|
|
16
26
|
# @param body [Entity]
|
|
17
27
|
def initialize(id, arguments, body)
|
|
18
|
-
super(
|
|
19
|
-
|
|
28
|
+
super(arguments: arguments.map(&:to_sym))
|
|
29
|
+
|
|
30
|
+
@body = ::T.let(body, ::CSVPlusPlus::Entities::Entity)
|
|
31
|
+
@id = ::T.let(identifier(id), ::Symbol)
|
|
20
32
|
end
|
|
21
33
|
|
|
34
|
+
sig { override.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
35
|
+
# @param position [Position]
|
|
36
|
+
#
|
|
22
37
|
# @return [String]
|
|
23
|
-
def
|
|
24
|
-
"def #{@id.to_s.upcase}(#{
|
|
38
|
+
def evaluate(position)
|
|
39
|
+
"def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(position)}"
|
|
25
40
|
end
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
43
|
+
# @param other [Entity]
|
|
44
|
+
#
|
|
45
|
+
# @return [::T::Boolean]
|
|
28
46
|
def ==(other)
|
|
29
|
-
|
|
47
|
+
case other
|
|
48
|
+
when self.class
|
|
49
|
+
@body == other.body && super
|
|
50
|
+
else
|
|
51
|
+
false
|
|
52
|
+
end
|
|
30
53
|
end
|
|
31
54
|
end
|
|
32
55
|
end
|
|
@@ -1,34 +1,73 @@
|
|
|
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
|
+
include ::CSVPlusPlus::Entities::HasIdentifier
|
|
12
|
+
|
|
13
|
+
ArgumentsType = type_member { { fixed: ::CSVPlusPlus::Entities::Entity } }
|
|
14
|
+
public_constant :ArgumentsType
|
|
15
|
+
|
|
16
|
+
sig { returns(::T::Boolean) }
|
|
9
17
|
attr_reader :infix
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
sig { returns(::Symbol) }
|
|
20
|
+
attr_reader :id
|
|
21
|
+
|
|
22
|
+
sig do
|
|
23
|
+
params(
|
|
24
|
+
id: ::Symbol,
|
|
25
|
+
arguments: ::T::Array[::CSVPlusPlus::Entities::FunctionCall::ArgumentsType],
|
|
26
|
+
infix: ::T::Boolean
|
|
27
|
+
).void
|
|
28
|
+
end
|
|
29
|
+
# @param id [::String] The name of the function
|
|
12
30
|
# @param arguments [Array<Entity>] The arguments to the function
|
|
13
|
-
# @param infix [
|
|
31
|
+
# @param infix [T::Boolean] Whether the function is infix
|
|
14
32
|
def initialize(id, arguments, infix: false)
|
|
15
|
-
super(
|
|
33
|
+
super(arguments:)
|
|
16
34
|
|
|
35
|
+
@id = ::T.let(identifier(id), ::Symbol)
|
|
17
36
|
@infix = infix
|
|
18
37
|
end
|
|
19
38
|
|
|
20
|
-
|
|
21
|
-
|
|
39
|
+
sig { override.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
40
|
+
# @param position [Position]
|
|
41
|
+
#
|
|
42
|
+
# @return [::String]
|
|
43
|
+
def evaluate(position)
|
|
44
|
+
evaluated_arguments = evaluate_arguments(position)
|
|
45
|
+
|
|
22
46
|
if @infix
|
|
23
|
-
"(#{
|
|
47
|
+
"(#{evaluated_arguments.join(" #{@id} ")})"
|
|
24
48
|
else
|
|
25
|
-
"#{@id.to_s.upcase}(#{
|
|
49
|
+
"#{@id.to_s.upcase}(#{evaluated_arguments.join(', ')})"
|
|
26
50
|
end
|
|
27
51
|
end
|
|
28
52
|
|
|
29
|
-
|
|
53
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
54
|
+
# @param other [BasicObject]
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean]
|
|
30
57
|
def ==(other)
|
|
31
|
-
|
|
58
|
+
case other
|
|
59
|
+
when self.class
|
|
60
|
+
@id == other.id && @infix == other.infix
|
|
61
|
+
else
|
|
62
|
+
false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
sig { params(position: ::CSVPlusPlus::Runtime::Position).returns(::T::Array[::String]) }
|
|
69
|
+
def evaluate_arguments(position)
|
|
70
|
+
@arguments.map { |arg| arg.evaluate(position) }
|
|
32
71
|
end
|
|
33
72
|
end
|
|
34
73
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module CSVPlusPlus
|
|
5
|
+
module Entities
|
|
6
|
+
# Can be included on any class that has a comparable id
|
|
7
|
+
module HasIdentifier
|
|
8
|
+
extend ::T::Sig
|
|
9
|
+
|
|
10
|
+
sig { params(symbol: ::Symbol).returns(::Symbol) }
|
|
11
|
+
# Variables and functions are case insensitive. I hate it but it's how excel is
|
|
12
|
+
#
|
|
13
|
+
# @param symbol [Symbol]
|
|
14
|
+
def identifier(symbol)
|
|
15
|
+
symbol.downcase
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# typed: strict
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
module CSVPlusPlus
|
|
@@ -5,29 +6,47 @@ module CSVPlusPlus
|
|
|
5
6
|
# A number value
|
|
6
7
|
#
|
|
7
8
|
# @attr_reader value [Numeric] The parsed number value
|
|
8
|
-
class Number < Entity
|
|
9
|
+
class Number < ::CSVPlusPlus::Entities::Entity
|
|
10
|
+
extend ::T::Sig
|
|
11
|
+
|
|
12
|
+
sig { returns(::Numeric) }
|
|
9
13
|
attr_reader :value
|
|
10
14
|
|
|
15
|
+
sig { params(value: ::T.any(::String, ::Numeric)).void }
|
|
11
16
|
# @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
|
|
12
17
|
def initialize(value)
|
|
13
|
-
super(
|
|
18
|
+
super()
|
|
14
19
|
|
|
15
20
|
@value =
|
|
16
|
-
|
|
17
|
-
value.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
::T.let(
|
|
22
|
+
(if value.is_a?(::String)
|
|
23
|
+
value.include?('.') ? Float(value) : Integer(value, 10)
|
|
24
|
+
else
|
|
25
|
+
value
|
|
26
|
+
end),
|
|
27
|
+
::Numeric
|
|
28
|
+
)
|
|
21
29
|
end
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
sig { override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
32
|
+
# @param _position [Position]
|
|
33
|
+
#
|
|
34
|
+
# @return [::String]
|
|
35
|
+
def evaluate(_position)
|
|
25
36
|
@value.to_s
|
|
26
37
|
end
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
40
|
+
# @param other [BasicObject]
|
|
41
|
+
#
|
|
42
|
+
# @return [::T::Boolean]
|
|
29
43
|
def ==(other)
|
|
30
|
-
|
|
44
|
+
case other
|
|
45
|
+
when self.class
|
|
46
|
+
@value == other.value
|
|
47
|
+
else
|
|
48
|
+
false
|
|
49
|
+
end
|
|
31
50
|
end
|
|
32
51
|
end
|
|
33
52
|
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module CSVPlusPlus
|
|
5
|
+
module Entities
|
|
6
|
+
# A reference to something - this is typically either a cell reference (handled by +A1Reference+) or a reference
|
|
7
|
+
# to a variable.
|
|
8
|
+
#
|
|
9
|
+
# One sticking point with the design of this class is that we don't know if a reference is a variable reference
|
|
10
|
+
# unless we look at currently defined variables and see if there is one by that name. Since all cell references
|
|
11
|
+
# can be a valid variable name, we can't be sure which is which. So we delegate that decision as late as possible
|
|
12
|
+
# - in #evaluate
|
|
13
|
+
#
|
|
14
|
+
# @attr_reader scoped_to_expand [Expand, nil] If set, the expand in which this variable is scoped to. It cannot be
|
|
15
|
+
# resolved outside of the given expand.
|
|
16
|
+
class Reference < ::CSVPlusPlus::Entities::Entity
|
|
17
|
+
extend ::T::Sig
|
|
18
|
+
include ::CSVPlusPlus::Entities::HasIdentifier
|
|
19
|
+
|
|
20
|
+
sig { returns(::T.nilable(::String)) }
|
|
21
|
+
attr_reader :ref
|
|
22
|
+
|
|
23
|
+
# sig { returns(::T.nilable(::CSVPlusPlus::A1Reference)) }
|
|
24
|
+
# attr_reader :a1_ref
|
|
25
|
+
|
|
26
|
+
sig { params(ref: ::T.nilable(::String), a1_ref: ::T.nilable(::CSVPlusPlus::A1Reference)).void }
|
|
27
|
+
# Either +ref+, +cell_index+ or +row_index+ must be specified.
|
|
28
|
+
#
|
|
29
|
+
# @param ref [Integer, nil] An A1-style cell reference (that will be parsed into it's row/cell indexes).
|
|
30
|
+
# @param a1_ref [Integer, nil] An A1-style cell reference (that will be parsed into it's row/cell indexes).
|
|
31
|
+
def initialize(ref: nil, a1_ref: nil)
|
|
32
|
+
super()
|
|
33
|
+
|
|
34
|
+
raise(::ArgumentError, 'Must specify :ref or :a1_ref') unless ref || a1_ref
|
|
35
|
+
|
|
36
|
+
@ref = ref
|
|
37
|
+
@a1_ref = a1_ref
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
|
|
41
|
+
# @param other [BasicObject]
|
|
42
|
+
#
|
|
43
|
+
# @return [boolean]
|
|
44
|
+
def ==(other)
|
|
45
|
+
case other
|
|
46
|
+
when self.class
|
|
47
|
+
a1_ref == other.a1_ref
|
|
48
|
+
else
|
|
49
|
+
false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
sig { returns(::CSVPlusPlus::A1Reference) }
|
|
54
|
+
# @return [A1Reference]
|
|
55
|
+
def a1_ref
|
|
56
|
+
@a1_ref ||= ::CSVPlusPlus::A1Reference.new(ref: ::T.must(ref))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { override.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
|
|
60
|
+
# Get the A1-style cell reference
|
|
61
|
+
#
|
|
62
|
+
# @param position [Position] The current position
|
|
63
|
+
#
|
|
64
|
+
# @return [::String] An A1-style reference
|
|
65
|
+
def evaluate(position)
|
|
66
|
+
# TODO: ugh make to_a1_ref not return a nil
|
|
67
|
+
ref || a1_ref.to_a1_ref(position) || ''
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
sig { returns(::T.nilable(::Symbol)) }
|
|
71
|
+
# @return [Symbol]
|
|
72
|
+
def id
|
|
73
|
+
ref && identifier(::T.must(ref).to_sym)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|