# Class FormatLine handles the parsing of comments, dot commands, and # simple formatting characters. class FormatLine < StringParser 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 } attr_reader :out attr_reader :tokenlist def initialize(line) super @token = Null.dup @tokenlist = [] end def self.parse!(line) return nil if line.nil? x = self.new(line.chomp) t = x.tokenize # TTY.puts "tokens = \n#{t.inspect}\n " x.evaluate end def tokenize # add grab loop do case peek when Escape; grab; add peek; grab; add peek when "$" dollar when "*", "_", "`", "~" marker peek add peek when LF break if @i >= line.size - 1 when nil break else add peek end grab end add_token(:str) @tokenlist end def terminate?(terminators, ch) if terminators.is_a? Regexp terminators === ch else terminators.include?(ch) end end def self.var_func_parse(str) return nil if str.nil? x = self.new(str.chomp) char = x.peek loop do char = x.grab break if char == LF || char == nil x.handle_escaping if char == Escape x.dollar if char == "$" x.add char end x.add_token(:str) result = x.evaluate result end def handle_escaping grab add grab 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 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 peek 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 eos? str << peek break if terminate?(NoAlpha, next!) grab end str end def grab_alpha_dot str = Null.dup grab loop do break if eos? # peek.nil? str << peek break if terminate?(NoAlphaDot, next!) grab end str end def dollar grab case peek 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 = peek + grab_alpha_dot add_token(:var, var) else add "$" + peek 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, "$$" + peek; 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 peek 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 = peek + 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 peek == Escape grab str << grab next end if terminate?(terminators, peek) break end str << peek # not a terminator grab end if peek == "]" # 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 grab ch = 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 peek.nil? return str when peek == Escape str << escaped next when terminate?(terminators, peek) break else str << peek # 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 def funcall(name, param) err = "[Error evaluating $$#{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) rescue err end result end def varsub(name) result = Livetext::Vars[name] || "[#{name} is undefined]" result end def embedded? ! (['"', "'", " ", nil].include? prev) end end