csv_plus_plus 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +9 -3
  4. data/bin/csv++ +1 -37
  5. data/bin/csvpp +6 -0
  6. data/lib/csv_plus_plus/cell.rb +24 -8
  7. data/lib/csv_plus_plus/cli.rb +97 -0
  8. data/lib/csv_plus_plus/cli_flag.rb +10 -2
  9. data/lib/csv_plus_plus/code_section.rb +22 -3
  10. data/lib/csv_plus_plus/color.rb +19 -5
  11. data/lib/csv_plus_plus/google_api_client.rb +20 -0
  12. data/lib/csv_plus_plus/google_options.rb +6 -2
  13. data/lib/csv_plus_plus/graph.rb +0 -1
  14. data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
  15. data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
  16. data/lib/csv_plus_plus/language/builtins.rb +46 -0
  17. data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
  18. data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
  19. data/lib/csv_plus_plus/language/compiler.rb +74 -86
  20. data/lib/csv_plus_plus/language/entities/boolean.rb +5 -4
  21. data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
  22. data/lib/csv_plus_plus/language/entities/entity.rb +22 -6
  23. data/lib/csv_plus_plus/language/entities/function.rb +6 -4
  24. data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
  25. data/lib/csv_plus_plus/language/entities/number.rb +6 -4
  26. data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
  27. data/lib/csv_plus_plus/language/entities/string.rb +6 -4
  28. data/lib/csv_plus_plus/language/references.rb +22 -5
  29. data/lib/csv_plus_plus/language/runtime.rb +80 -22
  30. data/lib/csv_plus_plus/language/scope.rb +29 -38
  31. data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
  32. data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
  33. data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
  34. data/lib/csv_plus_plus/modifier.rb +71 -8
  35. data/lib/csv_plus_plus/modifier.tab.rb +2 -2
  36. data/lib/csv_plus_plus/options.rb +17 -3
  37. data/lib/csv_plus_plus/row.rb +15 -4
  38. data/lib/csv_plus_plus/template.rb +10 -6
  39. data/lib/csv_plus_plus/version.rb +1 -1
  40. data/lib/csv_plus_plus/writer/base_writer.rb +0 -1
  41. data/lib/csv_plus_plus/writer/csv.rb +4 -1
  42. data/lib/csv_plus_plus/writer/excel.rb +5 -9
  43. data/lib/csv_plus_plus/writer/file_backer_upper.rb +58 -0
  44. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
  45. data/lib/csv_plus_plus/writer/google_sheets.rb +22 -41
  46. data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
  47. data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
  48. data/lib/csv_plus_plus.rb +26 -4
  49. metadata +29 -7
@@ -29,11 +29,10 @@ module_eval(<<'...end cell_value.y/module_eval...', 'cell_value.y', 48)
29
29
  @ast
30
30
  end
31
31
 
32
- def tokenizer(input)
32
+ def tokenizer
33
33
  ::CSVPlusPlus::Lexer::Tokenizer.new(
34
34
  catchall: /[\(\)\/\*\+\-,=&]/,
35
35
  ignore: /\s+/,
36
- input:,
37
36
  tokens: [
38
37
  [/true/i, :TRUE],
39
38
  [/false/i, :FALSE],
@@ -33,11 +33,10 @@ module_eval(<<'...end code_section.y/module_eval...', 'code_section.y', 67)
33
33
  'code section'
34
34
  end
35
35
 
36
- def tokenizer(input)
36
+ def tokenizer
37
37
  ::CSVPlusPlus::Lexer::Tokenizer.new(
38
38
  catchall: /[\(\)\{\}\/\*\+\-,=&]/,
39
39
  ignore: /\s+|\#[^\n]+\n/,
40
- input:,
41
40
  stop_fn: lambda do |scanner|
42
41
  return false unless scanner.scan(/#{::CSVPlusPlus::Lexer::END_OF_CODE_SECTION}/)
43
42
 
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'benchmark'
4
3
  require 'csv'
4
+ # TODO: move some of these out to csv_plus_plus.rb
5
5
  require_relative '../cell'
6
6
  require_relative '../modifier'
7
7
  require_relative '../modifier.tab'
8
8
  require_relative '../row'
9
9
  require_relative '../template'
10
+ require_relative 'benchmarked_compiler'
10
11
  require_relative 'code_section.tab'
11
12
  require_relative 'entities'
12
13
  require_relative 'runtime'
@@ -14,59 +15,73 @@ require_relative 'scope'
14
15
 
15
16
  module CSVPlusPlus
16
17
  module Language
17
- # Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+).
18
- # Variable resolution is delegated to the +Scope+
19
- # rubocop:disable Metrics/ClassLength
18
+ # Encapsulates the parsing and building of objects (+Template+ -> +Row+ -> +Cell+). Variable resolution is delegated
19
+ # to the +Scope+
20
+ #
21
+ # @attr_reader options [Options] The +Options+ to compile with
22
+ # @attr_reader runtime [Runtime] The runtime execution
23
+ # @attr_reader scope [Scope] +Scope+ for variable resolution
20
24
  class Compiler
21
25
  attr_reader :timings, :benchmark, :options, :runtime, :scope
22
26
 
23
27
  # Create a compiler and make sure it gets cleaned up
24
- def self.with_compiler(input:, filename:, options:, &block)
25
- runtime = ::CSVPlusPlus::Language::Runtime.new(filename:, input:)
26
-
28
+ #
29
+ # @param runtime [Runtime] The initial +Runtime+ for the compiler
30
+ # @param options [Options]
31
+ def self.with_compiler(runtime:, options:, &block)
32
+ compiler = new(options:, runtime:)
27
33
  if options.verbose
28
- compiler_with_timings(runtime:, options:) do |c|
34
+ ::CSVPlusPlus::Language::BenchmarkedCompiler.with_benchmarks(compiler) do |c|
29
35
  block.call(c)
30
36
  end
31
37
  else
32
- yield(new(runtime:, options:))
38
+ yield(compiler)
33
39
  end
34
40
  ensure
35
41
  runtime.cleanup!
36
42
  end
37
43
 
38
- # Create a compiler that can time each of it's stages
39
- def self.compiler_with_timings(options:, runtime:, &block)
40
- ::Benchmark.benchmark(::Benchmark::CAPTION, 25, ::Benchmark::FORMAT, '> Total') do |x|
41
- compiler = new(options:, runtime:, benchmark: x)
42
- block.call(compiler)
43
- [compiler.timings.reduce(:+)]
44
- end
45
- end
46
-
47
- # initialize
48
- def initialize(runtime:, options:, scope: nil, benchmark: nil)
44
+ # @param runtime [Runtime]
45
+ # @param options [Options]
46
+ # @param scope [Scope, nil]
47
+ def initialize(runtime:, options:, scope: nil)
49
48
  @options = options
50
49
  @runtime = runtime
51
50
  @scope = scope || ::CSVPlusPlus::Language::Scope.new(runtime:)
52
- @benchmark = benchmark
53
- @timings = [] if benchmark
54
51
  end
55
52
 
56
- # Parse an entire template and return a +::CSVPlusPlus::Template+ instance
57
- def parse_template
53
+ # Write the compiled results
54
+ def outputting!
55
+ @runtime.start_at_csv!
56
+ yield
57
+ end
58
+
59
+ # Compile a template and return a +::CSVPlusPlus::Template+ instance ready to be written with a +Writer+
60
+ #
61
+ # @return [Template]
62
+ def compile_template
58
63
  parse_code_section!
59
64
  rows = parse_csv_section!
60
65
 
61
- ::CSVPlusPlus::Template.new(rows:, scope: @scope).tap do |t|
66
+ ::CSVPlusPlus::Template.new(rows:).tap do |t|
62
67
  t.validate_infinite_expands(@runtime)
63
68
  expanding { t.expand_rows! }
64
69
  resolve_all_cells!(t)
65
70
  end
66
71
  end
67
72
 
68
- # parses the input file and returns a +CodeSection+
73
+ # @return [String]
74
+ def to_s
75
+ "Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
76
+ end
77
+
78
+ protected
79
+
80
+ # Parses the input file and returns a +CodeSection+
81
+ #
82
+ # @return [CodeSection]
69
83
  def parse_code_section!
84
+ @runtime.start!
70
85
  parsing_code_section do |input|
71
86
  code_section, csv_section = ::CSVPlusPlus::Language::CodeSectionParser.new.parse(input, self)
72
87
  # TODO: infer a type
@@ -82,20 +97,48 @@ module CSVPlusPlus
82
97
  @scope.code_section
83
98
  end
84
99
 
85
- # workflow when parsing csv
100
+ # Parse the CSV section and return an array of +Row+s
101
+ #
102
+ # @return [Array<Row>]
86
103
  def parse_csv_section!
87
- workflow(stage: 'Parsing CSV section') do
88
- @runtime.map_rows(::CSV.new(runtime.input)) do |csv_row|
89
- parse_row(csv_row)
90
- end
104
+ @runtime.start_at_csv!
105
+ @runtime.map_rows(::CSV.new(runtime.input)) do |csv_row|
106
+ parse_row(csv_row)
91
107
  end
92
108
  ensure
93
109
  # we're done with the file and everything is in memory
94
110
  @runtime.cleanup!
95
111
  end
96
112
 
113
+ # Iterates through each cell of each row and resolves it's variable and function references.
114
+ #
115
+ # @param template [Template]
116
+ # @return [Array<Entity>]
117
+ def resolve_all_cells!(template)
118
+ @runtime.start_at_csv!
119
+ @runtime.map_rows(template.rows, cells_too: true) do |cell|
120
+ cell.ast = @scope.resolve_cell_value if cell.ast
121
+ end
122
+ end
123
+
124
+ # Expanding rows
125
+ def expanding
126
+ @runtime.start_at_csv!
127
+ yield
128
+ end
129
+
130
+ private
131
+
132
+ def parsing_code_section
133
+ csv_section = yield(@runtime.input.read)
134
+ @runtime.rewrite_input!(csv_section)
135
+ end
136
+
97
137
  # Using the current +@runtime+ and the given +csv_row+ parse it into a +Row+ of +Cell+s
98
138
  # +csv_row+ should have already been run through a CSV parser and is an array of strings
139
+ #
140
+ # @param csv_row [Array<Array<String>>]
141
+ # @return [Row]
99
142
  def parse_row(csv_row)
100
143
  row_modifier = ::CSVPlusPlus::Modifier.new(row_level: true)
101
144
 
@@ -109,61 +152,6 @@ module CSVPlusPlus
109
152
 
110
153
  ::CSVPlusPlus::Row.new(@runtime.row_index, cells, row_modifier)
111
154
  end
112
-
113
- # workflow when resolving the values of all cells
114
- def resolve_all_cells!(template)
115
- workflow(stage: 'Resolving each cell') do
116
- @runtime.map_rows(template.rows, cells_too: true) do |cell|
117
- cell.ast = @scope.resolve_cell_value if cell.ast
118
- end
119
- end
120
- end
121
-
122
- # workflow when writing results
123
- def outputting!(&block)
124
- workflow(stage: 'Writing the spreadsheet') do
125
- block.call
126
- end
127
- end
128
-
129
- # to_s
130
- def to_s
131
- "Compiler(options: #{@options}, runtime: #{@runtime}, scope: #{@scope})"
132
- end
133
-
134
- private
135
-
136
- # workflow when parsing the code section
137
- def parsing_code_section(&block)
138
- workflow(
139
- stage: 'Parsing code section',
140
- processing_code_section: true
141
- ) do
142
- csv_section = block.call(@runtime.input.read)
143
- @runtime.rewrite_input!(csv_section)
144
- end
145
- end
146
-
147
- # workflow when expanding rows
148
- def expanding(&block)
149
- workflow(stage: 'Expanding rows') do
150
- block.call
151
- end
152
- end
153
-
154
- def workflow(stage:, processing_code_section: false, &block)
155
- @runtime.init!(processing_code_section ? 1 : (@runtime.length_of_code_section || 1))
156
-
157
- ret = nil
158
- if @benchmark
159
- @timings << @benchmark.report(stage) { ret = block.call }
160
- else
161
- ret = block.call
162
- end
163
-
164
- ret
165
- end
166
155
  end
167
- # rubocop:enable Metrics/ClassLength
168
156
  end
169
157
  end
@@ -5,24 +5,25 @@ require_relative './entity'
5
5
  module CSVPlusPlus
6
6
  module Language
7
7
  module Entities
8
- ##
9
8
  # A boolean value
9
+ #
10
+ # @attr_reader value [true, false]
10
11
  class Boolean < Entity
11
12
  attr_reader :value
12
13
 
13
- # initialize
14
+ # @param value [String, Boolean]
14
15
  def initialize(value)
15
16
  super(:boolean)
16
17
  # TODO: probably can do a lot better in general on type validation
17
18
  @value = value.is_a?(::String) ? (value.downcase == 'true') : value
18
19
  end
19
20
 
20
- # to_s
21
+ # @return [String]
21
22
  def to_s
22
23
  @value.to_s.upcase
23
24
  end
24
25
 
25
- # ==
26
+ # @return [boolean]
26
27
  def ==(other)
27
28
  super && value == other.value
28
29
  end
@@ -5,21 +5,28 @@ require_relative './entity'
5
5
  module CSVPlusPlus
6
6
  module Language
7
7
  module Entities
8
- ##
9
8
  # A reference to a cell
9
+ #
10
+ # @attr_reader cell_reference [String] The cell reference in A1 format
10
11
  class CellReference < Entity
11
12
  attr_reader :cell_reference
12
13
 
13
- # initialize
14
+ # @param cell_reference [String] The cell reference in A1 format
14
15
  def initialize(cell_reference)
15
16
  super(:cell_reference)
17
+
16
18
  @cell_reference = cell_reference
17
19
  end
18
20
 
19
- # to_s
21
+ # @return [String]
20
22
  def to_s
21
23
  @cell_reference
22
24
  end
25
+
26
+ # @return [Boolean]
27
+ def ==(other)
28
+ super && @cell_reference == other.cell_reference
29
+ end
23
30
  end
24
31
  end
25
32
  end
@@ -6,21 +6,28 @@ module CSVPlusPlus
6
6
  module Language
7
7
  module Entities
8
8
  # A basic building block of the abstract syntax tree (AST)
9
+ #
10
+ # @attr_reader id [Symbol] The identifier of the entity. For functions this is the function name,
11
+ # for variables it's the variable name
12
+ # @attr_reader type [Symbol] The type of the entity. Valid values are defined in +::CSVPlusPlus::Language::Types+
9
13
  class Entity
10
14
  attr_reader :id, :type
11
15
 
12
- # initialize
16
+ # @param type [::String, Symbol]
17
+ # @param id [::String, nil]
13
18
  def initialize(type, id: nil)
14
19
  @type = type.to_sym
15
20
  @id = id.downcase.to_sym if id
16
21
  end
17
22
 
18
- # ==
23
+ # @return [boolean]
19
24
  def ==(other)
20
25
  self.class == other.class && @type == other.type && @id == other.id
21
26
  end
22
27
 
23
28
  # Respond to predicates that correspond to types like #boolean?, #string?, etc
29
+ #
30
+ # @param method_name [Symbol] The +method_name+ to respond to
24
31
  def method_missing(method_name, *_arguments)
25
32
  if method_name =~ /^(\w+)\?$/
26
33
  t = ::Regexp.last_match(1)
@@ -30,7 +37,11 @@ module CSVPlusPlus
30
37
  end
31
38
  end
32
39
 
33
- # support predicates by type
40
+ # Respond to predicates by type (entity.boolean?, entity.string?, etc)
41
+ #
42
+ # @param method_name [Symbol] The +method_name+ to respond to
43
+ #
44
+ # @return [boolean]
34
45
  def respond_to_missing?(method_name, *_arguments)
35
46
  (method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
36
47
  end
@@ -42,17 +53,22 @@ module CSVPlusPlus
42
53
  end
43
54
  end
44
55
 
45
- # An entity that can take arguments
56
+ # An entity that can take other entities as arguments. Current use cases for this
57
+ # are function calls and function definitions
58
+ #
59
+ # @attr_reader arguments [Array<Entity>] The arguments supplied to this entity.
46
60
  class EntityWithArguments < Entity
47
61
  attr_reader :arguments
48
62
 
49
- # initialize
63
+ # @param type [::String, Symbol]
64
+ # @param id [::String]
65
+ # @param arguments [Array<Entity>]
50
66
  def initialize(type, id: nil, arguments: [])
51
67
  super(type, id:)
52
68
  @arguments = arguments
53
69
  end
54
70
 
55
- # ==
71
+ # @return [boolean]
56
72
  def ==(other)
57
73
  super && @arguments == other.arguments
58
74
  end
@@ -6,24 +6,26 @@ module CSVPlusPlus
6
6
  module Language
7
7
  module Entities
8
8
  # A function definition
9
+ #
10
+ # @attr_reader body [Entity] The body of the function. +body+ can contain variable references
11
+ # from +@arguments+
9
12
  class Function < EntityWithArguments
10
13
  attr_reader :body
11
14
 
12
- # Create a function
13
15
  # @param id [Symbool, String] the name of the function - what it will be callable by
14
- # @param arguments [Array(Symbol)]
16
+ # @param arguments [Array<Symbol>]
15
17
  # @param body [Entity]
16
18
  def initialize(id, arguments, body)
17
19
  super(:function, id:, arguments: arguments.map(&:to_sym))
18
20
  @body = body
19
21
  end
20
22
 
21
- # to_s
23
+ # @return [String]
22
24
  def to_s
23
25
  "def #{@id.to_s.upcase}(#{arguments_to_s}) #{@body}"
24
26
  end
25
27
 
26
- # ==
28
+ # @return [boolean]
27
29
  def ==(other)
28
30
  super && @body == other.body
29
31
  end
@@ -5,17 +5,18 @@ module CSVPlusPlus
5
5
  module Entities
6
6
  # A function call
7
7
  class FunctionCall < EntityWithArguments
8
- # initialize
8
+ # @param id [String] The name of the function
9
+ # @param arguments [Array<Entity>] The arguments to the function
9
10
  def initialize(id, arguments)
10
11
  super(:function_call, id:, arguments:)
11
12
  end
12
13
 
13
- # to_s
14
+ # @return [String]
14
15
  def to_s
15
16
  "#{@id.to_s.upcase}(#{arguments_to_s})"
16
17
  end
17
18
 
18
- # ==
19
+ # @return [boolean]
19
20
  def ==(other)
20
21
  super && @id == other.id
21
22
  end
@@ -3,14 +3,16 @@
3
3
  module CSVPlusPlus
4
4
  module Language
5
5
  module Entities
6
- ##
7
6
  # A number value
7
+ #
8
+ # @attr_reader value [Numeric] The parsed number value
8
9
  class Number < Entity
9
10
  attr_reader :value
10
11
 
11
- # initialize
12
+ # @param value [String, Numeric] Either a +String+ that looks like a number, or an already parsed Numeric
12
13
  def initialize(value)
13
14
  super(:number)
15
+
14
16
  @value =
15
17
  if value.instance_of?(::String)
16
18
  value.include?('.') ? Float(value) : Integer(value, 10)
@@ -19,12 +21,12 @@ module CSVPlusPlus
19
21
  end
20
22
  end
21
23
 
22
- # to_s
24
+ # @return [String]
23
25
  def to_s
24
26
  @value.to_s
25
27
  end
26
28
 
27
- # ==
29
+ # @return [boolean]
28
30
  def ==(other)
29
31
  super && value == other.value
30
32
  end
@@ -3,21 +3,22 @@
3
3
  module CSVPlusPlus
4
4
  module Language
5
5
  module Entities
6
- ##
7
- # A runtime value
8
- #
9
- # These are values which can be materialized at any point via the +resolve_fn+
6
+ # A runtime value. These are values which can be materialized at any point via the +resolve_fn+
10
7
  # which takes an ExecutionContext as a param
8
+ #
9
+ # @attr_reader resolve_fn [lambda] A lambda that is called when the runtime value is resolved
11
10
  class RuntimeValue < Entity
12
- attr_reader :resolve_fn
11
+ attr_reader :arguments, :resolve_fn
13
12
 
14
- # initialize
15
- def initialize(resolve_fn)
13
+ # @param resolve_fn [lambda] A lambda that is called when the runtime value is resolved
14
+ def initialize(resolve_fn, arguments: nil)
16
15
  super(:runtime_value)
16
+
17
+ @arguments = arguments
17
18
  @resolve_fn = resolve_fn
18
19
  end
19
20
 
20
- # to_s
21
+ # @return [String]
21
22
  def to_s
22
23
  '(runtime_value)'
23
24
  end
@@ -3,23 +3,25 @@
3
3
  module CSVPlusPlus
4
4
  module Language
5
5
  module Entities
6
- ##
7
6
  # A string value
7
+ #
8
+ # @attr_reader value [String]
8
9
  class String < Entity
9
10
  attr_reader :value
10
11
 
11
- # initialize
12
+ # @param value [String] The string that has been parsed out of the template
12
13
  def initialize(value)
13
14
  super(:string)
15
+
14
16
  @value = value.gsub(/^"|"$/, '')
15
17
  end
16
18
 
17
- # to_s
19
+ # @return [String]
18
20
  def to_s
19
21
  "\"#{@value}\""
20
22
  end
21
23
 
22
- # ==
24
+ # @return [boolean]
23
25
  def ==(other)
24
26
  super && value == other.value
25
27
  end
@@ -6,10 +6,19 @@ require_relative './scope'
6
6
  module CSVPlusPlus
7
7
  module Language
8
8
  # References in an AST that need to be resolved
9
+ #
10
+ # @attr functions [Array<Entities::Function>] Functions references
11
+ # @attr variables [Array<Entities::Variable>] Variable references
9
12
  class References
10
13
  attr_accessor :functions, :variables
11
14
 
12
- # Extract references from an AST. And return them in a new +References+ object
15
+ # Extract references from an AST and return them in a new +References+ object
16
+ #
17
+ # @param ast [Entity] An +Entity+ to do a depth first search on for references. Entities can be
18
+ # infinitely deep because they can contain other function calls as params to a function call
19
+ # @param code_section [CodeSection] The +CodeSection+ containing all currently defined functions
20
+ #
21
+ # @return [References]
13
22
  def self.extract(ast, code_section)
14
23
  new.tap do |refs|
15
24
  ::CSVPlusPlus::Graph.depth_first_search(ast) do |node|
@@ -22,8 +31,14 @@ module CSVPlusPlus
22
31
  end
23
32
 
24
33
  # Is the node a resolvable reference?
34
+ #
35
+ # @param node [Entity] The node to check if it's resolvable
36
+ #
37
+ # @return [boolean]
38
+ # TODO: move this into the Entity subclasses
25
39
  def self.function_reference?(node, code_section)
26
- node.function_call? && (code_section.defined_function?(node.id) || ::BUILTIN_FUNCTIONS.key?(node.id))
40
+ node.function_call? && (code_section.defined_function?(node.id) \
41
+ || ::CSVPlusPlus::Language::Builtins::FUNCTIONS.key?(node.id))
27
42
  end
28
43
 
29
44
  private_class_method :function_reference?
@@ -34,17 +49,19 @@ module CSVPlusPlus
34
49
  @variables = []
35
50
  end
36
51
 
37
- # are there any references to be resolved?
52
+ # Are there any references to be resolved?
53
+ #
54
+ # @return [boolean]
38
55
  def empty?
39
56
  @functions.empty? && @variables.empty?
40
57
  end
41
58
 
42
- # to_s
59
+ # @return [String]
43
60
  def to_s
44
61
  "References(functions: #{@functions}, variables: #{@variables})"
45
62
  end
46
63
 
47
- # ==
64
+ # @return [boolean]
48
65
  def ==(other)
49
66
  @functions == other.functions && @variables == other.variables
50
67
  end