Class: Idl::Compiler

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

Overview

the Idl compiler

Instance Method Summary collapse

Constructor Details

#initialize(arch_def) ⇒ Compiler

Returns a new instance of Compiler.

Parameters:

  • arch_def (ArchDef)

    Architecture defintion, the context of the compilation



39
40
41
42
# File 'lib/idl.rb', line 39

def initialize(arch_def)
  @parser = IdlParser.new
  @arch_def = arch_def
end

Instance Method Details

#compile_expression(expression, symtab, pass_error: false) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/idl.rb', line 239

def compile_expression(expression, symtab, pass_error: false)
  m = @parser.parse(expression, root: :expression)
  if m.nil?
    raise SyntaxError, <<~MSG
      While parsing #{expression}:#{@parser.failure_line}:#{@parser.failure_column}

      #{@parser.failure_reason}
    MSG
  end

  ast = m.to_ast
  ast.set_input_file("[EXPRESSION]", 0)
  ast.freeze_tree(symtab)
  begin
    ast.type_check(symtab)
  rescue AstNode::TypeError => e
    raise e if pass_error

    warn "Compiling #{expression}"
    warn e.what
    warn e.backtrace
    exit 1
  rescue AstNode::InternalError => e
    raise e if pass_error

    warn "Compiling #{expression}"
    warn e.what
    warn e.backtrace
    exit 1
  end


  ast
end

#compile_file(path) ⇒ Object



44
45
46
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/idl.rb', line 44

def compile_file(path)
  @parser.set_input_file(path.to_s)

  m = @parser.parse path.read

  if m.nil?
    raise SyntaxError, <<~MSG
      While parsing #{@parser.input_file}:#{@parser.failure_line}:#{@parser.failure_column}

      #{@parser.failure_reason}
    MSG
  end

  raise "unexpected type #{m.class.name}" unless m.is_a?(IsaSyntaxNode)

  ast = m.to_ast

  ast.children.each do |child|
    next unless child.is_a?(IncludeStatementAst)

    if child.filename.empty?
      raise SyntaxError, <<~MSG
        While parsing #{path}:#{child.lineno}:

        Empty include statement
      MSG
    end

    include_path =
      if child.filename[0] == "/"
        Pathname.new(child.filename)
      else
        (path.dirname / child.filename)
      end

    unless include_path.exist?
      raise SyntaxError, <<~MSG
        While parsing #{path}:#{child.lineno}:

        Path #{include_path} does not exist
      MSG
    end
    unless include_path.readable?
      raise SyntaxError, <<~MSG
        While parsing #{path}:#{child.lineno}:

        Path #{include_path} cannot be read
      MSG
    end

    include_ast = compile_file(include_path)
    include_ast.set_input_file_unless_already_set(include_path)
    ast.replace_include!(child, include_ast)
  end

  # we may have already set an input file from an include, so only set it if it's not already set
  ast.set_input_file_unless_already_set(path.to_s)

  ast
end

#compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file: nil, input_line: 0, no_rescue: false, extra_syms: {}, type_check: true) ⇒ Ast

compile a function body, and return the abstract syntax tree

Parameters:

  • body (String)

    Function body source code

  • return_type (Type) (defaults to: nil)

    Expected return type, if known

  • symtab (SymbolTable) (defaults to: nil)

    Symbol table to use for type checking

  • name (String) (defaults to: nil)

    Function name, used for error messages

  • input_file (Pathname) (defaults to: nil)

    Path to the input file this source comes from

  • input_line (Integer) (defaults to: 0)

    Starting line in the input file that this source comes from

  • no_rescue (Boolean) (defaults to: false)

    Whether or not to automatically catch any errors

Returns:

  • (Ast)

    The root of the abstract syntax tree



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/idl.rb', line 115

def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file: nil, input_line: 0, no_rescue: false, extra_syms: {}, type_check: true)
  @parser.set_input_file(input_file, input_line)

  m = @parser.parse(body, root: :function_body)
  if m.nil?
    raise SyntaxError, <<~MSG
      While parsing #{name} at #{input_file}:#{input_line + @parser.failure_line}

      #{@parser.failure_reason}
    MSG
  end

  # fix up left recursion
  ast = m.to_ast
  ast.set_input_file(input_file, input_line)
  ast.freeze_tree(symtab)

  # type check
  unless type_check == false
    cloned_symtab = symtab.deep_clone

    cloned_symtab.push(ast)
    cloned_symtab.add("__expected_return_type", return_type) unless return_type.nil?

    extra_syms.each { |k, v|
      cloned_symtab.add(k, v)
    }

    begin
      ast.statements.each do |s|
        s.type_check(cloned_symtab)
      end
    rescue AstNode::TypeError => e
      raise e if no_rescue

      if name && parent
        warn "In function #{name} of #{parent}:"
      elsif name && parent.nil?
        warn "In function #{name}:"
      end
      warn e.what
      exit 1
    rescue AstNode::InternalError => e
      raise if no_rescue

      if name && parent
        warn "In function #{name} of #{parent}:"
      elsif name && parent.nil?
        warn "In function #{name}:"
      end
      warn e.what
      warn e.backtrace
      exit 1
    ensure
      cloned_symtab.pop
    end

  end

  ast
end

#compile_inst_operation(inst, symtab:, input_file: nil, input_line: 0) ⇒ Ast

compile an instruction operation, and return the abstract syntax tree

Parameters:

  • inst (Instruction)

    Instruction object

  • symtab (SymbolTable)

    Symbol table

  • input_file (Pathname) (defaults to: nil)

    Path to the input file this source comes from

  • input_line (Integer) (defaults to: 0)

    Starting line in the input file that this source comes from

Returns:

  • (Ast)

    The root of the abstract syntax tree



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/idl.rb', line 184

def compile_inst_operation(inst, symtab:, input_file: nil, input_line: 0)
  operation = inst["operation()"]
  @parser.set_input_file(input_file, input_line)

  m = @parser.parse(operation, root: :instruction_operation)
  if m.nil?
    raise SyntaxError, <<~MSG
      While parsing #{input_file}:#{input_line + @parser.failure_line}

      #{@parser.failure_reason}
    MSG
  end

  # fix up left recursion
  ast = m.to_ast
  ast.set_input_file(input_file, input_line)
  ast.freeze_tree(symtab)

  ast
end

#type_check(ast, symtab, what) ⇒ Object

Type check an abstract syntax tree

Parameters:

  • ast (AstNode)

    An abstract syntax tree

  • symtab (SymbolTable)

    The compilation context

  • what (String)

    A description of what you are type checking (for error messages)

Raises:

  • AstNode::TypeError if a type error is found



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/idl.rb', line 211

def type_check(ast, symtab, what)
  # type check
  raise "Tree should be frozen" unless ast.frozen?

  begin
    value_result = AstNode.value_try do
      ast.type_check(symtab)
    end
    AstNode.value_else(value_result) do
      warn "While type checking #{what}, got a value error on:"
      warn ast.text_value
      warn AstNode.value_error_reason
      warn symtab.callstack
      unless AstNode.value_error_ast.nil?
        warn "At #{AstNode.value_error_ast.input_file}:#{AstNode.value_error_ast.lineno}"
      end
      exit 1
    end
  rescue AstNode::InternalError => e
    warn "While type checking #{what}:"
    warn e.what
    warn e.backtrace
    exit 1
  end

  ast
end