csv_plus_plus 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|