Class: QED::Parser

Inherits:
Object show all
Defined in:
lib/qed/parser.rb

Overview

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.

Defined Under Namespace

Classes: Block

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, options = {}) ⇒ Parser

Returns a new instance of Parser.



18
19
20
21
22
# File 'lib/qed/parser.rb', line 18

def initialize(file, options={})
  @file    = file
  @options = options
  @ast     = []
end

Instance Attribute Details

#astObject (readonly)

Abstract Syntax Tree



25
26
27
# File 'lib/qed/parser.rb', line 25

def ast
  @ast
end

#fileObject (readonly)

File to parse.



28
29
30
# File 'lib/qed/parser.rb', line 28

def file
  @file
end

#optionsObject (readonly)

Parser options.



31
32
33
# File 'lib/qed/parser.rb', line 31

def options
  @options
end

Instance Method Details

#linesObject



34
35
36
# File 'lib/qed/parser.rb', line 34

def lines
  @lines ||= parse_lines
end

#parseObject

# Parse the demo into an abstract syntax tree.

#
# TODO: I know there has to be a faster way to do this.
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?

  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


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/qed/parser.rb', line 148

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]
      else
        block.raw << [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]
    else
      if pend || !flush
        tree << block.ready!(flush, tree.last)
        pend  = false
        flush = true
        block = Block.new(file)
      end
      block.raw << [lineno, line]
    end
  end
  tree << block.ready!(flush, tree.last)
  @ast = tree
end

#parse_comment_linesObject

TODO: It would be nice if we could get ther require statement for the comment mode to be relative to an actual loadpath.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/qed/parser.rb', line 53

def parse_comment_lines
  ruby_omit = false
  rdoc_omit = false
  lines = [
    [0, "Load #{File.basename(file)} script.\n"],
    [0, "\n"],
    [0, "  require '#{file}'\n"]
  ]
  index = 1
  File.readlines(file).each do |l|
    case l
    when /^=begin(?!\s+qed)/
      ruby_omit = true
    when /^=end/
      ruby_omit = false
    when /^\s*\#\-\-\s*$/
      rdoc_omit = true
    when /^\s*\#\+\+\s*$/
      rdoc_omit = false
    ##when /^\s*\#\ \-\-/  # not needed just double comment
    ##  # -- skip internal comments
    when /^\s*##/
      ## skip internal comments
    when /^\s*\#/
      lines << [index, l.lstrip.sub(/^\#\ ?/, '')] unless (ruby_omit or rdoc_omit)
    else
      lines << [index, "\n"] unless lines.last[1] == "\n" unless (ruby_omit or rdoc_omit)
    end
    index += 1
  end
  lines
end

#parse_linesObject



39
40
41
42
43
44
45
46
47
48
49
# File 'lib/qed/parser.rb', line 39

def parse_lines
  case options[:mode]
  when :comment
    parse_comment_lines
  else
    index = 0  #-1
    File.readlines(file).to_a.map do |line|
      [index += 1, line]
    end
  end
end