Class: Rubex::AST::Node::Base

Inherits:
Object
  • Object
show all
Includes:
Helpers::Writers
Defined in:
lib/rubex/ast/node.rb

Direct Known Subclasses

FileNode, MainNode

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Helpers::Writers

#declare_carrays, #declare_ruby_objects, #declare_temps, #declare_types, #declare_vars, #sue_footer, #sue_header, #write_char_2_ruby_str_code, #write_char_2_ruby_str_header, #write_usability_functions_code, #write_usability_functions_header, #write_usability_macros

Constructor Details

#initialize(statements, file_name) ⇒ Base

Returns a new instance of Base.



9
10
11
12
13
# File 'lib/rubex/ast/node.rb', line 9

def initialize(statements, file_name)
  @statements = statements.flatten
  @file_name = file_name
  @dependent_files = []
end

Instance Attribute Details

#file_nameObject (readonly)

Returns the value of attribute file_name.



7
8
9
# File 'lib/rubex/ast/node.rb', line 7

def file_name
  @file_name
end

#statementsObject (readonly)

Returns the value of attribute statements.



7
8
9
# File 'lib/rubex/ast/node.rb', line 7

def statements
  @statements
end

Instance Method Details

#==(other) ⇒ Object



15
16
17
# File 'lib/rubex/ast/node.rb', line 15

def ==(other)
  self.class == other.class
end

#add_top_statements_to_object_scopeObject

Scan all the statements that do not belong to any particular class

(meaning that they belong to Object) and add them to the Object class,
which becomes the class from which all other classes will inherit from.

Top-level statements that do not belong inside classes and which do not

have any relevance inside a class at the level of a C extension are
added to a different array and are analysed differently. For example
'require' statements that are top level statements but cannot be 'defined'
inside the Init_ method the way a ruby class or method can be. These are
stored in @outside_statements.

If the user defines multiple Object classes, they will share the same @scope

object and will therefore have accessible members between each other. Since
Ruby also allows users to open classes whenever and wherever they want, this
behaviour is conformant with actual Ruby behaviour. It also implies that a
FileNode can basically create an Object class of its own and the scope will
shared between FileNode and MainNode and other FileNodes as long as the FileNode
shares the same @scope object.


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/rubex/ast/node.rb', line 47

def add_top_statements_to_object_scope
  temp = []
  combined_statements = []
  @outside_statements = []
  @statements.each do |stmt|
    if stmt.is_a?(TopStatement::Klass) || stmt.is_a?(TopStatement::CBindings)
      if !temp.empty?
        object_klass = TopStatement::Klass.new('Object', @scope, temp)
        combined_statements << object_klass
      end

      combined_statements << stmt
      temp = []
    elsif outside_statement?(stmt)
      @outside_statements << stmt
    elsif stmt.is_a?(Node::FileNode)
      combined_statements << stmt
    else
      temp << stmt
    end
  end

  unless temp.empty?
    combined_statements << TopStatement::Klass.new('Object', @scope, temp)
  end

  @statements = combined_statements
end

#analyse_statementObject



19
20
21
22
23
24
25
26
27
# File 'lib/rubex/ast/node.rb', line 19

def analyse_statement
  @statements.each do |stat|
  @dependent_files << stat.file_name if stat.is_a?(Node::FileNode)
  stat.analyse_statement @scope         
  end
  @outside_statements.each do |stmt|
    stmt.analyse_statement @scope
  end
end

#c_name_for_class(name) ⇒ Object



196
197
198
199
200
201
202
203
204
205
# File 'lib/rubex/ast/node.rb', line 196

def c_name_for_class(name)
  c_name =
    if Rubex::DEFAULT_CLASS_MAPPINGS.key? name
      Rubex::DEFAULT_CLASS_MAPPINGS[name]
    else
      Rubex::RUBY_CLASS_PREFIX + name
    end

  c_name
end

#call_dependent_init_methods(code) ⇒ Object



276
277
278
279
280
281
# File 'lib/rubex/ast/node.rb', line 276

def call_dependent_init_methods(code)
  @dependent_files.each do |dep|
    code << "Init_#{dep}();"
    code.nl
  end
end

#create_symtab_entries_for_top_statements(s) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/rubex/ast/node.rb', line 156

def create_symtab_entries_for_top_statements(s)
  @scope = s
  @statements.each do |stat|
    stat.create_symtab_entries_for_top_statements(@scope) if stat.is_a?(Node::FileNode)
    next unless stat.is_a? Rubex::AST::TopStatement::Klass
    name = stat.name
    # The top level scope in Ruby is Object. The Object class's immediate
    # ancestor is also Object. Hence, it is important to set the class
    # scope and ancestor scope of Object as Object, and make sure that
    # the same scope object is used for 'Object' class every single time
    # throughout the compilation process.
    if name != 'Object'
      ancestor_entry = @scope.find(stat.ancestor)
      if !ancestor_entry && Rubex::DEFAULT_CLASS_MAPPINGS[stat.ancestor]
        ancestor_c_name = Rubex::DEFAULT_CLASS_MAPPINGS[stat.ancestor]
        ancestor_scope = object_or_stdlib_klass_scope stat.ancestor
        @scope.add_ruby_class(name: stat.ancestor, c_name: ancestor_c_name,
                              scope: @scope, ancestor: nil, extern: true)
      else
        ancestor_scope = ancestor_entry&.type&.scope || @scope
      end
      klass_scope = Rubex::SymbolTable::Scope::Klass.new(
        name, ancestor_scope
      )
    else
      ancestor_scope = @scope
      klass_scope = @scope
    end
    c_name = c_name_for_class name

    @scope.add_ruby_class(name: name, c_name: c_name, scope: klass_scope,
                          ancestor: ancestor_scope, extern: false)
  end
end

#declare_unique_types(header) ⇒ Object



81
82
83
84
85
86
87
88
# File 'lib/rubex/ast/node.rb', line 81

def declare_unique_types header
  types = []
  @statements.grep(Rubex::AST::TopStatement::Klass).each do |klass|
    types.concat klass.scope.type_entries
  end
  types.uniq!
  declare_types header, types
end

#define_classes(code) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rubex/ast/node.rb', line 225

def define_classes(code)
  @statements.each do |top_stmt|
    # define a class
    if top_stmt.is_a?(TopStatement::Klass) && top_stmt.name != 'Object'
      entry = top_stmt.entry
      ancestor_entry = @scope.find top_stmt.ancestor.name
      c_name = ancestor_entry ? ancestor_entry.c_name : 'rb_cObject'
      rhs = "rb_define_class(\"#{entry.name}\", #{c_name})"
      code.init_variable lhs: entry.c_name, rhs: rhs
    end

    # specify allocation method in case of attached class
    next unless top_stmt.is_a?(TopStatement::AttachedKlass)
    entry = top_stmt.entry
    scope = top_stmt.scope
    alloc = ''
    alloc << "rb_define_alloc_func(#{entry.c_name}, "
    alloc << "#{scope.find(TopStatement::AttachedKlass::ALLOC_FUNC_NAME).c_name});\n"

    code << alloc
  end
  code.nl
end

#define_instance_and_singleton_methods_for_all_classes(code) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/rubex/ast/node.rb', line 249

def define_instance_and_singleton_methods_for_all_classes(code)
  @statements.each do |top_stmt|
    next unless top_stmt.is_a? TopStatement::Klass
    entry = @scope.find top_stmt.name
    klass_scope = entry.type.scope
    klass_scope.ruby_method_entries.each do |meth|
      if meth.singleton?
        code.write_singleton_method klass: entry.c_name,
                                    method_name: meth.name, method_c_name: meth.c_name
      else
        code.write_instance_method klass: entry.c_name,
                                   method_name: meth.name, method_c_name: meth.c_name
      end
    end
  end        
end

#generate_code(supervisor) ⇒ Object

FIXME: Find a way to eradicate the if statement.



215
216
217
218
219
220
221
222
223
# File 'lib/rubex/ast/node.rb', line 215

def generate_code(supervisor)
  @statements.each do |stat|
    if stat.is_a?(Rubex::AST::Node::FileNode)
      stat.generate_code supervisor
    else
      stat.generate_code supervisor.code(@file_name)
    end
  end
end

#generate_header_file(target_name, header) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/rubex/ast/node.rb', line 90

def generate_header_file(target_name, header)
  header_def_name = target_name.gsub("/", "_").gsub(".", "_").upcase + "_H"
  header.in_header_guard(header_def_name) do
    header.write_include Rubex::COMMON_UTILS_FILE
    @scope.include_files.each do |name|
      header <<
        if name[0] == '<' && name[-1] == '>'
          "#include #{name}\n"
        else
          "#include \"#{name}\"\n"
        end
    end
    declare_unique_types header
    write_user_klasses header
    write_global_variable_declarations header
    write_function_declarations header
    header.write_func_declaration type: 'void', c_name: init_function,
                                  args: [], static: false
  end
end

#generate_init_method(code) ⇒ Object



283
284
285
286
287
288
289
290
291
292
293
# File 'lib/rubex/ast/node.rb', line 283

def generate_init_method(code)
  name = init_function
  code.new_line
  code.write_c_method_header type: 'void', c_name: name, args: [], static: false
  code.block do
    call_dependent_init_methods(code)
    write_outside_statements(code)
    define_classes(code)
    define_instance_and_singleton_methods_for_all_classes(code)
  end
end

#init_functionObject



272
273
274
# File 'lib/rubex/ast/node.rb', line 272

def init_function
  "Init_#{@file_name}"
end

#object_or_stdlib_klass_scope(name) ⇒ Object



191
192
193
194
# File 'lib/rubex/ast/node.rb', line 191

def object_or_stdlib_klass_scope(name)
  name != 'Object' ? Rubex::SymbolTable::Scope::Klass.new(name, nil) :
    @scope
end

#outside_statement?(stmt) ⇒ Boolean

TODO: accomodate all sorts of outside stmts like if blocks/while/for etc

Returns:



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

def outside_statement?(stmt)
  stmt.is_a?(Statement::Expression)
end

#rescan_declarations(_scope) ⇒ Object



207
208
209
210
211
212
# File 'lib/rubex/ast/node.rb', line 207

def rescan_declarations(_scope)
  @statements.each do |stat|
    stat.respond_to?(:rescan_declarations) &&
      stat.rescan_declarations(@scope)
  end
end

#write_function_declarations(code) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rubex/ast/node.rb', line 134

def write_function_declarations(code)
  @statements.each do |stmt|
    next unless stmt.is_a?(Rubex::AST::TopStatement::Klass)
    stmt.scope.ruby_method_entries.each do |entry|
      code.write_ruby_method_header(
        type: entry.type.type.to_s, c_name: entry.c_name
      )
      code.colon
    end

    stmt.scope.c_method_entries.each do |entry|
      next if entry.extern?
      code.write_c_method_header(
        type: entry.type.type.to_s,
        c_name: entry.c_name,
        args: Helpers.create_arg_arrays(entry.type.arg_list)
      )
      code.colon
    end
  end
end

#write_global_variable_declarations(code) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/rubex/ast/node.rb', line 111

def write_global_variable_declarations(code)
  @statements.each do |stmt|
    next unless stmt.is_a?(TopStatement::Klass)
    stmt.statements.each do |s|
      next unless s.is_a?(TopStatement::MethodDef)
      s.scope.global_entries.each do |g|
        code << "static #{g.type} #{g.c_name};"
        code.nl
      end
    end
  end
end

#write_outside_statements(code) ⇒ Object



266
267
268
269
270
# File 'lib/rubex/ast/node.rb', line 266

def write_outside_statements(code)
  @outside_statements.each do |stmt|
    stmt.generate_code(code, @scope)
  end
end

#write_user_klasses(code) ⇒ Object



124
125
126
127
128
129
130
131
132
# File 'lib/rubex/ast/node.rb', line 124

def write_user_klasses(code)
  code.nl
  @scope.ruby_class_entries.each do |klass|
    unless Rubex::DEFAULT_CLASS_MAPPINGS.has_key?(klass.name)
      code << "VALUE #{klass.c_name};" 
      code.nl
    end
  end
end