From: John Browning <jb@poplar.com>
Date: May 8, 2006 12:19:12 PM CDT
To: submission@rubyquiz.com
Subject: Please Forward: Ruby Quiz SubmissionI'm doing this for learning, but if you feel it worthy of submission please do so. It does a bit more error correction than solutions I've seen.
Many thanks for all the interesting and useful quizzes
allbests
code follows...................................
#!/usr/bin/ruby
# possible solution to Ruby Quiz 78 on Bracket Parsing
# If string validates, it prints it and returns 0
# If string can be corrected, it prints the corrected version and returns 0 (should perhaps return 1 or otherwise signal correction?)
# If string cannot be corrected -- eg because there are two or more possible places for the correcting bracket to go --
# it returns 1 and prints a string indicating possible corrections# works pretty much like a recursive descent parser, except that instead of looking only at
# the leftmost token, it uses regular expressions to find bracketed pairs.
# It parses from the outside in, or, if there are two or more pairs at the same level, from the left (or not, depending on ruby implementation)
# Parsing like this makes it easier to keep track of where we are to do corrections# TODO? It might be nice if this corrected and/or signalled all errors at once instead of just one level at a time. That would require moving error
# marking/correction to within parsing, so that parsing always returns a complete value. Errors would then presumably be signalled
# via global @err_val# QUESTION: is BB+ legal? It's not in this implementation, but should it be? It would be more ecological...
class BracketParser
# called with string to parse
def initialize( toparse )
@spec = toparse
end# qc initiates parsing and prints a viable spec string or error.
# returns 0 if string usable, else 1
def qc
begin
print "#{parse( @spec )}\n"
return 0
rescue UncorrectableError => uerr
uerr.show
return 1
end
end# parse does what it says, recursively
def parse( spec )
# print "parsing #{spec}\n"
case spec
when "B": # contents of box, which is also a leaf of parse tree, so stop recursion
return spec
when /^(\[.+?\]|\(.+?\)|\{.+?\})((\[.+?\]|\(.+?\)|\{.+?\})+)$/ # two boxes, parse each
return parse( $1 ) + parse( $2 )
when /^(\[)(.+?)(\])$/, /^(\()(.+?)(\))$/, /^(\{)(.+?)(\})$/ # just one box, parse contents
return $1 + parse($2) + $3
when /^(\[|\(|\{)(\[.+?\]$|\(.+?\)$|\{.+?\}$|B$)/ # extra opening bracket
open, rest = $1, $2
if ( rest =~ /^(\[.+?\]|\(.+?\)|\{.+?\})((\[.+?\]|\(.+?\)|\{.+?\})+)$/ )
# can't correct if there are more than two possible boxes, so signal error and show possible closures
raise UncorrectableError.new( open, rest, @spec )
else
# there's only one place the missing bracket can go, so put it there and keep parsing
return open + parse( rest ) + (open == "["? "]": open == "("? ")": "}")
end
when /^(\[.+?\]|\(.+?\)|\{.+?\}|B)(\]|\)|\})$/ # extra closing bracket
rest, close = $1, $2
if ( rest =~ /^(\[.+?\]|\(.+?\)|\{.+?\})((\[.+?\]|\(.+?\)|\{.+?\})+)$/ )
# can't correct if there are more than two possible boxes, so signal error and show possible closures
raise UncorrectableError.new( close, rest, @spec )
else
# there's only one place the missing bracket can go, so put it there and keep parsing
return (close == "]"? "[": close == ")"? "(": "{") + parse( rest ) + close
end
else
# no clue, give up
raise UncorrectableError.new( nil, nil, @spec )
end
endend
class UncorrectableError < RuntimeError
# shows errors that can't be corrected.
# err can have one of three value:
# nil means we don't know what to do about this error, so it just prints spec and admits defeat
# an opening bracket means we lack a closing bracket but don't know where to put it, so print possibilities
# a closing bracket is vice versa.
# context is the part of the string where the error could be (and is ignored when err is nil)
# spec is the total string
def initialize( err, context, spec )
@err = err
@spec = spec
if @err
@context = context
# split into boxes to show where missing bracket could go
@possibles = @context.scan(/\[.+?\]|\(.+?\)|\{.+?\}/)
# missing closing bracket
if @err =~ /\[|\(|\{/ then
@spec = @spec.gsub( (err + context), "*" )
@fix = @err == "["? "]": @err == "("? ")": "}"
@prompts = @err + @possibles.collect { |n| n + @fix + "?" }.to_s
# missing opening bracket, which is pretty much the same apart from the order of things
else
@spec = @spec.gsub( (context + err), "*" )
@fix = @err == "]"? "[": @err == ")"? "(": "{"
@prompts = @possibles.collect { |n| @fix + "?" + n }.to_s + @err
end
end
enddef show
if @err
print "#{@spec.gsub( "*", @prompts )}\n"
else
print "#{@spec} just doesn't make sense at all\n"
end
endend
box = BracketParser.new( ARGV.first.to_s )
box.qc..................................................................................
John Browning
t: +44 20 7700 1230 f: +44 20 7700 5255
ยทยทยท
Begin forwarded message: