Please Forward: Ruby Quiz Submission

From: John Browning <jb@poplar.com>
Date: May 8, 2006 12:19:12 PM CDT
To: submission@rubyquiz.com
Subject: Please Forward: Ruby Quiz Submission

I'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
  end

end

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
  end

  def show
    if @err
      print "#{@spec.gsub( "*", @prompts )}\n"
    else
      print "#{@spec} just doesn't make sense at all\n"
    end
  end

end

box = BracketParser.new( ARGV.first.to_s )
box.qc

..................................................................................
John Browning
t: +44 20 7700 1230 f: +44 20 7700 5255

ยทยทยท

Begin forwarded message: