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.
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