Skip to content

Instantly share code, notes, and snippets.

@iomeone
Created May 22, 2019 08:17
Show Gist options
  • Save iomeone/88d546b069086c2fffb665cf8d917f58 to your computer and use it in GitHub Desktop.
Save iomeone/88d546b069086c2fffb665cf8d917f58 to your computer and use it in GitHub Desktop.
duktape parser enumerate javascript object
"use strict";
function Parsimmon(action) {
if (!(this instanceof Parsimmon)) {
return new Parsimmon(action);
}
this._ = action;
}
var _ = Parsimmon.prototype;
function times(n, f) {
var i = 0;
for (i; i < n; i++) {
f(i);
}
}
function forEach(f, arr) {
times(arr.length, function(i) {
f(arr[i], i, arr);
});
}
function reduce(f, seed, arr) {
forEach(function(elem, i, arr) {
seed = f(seed, elem, i, arr);
}, arr);
return seed;
}
function map(f, arr) {
return reduce(
function(acc, elem, i, a) {
return acc.concat([f(elem, i, a)]);
},
[],
arr
);
}
function lshiftBuffer(input) {
var asTwoBytes = reduce(
function(a, v, i, b) {
return a.concat(
i === b.length - 1
? Buffer.from([v, 0]).readUInt16BE(0)
: b.readUInt16BE(i)
);
},
[],
input
);
return Buffer.from(
map(function(x) {
return ((x << 1) & 0xffff) >> 8;
}, asTwoBytes)
);
}
function consumeBitsFromBuffer(n, input) {
var state = { v: 0, buf: input };
times(n, function() {
state = {
v: (state.v << 1) | bitPeekBuffer(state.buf),
buf: lshiftBuffer(state.buf)
};
});
return state;
}
function bitPeekBuffer(input) {
return input[0] >> 7;
}
function sum(numArr) {
return reduce(
function(x, y) {
return x + y;
},
0,
numArr
);
}
function find(pred, arr) {
return reduce(
function(found, elem) {
return found || (pred(elem) ? elem : found);
},
null,
arr
);
}
function bufferExists() {
return typeof Buffer !== "undefined";
}
function ensureBuffer() {
if (!bufferExists()) {
throw new Error(
"Buffer global does not exist; please consider using https://github.com/feross/buffer if you are running Parsimmon in a browser."
);
}
}
function bitSeq(alignments) {
ensureBuffer();
var totalBits = sum(alignments);
if (totalBits % 8 !== 0) {
throw new Error(
"The bits [" +
alignments.join(", ") +
"] add up to " +
totalBits +
" which is not an even number of bytes; the total should be divisible by 8"
);
}
var bytes = totalBits / 8;
var tooBigRange = find(function(x) {
return x > 48;
}, alignments);
if (tooBigRange) {
throw new Error(
tooBigRange + " bit range requested exceeds 48 bit (6 byte) Number max."
);
}
return new Parsimmon(function(input, i) {
var newPos = bytes + i;
if (newPos > input.length) {
return makeFailure(i, bytes.toString() + " bytes");
}
return makeSuccess(
newPos,
reduce(
function(acc, bits) {
var state = consumeBitsFromBuffer(bits, acc.buf);
return {
coll: acc.coll.concat(state.v),
buf: state.buf
};
},
{ coll: [], buf: input.slice(i, newPos) },
alignments
).coll
);
});
}
function bitSeqObj(namedAlignments) {
ensureBuffer();
var seenKeys = {};
var totalKeys = 0;
var fullAlignments = map(function(item) {
if (isArray(item)) {
var pair = item;
if (pair.length !== 2) {
throw new Error(
"[" +
pair.join(", ") +
"] should be length 2, got length " +
pair.length
);
}
assertString(pair[0]);
assertNumber(pair[1]);
if (Object.prototype.hasOwnProperty.call(seenKeys, pair[0])) {
throw new Error("duplicate key in bitSeqObj: " + pair[0]);
}
seenKeys[pair[0]] = true;
totalKeys++;
return pair;
} else {
assertNumber(item);
return [null, item];
}
}, namedAlignments);
if (totalKeys < 1) {
throw new Error(
"bitSeqObj expects at least one named pair, got [" +
namedAlignments.join(", ") +
"]"
);
}
var namesOnly = map(function(pair) {
return pair[0];
}, fullAlignments);
var alignmentsOnly = map(function(pair) {
return pair[1];
}, fullAlignments);
return bitSeq(alignmentsOnly).map(function(parsed) {
var namedParsed = map(function(name, i) {
return [name, parsed[i]];
}, namesOnly);
return reduce(
function(obj, kv) {
if (kv[0] !== null) {
obj[kv[0]] = kv[1];
}
return obj;
},
{},
namedParsed
);
});
}
function parseBufferFor(other, length) {
ensureBuffer();
return new Parsimmon(function(input, i) {
if (i + length > input.length) {
return makeFailure(i, length + " bytes for " + other);
}
return makeSuccess(i + length, input.slice(i, i + length));
});
}
function parseBuffer(length) {
return parseBufferFor("buffer", length).map(function(unsafe) {
return Buffer.from(unsafe);
});
}
function encodedString(encoding, length) {
return parseBufferFor("string", length).map(function(buff) {
return buff.toString(encoding);
});
}
function isInteger(value) {
return typeof value === "number" && Math.floor(value) === value;
}
function assertValidIntegerByteLengthFor(who, length) {
if (!isInteger(length) || length < 0 || length > 6) {
throw new Error(who + " requires integer length in range [0, 6].");
}
}
function uintBE(length) {
assertValidIntegerByteLengthFor("uintBE", length);
return parseBufferFor("uintBE(" + length + ")", length).map(function(buff) {
return buff.readUIntBE(0, length);
});
}
function uintLE(length) {
assertValidIntegerByteLengthFor("uintLE", length);
return parseBufferFor("uintLE(" + length + ")", length).map(function(buff) {
return buff.readUIntLE(0, length);
});
}
function intBE(length) {
assertValidIntegerByteLengthFor("intBE", length);
return parseBufferFor("intBE(" + length + ")", length).map(function(buff) {
return buff.readIntBE(0, length);
});
}
function intLE(length) {
assertValidIntegerByteLengthFor("intLE", length);
return parseBufferFor("intLE(" + length + ")", length).map(function(buff) {
return buff.readIntLE(0, length);
});
}
function floatBE() {
return parseBufferFor("floatBE", 4).map(function(buff) {
return buff.readFloatBE(0);
});
}
function floatLE() {
return parseBufferFor("floatLE", 4).map(function(buff) {
return buff.readFloatLE(0);
});
}
function doubleBE() {
return parseBufferFor("doubleBE", 8).map(function(buff) {
return buff.readDoubleBE(0);
});
}
function doubleLE() {
return parseBufferFor("doubleLE", 8).map(function(buff) {
return buff.readDoubleLE(0);
});
}
function toArray(arrLike) {
return Array.prototype.slice.call(arrLike);
}
// -*- Helpers -*-
function isParser(obj) {
return obj instanceof Parsimmon;
}
function isArray(x) {
return {}.toString.call(x) === "[object Array]";
}
function isBuffer(x) {
/* global Buffer */
return bufferExists() && Buffer.isBuffer(x);
}
function makeSuccess(index, value) {
return {
status: true,
index: index,
value: value,
furthest: -1,
expected: []
};
}
function makeFailure(index, expected) {
if (!isArray(expected)) {
expected = [expected];
}
return {
status: false,
index: -1,
value: null,
furthest: index,
expected: expected
};
}
function mergeReplies(result, last) {
if (!last) {
return result;
}
if (result.furthest > last.furthest) {
return result;
}
var expected =
result.furthest === last.furthest
? union(result.expected, last.expected)
: last.expected;
return {
status: result.status,
index: result.index,
value: result.value,
furthest: last.furthest,
expected: expected
};
}
function makeLineColumnIndex(input, i) {
if (isBuffer(input)) {
return {
offset: i,
line: -1,
column: -1
};
}
var lines = input.slice(0, i).split("\n");
// Note that unlike the character offset, the line and column offsets are
// 1-based.
var lineWeAreUpTo = lines.length;
var columnWeAreUpTo = lines[lines.length - 1].length + 1;
return {
offset: i,
line: lineWeAreUpTo,
column: columnWeAreUpTo
};
}
// Returns the sorted set union of two arrays of strings
function union(xs, ys) {
var obj = {};
for (var i = 0; i < xs.length; i++) {
obj[xs[i]] = true;
}
for (var j = 0; j < ys.length; j++) {
obj[ys[j]] = true;
}
var keys = [];
for (var k in obj) {
if ({}.hasOwnProperty.call(obj, k)) {
keys.push(k);
}
}
keys.sort();
return keys;
}
function assertParser(p) {
if (!isParser(p)) {
throw new Error("not a parser: " + p);
}
}
function get(input, i) {
if (typeof input === "string") {
return input.charAt(i);
}
return input[i];
}
// TODO[ES5]: Switch to Array.isArray eventually.
function assertArray(x) {
if (!isArray(x)) {
throw new Error("not an array: " + x);
}
}
function assertNumber(x) {
if (typeof x !== "number") {
throw new Error("not a number: " + x);
}
}
function assertRegexp(x) {
if (!(x instanceof RegExp)) {
throw new Error("not a regexp: " + x);
}
var f = flags(x);
for (var i = 0; i < f.length; i++) {
var c = f.charAt(i);
// Only allow regexp flags [imu] for now, since [g] and [y] specifically
// mess up Parsimmon. If more non-stateful regexp flags are added in the
// future, this will need to be revisited.
if (c !== "i" && c !== "m" && c !== "u") {
throw new Error('unsupported regexp flag "' + c + '": ' + x);
}
}
}
function assertFunction(x) {
if (typeof x !== "function") {
throw new Error("not a function: " + x);
}
}
function assertString(x) {
if (typeof x !== "string") {
throw new Error("not a string: " + x);
}
}
// -*- Error Formatting -*-
var linesBeforeStringError = 2;
var linesAfterStringError = 3;
var bytesPerLine = 8;
var bytesBefore = bytesPerLine * 5;
var bytesAfter = bytesPerLine * 4;
var defaultLinePrefix = " ";
function repeat(string, amount) {
return new Array(amount + 1).join(string);
}
function formatExpected(expected) {
if (expected.length === 1) {
return "Expected:\n\n" + expected[0];
}
return "Expected one of the following: \n\n" + expected.join(", ");
}
function leftPad(str, pad, char) {
var add = pad - str.length;
if (add <= 0) {
return str;
}
return repeat(char, add) + str;
}
function toChunks(arr, chunkSize) {
var length = arr.length;
var chunks = [];
var chunkIndex = 0;
if (length <= chunkSize) {
return [arr.slice()];
}
for (var i = 0; i < length; i++) {
if (!chunks[chunkIndex]) {
chunks.push([]);
}
chunks[chunkIndex].push(arr[i]);
if ((i + 1) % chunkSize === 0) {
chunkIndex++;
}
}
return chunks;
}
// Get a range of indexes including `i`-th element and `before` and `after` amount of elements from `arr`.
function rangeFromIndexAndOffsets(i, before, after, length) {
return {
// Guard against the negative upper bound for lines included in the output.
from: i - before > 0 ? i - before : 0,
to: i + after > length ? length : i + after
};
}
function byteRangeToRange(byteRange) {
// Exception for inputs smaller than `bytesPerLine`
if (byteRange.from === 0 && byteRange.to === 1) {
return {
from: byteRange.from,
to: byteRange.to
};
}
return {
from: byteRange.from / bytesPerLine,
// Round `to`, so we don't get float if the amount of bytes is not divisible by `bytesPerLine`
to: Math.floor(byteRange.to / bytesPerLine)
};
}
function formatGot(input, error) {
var index = error.index;
var i = index.offset;
var verticalMarkerLength = 1;
var column;
var lineWithErrorIndex;
var lines;
var lineRange;
var lastLineNumberLabelLength;
if (i === input.length) {
return "Got the end of the input";
}
if (isBuffer(input)) {
var byteLineWithErrorIndex = i - (i % bytesPerLine);
var columnByteIndex = i - byteLineWithErrorIndex;
var byteRange = rangeFromIndexAndOffsets(
byteLineWithErrorIndex,
bytesBefore,
bytesAfter + bytesPerLine,
input.length
);
var bytes = input.slice(byteRange.from, byteRange.to);
var bytesInChunks = toChunks(bytes.toJSON().data, bytesPerLine);
var byteLines = map(function(byteRow) {
return map(function(byteValue) {
// Prefix byte values with a `0` if they are shorter than 2 characters.
return leftPad(byteValue.toString(16), 2, "0");
}, byteRow);
}, bytesInChunks);
lineRange = byteRangeToRange(byteRange);
lineWithErrorIndex = byteLineWithErrorIndex / bytesPerLine;
column = columnByteIndex * 3;
// Account for an extra space.
if (columnByteIndex >= 4) {
column += 1;
}
verticalMarkerLength = 2;
lines = map(function(byteLine) {
return byteLine.length <= 4
? byteLine.join(" ")
: byteLine.slice(0, 4).join(" ") + " " + byteLine.slice(4).join(" ");
}, byteLines);
lastLineNumberLabelLength = (
(lineRange.to > 0 ? lineRange.to - 1 : lineRange.to) * 8
).toString(16).length;
if (lastLineNumberLabelLength < 2) {
lastLineNumberLabelLength = 2;
}
} else {
var inputLines = input.split(/\r\n|[\n\r\u2028\u2029]/);
column = index.column - 1;
lineWithErrorIndex = index.line - 1;
lineRange = rangeFromIndexAndOffsets(
lineWithErrorIndex,
linesBeforeStringError,
linesAfterStringError,
inputLines.length
);
lines = inputLines.slice(lineRange.from, lineRange.to);
lastLineNumberLabelLength = lineRange.to.toString().length;
}
var lineWithErrorCurrentIndex = lineWithErrorIndex - lineRange.from;
if (isBuffer(input)) {
lastLineNumberLabelLength = (
(lineRange.to > 0 ? lineRange.to - 1 : lineRange.to) * 8
).toString(16).length;
if (lastLineNumberLabelLength < 2) {
lastLineNumberLabelLength = 2;
}
}
var linesWithLineNumbers = reduce(
function(acc, lineSource, index) {
var isLineWithError = index === lineWithErrorCurrentIndex;
var prefix = isLineWithError ? "> " : defaultLinePrefix;
var lineNumberLabel;
if (isBuffer(input)) {
lineNumberLabel = leftPad(
((lineRange.from + index) * 8).toString(16),
lastLineNumberLabelLength,
"0"
);
} else {
lineNumberLabel = leftPad(
(lineRange.from + index + 1).toString(),
lastLineNumberLabelLength,
" "
);
}
return [].concat(
acc,
[prefix + lineNumberLabel + " | " + lineSource],
isLineWithError
? [
defaultLinePrefix +
repeat(" ", lastLineNumberLabelLength) +
" | " +
leftPad("", column, " ") +
repeat("^", verticalMarkerLength)
]
: []
);
},
[],
lines
);
return linesWithLineNumbers.join("\n");
}
function formatError(input, error) {
return [
"\n",
"-- PARSING FAILED " + repeat("-", 50),
"\n\n",
formatGot(input, error),
"\n\n",
formatExpected(error.expected),
"\n"
].join("");
}
function flags(re) {
var s = "" + re;
return s.slice(s.lastIndexOf("/") + 1);
}
function anchoredRegexp(re) {
return RegExp("^(?:" + re.source + ")", flags(re));
}
// -*- Combinators -*-
function seq() {
var parsers = [].slice.call(arguments);
var numParsers = parsers.length;
for (var j = 0; j < numParsers; j += 1) {
assertParser(parsers[j]);
}
return Parsimmon(function(input, i) {
var result;
var accum = new Array(numParsers);
for (var j = 0; j < numParsers; j += 1) {
result = mergeReplies(parsers[j]._(input, i), result);
if (!result.status) {
return result;
}
accum[j] = result.value;
i = result.index;
}
return mergeReplies(makeSuccess(i, accum), result);
});
}
function seqObj() {
var seenKeys = {};
var totalKeys = 0;
var parsers = toArray(arguments);
var numParsers = parsers.length;
for (var j = 0; j < numParsers; j += 1) {
var p = parsers[j];
if (isParser(p)) {
continue;
}
if (isArray(p)) {
var isWellFormed =
p.length === 2 && typeof p[0] === "string" && isParser(p[1]);
if (isWellFormed) {
var key = p[0];
if (Object.prototype.hasOwnProperty.call(seenKeys, key)) {
throw new Error("seqObj: duplicate key " + key);
}
seenKeys[key] = true;
totalKeys++;
continue;
}
}
throw new Error(
"seqObj arguments must be parsers or [string, parser] array pairs."
);
}
if (totalKeys === 0) {
throw new Error("seqObj expects at least one named parser, found zero");
}
return Parsimmon(function(input, i) {
var result;
var accum = {};
for (var j = 0; j < numParsers; j += 1) {
var name;
var parser;
if (isArray(parsers[j])) {
name = parsers[j][0];
parser = parsers[j][1];
} else {
name = null;
parser = parsers[j];
}
result = mergeReplies(parser._(input, i), result);
if (!result.status) {
return result;
}
if (name) {
accum[name] = result.value;
}
i = result.index;
}
return mergeReplies(makeSuccess(i, accum), result);
});
}
function seqMap() {
var args = [].slice.call(arguments);
if (args.length === 0) {
throw new Error("seqMap needs at least one argument");
}
var mapper = args.pop();
assertFunction(mapper);
return seq.apply(null, args).map(function(results) {
return mapper.apply(null, results);
});
}
// TODO[ES5]: Revisit this with Object.keys and .bind.
function createLanguage(parsers) {
var language = {};
for (var key in parsers) {
if ({}.hasOwnProperty.call(parsers, key)) {
(function(key) {
var func = function() {
return parsers[key](language);
};
language[key] = lazy(func);
})(key);
}
}
return language;
}
function alt() {
var parsers = [].slice.call(arguments);
var numParsers = parsers.length;
if (numParsers === 0) {
return fail("zero alternates");
}
for (var j = 0; j < numParsers; j += 1) {
assertParser(parsers[j]);
}
return Parsimmon(function(input, i) {
var result;
for (var j = 0; j < parsers.length; j += 1) {
result = mergeReplies(parsers[j]._(input, i), result);
if (result.status) {
return result;
}
}
return result;
});
}
function sepBy(parser, separator) {
// Argument asserted by sepBy1
return sepBy1(parser, separator).or(succeed([]));
}
function sepBy1(parser, separator) {
assertParser(parser);
assertParser(separator);
var pairs = separator.then(parser).many();
return seqMap(parser, pairs, function(r, rs) {
return [r].concat(rs);
});
}
// -*- Core Parsing Methods -*-
_.parse = function(input) {
if (typeof input !== "string" && !isBuffer(input)) {
throw new Error(
".parse must be called with a string or Buffer as its argument"
);
}
var result = this.skip(eof)._(input, 0);
if (result.status) {
return {
status: true,
value: result.value
};
}
return {
status: false,
index: makeLineColumnIndex(input, result.furthest),
expected: result.expected
};
};
// -*- Other Methods -*-
_.tryParse = function(str) {
var result = this.parse(str);
if (result.status) {
return result.value;
} else {
var msg = formatError(str, result);
var err = new Error(msg);
err.type = "ParsimmonError";
err.result = result;
throw err;
}
};
_.or = function(alternative) {
return alt(this, alternative);
};
_.trim = function(parser) {
return this.wrap(parser, parser);
};
_.wrap = function(leftParser, rightParser) {
return seqMap(leftParser, this, rightParser, function(left, middle) {
return middle;
});
};
_.thru = function(wrapper) {
return wrapper(this);
};
_.then = function(next) {
assertParser(next);
return seq(this, next).map(function(results) {
return results[1];
});
};
_.many = function() {
var self = this;
return Parsimmon(function(input, i) {
var accum = [];
var result = undefined;
for (;;) {
result = mergeReplies(self._(input, i), result);
if (result.status) {
if (i === result.index) {
throw new Error(
"infinite loop detected in .many() parser --- calling .many() on " +
"a parser which can accept zero characters is usually the cause"
);
}
i = result.index;
accum.push(result.value);
} else {
return mergeReplies(makeSuccess(i, accum), result);
}
}
});
};
_.tieWith = function(separator) {
assertString(separator);
return this.map(function(args) {
assertArray(args);
if (args.length) {
assertString(args[0]);
var s = args[0];
for (var i = 1; i < args.length; i++) {
assertString(args[i]);
s += separator + args[i];
}
return s;
} else {
return "";
}
});
};
_.tie = function() {
return this.tieWith("");
};
_.times = function(min, max) {
var self = this;
if (arguments.length < 2) {
max = min;
}
assertNumber(min);
assertNumber(max);
return Parsimmon(function(input, i) {
var accum = [];
var result = undefined;
var prevResult = undefined;
for (var times = 0; times < min; times += 1) {
result = self._(input, i);
prevResult = mergeReplies(result, prevResult);
if (result.status) {
i = result.index;
accum.push(result.value);
} else {
return prevResult;
}
}
for (; times < max; times += 1) {
result = self._(input, i);
prevResult = mergeReplies(result, prevResult);
if (result.status) {
i = result.index;
accum.push(result.value);
} else {
break;
}
}
return mergeReplies(makeSuccess(i, accum), prevResult);
});
};
_.result = function(res) {
return this.map(function() {
return res;
});
};
_.atMost = function(n) {
return this.times(0, n);
};
_.atLeast = function(n) {
return seqMap(this.times(n), this.many(), function(init, rest) {
return init.concat(rest);
});
};
_.map = function(fn) {
assertFunction(fn);
var self = this;
return Parsimmon(function(input, i) {
var result = self._(input, i);
if (!result.status) {
return result;
}
return mergeReplies(makeSuccess(result.index, fn(result.value)), result);
});
};
_.contramap = function(fn) {
assertFunction(fn);
var self = this;
return Parsimmon(function(input, i) {
var result = self.parse(fn(input.slice(i)));
if (!result.status) {
return result;
}
return makeSuccess(i + input.length, result.value);
});
};
_.promap = function(f, g) {
assertFunction(f);
assertFunction(g);
return this.contramap(f).map(g);
};
_.skip = function(next) {
return seq(this, next).map(function(results) {
return results[0];
});
};
_.mark = function() {
return seqMap(index, this, index, function(start, value, end) {
return {
start: start,
value: value,
end: end
};
});
};
_.node = function(name) {
return seqMap(index, this, index, function(start, value, end) {
return {
name: name,
value: value,
start: start,
end: end
};
});
};
_.sepBy = function(separator) {
return sepBy(this, separator);
};
_.sepBy1 = function(separator) {
return sepBy1(this, separator);
};
_.lookahead = function(x) {
return this.skip(lookahead(x));
};
_.notFollowedBy = function(x) {
return this.skip(notFollowedBy(x));
};
_.desc = function(expected) {
if (!isArray(expected)) {
expected = [expected];
}
var self = this;
return Parsimmon(function(input, i) {
var reply = self._(input, i);
if (!reply.status) {
reply.expected = expected;
}
return reply;
});
};
_.fallback = function(result) {
return this.or(succeed(result));
};
_.ap = function(other) {
return seqMap(other, this, function(f, x) {
return f(x);
});
};
_.chain = function(f) {
var self = this;
return Parsimmon(function(input, i) {
var result = self._(input, i);
if (!result.status) {
return result;
}
var nextParser = f(result.value);
return mergeReplies(nextParser._(input, result.index), result);
});
};
// -*- Constructors -*-
function string(str) {
assertString(str);
var expected = "'" + str + "'";
return Parsimmon(function(input, i) {
var j = i + str.length;
var head = input.slice(i, j);
if (head === str) {
return makeSuccess(j, head);
} else {
return makeFailure(i, expected);
}
});
}
function byte(b) {
ensureBuffer();
assertNumber(b);
if (b > 0xff) {
throw new Error(
"Value specified to byte constructor (" +
b +
"=0x" +
b.toString(16) +
") is larger in value than a single byte."
);
}
var expected = (b > 0xf ? "0x" : "0x0") + b.toString(16);
return Parsimmon(function(input, i) {
var head = get(input, i);
if (head === b) {
return makeSuccess(i + 1, head);
} else {
return makeFailure(i, expected);
}
});
}
function regexp(re, group) {
assertRegexp(re);
if (arguments.length >= 2) {
assertNumber(group);
} else {
group = 0;
}
var anchored = anchoredRegexp(re);
var expected = "" + re;
return Parsimmon(function(input, i) {
var match = anchored.exec(input.slice(i));
if (match) {
if (0 <= group && group <= match.length) {
var fullMatch = match[0];
var groupMatch = match[group];
return makeSuccess(i + fullMatch.length, groupMatch);
}
var message =
"valid match group (0 to " + match.length + ") in " + expected;
return makeFailure(i, message);
}
return makeFailure(i, expected);
});
}
function succeed(value) {
return Parsimmon(function(input, i) {
return makeSuccess(i, value);
});
}
function fail(expected) {
return Parsimmon(function(input, i) {
return makeFailure(i, expected);
});
}
function lookahead(x) {
if (isParser(x)) {
return Parsimmon(function(input, i) {
var result = x._(input, i);
result.index = i;
result.value = "";
return result;
});
} else if (typeof x === "string") {
return lookahead(string(x));
} else if (x instanceof RegExp) {
return lookahead(regexp(x));
}
throw new Error("not a string, regexp, or parser: " + x);
}
function notFollowedBy(parser) {
assertParser(parser);
return Parsimmon(function(input, i) {
var result = parser._(input, i);
var text = input.slice(i, result.index);
return result.status
? makeFailure(i, 'not "' + text + '"')
: makeSuccess(i, null);
});
}
function test(predicate) {
assertFunction(predicate);
return Parsimmon(function(input, i) {
var char = get(input, i);
if (i < input.length && predicate(char)) {
return makeSuccess(i + 1, char);
} else {
return makeFailure(i, "a character/byte matching " + predicate);
}
});
}
function oneOf(str) {
var expected = str.split("");
for (var idx = 0; idx < expected.length; idx++) {
expected[idx] = "'" + expected[idx] + "'";
}
return test(function(ch) {
return str.indexOf(ch) >= 0;
}).desc(expected);
}
function noneOf(str) {
return test(function(ch) {
return str.indexOf(ch) < 0;
}).desc("none of '" + str + "'");
}
function custom(parsingFunction) {
return Parsimmon(parsingFunction(makeSuccess, makeFailure));
}
// TODO[ES5]: Improve error message using JSON.stringify eventually.
function range(begin, end) {
return test(function(ch) {
return begin <= ch && ch <= end;
}).desc(begin + "-" + end);
}
function takeWhile(predicate) {
assertFunction(predicate);
return Parsimmon(function(input, i) {
var j = i;
while (j < input.length && predicate(get(input, j))) {
j++;
}
return makeSuccess(j, input.slice(i, j));
});
}
function lazy(desc, f) {
if (arguments.length < 2) {
f = desc;
desc = undefined;
}
var parser = Parsimmon(function(input, i) {
parser._ = f()._;
return parser._(input, i);
});
if (desc) {
return parser.desc(desc);
} else {
return parser;
}
}
// -*- Fantasy Land Extras -*-
function empty() {
return fail("fantasy-land/empty");
}
_.concat = _.or;
_.empty = empty;
_.of = succeed;
_["fantasy-land/ap"] = _.ap;
_["fantasy-land/chain"] = _.chain;
_["fantasy-land/concat"] = _.concat;
_["fantasy-land/empty"] = _.empty;
_["fantasy-land/of"] = _.of;
_["fantasy-land/map"] = _.map;
// -*- Base Parsers -*-
var index = Parsimmon(function(input, i) {
return makeSuccess(i, makeLineColumnIndex(input, i));
});
var any = Parsimmon(function(input, i) {
if (i >= input.length) {
return makeFailure(i, "any character/byte");
}
return makeSuccess(i + 1, get(input, i));
});
var all = Parsimmon(function(input, i) {
return makeSuccess(input.length, input.slice(i));
});
var eof = Parsimmon(function(input, i) {
if (i < input.length) {
return makeFailure(i, "EOF");
}
return makeSuccess(i, null);
});
var digit = regexp(/[0-9]/).desc("a digit");
var digits = regexp(/[0-9]*/).desc("optional digits");
var letter = regexp(/[a-z]/i).desc("a letter");
var letters = regexp(/[a-z]*/i).desc("optional letters");
var optWhitespace = regexp(/\s*/).desc("optional whitespace");
var whitespace = regexp(/\s+/).desc("whitespace");
var cr = string("\r");
var lf = string("\n");
var crlf = string("\r\n");
var newline = alt(crlf, lf, cr).desc("newline");
var end = alt(newline, eof);
Parsimmon.all = all;
Parsimmon.alt = alt;
Parsimmon.any = any;
Parsimmon.cr = cr;
Parsimmon.createLanguage = createLanguage;
Parsimmon.crlf = crlf;
Parsimmon.custom = custom;
Parsimmon.digit = digit;
Parsimmon.digits = digits;
Parsimmon.empty = empty;
Parsimmon.end = end;
Parsimmon.eof = eof;
Parsimmon.fail = fail;
Parsimmon.formatError = formatError;
Parsimmon.index = index;
Parsimmon.isParser = isParser;
Parsimmon.lazy = lazy;
Parsimmon.letter = letter;
Parsimmon.letters = letters;
Parsimmon.lf = lf;
Parsimmon.lookahead = lookahead;
Parsimmon.makeFailure = makeFailure;
Parsimmon.makeSuccess = makeSuccess;
Parsimmon.newline = newline;
Parsimmon.noneOf = noneOf;
Parsimmon.notFollowedBy = notFollowedBy;
Parsimmon.of = succeed;
Parsimmon.oneOf = oneOf;
Parsimmon.optWhitespace = optWhitespace;
Parsimmon.Parser = Parsimmon;
Parsimmon.range = range;
Parsimmon.regex = regexp;
Parsimmon.regexp = regexp;
Parsimmon.sepBy = sepBy;
Parsimmon.sepBy1 = sepBy1;
Parsimmon.seq = seq;
Parsimmon.seqMap = seqMap;
Parsimmon.seqObj = seqObj;
Parsimmon.string = string;
Parsimmon.succeed = succeed;
Parsimmon.takeWhile = takeWhile;
Parsimmon.test = test;
Parsimmon.whitespace = whitespace;
Parsimmon["fantasy-land/empty"] = empty;
Parsimmon["fantasy-land/of"] = succeed;
Parsimmon.Binary = {
bitSeq: bitSeq,
bitSeqObj: bitSeqObj,
byte: byte,
buffer: parseBuffer,
encodedString: encodedString,
uintBE: uintBE,
uint8BE: uintBE(1),
uint16BE: uintBE(2),
uint32BE: uintBE(4),
uintLE: uintLE,
uint8LE: uintLE(1),
uint16LE: uintLE(2),
uint32LE: uintLE(4),
intBE: intBE,
int8BE: intBE(1),
int16BE: intBE(2),
int32BE: intBE(4),
intLE: intLE,
int8LE: intLE(1),
int16LE: intLE(2),
int32LE: intLE(4),
floatBE: floatBE(),
floatLE: floatLE(),
doubleBE: doubleBE(),
doubleLE: doubleLE()
};
var Lang = Parsimmon.createLanguage({
Value: function(r) {
return Parsimmon.alt(
r.Number,
r.Symbol,
r.List
);
},
Number: function() {
return Parsimmon.regexp(/[0-9]+/).map(Number);
},
Symbol: function() {
return Parsimmon.regexp(/[a-z]+/);
},
List: function(r) {
return Parsimmon.string('(')
.then(r.Value.sepBy(r._))
.skip(Parsimmon.string(')'));
},
_: function() {
return Parsimmon.optWhitespace;
}
});
Lang.Value.tryParse('(list 1 2 foo (list nice 3 56 989 asdasdas))');
var CustomString =
Parsimmon.string('%')
.then(Parsimmon.any)
.chain(function(start) {
var end = {
'[': ']',
'(': ')',
'{': '}',
'<': '>'
}[start] || start;
return Parsimmon.takeWhile(function(c) {
return c !== end;
}).skip(Parsimmon.string(end));
});
CustomString.parse('%:a string:'); // => {status: true, value: 'a string'}
CustomString.parse('%[a string]'); // => {status: true, value: 'a string'}
CustomString.parse('%{a string}'); // => {status: true, value: 'a string'}
CustomString.parse('%(a string)'); // => {status: true, value: 'a string'}
CustomString.parse('%<a string>'); // => {status: true, value: 'a string'}
var pNum =
Parsimmon.regexp(/[0-9]+/)
.map(Number)
.map(function(x) {
return x + 1;
});
var pr = pNum.parse('9');
function printObject(o) {
var out = 'init';
for (var p in o) {
out += p + ': ' + o[p] + '\n';
}
alert(out);
}
var aii = 1;
print(pr.toString());
String.prototype.repeat = function(num) {
if (num < 0) {
return '';
} else {
return new Array(num + 1).join(this);
}
};
function is_defined(x) {
return typeof x !== 'undefined';
}
function is_object(x) {
return Object.prototype.toString.call(x) === "[object Object]";
}
function is_array(x) {
return Object.prototype.toString.call(x) === "[object Array]";
}
/**
* Main.
*/
function xlog(v, label) {
var tab = 0;
var rt = function() {
return ' '.repeat(tab);
};
// Log Fn
var lg = function(x) {
// Limit
if (tab > 10) return '[...]';
var r = '';
if (!is_defined(x)) {
r = '[VAR: UNDEFINED]';
} else if (x === '') {
r = '[VAR: EMPTY STRING]';
} else if (is_array(x)) {
r = '[\n';
tab++;
for (var k in x) {
r += rt() + k + ' : ' + lg(x[k]) + ',\n';
}
tab--;
r += rt() + ']';
} else if (is_object(x)) {
r = '{\n';
tab++;
for (var k in x) {
r += rt() + k + ' : ' + lg(x[k]) + ',\n';
}
tab--;
r += rt() + '}';
} else {
r = x;
}
return r;
};
// Space
print('\n\n');
// Log
print('< ' + (is_defined(label) ? (label + ' ') : '') + Object.prototype.toString.call(v) + ' >\n' + lg(v));
};
var o = {
'aaa' : 123,
'bbb' : 'zzzz',
'o' : {
'obj1' : 'val1',
'obj2' : 'val2',
'obj3' : [1, 3, 5, 6],
'obj4' : {
'a' : 'aaaa',
'b' : null
}
},
'a' : [ 'asd', 123, false, true ],
'func' : function() {
alert('test');
},
'fff' : false,
't' : true,
'nnn' : null
};
xlog(o); // Without label
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment