Class: CSVPlusPlus::Runtime

Inherits:
Object
  • Object
show all
Defined in:
lib/csv_plus_plus/runtime.rb

Overview

The runtime state of the compiler (the current line_number/row_index, cell being processed, etc) for parsing a given file. We take multiple runs through the input file for parsing so it’s really convenient to have a central place for these things to be managed.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input:, filename:) ⇒ Runtime

Returns a new instance of Runtime.

Parameters:

  • input (String)

    The input to be parsed

  • filename (String, nil)

    The filename that the input came from (mostly used for debugging since filename can be nil if it’s read from stdin



28
29
30
31
32
33
# File 'lib/csv_plus_plus/runtime.rb', line 28

def initialize(input:, filename:)
  @filename = filename || 'stdin'

  init_input!(input)
  start!
end

Instance Attribute Details

#cellCell

The current cell being processed

Returns:

  • (Cell)

    the current value of cell



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def cell
  @cell
end

#cell_indexInteger

The index of the current cell being processed (starts at 0)

Returns:

  • (Integer)

    the current value of cell_index



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def cell_index
  @cell_index
end

#filenameString? (readonly)

The filename that the input came from (mostly used for debugging since filename can be nil if it’s read from stdin.

Returns:

  • (String, nil)

    the current value of filename



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def filename
  @filename
end

#length_of_code_sectionInteger (readonly)

The length (count of lines) of the code section part of the original input.

Returns:

  • (Integer)

    the current value of length_of_code_section



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def length_of_code_section
  @length_of_code_section
end

#length_of_csv_sectionInteger (readonly)

The length (count of lines) of the CSV part of the original csvpp input.

Returns:

  • (Integer)

    the current value of length_of_csv_section



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def length_of_csv_section
  @length_of_csv_section
end

#length_of_original_fileInteger (readonly)

The length (count of lines) of the original csvpp input.

Returns:

  • (Integer)

    the current value of length_of_original_file



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def length_of_original_file
  @length_of_original_file
end

#line_numberInteger

The line number of the original csvpp template (starts at 1)

Returns:

  • (Integer)

    the current value of line_number



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def line_number
  @line_number
end

#row_indexInteger

The index of the current row being processed (starts at 0)

Returns:

  • (Integer)

    the current value of row_index



20
21
22
# File 'lib/csv_plus_plus/runtime.rb', line 20

def row_index
  @row_index
end

Instance Method Details

#cleanup!Object

Clean up the Tempfile we’re using for parsing



172
173
174
175
176
177
178
# File 'lib/csv_plus_plus/runtime.rb', line 172

def cleanup!
  return unless @tmp

  @tmp.close
  @tmp.unlink
  @tmp = nil
end

#inputString

The currently available input for parsing. The tmp state will be re-written between parsing the code section and the CSV section

Returns:

  • (String)


158
159
160
# File 'lib/csv_plus_plus/runtime.rb', line 158

def input
  @tmp
end

#map_lines(lines, &block) ⇒ Array

Map over an a csvpp file and keep track of line_number and row_index

Parameters:

  • lines (Array)

Returns:

  • (Array)


40
41
42
43
44
45
# File 'lib/csv_plus_plus/runtime.rb', line 40

def map_lines(lines, &block)
  @line_number = 1
  lines.map do |line|
    block.call(line).tap { next_line! }
  end
end

#map_row(row, &block) ⇒ Array

Map over a single row and keep track of the cell and it’s index

Parameters:

  • row (Array<Cell>)

    The row to map each cell over

Returns:

  • (Array)


52
53
54
55
56
57
58
# File 'lib/csv_plus_plus/runtime.rb', line 52

def map_row(row, &block)
  @cell_index = 0
  row.map.with_index do |cell, index|
    set_cell!(cell, index)
    block.call(cell, index)
  end
end

#map_rows(rows, cells_too: false, &block) ⇒ Array

Map over all rows and keep track of row and line numbers

Parameters:

  • rows (Array<Row>)

    The rows to map over (and keep track of indexes)

  • cells_too (boolean) (defaults to: false)

    If the cells of each row should be iterated over also.

Returns:

  • (Array)


66
67
68
69
70
71
72
73
74
75
76
# File 'lib/csv_plus_plus/runtime.rb', line 66

def map_rows(rows, cells_too: false, &block)
  @row_index = 0
  map_lines(rows) do |row|
    if cells_too
      # it's either CSV or a Row object
      map_row((row.is_a?(::CSVPlusPlus::Row) ? row.cells : row), &block)
    else
      block.call(row)
    end
  end
end

#next_line!Integer

Increment state to the next line

Returns:

  • (Integer)


81
82
83
84
# File 'lib/csv_plus_plus/runtime.rb', line 81

def next_line!
  @row_index += 1 unless @row_index.nil?
  @line_number += 1
end

#raise_formula_syntax_error(message, bad_input, wrapped_error: nil) ⇒ Object

Called when an error is encoutered during parsing. It will construct a useful error with the current @row/@cell_index, @line_number and @filename

Parameters:

  • message (String)

    A message relevant to why this error is being raised.

  • bad_input (String)

    The offending input that caused this error to be thrown.

  • wrapped_error (StandardError, nil) (defaults to: nil)

    The underlying error that was raised (if it’s not from our own logic)

Raises:



150
151
152
# File 'lib/csv_plus_plus/runtime.rb', line 150

def raise_formula_syntax_error(message, bad_input, wrapped_error: nil)
  raise(::CSVPlusPlus::Error::FormulaSyntaxError.new(message, bad_input, self, wrapped_error:))
end

#rewrite_input!(data) ⇒ Object

We mutate the input over and over. It’s ok because it’s just a Tempfile

Parameters:

  • data (String)

    The data to rewrite our input file to



165
166
167
168
169
# File 'lib/csv_plus_plus/runtime.rb', line 165

def rewrite_input!(data)
  @tmp.truncate(0)
  @tmp.write(data)
  @tmp.rewind
end

#rownumInteger?

Return the current spreadsheet row number. It parallels @row_index but starts at 1.

Returns:

  • (Integer, nil)


89
90
91
92
93
# File 'lib/csv_plus_plus/runtime.rb', line 89

def rownum
  return if @row_index.nil?

  @row_index + 1
end

#runtime_value(var_id) ⇒ Entity

Get the current (entity) value of a runtime value

Parameters:

  • var_id (String, Symbol)

    The Variable#id of the variable being resolved.

Returns:

  • (Entity)


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

def runtime_value(var_id)
  if runtime_variable?(var_id)
    ::CSVPlusPlus::Entities::Builtins::VARIABLES[var_id.to_sym].resolve_fn.call(self)
  else
    raise_formula_syntax_error('Undefined variable', var_id)
  end
end

#runtime_variable?(var_id) ⇒ boolean

Is var_id a runtime variable? (it’s a static variable otherwise)

Parameters:

  • var_id (String, Symbol)

    The Variable#id to check if it’s a runtime variable

Returns:

  • (boolean)


140
141
142
# File 'lib/csv_plus_plus/runtime.rb', line 140

def runtime_variable?(var_id)
  ::CSVPlusPlus::Entities::Builtins::VARIABLES.key?(var_id.to_sym)
end

#set_cell!(cell, cell_index) ⇒ Object

Set the current cell and index

Parameters:

  • cell (Cell)

    The current cell

  • cell_index (Integer)

    The index of the cell



99
100
101
102
# File 'lib/csv_plus_plus/runtime.rb', line 99

def set_cell!(cell, cell_index)
  @cell = cell
  @cell_index = cell_index
end

#start!Object

Each time we run a parse on the input, reset the runtime state starting at the beginning of the file



105
106
107
108
# File 'lib/csv_plus_plus/runtime.rb', line 105

def start!
  @row_index = @cell_index = nil
  @line_number = 1
end

#start_at_csv!Object

Reset the runtime state starting at the CSV section



111
112
113
114
115
# File 'lib/csv_plus_plus/runtime.rb', line 111

def start_at_csv!
  # TODO: isn't the input re-written anyway without the code section? why do we need this?
  start!
  @line_number = @length_of_code_section || 1
end

#to_sString

Returns:

  • (String)


118
119
120
# File 'lib/csv_plus_plus/runtime.rb', line 118

def to_s
  "Runtime(cell: #{@cell}, row_index: #{@row_index}, cell_index: #{@cell_index})"
end