csv_plus_plus 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/docs/CHANGELOG.md +16 -0
  4. data/lib/csv_plus_plus/a1_reference.rb +202 -0
  5. data/lib/csv_plus_plus/benchmarked_compiler.rb +3 -3
  6. data/lib/csv_plus_plus/cell.rb +1 -35
  7. data/lib/csv_plus_plus/cli.rb +43 -80
  8. data/lib/csv_plus_plus/cli_flag.rb +71 -70
  9. data/lib/csv_plus_plus/color.rb +1 -1
  10. data/lib/csv_plus_plus/compiler.rb +31 -21
  11. data/lib/csv_plus_plus/entities/ast_builder.rb +11 -4
  12. data/lib/csv_plus_plus/entities/boolean.rb +16 -9
  13. data/lib/csv_plus_plus/entities/builtins.rb +68 -40
  14. data/lib/csv_plus_plus/entities/date.rb +14 -11
  15. data/lib/csv_plus_plus/entities/entity.rb +11 -29
  16. data/lib/csv_plus_plus/entities/entity_with_arguments.rb +18 -31
  17. data/lib/csv_plus_plus/entities/function.rb +22 -11
  18. data/lib/csv_plus_plus/entities/function_call.rb +35 -11
  19. data/lib/csv_plus_plus/entities/has_identifier.rb +19 -0
  20. data/lib/csv_plus_plus/entities/number.rb +15 -10
  21. data/lib/csv_plus_plus/entities/reference.rb +77 -0
  22. data/lib/csv_plus_plus/entities/runtime_value.rb +36 -23
  23. data/lib/csv_plus_plus/entities/string.rb +13 -10
  24. data/lib/csv_plus_plus/entities.rb +2 -18
  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 +18 -5
  28. data/lib/csv_plus_plus/error/formula_syntax_error.rb +12 -13
  29. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +10 -36
  30. data/lib/csv_plus_plus/error/modifier_validation_error.rb +6 -32
  31. data/lib/csv_plus_plus/error/positional_error.rb +15 -0
  32. data/lib/csv_plus_plus/error/writer_error.rb +1 -1
  33. data/lib/csv_plus_plus/error.rb +4 -1
  34. data/lib/csv_plus_plus/error_formatter.rb +111 -0
  35. data/lib/csv_plus_plus/google_api_client.rb +18 -8
  36. data/lib/csv_plus_plus/lexer/racc_lexer.rb +144 -0
  37. data/lib/csv_plus_plus/lexer/tokenizer.rb +53 -17
  38. data/lib/csv_plus_plus/lexer.rb +40 -1
  39. data/lib/csv_plus_plus/modifier/data_validation.rb +1 -1
  40. data/lib/csv_plus_plus/modifier/expand.rb +17 -0
  41. data/lib/csv_plus_plus/modifier.rb +6 -1
  42. data/lib/csv_plus_plus/options/file_options.rb +49 -0
  43. data/lib/csv_plus_plus/options/google_sheets_options.rb +42 -0
  44. data/lib/csv_plus_plus/options/options.rb +97 -0
  45. data/lib/csv_plus_plus/options.rb +22 -110
  46. data/lib/csv_plus_plus/parser/cell_value.tab.rb +65 -66
  47. data/lib/csv_plus_plus/parser/code_section.tab.rb +92 -84
  48. data/lib/csv_plus_plus/parser/modifier.tab.rb +40 -30
  49. data/lib/csv_plus_plus/reader/csv.rb +50 -0
  50. data/lib/csv_plus_plus/reader/google_sheets.rb +129 -0
  51. data/lib/csv_plus_plus/reader/reader.rb +27 -0
  52. data/lib/csv_plus_plus/reader/rubyxl.rb +37 -0
  53. data/lib/csv_plus_plus/reader.rb +14 -0
  54. data/lib/csv_plus_plus/runtime/graph.rb +6 -6
  55. data/lib/csv_plus_plus/runtime/{position_tracker.rb → position.rb} +16 -5
  56. data/lib/csv_plus_plus/runtime/references.rb +32 -27
  57. data/lib/csv_plus_plus/runtime/runtime.rb +73 -67
  58. data/lib/csv_plus_plus/runtime/scope.rb +280 -0
  59. data/lib/csv_plus_plus/runtime.rb +9 -9
  60. data/lib/csv_plus_plus/source_code.rb +14 -9
  61. data/lib/csv_plus_plus/template.rb +17 -12
  62. data/lib/csv_plus_plus/version.rb +1 -1
  63. data/lib/csv_plus_plus/writer/csv.rb +32 -5
  64. data/lib/csv_plus_plus/writer/excel.rb +19 -6
  65. data/lib/csv_plus_plus/writer/file_backer_upper.rb +27 -14
  66. data/lib/csv_plus_plus/writer/google_sheets.rb +23 -129
  67. data/lib/csv_plus_plus/writer/{google_sheet_builder.rb → google_sheets_builder.rb} +39 -55
  68. data/lib/csv_plus_plus/writer/merger.rb +31 -0
  69. data/lib/csv_plus_plus/writer/open_document.rb +16 -2
  70. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +68 -43
  71. data/lib/csv_plus_plus/writer/writer.rb +42 -0
  72. data/lib/csv_plus_plus/writer.rb +58 -19
  73. data/lib/csv_plus_plus.rb +26 -14
  74. metadata +37 -12
  75. data/lib/csv_plus_plus/entities/cell_reference.rb +0 -231
  76. data/lib/csv_plus_plus/entities/variable.rb +0 -37
  77. data/lib/csv_plus_plus/error/syntax_error.rb +0 -71
  78. data/lib/csv_plus_plus/google_options.rb +0 -32
  79. data/lib/csv_plus_plus/lexer/lexer.rb +0 -89
  80. data/lib/csv_plus_plus/runtime/can_define_references.rb +0 -87
  81. data/lib/csv_plus_plus/runtime/can_resolve_references.rb +0 -209
  82. data/lib/csv_plus_plus/writer/base_writer.rb +0 -45
@@ -42,7 +42,7 @@ module CSVPlusPlus
42
42
  red_hex, green_hex, blue_hex = hex_string.strip.match(::CSVPlusPlus::Color::HEX_STRING_REGEXP)
43
43
  &.captures
44
44
  &.map { |s| s.length == 1 ? s + s : s }
45
- raise(::CSVPlusPlus::Error::Error, "Invalid color: #{hex_string}") unless red_hex && green_hex && blue_hex
45
+ raise(::CSVPlusPlus::Error::CompilerError, "Invalid color: #{hex_string}") unless red_hex && green_hex && blue_hex
46
46
 
47
47
  @red_hex = ::T.let(red_hex, ::String)
48
48
  @green_hex = ::T.let(green_hex, ::String)
@@ -11,7 +11,7 @@ module CSVPlusPlus
11
11
  class Compiler
12
12
  extend ::T::Sig
13
13
 
14
- sig { returns(::CSVPlusPlus::Options) }
14
+ sig { returns(::CSVPlusPlus::Options::Options) }
15
15
  attr_reader :options
16
16
 
17
17
  sig { returns(::CSVPlusPlus::Runtime::Runtime) }
@@ -19,7 +19,7 @@ module CSVPlusPlus
19
19
 
20
20
  sig do
21
21
  params(
22
- options: ::CSVPlusPlus::Options,
22
+ options: ::CSVPlusPlus::Options::Options,
23
23
  runtime: ::CSVPlusPlus::Runtime::Runtime,
24
24
  block: ::T.proc.params(arg0: ::CSVPlusPlus::Compiler).void
25
25
  ).void
@@ -37,10 +37,10 @@ module CSVPlusPlus
37
37
  block.call(new(options:, runtime:))
38
38
  end
39
39
  ensure
40
- runtime.cleanup!
40
+ runtime.position.cleanup!
41
41
  end
42
42
 
43
- sig { params(options: ::CSVPlusPlus::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
43
+ sig { params(options: ::CSVPlusPlus::Options::Options, runtime: ::CSVPlusPlus::Runtime::Runtime).void }
44
44
  # @param options [Options]
45
45
  # @param runtime [Runtime]
46
46
  def initialize(options:, runtime:)
@@ -49,7 +49,7 @@ module CSVPlusPlus
49
49
 
50
50
  # TODO: infer a type
51
51
  # allow user-supplied key/values to override anything global or from the code section
52
- @runtime.def_variables(
52
+ @runtime.scope.def_variables(
53
53
  options.key_values.transform_values { |v| ::CSVPlusPlus::Entities::String.new(v.to_s) }
54
54
  )
55
55
  end
@@ -72,17 +72,17 @@ module CSVPlusPlus
72
72
  rows = parse_csv_section!
73
73
 
74
74
  ::CSVPlusPlus::Template.new(rows:, runtime: @runtime).tap do |t|
75
- t.validate_infinite_expands(@runtime)
75
+ t.validate_infinite_expands
76
76
  expanding! { t.expand_rows! }
77
77
  bind_all_vars! { t.bind_all_vars!(@runtime) }
78
78
  resolve_all_cells!(t)
79
79
  end
80
80
  end
81
81
 
82
- sig { params(block: ::T.proc.params(runtime: ::CSVPlusPlus::Runtime::Runtime).void).void }
82
+ sig { params(block: ::T.proc.params(position: ::CSVPlusPlus::Runtime::Position).void).void }
83
83
  # Write the compiled results
84
84
  def outputting!(&block)
85
- @runtime.start_at_csv! { block.call(@runtime) }
85
+ @runtime.start_at_csv! { block.call(@runtime.position) }
86
86
  end
87
87
 
88
88
  protected
@@ -94,7 +94,7 @@ module CSVPlusPlus
94
94
  # TODO: this flow can probably be refactored, it used to have more needs back when we had to
95
95
  # parse and save the code_section
96
96
  parsing_code_section do |input|
97
- csv_section = ::CSVPlusPlus::Parser::CodeSection.new.parse(input, @runtime)
97
+ csv_section = ::CSVPlusPlus::Parser::CodeSection.new(@runtime.scope).parse(input)
98
98
 
99
99
  # return the csv_section to the caller because they're gonna re-write input with it
100
100
  next csv_section
@@ -108,16 +108,19 @@ module CSVPlusPlus
108
108
  # @return [Array<Row>]
109
109
  def parse_csv_section!
110
110
  @runtime.start_at_csv! do
111
- @runtime.map_lines(::CSV.new(::T.unsafe(@runtime.input))) do |csv_row|
111
+ @runtime.position.map_lines(::CSV.new(::T.unsafe(@runtime.position.input))) do |csv_row|
112
112
  parse_row(::T.cast(csv_row, ::T::Array[::String]))
113
113
  end
114
114
  end
115
115
  ensure
116
116
  # we're done with the file and everything is in memory
117
- @runtime.cleanup!
117
+ @runtime.position.cleanup!
118
118
  end
119
119
 
120
- sig { params(template: ::CSVPlusPlus::Template).returns(::T::Array[::T::Array[::CSVPlusPlus::Entities::Entity]]) }
120
+ sig do
121
+ params(template: ::CSVPlusPlus::Template)
122
+ .returns(::T::Array[::T::Array[::T.nilable(::CSVPlusPlus::Entities::Entity)]])
123
+ end
121
124
  # Iterates through each cell of each row and resolves it's variable and function references.
122
125
  #
123
126
  # @param template [Template]
@@ -125,8 +128,8 @@ module CSVPlusPlus
125
128
  # @return [Array<Entity>]
126
129
  def resolve_all_cells!(template)
127
130
  @runtime.start_at_csv! do
128
- @runtime.map_all_cells(template.rows) do |cell|
129
- cell.ast = @runtime.resolve_cell_value if cell.ast
131
+ @runtime.position.map_all_cells(template.rows) do |cell|
132
+ cell.ast = @runtime.resolve_cell_value(::T.must(cell.ast)) if cell.ast
130
133
  end
131
134
  end
132
135
  end
@@ -147,8 +150,8 @@ module CSVPlusPlus
147
150
 
148
151
  sig { params(block: ::T.proc.params(arg0: ::String).returns(::String)).void }
149
152
  def parsing_code_section(&block)
150
- csv_section = block.call(::T.must(::T.must(@runtime.input).read))
151
- @runtime.rewrite_input!(csv_section)
153
+ csv_section = block.call(::T.must(::T.must(@runtime.position.input).read))
154
+ @runtime.position.rewrite_input!(csv_section)
152
155
  end
153
156
 
154
157
  sig { params(csv_row: ::T::Array[::T.nilable(::String)]).returns(::CSVPlusPlus::Row) }
@@ -161,17 +164,24 @@ module CSVPlusPlus
161
164
  def parse_row(csv_row)
162
165
  row_modifier = ::CSVPlusPlus::Modifier.new(@options, row_level: true)
163
166
 
164
- cells = @runtime.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
167
+ cells = @runtime.position.map_row(csv_row) { |value, _cell_index| parse_cell(value || '', row_modifier) }
165
168
 
166
- ::CSVPlusPlus::Row.new(cells:, index: @runtime.row_index, modifier: row_modifier)
169
+ ::CSVPlusPlus::Row.new(cells:, index: @runtime.position.row_index, modifier: row_modifier)
167
170
  end
168
171
 
169
172
  sig { params(value: ::String, row_modifier: ::CSVPlusPlus::Modifier::Modifier).returns(::CSVPlusPlus::Cell) }
170
173
  def parse_cell(value, row_modifier)
171
174
  cell_modifier = ::CSVPlusPlus::Modifier.new(@options)
172
- parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value, @runtime)
173
-
174
- ::CSVPlusPlus::Cell.parse(parsed_value, runtime:, modifier: cell_modifier)
175
+ parsed_value = ::CSVPlusPlus::Parser::Modifier.new(cell_modifier:, row_modifier:).parse(value)
176
+
177
+ ::CSVPlusPlus::Cell.new(
178
+ value: parsed_value,
179
+ row_index: @runtime.position.row_index,
180
+ index: @runtime.position.cell_index,
181
+ modifier: cell_modifier
182
+ ).tap do |c|
183
+ c.ast = ::CSVPlusPlus::Parser::CellValue.new.parse(parsed_value) unless parsed_value.nil?
184
+ end
175
185
  end
176
186
  end
177
187
  # rubocop:enable Metrics/ClassLength
@@ -25,8 +25,7 @@ module CSVPlusPlus
25
25
  # @return [Entity, #super]
26
26
  # rubocop:disable Naming/BlockForwarding
27
27
  def method_missing(method_name, *args, **kwargs, &block)
28
- entity_class_name = method_name.to_s.split('_').map(&:capitalize).join.to_sym
29
- ::CSVPlusPlus::Entities.const_get(entity_class_name).new(*args, **kwargs, &block)
28
+ ::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name)).new(*args, **kwargs, &block)
30
29
  rescue ::NameError
31
30
  super
32
31
  end
@@ -41,10 +40,18 @@ module CSVPlusPlus
41
40
  #
42
41
  # @return [::T::Boolean, #super]
43
42
  def respond_to_missing?(method_name, *_args)
44
- !::CSVPlusPlus::Entities::Type.deserialize(method_name.to_s.gsub('_', '')).nil?
45
- rescue ::KeyError
43
+ ::CSVPlusPlus::Entities.const_get(snake_case_to_class_name(method_name))
44
+ true
45
+ rescue ::NameError
46
46
  super
47
47
  end
48
+
49
+ private
50
+
51
+ sig { params(method_name: ::Symbol).returns(::Symbol) }
52
+ def snake_case_to_class_name(method_name)
53
+ method_name.to_s.split('_').map(&:capitalize).join.to_sym
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -6,34 +6,41 @@ module CSVPlusPlus
6
6
  # A boolean value
7
7
  #
8
8
  # @attr_reader value [true, false]
9
- class Boolean < Entity
9
+ class Boolean < ::CSVPlusPlus::Entities::Entity
10
+ extend ::T::Sig
11
+
10
12
  sig { returns(::T::Boolean) }
11
13
  attr_reader :value
12
14
 
13
15
  sig { params(value: ::T.any(::String, ::T::Boolean)).void }
14
16
  # @param value [::String, boolean]
15
17
  def initialize(value)
16
- super(::CSVPlusPlus::Entities::Type::Boolean)
18
+ super()
17
19
  # TODO: probably can do a lot better in general on type validation
18
20
  @value = ::T.let(value.is_a?(::String) ? (value.downcase == 'true') : value, ::T::Boolean)
19
21
  end
20
22
 
21
- sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
22
- # @param _runtime [Runtime]
23
+ sig do
24
+ override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String)
25
+ end
26
+ # @param _position [Position]
23
27
  #
24
28
  # @return [::String]
25
- def evaluate(_runtime)
29
+ def evaluate(_position)
26
30
  @value.to_s.upcase
27
31
  end
28
32
 
29
- sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
33
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
30
34
  # @param other [Entity]
31
35
  #
32
36
  # @return [::T::Boolean]
33
37
  def ==(other)
34
- return false unless super
35
-
36
- other.is_a?(self.class) && value == other.value
38
+ case other
39
+ when self.class
40
+ value == other.value
41
+ else
42
+ false
43
+ end
37
44
  end
38
45
  end
39
46
  end
@@ -1,58 +1,86 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module CSVPlusPlus
5
5
  module Entities
6
- # Provides ASTs for builtin functions and variables
6
+ # Provides +RuntimeValue+s for builtin functions and variables
7
7
  module Builtins
8
+ extend ::T::Sig
9
+
8
10
  extend ::CSVPlusPlus::Entities::ASTBuilder
9
11
 
10
- VARIABLES = {
11
- # The number (integer) of the current cell. Starts at 1
12
- cellnum: runtime_value(->(r) { number(r.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) }),
13
16
 
14
- # A reference to the current cell
15
- cellref: runtime_value(->(r) { cell_reference(row_index: r.row_index, cell_index: r.cell_index) }),
17
+ # A reference to the current cell
18
+ cellref: runtime_value(->(p, _args) { cell_ref(p.row_index, p.cell_index) }),
16
19
 
17
- # A reference to the row above
18
- rowabove: runtime_value(->(r) { cell_reference(row_index: [0, (r.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) }),
19
22
 
20
- # A reference to the row below
21
- rowbelow: runtime_value(->(r) { cell_reference(row_index: r.row_index + 1) }),
23
+ # A reference to the row below
24
+ rowbelow: runtime_value(->(p, _args) { cell_ref(p.row_index + 1) }),
22
25
 
23
- # The number (integer) of the current row. Starts at 1
24
- rownum: runtime_value(->(r) { number(r.rownum) }),
26
+ # The number (integer) of the current row. Starts at 1
27
+ rownum: runtime_value(->(p, _args) { number(p.rownum) }),
25
28
 
26
- # A reference to the current row
27
- rowref: runtime_value(->(r) { cell_reference(row_index: r.row_index) })
28
- }.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
+ )
29
34
  public_constant :VARIABLES
30
35
 
31
- FUNCTIONS = {
32
- # TODO: A reference to a cell in a given row?
33
- # A reference to a cell above the current row
34
- # cellabove: runtime_value(->(r, args) { cell_reference(ref: [args[0], [1, (r.rownum - 1)].max].join) }),
35
- cellabove: runtime_value(
36
- lambda { |r, args|
37
- cell_reference(cell_index: args[0].cell_index, row_index: [0, (r.row_index - 1)].max)
38
- }
39
- ),
40
-
41
- # A reference to a cell in the current row
42
- celladjacent: runtime_value(
43
- lambda { |r, args|
44
- cell_reference(cell_index: args[0].cell_index, row_index: r.row_index)
45
- }
46
- ),
47
-
48
- # A reference to a cell below the current row
49
- cellbelow: runtime_value(
50
- lambda { |r, args|
51
- cell_reference(cell_index: args[0].cell_index, row_index: r.row_index + 1)
52
- }
53
- )
54
- }.freeze
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) }),
42
+
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) }),
45
+
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
+ )
55
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
56
84
  end
57
85
  end
58
86
  end
@@ -6,7 +6,7 @@ module CSVPlusPlus
6
6
  # A date value
7
7
  #
8
8
  # @attr_reader value [Date] The parsed date
9
- class Date < Entity
9
+ class Date < ::CSVPlusPlus::Entities::Entity
10
10
  extend ::T::Sig
11
11
 
12
12
  sig { returns(::Date) }
@@ -30,7 +30,7 @@ module CSVPlusPlus
30
30
  sig { params(value: ::String).void }
31
31
  # @param value [::String] The user-inputted date value
32
32
  def initialize(value)
33
- super(::CSVPlusPlus::Entities::Type::Date)
33
+ super()
34
34
 
35
35
  parsed =
36
36
  begin
@@ -41,22 +41,25 @@ module CSVPlusPlus
41
41
  @value = ::T.let(parsed, ::Date)
42
42
  end
43
43
 
44
- sig { override.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
45
- # @param _runtime [Runtime]
44
+ sig { override.params(_position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
45
+ # @param _position [Position]
46
46
  #
47
47
  # @return [::String]
48
- def evaluate(_runtime)
48
+ def evaluate(_position)
49
49
  @value.strftime('%m/%d/%y')
50
50
  end
51
51
 
52
- sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
53
- # @param other [Entity]
52
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
53
+ # @param other [BasicObject]
54
54
  #
55
- # @return [T::Boolean]
55
+ # @return [Boolean]
56
56
  def ==(other)
57
- return false unless super
58
-
59
- other.is_a?(self.class) && other.value == @value
57
+ case other
58
+ when self.class
59
+ other.value == @value
60
+ else
61
+ false
62
+ end
60
63
  end
61
64
  end
62
65
  end
@@ -3,48 +3,30 @@
3
3
 
4
4
  module CSVPlusPlus
5
5
  module Entities
6
- # A basic building block of the abstract syntax tree (AST)
7
- #
8
- # @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
9
- # for variables it's the variable name
10
- # @attr_reader type [Entities::Type] The type of the entity. Each type should have a corresponding class definition
11
- # in CSVPlusPlus::Entities
6
+ # All classes that are a part of an AST must implement this interface
12
7
  class Entity
13
8
  extend ::T::Sig
14
9
  extend ::T::Helpers
15
10
 
16
11
  abstract!
17
12
 
18
- sig { returns(::T.nilable(::Symbol)) }
19
- attr_reader :id
20
-
21
- sig { returns(::CSVPlusPlus::Entities::Type) }
22
- attr_reader :type
23
-
24
- sig { params(type: ::CSVPlusPlus::Entities::Type, id: ::T.nilable(::Symbol)).void }
25
- # @param type [Entities::Type]
26
- # @param id [Symbol, nil]
27
- def initialize(type, id: nil)
28
- @type = type
29
- @id = ::T.let(id&.downcase&.to_sym || nil, ::T.nilable(::Symbol))
30
- end
31
-
32
- sig { overridable.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
33
- # Each class should define it's own version of #==
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
15
+ #
34
16
  # @param other [Entity]
35
17
  #
36
18
  # @return [boolean]
37
- def ==(other)
38
- self.class == other.class && @type == other.type && @id == other.id
39
- end
19
+ def ==(other); end
40
20
 
41
- sig { abstract.params(_runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
42
- # Uses the given +runtime+ to evaluate itself in the current context
21
+ sig do
22
+ abstract.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String)
23
+ end
24
+ # Uses the given +position+ to evaluate itself in the current context
43
25
  #
44
- # @param _runtime [Runtime] The current runtime
26
+ # @param position [Position] The current runtime
45
27
  #
46
28
  # @return [::String]
47
- def evaluate(_runtime); end
29
+ def evaluate(position); end
48
30
  end
49
31
  end
50
32
  end
@@ -7,50 +7,37 @@ module CSVPlusPlus
7
7
  # are function calls and function definitions
8
8
  #
9
9
  # @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
10
- class EntityWithArguments < Entity
10
+ class EntityWithArguments < ::CSVPlusPlus::Entities::Entity
11
11
  extend ::T::Sig
12
+ extend ::T::Helpers
13
+ extend ::T::Generic
12
14
 
13
15
  abstract!
14
16
 
15
- sig { returns(::T::Array[::CSVPlusPlus::Entities::Entity]) }
17
+ ArgumentsType = type_member
18
+ public_constant :ArgumentsType
19
+
20
+ sig { returns(::T::Array[::CSVPlusPlus::Entities::EntityWithArguments::ArgumentsType]) }
16
21
  attr_reader :arguments
17
22
 
18
- sig do
19
- params(
20
- type: ::CSVPlusPlus::Entities::Type,
21
- id: ::T.nilable(::Symbol),
22
- arguments: ::T::Array[::CSVPlusPlus::Entities::Entity]
23
- ).void
24
- end
25
- # @param type [Entities::Type]
26
- # @param id [::String]
27
- # @param arguments [Array<Entity>]
28
- def initialize(type, id: nil, arguments: [])
29
- super(type, id:)
23
+ sig { params(arguments: ::T::Array[::CSVPlusPlus::Entities::EntityWithArguments::ArgumentsType]).void }
24
+ # @param arguments [Array<ArgumentsType>]
25
+ def initialize(arguments: [])
26
+ super()
30
27
  @arguments = arguments
31
28
  end
32
29
 
33
- sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
30
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
34
31
  # @param other [Entity]
35
32
  #
36
33
  # @return [boolean]
37
34
  def ==(other)
38
- return false unless other.is_a?(self.class)
39
-
40
- @arguments == other.arguments && super
41
- end
42
-
43
- protected
44
-
45
- sig do
46
- params(arguments: ::T::Array[::CSVPlusPlus::Entities::Entity])
47
- .returns(::T::Array[::CSVPlusPlus::Entities::Entity])
48
- end
49
- attr_writer :arguments
50
-
51
- sig { params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::T::Array[::String]) }
52
- def evaluate_arguments(runtime)
53
- @arguments.map { |arg| arg.evaluate(runtime) }
35
+ case other
36
+ when self.class
37
+ @arguments == other.arguments
38
+ else
39
+ false
40
+ end
54
41
  end
55
42
  end
56
43
  end
@@ -7,8 +7,15 @@ module CSVPlusPlus
7
7
  #
8
8
  # @attr_reader body [Entity] The body of the function. +body+ can contain variable references
9
9
  # from +@arguments+
10
- class Function < EntityWithArguments
10
+ class Function < ::CSVPlusPlus::Entities::EntityWithArguments
11
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
12
19
 
13
20
  sig { returns(::CSVPlusPlus::Entities::Entity) }
14
21
  attr_reader :body
@@ -18,27 +25,31 @@ module CSVPlusPlus
18
25
  # @param arguments [Array<Symbol>]
19
26
  # @param body [Entity]
20
27
  def initialize(id, arguments, body)
21
- super(::CSVPlusPlus::Entities::Type::Function, id:, arguments: arguments.map(&:to_sym))
28
+ super(arguments: arguments.map(&:to_sym))
22
29
 
23
30
  @body = ::T.let(body, ::CSVPlusPlus::Entities::Entity)
31
+ @id = ::T.let(identifier(id), ::Symbol)
24
32
  end
25
33
 
26
- sig { override.params(runtime: ::CSVPlusPlus::Runtime::Runtime).returns(::String) }
27
- # @param runtime [Runtime]
34
+ sig { override.params(position: ::CSVPlusPlus::Runtime::Position).returns(::String) }
35
+ # @param position [Position]
28
36
  #
29
- # @return [::String]
30
- def evaluate(runtime)
31
- "def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(runtime)}"
37
+ # @return [String]
38
+ def evaluate(position)
39
+ "def #{@id.to_s.upcase}(#{arguments.map(&:to_s).join(', ')}) #{@body.evaluate(position)}"
32
40
  end
33
41
 
34
- sig { override.params(other: ::CSVPlusPlus::Entities::Entity).returns(::T::Boolean) }
42
+ sig { override.params(other: ::BasicObject).returns(::T::Boolean) }
35
43
  # @param other [Entity]
36
44
  #
37
45
  # @return [::T::Boolean]
38
46
  def ==(other)
39
- return false unless super
40
-
41
- other.is_a?(self.class) && @body == other.body
47
+ case other
48
+ when self.class
49
+ @body == other.body && super
50
+ else
51
+ false
52
+ end
42
53
  end
43
54
  end
44
55
  end