yadriggy 1.0.0

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.
@@ -0,0 +1,11 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy'
4
+ require 'yadriggy/c/c'
5
+ require 'yadriggy/c/program'
6
+
7
+ module Yadriggy
8
+ # C language embedded in Ruby
9
+ module C
10
+ end
11
+ end
@@ -0,0 +1,220 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy'
4
+ require 'yadriggy/c/ctypecheck'
5
+ require 'yadriggy/c/codegen'
6
+ require 'yadriggy/c/ffi'
7
+ require 'yadriggy/c/config'
8
+
9
+ module Yadriggy
10
+ module C
11
+ # yadriggy/c/ffi.rb also defiens methods in this module.
12
+
13
+ # An error thrown during compilation.
14
+ #
15
+ class BuildError < RuntimeError
16
+ attr_accessor :all_messages
17
+
18
+ # @param [Array<String>] msg an array of strings.
19
+ def initialize(msg)
20
+ super(msg.empty? ? '' : msg.is_a?(String) ? msg : msg[0])
21
+ @all_messages = msg.is_a?(String) ? [msg] : msg
22
+ end
23
+ end
24
+
25
+ @syntax = Yadriggy::define_syntax do
26
+ expr <= Name | Number | Binary | Unary |
27
+ ConstPathRef | StringLiteral | ArrayRef |
28
+ Paren | typedecl | method_call
29
+ stmnt <= expr | Return | ForLoop | Loop | Conditional
30
+ exprs <= Exprs + { expressions: [ stmnt ] } | stmnt
31
+
32
+ Name <= {}
33
+ Number <= {}
34
+ SymbolLiteral <= nil
35
+ InstanceVariable <= {}
36
+ GlobalVariable <= nil
37
+ Reserved <= nil
38
+ Const <= Name
39
+ ConstPathRef <= { scope: (ConstPathRef | Const), name: Const }
40
+ StringLiteral <= {}
41
+ Paren <= { expression: expr }
42
+ Return <= { values: expr | nil } # only single return value
43
+ ForLoop <= {vars: Name, set: Dots, body: exprs }
44
+ Loop <= { op: Symbol, cond: expr, body: exprs }
45
+ Conditional <= { op: Symbol, cond: expr, then: exprs,
46
+ all_elsif: [expr * exprs], else: (exprs) }
47
+ method_call <= Call +
48
+ { receiver: (expr), op: (Symbol), name: Name,
49
+ args: [ expr ], block_arg: nil }
50
+
51
+ arrayof_name <= Identifier + { name: 'arrayof' }
52
+ arrayof <= Call + { receiver: nil, op: nil, name: arrayof_name,
53
+ args: [ expr ], block_arg: nil, block: nil }
54
+ typedecl_hash <= HashLiteral + { pairs: [ Label * label_value ] }
55
+ label_value <= Const | ConstPathRef | arrayof | StringLiteral
56
+ typedecl_name <= Identifier + { name: 'typedecl' }
57
+ typedecl <= Call +
58
+ { name: typedecl_name, args: [ typedecl_hash ] }
59
+
60
+ return_type <= Unary + { expr: Const | ConstPathRef | arrayof }
61
+ func_body <= return_type | stmnt |
62
+ Exprs + { expressions: [ (return_type), stmnt ] }
63
+
64
+ Parameters <= { params: [ Identifier ], optionals: nil,
65
+ rest_of_params: nil, params_after_rest: nil,
66
+ keywords: nil, rest_of_keywords: nil,
67
+ block_param: nil }
68
+ Block <= Parameters + { body: func_body }
69
+ Def <= Parameters + { singular: nil, name: Identifier,
70
+ body: func_body, rescue: nil }
71
+ Program <= { elements: exprs }
72
+ end
73
+
74
+ # @return [Syntax] the syntax.
75
+ def self.syntax
76
+ @syntax
77
+ end
78
+
79
+ # Compiles methods into binary code.
80
+ #
81
+ # @param [Proc|Method|UnboundMethod|Object] obj the exposed method
82
+ # or a block. If `obj` is neither a method or a block,
83
+ # all the public methods available on `obj` are exposed.
84
+ # The methods invoked by the exposed methods are also compiled.
85
+ # @param [String] lib_name the library name.
86
+ # @param [String] dir the directory name.
87
+ # @param [String] module_name the module name where the exposed methods
88
+ # are attached when the generated Ruby script is executed.
89
+ # If `method_name` is nil, no Ruby script is generated.
90
+ # @return [Module] the module object where the exposed methods
91
+ # are attached. It does not have a name.
92
+ def self.compile(obj, lib_name=nil, dir=Config::WorkDir, module_name=nil)
93
+ mod, funcs = compile0(obj, lib_name, dir, module_name,
94
+ ClangTypeChecker, CodeGen)[0]
95
+ mod
96
+ end
97
+
98
+ # @private
99
+ # @return [Pair<Module,Array<String>>]
100
+ def self.compile0(obj, lib_name, dir, module_name,
101
+ typechecker_class, gen_class)
102
+ begin
103
+ compile1(obj, lib_name, dir, module_name,
104
+ typechecker_class, gen_class)
105
+ rescue SyntaxError, CheckError, BuildError => err
106
+ raise err if Yadriggy.debug > 0
107
+ warn err.message
108
+ nil
109
+ end
110
+ end
111
+
112
+ # @private
113
+ # @return [Pair<Module,Array<String>>]
114
+ def self.compile1(obj, lib_name, dir, module_name,
115
+ typechecker_class, gen_class)
116
+ lib_name0 = obj.class.name
117
+ method_objs = if obj.is_a?(Proc) || obj.is_a?(Method) ||
118
+ obj.is_a?(UnboundMethod)
119
+ lib_name0 += obj.object_id.to_s(16)
120
+ [obj]
121
+ else
122
+ obj.public_methods(false).map do |name|
123
+ obj.method(name)
124
+ end
125
+ end
126
+ lib_name = lib_name0.gsub('::', '_').downcase if lib_name.nil?
127
+
128
+ raise BuildError.new('no methods specified') if method_objs.size < 1
129
+
130
+ checker = typechecker_class.new(@syntax)
131
+ pub_methods = compiled_methods(checker, method_objs)
132
+
133
+ dir += File::Separator unless dir.end_with?(File::Separator)
134
+ FileUtils.mkdir_p(dir)
135
+ printer = Yadriggy::FilePrinter.new(gen_class.c_src_file(dir, lib_name))
136
+ gen = gen_class.new(printer, checker, pub_methods)
137
+
138
+ generate_funcs(pub_methods[0], gen, printer)
139
+ gen.build_lib(lib_name, dir)
140
+
141
+ attach_funcs(pub_methods, checker, gen, module_name, lib_name, dir)
142
+ end
143
+
144
+ # @private
145
+ # @return [Array<ASTree>] the ASTs of compiled methods.
146
+ def self.compiled_methods(checker, method_objs)
147
+ ast = nil
148
+ pub_methods = method_objs.map do |mthd|
149
+ if ast.nil?
150
+ ast = Yadriggy::reify(mthd)
151
+ else
152
+ ast = ast.reify(mthd)
153
+ end
154
+
155
+ if ast.nil?
156
+ raise SyntaxError.new(
157
+ "cannot locate the source: #{mthd.name.to_s} in #{mthd.receiver.class}")
158
+ end
159
+
160
+ @syntax.raise_error unless @syntax.check(ast.tree)
161
+ checker.typecheck(ast.tree)
162
+ ast
163
+ end
164
+
165
+ return pub_methods
166
+ end
167
+
168
+ # @private
169
+ def self.generate_funcs(ast, gen, printer)
170
+ gen.name_global_variables
171
+ gen.headers
172
+ gen.variable_declarations
173
+ ast.astrees.each do |e|
174
+ gen.prototype(e.tree)
175
+ end
176
+ gen.preamble
177
+ ast.astrees.each do |e|
178
+ printer << :nl
179
+ gen.c_function(e.tree)
180
+ end
181
+
182
+ printer.output
183
+ printer.close
184
+
185
+ raise BuildError.new(gen.error_messages) if gen.errors?
186
+ end
187
+
188
+ # @private
189
+ # @return [Pair<Module,Array<String>>] the module where the methods
190
+ # are attached. The second element is method names.
191
+ def self.attach_funcs(pub_methods, checker, gen, module_name,
192
+ lib_name, dir)
193
+ func_names = pub_methods.map { |ast| gen.c_function_name(ast.tree) }
194
+ func_types = pub_methods.map { |ast| checker.type(ast.tree) }
195
+
196
+ func_names, func_types = gen.expand_functions(func_names, func_types)
197
+
198
+ unless module_name.nil?
199
+ make_attach_file(module_name, func_names, func_types,
200
+ lib_name, dir)
201
+ end
202
+
203
+ [attach(Module.new, func_names, func_types, lib_name, dir),
204
+ func_names]
205
+ end
206
+
207
+ # Compiles and runs a block.
208
+ #
209
+ # @param [String] lib_name the library name.
210
+ # @param [String] dir the directory name.
211
+ # @param [Object...] args the arguments to the block.
212
+ # @return [Object] the result of running the given block.
213
+ def self.run(lib_name=nil, *args, dir: Config::WorkDir, &block)
214
+ raise BuildError.new('no block given') if block.nil?
215
+ mod, mths = compile0(block, lib_name, dir, nil,
216
+ ClangTypeChecker, CodeGen)
217
+ mod.method(mths[0]).call(*args)
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,481 @@
1
+ # Copyright (C) 2017- Shigeru Chiba. All rights reserved.
2
+
3
+ require 'yadriggy/c/config'
4
+ require 'yadriggy/c/ffi'
5
+ require 'yadriggy/c/ctype'
6
+
7
+ module Yadriggy
8
+ module C
9
+
10
+ # C-code generator
11
+ #
12
+ # Since Checker implements Visitor pattern, use it for code
13
+ # generation.
14
+ #
15
+ class CodeGen < Checker
16
+ # printer is a Printer object.
17
+ # Only main_method is converted into an extern function.
18
+ # Other methods are converted into static functions.
19
+ # @param [Array<ASTree>] public_methods publicly exported methods.
20
+ def initialize(printer, typechecker, public_methods)
21
+ super()
22
+ @printer = printer
23
+ @typechecker = typechecker
24
+ @func_counter = 0
25
+ @func_names = {}
26
+ @nerrors = 0
27
+ @messages = []
28
+ @public_methods = {}
29
+ public_methods.each {|m| @public_methods[m.tree] = m.tree }
30
+ @gvariables = {}
31
+ end
32
+
33
+ # Tests whether a type error was found.
34
+ #
35
+ def errors?
36
+ @nerrors > 0
37
+ end
38
+
39
+ # Gets an array of error messages.
40
+ #
41
+ def error_messages
42
+ @messages
43
+ end
44
+
45
+ # Gets the type checker.
46
+ #
47
+ def typechecker
48
+ @typechecker
49
+ end
50
+
51
+ # Gets the printer given when object construction.
52
+ #
53
+ def printer
54
+ @printer
55
+ end
56
+
57
+ rule(:typedecl) do
58
+ # nothing to be printed
59
+ end
60
+
61
+ rule(:return_type) do
62
+ # nothing to be printed
63
+ end
64
+
65
+ rule(Number) do
66
+ @printer << ast.value.to_s
67
+ end
68
+
69
+ rule(Name) do
70
+ @printer << ast.name
71
+ end
72
+
73
+ rule(IdentifierOrCall) do
74
+ t = @typechecker.type(ast)
75
+ rt = ResultType.role(t)
76
+ unless rt.nil?
77
+ mdef = rt.method_def
78
+ @printer << c_function_name(mdef) << '()'
79
+ else
80
+ it = InstanceType.role(t)
81
+ unless it.nil?
82
+ @printer << it.object
83
+ else
84
+ @printer << ast.name
85
+ end
86
+ end
87
+ end
88
+
89
+ rule(Const) do
90
+ t = @typechecker.type(ast)
91
+ @printer << InstanceType.role(t)&.object
92
+ end
93
+
94
+ rule(ConstPathRef) do
95
+ t = @typechecker.type(ast)
96
+ @printer << InstanceType.role(t)&.object
97
+ end
98
+
99
+ rule(InstanceVariable) do
100
+ t = @typechecker.type(ast)
101
+ vname = @gvariables[InstanceType.role(t)&.object]
102
+ error(ast, 'unknown instance variable') if vname.nil?
103
+ @printer << vname
104
+ end
105
+
106
+ rule(Exprs) do
107
+ ast.expressions.map do |e|
108
+ check(e)
109
+ unless e.is_a?(Conditional) ||
110
+ e.is_a?(Loop) || e.is_a?(ForLoop)
111
+ @printer << ';' << :nl
112
+ end
113
+ end
114
+ end
115
+
116
+ rule(ArrayLiteral) do
117
+ @printer << ' { '
118
+ ast.elements.map do |e|
119
+ check(e)
120
+ @printer << ', '
121
+ end
122
+ @printer << ' } '
123
+ end
124
+
125
+ rule(StringLiteral) do
126
+ @printer << '"' << ast.value.gsub(/\n/, '\\n') << '"'
127
+ end
128
+
129
+ rule(Paren) do
130
+ @printer << '('
131
+ check(ast.expression)
132
+ @printer << ')'
133
+ end
134
+
135
+ rule(Unary) do
136
+ @printer << ast.real_operator.to_s
137
+ check(ast.expr)
138
+ end
139
+
140
+ rule(Binary) do
141
+ check(ast.left)
142
+ @printer << ' ' << ast.op.to_s << ' '
143
+ check(ast.right)
144
+ end
145
+
146
+ rule(ArrayRef) do
147
+ check(ast.array)
148
+ ast.indexes.each do |idx|
149
+ @printer << '['
150
+ check(idx)
151
+ @printer << ']'
152
+ end
153
+ end
154
+
155
+ rule(Dots) do
156
+ error(expr, 'a range object is not available')
157
+ end
158
+
159
+ rule(Call) do
160
+ if @typechecker.method_with_block?(ast.name.name)
161
+ call_with_block(ast)
162
+ else
163
+ t = @typechecker.type(ast)
164
+ mdef = ResultType.role(t).method_def
165
+
166
+ if mdef.nil?
167
+ @printer << ast.name.name << '('
168
+ else
169
+ @printer << c_function_name(mdef) << '('
170
+ end
171
+
172
+ ast.args.each_with_index do |e, i|
173
+ @printer << ', ' if i > 0
174
+ check(e)
175
+ end
176
+ @printer << ')'
177
+ end
178
+ end
179
+
180
+ def call_with_block(call_ast)
181
+ loop_param = call_ast.block.params[0]
182
+ @printer << 'for (' << c_type(RubyClass::Integer) << ' '
183
+ check(loop_param)
184
+ @printer << ' = ('
185
+ check(ast.receiver)
186
+ @printer << ') - 1; '
187
+ check(loop_param)
188
+ @printer << ' >= 0; '
189
+ check(loop_param)
190
+ @printer << '--) {'
191
+ @printer.down
192
+ local_var_declarations(ast.block)
193
+ check(ast.block.body)
194
+ @printer << ';' unless ast.block.body.is_a?(Exprs)
195
+ @printer.up
196
+ @printer << '}' << :nl
197
+ end
198
+
199
+ rule(Conditional) do
200
+ case ast.op
201
+ when :unless, :unless_mod
202
+ error(ast, "a bad control statement")
203
+ when :ifop
204
+ @printer << '('
205
+ check(ast.cond)
206
+ @printer << ') ? ('
207
+ check(ast.then)
208
+ @printer << ') : ('
209
+ check(ast.else)
210
+ @printer << ')'
211
+ else
212
+ @printer << "if ("
213
+ check(ast.cond)
214
+ @printer << ') {'
215
+ @printer.down
216
+ check(ast.then)
217
+ @printer << ';' unless ast.then.is_a?(Exprs)
218
+ @printer.up
219
+ unless ast.else.nil?
220
+ @printer << '} else {'
221
+ @printer.down
222
+ check(ast.else)
223
+ @printer << ';' unless ast.else.is_a?(Exprs)
224
+ @printer.up
225
+ end
226
+ @printer << '}' << :nl
227
+ end
228
+ end
229
+
230
+ rule(Loop) do
231
+ case ast.op
232
+ when :until, :while_mod, :until_mod
233
+ error(ast, "#{ast.op} is not available")
234
+ else
235
+ @printer << 'while ('
236
+ check(ast.cond)
237
+ @printer << ') {'
238
+ @printer.down
239
+ check(ast.body)
240
+ @printer << ';' unless ast.body.is_a?(Exprs)
241
+ @printer.up
242
+ @printer << '}' << :nl
243
+ end
244
+ end
245
+
246
+ rule(ForLoop) do
247
+ var_name = ast.vars[0].name
248
+ @printer << 'for (' << var_name << ' = '
249
+ check(ast.set.left)
250
+ @printer << '; ' << var_name
251
+ if ast.set.op == :'...'
252
+ @printer << ' < '
253
+ else
254
+ @printer << ' <= '
255
+ end
256
+ check(ast.set.right)
257
+ @printer << '; ++' << var_name << ') {' << :nl
258
+ @printer.down
259
+ check(ast.body)
260
+ @printer << ';' unless ast.body.is_a?(Exprs)
261
+ @printer.up
262
+ @printer << '}' << :nl
263
+ end
264
+
265
+ rule(Return) do
266
+ @printer << 'return '
267
+ check(ast.values[0]) # ast.values.size is < 2.
268
+ end
269
+
270
+ rule(Block) do
271
+ def_function(ast, c_function_name(ast))
272
+ end
273
+
274
+ rule(Def) do
275
+ def_function(ast, c_function_name(ast))
276
+ end
277
+
278
+ # Obtains the file name of the generated source code.
279
+ # A subclass can redefine this method.
280
+ #
281
+ # @param [String] dir a directory name.
282
+ # @param [String] lib_name a library name.
283
+ # @return [String] a source file name.
284
+ def self.c_src_file(dir, lib_name)
285
+ "#{dir}#{lib_name}.c"
286
+ end
287
+
288
+ # Runs a compiler.
289
+ # A subclass can redefine this method.
290
+ #
291
+ # @param [String] lib_name a library name.
292
+ # @param [String] dir a directory name.
293
+ # @return [void]
294
+ def build_lib(lib_name, dir='./')
295
+ file_name = self.class.c_src_file(dir, lib_name)
296
+ lib_file_name = "#{dir}lib#{lib_name}#{Config::LibExtension}"
297
+ cmd = "#{build_cmd} #{Config::CoptOutput}#{lib_file_name} #{file_name}"
298
+ system(cmd)
299
+ status = $?.exitstatus
300
+ raise BuildError.new(["exit #{status}"]) if status > 0
301
+ end
302
+
303
+ # Obtains compiler command.
304
+ # A subclass can redefine this method.
305
+ #
306
+ # @return [String] command.
307
+ def build_cmd
308
+ Config::Compiler
309
+ end
310
+
311
+ # Prints `#include` derectives.
312
+ #
313
+ # @return [void]
314
+ def headers()
315
+ Config::Headers.each {|h| @printer << h << :nl }
316
+ @printer << :nl
317
+ end
318
+
319
+ # Gives a name to each global variable.
320
+ # @return [void]
321
+ def name_global_variables()
322
+ id = 0
323
+ @typechecker.instance_variables.each do |obj|
324
+ @gvariables[obj] = "_gvar_#{id}_"
325
+ id += 1
326
+ end
327
+ end
328
+
329
+ # Prints variable declarations.
330
+ # @return [void]
331
+ def variable_declarations()
332
+ @gvariables.each do |obj, name|
333
+ if obj.is_a?(CType::CArray)
334
+ @printer << 'static ' << c_type(obj.type) << ' ' << name
335
+ obj.sizes.each {|s| @printer << '[' << s << ']' }
336
+ @printer << ';' << :nl
337
+ end
338
+ end
339
+ @printer << :nl
340
+ end
341
+
342
+ # Prints a function prototype.
343
+ #
344
+ # @param [Def|Block] expr the function.
345
+ # @return [void]
346
+ def prototype(expr)
347
+ t = @typechecker.type(expr)
348
+ return if ForeignMethodType.role(t)
349
+
350
+ @printer << 'static ' if @public_methods[expr].nil?
351
+
352
+ fname_str = c_function_name(expr)
353
+ mt = MethodType.role(t)
354
+ if mt
355
+ parameters(expr, fname_str, mt)
356
+ @printer << ';' << :nl
357
+ else
358
+ error(expr, "bad method #{fname_str}")
359
+ end
360
+ self
361
+ end
362
+
363
+ # Prints a preamble. This method is invoked right after printing
364
+ # function prototypes. A subclass can override this method.
365
+ # The original implementation does not print anything.
366
+ #
367
+ def preamble
368
+ end
369
+
370
+ # Appends implicitly generated functions.
371
+ # A subclass can override this method.
372
+ # The original implementation does not append any.
373
+ #
374
+ # @param [Array<String>] func_names the names of the generated
375
+ # functions.
376
+ # @param [Array<Type>] func_names the types of the original methods.
377
+ # @return [Array<String>, Array<Type>] the names and types.
378
+ def expand_functions(func_names, func_types)
379
+ return func_names, func_types
380
+ end
381
+
382
+ # Prints a function implementation.
383
+ #
384
+ # @param [Def|Block] expr the function.
385
+ # @return [void]
386
+ def c_function(expr)
387
+ check(expr)
388
+ end
389
+
390
+ # Gets the function name in C after the translation from a Ruby
391
+ # method into a C function.
392
+ #
393
+ # @param [Block|Def|Call] expr an expression.
394
+ # @return [String] the function name for `expr`.
395
+ def c_function_name(expr)
396
+ return expr.name.name if expr.is_a?(Def) &&
397
+ @public_methods.include?(expr)
398
+
399
+ fname_str = @func_names[expr]
400
+ if fname_str.nil?
401
+ @func_counter += 1
402
+ fname_str = if expr.is_a?(Block)
403
+ "yadriggy_blk#{@func_counter}"
404
+ else
405
+ "#{expr.name.name}_#{@func_counter}"
406
+ end
407
+ @func_names[expr] = fname_str
408
+ end
409
+ fname_str
410
+ end
411
+
412
+ private
413
+
414
+ def def_function(expr, fname)
415
+ t = @typechecker.type(expr)
416
+ return if ForeignMethodType.role(t)
417
+
418
+ @printer << 'static ' if @public_methods[expr].nil?
419
+
420
+ mt = MethodType.role(t)
421
+ if mt
422
+ parameters(expr, fname, mt)
423
+ else
424
+ error(expr, 'not a function')
425
+ end
426
+
427
+ @printer << ' {'
428
+ @printer.down
429
+ native_t = NativeMethodType.role(t)
430
+ if native_t
431
+ @printer << native_t.body
432
+ else
433
+ local_var_declarations(expr)
434
+ check(expr.body)
435
+ @printer << ';' unless expr.body.is_a?(Exprs)
436
+ end
437
+ @printer.up
438
+ @printer << '}' << :nl
439
+ end
440
+
441
+ def parameters(expr, fname_str, mtype)
442
+ ret_type = mtype.result_type
443
+ @printer << c_type(ret_type) << ' '
444
+ @printer << fname_str << '('
445
+ param_types = mtype.params
446
+ if param_types.is_a?(Array)
447
+ expr.params.each_with_index do |p, i|
448
+ @printer << ', ' if i > 0
449
+ @printer << c_type(param_types[i]) << ' ' << p.name
450
+ end
451
+ else
452
+ error(expr, 'bad parameter types')
453
+ end
454
+ @printer << ')'
455
+ end
456
+
457
+ # @param [Def|Block] def_or_block
458
+ def local_var_declarations(def_or_block)
459
+ local_vars = @typechecker.local_vars_table[def_or_block]
460
+ if local_vars.nil?
461
+ error(def_or_block, 'bad function definition or block')
462
+ else
463
+ local_vars.each do |name, type|
464
+ @printer << c_type(type) << ' ' << name.to_s << ';' << :nl
465
+ end
466
+ end
467
+ end
468
+
469
+ # @param [Type] type
470
+ def c_type(type)
471
+ CFI::c_type_name(type)
472
+ end
473
+
474
+ def error(ast, msg)
475
+ @nerrors += 1
476
+ @messages << "#{ast.source_location_string}: #{msg}"
477
+ end
478
+
479
+ end # end of CodeGen
480
+ end
481
+ end