Permalink
Browse files

let optional() have a default value, and make onMatch() turn exceptio…

…ns into failures
  • Loading branch information...
1 parent c428fd7 commit fd0a8fb03a1b163bbbd786737e323ea4d0cde0c5 Robey Pointer committed Jun 15, 2012
Showing with 25 additions and 11 deletions.
  1. +2 −2 README.md
  2. +16 −9 src/packrattle/parser.coffee
  3. +7 −0 test/test_parser.coffee
View
@@ -63,8 +63,8 @@ the real power is in combining the parsers:
result will be an array of all of the non-null match results of p1 and
friends
-- `parser.optional(p)` - match p or return the empty string, succeeding
- either way
+- `parser.optional(p, defaultValue)` - match p or return the default value
+ (usually the empty string), succeeding either way
- `parser.check(p)` - verify that p matches, but don't advance the parser's
position
@@ -79,7 +79,13 @@ class Parser
new Parser @message, (state) =>
rv = @parse(state)
if not rv.ok then return rv
- new Match(rv.state, if (f instanceof Function) then f(rv.match) else f)
+ if f instanceof Function
+ try
+ new Match(rv.state, f(rv.match))
+ catch e
+ new NoMatch(state, e.toString())
+ else
+ new Match(rv.state, f)
# only succeed if f(match) returns true.
matchIf: (f) ->
@@ -99,12 +105,13 @@ class Parser
or: (others...) ->
parsers = (implicit(p) for p in [ @ ].concat(others))
message = (m.message for m in parsers).join(" or ")
- new Parser message, (state) =>
- rv = @parse(state)
+ outer = @
+ new Parser message, (state) ->
+ rv = outer.parse(state)
for p in parsers
rv = resolve(p).parse(state)
if rv.ok then return rv
- new NoMatch(state, message)
+ @fail(state)
# if this parser is prefixed by p, parse it and drop it first
# (for example, whitespace: `p.skim(/\s+/)`)
@@ -117,7 +124,7 @@ class Parser
then: (p) -> seq(@, p)
- optional: -> optional(@)
+ optional: (defaultValue="") -> optional(@, defaultValue)
repeat: (sep = null) -> repeat(@, sep)
@@ -183,13 +190,13 @@ seq = (parsers...) ->
new Match(state, results)
# a parser that can fail to match, and just returns the empty string
-optional = (p) ->
+optional = (p, defaultValue="") ->
p = implicit(p)
new Parser p.message, (state) ->
p = resolve(p)
rv = p.parse(state)
if rv.ok then return rv
- new Match(state, "")
+ new Match(state, defaultValue)
# one or more repetitions of a parser, returned as an array
# optionally separated by a separation parser
@@ -300,5 +307,5 @@ exports.drop = drop
exports.parse = parse
exports.check = check
-# to-do: guard (match p but don't move the pointer)
-# times (exactly n times)
+# to-do:
+# p.trim() ==> p.skip(whitespace)
View
@@ -78,6 +78,12 @@ describe "Parser", ->
rv.state.pos.should.equal(5)
rv.match.should.eql("yes")
+ it "transforms a match into a failure on exception", ->
+ p = parser.string("hello").onMatch((s) -> throw "utter failure")
+ rv = p.parse("hello")
+ rv.ok.should.equal(false)
+ rv.message.should.match(/utter failure/)
+
it "transforms the error message", ->
p = parser.string("hello").onFail("Try a greeting.")
rv = p.parse("cat")
@@ -253,6 +259,7 @@ describe "Parser", ->
rv.match.should.eql("hello")
rv = p.consume("hello!")
rv.ok.should.equal(false)
+ rv.state.pos.should.equal(5)
rv.message.should.match(/end/)
it "can perform a non-advancing check", ->

0 comments on commit fd0a8fb

Please sign in to comment.