lib/luobo.rb in luobo-0.0.2 vs lib/luobo.rb in luobo-0.0.5

- old
+ new

@@ -1,5 +1,221 @@ require "yaml" - +require "erubis" require "luobo/token" -require "luobo/driver" -require "luobo/base" + +# converter class +class Luobo + + def initialize file, output + @source_file = file + + # handle output file or output IO + if output.is_a?(IO) + @output = output + elsif output.is_a?(String) + @output_file = output + @output = File.open(output, "w") + end + + # initialize a array to hold tokens waiting for process: + @token_stack = Array.new + + # initialize loop template and examples + self.reset_loop + end + + # initialize the holders for a example based loop, + # or reset after a loop expansion + def reset_loop + @loop_start_ln = 0 + @loop_template = nil + @loop_examples = Array.new + end + + # extend a loop be examples + def expand_loop + raise "no examples found for loop start on line #{@loop_start_ln}" unless @loop_examples.size > 0 + loop_n = 0 + @loop_examples.each do |exa| + loop_n += 1 + # treat each loop example as a yaml var definition for the template + rslt = Erubis::Eruby.new(@loop_template).result(YAML.load(exa)) + li = 0 + rslt.split("\n").each do |line| + li += 1 + self.process_line @loop_start_ln + li, line + end + end + + # clear up holders + self.reset_loop + end + + # add a new line to the example, separate examples to array + def add_example_line line + if /#{regex_new_example}/.match(line) + # start a new loop example with a placeholder nil + @loop_examples << nil + else + raise "you need use '#{regex_new_example}' to start a loop example" unless @loop_examples.size > 0 + line.gsub!(/#{regex_example_head}/, '') + @loop_examples[-1] = @loop_examples[-1] ? @loop_examples[-1] + "\n" + line : line + end + end + + # handle convert for each token + def convert token + pname = "do_" + token.processor_name.downcase + if self.respond_to?(pname) + self.send(pname.to_sym, token) + else + self.do__missing(token) + end + end + + # processor converters and dump callbacks + def do_setup; "" end # call before all tokens + def do_cleanup; "" end # call after all tokens + + def do__raw token + if token.line and token.line.size > 0 + token.line#.gsub(/^\s*/, "") + "\n" + else + "" + end + end + + def do__missing token + src = token.line + src += token.block_code + "\n" if token.block_code + src + end + + def dump contents + @output.print contents + end + + # regex settings + def regex_line_comment; "" end + def regex_proc_head; "(#{regex_line_comment})?(?<leading_spaces_>\s*)" end + def regex_proc_name; "(?<processor_name_>[A-Z][_A-Z0-9]*)" end + def regex_proc_line; "^" + regex_proc_head + regex_proc_name + regex_proc_end + "(?<line_code_>.+)" end + def regex_proc_end; "\s*\:?\s+" end + def regex_block_start; "\-\>" end + + # example loop related: + def regex_loop_line; "^" + regex_proc_head + "\%\s*\=\=\=\=+\s*" end + def regex_new_example; "^" + regex_proc_head + "\%\s*\-\-\-\-+\s*" end + def regex_example_head; "^" + regex_proc_head + "\%\s*" end + def regex_example_line; "^" + regex_proc_head + "\%\s*(?<example_>.+)\s*" end + + # create a token from line + def tokenize ln, line + pure_line = line.gsub(/^#{regex_line_comment}/, '') # trim line comment marker + + indent_level = 0 + processor_name = '_raw' + line_code = '' + block_open = false + if matches = /#{regex_proc_line}/.match(pure_line) + processor_name = matches["processor_name_"] + indent_level = matches["leading_spaces_"].size + line_code = matches["line_code_"] + block_open = true if /#{regex_block_start}\s*$/ =~ line_code + line_code.gsub!(/\s*(#{regex_block_start})?\s*$/, '') + elsif matches = /^#{regex_proc_head}[^\s]/.match(pure_line) + indent_level = matches["leading_spaces_"].size + end + + Token.new ln, line, indent_level, processor_name, line_code, block_open + end + + # travel up through the token stack, close all token with + # indent level larger or equal to the parameter + def close_stack indent_level + while @token_stack.size > 0 and @token_stack[-1].indent_level >= indent_level + if @token_stack.size > 1 # if this is not the last token, add to parents + @token_stack[-2].add_block_code self.convert(@token_stack[-1]) + else # this is the last token in the stack + dump self.convert(@token_stack[-1]) + end + @token_stack.pop + end + end + + # add a new line to the existing token if it requires block codes + # or write the last token out before a new token + # this function invokes after a loop expansion or if the line + # is not in any examples and loop templates. + def process_line ln, line + # create a token, with processor name + token = self.tokenize(ln, line) + # based on the the token indent level, close token stack if any + self.close_stack token.indent_level + + # add token and then close it unless it opens a block + @token_stack << token + self.close_stack token.indent_level unless token.block_open? + + self # for methods chain + end + + # process + def process! + fh = File.open(@source_file, 'r:utf-8') + in_loop = false + in_example = false + fh.each do |line| + line.chomp! + line.gsub!("\t", " ") # convert tab to 2 spaces + + # interrupt for loops + if /#{regex_loop_line}/.match(line) + in_loop = true + @loop_start_ln = $. + next # break further processing if detecting a loop + end + + # expand and end current loop + if in_example + unless /#{regex_example_head}/.match(line) # if the line not marked as an example + self.expand_loop + in_example = false + in_loop = false + end + end + + # end a loop template and start a loop example + if in_loop + in_example = true if /#{regex_example_head}/.match(line) + end + + # dispatch the current line to different holders + if in_example + self.add_example_line line + elsif in_loop + @loop_template = @loop_template ? @loop_template + "\n" + line : line + else + self.process_line $., line + end + + end + fh.close + + # do not forget expand last loop if it reaches over EOF. + self.expand_loop if @loop_start_ln > 0 + + # close all remain stacks (if any) + self.close_stack 0 + + self.dump(self.do_cleanup) + @output.close if @output_file + + self # return self for method chains + end + + # command handler + def self.convert! file, output + self.new(file, output).process! + end + +end