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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +9 -5
  3. data/{CHANGELOG.md → docs/CHANGELOG.md} +25 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +70 -20
  6. data/lib/csv_plus_plus/cell.rb +29 -41
  7. data/lib/csv_plus_plus/cli.rb +53 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -71
  9. data/lib/csv_plus_plus/color.rb +32 -7
  10. data/lib/csv_plus_plus/compiler.rb +98 -66
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +30 -39
  12. data/lib/csv_plus_plus/entities/boolean.rb +26 -10
  13. data/lib/csv_plus_plus/entities/builtins.rb +66 -24
  14. data/lib/csv_plus_plus/entities/date.rb +42 -6
  15. data/lib/csv_plus_plus/entities/entity.rb +17 -69
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +44 -0
  17. data/lib/csv_plus_plus/entities/function.rb +34 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +49 -10
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +30 -11
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +43 -13
  23. data/lib/csv_plus_plus/entities/string.rb +23 -7
  24. data/lib/csv_plus_plus/entities.rb +7 -16
  25. data/lib/csv_plus_plus/error/cli_error.rb +17 -0
  26. data/lib/csv_plus_plus/error/compiler_error.rb +17 -0
  27. data/lib/csv_plus_plus/error/error.rb +25 -2
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -12
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +34 -12
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +21 -27
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +8 -0
  33. data/lib/csv_plus_plus/error.rb +5 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +25 -10
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +58 -17
  38. data/lib/csv_plus_plus/lexer.rb +64 -1
  39. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +1 -0
  40. data/lib/csv_plus_plus/modifier/data_validation.rb +138 -0
  41. data/lib/csv_plus_plus/modifier/expand.rb +78 -0
  42. data/lib/csv_plus_plus/modifier/google_sheet_modifier.rb +133 -0
  43. data/lib/csv_plus_plus/modifier/modifier.rb +222 -0
  44. data/lib/csv_plus_plus/modifier/modifier_validator.rb +243 -0
  45. data/lib/csv_plus_plus/modifier/rubyxl_modifier.rb +84 -0
  46. data/lib/csv_plus_plus/modifier.rb +89 -160
  47. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  48. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  49. data/lib/csv_plus_plus/options/options.rb +97 -0
  50. data/lib/csv_plus_plus/options.rb +34 -77
  51. data/lib/csv_plus_plus/parser/cell_value.tab.rb +66 -67
  52. data/lib/csv_plus_plus/parser/code_section.tab.rb +86 -83
  53. data/lib/csv_plus_plus/parser/modifier.tab.rb +57 -53
  54. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  55. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  56. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  57. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  58. data/lib/csv_plus_plus/reader.rb +14 -0
  59. data/lib/csv_plus_plus/row.rb +53 -12
  60. data/lib/csv_plus_plus/runtime/graph.rb +68 -0
  61. data/lib/csv_plus_plus/runtime/position.rb +242 -0
  62. data/lib/csv_plus_plus/runtime/references.rb +115 -0
  63. data/lib/csv_plus_plus/runtime/runtime.rb +132 -0
  64. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  65. data/lib/csv_plus_plus/runtime.rb +34 -191
  66. data/lib/csv_plus_plus/source_code.rb +71 -0
  67. data/lib/csv_plus_plus/template.rb +71 -39
  68. data/lib/csv_plus_plus/version.rb +2 -1
  69. data/lib/csv_plus_plus/writer/csv.rb +37 -8
  70. data/lib/csv_plus_plus/writer/excel.rb +25 -5
  71. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -13
  72. data/lib/csv_plus_plus/writer/google_sheets.rb +29 -85
  73. data/lib/csv_plus_plus/writer/google_sheets_builder.rb +179 -0
  74. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  75. data/lib/csv_plus_plus/writer/open_document.rb +21 -2
  76. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +140 -42
  77. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  78. data/lib/csv_plus_plus/writer.rb +79 -10
  79. data/lib/csv_plus_plus.rb +47 -18
  80. metadata +50 -21
  81. data/lib/csv_plus_plus/can_define_references.rb +0 -88
  82. data/lib/csv_plus_plus/can_resolve_references.rb +0 -8
  83. data/lib/csv_plus_plus/data_validation.rb +0 -138
  84. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -60
  85. data/lib/csv_plus_plus/entities/variable.rb +0 -25
  86. data/lib/csv_plus_plus/error/syntax_error.rb +0 -58
  87. data/lib/csv_plus_plus/expand.rb +0 -20
  88. data/lib/csv_plus_plus/google_options.rb +0 -27
  89. data/lib/csv_plus_plus/graph.rb +0 -62
  90. data/lib/csv_plus_plus/lexer/lexer.rb +0 -85
  91. data/lib/csv_plus_plus/references.rb +0 -68
  92. data/lib/csv_plus_plus/scope.rb +0 -196
  93. data/lib/csv_plus_plus/validated_modifier.rb +0 -164
  94. data/lib/csv_plus_plus/writer/base_writer.rb +0 -20
  95. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +0 -147
  96. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +0 -77
  97. 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 ASTs for builtin functions and variables
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
- # The number (integer) of the current cell. Starts at 1
11
- cellnum: runtime_value(->(runtime) { number(runtime.cell_index + 1) }),
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
- # A reference to the current cell
14
- cellref: runtime_value(->(runtime) { ref(row_index: runtime.row_index, cell_index: runtime.cell_index) }),
17
+ # A reference to the current cell
18
+ cellref: runtime_value(->(p, _args) { cell_ref(p.row_index, p.cell_index) }),
15
19
 
16
- # A reference to the row above
17
- rowabove: runtime_value(->(runtime) { ref(row_index: [0, (runtime.row_index - 1)].max) }),
20
+ # A reference to the row above
21
+ rowabove: runtime_value(->(p, _args) { cell_ref([0, (p.row_index - 1)].max) }),
18
22
 
19
- # A reference to the row below
20
- rowbelow: runtime_value(->(runtime) { ref(row_index: runtime.row_index + 1) }),
23
+ # A reference to the row below
24
+ rowbelow: runtime_value(->(p, _args) { cell_ref(p.row_index + 1) }),
21
25
 
22
- # The number (integer) of the current row. Starts at 1
23
- rownum: runtime_value(->(runtime) { number(runtime.rownum) }),
26
+ # The number (integer) of the current row. Starts at 1
27
+ rownum: runtime_value(->(p, _args) { number(p.rownum) }),
24
28
 
25
- # A reference to the current row
26
- rowref: runtime_value(->(runtime) { ref(row_index: runtime.row_index) })
27
- }.freeze
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
- FUNCTIONS = {
31
- # TODO: A reference to a cell in a given row?
32
- # A reference to a cell above the current row
33
- cellabove: runtime_value(->(runtime, args) { cell_reference([args[0], [1, (runtime.rownum - 1)].max].join) }),
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
- # A reference to a cell in the current row
36
- celladjacent: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum].join) }),
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
- # A reference to a cell below the current row
39
- cellbelow: runtime_value(->(runtime, args) { cell_reference([args[0], runtime.rownum + 1].join) })
40
- }.freeze
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
- !(date_string.strip =~ ::CSVPlusPlus::Entities::Date::DATE_STRING_REGEXP).nil?
24
+ new(date_string)
25
+ true
26
+ rescue ::Date::Error
27
+ false
20
28
  end
21
29
 
22
- # @param value [String] The user-inputted date value
30
+ sig { params(value: ::String).void }
31
+ # @param value [::String] The user-inputted date value
23
32
  def initialize(value)
24
- super(:date)
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
- @value = ::Date.parse(value)
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
- # A basic building block of the abstract syntax tree (AST)
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
- attr_reader :id, :type
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
- # Respond to predicates that correspond to types like #boolean?, #string?, etc
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
- # Respond to predicates by type (entity.boolean?, entity.string?, etc)
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 method_name [Symbol] The +method_name+ to respond to
16
+ # @param other [Entity]
42
17
  #
43
18
  # @return [boolean]
44
- def respond_to_missing?(method_name, *_arguments)
45
- (method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
46
- end
19
+ def ==(other); end
47
20
 
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(', ')
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
- # from +@arguments+
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
- # @param id [Symbool, String] the name of the function - what it will be callable by
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(:function, id:, arguments: arguments.map(&:to_sym))
19
- @body = body
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 to_s
24
- "def #{@id.to_s.upcase}(#{arguments_to_s}) #{@body}"
38
+ def evaluate(position)
39
+ "def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(position)}"
25
40
  end
26
41
 
27
- # @return [boolean]
42
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
43
+ # @param other [Entity]
44
+ #
45
+ # @return [::T::Boolean]
28
46
  def ==(other)
29
- super && @body == other.body
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
- # @param id [String] The name of the function
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 [boolean] Whether the function is infix
31
+ # @param infix [T::Boolean] Whether the function is infix
14
32
  def initialize(id, arguments, infix: false)
15
- super(:function_call, id:, arguments:)
33
+ super(arguments:)
16
34
 
35
+ @id = ::T.let(identifier(id), ::Symbol)
17
36
  @infix = infix
18
37
  end
19
38
 
20
- # @return [String]
21
- def to_s
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
- "(#{arguments.join(" #{@id} ")})"
47
+ "(#{evaluated_arguments.join(" #{@id} ")})"
24
48
  else
25
- "#{@id.to_s.upcase}(#{arguments_to_s})"
49
+ "#{@id.to_s.upcase}(#{evaluated_arguments.join(', ')})"
26
50
  end
27
51
  end
28
52
 
29
- # @return [boolean]
53
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
54
+ # @param other [BasicObject]
55
+ #
56
+ # @return [Boolean]
30
57
  def ==(other)
31
- super && @id == other.id
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(:number)
18
+ super()
14
19
 
15
20
  @value =
16
- if value.instance_of?(::String)
17
- value.include?('.') ? Float(value) : Integer(value, 10)
18
- else
19
- value
20
- end
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
- # @return [String]
24
- def to_s
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
- # @return [boolean]
39
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
40
+ # @param other [BasicObject]
41
+ #
42
+ # @return [::T::Boolean]
29
43
  def ==(other)
30
- super && value == other.value
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