Module: Ibex::Codegen::RubyAST

Included in:
ActionSource, Ruby, RubyActions
Defined in:
lib/ibex/codegen/ruby_ast.rb,
sig/ibex/codegen/ruby_ast.rbs

Overview

Generates typed AST data classes and traversal bases from @node metadata.

Instance Method Summary collapse

Instance Method Details

#append_ast(lines) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] lines) -> void

Parameters:

  • lines (Array[String])


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/ibex/codegen/ruby_ast.rb', line 11

def append_ast(lines)
  definitions = ast_node_definitions
  return if definitions.empty?

  lines << "  module AST"
  definitions.each_value do |node|
    members = node.fetch(:fields).map { |field| ":#{field}" }.join(", ")
    lines << "    #{node.fetch(:name)} = Ibex::Runtime::ASTData.define(#{members})"
  end
  lines << ""
  append_visitor(lines, definitions)
  lines << ""
  append_listener(lines, definitions)
  lines.push("  end", "")
end

#append_listener(lines, definitions) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] lines, Hash[String, IR::node_annotation] definitions) -> void

Parameters:

  • lines (Array[String])
  • definitions (Hash[String, IR::node_annotation])


65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ibex/codegen/ruby_ast.rb', line 65

def append_listener(lines, definitions)
  lines.push("    class Listener", "      def walk(node)", "        enter(node)",
             "        listener_children(node).each { |child| walk(child) }", "        exit(node)", "        node",
             "      end", "")
  append_listener_dispatch(lines, definitions, :enter)
  lines << ""
  append_listener_dispatch(lines, definitions, :exit)
  lines << ""
  definitions.each_value do |node|
    method = ast_method_name(node.fetch(:name))
    lines.push("      def enter_#{method}(_node); end", "      def exit_#{method}(_node); end")
  end
  lines << ""
  lines.push(
    "      def listener_children(node)",
    "        return node if node.is_a?(Array)",
    "        return node.deconstruct if node.respond_to?(:deconstruct)",
    "",
    "        []",
    "      end",
    "    end"
  )
end

#append_listener_dispatch(lines, definitions, kind) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] lines, Hash[String, IR::node_annotation] definitions, :enter | :exit kind) -> void

Parameters:

  • lines (Array[String])
  • definitions (Hash[String, IR::node_annotation])
  • kind (:enter, :exit)


90
91
92
93
94
95
96
97
# File 'lib/ibex/codegen/ruby_ast.rb', line 90

def append_listener_dispatch(lines, definitions, kind)
  lines.push("      def #{kind}(node)", "        case node")
  definitions.each_value do |node|
    method = ast_method_name(node.fetch(:name))
    lines << "        when #{node.fetch(:name)} then #{kind}_#{method}(node)"
  end
  lines.push("        end", "      end")
end

#append_visit_children(lines) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] lines) -> void

Parameters:

  • lines (Array[String])


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/ibex/codegen/ruby_ast.rb', line 48

def append_visit_children(lines)
  lines.push(
    "      def visit_children(node)",
    "        children = if node.is_a?(Array)",
    "                     node",
    "                   elsif node.respond_to?(:deconstruct)",
    "                     node.deconstruct",
    "                   else",
    "                     []",
    "                   end",
    "        children.each { |child| visit(child) }",
    "        node",
    "      end"
  )
end

#append_visitor(lines, definitions) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String] lines, Hash[String, IR::node_annotation] definitions) -> void

Parameters:

  • lines (Array[String])
  • definitions (Hash[String, IR::node_annotation])


33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ibex/codegen/ruby_ast.rb', line 33

def append_visitor(lines, definitions)
  lines.push("    class Visitor", "      def visit(node)", "        case node")
  definitions.each_value do |node|
    lines << "        when #{node.fetch(:name)} then visit_#{ast_method_name(node.fetch(:name))}(node)"
  end
  lines.push("        else visit_children(node)", "        end", "      end", "")
  definitions.each_value do |node|
    method = ast_method_name(node.fetch(:name))
    lines.push("      def visit_#{method}(node)", "        visit_children(node)", "      end", "")
  end
  append_visit_children(lines)
  lines << "    end"
end

#ast_method_name(name) ⇒ String

RBS:

  • (String name) -> String

Parameters:

  • name (String)

Returns:

  • (String)


114
115
116
117
118
# File 'lib/ibex/codegen/ruby_ast.rb', line 114

def ast_method_name(name)
  name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
      .gsub(/([a-z\d])([A-Z])/, '\1_\2')
      .downcase
end

#ast_node_action_source(production) ⇒ String

RBS:

  • (IR::Production production) -> String

Parameters:

Returns:

  • (String)


100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/ibex/codegen/ruby_ast.rb', line 100

def ast_node_action_source(production)
  node = production.node || raise(Ibex::Error, "missing AST node metadata")
  parameters = node.fetch(:fields).each_index.map { |index| "v#{index}" }
  arguments = node.fetch(:fields).each_index.map do |index|
    "#{node.fetch(:fields).fetch(index)}: v#{index}"
  end
  [
    "private def _ibex_action_#{production.id}(#{parameters.join(', ')})",
    "  AST::#{node.fetch(:name)}.new(#{arguments.join(', ')})",
    "end"
  ].join("\n")
end

#ast_node_definitionsHash[String, IR::node_annotation]

RBS:

  • () -> Hash[String, IR::node_annotation]

Returns:

  • (Hash[String, IR::node_annotation])


28
29
30
# File 'lib/ibex/codegen/ruby_ast.rb', line 28

def ast_node_definitions
  @grammar.productions.filter_map(&:node).to_h { |node| [node.fetch(:name), node] }
end