csv_plus_plus 0.1.0 → 0.1.2

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -1
  3. data/README.md +18 -62
  4. data/lib/csv_plus_plus/benchmarked_compiler.rb +62 -0
  5. data/lib/csv_plus_plus/can_define_references.rb +88 -0
  6. data/lib/csv_plus_plus/can_resolve_references.rb +8 -0
  7. data/lib/csv_plus_plus/cell.rb +3 -3
  8. data/lib/csv_plus_plus/cli.rb +24 -7
  9. data/lib/csv_plus_plus/color.rb +12 -6
  10. data/lib/csv_plus_plus/compiler.rb +156 -0
  11. data/lib/csv_plus_plus/data_validation.rb +138 -0
  12. data/lib/csv_plus_plus/{language → entities}/ast_builder.rb +5 -7
  13. data/lib/csv_plus_plus/entities/boolean.rb +31 -0
  14. data/lib/csv_plus_plus/{language → entities}/builtins.rb +2 -4
  15. data/lib/csv_plus_plus/entities/cell_reference.rb +60 -0
  16. data/lib/csv_plus_plus/entities/date.rb +30 -0
  17. data/lib/csv_plus_plus/entities/entity.rb +84 -0
  18. data/lib/csv_plus_plus/entities/function.rb +33 -0
  19. data/lib/csv_plus_plus/entities/function_call.rb +35 -0
  20. data/lib/csv_plus_plus/entities/number.rb +34 -0
  21. data/lib/csv_plus_plus/entities/runtime_value.rb +26 -0
  22. data/lib/csv_plus_plus/entities/string.rb +29 -0
  23. data/lib/csv_plus_plus/entities/variable.rb +25 -0
  24. data/lib/csv_plus_plus/entities.rb +33 -0
  25. data/lib/csv_plus_plus/error/error.rb +10 -0
  26. data/lib/csv_plus_plus/error/formula_syntax_error.rb +36 -0
  27. data/lib/csv_plus_plus/error/modifier_syntax_error.rb +27 -0
  28. data/lib/csv_plus_plus/error/modifier_validation_error.rb +49 -0
  29. data/lib/csv_plus_plus/{language → error}/syntax_error.rb +6 -14
  30. data/lib/csv_plus_plus/error/writer_error.rb +9 -0
  31. data/lib/csv_plus_plus/error.rb +9 -2
  32. data/lib/csv_plus_plus/expand.rb +3 -1
  33. data/lib/csv_plus_plus/google_api_client.rb +4 -0
  34. data/lib/csv_plus_plus/lexer/lexer.rb +19 -11
  35. data/lib/csv_plus_plus/modifier/conditional_formatting.rb +17 -0
  36. data/lib/csv_plus_plus/modifier.rb +73 -70
  37. data/lib/csv_plus_plus/options.rb +3 -0
  38. data/lib/csv_plus_plus/parser/cell_value.tab.rb +305 -0
  39. data/lib/csv_plus_plus/parser/code_section.tab.rb +410 -0
  40. data/lib/csv_plus_plus/parser/modifier.tab.rb +484 -0
  41. data/lib/csv_plus_plus/references.rb +68 -0
  42. data/lib/csv_plus_plus/row.rb +0 -3
  43. data/lib/csv_plus_plus/runtime.rb +199 -0
  44. data/lib/csv_plus_plus/scope.rb +196 -0
  45. data/lib/csv_plus_plus/template.rb +21 -5
  46. data/lib/csv_plus_plus/validated_modifier.rb +164 -0
  47. data/lib/csv_plus_plus/version.rb +1 -1
  48. data/lib/csv_plus_plus/writer/file_backer_upper.rb +6 -4
  49. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +24 -29
  50. data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +33 -12
  51. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +3 -6
  52. data/lib/csv_plus_plus.rb +41 -16
  53. metadata +34 -24
  54. data/lib/csv_plus_plus/code_section.rb +0 -68
  55. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +0 -65
  56. data/lib/csv_plus_plus/language/cell_value.tab.rb +0 -332
  57. data/lib/csv_plus_plus/language/code_section.tab.rb +0 -442
  58. data/lib/csv_plus_plus/language/compiler.rb +0 -157
  59. data/lib/csv_plus_plus/language/entities/boolean.rb +0 -33
  60. data/lib/csv_plus_plus/language/entities/cell_reference.rb +0 -33
  61. data/lib/csv_plus_plus/language/entities/entity.rb +0 -86
  62. data/lib/csv_plus_plus/language/entities/function.rb +0 -35
  63. data/lib/csv_plus_plus/language/entities/function_call.rb +0 -26
  64. data/lib/csv_plus_plus/language/entities/number.rb +0 -36
  65. data/lib/csv_plus_plus/language/entities/runtime_value.rb +0 -28
  66. data/lib/csv_plus_plus/language/entities/string.rb +0 -31
  67. data/lib/csv_plus_plus/language/entities/variable.rb +0 -25
  68. data/lib/csv_plus_plus/language/entities.rb +0 -28
  69. data/lib/csv_plus_plus/language/references.rb +0 -70
  70. data/lib/csv_plus_plus/language/runtime.rb +0 -205
  71. data/lib/csv_plus_plus/language/scope.rb +0 -188
  72. data/lib/csv_plus_plus/modifier.tab.rb +0 -907
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ # A validation on a cell value. Used to support the `validate=` modifier directive. This is mostly based on the
5
+ # Google Sheets API spec which can be seen here:
6
+ #
7
+ # {https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType}
8
+ #
9
+ # @attr_reader arguments [Array<::String>] The parsed arguments as required by the condition.
10
+ # @attr_reader condition [Symbol] The condition (:blank, :text_eq, :date_before, etc.)
11
+ # @attr_reader invalid_reason [::String, nil] If set, the reason why this modifier is not valid.
12
+ class DataValidation
13
+ attr_reader :arguments, :condition, :invalid_reason
14
+
15
+ # @param value [::String] The value to parse as a data validation
16
+ def initialize(value)
17
+ condition, args = unquote(value).split(/\s*:\s*/)
18
+ @arguments = unquote(args || '').split(/\s+/)
19
+ @condition = condition.to_sym
20
+
21
+ validate!
22
+ end
23
+
24
+ # Each data validation represented by (+condition+) has their own require
25
+ # @return [boolean]
26
+ def valid?
27
+ @invalid_reason.nil?
28
+ end
29
+
30
+ protected
31
+
32
+ def unquote(str)
33
+ # TODO: I'm pretty sure this isn't sufficient and we need to deal with the backslashes
34
+ str.gsub(/^['\s]*|['\s]*$/, '')
35
+ end
36
+
37
+ def invalid!(reason)
38
+ @invalid_reason = reason
39
+ end
40
+
41
+ def a_number(arg)
42
+ Float(arg)
43
+ rescue ::ArgumentError
44
+ invalid!("Requires a number but given: #{arg}")
45
+ end
46
+
47
+ def a1_notation(arg)
48
+ return arg if ::CSVPlusPlus::Entities::CellReference.valid_cell_reference?(arg)
49
+ end
50
+
51
+ def a_date(arg, allow_relative_date: false)
52
+ return arg if ::CSVPlusPlus::Entities::Date.valid_date?(arg)
53
+
54
+ if allow_relative_date
55
+ a_relative_date(arg)
56
+ else
57
+ invalid!("Requires a date but given: #{arg}")
58
+ end
59
+ end
60
+
61
+ def a_relative_date(arg)
62
+ return arg if %w[past_month past_week past_year yesterday today tomorrow].include?(arg.downcase)
63
+
64
+ invalid!('Requires a relative date: past_month, past_week, past_year, yesterday, today or tomorrow')
65
+ end
66
+
67
+ def no_args
68
+ return if @arguments.empty?
69
+
70
+ invalid!("Requires no arguments but #{@arguments.length} given: #{@arguments}")
71
+ end
72
+
73
+ def one_arg
74
+ return @arguments[0] if @arguments.length == 1
75
+
76
+ invalid!("Requires only one argument but #{@arguments.length} given: #{@arguments}")
77
+ end
78
+
79
+ def one_arg_or_more
80
+ return @arguments if @arguments.length.positive?
81
+
82
+ invalid!("Requires at least one argument but #{@arguments.length} given: #{@arguments}")
83
+ end
84
+
85
+ def two_dates
86
+ return @arguments if @arguments.length == 2 && a_date(@arguments[0]) && a_date(@arguments[1])
87
+
88
+ invalid!("Requires exactly two dates but given: #{@arguments}")
89
+ end
90
+
91
+ def two_numbers
92
+ return @arguments if @arguments.length == 2 && a_number(@arguments[0]) && a_number(@arguments[1])
93
+
94
+ invalid!("Requires exactly two numbers but given: #{@arguments}")
95
+ end
96
+
97
+ # validate_boolean is a weird one because it can have 0, 1 or 2 @arguments - all of them must be (true | false)
98
+ def validate_boolean
99
+ return @arguments if @arguments.empty?
100
+
101
+ converted_args = @arguments.map(&:strip).map(&:downcase)
102
+ return @arguments if [1, 2].include?(@arguments.length) && converted_args.all? do |arg|
103
+ %w[true false].include?(arg)
104
+ end
105
+
106
+ invalid!("Requires 0, 1 or 2 arguments and they all must be either 'true' or 'false'. Received: #{arguments}")
107
+ end
108
+
109
+ # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
110
+ def validate!
111
+ case condition.to_sym
112
+ when :blank, :date_is_valid, :not_blank, :text_is_email, :text_is_url
113
+ no_args
114
+ when :text_contains, :text_ends_with, :text_eq, :text_not_contains, :text_starts_with
115
+ one_arg
116
+ when :date_after, :date_before, :date_on_or_after, :date_on_or_before
117
+ a_date(one_arg, allow_relative_date: true)
118
+ when :date_eq, :date_not_eq
119
+ a_date(one_arg)
120
+ when :date_between, :date_not_between
121
+ two_dates
122
+ when :one_of_range
123
+ a1_notation(one_arg)
124
+ when :custom_formula, :one_of_list, :text_not_eq
125
+ one_arg_or_more
126
+ when :number_eq, :number_greater, :number_greater_than_eq, :number_less, :number_less_than_eq, :number_not_eq
127
+ a_number(one_arg)
128
+ when :number_between, :number_not_between
129
+ two_numbers
130
+ when :boolean
131
+ validate_boolean
132
+ else
133
+ invalid!('Not a recognized data validation directive')
134
+ end
135
+ end
136
+ # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
137
+ end
138
+ end
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './entities'
4
-
5
3
  module CSVPlusPlus
6
- module Language
4
+ module Entities
7
5
  # Some helpful functions that can be mixed into a class to help building ASTs
8
6
  module ASTBuilder
9
7
  # Let the current class have functions which can build a given entity by calling it's type. For example
@@ -13,11 +11,11 @@ module CSVPlusPlus
13
11
  # @param arguments [] The arguments to create the entity with
14
12
  #
15
13
  # @return [Entity, #super]
16
- def method_missing(method_name, *arguments)
17
- entity_class = ::CSVPlusPlus::Language::TYPES[method_name.to_sym]
14
+ def method_missing(method_name, *args, **kwargs, &)
15
+ entity_class = ::CSVPlusPlus::Entities::TYPES[method_name.to_sym]
18
16
  return super unless entity_class
19
17
 
20
- entity_class.new(*arguments)
18
+ entity_class.new(*args, **kwargs, &)
21
19
  end
22
20
 
23
21
  # Let the current class have functions which can build a given entity by calling it's type. For example
@@ -28,7 +26,7 @@ module CSVPlusPlus
28
26
  #
29
27
  # @return [Boolean, #super]
30
28
  def respond_to_missing?(method_name, *_arguments)
31
- ::CSVPlusPlus::Language::TYPES.include?(method_name.to_sym) || super
29
+ ::CSVPlusPlus::Entities::TYPES.include?(method_name.to_sym) || super
32
30
  end
33
31
 
34
32
  # Turns index-based/X,Y coordinates into a A1 format
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './entity'
4
+
5
+ module CSVPlusPlus
6
+ module Entities
7
+ # A boolean value
8
+ #
9
+ # @attr_reader value [true, false]
10
+ class Boolean < Entity
11
+ attr_reader :value
12
+
13
+ # @param value [String, Boolean]
14
+ def initialize(value)
15
+ super(:boolean)
16
+ # TODO: probably can do a lot better in general on type validation
17
+ @value = value.is_a?(::String) ? (value.downcase == 'true') : value
18
+ end
19
+
20
+ # @return [String]
21
+ def to_s
22
+ @value.to_s.upcase
23
+ end
24
+
25
+ # @return [boolean]
26
+ def ==(other)
27
+ super && value == other.value
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './ast_builder'
4
-
5
3
  module CSVPlusPlus
6
- module Language
4
+ module Entities
7
5
  # Provides ASTs for builtin functions and variables
8
6
  module Builtins
9
- extend ::CSVPlusPlus::Language::ASTBuilder
7
+ extend ::CSVPlusPlus::Entities::ASTBuilder
10
8
 
11
9
  VARIABLES = {
12
10
  # The number (integer) of the current cell. Starts at 1
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ast_builder'
4
+ require_relative './entity'
5
+
6
+ module CSVPlusPlus
7
+ module Entities
8
+ # A reference to a cell
9
+ #
10
+ # @attr_reader cell_reference [String] The cell reference in A1 format
11
+ class CellReference < Entity
12
+ attr_reader :cell_reference
13
+
14
+ A1_NOTATION_REGEXP = /(['\w]+!)?\w+:\w+/
15
+ public_constant :A1_NOTATION_REGEXP
16
+
17
+ # Create a +CellReference+ to the given indexes
18
+ #
19
+ # @param cell_index [Integer] The current cell index
20
+ # @param row_index [Integer] The current row index
21
+ #
22
+ # @return [CellReference]
23
+ def self.from_index(cell_index:, row_index:)
24
+ return unless row_index || cell_index
25
+
26
+ # I can't just extend this class due to circular references :(
27
+ ::Class.new.extend(::CSVPlusPlus::Entities::ASTBuilder).ref(cell_index:, row_index:)
28
+ end
29
+
30
+ # Does the given +cell_reference_string+ conform to a valid cell reference?
31
+ #
32
+ # {https://developers.google.com/sheets/api/guides/concepts}
33
+ #
34
+ # @param cell_reference_string [::String] The string to check if it is a valid cell reference (we assume it's in
35
+ # A1 notation but maybe can support R1C1)
36
+ #
37
+ # @return [boolean]
38
+ def self.valid_cell_reference?(cell_reference_string)
39
+ !(cell_reference_string =~ ::CSVPlusPlus::Entities::CellReference::A1_NOTATION_REGEXP).nil?
40
+ end
41
+
42
+ # @param cell_reference [String] The cell reference in A1 format
43
+ def initialize(cell_reference)
44
+ super(:cell_reference)
45
+
46
+ @cell_reference = cell_reference
47
+ end
48
+
49
+ # @return [::String]
50
+ def to_s
51
+ @cell_reference
52
+ end
53
+
54
+ # @return [Boolean]
55
+ def ==(other)
56
+ super && @cell_reference == other.cell_reference
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # A date value
6
+ #
7
+ # @attr_reader value [Date] The parsed date
8
+ class Date < Entity
9
+ attr_reader :value
10
+
11
+ # TODO: support time?
12
+ DATE_STRING_REGEXP = %r{^\d{1,2}[/-]\d{1,2}[/-]\d{1,4}?$}
13
+ public_constant :DATE_STRING_REGEXP
14
+
15
+ # Is the given string a valid date?
16
+ #
17
+ # @param date_string [::String]
18
+ def self.valid_date?(date_string)
19
+ !(date_string.strip =~ ::CSVPlusPlus::Entities::Date::DATE_STRING_REGEXP).nil?
20
+ end
21
+
22
+ # @param value [String] The user-inputted date value
23
+ def initialize(value)
24
+ super(:date)
25
+
26
+ @value = ::Date.parse(value)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../entities'
4
+
5
+ module CSVPlusPlus
6
+ 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+
12
+ 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
26
+
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
38
+
39
+ # Respond to predicates by type (entity.boolean?, entity.string?, etc)
40
+ #
41
+ # @param method_name [Symbol] The +method_name+ to respond to
42
+ #
43
+ # @return [boolean]
44
+ def respond_to_missing?(method_name, *_arguments)
45
+ (method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
46
+ end
47
+
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(', ')
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './entity'
4
+
5
+ module CSVPlusPlus
6
+ module Entities
7
+ # A function definition
8
+ #
9
+ # @attr_reader body [Entity] The body of the function. +body+ can contain variable references
10
+ # from +@arguments+
11
+ class Function < EntityWithArguments
12
+ attr_reader :body
13
+
14
+ # @param id [Symbool, String] the name of the function - what it will be callable by
15
+ # @param arguments [Array<Symbol>]
16
+ # @param body [Entity]
17
+ def initialize(id, arguments, body)
18
+ super(:function, id:, arguments: arguments.map(&:to_sym))
19
+ @body = body
20
+ end
21
+
22
+ # @return [String]
23
+ def to_s
24
+ "def #{@id.to_s.upcase}(#{arguments_to_s}) #{@body}"
25
+ end
26
+
27
+ # @return [boolean]
28
+ def ==(other)
29
+ super && @body == other.body
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # A function call
6
+ #
7
+ # @attr_reader infix [boolean] Whether or not this function call is infix (X * Y, A + B, etc)
8
+ class FunctionCall < EntityWithArguments
9
+ attr_reader :infix
10
+
11
+ # @param id [String] The name of the function
12
+ # @param arguments [Array<Entity>] The arguments to the function
13
+ # @param infix [boolean] Whether the function is infix
14
+ def initialize(id, arguments, infix: false)
15
+ super(:function_call, id:, arguments:)
16
+
17
+ @infix = infix
18
+ end
19
+
20
+ # @return [String]
21
+ def to_s
22
+ if @infix
23
+ "(#{arguments.join(" #{@id} ")})"
24
+ else
25
+ "#{@id.to_s.upcase}(#{arguments_to_s})"
26
+ end
27
+ end
28
+
29
+ # @return [boolean]
30
+ def ==(other)
31
+ super && @id == other.id
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # A number value
6
+ #
7
+ # @attr_reader value [Numeric] The parsed number value
8
+ class Number < Entity
9
+ attr_reader :value
10
+
11
+ # @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
12
+ def initialize(value)
13
+ super(:number)
14
+
15
+ @value =
16
+ if value.instance_of?(::String)
17
+ value.include?('.') ? Float(value) : Integer(value, 10)
18
+ else
19
+ value
20
+ end
21
+ end
22
+
23
+ # @return [String]
24
+ def to_s
25
+ @value.to_s
26
+ end
27
+
28
+ # @return [boolean]
29
+ def ==(other)
30
+ super && value == other.value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # A runtime value. These are values which can be materialized at any point via the +resolve_fn+
6
+ # which takes an ExecutionContext as a param
7
+ #
8
+ # @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
9
+ class RuntimeValue < Entity
10
+ attr_reader :arguments, :resolve_fn
11
+
12
+ # @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
13
+ def initialize(resolve_fn, arguments: nil)
14
+ super(:runtime_value)
15
+
16
+ @arguments = arguments
17
+ @resolve_fn = resolve_fn
18
+ end
19
+
20
+ # @return [String]
21
+ def to_s
22
+ '(runtime_value)'
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # A string value
6
+ #
7
+ # @attr_reader value [String]
8
+ class String < Entity
9
+ attr_reader :value
10
+
11
+ # @param value [String] The string that has been parsed out of the template
12
+ def initialize(value)
13
+ super(:string)
14
+
15
+ @value = value.gsub(/^"|"$/, '')
16
+ end
17
+
18
+ # @return [String]
19
+ def to_s
20
+ "\"#{@value}\""
21
+ end
22
+
23
+ # @return [boolean]
24
+ def ==(other)
25
+ super && value == other.value
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Entities
5
+ # TODO: get rid of this I think - everything will just be References
6
+ #
7
+ # A reference to a variable
8
+ class Variable < Entity
9
+ # @param id [Symbol] The identifier of the variable
10
+ def initialize(id)
11
+ super(:variable, id:)
12
+ end
13
+
14
+ # @return [String]
15
+ def to_s
16
+ "$$#{@id}"
17
+ end
18
+
19
+ # @return [boolean]
20
+ def ==(other)
21
+ super && id == other.id
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entities/boolean'
4
+ require_relative 'entities/cell_reference'
5
+ require_relative 'entities/date'
6
+ require_relative 'entities/entity'
7
+ require_relative 'entities/function'
8
+ require_relative 'entities/function_call'
9
+ require_relative 'entities/number'
10
+ require_relative 'entities/runtime_value'
11
+ require_relative 'entities/string'
12
+ require_relative 'entities/variable'
13
+
14
+ module CSVPlusPlus
15
+ module Entities
16
+ TYPES = {
17
+ boolean: ::CSVPlusPlus::Entities::Boolean,
18
+ cell_reference: ::CSVPlusPlus::Entities::CellReference,
19
+ date: ::CSVPlusPlus::Entities::Date,
20
+ function: ::CSVPlusPlus::Entities::Function,
21
+ function_call: ::CSVPlusPlus::Entities::FunctionCall,
22
+ number: ::CSVPlusPlus::Entities::Number,
23
+ runtime_value: ::CSVPlusPlus::Entities::RuntimeValue,
24
+ string: ::CSVPlusPlus::Entities::String,
25
+ variable: ::CSVPlusPlus::Entities::Variable
26
+ }.freeze
27
+
28
+ public_constant :TYPES
29
+ end
30
+ end
31
+
32
+ require_relative 'entities/ast_builder'
33
+ require_relative 'entities/builtins'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSVPlusPlus
4
+ module Error
5
+ # An error thrown by our code (generally to be handled at the top level bin/ command)
6
+ class Error < StandardError
7
+ # TODO: perhaps give this a better name? something more descriptive than just Error
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './syntax_error'
4
+
5
+ module CSVPlusPlus
6
+ module Error
7
+ # An error that can be thrown when there is an error parsing a modifier
8
+ #
9
+ # @attr_reader message [::String] A helpful error message
10
+ # @attr_reader bad_input [String] The offending input that caused the error to be thrown
11
+ class FormulaSyntaxError < ::CSVPlusPlus::Error::SyntaxError
12
+ attr_reader :message, :bad_input
13
+
14
+ # You must supply either a +choices+ or +message+
15
+ #
16
+ # @param message [String] A relevant message to show
17
+ # @param bad_input [String] The offending input that caused the error to be thrown
18
+ # @param runtime [Runtime] The current runtime
19
+ # @param wrapped_error [StandardError] The underlying error that caused the syntax error. For example a
20
+ # Racc::ParseError that was thrown
21
+ def initialize(message, bad_input, runtime, wrapped_error: nil)
22
+ @bad_input = bad_input
23
+ @message = message
24
+
25
+ super(runtime, wrapped_error:)
26
+ end
27
+
28
+ # Create a relevant error message given +@bad_input+ and +@message+.
29
+ #
30
+ # @return [::String]
31
+ def error_message
32
+ "#{@message}: \"#{@bad_input}\""
33
+ end
34
+ end
35
+ end
36
+ end