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.



13
14
15
16
17
# File 'lib/qed/parser.rb', line 13

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

Instance Attribute Details

#astObject (readonly)

Abstract Syntax Tree



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

def ast
  @ast
end

#fileObject (readonly)

File to parse.



23
24
25
# File 'lib/qed/parser.rb', line 23

def file
  @file
end

#optionsObject (readonly)

Parser options.



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

def options
  @options
end

Instance Method Details

#linesObject



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

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


143
144
145
146
147
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
# File 'lib/qed/parser.rb', line 143

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.



48
49
50
51
52
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
# File 'lib/qed/parser.rb', line 48

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



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/qed/parser.rb', line 34

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