#!/usr/local/bin/ruby # # rbc.rb - # $Release Version: 0.8 $ # $Revision: 1.8 $ # $Date: 1998/03/11 05:43:00 $ # by Keiju ISHITSUKA(Nippon Rational Inc.) # # -- # Usage: # # rbc.rb [options] file_name opts # options: # -d debug mode (not recommended) # -f does not read ~/.irbrc # -m bc mode (rational/matrix calc) # -r load-module same as `ruby -r' # --inspect use inspect for result output # (default for non-bc mode) # --noinspect does not use inspect for result output # --noreadline does not use readline library # (default: try to use readline) # # additional private method (as function): # exit, quit terminate the interpreter # inspect_mode(sw = nil) toggle inspect mode # trace_load(sw = nil) change trace mode for file loading using # load/require. (default: trace-mode on) # require "e2mmap.rb" $stdout.sync = TRUE module BC_APPLICATION__ RCS_ID='-$Id: rbc.rb,v 1.8 1998/03/11 05:43:00 keiju Exp keiju $-' extend Exception2MessageMapper def_exception :UnrecognizedSwitch, "Unrecognized switch: %s" CONFIG = {} CONFIG[0] = $0 CONFIG[:USE_READLINE] = TRUE CONFIG[:LOAD_MODULES] = [] CONFIG[:INSPECT] = nil CONFIG[:TRACE_LOAD] = FALSE CONFIG[:RC] = TRUE CONFIG[:DEBUG] = FALSE while opt = ARGV.shift case opt when "-d" CONFIG[:DEBUG] = TRUE when "-m" CONFIG[:INSPECT] = FALSE if CONFIG[:INSPECT].nil? require "mathn.rb" include Math when "-r" opt = ARGV.shift CONFIG[:LOAD_MODULES].push opt if opt when "-f" opt = ARGV.shift CONFIG[:RC] = FALSE when "--inspect" CONFIG[:INSPECT] = TRUE when "--noinspect" CONFIG[:INSPECT] = FALSE when "--noreadline" CONFIG[:USE_READLINE] = FALSE when /^-/ # print UnrecognizedSwitch.inspect, "\n" BC_APPLICATION__.fail UnrecognizedSwitch, opt else CONFIG[:USE_READLINE] = FALSE $0 = opt break end end CONFIG[:INSPECT] = TRUE if CONFIG[:INSPECT].nil? PROMPTi = "rbc%d> " PROMPTs = "rbc%d%s " PROMPTe = "rbc%d* " class BC def initialize lex_init end def eval_input(io, cont, bind) line = '' @io = io @ltype = nil @quoted = nil @indent = 0 @lex_state = EXPR_BEG @io.prompt = format(PROMPTi, @indent) loop do @continue = FALSE l = @io.gets unless l break if line == '' else line = line + l lex(l) if l != "\n" print @quoted.inspect, "\n" if CONFIG[:DEBUG] if @ltype @io.prompt = format(PROMPTs, @indent, @ltype) next elsif @continue @io.prompt = format(PROMPTe, @indent) next elsif @indent > 0 @io.prompt = format(PROMPTi, @indent) next end end if line != "\n" begin if CONFIG[:INSPECT] print((cont._=eval(line, bind)).inspect, "\n") else print((cont._=eval(line, bind)), "\n") end rescue # $! = 'exception raised' unless $! # print "ERR: ", $!, "\n" $! = RuntimeError.new("exception raised") unless $! print $!.type, ": ", $!, "\n" end end break if not l line = '' indent = 0 @io.prompt = format(PROMPTi, indent) end print "\n" end EXPR_BEG = :EXPR_BEG EXPR_MID = :EXPR_MID EXPR_END = :EXPR_END EXPR_ARG = :EXPR_ARG EXPR_FNAME = :EXPR_FNAME CLAUSE_STATE_TRANS = { "alias" => EXPR_FNAME, "and" => EXPR_BEG, "begin" => EXPR_BEG, "case" => EXPR_BEG, "class" => EXPR_BEG, "def" => EXPR_FNAME, "defined?" => EXPR_END, "do" => EXPR_BEG, "else" => EXPR_BEG, "elsif" => EXPR_BEG, "end" => EXPR_END, "ensure" => EXPR_BEG, "for" => EXPR_BEG, "if" => EXPR_BEG, "in" => EXPR_BEG, "module" => EXPR_BEG, "nil" => EXPR_END, "not" => EXPR_BEG, "or" => EXPR_BEG, "rescue" => EXPR_MID, "return" => EXPR_MID, "self" => EXPR_END, "super" => EXPR_END, "then" => EXPR_BEG, "undef" => EXPR_FNAME, "unless" => EXPR_BEG, "until" => EXPR_BEG, "when" => EXPR_BEG, "while" => EXPR_BEG, "yield" => EXPR_END } ENINDENT_CLAUSE = [ "case", "class", "def", "do", "for", "if", "module", "unless", "until", "while", "begin" #, "when" ] DEINDENT_CLAUSE = ["end" #, "when" ] PARCENT_LTYPE = { "q" => "\'", "Q" => "\"", "x" => "\`", "r" => "\/" } PARCENT_PAREN = { "{" => "}", "[" => "]", "<" => ">", "(" => ")" } def lex_init() @OP = Trie.new @OP.def_rules("\0", "\004", "\032"){} @OP.def_rules(" ", "\t", "\f", "\r", "\13") do @space_seen = TRUE next end @OP.def_rule("#") do |op, rests| @ltype = "#" identify_comment(rests) end @OP.def_rule("\n") do print "\\n\n" if CONFIG[:DEBUG] if @lex_state == EXPR_BEG || @lex_state == EXPR_FNAME @continue = TRUE else @lex_state = EXPR_BEG end end @OP.def_rules("*", "*=", "**=", "**") {@lex_state = EXPR_BEG} @OP.def_rules("!", "!=", "!~") {@lex_state = EXPR_BEG} @OP.def_rules("=", "==", "===", "=~", "<=>") {@lex_state = EXPR_BEG} @OP.def_rules("<", "<=", "<<") {@lex_state = EXPR_BEG} @OP.def_rules(">", ">=", ">>") {@lex_state = EXPR_BEG} @OP.def_rules("'", '"') do |op, rests| @ltype = op @quoted = op identify_string(rests) end @OP.def_rules("`") do |op, rests| if @lex_state != EXPR_FNAME @ltype = op @quoted = op identify_string(rests) end end @OP.def_rules('?') do |op, rests| @lex_state = EXPR_END identify_question(rests) end @OP.def_rules("&", "&&", "&=", "|", "||", "|=") do @lex_state = EXPR_BEG end @OP.def_rule("+@", proc{@lex_state == EXPR_FNAME}) {} @OP.def_rule("-@", proc{@lex_state == EXPR_FNAME}) {} @OP.def_rules("+=", "-=") {@lex_state = EXPR_BEG} @OP.def_rules("+", "-") do |op, rests| if @lex_state == EXPR_ARG if @space_seen and rests[0] =~ /[0-9]/ identify_number(rests) else @lex_state = EXPR_BEG end elsif @lex_state != EXPR_END and rests[0] =~ /[0-9]/ identify_number(rests) else @lex_state = EXPR_BEG end end @OP.def_rule(".") do |op, rests| @lex_state = EXPR_BEG if rests[0] =~ /[0-9]/ rests.unshift op identify_number(rests) else # handle ``obj.if'' and such identify_identifier(rests, TRUE) @lex_state = EXPR_ARG end end @OP.def_rules("..", "...") {@lex_state = EXPR_BEG} lex_int2 end def lex_int2 @OP.def_rules("]", "}", ")") do @lex_state = EXPR_END @indent -= 1 end @OP.def_rule(":") {|op,rests| identify_identifier(rests, TRUE) } @OP.def_rule("::") {|op,rests| identify_identifier(rests, TRUE); } @OP.def_rule("/") do |op, rests| if @lex_state == EXPR_BEG || @lex_state == EXPR_MID @ltype = op @quoted = op identify_string(rests) elsif rests[0] == '=' rests.shift @lex_state = EXPR_BEG elsif @lex_state == EXPR_ARG and @space_seen and rests[0] =~ /\s/ @ltype = op @quoted = op identify_string(rests) else @lex_state = EXPR_BEG end end @OP.def_rules("^", "^=") {@lex_state = EXPR_BEG} @OP.def_rules(",", ";") {@lex_state = EXPR_BEG} @OP.def_rule("~") {@lex_state = EXPR_BEG} @OP.def_rule("~@", proc{@lex_state = EXPR_FNAME}) {} @OP.def_rule("(") do @lex_state = EXPR_BEG @indent += 1 end @OP.def_rule("[]", proc{@lex_state == EXPR_FNAME}) {} @OP.def_rule("[]=", proc{@lex_state == EXPR_FNAME}) {} @OP.def_rule("[") do @indent += 1 if @lex_state != EXPR_FNAME @lex_state = EXPR_BEG end end @OP.def_rule("{") do @lex_state = EXPR_BEG @indent += 1 end @OP.def_rule('\\') {|op, rests| identify_escape(rests)} #') @OP.def_rule('%') do |op, rests| if @lex_state == EXPR_BEG || @lex_state == EXPR_MID identify_quotation(rests) elsif rests[0] == '=' rests.shift elsif @lex_state == EXPR_ARG and @space_seen and rests[0] =~ /\s/ identify_quotation(rests) else @lex_state = EXPR_BEG end end @OP.def_rule('$') do #' |op, rests| identify_gvar(rests) end @OP.def_rule('@') do |op, rests| if rests[0] =~ /[\w_]/ rests.unshift op identify_identifier(rests) end end @OP.def_rule("def", proc{|op, chrs| /\s/ =~ chrs[0]}) do |op, rests| @indent += 1 @lex_state = EXPR_END until rests[0] == "\n" or rests[0] == ";" rests.shift end end @OP.def_rule("") do |op, rests| printf "MATCH: start %s: %s\n", op, rests.inspect if CONFIG[:DEBUG] if rests[0] =~ /[0-9]/ identify_number(rests) elsif rests[0] =~ /[\w_]/ identify_identifier(rests) end printf "MATCH: end %s: %s\n", op, rests.inspect if CONFIG[:DEBUG] end p @OP if CONFIG[:DEBUG] end def lex(l) chrs = l.split(//) tokens = [] case @ltype when "'", '"', '`', '/' identify_string(chrs) return if chrs.empty? when "#" identify_comment(chrs) return when "=" if l =~ /^=end/ $ltype = nil return end else if l =~ /^=begin/ $ltype = "=" return end end until chrs.empty? @space_seen = FALSE printf "perse: %s\n", chrs.join("") if CONFIG[:DEBUG] @OP.match(chrs) printf "lex_state: %s continue: %s\n", @lex_state.id2name, @continue if CONFIG[:DEBUG] end end def identify_gvar(chrs) @lex_state = EXPR_END ch = chrs.shift case ch when /[_~*$?!@/\\;,.=:<>"]/ #" return when "-" ch = chrs.shift return when "&", "`", "'", "+" return when /[1-9]/ chrs.unshift ch v = "$" while (ch = chrs.shift) =~ /[0-9]/ end chrs.unshift ch return when /\w/ chrs.unshift ch chrs.unshift "$" identify_identifier(chrs) return else chrs.unshift ch return end end def identify_identifier(chrs, escaped = FALSE) token = "" token.concat chrs.shift if chrs[0] =~ /[$@]/ or escaped while (ch = chrs.shift) =~ /\w|_/ print ":", ch, ":" if CONFIG[:DEBUG] token.concat ch end chrs.unshift ch if ch == "!" or ch == "?" chrs.shift token.concat ch end # fix token if token =~ /^[$@]/ or escaped @lex_state = EXPR_END return end print token, "\n" if CONFIG[:DEBUG] if state = CLAUSE_STATE_TRANS[token] if @lex_state != EXPR_BEG and token =~ /^(if|unless|while|until)/ # modifiers else if ENINDENT_CLAUSE.include?(token) @indent += 1 elsif DEINDENT_CLAUSE.include?(token) @indent -= 1 end end @lex_state = state return end if @lex_state == EXPR_FNAME @lex_state = EXPR_END if chrs[0] == '=' chrs.shift end elsif @lex_state == EXPR_BEG @lex_state = EXPR_ARG else @lex_state = EXPR_END end end def identify_quotation(chrs) ch = chrs.shift if lt = PARCENT_LTYPE[ch] ch = chrs.shift else lt = "\"" end if ch !~ /\W/ chrs.unshift ch next end @ltype = lt unless @quoted = PARCENT_PAREN[ch] @quoted = ch end identify_string(chrs) end def identify_number(chrs) @lex_state = EXPR_END ch = chrs.shift case ch when /0/ if (ch = chrs[0]) == "x" chrs.shift match = /[0-9a-f_]/ else match = /[0-7_]/ end while ch = chrs.shift if ch !~ match chrs.unshift ch break end end return end while ch = chrs.shift case ch when /[0-9]/ when "e", "E" # type = FLOAT unless (ch = chrs.shift) == "+" or ch == "-" chrs.unshift ch end when "." # type = FLOAT when "_" else chrs.unshift ch return end end end def identify_question(chrs) @lex_state = EXPR_END if chrs.shift == "\\" #" identify_escape(chrs) end end def identify_string(chrs) while ch = chrs.shift if @quoted == ch if @ltype == "/" if chrs[0] =~ /i|o|n|e|s/ chrs.shift end end @ltype = nil @quoted = nil @lex_state = EXPR_END break elsif ch == '\\' #' identify_escape(chrs) end end end def identify_comment(chrs) while ch = chrs.shift if ch == "\\" #" identify_escape(chrs) end if ch == "\n" @ltype = nil chrs.unshift ch break end end end def identify_escape(chrs) ch = chrs.shift case ch when "\n", "\r", "\f" @continue = TRUE when "\\", "n", "t", "r", "f", "v", "a", "e", "b" #" when /[0-7]/ chrs.unshift ch 3.times do ch = chrs.shift case ch when /[0-7]/ when nil break else chrs.unshift ch break end end when "x" 2.times do ch = chrs.shift case ch when /[0-9a-fA-F]/ when nil break else chrs.unshift ch break end end when "M" if (ch = chrs.shift) != '-' chrs.unshift ch elsif (ch = chrs.shift) == "\\" #" identify_escape(chrs) end return when "C", "c", "^" if ch == "C" and (ch = chrs.shift) != "-" chrs.unshift ch elsif (ch = chrs.shift) == "\\" #" identify_escape(chrs) end return end end end class Trie extend Exception2MessageMapper def_exception :ErrNodeNothing, "node nothing" def_exception :ErrNodeAlreadyExists, "node already exists" class Node # abstract node if postproc is nil. def initialize(preproc = nil, postproc = nil) @Tree = {} @preproc = preproc @postproc = postproc end attr :preproc, TRUE attr :postproc, TRUE def search(chrs, opt = nil) return self if chrs.empty? ch = chrs.shift if node = @Tree[ch] node.search(chrs, opt) else if opt chrs.unshift ch self.create_subnode(chrs) else Trie.fail ErrNodeNothing end end end def create_subnode(chrs, preproc = nil, postproc = nil) if chrs.empty? if @postproc p node Trie.fail ErrNodeAlreadyExists else print "Warn: change abstruct node to real node\n" if CONFIG[:DEBUG] @preproc = preproc @postproc = postproc end return self end ch = chrs.shift if node = @Tree[ch] if chrs.empty? if node.postproc p node Trie.fail ErrNodeAlreadyExists else print "Warn: change abstruct node to real node\n" if CONFIG[:DEBUG] node.preproc = preproc node.postproc = postproc end else node.create_subnode(chrs, preproc, postproc) end else if chrs.empty? node = Node.new(preproc, postproc) else node = Node.new node.create_subnode(chrs, preproc, postproc) end @Tree[ch] = node end node end def match(chrs, op = "") print "match>: ", chrs, "op:", op, "\n" if CONFIG[:DEBUG] if chrs.empty? if @preproc.nil? || @preproc.call(op, chrs) printf "op1: %s\n", op if CONFIG[:DEBUG] @postproc.call(op, chrs) "" else nil end else ch = chrs.shift if node = @Tree[ch] if ret = node.match(chrs, op+ch) return ch+ret else chrs.unshift ch if @postproc and @preproc.nil? || @preproc.call(op, chrs) printf "op2: %s\n", op.inspect if CONFIG[:DEBUG] @postproc.call(op, chrs) return "" else return nil end end else chrs.unshift ch if @postproc and @preproc.nil? || @preproc.call(op, chrs) printf "op3: %s\n", op if CONFIG[:DEBUG] @postproc.call(op, chrs) return "" else return nil end end end end end def initialize @head = Node.new("") end def def_rule(token, preproc = nil, postproc = nil) # print node.inspect, "\n" if CONFIG[:DEBUG] postproc = proc if iterator? node = create(token, preproc, postproc) end def def_rules(*tokens) if iterator? p = proc end for token in tokens def_rule(token, nil, p) end end def preporc(token, proc) node = search(token) node.preproc=proc end def postproc(token) node = search(token, proc) node.postproc=proc end def search(token) @head.search(token.split(//)) end def create(token, preproc = nil, postproc = nil) @head.create_subnode(token.split(//), preproc, postproc) end def match(token) token = token.split(//) if token.kind_of?(String) ret = @head.match(token) printf "match end: %s:%s", ret, token.inspect if CONFIG[:DEBUG] ret end def inspect format("", @head.inspect) end end if /^-tt(.*)$/ =~ ARGV[0] # Tracer.on case $1 when "1" tr = Trie.new print "0: ", tr.inspect, "\n" tr.def_rule("=") {print "=\n"} print "1: ", tr.inspect, "\n" tr.def_rule("==") {print "==\n"} print "2: ", tr.inspect, "\n" print "case 1:\n" print tr.match("="), "\n" print "case 2:\n" print tr.match("=="), "\n" print "case 3:\n" print tr.match("=>"), "\n" when "2" tr = Trie.new print "0: ", tr.inspect, "\n" tr.def_rule("=") {print "=\n"} print "1: ", tr.inspect, "\n" tr.def_rule("==", proc{FALSE}) {print "==\n"} print "2: ", tr.inspect, "\n" print "case 1:\n" print tr.match("="), "\n" print "case 2:\n" print tr.match("=="), "\n" print "case 3:\n" print tr.match("=>"), "\n" end exit end module CONTEXT def _=(value) CONFIG[:_] = value eval "_=BC_APPLICATION__::CONFIG[:_]", CONFIG[:BIND] end # def _ # eval "_", CONFIG[:BIND] # end def quit exit end def trace_load(opt = nil) if !opt.nil? CONFIG[:TRACE_LOAD] = opt else CONFIG[:TRACE_LOAD] = !CONFIG[:TRACE_LOAD] end print "Switch to load/require #{unless CONFIG[:TRACE_LOAD]; ' non';end} trace mode.\n" if CONFIG[:TRACE_LOAD] eval %{ class << self alias load rbc_load alias require rbc_require end } else eval %{ class << self alias load rbc_load_org alias require rbc_require_org end } end CONFIG[:TRACE_LOAD] end alias rbc_load_org load def rbc_load(file_name) return true if load_sub(file_name) raise LoadError, "No such file to load -- #{file_name}" end alias rbc_require_org require def rbc_require(file_name) rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") return false if $".find{|f| f =~ rex} case file_name when /\.rb$/ if load_sub(file_name) $".push file_name return true end when /\.(so|o|sl)$/ rbc_require_org(file_name) end if load_sub(f = file_name + ".rb") $".push f return true end rbc_require_org(file_name) end def load_sub(fn) if fn =~ /^#{Regexp.quote(File::Separator)}/ return false unless File.exist?(fn) BC.new.eval_input FileInputMethod.new(fn), self, CONFIG[:BIND] return true end for path in $: if File.exist?(f = File.join(path, fn)) BC.new.eval_input FileInputMethod.new(f), self, CONFIG[:BIND] return true end end return false end def inspect_mode(opt = nil) if opt CONFIG[:INSPECT] = opt else CONFIG[:INSPECT] = !CONFIG[:INSPECT] end print "Switch to#{unless CONFIG[:INSPECT]; ' non';end} inspect mode.\n" CONFIG[:INSPECT] end def run(bind) CONFIG[:BIND] = bind if CONFIG[:RC] rc = File.expand_path("~/.irbrc") if File.exists?(rc) begin load rc rescue print "load error: #{rc}\n" print $!.type, ": ", $!, "\n" for err in $@[0, $@.size - 2] print "\t", err, "\n" end end end end if CONFIG[:TRACE_LOAD] trace_load true end for m in CONFIG[:LOAD_MODULES] begin require m rescue print $@[0], ":", $!.type, ": ", $!, "\n" end end if !$0.equal?(CONFIG[0]) io = FileInputMethod.new($0) elsif defined? Readline io = ReadlineInputMethod.new else io = StdioInputMethod.new end BC.new.eval_input io, self, CONFIG[:BIND] end end class InputMethod attr :prompt, TRUE def gets end public :gets end class StdioInputMethod < InputMethod def gets print @prompt $stdin.gets end end class FileInputMethod < InputMethod def initialize(file) @io = open(file) end def gets l = @io.gets print @prompt, l l end end if CONFIG[:USE_READLINE] begin require "readline" print "use readline module\n" class ReadlineInputMethod < InputMethod include Readline def gets if l = readline(@prompt, TRUE) l + "\n" else l end end end rescue CONFIG[:USE_READLINE] = FALSE end end end extend BC_APPLICATION__::CONTEXT run(binding)