lib/qed/parser.rb in qed-2.6.3 vs lib/qed/parser.rb in qed-2.7.0

- old
+ new

@@ -1,45 +1,56 @@ +require 'qed/step' + module QED - # - def self.all_steps - @all_steps ||= [] - end - - # The parser breaks down a demonstandum into - # structured object to passed thru the script - # evaluator. + # The parser breaks down a demonstandum into structured object + # to passed thru the script evaluator. # - # Technically is defines it's own markup language - # but for interoperability sake it is RDoc and a bit of - # support for Markdown. + # Technically is defines it's own markup language but for + # interoperability sake it is RDoc and/or Markdown. + # class Parser + # Setup new parser instance. # - def initialize(file, options={}) - @file = file - @options = options - @ast = [] + # @param [Demo] demo + # This demo, which is to be parsed. + # + # @param [Hash] options + # Parsing options. + # + # @option options [Symbol] :mode + # Parse in `:comment` mode or default mode. + # + def initialize(demo, options={}) + @demo = demo + @mode = options[:mode] + @steps = [] end - # Abstract Syntax Tree - attr :ast + # The demo to parse. + attr :demo - # File to parse. - attr :file + # Parser mode. + attr :mode - # Parser options. - attr :options + # Abstract Syntax Tree + attr :steps - # + # The demo's file to parse. + def file + demo.file + end + + # Lines of demo, prepared for parsing into steps. def lines @lines ||= parse_lines end - # + # Prepare lines for parsing into steps. def parse_lines - case options[:mode] + case mode when :comment parse_comment_lines else index = 0 #-1 File.readlines(file).to_a.map do |line| @@ -48,10 +59,12 @@ end end # TODO: It would be nice if we could get ther require statement for the # comment mode to be relative to an actual loadpath. + + # Parse comment lines into a format that the parse method can use. def parse_comment_lines ruby_omit = false rdoc_omit = false lines = [ [0, "Load #{File.basename(file)} script.\n"], @@ -81,278 +94,43 @@ index += 1 end lines end -=begin - # Parse the demo into an abstract syntax tree. - # - # TODO: I know there has to be a faster way to do this. + # Parse demo file into steps. def parse - blocks = [[]] - state = :none - lines.each do |lineno, line| - case line - when /^$/ - case state - when :code - blocks.last << line - when :blank - blocks.last << line - else - blocks.last << line - state = :blank - end - when /^\s+/ - blocks << [] if state != :code - blocks.last << line - state = :code - else - blocks << [] if state != :text - blocks.last << line - state = :text - end - end - blocks.shift if blocks.first.empty? + steps = [] + blank = false + indented = false + explain = [] + example = [] #Step.new(file) - line_cnt = 1 - blocks.each do |block| - text = block.join - case text - when /\A\s+/ - add_section(:code, text, line_cnt) - else - add_section(:text, text, line_cnt) - end - line_cnt += block.size - end - #@ast.reject!{ |sect| sect.type == :code && sect.text.strip.empty? } - return @ast - end - - # - def add_section(state, text, lineno) - case state - when :code - if ast.last && ast.last.cont? - @ast.last << text #clean_quote(text) - else - @ast << CodeSection.new(text, lineno) - end - else - @ast << TextSection.new(text, lineno) - #cont = (/\.\.\.\s*^/ =~ text ? true : false) - end - end -=end - - def parse - tree = [] - flush = true - pend = false - block = Block.new(file) lines.each do |lineno, line| case line - when /^\s*$/ - if flush - pend = true unless lineno == 0 - block.raw << [lineno, line] + when /^\s*$/ # blank line + blank = true + if indented + example << [lineno, line] else - block.raw << [lineno, line] + explain << [lineno, line] end - when /\A\s+/ - if flush - tree << block.ready!(flush, tree.last) - block = Block.new(file) - end - pend = false - flush = false - block.raw << [lineno, line] + when /\A\s+/ #/\A(\t|\ \ +)/ # indented + indented = true + blank = false + example << [lineno, line] else - if pend || !flush - tree << block.ready!(flush, tree.last) - pend = false - flush = true - block = Block.new(file) + if indented or blank + steps << Step.new(demo, explain, example, steps.last) + explain, example = [], [] #Step.new(file) end - block.raw << [lineno, line] + indented = false + blank = false + explain << [lineno, line] end end - tree << block.ready!(flush, tree.last) - @ast = tree + steps << Step.new(demo, explain, example, steps.last) + @steps = steps end - # TODO: We need to preserve the indentation for the verbatim reporter. - #def clean_quote(text) - # text = text.tabto(0).chomp.sub(/\A\n/,'') - # if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(text) - # text = md[1] - # end - # text.rstrip - #end - - # Section Block - class Block - # Block raw code/text. - attr :raw - - # previous block - attr :back_step - - # next block - attr :next_step - - # - def initialize(file) - QED.all_steps << self - - @file = file - @raw = [] - @type = :description - @back_step = nil - @next_step = nil - end - - # - def ready!(flush, back_step) - @flush = flush - @back_step = back_step - - @text = raw.map{ |lineno, line| line }.join - @type = parse_type - - @back_step.next_step = self if @back_step - - self - end - - # - def to_s - case type - when :description - text - else - text - end - end - - # - def text - @text - end - - # - def flush? - @flush - end - - # Returns an Array of prepared example text - # for use in advice. - def arguments - if next_step && next_step.data? - [next_step.sample_text] - else - [] - end - end - - # What type of block is this? - def type - @type - end - - # - def head? ; @type == :head ; end - - # - def desc? ; @type == :desc ; end - - # - def code? ; @type == :code ; end - - # Any commentary ending in `...` or `:` will mark the following - # block as a plain text *sample* and not example code to be evaluated. - def data? ; @type == :data ; end - - # - alias_method :header?, :head? - - # - alias_method :description?, :desc? - - - # First line of example text. - def lineno - @line ||= @raw.first.first - end - - # - def code - @code ||= tweak_code - end - - # Clean up the example text, removing unccesseary white lines - # and triple quote brackets, but keep indention intact. - def clean_text - str = text.chomp.sub(/\A\n/,'') - if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str) - str = md[1] - end - str.rstrip - end - - # When the text is sample text and passed to an adivce block, this - # provides the prepared form of the example text, removing white lines, - # triple quote brackets and indention. - def sample_text - str = text.tabto(0).chomp.sub(/\A\n/,'') - if md = /\A["]{3,}(.*?)["]{3,}\Z/.match(str) - str = md[1] - end - str.rstrip - end - - # TODO: object_hexid - def inspect - %[#<Block:#{object_id} "#{text[0..25]} ...">] - end - - protected - - # - def next_step=(n) - @next_step = n - end - - private - - # - def parse_type - if flush? - if /\A[=#]/ =~ text - :head - else - :desc - end - else - if back_step && /(\.\.\.|\:)\s*\Z/m =~ back_step.text.strip - :data - else - :code - end - end - end - - # - def tweak_code - code = text.dup - code.gsub!(/\n\s*\#\ ?\=\>/, '.assert = ') - code.gsub!(/\s*\#\ ?\=\>/, '.assert = ') - code - end - - end - end end -