copied everything over from 2012 and removed all of the actual robot code except the drivetrain stuff
git-svn-id: https://robotics.mvla.net/svn/frc971/2013/trunk/src@4078 f308d9b7-e957-4cde-b6ac-9a88185e7312
diff --git a/aos/build/parser.rb b/aos/build/parser.rb
new file mode 100644
index 0000000..6163129
--- /dev/null
+++ b/aos/build/parser.rb
@@ -0,0 +1,556 @@
+require 'digest'
+require 'fileutils'
+
+def javaify name
+ name = name.dup
+ name.gsub! /(\w)_(\w)/ do
+ $1 + $2.upcase
+ end
+ name.gsub /^\w/ do |char|
+ char.downcase
+ end
+end
+
+module Contents
+ class SyntaxError < Exception
+ end
+ class Tokenizer
+ def initialize file
+ @file = file
+ @token = ""
+ @lineno = 0
+ end
+ def filename
+ @file.path
+ end
+ def pop_char
+ if char = @hold_char
+ @hold_char = nil
+ return char
+ end
+ return @file.read(1)
+ end
+ def unpop_char char
+ @hold_char = char
+ end
+ def clear_comment
+ @hold_char = nil
+ @file.gets
+ @lineno += 1
+ end
+ def syntax_error error
+ filename = File.basename(@file.path)
+ line = @lineno + 1
+ raise SyntaxError, error + "\n from #{line} of #{filename}", caller
+ end
+ def item_missing item
+ syntax_error "expected \"#{item}\"! you missing something!?"
+ end
+ def peek_token
+ @peek_token = next_token
+ end
+ def next_token
+ if token = @peek_token
+ @peek_token = nil
+ return token
+ end
+ token = @token
+ while char = pop_char
+ if char == "\n"
+ @lineno += 1
+ end
+ if char == "/"
+ if pop_char == "/"
+ clear_comment
+ else
+ syntax_error("unexpected #{char.inspect}")
+ end
+ elsif char =~ /[\s\r\n]/
+ if token.length > 0
+ @token = ""
+ return token
+ end
+ elsif char =~ /[;\{\}]/
+ if token.length > 0
+ unpop_char char
+ @token = ""
+ return token
+ end
+ return(char)
+ elsif token.length > 0 && char =~ /[\w:]/
+ token += char
+ elsif char =~ /[a-zA-Z0-9]/
+ token = char
+ else
+ syntax_error("unexpected #{char.inspect}")
+ end
+ end
+ rescue EOFError
+ end
+ def self.is_string token
+ token =~ /[a-zA-Z]\w*/
+ end
+ def self.is_number token
+ token =~ /[0-9]*/
+ end
+ end
+
+ class Struct
+ class StructField
+ def initialize
+ @members = [] # array of strings
+ end
+ def parse tokenizer
+ while true
+ token = tokenizer.next_token
+ if Tokenizer.is_string(token)
+ @members.push token
+ elsif token == ";" || token == "\n"
+ if @members.length > 0
+ return @members
+ else
+ return nil
+ end
+ else
+ tokenizer.syntax_error("expected member name in struct!")
+ end
+ end
+ end
+ def self.parse *args
+ self.new.parse *args
+ end
+ def use members
+ members
+ end
+ def self.use *args
+ self.new.use *args
+ end
+ def to_s
+ @members.join " "
+ end
+ end
+
+ def parse tokenizer, parse_name = true
+ if parse_name
+ token = tokenizer.next_token
+ if Tokenizer.is_string(token)
+ @name_raw = token
+ else
+ tokenizer.syntax_error("expected struct name!")
+ end
+ else
+ @name_raw = nil
+ @name_data = tokenizer.filename
+ end
+ token = tokenizer.next_token
+ tokenizer.syntax_error("expected '{', got '#{token}'") if(token != "{")
+ while token != "}"
+ token = tokenizer.peek_token
+ if token != "}"
+ field = StructField.parse(tokenizer)
+ @fields.push(field) if(field)
+ end
+ end
+ if tokenizer.next_token == "}"
+ return self
+ else
+ tokenizer.syntax_error("wahh; call parker. #{__LINE__}")
+ end
+ end
+ def self.parse *args
+ self.new.parse *args
+ end
+
+ def use fields, name_data
+ @name_raw = nil
+ @name_data = name_data
+ fields.each do |field|
+ @fields.push(StructField.use field.split(' '))
+ end
+ self
+ end
+ def self.use *args
+ self.new.use *args
+ end
+
+ def name
+ @name_raw || gen_name
+ end
+ def gen_name
+ unless @generated_name
+ @generated_name = 'a' + Digest::SHA1.hexdigest(@fields.join('') + $namespace + @name_data)
+ end
+ @generated_name
+ end
+
+ def initialize
+ @fields = [] # array of arrays of strings
+ @hidden_fields = []
+ end
+ def upcase_name
+ name.gsub(/^[a-z]|_[a-z]/) do |v|
+ v[-1].chr.upcase
+ end
+ end
+ def join_fields array
+ (array.collect { |a|
+ " #{a.join(" ")};"
+ }).join("\n")
+ end
+ def fields
+ join_fields @fields
+ end
+ def hidden_fields
+ join_fields @hidden_fields
+ end
+ def add_hidden_field k, v
+ @hidden_fields.push [k, v]
+ end
+ def params
+ (@fields.collect do |a|
+ a.join(" ")
+ end).join(', ')
+ end
+ def copy_params_into varname, decl = true
+ (decl ? "#{name} #{varname};\n" : '') + (@fields.collect do |a|
+ "#{varname}.#{a[-1]} = #{a[-1]};"
+ end).join("\n")
+ end
+ def params_from name
+ (@fields.collect do |a|
+ name + '.' + a[-1]
+ end).join(', ')
+ end
+ def builder_name aos_namespace = true, this_namespace = true
+ (aos_namespace ? "aos::" : '') + "QueueBuilder<#{this_namespace ? $namespace + '::' : ''}#{name}>"
+ end
+ def java_builder
+ name + 'Builder'
+ end
+ def builder_defs name
+ (@fields.collect do |field|
+ " inline #{name} &#{field[-1]}" +
+ "(#{field[0...-1].join(" ")} in) " +
+ "{ holder_.View().#{field[-1]} = in; return *this; }"
+ end).join "\n"
+ end
+ def swig_builder_defs name
+ (@fields.collect do |field|
+ " %rename(#{javaify field[-1]}) #{field[-1]};\n" +
+ " #{name} &#{field[-1]}" +
+ "(#{field[0...-1].join(" ")} #{field[-1]});"
+ end).join "\n"
+ end
+ def zero name
+ (@fields.collect do |field|
+ " new (&#{name}.#{field[-1]}) #{field[0...-1].join ' '}();"
+ end).join("\n")
+ end
+ def size
+ (@fields.collect do |field|
+ "sizeof(#{$namespace}::#{name}::#{field[-1]})"
+ end.push('0')).join(' + ')
+ end
+ def get_format(field)
+ case(field[0...-1])
+ when ['int']
+ r = '%d'
+ when ['float'], ['double']
+ r = '%f'
+ when ['bool']
+ r = '%s'
+ when ['uint8_t']
+ r = '%hhu'
+ when ['uint16_t']
+ r = '%d'
+ when ['struct', 'timespec']
+ r = '%jdsec,%ldnsec'
+ else
+ return 'generator_error'
+ end
+ return field[-1] + ': ' + r
+ end
+ def to_printf(name, field)
+ case(field[0...-1])
+ when ['bool']
+ return name + '.' + field[-1] + ' ? "true" : "false"'
+ when ['uint16_t']
+ return "static_cast<int>(#{name}.#{field[-1]})"
+ when ['struct', 'timespec']
+ return "#{name}.#{field[-1]}.tv_sec, #{name}.#{field[-1]}.tv_nsec"
+ else
+ return name + '.' + field[-1]
+ end
+ end
+ def netop name, buffer
+ offset = '0'
+ (@fields.collect do |field|
+ # block |var_pointer, output_pointer|
+ val = yield "&#{name}.#{field[-1]}", "&#{buffer}[#{offset}]"
+ offset += " + sizeof(#{name}.#{field[-1]})"
+ ' ' + val
+ end).join("\n") + "\n " +
+ "static_assert(#{offset} == #{size}, \"code generator issues\");"
+ end
+ def hton name, output
+ netop(name, output) do |var, output|
+ "to_network(#{var}, #{output});"
+ end
+ end
+ def ntoh input, name
+ netop(name, input) do |var, input|
+ "to_host(#{input}, #{var});"
+ end
+ end
+ def swig_writer
+ <<END
+struct #{name} {
+#{(@fields.collect { |a|
+ " %rename(#{javaify a[-1]}) #{a[-1]};"
+}).join("\n")}
+#{self.fields}
+ %extend {
+ const char *toString() {
+ return aos::TypeOperator<#{$namespace}::#{name}>::Print(*$self);
+ }
+ }
+ private:
+ #{name}();
+};
+} // namespace #{$namespace}
+namespace aos {
+%typemap(jstype) #{builder_name false}& "#{java_builder}"
+%typemap(javaout) #{builder_name false}& {
+ $jnicall;
+ return this;
+ }
+template <> class #{builder_name false} {
+ private:
+ #{builder_name false}();
+ public:
+ inline bool Send();
+ %rename(#{javaify 'Send'}) Send;
+#{swig_builder_defs builder_name(false)}
+};
+%template(#{java_builder}) #{builder_name false};
+%typemap(javaout) #{builder_name false}& {
+ return new #{java_builder}($jnicall, false);
+ }
+} // namespace aos
+namespace #{$namespace} {
+END
+ end
+ def writer
+ <<END
+struct #{name} {
+#{self.fields}
+#{self.hidden_fields}
+};
+} // namespace #{$namespace}
+namespace aos {
+template <> class TypeOperator<#{$namespace}::#{name}> {
+ public:
+ static void Zero(#{$namespace}::#{name} &inst) {
+ (void)inst;
+#{zero 'inst'}
+ }
+ static void NToH(const char *input, #{$namespace}::#{name} &inst) {
+ (void)input;
+ (void)inst;
+#{ntoh 'input', 'inst'}
+ }
+ static void HToN(const #{$namespace}::#{name} &inst, char *output) {
+ (void)inst;
+ (void)output;
+#{hton 'inst', 'output'}
+ }
+ static inline size_t Size() { return #{size}; }
+ static const char *Print(const #{$namespace}::#{name} &inst) {
+#{@fields.empty? ? <<EMPTYEND : <<NOTEMPTYEND}
+ (void)inst;
+ return "";
+EMPTYEND
+ static char buf[1024];
+ if (snprintf(buf, sizeof(buf), "#{@fields.collect do |field|
+ get_format(field)
+ end.join(', ')}", #{@fields.collect do |field|
+ to_printf('inst', field)
+ end.join(', ')}) >= static_cast<ssize_t>(sizeof(buf))) {
+ LOG(WARNING, "#{name}'s buffer was too small\\n");
+ buf[sizeof(buf) - 1] = '\\0';
+ }
+ return buf;
+NOTEMPTYEND
+ }
+};
+template <> class #{builder_name false} {
+ private:
+ aos::QueueHolder<#{$namespace}::#{name}> &holder_;
+ public:
+ #{builder_name false}(aos::QueueHolder<#{$namespace}::#{name}> &holder) : holder_(holder) {}
+ inline bool Send() { return holder_.Send(); }
+ inline const char *Print() const { return holder_.Print(); }
+#{builder_defs builder_name(false)}
+};
+} // namespace aos
+namespace #{$namespace} {
+END
+ end
+ def to_s
+ return <<END
+#{name}: #{(@fields.collect {|n| n.join(" ") }).join("\n\t")}
+END
+ end
+ end
+
+ class SimpleField
+ def initialize check_function = :is_string
+ @check_function = check_function
+ @name = nil
+ end
+ def parse tokenizer
+ token = tokenizer.next_token
+ if Tokenizer.__send__ @check_function, token
+ @name = token
+ else
+ tokenizer.syntax_error('expected value!')
+ end
+ if tokenizer.next_token == ';'
+ @name
+ else
+ tokenizer.syntax_error('expected ";"!')
+ end
+ end
+ def self.parse tokenizer
+ self.new.parse tokenizer
+ end
+ end
+ class NameField < SimpleField
+ end
+
+ class OutputFile
+ def initialize namespace, filename, topdir, outpath
+ @namespace = namespace
+ $namespace = namespace
+ @base = filename.gsub(/\.\w*$/, "").gsub(/^.*\//, '')
+ @topdir = topdir
+ @filebase = outpath + @base
+ @filename = filename
+ FileUtils.mkdir_p(outpath)
+
+ fillin_initials if respond_to? :fillin_initials
+ parse filename
+ fillin_defaults if respond_to? :fillin_defaults
+ self
+ rescue SyntaxError => e
+ puts e
+ exit 1
+ end
+ def filename type
+ case type
+ when 'h'
+ @filebase + '.q.h'
+ when 'cc'
+ @filebase + '.q.cc'
+ when 'main'
+ @filebase + '_main.cc'
+ when 'swig'
+ @filebase + '.swg'
+ when 'java_dir'
+ @filebase + '_java/'
+ when 'java_wrap'
+ @filebase + '_java_wrap.cc'
+ else
+ throw SyntaxError, "unknown filetype '#{type}'"
+ end
+ end
+ def parse filename
+ file = File.open filename
+ tokenizer = Tokenizer.new file
+ while token = tokenizer.next_token
+ if !token || token.gsub('\s', '').empty?
+ elsif token == ';'
+ else
+ error = catch :syntax_error do
+ case token
+ when 'namespace'
+ $namespace = NameField.parse tokenizer
+ else
+ parse_token token, tokenizer
+ end
+ nil
+ end
+ if error
+ tokenizer.syntax_error error.to_s
+ raise error
+ end
+ end
+ end
+
+ check_format tokenizer
+ end
+ def call_swig
+ output_dir = filename('java_dir') + $namespace
+ FileUtils.mkdir_p(output_dir)
+ if (!system('swig', '-c++', '-Wall', '-Wextra', '-java',
+ '-package', $namespace, "-I#{@topdir}",
+ '-o', filename('java_wrap'),
+ '-outdir', output_dir, filename('swig')))
+ exit $?.to_i
+ end
+ end
+
+ def queue_holder_accessors suffix, var, type, force_timing = nil
+<<END
+ inline bool Get#{suffix}(#{force_timing ? '' : 'bool check_time'}) aos_check_rv { return #{var}.Get(#{force_timing || 'check_time'}); }
+ inline #{type} &View#{suffix}() { return #{var}.View(); }
+ inline void Clear#{suffix}() { #{var}.Clear(); }
+ inline bool Send#{suffix}() { return #{var}.Send(); }
+ inline const char *Print#{suffix}() { return #{var}.Print(); }
+ inline aos::QueueBuilder<#{type}> &#{suffix || 'Builder'}() { return #{var}.Builder(); }
+END
+ end
+ def queue_holder_accessors_swig suffix, var, type, force_timing = nil
+<<END
+ %rename(#{javaify "Get#{suffix}"}) Get#{suffix};
+ bool Get#{suffix}(#{force_timing ? '' : 'bool check_time'});
+ %rename(#{javaify "View#{suffix}"}) View#{suffix};
+ #{type} &View#{suffix}();
+ %rename(#{javaify "Clear#{suffix}"}) Clear#{suffix};
+ void Clear#{suffix}();
+ %rename(#{javaify "Send#{suffix}"}) Send#{suffix};
+ bool Send#{suffix}();
+ %rename(#{javaify suffix || 'Builder'}) #{suffix || 'Builder'};
+ aos::QueueBuilder<#{type}> &#{suffix || 'Builder'}();
+END
+ end
+ end
+end
+
+def write_file_out
+ if ARGV.length < 3
+ puts 'Error: at least 3 arguments required!!!'
+ exit 1
+ end
+ file = Contents::OutputFile.new ARGV.shift,
+ File.expand_path(ARGV.shift),
+ ARGV.shift,
+ File.expand_path(ARGV.shift) + '/'
+ while !ARGV.empty?
+ case type = ARGV.shift
+ when 'cpp'
+ file.write_cpp
+ when 'header'
+ file.write_header
+ when 'main'
+ file.write_main
+ when 'swig'
+ file.write_swig
+ file.call_swig
+ else
+ puts "Error: unknown output type '#{type}'"
+ exit 1
+ end
+ end
+end
+