csv_plus_plus 0.1.3 → 0.2.1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -3
  3. data/docs/CHANGELOG.md +18 -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 +77 -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 +102 -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 +56 -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 +43 -18
  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
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module CSVPlusPlus
@@ -8,83 +8,90 @@ module CSVPlusPlus
8
8
  # @attr_reader long_flag [String] A definition of the long/word-based flag
9
9
  # @attr_reader description [String] A description of what the flag does
10
10
  # @attr_reader handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
11
- class CliFlag
12
- attr_reader :short_flag, :long_flag, :description, :handler
11
+ class CLIFlag
12
+ extend ::T::Sig
13
13
 
14
+ sig { returns(::String) }
15
+ attr_reader :description
16
+
17
+ sig { returns(::String) }
18
+ attr_reader :long_flag
19
+
20
+ sig { returns(::String) }
21
+ attr_reader :short_flag
22
+
23
+ sig { params(short_flag: ::String, long_flag: ::String, description: ::String).void }
14
24
  # @param short_flag [String] A definition of the short/single-character flag
15
25
  # @param long_flag [String] A definition of the long/word-based flag
16
26
  # @param description [String] A description of what the flag does
17
- # @param handler [Proc(Options, String)] A proc which is called to handle when this flag is seen
18
- def initialize(short_flag, long_flag, description, handler)
27
+ def initialize(short_flag, long_flag, description)
19
28
  @short_flag = short_flag
20
29
  @long_flag = long_flag
21
30
  @description = description
22
- @handler = handler
23
- end
24
-
25
- # @return [String]
26
- def to_s
27
- "#{@short_flag}, #{@long_flag} #{@description}"
28
31
  end
29
32
  end
30
- end
31
33
 
32
- SUPPORTED_CSVPP_FLAGS = [
33
- ::CSVPlusPlus::CliFlag.new(
34
- '-b',
35
- '--backup',
36
- 'Create a backup of the spreadsheet before applying changes.',
37
- ->(options, _v) { options.backup = true }
38
- ),
39
- ::CSVPlusPlus::CliFlag.new(
40
- '-c',
41
- '--create',
42
- "Create the sheet if it doesn't exist. It will use --sheet-name if specified",
43
- ->(options, _v) { options.create_if_not_exists = true }
44
- ),
45
- ::CSVPlusPlus::CliFlag.new(
46
- '-g SHEET_ID',
47
- '--google-sheet-id SHEET_ID',
48
- 'The id of the sheet - you can extract this from the URL: ' \
49
- 'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0',
50
- ->(options, v) { options.google_sheet_id = v }
51
- ),
52
- ::CSVPlusPlus::CliFlag.new(
53
- '-k',
54
- '--key-values KEY_VALUES',
55
- 'A comma-separated list of key=values which will be made available to the template',
56
- lambda do |options, v|
57
- options.key_values =
58
- begin
59
- [v.split('=')].to_h
60
- rescue ::StandardError
61
- {}
62
- end
63
- end
64
- ),
65
- ::CSVPlusPlus::CliFlag.new(
66
- '-n SHEET_NAME',
67
- '--sheet-name SHEET_NAME',
68
- 'The name of the sheet to apply the template to',
69
- ->(options, v) { options.sheet_name = v }
70
- ),
71
- ::CSVPlusPlus::CliFlag.new(
72
- '-o OUTPUT_FILE',
73
- '--output OUTPUT_FILE',
74
- 'The file to write to (must be .csv, .ods, .xls)',
75
- ->(options, v) { options.output_filename = v }
76
- ),
77
- ::CSVPlusPlus::CliFlag.new('-v', '--verbose', 'Enable verbose output', ->(options, _v) { options.verbose = true }),
78
- ::CSVPlusPlus::CliFlag.new(
79
- '-x OFFSET',
80
- '--offset-columns OFFSET',
81
- 'Apply the template offset by OFFSET cells',
82
- ->(options, v) { options.offset[0] = v }
83
- ),
84
- ::CSVPlusPlus::CliFlag.new(
85
- '-y OFFSET',
86
- '--offset-rows OFFSET',
87
- 'Apply the template offset by OFFSET rows',
88
- ->(options, v) { options.offset[1] = v }
34
+ FLAG_HANDLERS = ::T.let(
35
+ {
36
+ backup: ->(options, _v) { options.backup = true },
37
+ create: ->(options, _v) { options.create_if_not_exists = true },
38
+ 'key-values': lambda { |options, v|
39
+ options.key_values =
40
+ begin
41
+ [v.split('=')].to_h
42
+ rescue ::StandardError
43
+ {}
44
+ end
45
+ },
46
+ 'offset-columns': ->(options, v) { options.offset[0] = v },
47
+ 'offset-rows': ->(options, v) { options.offset[1] = v },
48
+ output: ->(options, v) { options.output_filename = ::Pathname.new(v) },
49
+ safe: ->(options, _v) { options.overwrite_values = false },
50
+ verbose: ->(options, _v) { options.verbose = true }
51
+ },
52
+ ::T::Hash[::Symbol, ::T.proc.params(options: ::CSVPlusPlus::Options::Options, v: ::String).void]
89
53
  )
90
- ].freeze
54
+ public_constant :FLAG_HANDLERS
55
+
56
+ SUPPORTED_CSVPP_FLAGS = ::T.let(
57
+ [
58
+ ::CSVPlusPlus::CLIFlag.new('-b', '--backup', 'Create a backup of the spreadsheet before applying changes.'),
59
+ ::CSVPlusPlus::CLIFlag.new(
60
+ '-c',
61
+ '--create',
62
+ "Create the sheet if it doesn't exist. It will use --sheet-name if specified"
63
+ ),
64
+ ::CSVPlusPlus::CLIFlag.new(
65
+ '-g SHEET_ID',
66
+ '--google-sheet-id SHEET_ID',
67
+ 'The id of the sheet - you can extract this from the URL: ' \
68
+ 'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0'
69
+ ),
70
+ ::CSVPlusPlus::CLIFlag.new(
71
+ '-k',
72
+ '--key-values KEY_VALUES',
73
+ 'A comma-separated list of key=values which will be made available to the template'
74
+ ),
75
+ ::CSVPlusPlus::CLIFlag.new(
76
+ '-n SHEET_NAME',
77
+ '--sheet-name SHEET_NAME',
78
+ 'The name of the sheet to apply the template to'
79
+ ),
80
+ ::CSVPlusPlus::CLIFlag.new(
81
+ '-o OUTPUT_FILE',
82
+ '--output OUTPUT_FILE',
83
+ 'The file to write to (must be .csv, .ods, .xls)'
84
+ ),
85
+ ::CSVPlusPlus::CLIFlag.new(
86
+ '-s',
87
+ '--safe',
88
+ 'Do not overwrite values in the spreadsheet being written to. The default is to overwrite'
89
+ ),
90
+ ::CSVPlusPlus::CLIFlag.new('-v', '--verbose', 'Enable verbose output'),
91
+ ::CSVPlusPlus::CLIFlag.new('-x OFFSET', '--offset-columns OFFSET', 'Apply the template offset by OFFSET cells'),
92
+ ::CSVPlusPlus::CLIFlag.new('-y OFFSET', '--offset-rows OFFSET', 'Apply the template offset by OFFSET rows')
93
+ ].freeze,
94
+ ::T::Array[::CSVPlusPlus::CLIFlag]
95
+ )
96
+ public_constant :SUPPORTED_CSVPP_FLAGS
97
+ end
@@ -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