commit bd77a5742d7964997c5e259ff9daaf3205e34120 Author: Elf M. Sternberg Date: Thu May 3 16:41:00 2012 -0700 Docco'd the library. Updated the examples to use the new parse-inheritance. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7881b86 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.npm +node_modules +*~ +#* +.#* +*/#* +docs diff --git a/examples/calc.coffee b/examples/calc.coffee new file mode 100644 index 0000000..389b655 --- /dev/null +++ b/examples/calc.coffee @@ -0,0 +1,29 @@ +ReParse = require('../src/reparse').ReParse +util = require("util") + +class Calc extends ReParse + ignorews: true + + OPS: + '+': (a, b) -> a + b + "-": (a, b) -> a - b + "*": (a, b) -> a * b + "/": (a, b) -> a / b + + expr: => @chainl @term, @addop + term: => @chainl1 @factor, @mulop + factor: => @choice @group, @number + group: => @between /^\(/, /^\)/, @expr + number: => parseFloat @match(/^(\-?\d+(\.\d+)?)/) + mulop: => @OPS[@match(/^[\*\/]/)] + addop: => @OPS[@match(/^[\+\-]/)] + + parse: => + super + @start(@expr) + +unless process.argv.length is 3 + util.puts "Usage: node " + process.argv[1] + " expression" + process.exit 1 + +util.puts (new Calc).parse(process.argv[2]) diff --git a/examples/email-address.coffee b/examples/email-address.coffee new file mode 100644 index 0000000..044e7f0 --- /dev/null +++ b/examples/email-address.coffee @@ -0,0 +1,23 @@ +ReParse = require('../src/reparse').ReParse +util = require("util") + +class EmailAddress extends ReParse + + addressList: => @sepEndBy @address, /^\s*,\s*/ + address: => @choice @namedAddress, @bareAddress + namedAddress: => @seq(@phrase, /^\s*/)[2] + bareAddress: => @seq(@word, /^@/, @word).join "" + phrase: => @many @word + word: => @skip(/^\s+/).choice @quoted, @dottedAtom + quoted: => @match /^"(?:\\.|[^"\r\n])+"/m + dottedAtom: => @match /^[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+(?:\.[!#\$%&'\*\+\-\/\w=\?\^`\{\|\}~]+)*/m + + parse: => + super + @start(@addressList) + +unless process.argv.length is 3 + util.puts "Usage: node " + process.argv[1] + " list-of-addresses" + process.exit 1 + +console.log util.inspect (new EmailAddress).parse(process.argv[2]) diff --git a/examples/json.coffee b/examples/json.coffee new file mode 100644 index 0000000..6b1f3fb --- /dev/null +++ b/examples/json.coffee @@ -0,0 +1,51 @@ +ReParse = require('../src/reparse').ReParse +util = require("util") +peg = require('./pegjson').parser; +require('./upgrades') + +class ReJSON extends ReParse + ignorews: true + + LITERAL = {'true': true, 'false': false, 'null': null} + STRING = {"\"": 34, "\\": 92, "/": 47, 'b': 8, 'f': 12, 'n': 10, 'r': 13, 't': 9} + + value: => @choice @literal, @string, @number, @array, @object + object: => @between(/^\{/, /^\}/, @members).reduce ((obj, pair) => obj[pair[0]] = pair[2]; obj), {} + members: => @sepBy @pair, /^,/ + pair: => @seq @string, /^:/, @value + array: => @between /^\[/, /^\]/, @elements + elements: => @sepBy @value, /^,/ + literal: => LITERAL[@match(/^(true|false|null)/)] + number: => parseFloat @match(/^\-?\d+(?:\.\d+)?(?:[eE][\+\-]?\d+)?/) + + string: => + chars = @match(/^"((?:\\["\\/bfnrt]|\\u[0-9a-fA-F]{4}|[^"\\])*)"/) + chars.replace /\\(["\\/bfnrt])|\\u([0-9a-fA-F]{4})/g, (_, $1, $2) => + String.fromCharCode (if $1 then STRING[$1] else parseInt($2, 16)) # " + + parse: => + super + @start(@value) + + +capture = (stream, encoding, fn) => + data = "" + stream.setEncoding encoding + stream.on "data", (chunk) => data += chunk + stream.on "end", => fn data + +time = (label, reps, fn) => + start = Date.now() + for i in [0..reps] + fn() + util.puts label + ": " + (Date.now() - start) + +input = "{\"a\": [1, \"foo\", [], {\"foo\": 1, \"bar\": [1, 2, 3]}] }" +console.log util.inspect (new ReJSON).parse( input), false, 4 + +jsonparse = new ReJSON() + +time "JSON", 1000, => JSON.parse input +time "PEG.js", 1000, => peg.parse input +time "ReParse", 1000, => jsonparse.parse(input) + diff --git a/examples/pegjson.js b/examples/pegjson.js new file mode 100644 index 0000000..a824149 --- /dev/null +++ b/examples/pegjson.js @@ -0,0 +1,1747 @@ +exports.parser = (function(){ + /* Generated by PEG.js (http://pegjs.majda.cz/). */ + + var result = { + /* + * Parses the input with a generated parser. If the parsing is successfull, + * returns a value explicitly or implicitly specified by the grammar from + * which the parser was generated (see |PEG.buildParser|). If the parsing is + * unsuccessful, throws |PEG.grammarParser.SyntaxError| describing the error. + */ + parse: function(input) { + var pos = 0; + var rightmostMatchFailuresPos = 0; + var rightmostMatchFailuresExpected = []; + var cache = {}; + + function quoteString(s) { + /* + * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a + * string literal except for the closing quote character, backslash, + * carriage return, line separator, paragraph separator, and line feed. + * Any character may appear in the form of an escape sequence. + */ + return '"' + s + .replace(/\\/g, '\\\\') // backslash + .replace(/"/g, '\\"') // closing quote character + .replace(/\r/g, '\\r') // carriage return + .replace(/\u2028/g, '\\u2028') // line separator + .replace(/\u2029/g, '\\u2029') // paragraph separator + .replace(/\n/g, '\\n') // line feed + + '"'; + } + + function arrayContains(array, value) { + /* + * Stupid IE does not have Array.prototype.indexOf, otherwise this + * function would be a one-liner. + */ + var length = array.length; + for (var i = 0; i < length; i++) { + if (array[i] === value) { + return true; + } + } + return false; + } + + function matchFailed(failure) { + if (pos < rightmostMatchFailuresPos) { + return; + } + + if (pos > rightmostMatchFailuresPos) { + rightmostMatchFailuresPos = pos; + rightmostMatchFailuresExpected = []; + } + + if (!arrayContains(rightmostMatchFailuresExpected, failure)) { + rightmostMatchFailuresExpected.push(failure); + } + } + + function parse_start(context) { + var cacheKey = "start" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result2 = parse__(context); + if (result2 !== null) { + var result3 = parse_object(context); + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(object) { return object; })(result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_object(context) { + var cacheKey = "object" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos1 = pos; + if (input.substr(pos, 1) === "{") { + var result10 = "{"; + pos += 1; + } else { + var result10 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("{")); + } + } + if (result10 !== null) { + var result11 = parse__(context); + if (result11 !== null) { + if (input.substr(pos, 1) === "}") { + var result12 = "}"; + pos += 1; + } else { + var result12 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("}")); + } + } + if (result12 !== null) { + var result13 = parse__(context); + if (result13 !== null) { + var result9 = [result10, result11, result12, result13]; + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + var result8 = result9 !== null + ? (function() { return {}; })() + : null; + if (result8 !== null) { + var result0 = result8; + } else { + var savedPos0 = pos; + if (input.substr(pos, 1) === "{") { + var result3 = "{"; + pos += 1; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("{")); + } + } + if (result3 !== null) { + var result4 = parse__(context); + if (result4 !== null) { + var result5 = parse_members(context); + if (result5 !== null) { + if (input.substr(pos, 1) === "}") { + var result6 = "}"; + pos += 1; + } else { + var result6 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("}")); + } + } + if (result6 !== null) { + var result7 = parse__(context); + if (result7 !== null) { + var result2 = [result3, result4, result5, result6, result7]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(members) { return members; })(result2[2]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_members(context) { + var cacheKey = "members" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result2 = parse_pair(context); + if (result2 !== null) { + var result3 = []; + var savedPos1 = pos; + if (input.substr(pos, 1) === ",") { + var result5 = ","; + pos += 1; + } else { + var result5 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(",")); + } + } + if (result5 !== null) { + var result6 = parse__(context); + if (result6 !== null) { + var result7 = parse_pair(context); + if (result7 !== null) { + var result4 = [result5, result6, result7]; + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + while (result4 !== null) { + result3.push(result4); + var savedPos1 = pos; + if (input.substr(pos, 1) === ",") { + var result5 = ","; + pos += 1; + } else { + var result5 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(",")); + } + } + if (result5 !== null) { + var result6 = parse__(context); + if (result6 !== null) { + var result7 = parse_pair(context); + if (result7 !== null) { + var result4 = [result5, result6, result7]; + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(head, tail) { + var result = {}; + result[head[0]] = head[1]; + for (var i = 0; i < tail.length; i++) { + result[tail[i][2][0]] = tail[i][2][1]; + } + return result; + })(result1[0], result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_pair(context) { + var cacheKey = "pair" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result2 = parse_string(context); + if (result2 !== null) { + if (input.substr(pos, 1) === ":") { + var result3 = ":"; + pos += 1; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(":")); + } + } + if (result3 !== null) { + var result4 = parse__(context); + if (result4 !== null) { + var result5 = parse_value(context); + if (result5 !== null) { + var result1 = [result2, result3, result4, result5]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(name, value) { return [name, value]; })(result1[0], result1[3]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_array(context) { + var cacheKey = "array" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos1 = pos; + if (input.substr(pos, 1) === "[") { + var result10 = "["; + pos += 1; + } else { + var result10 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("[")); + } + } + if (result10 !== null) { + var result11 = parse__(context); + if (result11 !== null) { + if (input.substr(pos, 1) === "]") { + var result12 = "]"; + pos += 1; + } else { + var result12 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("]")); + } + } + if (result12 !== null) { + var result13 = parse__(context); + if (result13 !== null) { + var result9 = [result10, result11, result12, result13]; + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + } else { + var result9 = null; + pos = savedPos1; + } + var result8 = result9 !== null + ? (function() { return []; })() + : null; + if (result8 !== null) { + var result0 = result8; + } else { + var savedPos0 = pos; + if (input.substr(pos, 1) === "[") { + var result3 = "["; + pos += 1; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("[")); + } + } + if (result3 !== null) { + var result4 = parse__(context); + if (result4 !== null) { + var result5 = parse_elements(context); + if (result5 !== null) { + if (input.substr(pos, 1) === "]") { + var result6 = "]"; + pos += 1; + } else { + var result6 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("]")); + } + } + if (result6 !== null) { + var result7 = parse__(context); + if (result7 !== null) { + var result2 = [result3, result4, result5, result6, result7]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(elements) { return elements; })(result2[2]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_elements(context) { + var cacheKey = "elements" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result2 = parse_value(context); + if (result2 !== null) { + var result3 = []; + var savedPos1 = pos; + if (input.substr(pos, 1) === ",") { + var result5 = ","; + pos += 1; + } else { + var result5 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(",")); + } + } + if (result5 !== null) { + var result6 = parse__(context); + if (result6 !== null) { + var result7 = parse_value(context); + if (result7 !== null) { + var result4 = [result5, result6, result7]; + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + while (result4 !== null) { + result3.push(result4); + var savedPos1 = pos; + if (input.substr(pos, 1) === ",") { + var result5 = ","; + pos += 1; + } else { + var result5 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(",")); + } + } + if (result5 !== null) { + var result6 = parse__(context); + if (result6 !== null) { + var result7 = parse_value(context); + if (result7 !== null) { + var result4 = [result5, result6, result7]; + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } else { + var result4 = null; + pos = savedPos1; + } + } + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(head, tail) { + var result = [head]; + for (var i = 0; i < tail.length; i++) { + result.push(tail[i][2]); + } + return result; + })(result1[0], result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_value(context) { + var cacheKey = "value" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var result16 = parse_string(context); + if (result16 !== null) { + var result0 = result16; + } else { + var result15 = parse_number(context); + if (result15 !== null) { + var result0 = result15; + } else { + var result14 = parse_object(context); + if (result14 !== null) { + var result0 = result14; + } else { + var result13 = parse_array(context); + if (result13 !== null) { + var result0 = result13; + } else { + var savedPos2 = pos; + if (input.substr(pos, 4) === "true") { + var result11 = "true"; + pos += 4; + } else { + var result11 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("true")); + } + } + if (result11 !== null) { + var result12 = parse__(context); + if (result12 !== null) { + var result10 = [result11, result12]; + } else { + var result10 = null; + pos = savedPos2; + } + } else { + var result10 = null; + pos = savedPos2; + } + var result9 = result10 !== null + ? (function() { return true; })() + : null; + if (result9 !== null) { + var result0 = result9; + } else { + var savedPos1 = pos; + if (input.substr(pos, 5) === "false") { + var result7 = "false"; + pos += 5; + } else { + var result7 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("false")); + } + } + if (result7 !== null) { + var result8 = parse__(context); + if (result8 !== null) { + var result6 = [result7, result8]; + } else { + var result6 = null; + pos = savedPos1; + } + } else { + var result6 = null; + pos = savedPos1; + } + var result5 = result6 !== null + ? (function() { return false; })() + : null; + if (result5 !== null) { + var result0 = result5; + } else { + var savedPos0 = pos; + if (input.substr(pos, 4) === "null") { + var result3 = "null"; + pos += 4; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("null")); + } + } + if (result3 !== null) { + var result4 = parse__(context); + if (result4 !== null) { + var result2 = [result3, result4]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function() { return "null"; })() + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + }; + }; + }; + }; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_string(context) { + var cacheKey = "string" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + var savedReportMatchFailures = context.reportMatchFailures; + context.reportMatchFailures = false; + var savedPos1 = pos; + if (input.substr(pos, 1) === "\"") { + var result9 = "\""; + pos += 1; + } else { + var result9 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\"")); + } + } + if (result9 !== null) { + if (input.substr(pos, 1) === "\"") { + var result10 = "\""; + pos += 1; + } else { + var result10 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\"")); + } + } + if (result10 !== null) { + var result11 = parse__(context); + if (result11 !== null) { + var result8 = [result9, result10, result11]; + } else { + var result8 = null; + pos = savedPos1; + } + } else { + var result8 = null; + pos = savedPos1; + } + } else { + var result8 = null; + pos = savedPos1; + } + var result7 = result8 !== null + ? (function() { return ""; })() + : null; + if (result7 !== null) { + var result0 = result7; + } else { + var savedPos0 = pos; + if (input.substr(pos, 1) === "\"") { + var result3 = "\""; + pos += 1; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\"")); + } + } + if (result3 !== null) { + var result4 = parse_chars(context); + if (result4 !== null) { + if (input.substr(pos, 1) === "\"") { + var result5 = "\""; + pos += 1; + } else { + var result5 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\"")); + } + } + if (result5 !== null) { + var result6 = parse__(context); + if (result6 !== null) { + var result2 = [result3, result4, result5, result6]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(chars) { return chars; })(result2[1]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + } + context.reportMatchFailures = savedReportMatchFailures; + if (context.reportMatchFailures && result0 === null) { + matchFailed("string"); + } + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_chars(context) { + var cacheKey = "chars" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var result2 = parse_char(context); + if (result2 !== null) { + var result1 = []; + while (result2 !== null) { + result1.push(result2); + var result2 = parse_char(context); + } + } else { + var result1 = null; + } + var result0 = result1 !== null + ? (function(chars) { return chars.join(""); })(result1) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_char(context) { + var cacheKey = "char" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[^"\\\0-]/) !== null) { + var result24 = input.charAt(pos); + pos++; + } else { + var result24 = null; + if (context.reportMatchFailures) { + matchFailed("[^\"\\\\\\0-]"); + } + } + if (result24 !== null) { + var result0 = result24; + } else { + if (input.substr(pos, 2) === "\\\"") { + var result23 = "\\\""; + pos += 2; + } else { + var result23 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\\"")); + } + } + var result22 = result23 !== null + ? (function() { return '"'; })() + : null; + if (result22 !== null) { + var result0 = result22; + } else { + if (input.substr(pos, 2) === "\\\\") { + var result21 = "\\\\"; + pos += 2; + } else { + var result21 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\\\")); + } + } + var result20 = result21 !== null + ? (function() { return "\\"; })() + : null; + if (result20 !== null) { + var result0 = result20; + } else { + if (input.substr(pos, 2) === "\\/") { + var result19 = "\\/"; + pos += 2; + } else { + var result19 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\/")); + } + } + var result18 = result19 !== null + ? (function() { return "/"; })() + : null; + if (result18 !== null) { + var result0 = result18; + } else { + if (input.substr(pos, 2) === "\\b") { + var result17 = "\\b"; + pos += 2; + } else { + var result17 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\b")); + } + } + var result16 = result17 !== null + ? (function() { return "\b"; })() + : null; + if (result16 !== null) { + var result0 = result16; + } else { + if (input.substr(pos, 2) === "\\f") { + var result15 = "\\f"; + pos += 2; + } else { + var result15 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\f")); + } + } + var result14 = result15 !== null + ? (function() { return "\f"; })() + : null; + if (result14 !== null) { + var result0 = result14; + } else { + if (input.substr(pos, 2) === "\\n") { + var result13 = "\\n"; + pos += 2; + } else { + var result13 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\n")); + } + } + var result12 = result13 !== null + ? (function() { return "\n"; })() + : null; + if (result12 !== null) { + var result0 = result12; + } else { + if (input.substr(pos, 2) === "\\r") { + var result11 = "\\r"; + pos += 2; + } else { + var result11 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\r")); + } + } + var result10 = result11 !== null + ? (function() { return "\r"; })() + : null; + if (result10 !== null) { + var result0 = result10; + } else { + if (input.substr(pos, 2) === "\\t") { + var result9 = "\\t"; + pos += 2; + } else { + var result9 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\t")); + } + } + var result8 = result9 !== null + ? (function() { return "\t"; })() + : null; + if (result8 !== null) { + var result0 = result8; + } else { + var savedPos0 = pos; + if (input.substr(pos, 2) === "\\u") { + var result3 = "\\u"; + pos += 2; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("\\u")); + } + } + if (result3 !== null) { + var result4 = parse_hexDigit(context); + if (result4 !== null) { + var result5 = parse_hexDigit(context); + if (result5 !== null) { + var result6 = parse_hexDigit(context); + if (result6 !== null) { + var result7 = parse_hexDigit(context); + if (result7 !== null) { + var result2 = [result3, result4, result5, result6, result7]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(h1, h2, h3, h4) { + return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); + })(result2[1], result2[2], result2[3], result2[4]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + }; + }; + }; + }; + }; + }; + }; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_number(context) { + var cacheKey = "number" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + var savedReportMatchFailures = context.reportMatchFailures; + context.reportMatchFailures = false; + var savedPos3 = pos; + var result17 = parse_int(context); + if (result17 !== null) { + var result18 = parse_frac(context); + if (result18 !== null) { + var result19 = parse_exp(context); + if (result19 !== null) { + var result20 = parse__(context); + if (result20 !== null) { + var result16 = [result17, result18, result19, result20]; + } else { + var result16 = null; + pos = savedPos3; + } + } else { + var result16 = null; + pos = savedPos3; + } + } else { + var result16 = null; + pos = savedPos3; + } + } else { + var result16 = null; + pos = savedPos3; + } + var result15 = result16 !== null + ? (function(int_, frac, exp) { return parseFloat(int_ + frac + exp); })(result16[0], result16[1], result16[2]) + : null; + if (result15 !== null) { + var result0 = result15; + } else { + var savedPos2 = pos; + var result12 = parse_int(context); + if (result12 !== null) { + var result13 = parse_frac(context); + if (result13 !== null) { + var result14 = parse__(context); + if (result14 !== null) { + var result11 = [result12, result13, result14]; + } else { + var result11 = null; + pos = savedPos2; + } + } else { + var result11 = null; + pos = savedPos2; + } + } else { + var result11 = null; + pos = savedPos2; + } + var result10 = result11 !== null + ? (function(int_, frac) { return parseFloat(int_ + frac); })(result11[0], result11[1]) + : null; + if (result10 !== null) { + var result0 = result10; + } else { + var savedPos1 = pos; + var result7 = parse_int(context); + if (result7 !== null) { + var result8 = parse_exp(context); + if (result8 !== null) { + var result9 = parse__(context); + if (result9 !== null) { + var result6 = [result7, result8, result9]; + } else { + var result6 = null; + pos = savedPos1; + } + } else { + var result6 = null; + pos = savedPos1; + } + } else { + var result6 = null; + pos = savedPos1; + } + var result5 = result6 !== null + ? (function(int_, exp) { return parseFloat(int_ + exp); })(result6[0], result6[1]) + : null; + if (result5 !== null) { + var result0 = result5; + } else { + var savedPos0 = pos; + var result3 = parse_int(context); + if (result3 !== null) { + var result4 = parse__(context); + if (result4 !== null) { + var result2 = [result3, result4]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(int_) { return parseFloat(int_); })(result2[0]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + }; + }; + } + context.reportMatchFailures = savedReportMatchFailures; + if (context.reportMatchFailures && result0 === null) { + matchFailed("number"); + } + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_int(context) { + var cacheKey = "int" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos2 = pos; + var result13 = parse_digit19(context); + if (result13 !== null) { + var result14 = parse_digits(context); + if (result14 !== null) { + var result12 = [result13, result14]; + } else { + var result12 = null; + pos = savedPos2; + } + } else { + var result12 = null; + pos = savedPos2; + } + var result11 = result12 !== null + ? (function(digit19, digits) { return digit19 + digits; })(result12[0], result12[1]) + : null; + if (result11 !== null) { + var result0 = result11; + } else { + var result10 = parse_digit(context); + if (result10 !== null) { + var result0 = result10; + } else { + var savedPos1 = pos; + if (input.substr(pos, 1) === "-") { + var result7 = "-"; + pos += 1; + } else { + var result7 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("-")); + } + } + if (result7 !== null) { + var result8 = parse_digit19(context); + if (result8 !== null) { + var result9 = parse_digits(context); + if (result9 !== null) { + var result6 = [result7, result8, result9]; + } else { + var result6 = null; + pos = savedPos1; + } + } else { + var result6 = null; + pos = savedPos1; + } + } else { + var result6 = null; + pos = savedPos1; + } + var result5 = result6 !== null + ? (function(digit19, digits) { return "-" + digit19 + digits; })(result6[1], result6[2]) + : null; + if (result5 !== null) { + var result0 = result5; + } else { + var savedPos0 = pos; + if (input.substr(pos, 1) === "-") { + var result3 = "-"; + pos += 1; + } else { + var result3 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString("-")); + } + } + if (result3 !== null) { + var result4 = parse_digit(context); + if (result4 !== null) { + var result2 = [result3, result4]; + } else { + var result2 = null; + pos = savedPos0; + } + } else { + var result2 = null; + pos = savedPos0; + } + var result1 = result2 !== null + ? (function(digit) { return "-" + digit; })(result2[1]) + : null; + if (result1 !== null) { + var result0 = result1; + } else { + var result0 = null;; + }; + }; + }; + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_frac(context) { + var cacheKey = "frac" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + if (input.substr(pos, 1) === ".") { + var result2 = "."; + pos += 1; + } else { + var result2 = null; + if (context.reportMatchFailures) { + matchFailed(quoteString(".")); + } + } + if (result2 !== null) { + var result3 = parse_digits(context); + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(digits) { return "." + digits; })(result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_exp(context) { + var cacheKey = "exp" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + var result2 = parse_e(context); + if (result2 !== null) { + var result3 = parse_digits(context); + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(e, digits) { return e + digits; })(result1[0], result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_digits(context) { + var cacheKey = "digits" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var result2 = parse_digit(context); + if (result2 !== null) { + var result1 = []; + while (result2 !== null) { + result1.push(result2); + var result2 = parse_digit(context); + } + } else { + var result1 = null; + } + var result0 = result1 !== null + ? (function(digits) { return digits.join(""); })(result1) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_e(context) { + var cacheKey = "e" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + var savedPos0 = pos; + if (input.substr(pos).match(/^[eE]/) !== null) { + var result2 = input.charAt(pos); + pos++; + } else { + var result2 = null; + if (context.reportMatchFailures) { + matchFailed("[eE]"); + } + } + if (result2 !== null) { + if (input.substr(pos).match(/^[+\-]/) !== null) { + var result4 = input.charAt(pos); + pos++; + } else { + var result4 = null; + if (context.reportMatchFailures) { + matchFailed("[+\\-]"); + } + } + var result3 = result4 !== null ? result4 : ''; + if (result3 !== null) { + var result1 = [result2, result3]; + } else { + var result1 = null; + pos = savedPos0; + } + } else { + var result1 = null; + pos = savedPos0; + } + var result0 = result1 !== null + ? (function(e, sign) { return e + sign; })(result1[0], result1[1]) + : null; + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_digit(context) { + var cacheKey = "digit" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[0-9]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (context.reportMatchFailures) { + matchFailed("[0-9]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_digit19(context) { + var cacheKey = "digit19" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[1-9]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (context.reportMatchFailures) { + matchFailed("[1-9]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_hexDigit(context) { + var cacheKey = "hexDigit" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[0-9a-fA-F]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (context.reportMatchFailures) { + matchFailed("[0-9a-fA-F]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse__(context) { + var cacheKey = "_" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + var savedReportMatchFailures = context.reportMatchFailures; + context.reportMatchFailures = false; + var result0 = []; + var result1 = parse_whitespace(context); + while (result1 !== null) { + result0.push(result1); + var result1 = parse_whitespace(context); + } + context.reportMatchFailures = savedReportMatchFailures; + if (context.reportMatchFailures && result0 === null) { + matchFailed("whitespace"); + } + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function parse_whitespace(context) { + var cacheKey = "whitespace" + '@' + pos; + var cachedResult = cache[cacheKey]; + if (cachedResult) { + pos = cachedResult.nextPos; + return cachedResult.result; + } + + + if (input.substr(pos).match(/^[ \n\r]/) !== null) { + var result0 = input.charAt(pos); + pos++; + } else { + var result0 = null; + if (context.reportMatchFailures) { + matchFailed("[ \\n\\r]"); + } + } + + + + cache[cacheKey] = { + nextPos: pos, + result: result0 + }; + return result0; + } + + function buildErrorMessage() { + function buildExpected(failuresExpected) { + switch (failuresExpected.length) { + case 0: + return 'end of input'; + case 1: + return failuresExpected[0]; + default: + failuresExpected.sort(); + return failuresExpected.slice(0, failuresExpected.length - 1).join(', ') + + ' or ' + + failuresExpected[failuresExpected.length - 1]; + } + } + + var expected = buildExpected(rightmostMatchFailuresExpected); + var actualPos = Math.max(pos, rightmostMatchFailuresPos); + var actual = actualPos < input.length + ? quoteString(input.charAt(actualPos)) + : 'end of input'; + + return 'Expected ' + expected + ' but ' + actual + ' found.'; + } + + function computeErrorPosition() { + /* + * The first idea was to use |String.split| to break the input up to the + * error position along newlines and derive the line and column from + * there. However IE's |split| implementation is so broken that it was + * enough to prevent it. + */ + + var line = 1; + var column = 1; + var seenCR = false; + + for (var i = 0; i < rightmostMatchFailuresPos; i++) { + var ch = input.charAt(i); + if (ch === '\n') { + if (!seenCR) { line++; } + column = 1; + seenCR = false; + } else if (ch === '\r' | ch === '\u2028' || ch === '\u2029') { + line++; + column = 1; + seenCR = true; + } else { + column++; + seenCR = false; + } + } + + return { line: line, column: column }; + } + + + + var result = parse_start({ reportMatchFailures: true }); + + /* + * The parser is now in one of the following three states: + * + * 1. The parser successfully parsed the whole input. + * + * - |result !== null| + * - |pos === input.length| + * - |rightmostMatchFailuresExpected| may or may not contain something + * + * 2. The parser successfully parsed only a part of the input. + * + * - |result !== null| + * - |pos < input.length| + * - |rightmostMatchFailuresExpected| may or may not contain something + * + * 3. The parser did not successfully parse any part of the input. + * + * - |result === null| + * - |pos === 0| + * - |rightmostMatchFailuresExpected| contains at least one failure + * + * All code following this comment (including called functions) must + * handle these states. + */ + if (result === null || pos !== input.length) { + var errorPosition = computeErrorPosition(); + throw new this.SyntaxError( + buildErrorMessage(), + errorPosition.line, + errorPosition.column + ); + } + + return result; + }, + + /* Returns the parser source code. */ + toSource: function() { return this._source; } + }; + + /* Thrown when a parser encounters a syntax error. */ + + result.SyntaxError = function(message, line, column) { + this.name = 'SyntaxError'; + this.message = message; + this.line = line; + this.column = column; + }; + + result.SyntaxError.prototype = Error.prototype; + + return result; +})(); \ No newline at end of file diff --git a/examples/upgrades.coffee b/examples/upgrades.coffee new file mode 100644 index 0000000..4570283 --- /dev/null +++ b/examples/upgrades.coffee @@ -0,0 +1,21 @@ +# Taken from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce + +if not Array::reduce? + Array::reduce = -> + accumulator = arguments[0] + ctr = 0 + + if typeof accumulator != 'function' + throw new TypeError "First argument is not callable" + + curr = if arguments.length < 2 + if @length == 0 then throw new TypeError "Array length is 0 and no second argument" + ctr = 1 + @[0] + else + arguments[1] + + for i in [ctr...@length] + curr = accumulator.call(undefined, curr, @[i], i, @) + + curr diff --git a/package.json b/package.json new file mode 100644 index 0000000..c19580b --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "reparse-coffee", + "description": "An implementation of a parser combinator in coffeescript.", + "author": "Elf M. Sternberg (elf.sternberg@gmail.com)", + "version": "0.0.1", + "dependencies": { + "coffee-script": "1.x.x" + }, + "devDependencies": { + "docco": "0.3.x" + }, + "engine": "node >= 0.6.0" +} diff --git a/src/reparse.coffee b/src/reparse.coffee new file mode 100644 index 0000000..9bf59cc --- /dev/null +++ b/src/reparse.coffee @@ -0,0 +1,285 @@ +#; -*- mode: coffee -*- + +# ___ ___ ___ __ __ +# | _ \___| _ \__ _ _ _ ___ ___ / __|___ / _|/ _|___ ___ +# | / -_) _/ _` | '_(_- + # super + # @start(@your_top_level_production) + + parse: (input) => + @input = input + + # Returns true when this parser has exhausted its input. + + eof: => + @input is "" + + # Indicate failure, optionally resetting the input to a previous + # state. This is not an exceptional condition (in choice and + # maybes, for example). + + fail: (input) => + @input = input if input isnt `undefined` + throw @fail + + # Execute a production, which could be a function or a RegExp. + + produce: (method) => + val = if (method instanceof RegExp) then @match(method) else method.call(this) + @skipWS() if @ignorews + val + + # Begin parsing using the given production, return the result. + # All input must be consumed. + + start: (method) => + val = undefined + @ignorews and @skipWS() + try + val = @produce method + return val if @eof() + catch err + throw err if err isnt @fail + throw new Error("Could not parse '" + @input + "'.") + + + # Attempts to apply the method and produce a value. If it fails, + # restores the input to the previous state. + + maybe: (method) => + input = @input + try + return @produce method + catch err + throw err if err isnt @fail + @fail input + + # Try to run the production `method`. If the production fails, + # don't fail, just return the otherwise. + + option: (method, otherwise) => + try + return @maybe method + catch err + throw err if err isnt @fail + return otherwise + + # Given three parsers, return the value produced by `body`. This + # is equivalent to seq(left, body, right)[0]. I'm not sure why + # Weaver created an alternative syntax, then. Wishing JSParsec + # wasn't so damned unreadable. + + between: (left, right, body) => + input = @input + val = undefined + try + @produce left + val = @produce body + @produce right + return val + catch err + throw err if err isnt @fail + @fail input + + # Match a regular expression against the input, returning the + # first captured group. If no group is captured, return the + # matched string. This can result in surprises, if you don't wrap + # your groups exactly right, which is common in ()? regexps. + + match: (pattern) => + probe = @input.match pattern + return @fail() unless probe + @input = @input.substr probe[0].length + if probe[1] is `undefined` then probe[0] else probe[1] + + # Returns the first production among arguments for which the + # production does not fail. + + choice: => + input = @input + for arg in arguments + try + return @produce arg + catch err + throw err if err isnt @fail + @fail input + + # Match every production in a sequence, returning a list of the + # values produced. Sometimes Coffeescript's parser surprises me, + # as in this case where the try-return pairing confused it, and it + # needed help isolating the element. + # + # I have yet to find a case where where Weaver's unshift of the + # beginning of the input string to the front of the return value + # makes sense. It's not a feature of Parsec's sequence primitive, + # for example. + # + # It could be useful if one needed the raw of a seq: for example, + # when processing XML entities for correctness, not value. But in + # the short term, the productions can be as preservative as + # Weaver's technique, and for my needs that's my story, and I'm + # sticking to it. + + seq: => + input = @input + try + return (for arg in arguments + @produce(arg)) + catch err + throw err if err isnt @fail + @fail input + + # Applies the production `method` `min` or more times. Returns + # the parser object as a chainable convenience if it does not + # fail. Will fail if it skips less than `min` times. + + skip: (method, min = null) => + found = 0 + input = @input + until @eof() + try + @maybe method + found++ + catch err + throw err if err isnt @fail + break + if min and (found < min) then @fail input else @ + + # Applies the production `method` one or more times. + + skip1: (method) => @skip(method, 1) + + # Skip whitespace. Returns the parser object for chainable + # convenience. Note that this is the baseline whitespace: this + # will not skip carriage returns or linefeeds. + + skipWS: => + @match(/^\s*/) + @ + + # Returns an array of `min` values produced by `method`. + + many: (method, min = null) => + input = @input + result = until @eof() + try + @maybe(method) + catch err + throw err if err isnt @fail + break + + if min and (result.length < min) then @fail input else result + + # Returns an array of at least one values produced by `method`. + # Fails if zero values are produced. + + many1: (method) => @many method, 1 + + # Return the array of values produced by `method` with `sep` + # between each value. The series may be terminated by a `sep`. + + sepBy: (method, sep, min = 0) => + orig = @input + input = undefined + result = [] + try + result.push @produce method + until @eof() + try + input = @input + @produce sep + result.push @produce method + catch err + throw err if err isnt @fail + @fail input + catch err + throw err if err isnt @fail + if min and (result.length < min) then @fail orig else result + + sepBy1: (method, sep) => @sepBy method, sep, 1 + + # parses `min` or more productions of `method` (zero by default), + # which must be terminated with the `end` production. RESOLVE: + # There is no alternative production being given to `@option` in + # Weaver's code. I've changed this to @produce for the time + # being, which seems to be in line with the JSParsec + # implementation. + + endBy: (method, end, min = 0) => + val = @many method, min + @option end + val + + # Parses 1 or more productions of method, which must be terminated + # with the end production + + endBy1: (method, end) => + @endBy method, end, 1 + + # Returns an array of `min` or more values produced by `method`, + # separated by `sep`, and optionally terminated by `sep`. + # Defaults to zero productions. + + sepEndBy: (method, sep, min = 0) => + val = @sepBy method, sep, min + @option sep + val + + # Returns an array of `min` or more values produced by `method`, + # separated by `sep`, and optionally terminated by `sep`. + # Defaults to zero productions. Must return at least one + # production; fails if there are zero productions. + + sepEndBy1: (method, sep) => @sepEndBy method, sep, 1 + + # Process `min` occurrences of `method`, separated by `op`. Return + # a value obtained by the repeated application of the return of + # `op` to the return of `method`. If there are less that `min` + # occurrences of `method`, `otherwise` is returned. Used, for + # example, to process a collection of mathematical productions of + # the same precedence. + + chainl: (method, op, otherwise = null, min = null) => + found = 0 + result = otherwise + orig = @input + input = undefined + try + result = @maybe(method) + found++ + until @eof() + try + input = @input + result = @produce(op)(result, @produce(method)) + found++ + catch err + throw err if err isnt @fail + @fail input + catch err + throw err if err isnt @fail + if min and (found < min) then @fail input else result + + # Like `chainl`, but must produce at least one production. Fails + # if there are zero productions. + + chainl1: (method, op) => @chainl method, op, null, 1 +