idlc 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/bin/idlc +10 -0
- data/lib/idlc/ast.rb +9611 -0
- data/lib/idlc/ast_decl.rb +22 -0
- data/lib/idlc/cli.rb +232 -0
- data/lib/idlc/idl.treetop +675 -0
- data/lib/idlc/idl_parser.rb +15386 -0
- data/lib/idlc/interfaces.rb +135 -0
- data/lib/idlc/log.rb +23 -0
- data/lib/idlc/passes/find_referenced_csrs.rb +39 -0
- data/lib/idlc/passes/find_return_values.rb +76 -0
- data/lib/idlc/passes/find_src_registers.rb +125 -0
- data/lib/idlc/passes/gen_adoc.rb +355 -0
- data/lib/idlc/passes/gen_option_adoc.rb +169 -0
- data/lib/idlc/passes/prune.rb +957 -0
- data/lib/idlc/passes/reachable_exceptions.rb +206 -0
- data/lib/idlc/passes/reachable_functions.rb +221 -0
- data/lib/idlc/symbol_table.rb +549 -0
- data/lib/idlc/syntax_node.rb +64 -0
- data/lib/idlc/type.rb +992 -0
- data/lib/idlc/version.rb +10 -0
- data/lib/idlc.rb +409 -0
- metadata +394 -0
data/lib/idlc/version.rb
ADDED
data/lib/idlc.rb
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
|
2
|
+
# SPDX-License-Identifier: BSD-3-Clause-Clear
|
|
3
|
+
|
|
4
|
+
# typed: true
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
require "pathname"
|
|
8
|
+
require "sorbet-runtime"
|
|
9
|
+
require "treetop"
|
|
10
|
+
|
|
11
|
+
require_relative "idlc/syntax_node"
|
|
12
|
+
|
|
13
|
+
class IdlParser < Treetop::Runtime::CompiledParser
|
|
14
|
+
attr_reader :input_file
|
|
15
|
+
|
|
16
|
+
def set_input_file(filename, starting_line = 0, starting_offset = 0, line_file_offsets = nil)
|
|
17
|
+
@input_file = filename
|
|
18
|
+
@starting_line = starting_line
|
|
19
|
+
@starting_offset = starting_offset
|
|
20
|
+
@line_file_offsets = line_file_offsets
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# alias instantiate_node so we can call it from the override
|
|
24
|
+
alias idlc_instantiate_node instantiate_node
|
|
25
|
+
|
|
26
|
+
# override instatiate_node so we can set the input file
|
|
27
|
+
def instantiate_node(node_type, *args)
|
|
28
|
+
node = T.unsafe(self).idlc_instantiate_node(node_type, *args)
|
|
29
|
+
node.set_input_file(input_file, @starting_line.nil? ? 0 : @starting_line, @starting_offset.nil? ? 0 : @starting_offset, @line_file_offsets)
|
|
30
|
+
node
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# rebuild the idl parser if the grammar has changed
|
|
35
|
+
tt_path = Pathname.new(__dir__) / "idlc" / "idl.treetop"
|
|
36
|
+
rb_path = Pathname.new(__dir__) / "idlc" / "idl_parser.rb"
|
|
37
|
+
needs_grammar_recompile = !rb_path.exist? || (rb_path.mtime < tt_path.mtime)
|
|
38
|
+
if needs_grammar_recompile
|
|
39
|
+
tt_compiler = Treetop::Compiler::GrammarCompiler.new
|
|
40
|
+
tt_compiler.compile(tt_path, rb_path)
|
|
41
|
+
|
|
42
|
+
# make sure there is a single newline at the end
|
|
43
|
+
compiler_src = File.read rb_path
|
|
44
|
+
File.write rb_path, "#{compiler_src.strip}\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
require_relative "idlc/ast"
|
|
48
|
+
require_relative "idlc/symbol_table"
|
|
49
|
+
require_relative "idlc/idl_parser"
|
|
50
|
+
|
|
51
|
+
module Idl
|
|
52
|
+
# the Idl compiler
|
|
53
|
+
class Compiler
|
|
54
|
+
extend T::Sig
|
|
55
|
+
|
|
56
|
+
attr_reader :parser
|
|
57
|
+
|
|
58
|
+
def initialize
|
|
59
|
+
@parser = ::IdlParser.new
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# set a progressbar
|
|
63
|
+
def pb=(pb)
|
|
64
|
+
@pb = pb
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# unset a progressbar
|
|
68
|
+
def unset_pb
|
|
69
|
+
@pb.finish unless @pb.nil?
|
|
70
|
+
@pb = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def compile_file(path, source_mapper = nil)
|
|
74
|
+
@parser.set_input_file(path.to_s)
|
|
75
|
+
|
|
76
|
+
unless source_mapper.nil?
|
|
77
|
+
source_mapper[path.to_s] = path.read
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
old_format = @pb.format unless @pb.nil?
|
|
81
|
+
@pb.format = "Parsing #{File.basename(path)} [:bar]" unless @pb.nil?
|
|
82
|
+
pid = unless @pb.nil?
|
|
83
|
+
fork {
|
|
84
|
+
loop do
|
|
85
|
+
sleep 1
|
|
86
|
+
@pb.advance unless @pb.nil?
|
|
87
|
+
end
|
|
88
|
+
}
|
|
89
|
+
end
|
|
90
|
+
m = @parser.parse path.read
|
|
91
|
+
unless @pb.nil?
|
|
92
|
+
Process.kill("TERM", T.must(pid))
|
|
93
|
+
Process.wait(T.must(pid))
|
|
94
|
+
@pb.format = old_format
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
if m.nil?
|
|
98
|
+
raise SyntaxError, <<~MSG
|
|
99
|
+
While parsing #{@parser.input_file}:#{@parser.failure_line}:#{@parser.failure_column}
|
|
100
|
+
|
|
101
|
+
#{@parser.failure_reason}
|
|
102
|
+
MSG
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise "unexpected type #{m.class.name}" unless m.is_a?(IsaSyntaxNode)
|
|
106
|
+
|
|
107
|
+
ast = m.to_ast
|
|
108
|
+
|
|
109
|
+
ast.children.each do |child|
|
|
110
|
+
next unless child.is_a?(IncludeStatementAst)
|
|
111
|
+
|
|
112
|
+
if child.filename.empty?
|
|
113
|
+
raise SyntaxError, <<~MSG
|
|
114
|
+
While parsing #{path}:#{child.lineno}:
|
|
115
|
+
|
|
116
|
+
Empty include statement
|
|
117
|
+
MSG
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
include_path =
|
|
121
|
+
if child.filename[0] == "/"
|
|
122
|
+
Pathname.new(child.filename)
|
|
123
|
+
else
|
|
124
|
+
(path.dirname / child.filename)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
unless include_path.exist?
|
|
128
|
+
raise SyntaxError, <<~MSG
|
|
129
|
+
While parsing #{path}:#{child.lineno}:
|
|
130
|
+
|
|
131
|
+
Path #{include_path} does not exist
|
|
132
|
+
MSG
|
|
133
|
+
end
|
|
134
|
+
unless include_path.readable?
|
|
135
|
+
raise SyntaxError, <<~MSG
|
|
136
|
+
While parsing #{path}:#{child.lineno}:
|
|
137
|
+
|
|
138
|
+
Path #{include_path} cannot be read
|
|
139
|
+
MSG
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
include_ast = compile_file(include_path)
|
|
143
|
+
include_ast.set_input_file_unless_already_set(include_path)
|
|
144
|
+
ast.replace_include!(child, include_ast)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# we may have already set an input file from an include, so only set it if it's not already set
|
|
148
|
+
ast.set_input_file_unless_already_set(path.to_s)
|
|
149
|
+
|
|
150
|
+
ast
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
sig { params(loop: String, symtab: SymbolTable, pass_error: T::Boolean).returns(ForLoopAst) }
|
|
154
|
+
def compile_for_loop(loop, symtab, pass_error: false)
|
|
155
|
+
m = @parser.parse(loop, root: :for_loop)
|
|
156
|
+
if m.nil?
|
|
157
|
+
raise SyntaxError, <<~MSG
|
|
158
|
+
While parsing #{loop}:#{@parser.failure_line}:#{@parser.failure_column}
|
|
159
|
+
|
|
160
|
+
#{@parser.failure_reason}
|
|
161
|
+
MSG
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
ast = m.to_ast
|
|
165
|
+
ast.set_input_file("[LOOP]", 0)
|
|
166
|
+
value_result = ast.value_try do
|
|
167
|
+
ast.freeze_tree(symtab)
|
|
168
|
+
end
|
|
169
|
+
if value_result == :unknown_value
|
|
170
|
+
raise AstNode::TypeError, "Bad literal value" if pass_error
|
|
171
|
+
|
|
172
|
+
warn "Compiling #{loop}"
|
|
173
|
+
warn "Bad literal value"
|
|
174
|
+
exit 1
|
|
175
|
+
end
|
|
176
|
+
begin
|
|
177
|
+
ast.type_check(symtab, strict: false)
|
|
178
|
+
rescue AstNode::TypeError => e
|
|
179
|
+
raise e if pass_error
|
|
180
|
+
|
|
181
|
+
warn "Compiling #{loop}"
|
|
182
|
+
warn e.what
|
|
183
|
+
warn T.must(e.backtrace).join("\n")
|
|
184
|
+
exit 1
|
|
185
|
+
rescue AstNode::InternalError => e
|
|
186
|
+
raise e if pass_error
|
|
187
|
+
|
|
188
|
+
warn "Compiling #{loop}"
|
|
189
|
+
warn e.what
|
|
190
|
+
warn T.must(e.backtrace).join("\n")
|
|
191
|
+
exit 1
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
ast
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# compile a function body, and return the abstract syntax tree
|
|
198
|
+
#
|
|
199
|
+
# @param body [String] Function body source code
|
|
200
|
+
# @param return_type [Type] Expected return type, if known
|
|
201
|
+
# @param symtab [SymbolTable] Symbol table to use for type checking
|
|
202
|
+
# @param name [String] Function name, used for error messages
|
|
203
|
+
# @param input_file [Pathname] Path to the input file this source comes from
|
|
204
|
+
# @param input_line [Integer] Starting line in the input file that this source comes from
|
|
205
|
+
# @param no_rescue [Boolean] Whether or not to automatically catch any errors
|
|
206
|
+
# @return [Ast] The root of the abstract syntax tree
|
|
207
|
+
def compile_func_body(body, return_type: nil, symtab: nil, name: nil, input_file: nil, input_line: 0, starting_offset: 0, line_file_offsets: nil, no_rescue: false, extra_syms: {}, type_check: true)
|
|
208
|
+
@parser.set_input_file(input_file, input_line, starting_offset, line_file_offsets)
|
|
209
|
+
|
|
210
|
+
m = @parser.parse(body, root: :function_body)
|
|
211
|
+
if m.nil?
|
|
212
|
+
unless input_file.nil? || input_line.nil?
|
|
213
|
+
raise SyntaxError, <<~MSG
|
|
214
|
+
While parsing #{name} at #{input_file}:#{input_line + @parser.failure_line}
|
|
215
|
+
|
|
216
|
+
#{@parser.failure_reason}
|
|
217
|
+
MSG
|
|
218
|
+
else
|
|
219
|
+
raise SyntaxError, <<~MSG
|
|
220
|
+
While parsing #{name}
|
|
221
|
+
|
|
222
|
+
#{@parser.failure_reason}
|
|
223
|
+
MSG
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# fix up left recursion
|
|
228
|
+
ast = m.to_ast
|
|
229
|
+
ast.set_input_file(input_file, input_line, starting_offset, line_file_offsets)
|
|
230
|
+
ast.freeze_tree(symtab)
|
|
231
|
+
|
|
232
|
+
# type check
|
|
233
|
+
unless type_check == false
|
|
234
|
+
cloned_symtab = symtab.deep_clone
|
|
235
|
+
|
|
236
|
+
cloned_symtab.push(ast)
|
|
237
|
+
cloned_symtab.add("__expected_return_type", return_type) unless return_type.nil?
|
|
238
|
+
|
|
239
|
+
extra_syms.each { |k, v|
|
|
240
|
+
cloned_symtab.add(k, v)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
begin
|
|
244
|
+
ast.statements.each do |s|
|
|
245
|
+
s.type_check(cloned_symtab, strict: false)
|
|
246
|
+
end
|
|
247
|
+
rescue AstNode::TypeError => e
|
|
248
|
+
raise e if no_rescue
|
|
249
|
+
|
|
250
|
+
warn "In function #{name}:"
|
|
251
|
+
warn e.what
|
|
252
|
+
exit 1
|
|
253
|
+
rescue AstNode::InternalError => e
|
|
254
|
+
raise if no_rescue
|
|
255
|
+
|
|
256
|
+
warn "In function #{name}:"
|
|
257
|
+
warn e.what
|
|
258
|
+
warn T.must(e.backtrace).join("\n")
|
|
259
|
+
exit 1
|
|
260
|
+
ensure
|
|
261
|
+
cloned_symtab.pop
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
ast
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def compile_inst_scope(idl, symtab:, input_file:, input_line: 0, starting_offset: 0, line_file_offsets: nil)
|
|
270
|
+
@parser.set_input_file(input_file, input_line, starting_offset, line_file_offsets)
|
|
271
|
+
|
|
272
|
+
m = @parser.parse(idl, root: :instruction_operation)
|
|
273
|
+
if m.nil?
|
|
274
|
+
raise SyntaxError, <<~MSG
|
|
275
|
+
While parsing #{input_file}:#{input_line + @parser.failure_line}
|
|
276
|
+
|
|
277
|
+
#{@parser.failure_reason}
|
|
278
|
+
MSG
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# fix up left recursion
|
|
282
|
+
ast = m.to_ast
|
|
283
|
+
ast.set_input_file(input_file, input_line, starting_offset, line_file_offsets)
|
|
284
|
+
ast.freeze_tree(symtab)
|
|
285
|
+
|
|
286
|
+
ast
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# compile an instruction operation, and return the abstract syntax tree
|
|
290
|
+
#
|
|
291
|
+
# @param inst [Instruction] Instruction object
|
|
292
|
+
# @param symtab [SymbolTable] Symbol table
|
|
293
|
+
# @param input_file [Pathname] Path to the input file this source comes from
|
|
294
|
+
# @param input_line [Integer] Starting line in the input file that this source comes from
|
|
295
|
+
# @return [Ast] The root of the abstract syntax tree
|
|
296
|
+
def compile_inst_operation(inst, symtab:, input_file: nil, input_line: 0, starting_offset: 0, line_file_offsets: nil)
|
|
297
|
+
operation = inst.data["operation()"]
|
|
298
|
+
compile_inst_scope(operation, symtab:, input_file:, input_line:, starting_offset:, line_file_offsets:)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Type check an abstract syntax tree
|
|
302
|
+
#
|
|
303
|
+
# @param ast [AstNode] An abstract syntax tree
|
|
304
|
+
# @param symtab [SymbolTable] The compilation context
|
|
305
|
+
# @param what [String] A description of what you are type checking (for error messages)
|
|
306
|
+
# @raise AstNode::TypeError if a type error is found
|
|
307
|
+
def type_check(ast, symtab, what)
|
|
308
|
+
# type check
|
|
309
|
+
raise "Tree should be frozen" unless ast.frozen?
|
|
310
|
+
|
|
311
|
+
begin
|
|
312
|
+
value_result = AstNode.value_try do
|
|
313
|
+
ast.type_check(symtab, strict: false)
|
|
314
|
+
end
|
|
315
|
+
AstNode.value_else(value_result) do
|
|
316
|
+
warn "While type checking #{what}, got a value error on:"
|
|
317
|
+
warn ast.text_value
|
|
318
|
+
warn AstNode.value_error_reason
|
|
319
|
+
warn symtab.callstack
|
|
320
|
+
unless AstNode.value_error_ast.nil?
|
|
321
|
+
warn "At #{AstNode.value_error_ast.input_file}:#{AstNode.value_error_ast.lineno}"
|
|
322
|
+
end
|
|
323
|
+
exit 1
|
|
324
|
+
end
|
|
325
|
+
rescue AstNode::InternalError => e
|
|
326
|
+
warn "While type checking #{what}:"
|
|
327
|
+
warn e.what
|
|
328
|
+
warn T.must(e.backtrace).join("\n")
|
|
329
|
+
exit 1
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
ast
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def compile_expression(expression, symtab, pass_error: false)
|
|
336
|
+
m = @parser.parse(expression, root: :expression)
|
|
337
|
+
if m.nil?
|
|
338
|
+
raise SyntaxError, <<~MSG
|
|
339
|
+
While parsing #{expression}:#{@parser.failure_line}:#{@parser.failure_column}
|
|
340
|
+
|
|
341
|
+
#{@parser.failure_reason}
|
|
342
|
+
MSG
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
ast = m.to_ast
|
|
346
|
+
ast.set_input_file("[EXPRESSION]", 0)
|
|
347
|
+
value_result = ast.value_try do
|
|
348
|
+
ast.freeze_tree(symtab)
|
|
349
|
+
end
|
|
350
|
+
if value_result == :unknown_value
|
|
351
|
+
raise AstNode::TypeError, "Bad literal value" if pass_error
|
|
352
|
+
|
|
353
|
+
warn "Compiling #{expression}"
|
|
354
|
+
warn "Bad literal value"
|
|
355
|
+
exit 1
|
|
356
|
+
end
|
|
357
|
+
begin
|
|
358
|
+
ast.type_check(symtab, strict: false)
|
|
359
|
+
rescue AstNode::TypeError => e
|
|
360
|
+
raise e if pass_error
|
|
361
|
+
|
|
362
|
+
warn "Compiling #{expression}"
|
|
363
|
+
warn e.what
|
|
364
|
+
warn T.must(e.backtrace).join("\n")
|
|
365
|
+
exit 1
|
|
366
|
+
rescue AstNode::InternalError => e
|
|
367
|
+
raise e if pass_error
|
|
368
|
+
|
|
369
|
+
warn "Compiling #{expression}"
|
|
370
|
+
warn e.what
|
|
371
|
+
warn T.must(e.backtrace).join("\n")
|
|
372
|
+
exit 1
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
ast
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
sig {
|
|
379
|
+
params(
|
|
380
|
+
body: String,
|
|
381
|
+
symtab: SymbolTable,
|
|
382
|
+
pass_error: T::Boolean,
|
|
383
|
+
input_file: T.nilable(T.any(String, Pathname)),
|
|
384
|
+
input_line: Integer,
|
|
385
|
+
starting_offset: Integer,
|
|
386
|
+
line_file_offsets: T.nilable(T::Array[Integer])
|
|
387
|
+
).returns(ConstraintBodyAst)
|
|
388
|
+
}
|
|
389
|
+
def compile_constraint(body, symtab, pass_error: false,
|
|
390
|
+
input_file: "[CONSTRAINT]", input_line: 0,
|
|
391
|
+
starting_offset: 0, line_file_offsets: nil)
|
|
392
|
+
m = @parser.parse(body, root: :constraint_body)
|
|
393
|
+
if m.nil?
|
|
394
|
+
raise SyntaxError, <<~MSG
|
|
395
|
+
While parsing #{body}:#{@parser.failure_line}:#{@parser.failure_column}
|
|
396
|
+
|
|
397
|
+
#{@parser.failure_reason}
|
|
398
|
+
MSG
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# fix up left recursion
|
|
402
|
+
ast = m.to_ast
|
|
403
|
+
ast.set_input_file(input_file, input_line, starting_offset, line_file_offsets)
|
|
404
|
+
ast.freeze_tree(symtab)
|
|
405
|
+
|
|
406
|
+
ast
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
end
|