Class: Idl::AstNode Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/idl/ast.rb

Overview

This class is abstract.

base class for all nodes considered part of the Ast

Defined Under Namespace

Classes: InternalError, TypeError, ValueError

Constant Summary collapse

Bits1Type =
Type.new(:bits, width: 1).freeze
Bits32Type =
Type.new(:bits, width: 32).freeze
Bits64Type =
Type.new(:bits, width: 64).freeze
ConstBoolType =
Type.new(:boolean, qualifiers: [:const]).freeze
BoolType =
Type.new(:boolean).freeze
VoidType =
Type.new(:void).freeze
StringType =
Type.new(:string).freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, interval, children) ⇒ AstNode

Returns a new instance of AstNode.

Parameters:

  • input (String)

    The source being compiled

  • interval (Range)

    The range in the source corresponding to this AstNode

  • children (Array<AstNode>)

    Children of this node



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/idl/ast.rb', line 167

def initialize(input, interval, children)
  @input = input
  @input_file = nil
  @starting_line = 0
  @interval = interval
  @text_value = input[interval]
  children.each { |child| raise ArgumentError, "Children of #{self.class.name} must be AstNodes (found a #{child.class.name})" unless child.is_a?(AstNode)}
  @children = children
  @parent = nil # will be set later unless this is the root
  @children.each { |child| child.instance_variable_set(:@parent, self) }
end

Class Attribute Details

.value_error_astObject

Returns the value of attribute value_error_ast.



315
316
317
# File 'lib/idl/ast.rb', line 315

def value_error_ast
  @value_error_ast
end

.value_error_reasonObject

Returns the value of attribute value_error_reason.



315
316
317
# File 'lib/idl/ast.rb', line 315

def value_error_reason
  @value_error_reason
end

Instance Attribute Details

#childrenArray<AstNode> (readonly)

Returns Children of this node.

Returns:

  • (Array<AstNode>)

    Children of this node



75
76
77
# File 'lib/idl/ast.rb', line 75

def children
  @children
end

#inputString (readonly)

Returns Source string.

Returns:

  • (String)

    Source string



62
63
64
# File 'lib/idl/ast.rb', line 62

def input
  @input
end

#input_fileString (readonly)

Returns Source input file.

Returns:

  • (String)

    Source input file



56
57
58
# File 'lib/idl/ast.rb', line 56

def input_file
  @input_file
end

#intervalObject (readonly)

Returns the value of attribute interval.



65
66
67
# File 'lib/idl/ast.rb', line 65

def interval
  @interval
end

#parentObject (readonly)

Returns the value of attribute parent.



72
73
74
# File 'lib/idl/ast.rb', line 72

def parent
  @parent
end

#starting_lineInteger (readonly)

Returns Starting line in the source input file (i.e., position 0 of #input in the file).

Returns:

  • (Integer)

    Starting line in the source input file (i.e., position 0 of #input in the file)



59
60
61
# File 'lib/idl/ast.rb', line 59

def starting_line
  @starting_line
end

#text_valueString (readonly)

Returns The IDL source of this node.

Returns:

  • (String)

    The IDL source of this node



68
69
70
# File 'lib/idl/ast.rb', line 68

def text_value
  @text_value
end

Class Method Details

.value_else(value_result) {|block| ... } ⇒ Object

Yields:

  • (block)


157
158
159
160
161
# File 'lib/idl/ast.rb', line 157

def self.value_else(value_result, &block)
  return unless value_result == :unknown_value

  yield block
end

.value_error(reason, ast = nil) ⇒ Object

raise a value error, indicating that the value is not known at compile time

Parameters:

  • reason (String)

    Error message

Raises:



322
323
324
325
326
327
328
329
# File 'lib/idl/ast.rb', line 322

def self.value_error(reason, ast = nil)
  AstNode.value_error_reason = reason
  AstNode.value_error_ast = ast
  # warn reason
  # warn "At #{ast.input_file}:#{ast.lineno}" unless ast.nil?
  throw(:value_error, :unknown_value)
  #raise AstNode::ValueError.new(lineno, input_file, reason), reason, []
end

.value_try(&block) ⇒ Object



150
151
152
153
154
# File 'lib/idl/ast.rb', line 150

def self.value_try(&block)
  catch(:value_error) do
    yield block
  end
end

Instance Method Details

#find_ancestor(klass) ⇒ AstNode?

Returns:

  • (AstNode)

    the first ancestor that is_a?(klass)

  • (nil)

    if no ancestor is found



216
217
218
219
220
221
222
223
224
# File 'lib/idl/ast.rb', line 216

def find_ancestor(klass)
  if parent.nil?
    nil
  elsif parent.is_a?(klass)
    parent
  else
    parent.find_ancestor(klass)
  end
end

#freeze_tree(global_symtab) ⇒ Object

freeze the entire tree from further modification This is also an opportunity to pre-calculate anything that only needs global symbols

Parameters:

  • global_symtab (SymbolTable)

    Symbol table with global scope populated



363
364
365
366
367
368
# File 'lib/idl/ast.rb', line 363

def freeze_tree(global_symtab)
  return if frozen?

  @children.each { |child| child.freeze_tree(global_symtab) }
  freeze
end

#inspectObject



403
# File 'lib/idl/ast.rb', line 403

def inspect = self.class.name.to_s

#internal_error(reason) ⇒ Object

raise an internal error

Parameters:

  • reason (String)

    Error message

Raises:



302
303
304
305
306
307
308
309
310
# File 'lib/idl/ast.rb', line 302

def internal_error(reason)
  msg = <<~WHAT
    In file #{input_file}
    On line #{lineno}
      An internal error occured
      #{reason}
  WHAT
  raise AstNode::InternalError, msg
end

#linenoInteger

Returns the current line number.

Returns:

  • (Integer)

    the current line number



210
211
212
# File 'lib/idl/ast.rb', line 210

def lineno
  input[0..interval.first].count("\n") + 1 + (@starting_line.nil? ? 0 : @starting_line)
end

#lines_aroundString

Returns +-2 lines around the current interval

Returns:

  • (String)

    returns +-2 lines around the current interval



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/idl/ast.rb', line 227

def lines_around
  cnt = 0
  interval_start = interval.min
  while cnt < 2
    cnt += 1 if input[interval_start] == "\n"
    break if interval_start.zero?

    interval_start -= 1
  end

  cnt = 0
  interval_end = interval.max
  while cnt < 3
    cnt += 1 if input[interval_end] == "\n"
    break if interval_end >= (input.size - 1)

    interval_end += 1
  end

  [
    input[interval_start..interval_end],
    (interval.min - interval_start..interval.max - interval_start),
    (interval_start + 1)..interval_end
  ]
end

#pathString

Returns A string representing the path to this node in the tree.

Returns:

  • (String)

    A string representing the path to this node in the tree



371
372
373
374
375
376
377
# File 'lib/idl/ast.rb', line 371

def path
  if parent.nil?
    self.class.name
  else
    "#{parent.path}.#{self.class.name}"
  end
end

pretty print the AST rooted at this node

Parameters:

  • indent (Integer) (defaults to: 0)

    The starting indentation, in # of spaces

  • indent_size (Integer) (defaults to: 2)

    The extra indentation applied to each level of the tree

  • io (IO) (defaults to: $stdout)

    Where to write the output



347
348
349
350
351
352
# File 'lib/idl/ast.rb', line 347

def print_ast(indent = 0, indent_size: 2, io: $stdout)
  io.puts "#{' ' * indent}#{self.class.name}:"
  children.each do |node|
    node.print_ast(indent + indent_size, indent_size:)
  end
end

#set_input_file(filename, starting_line = 0) ⇒ Object

remember where the code comes from

Parameters:

  • filename (String)

    Filename

  • starting_line (Integer) (defaults to: 0)

    Starting line in the file



200
201
202
203
204
205
206
207
# File 'lib/idl/ast.rb', line 200

def set_input_file(filename, starting_line = 0)
  @input_file = filename
  @starting_line = starting_line
  children.each do |child|
    child.set_input_file(filename, starting_line)
  end
  raise "?" if @starting_line.nil?
end

#set_input_file_unless_already_set(filename, starting_line = 0) ⇒ Object

Sets the input file for this syntax node unless it has already been set.

If the input file has not been set, it will be set with the given filename and starting line number.

Parameters:

  • filename (String)

    The name of the input file.

  • starting_line (Integer) (defaults to: 0)

    The starting line number in the input file.



185
186
187
188
189
190
191
192
193
194
# File 'lib/idl/ast.rb', line 185

def set_input_file_unless_already_set(filename, starting_line = 0)
  return unless @input_file.nil?

  @input_file = filename
  @starting_line = starting_line
  children.each do |child|
    child.set_input_file_unless_already_set(filename, starting_line)
  end
  raise "?" if @starting_line.nil?
end

#to_idlString

This method is abstract.

Return valid IDL representation of the node (and its subtree)

Returns:

  • (String)

    IDL code for the node

Raises:

  • (NotImplementedError)


401
# File 'lib/idl/ast.rb', line 401

def to_idl = raise NotImplementedError, "#{self.class.name} must implement to_idl"

#type_check(symtab) ⇒ void

This method is abstract.

This method returns an undefined value.

type check this node and all children

Calls to #type and/or #value may depend on type_check being called first with the same symtab. If not, those functions may raise an AstNode::InternalError

Parameters:

Raises:



392
# File 'lib/idl/ast.rb', line 392

def type_check(symtab) = raise NotImplementedError, "#{self.class.name} must implement type_check"

#type_error(reason) ⇒ Object

raise a type error

Parameters:

  • reason (String)

    Error message

Raises:



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/idl/ast.rb', line 257

def type_error(reason)
  lines, problem_interval, lines_interval = lines_around

  lines =
    if $stdout.isatty
      [
        lines[0...problem_interval.min],
        "\u001b[31m",
        lines[problem_interval],
        "\u001b[0m",
        lines[(problem_interval.max + 1)..]
      ].join("")
    else
      [
        lines[0...problem_interval.min],
        "**HERE** >> ",
        lines[problem_interval],
        " << **HERE**",
        lines[(problem_interval.max + 1)..]
      ].join("")
    end

  starting_lineno = input[0..lines_interval.min].count("\n")
  lines = lines.lines.map do |line|
    starting_lineno += 1
    "#{@starting_line + starting_lineno - 1}: #{line}"
  end.join("")

  msg = <<~WHAT
    In file #{input_file}
    On line #{lineno}
    In the code:

      #{lines.gsub("\n", "\n  ")}

    A type error occured
      #{$stdout.isatty ? "\u001b[31m#{reason}\u001b[0m" : reason}
  WHAT
  raise AstNode::TypeError, msg
end

#unindent(s) ⇒ String

unindent a multiline string, getting rid of all common leading whitespace (like <<~ heredocs)

borrowed from stackoverflow.com/questions/33527064/multiline-strings-with-no-indent

Parameters:

  • s (String)

    A string (presumably with newlines)

Returns:

  • (String)

    Unindented string



338
339
340
# File 'lib/idl/ast.rb', line 338

def unindent(s)
  s.gsub(/^#{s.scan(/^[ \t]+(?=\S)/).min}/, "")
end

#value_else(value_result, &block) ⇒ Object



162
# File 'lib/idl/ast.rb', line 162

def value_else(value_result, &block) = self.class.value_else(value_result, &block)

#value_error(reason) ⇒ Object

raise AstNode::ValueError.new(lineno, input_file, reason), reason, []



330
# File 'lib/idl/ast.rb', line 330

def value_error(reason) = self.class.value_error(reason, self)

#value_try(&block) ⇒ Object



155
# File 'lib/idl/ast.rb', line 155

def value_try(&block) = return self.class.value_try(&block)