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/or Markdown.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

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

Setup new parser instance.

Parameters:

  • demo (Demo)

    This demo, which is to be parsed.

  • options (Hash) (defaults to: {})

    Parsing options.

Options Hash (options):

  • :mode (Symbol)

    Parse in :comment mode or default mode.



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

def initialize(demo, options={})
  @demo  = demo
  @mode  = options[:mode]
  @steps = []
end

Instance Attribute Details

#demoObject (readonly)

The demo to parse.



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

def demo
  @demo
end

#modeObject (readonly)

Parser mode.



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

def mode
  @mode
end

#stepsObject (readonly)

Abstract Syntax Tree



37
38
39
# File 'lib/qed/parser.rb', line 37

def steps
  @steps
end

Instance Method Details

#fileObject

The demo’s file to parse.



40
41
42
# File 'lib/qed/parser.rb', line 40

def file
  demo.file
end

#linesObject

Lines of demo, prepared for parsing into steps.



45
46
47
# File 'lib/qed/parser.rb', line 45

def lines
  @lines ||= parse_lines
end

#parseObject

Parse demo file into steps.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/qed/parser.rb', line 100

def parse
  steps    = []
  blank    = false
  indented = false
  fenced   = false   # inside a ``` ruby/bare code fence
  foreign  = false   # inside a ``` non-ruby code fence
  explain  = []
  example  = [] #Step.new(file)

  lines.each do |lineno, line|
    case line
    when /\A```ruby\s*$/   # ```ruby opens a ruby fence
      fenced  = true
      foreign = false
      next
    when /\A```\s*$/       # bare ``` either opens or closes a fence
      if fenced || foreign
        fenced  = false
        foreign = false
        next
      else
        fenced = true      # bare ``` opens a ruby fence
        next
      end
    when /\A```\S/         # ```<language> opens a foreign fence
      foreign = true
      next
    else
      # skip lines inside foreign code fences
      next if foreign
    end

    if fenced
      # lines inside a ruby fence are treated as example code
      indented = true
      blank    = false
      example << [lineno, line]
    else
      case line
      when /^\s*$/  # blank line
        blank = true
        if indented
          example << [lineno, line]
        else
          explain << [lineno, line]
        end
      when /\A\s+/  # indented
        indented = true
        blank    = false
        example << [lineno, line]
      else
        if indented or blank
          steps << Step.new(demo, explain, example, steps.last)
          explain, example = [], []
        end
        indented = false
        blank    = false
        explain << [lineno, line]
      end
    end
  end
  steps << Step.new(demo, explain, example, steps.last)
  @steps = steps
end

#parse_comment_linesObject

Parse comment lines into a format that the parse method can use.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/qed/parser.rb', line 66

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

Prepare lines for parsing into steps.



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/qed/parser.rb', line 50

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