blob: 6163129dfab65abd5dfc527cc267bbb712c114be [file] [log] [blame]
brians343bc112013-02-10 01:53:46 +00001require 'digest'
2require 'fileutils'
3
4def javaify name
5 name = name.dup
6 name.gsub! /(\w)_(\w)/ do
7 $1 + $2.upcase
8 end
9 name.gsub /^\w/ do |char|
10 char.downcase
11 end
12end
13
14module Contents
15 class SyntaxError < Exception
16 end
17 class Tokenizer
18 def initialize file
19 @file = file
20 @token = ""
21 @lineno = 0
22 end
23 def filename
24 @file.path
25 end
26 def pop_char
27 if char = @hold_char
28 @hold_char = nil
29 return char
30 end
31 return @file.read(1)
32 end
33 def unpop_char char
34 @hold_char = char
35 end
36 def clear_comment
37 @hold_char = nil
38 @file.gets
39 @lineno += 1
40 end
41 def syntax_error error
42 filename = File.basename(@file.path)
43 line = @lineno + 1
44 raise SyntaxError, error + "\n from #{line} of #{filename}", caller
45 end
46 def item_missing item
47 syntax_error "expected \"#{item}\"! you missing something!?"
48 end
49 def peek_token
50 @peek_token = next_token
51 end
52 def next_token
53 if token = @peek_token
54 @peek_token = nil
55 return token
56 end
57 token = @token
58 while char = pop_char
59 if char == "\n"
60 @lineno += 1
61 end
62 if char == "/"
63 if pop_char == "/"
64 clear_comment
65 else
66 syntax_error("unexpected #{char.inspect}")
67 end
68 elsif char =~ /[\s\r\n]/
69 if token.length > 0
70 @token = ""
71 return token
72 end
73 elsif char =~ /[;\{\}]/
74 if token.length > 0
75 unpop_char char
76 @token = ""
77 return token
78 end
79 return(char)
80 elsif token.length > 0 && char =~ /[\w:]/
81 token += char
82 elsif char =~ /[a-zA-Z0-9]/
83 token = char
84 else
85 syntax_error("unexpected #{char.inspect}")
86 end
87 end
88 rescue EOFError
89 end
90 def self.is_string token
91 token =~ /[a-zA-Z]\w*/
92 end
93 def self.is_number token
94 token =~ /[0-9]*/
95 end
96 end
97
98 class Struct
99 class StructField
100 def initialize
101 @members = [] # array of strings
102 end
103 def parse tokenizer
104 while true
105 token = tokenizer.next_token
106 if Tokenizer.is_string(token)
107 @members.push token
108 elsif token == ";" || token == "\n"
109 if @members.length > 0
110 return @members
111 else
112 return nil
113 end
114 else
115 tokenizer.syntax_error("expected member name in struct!")
116 end
117 end
118 end
119 def self.parse *args
120 self.new.parse *args
121 end
122 def use members
123 members
124 end
125 def self.use *args
126 self.new.use *args
127 end
128 def to_s
129 @members.join " "
130 end
131 end
132
133 def parse tokenizer, parse_name = true
134 if parse_name
135 token = tokenizer.next_token
136 if Tokenizer.is_string(token)
137 @name_raw = token
138 else
139 tokenizer.syntax_error("expected struct name!")
140 end
141 else
142 @name_raw = nil
143 @name_data = tokenizer.filename
144 end
145 token = tokenizer.next_token
146 tokenizer.syntax_error("expected '{', got '#{token}'") if(token != "{")
147 while token != "}"
148 token = tokenizer.peek_token
149 if token != "}"
150 field = StructField.parse(tokenizer)
151 @fields.push(field) if(field)
152 end
153 end
154 if tokenizer.next_token == "}"
155 return self
156 else
157 tokenizer.syntax_error("wahh; call parker. #{__LINE__}")
158 end
159 end
160 def self.parse *args
161 self.new.parse *args
162 end
163
164 def use fields, name_data
165 @name_raw = nil
166 @name_data = name_data
167 fields.each do |field|
168 @fields.push(StructField.use field.split(' '))
169 end
170 self
171 end
172 def self.use *args
173 self.new.use *args
174 end
175
176 def name
177 @name_raw || gen_name
178 end
179 def gen_name
180 unless @generated_name
181 @generated_name = 'a' + Digest::SHA1.hexdigest(@fields.join('') + $namespace + @name_data)
182 end
183 @generated_name
184 end
185
186 def initialize
187 @fields = [] # array of arrays of strings
188 @hidden_fields = []
189 end
190 def upcase_name
191 name.gsub(/^[a-z]|_[a-z]/) do |v|
192 v[-1].chr.upcase
193 end
194 end
195 def join_fields array
196 (array.collect { |a|
197 " #{a.join(" ")};"
198 }).join("\n")
199 end
200 def fields
201 join_fields @fields
202 end
203 def hidden_fields
204 join_fields @hidden_fields
205 end
206 def add_hidden_field k, v
207 @hidden_fields.push [k, v]
208 end
209 def params
210 (@fields.collect do |a|
211 a.join(" ")
212 end).join(', ')
213 end
214 def copy_params_into varname, decl = true
215 (decl ? "#{name} #{varname};\n" : '') + (@fields.collect do |a|
216 "#{varname}.#{a[-1]} = #{a[-1]};"
217 end).join("\n")
218 end
219 def params_from name
220 (@fields.collect do |a|
221 name + '.' + a[-1]
222 end).join(', ')
223 end
224 def builder_name aos_namespace = true, this_namespace = true
225 (aos_namespace ? "aos::" : '') + "QueueBuilder<#{this_namespace ? $namespace + '::' : ''}#{name}>"
226 end
227 def java_builder
228 name + 'Builder'
229 end
230 def builder_defs name
231 (@fields.collect do |field|
232 " inline #{name} &#{field[-1]}" +
233 "(#{field[0...-1].join(" ")} in) " +
234 "{ holder_.View().#{field[-1]} = in; return *this; }"
235 end).join "\n"
236 end
237 def swig_builder_defs name
238 (@fields.collect do |field|
239 " %rename(#{javaify field[-1]}) #{field[-1]};\n" +
240 " #{name} &#{field[-1]}" +
241 "(#{field[0...-1].join(" ")} #{field[-1]});"
242 end).join "\n"
243 end
244 def zero name
245 (@fields.collect do |field|
246 " new (&#{name}.#{field[-1]}) #{field[0...-1].join ' '}();"
247 end).join("\n")
248 end
249 def size
250 (@fields.collect do |field|
251 "sizeof(#{$namespace}::#{name}::#{field[-1]})"
252 end.push('0')).join(' + ')
253 end
254 def get_format(field)
255 case(field[0...-1])
256 when ['int']
257 r = '%d'
258 when ['float'], ['double']
259 r = '%f'
260 when ['bool']
261 r = '%s'
262 when ['uint8_t']
263 r = '%hhu'
264 when ['uint16_t']
265 r = '%d'
266 when ['struct', 'timespec']
267 r = '%jdsec,%ldnsec'
268 else
269 return 'generator_error'
270 end
271 return field[-1] + ': ' + r
272 end
273 def to_printf(name, field)
274 case(field[0...-1])
275 when ['bool']
276 return name + '.' + field[-1] + ' ? "true" : "false"'
277 when ['uint16_t']
278 return "static_cast<int>(#{name}.#{field[-1]})"
279 when ['struct', 'timespec']
280 return "#{name}.#{field[-1]}.tv_sec, #{name}.#{field[-1]}.tv_nsec"
281 else
282 return name + '.' + field[-1]
283 end
284 end
285 def netop name, buffer
286 offset = '0'
287 (@fields.collect do |field|
288 # block |var_pointer, output_pointer|
289 val = yield "&#{name}.#{field[-1]}", "&#{buffer}[#{offset}]"
290 offset += " + sizeof(#{name}.#{field[-1]})"
291 ' ' + val
292 end).join("\n") + "\n " +
293 "static_assert(#{offset} == #{size}, \"code generator issues\");"
294 end
295 def hton name, output
296 netop(name, output) do |var, output|
297 "to_network(#{var}, #{output});"
298 end
299 end
300 def ntoh input, name
301 netop(name, input) do |var, input|
302 "to_host(#{input}, #{var});"
303 end
304 end
305 def swig_writer
306 <<END
307struct #{name} {
308#{(@fields.collect { |a|
309 " %rename(#{javaify a[-1]}) #{a[-1]};"
310}).join("\n")}
311#{self.fields}
312 %extend {
313 const char *toString() {
314 return aos::TypeOperator<#{$namespace}::#{name}>::Print(*$self);
315 }
316 }
317 private:
318 #{name}();
319};
320} // namespace #{$namespace}
321namespace aos {
322%typemap(jstype) #{builder_name false}& "#{java_builder}"
323%typemap(javaout) #{builder_name false}& {
324 $jnicall;
325 return this;
326 }
327template <> class #{builder_name false} {
328 private:
329 #{builder_name false}();
330 public:
331 inline bool Send();
332 %rename(#{javaify 'Send'}) Send;
333#{swig_builder_defs builder_name(false)}
334};
335%template(#{java_builder}) #{builder_name false};
336%typemap(javaout) #{builder_name false}& {
337 return new #{java_builder}($jnicall, false);
338 }
339} // namespace aos
340namespace #{$namespace} {
341END
342 end
343 def writer
344 <<END
345struct #{name} {
346#{self.fields}
347#{self.hidden_fields}
348};
349} // namespace #{$namespace}
350namespace aos {
351template <> class TypeOperator<#{$namespace}::#{name}> {
352 public:
353 static void Zero(#{$namespace}::#{name} &inst) {
354 (void)inst;
355#{zero 'inst'}
356 }
357 static void NToH(const char *input, #{$namespace}::#{name} &inst) {
358 (void)input;
359 (void)inst;
360#{ntoh 'input', 'inst'}
361 }
362 static void HToN(const #{$namespace}::#{name} &inst, char *output) {
363 (void)inst;
364 (void)output;
365#{hton 'inst', 'output'}
366 }
367 static inline size_t Size() { return #{size}; }
368 static const char *Print(const #{$namespace}::#{name} &inst) {
369#{@fields.empty? ? <<EMPTYEND : <<NOTEMPTYEND}
370 (void)inst;
371 return "";
372EMPTYEND
373 static char buf[1024];
374 if (snprintf(buf, sizeof(buf), "#{@fields.collect do |field|
375 get_format(field)
376 end.join(', ')}", #{@fields.collect do |field|
377 to_printf('inst', field)
378 end.join(', ')}) >= static_cast<ssize_t>(sizeof(buf))) {
379 LOG(WARNING, "#{name}'s buffer was too small\\n");
380 buf[sizeof(buf) - 1] = '\\0';
381 }
382 return buf;
383NOTEMPTYEND
384 }
385};
386template <> class #{builder_name false} {
387 private:
388 aos::QueueHolder<#{$namespace}::#{name}> &holder_;
389 public:
390 #{builder_name false}(aos::QueueHolder<#{$namespace}::#{name}> &holder) : holder_(holder) {}
391 inline bool Send() { return holder_.Send(); }
392 inline const char *Print() const { return holder_.Print(); }
393#{builder_defs builder_name(false)}
394};
395} // namespace aos
396namespace #{$namespace} {
397END
398 end
399 def to_s
400 return <<END
401#{name}: #{(@fields.collect {|n| n.join(" ") }).join("\n\t")}
402END
403 end
404 end
405
406 class SimpleField
407 def initialize check_function = :is_string
408 @check_function = check_function
409 @name = nil
410 end
411 def parse tokenizer
412 token = tokenizer.next_token
413 if Tokenizer.__send__ @check_function, token
414 @name = token
415 else
416 tokenizer.syntax_error('expected value!')
417 end
418 if tokenizer.next_token == ';'
419 @name
420 else
421 tokenizer.syntax_error('expected ";"!')
422 end
423 end
424 def self.parse tokenizer
425 self.new.parse tokenizer
426 end
427 end
428 class NameField < SimpleField
429 end
430
431 class OutputFile
432 def initialize namespace, filename, topdir, outpath
433 @namespace = namespace
434 $namespace = namespace
435 @base = filename.gsub(/\.\w*$/, "").gsub(/^.*\//, '')
436 @topdir = topdir
437 @filebase = outpath + @base
438 @filename = filename
439 FileUtils.mkdir_p(outpath)
440
441 fillin_initials if respond_to? :fillin_initials
442 parse filename
443 fillin_defaults if respond_to? :fillin_defaults
444 self
445 rescue SyntaxError => e
446 puts e
447 exit 1
448 end
449 def filename type
450 case type
451 when 'h'
452 @filebase + '.q.h'
453 when 'cc'
454 @filebase + '.q.cc'
455 when 'main'
456 @filebase + '_main.cc'
457 when 'swig'
458 @filebase + '.swg'
459 when 'java_dir'
460 @filebase + '_java/'
461 when 'java_wrap'
462 @filebase + '_java_wrap.cc'
463 else
464 throw SyntaxError, "unknown filetype '#{type}'"
465 end
466 end
467 def parse filename
468 file = File.open filename
469 tokenizer = Tokenizer.new file
470 while token = tokenizer.next_token
471 if !token || token.gsub('\s', '').empty?
472 elsif token == ';'
473 else
474 error = catch :syntax_error do
475 case token
476 when 'namespace'
477 $namespace = NameField.parse tokenizer
478 else
479 parse_token token, tokenizer
480 end
481 nil
482 end
483 if error
484 tokenizer.syntax_error error.to_s
485 raise error
486 end
487 end
488 end
489
490 check_format tokenizer
491 end
492 def call_swig
493 output_dir = filename('java_dir') + $namespace
494 FileUtils.mkdir_p(output_dir)
495 if (!system('swig', '-c++', '-Wall', '-Wextra', '-java',
496 '-package', $namespace, "-I#{@topdir}",
497 '-o', filename('java_wrap'),
498 '-outdir', output_dir, filename('swig')))
499 exit $?.to_i
500 end
501 end
502
503 def queue_holder_accessors suffix, var, type, force_timing = nil
504<<END
505 inline bool Get#{suffix}(#{force_timing ? '' : 'bool check_time'}) aos_check_rv { return #{var}.Get(#{force_timing || 'check_time'}); }
506 inline #{type} &View#{suffix}() { return #{var}.View(); }
507 inline void Clear#{suffix}() { #{var}.Clear(); }
508 inline bool Send#{suffix}() { return #{var}.Send(); }
509 inline const char *Print#{suffix}() { return #{var}.Print(); }
510 inline aos::QueueBuilder<#{type}> &#{suffix || 'Builder'}() { return #{var}.Builder(); }
511END
512 end
513 def queue_holder_accessors_swig suffix, var, type, force_timing = nil
514<<END
515 %rename(#{javaify "Get#{suffix}"}) Get#{suffix};
516 bool Get#{suffix}(#{force_timing ? '' : 'bool check_time'});
517 %rename(#{javaify "View#{suffix}"}) View#{suffix};
518 #{type} &View#{suffix}();
519 %rename(#{javaify "Clear#{suffix}"}) Clear#{suffix};
520 void Clear#{suffix}();
521 %rename(#{javaify "Send#{suffix}"}) Send#{suffix};
522 bool Send#{suffix}();
523 %rename(#{javaify suffix || 'Builder'}) #{suffix || 'Builder'};
524 aos::QueueBuilder<#{type}> &#{suffix || 'Builder'}();
525END
526 end
527 end
528end
529
530def write_file_out
531 if ARGV.length < 3
532 puts 'Error: at least 3 arguments required!!!'
533 exit 1
534 end
535 file = Contents::OutputFile.new ARGV.shift,
536 File.expand_path(ARGV.shift),
537 ARGV.shift,
538 File.expand_path(ARGV.shift) + '/'
539 while !ARGV.empty?
540 case type = ARGV.shift
541 when 'cpp'
542 file.write_cpp
543 when 'header'
544 file.write_header
545 when 'main'
546 file.write_main
547 when 'swig'
548 file.write_swig
549 file.call_swig
550 else
551 puts "Error: unknown output type '#{type}'"
552 exit 1
553 end
554 end
555end
556