csv_plus_plus 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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