blob: 6163129dfab65abd5dfc527cc267bbb712c114be [file] [log] [blame]
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