class IDL::Scanner
def check_include(path, fname)
def check_include(path, fname) fp = path + fname File.file?(fp) && File.readable?(fp) end
def do_parse?
def do_parse? @ifdef.empty? || @ifdef.last end
def enter_expansion(src, define)
def enter_expansion(src, define) IDL.log(2, "** RIDL - enter_expansion > #{define} = #{src}") @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, all = true)
def enter_include(src, all = true) if @directiver.is_included?(src) @directiver.declare_include(src) else fpath = find_include(src, all) if fpath.nil? parse_error "Cannot open include file '#{src}'" end @stack << [:include, @prefix, @ifdef, @in, @ifskip] # record file dir as new searchpath @xincludepaths << (File.dirname(fpath)+'/') @prefix = nil @ifdef = Array.new @in = In.new(File.open(fpath, 'r'), fpath) @directiver.enter_include(src, fpath) @directiver.pragma_prefix(nil) end end
def eval_directive(s)
def eval_directive(s) IDL.log(3,"** 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 skip_spaces # skip till next non-space or eol if peek_next == '(' token = next_token # parse '(' 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.to_s] = annotation_value end end end until token.first == ')' token = next_token_before_eol else token = next_token_before_eol # marker annotation or symbolic value; leave body nil end [token, annotation_body] end
def extract_annotation_value
def extract_annotation_value 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.to_s # get nested body token, member_annotation_body = extract_annotation # determin vaue type; if it has a body it is an annotation instance annotation_value = if member_annotation_body { member_annotation_id => member_annotation_body } else # otherwise it is a symbolic value member_annotation_id.to_sym end # get next token if needed token = next_token unless token else parse_error 'invalid annotation member' unless is_literal?(token.first) annotation_value = token.last token = next_token end [token, annotation_value] end
def find_include(fname, all = true)
def find_include(fname, all = true) if File.file?(fname) && File.readable?(fname) File.expand_path(fname) else # search transient include paths if allowed (quoted includes) fp = if all then @xincludepaths.find do |p| check_include(p, fname) end else nil end # search system include paths if still needed fp = @includepaths.find do |p| check_include(p, fname) end unless fp fp += fname if fp 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 _, 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 _, 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] || [] @xincludepaths = params[:xincludepaths] || [] @stack = [] @expansions = [] @prefix = nil @directiver = directiver @directiver.instance_variable_get('@d').instance_variable_set('@scanner', self) @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, StringIO 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) LITERALS.include?(o) end
def leave_source
def leave_source # make sure to close the input source @in.close # check if we have any previous source still stacked up if @stack.size>0 if @stack.last[0] == :include @xincludepaths.pop # remove directory of finished include @directiver.leave_include _, @prefix, @ifdef, @in, @ifskip = @stack.pop @directiver.pragma_prefix(@prefix) else _, _, _, @in, _ = @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 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 ret end
def next_identifier(first = nil)
def next_identifier(first = nil) @in.mark(first) while true if FULL_IDCHARS.include?(@in.lookc) @in.skipc else break end end s0 = @in.getregion # raw IDL id s1 = s0.downcase.to_sym # downcased symbolized s2 = s0.dup # (to be) unescaped id # simple check if s2.length == 0 parse_error 'identifier expected!' else if s2[0] == ?_ s2.slice!(0) ## if starts with CORBA IDL escape => remove else parse_error "identifier must begin with alphabet character: #{s2}" unless ALPHA_LC.include?(s2[0]) || ALPHA_UC.include?(s2[0]) end end # preprocessor check if @defined.has_key?(s2) and !is_expanded?(s2) # enter expansion as new source enter_expansion(@defined[s2], s2) # 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, Identifier.new(s2, s2, s0) ] end elsif (a = KEYWORDS.assoc(s1)).nil? # check for language mapping keyword [ :identifier, Identifier.new(s2, chk_identifier(s2), s0) ] elsif s0 == a[1] [ a[1], nil ] else parse_error "'#{s0}' collides with IDL 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 {|c| WHITESPACE.include?(c) } 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 token returned by parse_annotation or parse next token recursively return parse_annotation || next_token end when ch == ?: # if @in.lookc == ?: # @in.skipc return %w(:: ::) else return %w(: :) end when ch == ?L _nxtc = @in.lookc if _nxtc == ?\' #' #single quote, for a character literal. @in.skipc # skip 'L' _nxtc = @in.lookc ret = if _nxtc == ?\\ @in.skipc next_escape_str elsif _nxtc == ?\' #' [ nil, nil ] else [ :char, '' << @in.getc ] 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 token returned by parse_annotation or parse next token recursively return parse_annotation(true) || next_token else @in.skipuntil {|c| LFCR.include?(c) } end end str = '' # reset next else return %w(/ /) end when SIGNS.include?(ch) _nxtc = @in.lookc if DIGITS.include? _nxtc sign = ch str = '' # reset next else return [str, str] end when (?1..?9).include?(ch) @in.mark(sign, ch) @in.skipwhile {|c| DIGITS.include?(c) } 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 == DOT # @in.mark(ch) @in.skipwhile {|c| DIGITS.include?(c) } num_type = (DOT != @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) _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 {|c| OCTALS.include?(c) } if (?8..?9).include? @in.lookc dec = TRUE @in.skipwhile {|c| DIGITS.include?(c) } end num_type = ([?., ?e, ?E, ?d, ?D].include?(@in.lookc)) ? skipfloat_or_fixed : :integer_literal s = @in.getregion ret = if num_type == :floating_pt_literal [:floating_pt_literal, s.to_f] elsif num_type == :fixed_pt_literal [:fixed_pt_literal, s] elsif dec parse_error "decimal literal starting with '0' should be octal ('0'..'7' only): #{s}" else [:integer_literal, s.oct] end return ret end when ch == ?\' #' #single quote, for a character literal. _nxtc = @in.lookc ret = if _nxtc == ?\\ @in.skipc next_escape elsif _nxtc == ?\' #' 0 elsif _nxtc ('' << @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 next_token_before_eol
def next_token_before_eol @in.skipwhile {|c| SPACES.include?(c)} LFCR.include?(@in.lookc) ? nil : next_token end
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 annotation_position = self.position.dup # 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.to_s token, annotation_body = extract_annotation # pass annotation to directiver for processing @directiver.define_annotation(annotation_id, annotation_position, in_comment, annotation_body || {}) end until token.nil? || token.first != ANNOTATION_STR ensure @in_annotation = false @scan_comment = false end # check identifier for keywords if token && token.first == :identifier # keyword check if (a = KEYWORDS.assoc(token.last.to_s)).nil? token = [ :identifier, Identifier.new(token.last.to_s, chk_identifier(token.last.to_s), token.last.unescaped_name) ] elsif token.last == a[1] token = [ a[1], nil ] else parse_error "'#{token.last}' collides with a keyword '#{a[1]}'" end end token end
def parse_directive
def parse_directive @in.skipwhile {|c| SPACES.include?(c) } 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 |_| "#{$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 if /ifn?def/ === s1 if do_parse? parse_error 'no #if(n)def target.' unless /^(\w+)/ === s2 @ifdef.push(@defined[$1].nil? ^ (s1 == 'ifdef')) @ifskip = @ifdef.last else @ifnest += 1 end else # when 'if' if do_parse? # match 'defined(Foo)' or 'defined Foo' while s2 =~ /(^|[\W])defined(\s*\(\s*(\w+)\s*\)|\s+(\w+))/ IDL.log(3,"** RIDL - parse_directive : resolving 'defined(#{$3 || $4})'") def_id = $3 || $4 # resolve 'defined' expression to 'true' or 'false' according to actual macro definition s2.gsub!(/(^|[\W])(defined\s*[\s\(]\s*#{def_id}(\s*\))?)/, '\1'+"#{@defined.has_key?(def_id).to_s}") end # match and resolve any macro variables listed in conditional expression s2.gsub!(/(^|[\W])([A-Za-z_][\w]*)/) do |_| "#{$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' defid = s2.split.first parse_error 'no #define target.' unless defid parse_error "#{defid} is already #define-d." if @defined[defid] defval = s2.sub(/^\s*#{defid}/, '').strip defval = true if defval.empty? @defined[defid] = defval when 'undef' @defined.delete(s2) when 'include' if s2[0,1] == '"' || s2[0,1] == '<' quoted_inc = (s2[0,1] == '"') if s2.size>2 s2.strip! s2 = s2.slice(1..(s2.size-2)) else s2 = '' end end enter_include(s2, quoted_inc) 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 peek_next
def peek_next @in.lookc end
def position
def position @in.position end
def positions
def positions @stack.reverse.inject(@in.nil? ? [] : [@in.position]) {|pos_arr,(_, _, _,in_, _)| pos_arr << in_.position } end
def resolve_define(id, stack = [])
def resolve_define(id, stack = []) return id if %w(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 |_| "#{$1}#{resolve_define($2, stack)}" end else '0' # unknown id end end
def skip_spaces
def skip_spaces @in.skipwhile {|c| SPACES.include?(c) } end
def skipfloat_or_fixed
def skipfloat_or_fixed if @in.lookc == DOT @in.skipc @in.skipwhile {|c| DIGITS.include?(c) } end if [?e, ?E].include? @in.lookc @in.skipc @in.skipc if SIGNS.include? @in.lookc @in.skipwhile {|c| DIGITS.include?(c) } return :floating_pt_literal elsif [?d, ?D].include? @in.lookc @in.skipc @in.skipc if SIGNS.include? @in.lookc @in.skipwhile {|c| DIGITS.include?(c) } return :fixed_pt_literal end :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