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
BitsUnknownType =
Type.new(:bits, width: :unknown).freeze
ConstBitsUnknownType =
Type.new(:bits, width: :unknown, qualifiers: [:const]).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



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

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.



317
318
319
# File 'lib/idl/ast.rb', line 317

def value_error_ast
  @value_error_ast
end

.value_error_reasonObject

Returns the value of attribute value_error_reason.



317
318
319
# File 'lib/idl/ast.rb', line 317

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



77
78
79
# File 'lib/idl/ast.rb', line 77

def children
  @children
end

#inputString (readonly)

Returns Source string.

Returns:

  • (String)

    Source string



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

def input
  @input
end

#input_fileString (readonly)

Returns Source input file.

Returns:

  • (String)

    Source input file



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

def input_file
  @input_file
end

#intervalRange (readonly)

Returns Range within the input for this node.

Returns:

  • (Range)

    Range within the input for this node



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

def interval
  @interval
end

#parentAstNode? (readonly)

Returns:

  • (AstNode)

    The parent node

  • (nil)

    if this is the root of the tree



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

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)



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

def starting_line
  @starting_line
end

#text_valueString (readonly)

Returns The IDL source of this node.

Returns:

  • (String)

    The IDL source of this node



70
71
72
# File 'lib/idl/ast.rb', line 70

def text_value
  @text_value
end

Class Method Details

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

Yields:

  • (block)


159
160
161
162
163
# File 'lib/idl/ast.rb', line 159

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:



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

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



152
153
154
155
156
# File 'lib/idl/ast.rb', line 152

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



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

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



365
366
367
368
369
370
# File 'lib/idl/ast.rb', line 365

def freeze_tree(global_symtab)
  return if frozen?

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

#inspectObject



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

def inspect = self.class.name.to_s

#internal_error(reason) ⇒ Object

raise an internal error

Parameters:

  • reason (String)

    Error message

Raises:



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

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

#linenoInteger

Returns the current line number.

Returns:

  • (Integer)

    the current line number



212
213
214
# File 'lib/idl/ast.rb', line 212

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



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

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



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

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



349
350
351
352
353
354
# File 'lib/idl/ast.rb', line 349

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



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

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.



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

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)


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

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:



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

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:



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
297
298
# File 'lib/idl/ast.rb', line 259

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 occurred
      #{$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



340
341
342
# File 'lib/idl/ast.rb', line 340

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

#value_else(value_result, &block) ⇒ Object



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

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, []



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

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

#value_try(&block) ⇒ Object



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

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