# coding: US-ASCII
#--
# irb/ruby-lex.rb - ruby lexcal analyzer
# $Release Version: 0.9.5$
# $Revision: 17979 $
# $Date: 2008-07-09 10:17:05 -0700 (Wed, 09 Jul 2008) $
# by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
#++
require "e2mmap"
require "irb/slex"
require "stringio"
##
# Ruby lexer adapted from irb.
#
# The internals are not documented because they are scary.
class RDoc::RubyLex
##
# Raised upon invalid input
class Error < RDoc::Error
end
# :stopdoc:
extend Exception2MessageMapper
def_exception(:AlreadyDefinedToken, "Already defined token(%s)")
def_exception(:TkReading2TokenNoKey, "key nothing(key='%s')")
def_exception(:TkSymbol2TokenNoKey, "key nothing(key='%s')")
def_exception(:TkReading2TokenDuplicateError,
"key duplicate(token_n='%s', key='%s')")
def_exception(:SyntaxError, "%s")
def_exception(:TerminateLineInput, "Terminate Line Input")
include RDoc::RubyToken
include IRB
attr_accessor :continue
attr_accessor :lex_state
attr_reader :reader
class << self
attr_accessor :debug_level
end
def self.debug?
@debug_level > 0
end
self.debug_level = 0
# :startdoc:
##
# Returns an Array of +ruby+ tokens. See ::new for a description of
# +options+.
def self.tokenize ruby, options
tokens = []
scanner = RDoc::RubyLex.new ruby, options
scanner.exception_on_syntax_error = true
while token = scanner.token do
tokens << token
end
tokens
end
##
# Creates a new lexer for +content+. +options+ is an RDoc::Options, only
# +tab_width is used.
def initialize(content, options)
lex_init
if /\t/ =~ content then
tab_width = options.tab_width
content = content.split(/\n/).map do |line|
1 while line.gsub!(/\t+/) {
' ' * (tab_width*$&.length - $`.length % tab_width)
} && $~
line
end.join("\n")
end
content << "\n" unless content[-1, 1] == "\n"
set_input StringIO.new content
@base_char_no = 0
@char_no = 0
@exp_line_no = @line_no = 1
@here_readed = []
@readed = []
@rests = []
@seek = 0
@here_header = false
@indent = 0
@indent_stack = []
@lex_state = :EXPR_BEG
@space_seen = false
@continue = false
@line = ""
@skip_space = false
@readed_auto_clean_up = false
@exception_on_syntax_error = true
@prompt = nil
@prev_seek = nil
@ltype = nil
end
# :stopdoc:
def inspect # :nodoc:
"#<%s:0x%x pos %d lex_state %p space_seen %p>" % [
self.class, object_id,
@io.pos, @lex_state, @space_seen,
]
end
attr_accessor :skip_space
attr_accessor :readed_auto_clean_up
attr_accessor :exception_on_syntax_error
attr_reader :seek
attr_reader :char_no
attr_reader :line_no
attr_reader :indent
# io functions
def set_input(io, p = nil, &block)
@io = io
if p.respond_to?(:call)
@input = p
elsif block_given?
@input = block
else
@input = Proc.new{@io.gets}
end
end
def get_readed
if idx = @readed.rindex("\n")
@base_char_no = @readed.size - (idx + 1)
else
@base_char_no += @readed.size
end
readed = @readed.join("")
@readed = []
readed
end
def getc
while @rests.empty?
# return nil unless buf_input
@rests.push nil unless buf_input
end
c = @rests.shift
if @here_header
@here_readed.push c
else
@readed.push c
end
@seek += 1
if c == "\n"
@line_no += 1
@char_no = 0
else
@char_no += 1
end
c
end
def gets
l = ""
while c = getc
l.concat(c)
break if c == "\n"
end
return nil if l == "" and c.nil?
l
end
def eof?
@io.eof?
end
def getc_of_rests
if @rests.empty?
nil
else
getc
end
end
def ungetc(c = nil)
if @here_readed.empty?
c2 = @readed.pop
else
c2 = @here_readed.pop
end
c = c2 unless c
@rests.unshift c #c =
@seek -= 1
if c == "\n"
@line_no -= 1
if idx = @readed.rindex("\n")
@char_no = idx + 1
else
@char_no = @base_char_no + @readed.size
end
else
@char_no -= 1
end
end
def peek_equal?(str)
chrs = str.split(//)
until @rests.size >= chrs.size
return false unless buf_input
end
@rests[0, chrs.size] == chrs
end
def peek_match?(regexp)
while @rests.empty?
return false unless buf_input
end
regexp =~ @rests.join("")
end
def peek(i = 0)
while @rests.size <= i
return nil unless buf_input
end
@rests[i]
end
def buf_input
prompt
line = @input.call
return nil unless line
@rests.concat line.split(//)
true
end
private :buf_input
def set_prompt(p = nil, &block)
p = block if block_given?
if p.respond_to?(:call)
@prompt = p
else
@prompt = Proc.new{print p}
end
end
def prompt
if @prompt
@prompt.call(@ltype, @indent, @continue, @line_no)
end
end
def initialize_input
@ltype = nil
@quoted = nil
@indent = 0
@indent_stack = []
@lex_state = :EXPR_BEG
@space_seen = false
@here_header = false
@continue = false
prompt
@line = ""
@exp_line_no = @line_no
end
def each_top_level_statement
initialize_input
catch(:TERM_INPUT) do
loop do
begin
@continue = false
prompt
unless l = lex
throw :TERM_INPUT if @line == ''
else
#p l
@line.concat l
if @ltype or @continue or @indent > 0
next
end
end
if @line != "\n"
yield @line, @exp_line_no
end
break unless l
@line = ''
@exp_line_no = @line_no
@indent = 0
@indent_stack = []
prompt
rescue TerminateLineInput
initialize_input
prompt
get_readed
end
end
end
end
def lex
until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) &&
!@continue or
tk.nil?)
#p tk
#p @lex_state
#p self
end
line = get_readed
# print self.inspect
if line == "" and tk.kind_of?(TkEND_OF_SCRIPT) || tk.nil?
nil
else
line
end
end
def token
# require "tracer"
# Tracer.on
@prev_seek = @seek
@prev_line_no = @line_no
@prev_char_no = @char_no
begin
begin
tk = @OP.match(self)
@space_seen = tk.kind_of?(TkSPACE)
rescue SyntaxError => e
raise Error, "syntax error: #{e.message}" if
@exception_on_syntax_error
tk = TkError.new(@seek, @line_no, @char_no)
end
end while @skip_space and tk.kind_of?(TkSPACE)
if @readed_auto_clean_up
get_readed
end
# Tracer.off
tk
end
ENINDENT_CLAUSE = [
"case", "class", "def", "do", "for", "if",
"module", "unless", "until", "while", "begin" #, "when"
]
DEINDENT_CLAUSE = ["end" #, "when"
]
PERCENT_LTYPE = {
"q" => "\'",
"Q" => "\"",
"x" => "\`",
"r" => "/",
"w" => "]",
"W" => "]",
"s" => ":"
}
PERCENT_PAREN = {
"{" => "}",
"[" => "]",
"<" => ">",
"(" => ")"
}
PERCENT_PAREN_REV = PERCENT_PAREN.invert
Ltype2Token = {
"\'" => TkSTRING,
"\"" => TkSTRING,
"\`" => TkXSTRING,
"/" => TkREGEXP,
"]" => TkDSTRING,
":" => TkSYMBOL
}
DLtype2Token = {
"\"" => TkDSTRING,
"\`" => TkDXSTRING,
"/" => TkDREGEXP,
}
def lex_init()
@OP = IRB::SLex.new
@OP.def_rules("\0", "\004", "\032") do |op, io|
Token(TkEND_OF_SCRIPT, '')
end
@OP.def_rules(" ", "\t", "\f", "\r", "\13") do |op, io|
@space_seen = true
str = op
while (ch = getc) =~ /[ \t\f\r\13]/ do
str << ch
end
ungetc
Token TkSPACE, str
end
@OP.def_rule("#") do |op, io|
identify_comment
end
@OP.def_rule("=begin",
proc{|op, io| @prev_char_no == 0 && peek(0) =~ /\s/}) do
|op, io|
@ltype = "="
res = ''
nil until getc == "\n"
until ( peek_equal?("=end") && peek(4) =~ /\s/ ) do
(ch = getc)
res << ch
end
gets # consume =end
@ltype = nil
Token(TkRD_COMMENT, res)
end
@OP.def_rule("\n") do |op, io|
print "\\n\n" if RDoc::RubyLex.debug?
case @lex_state
when :EXPR_BEG, :EXPR_FNAME, :EXPR_DOT
@continue = true
else
@continue = false
@lex_state = :EXPR_BEG
until (@indent_stack.empty? ||
[TkLPAREN, TkLBRACK, TkLBRACE,
TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
@indent_stack.pop
end
end
@here_header = false
@here_readed = []
Token(TkNL)
end
@OP.def_rules("*", "**",
"=", "==", "===",
"=~", "<=>",
"<", "<=",
">", ">=", ">>") do
|op, io|
case @lex_state
when :EXPR_FNAME, :EXPR_DOT
@lex_state = :EXPR_ARG
else
@lex_state = :EXPR_BEG
end
Token(op)
end
@OP.def_rules("!", "!=", "!~") do
|op, io|
@lex_state = :EXPR_BEG
Token(op)
end
@OP.def_rules("<<") do
|op, io|
tk = nil
if @lex_state != :EXPR_END && @lex_state != :EXPR_CLASS &&
(@lex_state != :EXPR_ARG || @space_seen)
c = peek(0)
if /\S/ =~ c && (/["'`]/ =~ c || /\w/ =~ c || c == "-")
tk = identify_here_document
end
end
unless tk
tk = Token(op)
case @lex_state
when :EXPR_FNAME, :EXPR_DOT
@lex_state = :EXPR_ARG
else
@lex_state = :EXPR_BEG
end
end
tk
end
@OP.def_rules("'", '"') do
|op, io|
identify_string(op)
end
@OP.def_rules("`") do
|op, io|
if @lex_state == :EXPR_FNAME
@lex_state = :EXPR_END
Token(op)
else
identify_string(op)
end
end
@OP.def_rules('?') do
|op, io|
if @lex_state == :EXPR_END
@lex_state = :EXPR_BEG
Token(TkQUESTION)
else
ch = getc
if @lex_state == :EXPR_ARG && ch =~ /\s/
ungetc
@lex_state = :EXPR_BEG;
Token(TkQUESTION)
else
@lex_state = :EXPR_END
Token(TkCHAR, "?#{ch}")
end
end
end
@OP.def_rules("&", "&&", "|", "||") do
|op, io|
@lex_state = :EXPR_BEG
Token(op)
end
@OP.def_rules("+=", "-=", "*=", "**=",
"&=", "|=", "^=", "<<=", ">>=", "||=", "&&=") do
|op, io|
@lex_state = :EXPR_BEG
op =~ /^(.*)=$/
Token(TkOPASGN, $1)
end
@OP.def_rule("+@", proc{|op, io| @lex_state == :EXPR_FNAME}) do
|op, io|
@lex_state = :EXPR_ARG
Token(op)
end
@OP.def_rule("-@", proc{|op, io| @lex_state == :EXPR_FNAME}) do
|op, io|
@lex_state = :EXPR_ARG
Token(op)
end
@OP.def_rules("+", "-") do
|op, io|
catch(:RET) do
if @lex_state == :EXPR_ARG
if @space_seen and peek(0) =~ /[0-9]/
throw :RET, identify_number(op)
else
@lex_state = :EXPR_BEG
end
elsif @lex_state != :EXPR_END and peek(0) =~ /[0-9]/
throw :RET, identify_number(op)
else
@lex_state = :EXPR_BEG
end
Token(op)
end
end
@OP.def_rule(".") do
|op, io|
@lex_state = :EXPR_BEG
if peek(0) =~ /[0-9]/
ungetc
identify_number
else
# for "obj.if" etc.
@lex_state = :EXPR_DOT
Token(TkDOT)
end
end
@OP.def_rules("..", "...") do
|op, io|
@lex_state = :EXPR_BEG
Token(op)
end
lex_int2
end
def lex_int2
@OP.def_rules("]", "}", ")") do
|op, io|
@lex_state = :EXPR_END
@indent -= 1
@indent_stack.pop
Token(op)
end
@OP.def_rule(":") do
|op, io|
if @lex_state == :EXPR_END || peek(0) =~ /\s/
@lex_state = :EXPR_BEG
Token(TkCOLON)
else
@lex_state = :EXPR_FNAME;
Token(TkSYMBEG)
end
end
@OP.def_rule("::") do
|op, io|
# p @lex_state.id2name, @space_seen
if @lex_state == :EXPR_BEG or @lex_state == :EXPR_ARG && @space_seen
@lex_state = :EXPR_BEG
Token(TkCOLON3)
else
@lex_state = :EXPR_DOT
Token(TkCOLON2)
end
end
@OP.def_rule("/") do
|op, io|
if @lex_state == :EXPR_BEG || @lex_state == :EXPR_MID
identify_string(op)
elsif peek(0) == '='
getc
@lex_state = :EXPR_BEG
Token(TkOPASGN, "/") #/)
elsif @lex_state == :EXPR_ARG and @space_seen and peek(0) !~ /\s/
identify_string(op)
else
@lex_state = :EXPR_BEG
Token("/") #/)
end
end
@OP.def_rules("^") do
|op, io|
@lex_state = :EXPR_BEG
Token("^")
end
# @OP.def_rules("^=") do
# @lex_state = :EXPR_BEG
# Token(OP_ASGN, :^)
# end
@OP.def_rules(",") do
|op, io|
@lex_state = :EXPR_BEG
Token(op)
end
@OP.def_rules(";") do
|op, io|
@lex_state = :EXPR_BEG
until (@indent_stack.empty? ||
[TkLPAREN, TkLBRACK, TkLBRACE,
TkfLPAREN, TkfLBRACK, TkfLBRACE].include?(@indent_stack.last))
@indent_stack.pop
end
Token(op)
end
@OP.def_rule("~") do
|op, io|
@lex_state = :EXPR_BEG
Token("~")
end
@OP.def_rule("~@", proc{|op, io| @lex_state == :EXPR_FNAME}) do
|op, io|
@lex_state = :EXPR_BEG
Token("~")
end
@OP.def_rule("(") do
|op, io|
@indent += 1
if @lex_state == :EXPR_BEG || @lex_state == :EXPR_MID
@lex_state = :EXPR_BEG
tk_c = TkfLPAREN
else
@lex_state = :EXPR_BEG
tk_c = TkLPAREN
end
@indent_stack.push tk_c
Token tk_c
end
@OP.def_rule("[]", proc{|op, io| @lex_state == :EXPR_FNAME}) do
|op, io|
@lex_state = :EXPR_ARG
Token("[]")
end
@OP.def_rule("[]=", proc{|op, io| @lex_state == :EXPR_FNAME}) do
|op, io|
@lex_state = :EXPR_ARG
Token("[]=")
end
@OP.def_rule("[") do
|op, io|
@indent += 1
if @lex_state == :EXPR_FNAME
tk_c = TkfLBRACK
else
if @lex_state == :EXPR_BEG || @lex_state == :EXPR_MID
tk_c = TkLBRACK
elsif @lex_state == :EXPR_ARG && @space_seen
tk_c = TkLBRACK
else
tk_c = TkfLBRACK
end
@lex_state = :EXPR_BEG
end
@indent_stack.push tk_c
Token(tk_c)
end
@OP.def_rule("{") do
|op, io|
@indent += 1
if @lex_state != :EXPR_END && @lex_state != :EXPR_ARG
tk_c = TkLBRACE
else
tk_c = TkfLBRACE
end
@lex_state = :EXPR_BEG
@indent_stack.push tk_c
Token(tk_c)
end
@OP.def_rule('\\') do
|op, io|
if getc == "\n"
@space_seen = true
@continue = true
Token(TkSPACE)
else
ungetc
Token("\\")
end
end
@OP.def_rule('%') do
|op, io|
if @lex_state == :EXPR_BEG || @lex_state == :EXPR_MID
identify_quotation
elsif peek(0) == '='
getc
Token(TkOPASGN, :%)
elsif @lex_state == :EXPR_ARG and @space_seen and peek(0) !~ /\s/
identify_quotation
else
@lex_state = :EXPR_BEG
Token("%") #))
end
end
@OP.def_rule('$') do
|op, io|
identify_gvar
end
@OP.def_rule('@') do
|op, io|
if peek(0) =~ /[\w@]/
ungetc
identify_identifier
else
Token("@")
end
end
# @OP.def_rule("def", proc{|op, io| /\s/ =~ io.peek(0)}) do
# |op, io|
# @indent += 1
# @lex_state = :EXPR_FNAME
# # @lex_state = :EXPR_END
# # until @rests[0] == "\n" or @rests[0] == ";"
# # rests.shift
# # end
# end
@OP.def_rule("_") do
if peek_match?(/_END__/) and @lex_state == :EXPR_BEG then
6.times { getc }
Token(TkEND_OF_SCRIPT, '__END__')
else
ungetc
identify_identifier
end
end
@OP.def_rule("") do
|op, io|
printf "MATCH: start %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
if peek(0) =~ /[0-9]/
t = identify_number
else
t = identify_identifier
end
printf "MATCH: end %s: %s\n", op, io.inspect if RDoc::RubyLex.debug?
t
end
p @OP if RDoc::RubyLex.debug?
end
def identify_gvar
@lex_state = :EXPR_END
case ch = getc
when /[~_*$?!@\/\\;,=:<>".]/ #"
Token(TkGVAR, "$" + ch)
when "-"
Token(TkGVAR, "$-" + getc)
when "&", "`", "'", "+"
Token(TkBACK_REF, "$"+ch)
when /[1-9]/
ref = ch
while (ch = getc) =~ /[0-9]/ do ref << ch end
ungetc
Token(TkNTH_REF, "$#{ref}")
when /\w/
ungetc
ungetc
identify_identifier
else
ungetc
Token("$")
end
end
IDENT_RE = if defined? Encoding then
eval '/[\w\u{0080}-\u{FFFFF}]/u' # 1.8 can't parse \u{}
else
/[\w\x80-\xFF]/
end
def identify_identifier
token = ""
if peek(0) =~ /[$@]/
token.concat(c = getc)
if c == "@" and peek(0) == "@"
token.concat getc
end
end
while (ch = getc) =~ IDENT_RE do
print " :#{ch}: " if RDoc::RubyLex.debug?
token.concat ch
end
ungetc
if (ch == "!" || ch == "?") && token[0,1] =~ /\w/ && peek(0) != "="
token.concat getc
end
# almost fix token
case token
when /^\$/
return Token(TkGVAR, token)
when /^\@\@/
@lex_state = :EXPR_END
# p Token(TkCVAR, token)
return Token(TkCVAR, token)
when /^\@/
@lex_state = :EXPR_END
return Token(TkIVAR, token)
end
if @lex_state != :EXPR_DOT
print token, "\n" if RDoc::RubyLex.debug?
token_c, *trans = TkReading2Token[token]
if token_c
# reserved word?
if (@lex_state != :EXPR_BEG &&
@lex_state != :EXPR_FNAME &&
trans[1])
# modifiers
token_c = TkSymbol2Token[trans[1]]
@lex_state = trans[0]
else
if @lex_state != :EXPR_FNAME
if ENINDENT_CLAUSE.include?(token)
valid = peek(0) != ':'
# check for ``class = val'' etc.
case token
when "class"
valid = false unless peek_match?(/^\s*(<<|\w|::)/)
when "def"
valid = false if peek_match?(/^\s*(([+-\/*&\|^]|<<|>>|\|\||\&\&)=|\&\&|\|\|)/)
when "do"
valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&)/)
when *ENINDENT_CLAUSE
valid = false if peek_match?(/^\s*([+-\/*]?=|\*|<|>|\&|\|)/)
else
# no nothing
end if valid
if valid
if token == "do"
if ![TkFOR, TkWHILE, TkUNTIL].include?(@indent_stack.last)
@indent += 1
@indent_stack.push token_c
end
else
@indent += 1
@indent_stack.push token_c
end
else
token_c = TkIDENTIFIER
end
elsif DEINDENT_CLAUSE.include?(token)
@indent -= 1
@indent_stack.pop
end
@lex_state = trans[0]
else
@lex_state = :EXPR_END
end
end
return Token(token_c, token)
end
end
if @lex_state == :EXPR_FNAME
@lex_state = :EXPR_END
if peek(0) == '='
token.concat getc
end
elsif @lex_state == :EXPR_BEG || @lex_state == :EXPR_DOT ||
@lex_state == :EXPR_ARG
@lex_state = :EXPR_ARG
else
@lex_state = :EXPR_END
end
if token[0, 1] =~ /[A-Z]/
return Token(TkCONSTANT, token)
elsif token[token.size - 1, 1] =~ /[!?]/
return Token(TkFID, token)
else
return Token(TkIDENTIFIER, token)
end
end
def identify_here_document
ch = getc
# if lt = PERCENT_LTYPE[ch]
if ch == "-"
ch = getc
indent = true
end
if /['"`]/ =~ ch
user_quote = lt = ch
quoted = ""
while (c = getc) && c != lt
quoted.concat c
end
else
user_quote = nil
lt = '"'
quoted = ch.dup
while (c = getc) && c =~ /\w/
quoted.concat c
end
ungetc
end
ltback, @ltype = @ltype, lt
reserve = []
while ch = getc
reserve.push ch
if ch == "\\"
reserve.push ch = getc
elsif ch == "\n"
break
end
end
output_heredoc = reserve.join =~ /\A\r?\n\z/
if output_heredoc then
doc = '<<'
doc << '-' if indent
doc << "#{user_quote}#{quoted}#{user_quote}\n"
else
doc = '"'
end
@here_header = false
while l = gets
l = l.sub(/(:?\r)?\n\z/, "\n")
if (indent ? l.strip : l.chomp) == quoted
break
end
doc << l
end
if output_heredoc then
raise Error, "Missing terminating #{quoted} for string" unless l
doc << l.chomp
else
doc << '"'
end
@here_header = true
@here_readed.concat reserve
while ch = reserve.pop
ungetc ch
end
token_class = output_heredoc ? RDoc::RubyLex::TkHEREDOC : Ltype2Token[lt]
@ltype = ltback
@lex_state = :EXPR_END
Token(token_class, doc)
end
def identify_quotation
type = ch = getc
if lt = PERCENT_LTYPE[type]
ch = getc
elsif type =~ /\W/
type = nil
lt = "\""
else
return Token(TkMOD, '%')
end
# if ch !~ /\W/
# ungetc
# next
# end
#@ltype = lt
@quoted = ch unless @quoted = PERCENT_PAREN[ch]
identify_string(lt, @quoted, type)
end
def identify_number(op = "")
@lex_state = :EXPR_END
num = op
if peek(0) == "0" && peek(1) !~ /[.eE]/
num << getc
case peek(0)
when /[xX]/
ch = getc
match = /[0-9a-fA-F_]/
when /[bB]/
ch = getc
match = /[01_]/
when /[oO]/
ch = getc
match = /[0-7_]/
when /[dD]/
ch = getc
match = /[0-9_]/
when /[0-7]/
match = /[0-7_]/
when /[89]/
raise Error, "Illegal octal digit"
else
return Token(TkINTEGER, num)
end
num << ch if ch
len0 = true
non_digit = false
while ch = getc
num << ch
if match =~ ch
if ch == "_"
if non_digit
raise Error, "trailing `#{ch}' in number"
else
non_digit = ch
end
else
non_digit = false
len0 = false
end
else
ungetc
num[-1, 1] = ''
if len0
raise Error, "numeric literal without digits"
end
if non_digit
raise Error, "trailing `#{non_digit}' in number"
end
break
end
end
return Token(TkINTEGER, num)
end
type = TkINTEGER
allow_point = true
allow_e = true
non_digit = false
while ch = getc
num << ch
case ch
when /[0-9]/
non_digit = false
when "_"
non_digit = ch
when allow_point && "."
if non_digit
raise Error, "trailing `#{non_digit}' in number"
end
type = TkFLOAT
if peek(0) !~ /[0-9]/
type = TkINTEGER
ungetc
num[-1, 1] = ''
break
end
allow_point = false
when allow_e && "e", allow_e && "E"
if non_digit
raise Error, "trailing `#{non_digit}' in number"
end
type = TkFLOAT
if peek(0) =~ /[+-]/
num << getc
end
allow_e = false
allow_point = false
non_digit = ch
else
if non_digit
raise Error, "trailing `#{non_digit}' in number"
end
ungetc
num[-1, 1] = ''
break
end
end
Token(type, num)
end
def identify_string(ltype, quoted = ltype, type = nil)
close = PERCENT_PAREN.values.include?(quoted)
@ltype = ltype
@quoted = quoted
str = if ltype == quoted and %w[" ' /].include? ltype then
ltype.dup
elsif RUBY_VERSION > '1.9' then
"%#{type or PERCENT_LTYPE.key ltype}#{PERCENT_PAREN_REV[quoted]||quoted}"
else
"%#{type or PERCENT_LTYPE.index ltype}#{PERCENT_PAREN_REV[quoted]||quoted}"
end
subtype = nil
begin
nest = 0
while ch = getc
str << ch
if @quoted == ch and nest <= 0
break
elsif @ltype != "'" && @ltype != "]" && @ltype != ":" and ch == "#"
ch = getc
subtype = true
if ch == "{" then
str << ch << skip_inner_expression
next
else
ungetc
end
elsif ch == '\\'
if %w[' /].include? @ltype then
case ch = getc
when "\\", "\n", "'"
when @ltype
str << ch
else
ungetc
end
else
str << read_escape
end
end
if close then
if PERCENT_PAREN[ch] == @quoted
nest += 1
elsif ch == @quoted
nest -= 1
end
end
end
if @ltype == "/"
while peek(0) =~ /i|m|x|o|e|s|u|n/
str << getc
end
end
if subtype
Token(DLtype2Token[ltype], str)
else
Token(Ltype2Token[ltype], str)
end
ensure
@ltype = nil
@quoted = nil
@lex_state = :EXPR_END
end
end
def skip_inner_expression
res = ""
nest = 0
while ch = getc
res << ch
if ch == '}'
break if nest.zero?
nest -= 1
elsif ch == '{'
nest += 1
end
end
res
end
def identify_comment
@ltype = "#"
comment = '#'
while ch = getc
# if ch == "\\" #"
# read_escape
# end
if ch == "\n"
@ltype = nil
ungetc
break
end
comment << ch
end
return Token(TkCOMMENT, comment)
end
def read_escape
escape = ''
ch = getc
case ch
when "\n", "\r", "\f"
escape << ch
when "\\", "n", "t", "r", "f", "v", "a", "e", "b", "s" #"
escape << ch
when /[0-7]/
ungetc ch
3.times do
ch = getc
case ch
when /[0-7]/
escape << ch
when nil
break
else
ungetc
break
end
end
when "x"
escape << ch
2.times do
ch = getc
case ch
when /[0-9a-fA-F]/
escape << ch
when nil
break
else
ungetc
break
end
end
when "M"
escape << ch
ch = getc
if ch != '-'
ungetc
else
escape << ch
ch = getc
if ch == "\\" #"
ungetc
escape << read_escape
else
escape << ch
end
end
when "C", "c" #, "^"
escape << ch
if ch == "C"
ch = getc
if ch == "-"
escape << ch
ch = getc
escape << ch
escape << read_escape if ch == "\\"
else
ungetc
end
elsif (ch = getc) == "\\" #"
escape << ch << read_escape
end
else
escape << ch
# other characters
end
escape
end
# :startdoc:
end
#RDoc::RubyLex.debug_level = 1