brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 1 | require 'digest' |
| 2 | require 'fileutils' |
| 3 | |
| 4 | def 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 |
| 12 | end |
| 13 | |
| 14 | module 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 |
| 307 | struct #{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} |
| 321 | namespace aos { |
| 322 | %typemap(jstype) #{builder_name false}& "#{java_builder}" |
| 323 | %typemap(javaout) #{builder_name false}& { |
| 324 | $jnicall; |
| 325 | return this; |
| 326 | } |
| 327 | template <> 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 |
| 340 | namespace #{$namespace} { |
| 341 | END |
| 342 | end |
| 343 | def writer |
| 344 | <<END |
| 345 | struct #{name} { |
| 346 | #{self.fields} |
| 347 | #{self.hidden_fields} |
| 348 | }; |
| 349 | } // namespace #{$namespace} |
| 350 | namespace aos { |
| 351 | template <> 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 ""; |
| 372 | EMPTYEND |
| 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; |
| 383 | NOTEMPTYEND |
| 384 | } |
| 385 | }; |
| 386 | template <> 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 |
| 396 | namespace #{$namespace} { |
| 397 | END |
| 398 | end |
| 399 | def to_s |
| 400 | return <<END |
| 401 | #{name}: #{(@fields.collect {|n| n.join(" ") }).join("\n\t")} |
| 402 | END |
| 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(); } |
| 511 | END |
| 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'}(); |
| 525 | END |
| 526 | end |
| 527 | end |
| 528 | end |
| 529 | |
| 530 | def 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 |
| 555 | end |
| 556 | |