Class: CSVPlusPlus::Runtime::Scope

Inherits:
Object
  • Object
show all
Extended by:
T::Sig
Defined in:
lib/csv_plus_plus/runtime/scope.rb

Overview

Responsible for storing and resolving variables and function references rubocop:disable Metrics/ClassLength

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(functions: {}, variables: {}) ⇒ Scope

Returns a new instance of Scope.

Parameters:

  • functions (Hash<Symbol, Function>) (defaults to: {})

    Pre-defined functions

  • variables (Hash<Symbol, Entity>) (defaults to: {})

    Pre-defined variables



25
26
27
28
# File 'lib/csv_plus_plus/runtime/scope.rb', line 25

def initialize(functions: {}, variables: {})
  @functions = functions
  @variables = variables
end

Instance Attribute Details

#functionsObject (readonly)

Returns the value of attribute functions.



12
13
14
# File 'lib/csv_plus_plus/runtime/scope.rb', line 12

def functions
  @functions
end

#variablesObject (readonly)

Returns the value of attribute variables.



15
16
17
# File 'lib/csv_plus_plus/runtime/scope.rb', line 15

def variables
  @variables
end

Instance Method Details

#call_function(function, function_call) ⇒ Entities::Entity

Since functions are just built up from existing functions, a “call” is effectively replacing the variable references in the @body with the ones being passed as arguments

Parameters:

Returns:



210
211
212
213
214
215
216
217
# File 'lib/csv_plus_plus/runtime/scope.rb', line 210

def call_function(function, function_call)
  i = 0
  function.arguments.reduce(function.body.dup) do |ast, argument|
    variable_replace(ast, argument, ::T.must(function_call.arguments[i])).tap do
      i += 1
    end
  end
end

#call_function_or_builtin(position, function_or_builtin, function_call) ⇒ Entities::Entity

Parameters:

Returns:



189
190
191
192
193
194
195
# File 'lib/csv_plus_plus/runtime/scope.rb', line 189

def call_function_or_builtin(position, function_or_builtin, function_call)
  if function_or_builtin.is_a?(::CSVPlusPlus::Entities::RuntimeValue)
    function_or_builtin.call(position, function_call.arguments)
  else
    call_function(function_or_builtin, function_call)
  end
end

#def_function(id, function) ⇒ Entities::Function

Define a (or re-define an existing) function

Parameters:

  • id (Symbol)

    The identifier for the function

  • function (Entities::Function)

    The defined function

Returns:



58
59
60
# File 'lib/csv_plus_plus/runtime/scope.rb', line 58

def def_function(id, function)
  @functions[id.to_sym] = function
end

#def_variable(id, entity) ⇒ Entity

Define a (or re-define an existing) variable

Parameters:

  • id (String, Symbol)

    The identifier for the variable

  • entity (Entity)

    The value (entity) the variable holds

Returns:

  • (Entity)

    The value of the variable (entity)



37
38
39
# File 'lib/csv_plus_plus/runtime/scope.rb', line 37

def def_variable(id, entity)
  @variables[id] = entity
end

#def_variables(vars) ⇒ Object

Define (or re-define existing) variables

Parameters:

  • vars (Hash<Symbol, Variable>)

    Variables to define



45
46
47
# File 'lib/csv_plus_plus/runtime/scope.rb', line 45

def def_variables(vars)
  vars.each { |id, entity| def_variable(id, entity) }
end

#function_replace(position, node, fn_id, replacement) ⇒ Object

Make a copy of the AST represented by node and replace fn_id with replacement throughout rubocop:disable Metrics/MethodLength



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/csv_plus_plus/runtime/scope.rb', line 145

def function_replace(position, node, fn_id, replacement)
  if node.is_a?(::CSVPlusPlus::Entities::FunctionCall) && node.id == fn_id
    call_function_or_builtin(position, replacement, node)
  elsif node.is_a?(::CSVPlusPlus::Entities::FunctionCall)
    # not our function, but continue our depth first search on it
    ::CSVPlusPlus::Entities::FunctionCall.new(
      node.id,
      node.arguments.map { |n| function_replace(position, n, fn_id, replacement) },
      infix: node.infix
    )
  else
    node
  end
end

#function_summaryString

Create a summary of all currently defined functions

Returns:

  • (String)


271
272
273
274
275
276
# File 'lib/csv_plus_plus/runtime/scope.rb', line 271

def function_summary
  return '(no functions defined)' if @functions.empty?

  @functions.map { |k, f| "#{k}: #{f}" }
            .join("\n")
end

#in_scope?(var_id, position) ⇒ boolean

Variables outside of an ![[expand=…] are always in scope. If it’s defined within an expand then things get trickier because the variable is only in scope while we’re processing cells within that expand.

Parameters:

  • var_id (Symbol)

    The variable’s identifier that we are checking if it’s in scope

  • position (Position)

Returns:

  • (boolean)


70
71
72
73
74
75
76
77
# File 'lib/csv_plus_plus/runtime/scope.rb', line 70

def in_scope?(var_id, position)
  value = @variables[var_id]

  return false unless value

  expand = value.is_a?(::CSVPlusPlus::Entities::Reference) && value.a1_ref.scoped_to_expand
  !expand || expand.position_within?(position)
end

#resolve_function(fn_id) ⇒ Entities::Function

Parameters:

  • fn_id (Symbol)

Returns:

Raises:



168
169
170
171
172
173
174
175
# File 'lib/csv_plus_plus/runtime/scope.rb', line 168

def resolve_function(fn_id)
  return ::T.must(@functions[fn_id]) if @functions.key?(fn_id)

  builtin = ::CSVPlusPlus::Entities::Builtins::FUNCTIONS[fn_id]
  raise(::CSVPlusPlus::Error::FormulaSyntaxError.new('Undefined function', bad_input: fn_id.to_s)) unless builtin

  builtin
end

#resolve_functions(position, ast, refs) ⇒ Entity

Parameters:

  • position (Position)

    osition [Position

  • ast (Entity)
  • refs (Array<FunctionCall>)

Returns:

  • (Entity)


109
110
111
112
113
# File 'lib/csv_plus_plus/runtime/scope.rb', line 109

def resolve_functions(position, ast, refs)
  refs.reduce(ast.dup) do |acc, elem|
    function_replace(position, acc, elem.id, resolve_function(elem.id))
  end
end

#resolve_variable(position, var_id) ⇒ Entities::Entity

Parameters:

Returns:



246
247
248
249
250
251
252
253
254
# File 'lib/csv_plus_plus/runtime/scope.rb', line 246

def resolve_variable(position, var_id)
  return ::T.must(@variables[var_id]) if @variables.key?(var_id)

  unless ::CSVPlusPlus::Entities::Builtins.builtin_variable?(var_id)
    raise(::CSVPlusPlus::Error::FormulaSyntaxError.new('Undefined variable', bad_input: var_id.to_s))
  end

  ::T.must(::CSVPlusPlus::Entities::Builtins::VARIABLES[var_id]).call(position, [])
end

#resolve_variables(position, ast, refs) ⇒ Entity

Parameters:

  • position (Position)
  • ast (Entity)
  • refs (Array<Variable>)

Returns:

  • (Entity)


127
128
129
130
131
132
133
# File 'lib/csv_plus_plus/runtime/scope.rb', line 127

def resolve_variables(position, ast, refs)
  refs.reduce(ast.dup) do |acc, elem|
    next acc unless (id = elem.id)

    variable_replace(acc, id, resolve_variable(position, id))
  end
end

#variable_replace(node, var_id, replacement) ⇒ Object

Make a copy of the AST represented by node and replace var_id with replacement throughout



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/csv_plus_plus/runtime/scope.rb', line 227

def variable_replace(node, var_id, replacement)
  if node.is_a?(::CSVPlusPlus::Entities::FunctionCall)
    arguments = node.arguments.map { |n| variable_replace(n, var_id, replacement) }
    # TODO: refactor these places where we copy functions... it's brittle with the kwargs
    ::CSVPlusPlus::Entities::FunctionCall.new(node.id, arguments, infix: node.infix)
  elsif node.is_a?(::CSVPlusPlus::Entities::Reference) && node.id == var_id
    replacement
  else
    node
  end
end

#variable_summaryString

Create a summary of all currently defined variables

Returns:

  • (String)


260
261
262
263
264
265
# File 'lib/csv_plus_plus/runtime/scope.rb', line 260

def variable_summary
  return '(no variables defined)' if @variables.empty?

  @variables.map { |k, v| "#{k} := #{v}" }
            .join("\n")
end

#verbose_summary::String

Provide a summary of the functions and variables compiled (to show in verbose mode)

Returns:

  • (::String)


83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/csv_plus_plus/runtime/scope.rb', line 83

def verbose_summary
  <<~SUMMARY
    # Code Section Summary

    ## Resolved Variables

    #{variable_summary}

    ## Functions

    #{function_summary}
  SUMMARY
end