class IDL::Scanner
def do_parse?
def do_parse? @ifdef.empty? || @ifdef.last end
def enter_expansion(src, define)
def enter_expansion(src, define) @stack << [:define, nil, nil, @in, nil] @expansions << define @in = In.new(StrIStream.new(src), @in.position.name, @in.position.line, @in.position.column) end
def enter_include(src)
def enter_include(src) if @directiver.is_included?(src) @directiver.declare_include(src) else fpath = find_include(src) if fpath.nil? parse_error "Cannot open include file '#{src}'" end @stack << [:include, @prefix, @ifdef, @in, @ifskip] @prefix = nil @ifdef = Array.new @in = In.new(File.open(fpath, 'r'), fpath) @directiver.enter_include(src) @directiver.pragma_prefix(nil) end end
def eval_directive(s)
def eval_directive(s) IDL.log(2,"** RIDL - eval_directive(#{s})") rc = eval(s) case rc when FalseClass, TrueClass rc when Numeric rc != 0 else parse_error "invalid preprocessor expression." end end
def extract_annotation()
def extract_annotation() annotation_body = nil # next token should be '(' in case of normal/single value annotation # or anything else in case of marker annotation token = next_token if token.first == '(' begin # identifier or value (in case of single value annotation) expected token = next_token if token.first == ')' # marker annotation; leave body empty annotation_body = { } else parse_error 'annotation member expected!' unless token.first == :identifier || is_literal?(token.first) s1 = token.last token = next_token # ')' (in case of single value annotation) or '=' if token.first == ')' parse_error 'invalid annotation member' if annotation_body annotation_body = { :value => s1 } else parse_error 'invalid annotation member' unless token.first == '=' token, annotation_value = extract_annotation_value() parse_error 'invalid annotation body' unless token.first == ',' || token.first == ')' (annotation_body ||= {})[s1] = annotation_value end end end until token.first == ')' token = next_token # need to get next token else # marker annotation or symbolic value; leave body nil end return [token, annotation_body] end
def extract_annotation_value()
def extract_annotation_value() annotation_value = nil token = next_token # needs '{' (array) or literal or identifier (which means nested annotation object or enum value) if token.first == '{' # extract array of values (literals or identifiers) separated by ',' annotation_value = [] begin token, ann_value = extract_annotation_value() parse_error 'invalid annotation value array' unless token.first == ',' || token.first == '}' annotation_value << ann_value end until token.first == '}' token = next_token elsif token.first == :identifier member_annotation_id = token.last # get nested body token, member_annotation_body = extract_annotation() # determin vaue type; if it has a body it is an annotation instance if member_annotation_body annotation_value = { member_annotation_id => member_annotation_body } else # otherwise it is a symbolic value annotation_value = member_annotation_id.to_sym end else parse_error 'invalid annotation member' unless is_literal?(token.first) annotation_value = token.last token = next_token end return [token, annotation_value] end
def find_include(fname)
def find_include(fname) path = if File.file?(fname) && File.readable?(fname) fname else fp = @includepaths.find do |p| f = p + "/" + fname File.file?(f) && File.readable?(f) end fp += '/' + fname if !fp.nil? fp end end
def getline
def getline s = "" while TRUE ch = @in.lookc break if ch.nil? case when (ch == ?\") #" s << @in.getc # opening quote while TRUE if @in.lookc == ?\\ # escape sequence s << @in.getc esctyp, escstr = next_escape_str(true) s << escstr elsif @in.lookc == ?\" #" break elsif @in.lookc # normal character s << @in.getc else parse_error "unterminated string literal" end end s << @in.getc # closing quote when (ch == ?\') #' # quoted character s << @in.getc # opening quote if @in.lookc == ?\\ # escape sequence s << @in.getc esctyp, escstr = next_escape_str(true) s << escstr elsif @in.lookc && @in.lookc != ?\' #' # normal character s << @in.getc end if @in.lookc != ?\' #' parse_error "character literal must be single character enclosed in \"'\"" end s << @in.getc # closing quote when LFCR.include?(ch) @in.skipwhile() { |ch_| LFCR.include? ch_ } break when ch == ?/ @in.skipc if @in.lookc == ?/ # //-style comment; skip till eol @in.gets break elsif @in.lookc == ?* # /*...*/ style comment; skip comment ch1 = nil @in.skipuntil { |ch_| ch0 = ch1; ch1 = ch_ ch0 == ?* and ch1 == ?/ # } if @in.lookc.nil? parse_error "cannot find comment closing brace (\'*/\'). " end @in.skipc else s << ch end when ch == ?\\ @in.skipc if LFCR.include?(@in.lookc) # line continuation @in.skipwhile() { |ch_| LFCR.include? ch_ } if @in.lookc.nil? parse_error "line continuation character ('\\') not allowed as last character in file." end else s << ch end else @in.skipc s << ch end end s end
def in_expansion?
def in_expansion? more_source? and @stack.last[0] == :define end
def initialize(src, directiver, params = {})
def initialize(src, directiver, params = {}) @includepaths = params[:includepaths] || [] @stack = [] @expansions = [] @prefix = nil @directiver = directiver @defined = TokenRegistry.new # initialize with predefined macros if params[:macros] params[:macros].each do |(name, value)| @defined[name] = value end end @ifdef = Array.new @ifskip = false @ifnest = 0 i = nil nm = '' case src when String i = StrIStream.new(src) nm = '<string>' when File i = src nm = src.path when IO i = src nm = '<io>' else parse_error "illegal type for input source: #{src.class} " end @in = In.new(i, nm) @scan_comment = false # true if parsing commented annotation @in_annotation = false # true if parsing annotation end
def is_expanded?(define)
def is_expanded?(define) @expansions.include?(define) end
def is_literal?(o)
def is_literal?(o) return LITERALS.include?(o) end
def leave_source()
def leave_source() if @stack.size>0 if @stack.last[0] == :include @directiver.leave_include type, @prefix, @ifdef, @in, @ifskip = @stack.pop @directiver.pragma_prefix(@prefix) else type, prefix_, ifdef_, @in, elsif_ = @stack.pop @expansions.pop end end end
def more_source?
def more_source? @stack.size>0 end
def next_escape
def next_escape ret = 0 case (ch = @in.getc) when nil parse_error 'illegal escape sequence' when ?0..?7 ret = "" ret << ch 1.upto(2) { ch = @in.lookc if (?0..?7).include? ch ret << ch else break end @in.skipc } ret = ret.oct when ?x # i'm not sure '\x' should be 0 or 'x'. currently returns 0. ret = "" 1.upto(2) { ch = @in.lookc if HEXCHARS.include? ch ret << ch else break end @in.skipc } ret = ret.hex when ?u ret = "" 1.upto(4) { ch = @in.lookc if HEXCHARS.include? ch ret << ch else break end @in.skipc } ret = ret.hex when ?n, ?t, ?v, ?b, ?r, ?f, ?a ret = ESCTBL[ch] else ret = ('' << ch).unpack('C').first end return ret end
def next_escape_str(keep_type_ch = false)
def next_escape_str(keep_type_ch = false) ret = 0 case (ch = @in.getc) when nil parse_error 'illegal escape sequence' when ?0..?7 ret = "" ret << ch 1.upto(2) { ch = @in.lookc if (?0..?7).include? ch ret << ch else break end @in.skipc } ret = [ :oct, ret ] when ?x # i'm not sure '\x' should be 0 or 'x'. currently returns 0. ret = "" ret << ch if keep_type_ch 1.upto(2) { ch = @in.lookc if HEXCHARS.include? ch ret << ch else break end @in.skipc } ret = [ :hex2, ret ] when ?u ret = "" ret << ch if keep_type_ch 1.upto(4) { ch = @in.lookc if HEXCHARS.include? ch ret << ch else break end @in.skipc } ret = [ :hex4, ret ] when ?n, ?t, ?v, ?b, ?r, ?f, ?a ret = '' ret << ch ret = [ :esc, ret ] else ret = '' ret << ch ret = [ :esc_ch, ch ] end return ret end
def next_identifier(first = nil)
def next_identifier(first = nil) @in.mark(first) while TRUE case @in.lookc when nil break when ?0..?9, ?a..?z, ?A..?Z, ?_ @in.skipc else break end end s0 = @in.getregion s1 = s0.downcase # simple check if (s0.length == 0) parse_error "identifier expected!" else case s0[0] when ?a..?z, ?A..?Z when ?_ ## if starts with CORBA IDL escape => remove s0.slice!(0) else parse_error "identifier must begin with alphabet character: #{s0}" end end # preprocessor check if @defined.has_key?(s0) and !is_expanded?(s0) # enter expansion as new source enter_expansion(@defined[s0], s0) # call next_token to parse expanded source next_token # keyword check elsif @in_annotation if BOOL_LITERALS.has_key?(s1) [ :boolean_literal, BOOL_LITERALS[s1] ] else [ :identifier, s0 ] end elsif (a = KEYWORDS.assoc(s1)).nil? # check for language mapping keyword except when # - this is an IDL escaped ('_' prefix) identifier [ :identifier, Identifier.new(s0, s1[0] == ?_ ? s0 : chk_identifier(s0)) ] elsif s0 == a[1] [ a[1], nil ] else parse_error "`#{s0}' collides with a keyword `#{a[1]}'" end end
def next_token
def next_token sign = nil str = "" #initialize empty string while TRUE ch = @in.getc if ch.nil? if @ifdef.size>0 and !in_expansion? parse_error "mismatched #if/#endif" end if more_source? leave_source next else return [FALSE, nil] end end if WHITESPACE.include? ch @in.skipwhile( WHITESPACE ) next end if str.empty? && ch == ?\# parse_directive next end unless do_parse? skipline next end str << ch case when BREAKCHARS.include?(ch) if SHIFTCHARS.include?(ch) && @in.lookc == ch # '<<' or '>>' str << @in.getc end return [str, str] when ch == ANNOTATION if @in_annotation return [str, str] else return parse_annotation() end when ch == ?: # if @in.lookc == ?: # @in.skipc return ["::", "::"] else return [":", ":"] end when ch == ?L _nxtc = @in.lookc if _nxtc == ?\' #' #single quote, for a character literal. ret = 0 @in.skipc # skip 'L' _nxtc = @in.lookc if _nxtc == ?\\ @in.skipc ret = next_escape_str elsif _nxtc == ?\' #' ret = [ nil, nil ] else ret = '' ret << @in.getc ret = [ :char, ret ] end if @in.lookc != ?\' #' parse_error "wide character literal must be single wide character enclosed in \"'\"" end @in.skipc return [ :wide_character_literal, ret ] elsif _nxtc == ?\" #" #double quote, for a string literal. ret = [] chs = '' @in.skipc # skip 'L' while TRUE _nxtc = @in.lookc if _nxtc == ?\\ @in.skipc ret << [:char, chs] unless chs.empty? chs = '' ret << next_escape_str elsif _nxtc == ?\" #" @in.skipc ret << [:char, chs] unless chs.empty? return [ :wide_string_literal, ret ] else chs << @in.getc end end else return next_identifier(ch) end when IDCHARS.include?(ch) return next_identifier(ch) when ch == ?/ # _nxtc = @in.lookc if _nxtc == ?* # skip comment like a `/* ... */' @in.skipc # forward stream beyond `/*' ch1 = nil @in.skipuntil { |ch_| ch0 = ch1; ch1 = ch_ ch0 == ?* and ch1 == ?/ # } if @in.lookc.nil? parse_error "cannot find comment closing brace (\'*/\'). " end @in.skipc str = "" # reset next elsif _nxtc == ?/ # skip comment like a `// ...\n' @in.skipc unless @scan_comment # scan_comment will be true when parsing commented annotations _nxtc = @in.lookc if _nxtc == ANNOTATION @in.skipc return parse_annotation(true) else @in.skipuntil(?\n, ?\r) end end str = "" # reset next else return [ "/", "/" ] end when ch == ?+ || ch == ?- _nxtc = @in.lookc if (?0..?9).include? _nxtc sign = ch str = "" # reset next else return [str, str] end when (?1..?9).include?(ch) @in.mark(sign, ch) sign = nil @in.skipwhile(?0..?9) num_type = ([?., ?e, ?E, ?d, ?D].include?(@in.lookc)) ? skipfloat_or_fixed : :integer_literal r = @in.getregion if num_type == :floating_pt_literal return [:floating_pt_literal, r.to_f] elsif num_type == :fixed_pt_literal return [:fixed_pt_literal, r] else return [:integer_literal, r.to_i] end when ch == ?. # @in.mark(ch) @in.skipwhile(?0..?9) num_type = (?. != @in.lookc) ? skipfloat_or_fixed : nil s = @in.getregion if s == "." parse_error "token consisting of single dot (.) is invalid." end if num_type == :floating_pt_literal return [:floating_pt_literal, s.to_f] elsif num_type == :fixed_pt_literal return [:fixed_pt_literal, s] else parse_error "invalid floating point constant." end when ch == ?0 @in.mark(sign, ch) sign = nil _nxtc = @in.lookc if _nxtc == ?x || _nxtc == ?X @in.skipc @in.skipwhile() { |ch_| HEXCHARS.include? ch_ } s = @in.getregion return [:integer_literal, s.hex] else dec = FALSE @in.skipwhile(?0..?7) if (?8..?9).include? @in.lookc dec = TRUE @in.skipwhile(?0..?9) end num_type = ([?., ?e, ?E, ?d, ?D].include?(@in.lookc)) ? skipfloat_or_fixed : :integer_literal ret = nil s = @in.getregion if num_type == :floating_pt_literal ret = [:floating_pt_literal, s.to_f] elsif num_type == :fixed_pt_literal ret = [:fixed_pt_literal, s] elsif dec parse_error "decimal literal starting with '0' should be octal ('0'..'7' only): #{s}" else ret = [:integer_literal, s.oct] end return ret end when ch == ?\' #' #single quote, for a character literal. ret = 0 _nxtc = @in.lookc if _nxtc == ?\\ @in.skipc ret = next_escape elsif _nxtc == ?\' #' ret = 0 elsif _nxtc ret = ('' << @in.getc).unpack('C').first end if @in.lookc != ?\' #' parse_error "character literal must be single character enclosed in \"'\"" end @in.skipc return [ :character_literal, ret ] when ch == ?\" #" #double quote, for a string literal. ret = "" while TRUE _nxtc = @in.lookc if _nxtc == ?\\ @in.skipc ret << next_escape elsif _nxtc == ?\" #" @in.skipc return [ :string_literal, ret ] elsif _nxtc ret << @in.getc else parse_error "unterminated string literal" end end else parse_error 'illegal character [' << ch << ']' end #of case end #of while parse_error "unexcepted error" end #of method next_token
def parse_annotation(in_comment = false)
def parse_annotation(in_comment = false) @in_annotation = true @scan_comment = in_comment begin # parse (possibly multiple) annotation(s) begin # next token should be identifier (must be on same line following '@') token = next_token parse_error 'annotation identifier expected!' unless token.first == :identifier annotation_id = token.last token, annotation_body = extract_annotation() # pass annotation to directiver for processing @directiver.define_annotation(annotation_id, annotation_body || {}) end until token.first != ANNOTATION_STR ensure @in_annotation = false @scan_comment = false end # check identifier for keywords if token.first == :identifier # keyword check if (a = KEYWORDS.assoc(token.last)).nil? token = [ :identifier, Identifier.new(token.last, chk_identifier(token.last)) ] elsif token.last == a[1] token = [ a[1], nil ] else parse_error "`#{token.last}' collides with a keyword `#{a[1]}'" end end return token end
def parse_directive
def parse_directive @in.skipwhile(?\ , ?\t) s = getline /^(\w*)\s*/ === s s1,s2 = $1, $' #' if /(else|endif|elif)/ === s1 if @ifdef.empty? parse_error "#else/#elif/#endif must not appear without preceding #if" end case s1 when 'else' if @ifnest == 0 if @ifskip # true branch has already been parsed @ifdef[@ifdef.size - 1] = false else @ifdef[@ifdef.size - 1] ^= true; @ifskip = @ifdef.last end end when 'endif' if @ifnest == 0 @ifdef.pop @ifskip = @ifdef.last else @ifnest -= 1 end else if @ifnest == 0 if @ifskip || @ifdef[@ifdef.size - 1] # true branch has already been parsed so skip from now on @ifdef[@ifdef.size - 1] = false @ifskip = true else while s2 =~ /(^|[\W])defined\s*\(\s*(\w+)\s*\)/ def_id = $2 s2.gsub!(/(^|[\W])(defined\s*\(\s*\w+\s*\))/, '\1'+"#{@defined.has_key?(def_id).to_s}") end s2.gsub!(/(^|[\W])([A-Za-z_][\w]*)/) do |m_| "#{$1}#{resolve_define($2)}" end begin @ifdef[@ifdef.size - 1] = eval_directive(s2) @ifskip = @ifdef[@ifdef.size - 1] rescue IDL::ParseError raise rescue => ex p ex puts ex.backtrace.join("\n") parse_error "error evaluating #elif" end end end end elsif /(if|ifn?def)/ === s1 case s1 when /ifn?def/ if do_parse? if not (/^(\w+)/ === s2) parse_error "no #if(n)def target." end @ifdef.push(@defined[$1].nil? ^ (s1 == "ifdef")) @ifskip = @ifdef.last else @ifnest += 1 end when 'if' if do_parse? while s2 =~ /(^|[\W])defined\s*\(\s*(\w+)\s*\)/ def_id = $2 s2.gsub!(/(^|[\W])(defined\s*\(\s*\w+\s*\))/, '\1'+"#{@defined.has_key?(def_id).to_s}") end s2.gsub!(/(^|[\W])([A-Za-z_][\w]*)/) do |m_| "#{$1}#{resolve_define($2)}" end begin @ifdef.push(eval_directive(s2)) @ifskip = @ifdef.last rescue IDL::ParseError raise rescue => ex p ex puts ex.backtrace.join("\n") parse_error "error evaluating #if" end else @ifnest += 1 end end elsif do_parse? case s1 when 'pragma' parse_pragma(s2) when 'error' parse_error(s2) when 'define' a = s2.split a[1] = true if a[1].nil? if a[0].nil? parse_error "no #define target." elsif not @defined[a[0]].nil? parse_error "#{a[0]} is already #define-d." end @defined[a[0]] = a[1] when 'undef' @defined.delete(s2) when 'include' if s2[0,1] == '"' || s2[0,1] == '<' if s2.size>2 s2.strip! s2 = s2.slice(1..(s2.size-2)) else s2 = "" end end enter_include(s2) when /[0-9]+/ # ignore line directive else parse_error "unknown directive: #{s}." end end end
def parse_error(msg, ex = nil)
def parse_error(msg, ex = nil) e = IDL::ParseError.new(msg, positions) e.set_backtrace(ex.backtrace) unless ex.nil? raise e end
def parse_pragma(s)
def parse_pragma(s) case s when /^ID\s+(.*)\s+"(.*)"\s*$/ @directiver.pragma_id($1.strip, $2) when /^version\s+(.*)\s+([0-9]+)\.([0-9]+)\s*$/ @directiver.pragma_version($1.strip, $2, $3) when /^prefix\s+"(.*)"\s*$/ @prefix = $1 @directiver.pragma_prefix(@prefix) else @directiver.handle_pragma(s) end end
def positions
def positions @stack.reverse.inject(@in.nil? ? [] : [@in.position]) {|pos_arr,(type_,pfx_,ifdef_,in_,elsif_)| pos_arr << in_.position } end
def resolve_define(id, stack = [])
def resolve_define(id, stack = []) return id if ['true', 'false'].include?(id) IDL.log(3,"*** RIDL - resolve_define(#{id})") if @defined.has_key?(id) define_ = @defined[id] stack << id parse_error("circular macro reference detected for [#{define_}]") if stack.include?(define_) # resolve any nested macro definitions define_.gsub(/(^|[\W])([A-Za-z_][\w]*)/) do |m_| "#{$1}#{resolve_define($2, stack)}" end else '0' # unknown id end end
def skipfloat_or_fixed
def skipfloat_or_fixed if (@in.lookc == ?.) @in.skipc @in.skipwhile(?0..?9) end if [?e, ?E].include? @in.lookc @in.skipc @in.skipc if [?+, ?-].include? @in.lookc @in.skipwhile(?0..?9) return :floating_pt_literal elsif [?d, ?D].include? @in.lookc @in.skipc @in.skipc if [?+, ?-].include? @in.lookc @in.skipwhile(?0..?9) return :fixed_pt_literal end return :floating_pt_literal end
def skipline
def skipline while TRUE s = @in.gets until s.chomp!.nil?; end break unless s[s.length - 1] == ?\\ end end