# Class FormatLine handles the parsing of comments, dot commands, and # simple formatting characters. 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_]/ NoAlphaDot = /[^.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; grab; add curr; grab; add curr when "$" dollar when "*", "_", "`", "~" marker curr add curr 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.grab; 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 return nil if @i <= 0 @line[@i-1] end def next! @line[@i+1] end def grab @line[@i+=1] end def ungrab @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) return if token.nil? @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 grab_alpha_dot str = Null.dup grab loop do break if curr.nil? str << curr break if terminate?(NoAlphaDot, 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_dot 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) 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 str = curr + collect!(sym, Blank) add str add_token sym, str grab end end def double_marker(char) sym = Syms[char] kind = sym case next! # first char after ** when Space, LF, nil pre, post = SimpleFormats[sym] add_token kind else str = collect!(sym, Punc) add_token kind, str grab 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_bracketed(sym, terminators) str = Null.dup # next is not " ","*","[" grab # ZZZ loop do if curr == Escape str << grab # ch = escaped char grab next end if terminate?(terminators, curr) break end # STDERR.puts "#{curr.inspect} is not a terminator" str << curr # not a terminator grab # STDERR.puts "After grab, curr is #{curr.inspect}" end if curr == "]" # skip right bracket grab end add str str rescue => err STDERR.puts "ERR = #{err}\n#{err.backtrace}" STDERR.puts "=== str = #{str.inspect}" end def escaped ch = grab grab ch end def collect!(sym, terminators, bracketed=nil) return collect_bracketed(sym, terminators) if bracketed str = Null.dup # next is not " ","*","[" grab # ZZZ loop do case when curr.nil? return str when curr == Escape str << escaped next when terminate?(terminators, curr) break else str << curr # not a terminator end grab end ungrab add str str rescue => err STDERR.puts "ERR = #{err}\n#{err.backtrace}" STDERR.puts "=== str = #{str.inspect}" 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] || "[#{name} is undefined]" result end ##### def embedded? ! (['"', "'", " ", nil].include? prev) end end