class FormatLine SimpleFormats = {} SimpleFormats[:b] = %w[ ] SimpleFormats[:i] = %w[ ] SimpleFormats[:t] = ["", ""] SimpleFormats[:s] = %w[ ] Null = "" Space = " " Alpha = /[A-Za-z]/ AlNum = /[A-Za-z0-9_]/ LF = "\n" LBrack = "[" Blank = [" ", nil, "\n"] Punc = [")", ",", ".", " ", "\n"] NoAlpha = /[^A-Za-z0-9_]/ Param = ["]", "\n", nil] Escape = "\\" # not an ESC char Syms = { "*" => :b, "_" => :i, "`" => :t, "~" => :s } def terminate?(terminators, ch) if terminators.is_a? Regexp terminators === ch else terminators.include?(ch) end end attr_reader :out attr_reader :tokenlist def initialize(line) @line = line @i = -1 @token = Null.dup @tokenlist = [] end def self.parse!(line) return nil if line.nil? x = self.new(line.chomp) t = x.tokenize(line) x.evaluate end def tokenize(line) grab loop do case curr when Escape; go; add curr; grab when "$" dollar when "*", "_", "`", "~" marker curr add curr # grab when LF break if @i >= line.size - 1 when nil break else add curr end grab end add_token(:str) @tokenlist end def self.var_func_parse(str) return nil if str.nil? x = self.new(str.chomp) x.grab loop do case x.curr when Escape; x.go; x.add x.curr; x.grab when "$" x.dollar when LF, nil break else x.add x.curr end x.grab end x.add_token(:str) x.evaluate end def embed(sym, str) pre, post = SimpleFormats[sym] pre + str + post end def evaluate(tokens = @tokenlist) @out = "" return "" if tokens.empty? gen = tokens.each token = gen.next loop do break if token.nil? sym, val = *token case sym when :str @out << val unless val == "\n" # BUG when :var @out << varsub(val) when :func param = nil arg = gen.peek if [:colon, :brackets].include? arg[0] arg = gen.next # for real param = arg[1] param = FormatLine.var_func_parse(param) end @out << funcall(val, param) when :b, :i, :t, :s val = FormatLine.var_func_parse(val) @out << embed(sym, val) else add_token :str end token = gen.next end @out end def curr @line[@i] end def prev @line[@i-1] end def next! @line[@i+1] end def grab @line[@i+=1] end def grab_colon_param grab # grab : param = "" loop do case next! when Escape grab param << next! grab when Space, LF, nil; break else param << next! grab end end param = nil if param.empty? param end def grab_func_param grab # [ param = "" loop do case next! when Escape grab param << next! grab when "]", LF, nil; break else param << next! grab end end add curr grab param = nil if param.empty? param end def add(str) @token << str unless str.nil? end def add_token(kind, token = @token) @tokenlist << [kind, token] unless token.empty? @token = Null.dup end def grab_alpha str = Null.dup grab loop do break if curr.nil? str << curr break if terminate?(NoAlpha, next!) grab end str end def dollar grab case curr when LF; add "$"; add_token :str when " "; add "$ "; add_token :str when nil; add "$"; add_token :str when "$"; double_dollar # when "."; dollar_dot when /[A-Za-z]/ add_token :str var = curr + grab_alpha add_token(:var, var) else add "$" + curr add_token(:string) end end def double_dollar case next! when Space; add_token :string, "$$ "; grab; return when LF, nil; add "$$"; add_token :str when Alpha add_token(:str, @token) func = grab_alpha add_token(:func, func) case next! when ":"; param = grab_colon_param; add_token(:colon, param) when "["; param = grab_func_param; add_token(:brackets, param) else # do nothing end else grab; add_token :str, "$$" + curr; return end end # def dollar_dot # add_token :ddot, @line[@i..-1] # end def marker(char) add_token :str sym = Syms[char] if embedded? # add char # ??? add_token "*", :string return end grab case curr when Space add char + " " add_token :str grab when LF, nil add char add_token :str when char; double_marker(char) when LBrack; long_marker(char) else add curr str = collect!(sym, Blank) add_token sym, str add curr # next char onto next token... end end def double_marker(char) sym = Syms[char] grab kind = sym # "string_#{char}".to_sym case next! # first char after ** when Space, LF, nil pre, post = SimpleFormats[sym] add_token kind else str = collect!(sym, Punc) grab unless next!.nil? add_token kind, str end end def long_marker(char) sym = Syms[char] # grab # skip left bracket kind = sym # "param_#{sym}".to_sym arg = collect!(sym, Param, true) add_token kind, arg end def collect!(sym, terminators, param=false) str = Null.dup # next is not " ","*","[" grab loop do if curr == Escape str << grab # ch = escaped char grab next end break if terminate?(terminators, curr) str << curr # not a terminator grab end grab if param && curr == "]" # skip right bracket add str end ############ ### From FormatLine: def funcall(name, param) result = if self.respond_to?("func_" + name.to_s) self.send("func_" + name.to_s, param) else fobj = ::Livetext::Functions.new fobj.send(name, param) end result end def varsub(name) result = Livetext::Vars[name] result end ##### def showme(tag) char = @line[@cc] puts "--- #{tag}: ch=#{@ch.inspect} next=#{@next.inspect} (cc=#@cc:#{char.inspect}) out=#{@out.inspect}" end def embedded? ! (['"', "'", " ", nil].include? prev) end end