parsec/json.js

124 lines
3.8 KiB
JavaScript

var p = require('./parsec')
var c = p.combinators
var s = p.p_string
var f = p.funp
var char = function(ch) {
return c.token(p.p_string.char(ch)) }
var digit = function() {
return c.token(p.p_string.digit()) }
function literal(x, y) {
return c.bind(
c.nxtnxt.apply(null, x.split('').map(char))
, function() { return c.always(y) }) }
function many_str(parser) {
return c.bind(c.many(parser), function(x) { return c.always(x.join('')) }) }
function many1_str(parser) {
return c.bind(c.many1(parser), function(x) { return c.always(x.join('')) }) }
var line_comment = c.nxtnxt(
char('/')
, char('/')
, c.many(c.token(f.not(p.p_string.char('\n'))))
, char('\n'))
var inline_comment_content = c.next(
c.many(c.token(f.not(p.p_string.char('*'))))
, char('*'))
var inline_comment = c.nxtnxt(
char('/')
, char('*')
, c.many1(inline_comment_content)
, char('/'))
var comment = c.either(line_comment, inline_comment)
var opt_space = c.many(c.either(c.token(p.p_string.whitespace()), comment))
function between_whitespace(parser) {
return c.between(opt_space, opt_space, parser) }
function optional(def, parser) {
return c.either(parser, c.always(def)) }
// I was legitimately running out of stack space :-)
var trampoline = function() {
var i = 0
return function trampoline(parser) {
return function(state, cok, cerr, eok, eerr) {
// decrease this number if you get stack overflow errors :P
if (i++ < 15) return parser(state, cok, cerr, eok, eerr)
i=0
process.nextTick(function() { parser(state, cok, cerr, eok, eerr) })} } }()
var string = c.between(
char('"'), char('"')
, many_str(c.either(
c.token(f.not(f.or(s.control(), s.char('"'), s.char('\\'))))
// escaped chars
, c.bind(
char('\\')
, c.choice(
char('"'), char('\\'), char('/'), char('b')
, char('f'), char('n'), char('r'), char('t'))
, function(_, n) { return c.always('"\\/\b\f\n\r\t'['"\\/bfnrt'.indexOf(n)]) }))))
var number = c.bind(
// sign
optional('', char('-'))
// integer part
, c.either(char('0'), many1_str(digit()))
// fractional part
, optional('', c.next(char('.'), many1_str(digit())))
// 'e' part
, optional(0, c.bind(
c.either(char('e'), char('E'))
, c.choice(char('+'), char('-'), c.always('+'))
, many1_str(digit())
, function(_, sign, x) {
return c.always(+(sign+x)) }))
, function(sign, int, frac, exp) {
return c.always(+(sign + int + '.' + frac + 'e' + exp)) })
function array() {
return c.between(
char('['), char(']')
, c.many(c.bind(
c.wrap(value)() // avoid infinite recursion
// separator
, c.either(char(','), c.lookahead(char(']'))) // wrong: [1,2,] will work
, function(a) { return c.always(a) }))) }
function object() {
return c.between(
char('{')
, char('}')
, c.bind(
c.many(c.bind(
between_whitespace(string)
, char(':')
, c.wrap(value)() // avoid infinite recursion
, c.either(char(','), c.lookahead(char('}'))) // wrong: {"1":2,} will work
, function(k, _, v, _) {
return c.always([k, v]) }))
, function(pairs) {
var obj = {}
for (var i = 0; i < pairs.length; i++)
obj[pairs[i][0]] = pairs[i][1]
return c.always(obj)})) }
function value() {
return trampoline(between_whitespace(c.choice(
string
, number
, object()
, array()
, literal('true', true)
, literal('false', false)
, literal('null', null)))) }
module.exports.value = value