Permalink
Browse files

enable error querying from Parslet::ParseFailed

  • Loading branch information...
1 parent d20e051 commit 0828ea4cda191184127c1ae8b7a79ba284891348 @jmettraux committed Apr 16, 2012
Showing with 258 additions and 3 deletions.
  1. +65 −0 arbor0.rb
  2. +150 −0 arbor1.rb
  3. +32 −0 lib/parslet.rb
  4. +11 −3 lib/parslet/source.rb
View
@@ -0,0 +1,65 @@
+
+require 'pp'
+require 'parslet'
+
+class Parser < Parslet::Parser
+
+ rule(:spaces) {
+ match('\s').repeat(0)
+ }
+
+ rule(:something) {
+ str(' ').repeat(0) >> str('something')
+ }
+
+ rule(:begin) {
+ spaces >>
+ str("begin") >> spaces >>
+ something >> spaces >>
+ str("end") >> spaces
+ }
+
+ root(:begin)
+end
+
+class Parslet::Atoms::Base
+
+ # Extracts the deepest leaf in the error tree.
+ #
+ def deepest_error
+
+ line = 0
+ column = 0
+ cause = nil
+
+ error_tree.to_s.split("\n").each do |l|
+ m = l.match(/- (.+) at line (\d+) char (\d+)/)
+ if m && m[2].to_i >= line
+ line = m[2].to_i; column = m[3].to_i; cause = m[1]
+ end
+ end
+
+ "#{cause} at l#{line} c#{column}"
+ end
+end
+
+parser = Parser.new
+
+begin
+ parser.parse(%{
+ begin
+ somewhere
+ end
+ })
+rescue => e
+ puts
+ puts e
+ puts
+ puts parser.root.error_tree
+ puts
+ puts e.error_log
+ puts
+ puts e.error.to_s
+ puts
+end
+
View
@@ -0,0 +1,150 @@
+
+require 'pp'
+require 'parslet'
+
+
+class Parser < Parslet::Parser
+
+ # commons
+
+ rule(:space) { match('[ \t]').repeat(1) }
+ rule(:space?) { space.maybe }
+
+ rule(:newline) { match('[\r\n]') }
+
+ rule(:comment) { str('#') >> match('[^\r\n]').repeat }
+
+ rule(:line_separator) {
+ (space? >> ((comment.maybe >> newline) | str(';')) >> space?).repeat(1)
+ }
+
+ rule(:blank) { line_separator | space }
+ rule(:blank?) { blank.maybe }
+
+ rule(:identifier) { match('[a-zA-Z0-9_]').repeat(1) }
+
+ # res_statement
+
+ rule(:reference) {
+ ((str('@@') | str('@')) >> identifier).as(:reference)
+ }
+
+ rule(:res_action_or_link) {
+ str('.').as(:dot) >> (identifier >> str('?').maybe ).as(:name) >> str('()')
+ }
+
+ rule(:res_actions) {
+ (
+ reference
+ ).as(:resources) >>
+ (
+ res_action_or_link.as(:res_action)
+ ).repeat(0).as(:res_actions)
+ }
+
+ rule(:res_statement) {
+ res_actions >>
+ (str(':') >> identifier.as(:name)).maybe.as(:res_field)
+ }
+
+ # expression
+
+ rule(:expression) {
+ res_statement
+ }
+
+ # body
+
+ rule(:body) {
+ (line_separator >> (block | expression)).repeat(0).as(:body) >>
+ line_separator
+ }
+
+ # blocks
+
+ rule(:begin_block) {
+ (str('concurrent').as(:type) >> space).maybe.as(:pre) >>
+ str('begin').as(:begin) >>
+ body >>
+ str('end')
+ }
+
+ rule(:define_block) {
+ str('define').as(:define) >> space >>
+ identifier.as(:name) >> str('()') >>
+ body >>
+ str('end')
+ }
+
+ rule(:block) {
+ define_block | begin_block
+ }
+
+ # root
+
+ rule(:radix) {
+ line_separator.maybe >> block >> line_separator.maybe
+ }
+
+ root(:radix)
+end
+
+
+ds = [
+ %{
+ define f()
+ begin
+ @res:name
+ end
+ end
+ },
+ %{
+ define f()
+ begin
+ @res.name()
+ end
+ end
+ },
+ %{
+ define f()
+ @res.name
+ end
+ },
+ %{
+ define f()
+ begin
+ @res.name
+ end
+ end
+ }
+]
+
+ds.each do |d|
+
+ puts '-' * 80
+
+ d.split("\n").each_with_index do |l, i|
+ puts "#{i + 1}: #{l}"
+ end
+
+ parser = Parser.new
+
+ begin
+ puts
+ parser.parse(d)
+ puts 'success'
+ rescue => e
+ puts
+ puts e
+ puts
+ puts parser.root.error_tree
+ puts
+ puts e.error_log
+ puts
+ puts e.error.to_s
+ puts
+ end
+end
+
+puts '-' * 80
+
View
@@ -78,6 +78,38 @@ def initialize(message, cause=nil)
@cause = cause
end
attr_reader :cause
+
+ # Returns the list of errors (all of them instances of Cause).
+ #
+ def errors
+ @cause.source.errors
+ end
+
+ # Returns the most nested error (instance of Cause), most probably the
+ # point where the source failed to conform to the grammar by the thinnest
+ # edge.
+ #
+ def error
+ errors.sort_by(&:pos).last
+ end
+
+ # Returns a string, one line per error (cause), each line indented by
+ # its position.
+ #
+ # Trying to help visualize the work of the parser and where it tried
+ # hard (but ultimately failed).
+ #
+ def error_log
+ s = StringIO.new
+ positions = errors.collect(&:pos).uniq.sort
+
+ errors.each do |err|
+ s.print '.' * (positions.index(err.pos) + 1)
+ s.puts " #{err.pos} #{err.to_s}"
+ end
+
+ s.string
+ end
end
# Raised when the parse operation didn't consume all of its input. In this
View
@@ -9,15 +9,20 @@ module Parslet
# method for the current position.
#
class Source
+
+ # Returns a Hash { pos => [ causes ] }
+ #
+ attr_reader :errors
+
def initialize(io)
if io.respond_to? :to_str
io = StringIO.new(io)
end
@io = io
@line_cache = LineCache.new
-
@memo_cache = Hash.new { |h, k| h[k] = {} }
+ @errors = []
end
# Wrapping atom.try(source) in a memoizing embrace.
@@ -80,9 +85,12 @@ def line_and_column(position=nil)
# position.
#
def error(message, error_pos=nil)
- real_pos = (error_pos||self.pos)
+ real_pos = (error_pos || self.pos)
+
+ err = Cause.format(self, real_pos, message)
+ @errors << err
- Cause.format(self, real_pos, message)
+ err
end
private

0 comments on commit 0828ea4

Please sign in to comment.