match() shortened to just m(), now accepts strings as well as regexps.

I found myself writing a parser with lots of matches in sequence, and
wanted something a bit smarter.  This adds two bits of smarts: a shorter
name and string handling makes something like @seq(m('{'), @string,
m('}'))[1] much easier to read and debug.
This commit is contained in:
Elf M. Sternberg 2013-05-10 21:04:24 -07:00
parent 3cffc3d44e
commit 3d7d9cfe69
4 changed files with 27 additions and 20 deletions

View File

@ -13,10 +13,10 @@ class Calc extends ReParse
expr: => @chainl @term, @addop expr: => @chainl @term, @addop
term: => @chainl1 @factor, @mulop term: => @chainl1 @factor, @mulop
factor: => @choice @group, @number factor: => @choice @group, @number
group: => @between /^\(/, /^\)/, @expr group: => @between '(', ')', @expr
number: => parseFloat @match(/^(\-?\d+(\.\d+)?)/) number: => parseFloat @m(/^(\-?\d+(\.\d+)?)/)
mulop: => @OPS[@match(/^[\*\/]/)] mulop: => @OPS[@m(/^[\*\/]/)]
addop: => @OPS[@match(/^[\+\-]/)] addop: => @OPS[@m(/^[\+\-]/)]
parse: => parse: =>
super super

View File

@ -5,12 +5,12 @@ class EmailAddress extends ReParse
addressList: => @sepEndBy @address, /^\s*,\s*/ addressList: => @sepEndBy @address, /^\s*,\s*/
address: => @choice @namedAddress, @bareAddress address: => @choice @namedAddress, @bareAddress
namedAddress: => @seq(@phrase, /^\s*</m, @bareAddress, /^>/)[2] namedAddress: => @seq(@phrase, /^\s*</m, @bareAddress, '>')[2]
bareAddress: => @seq(@word, /^@/, @word).join "" bareAddress: => @seq(@word, '@', @word).join ""
phrase: => @many @word phrase: => @many @word
word: => @skip(/^\s+/).choice @quoted, @dottedAtom word: => @skip(/^\s+/).choice @quoted, @dottedAtom
quoted: => @match /^"(?:\\.|[^"\r\n])+"/m quoted: => @m /^"(?:\\.|[^"\r\n])+"/m
dottedAtom: => @match /^[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+(?:\.[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+)*/m dottedAtom: => @m /^[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+(?:\.[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+)*/m
parse: => parse: =>
super super

View File

@ -10,16 +10,16 @@ class ReJSON extends ReParse
STRING = {"\"": 34, "\\": 92, "/": 47, 'b': 8, 'f': 12, 'n': 10, 'r': 13, 't': 9} STRING = {"\"": 34, "\\": 92, "/": 47, 'b': 8, 'f': 12, 'n': 10, 'r': 13, 't': 9}
value: => @choice @literal, @string, @number, @array, @object value: => @choice @literal, @string, @number, @array, @object
object: => @between(/^\{/, /^\}/, @members).reduce ((obj, pair) => obj[pair[0]] = pair[2]; obj), {} object: => @between('{', '}', @members).reduce ((obj, pair) => obj[pair[0]] = pair[2]; obj), {}
members: => @sepBy @pair, /^,/ members: => @sepBy @pair, ','
pair: => @seq @string, /^:/, @value pair: => @seq @string, ':', @value
array: => @between /^\[/, /^\]/, @elements array: => @between '[', ']', @elements
elements: => @sepBy @value, /^,/ elements: => @sepBy @value, /^,/
literal: => LITERAL[@match(/^(true|false|null)/)] literal: => LITERAL[@m(/^(true|false|null)/)]
number: => parseFloat @match(/^\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?/) number: => parseFloat @m(/^\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?/)
string: => string: =>
chars = @match(/^"((?:\\["\\/bfnrt]|\\u[0-9a-fA-F]{4}|[^"\\])*)"/) chars = @m(/^"((?:\\["\\/bfnrt]|\\u[0-9a-fA-F]{4}|[^"\\])*)"/)
chars.replace /\\(["\\/bfnrt])|\\u([0-9a-fA-F]{4})/g, (_, $1, $2) => chars.replace /\\(["\\/bfnrt])|\\u([0-9a-fA-F]{4})/g, (_, $1, $2) =>
String.fromCharCode (if $1 then STRING[$1] else parseInt($2, 16)) # " String.fromCharCode (if $1 then STRING[$1] else parseInt($2, 16)) # "
@ -48,4 +48,3 @@ jsonparse = new ReJSON()
time "JSON", 1000, => JSON.parse input time "JSON", 1000, => JSON.parse input
time "PEG.js", 1000, => peg.parse input time "PEG.js", 1000, => peg.parse input
time "ReParse", 1000, => jsonparse.parse(input) time "ReParse", 1000, => jsonparse.parse(input)

View File

@ -44,7 +44,10 @@ exports.ReParse = class ReParse
# Execute a production, which could be a function or a RegExp. # Execute a production, which could be a function or a RegExp.
produce: (method) => produce: (method) =>
val = if (method instanceof RegExp) then @match(method) else method.call(@) val = if ((method instanceof RegExp) or (typeof method == 'string'))
@m(method)
else
method.call(@)
@skipWS() if @ignorews @skipWS() if @ignorews
val val
@ -75,7 +78,13 @@ exports.ReParse = class ReParse
# #
# Note that the `return fail()` call eventually leads to a throw. # Note that the `return fail()` call eventually leads to a throw.
match: (pattern, putback = false) => m: (pattern, putback = false) =>
if typeof pattern == 'string'
if @input.substr(0, pattern.length) == pattern
@input = @input.substr(pattern.length)
return pattern
return @fail()
probe = @input.match pattern probe = @input.match pattern
return @fail() unless probe return @fail() unless probe
@input = @input.substr (if probe[1]? and putback then probe[1].length else probe[0].length) @input = @input.substr (if probe[1]? and putback then probe[1].length else probe[0].length)
@ -181,7 +190,7 @@ exports.ReParse = class ReParse
# will not skip carriage returns or linefeeds. # will not skip carriage returns or linefeeds.
skipWS: => skipWS: =>
@match(/^\s*/) @m(/^\s*/)
@ @
# Returns an array of `min` values produced by `method`. # Returns an array of `min` values produced by `method`.
@ -292,4 +301,3 @@ exports.ReParse = class ReParse
# if there are zero productions. # if there are zero productions.
chainl1: (method, op) => @chainl method, op, null, 1 chainl1: (method, op) => @chainl method, op, null, 1