initial commit
commit
4d508f5894
|
@ -0,0 +1,123 @@
|
|||
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
|
|
@ -0,0 +1,242 @@
|
|||
var errors = function() {
|
||||
function ParseError(pos, msgs) {
|
||||
this.msgs = [].concat(msgs)
|
||||
this.line = pos.line
|
||||
this.column = pos.column }
|
||||
ParseError.prototype.show = function() {
|
||||
return this.msgs.join(', ') +
|
||||
" at" +
|
||||
" line: " + this.line +
|
||||
" column: " + this.column }
|
||||
ParseError.prototype.combine = function(errs) {
|
||||
if (!'length' in errs) errs = [errs]
|
||||
return new CombinedError([this].concat(errs)) }
|
||||
|
||||
function CombinedError(errs) {
|
||||
this.errs = errs ? errs : [] }
|
||||
CombinedError.prototype.combine = function(errs) {
|
||||
if (!'length' in errs) errs = [errs]
|
||||
this.errs = this.errs.concat(errs) }
|
||||
|
||||
function unknown_error(pos) {
|
||||
return new ParseError(pos, "Unknown error") }
|
||||
function unexpect_error(pos, msg) {
|
||||
return new ParseError(pos, "Unexpected " + msg) }
|
||||
function expect_error(pos, msg) {
|
||||
return new ParseError(pos, "Expected " + msg) }
|
||||
|
||||
function merge_errors() {
|
||||
var a0 = arguments[0]
|
||||
var ar = Array.prototype.slice.call(arguments, 1)
|
||||
return ar.length ? a0.combine(ar) : a0 }
|
||||
|
||||
return {
|
||||
Parse: ParseError
|
||||
, unknown: unknown_error
|
||||
, unexpect: unexpect_error
|
||||
, expect: expect_error
|
||||
, merge: merge_errors }}()
|
||||
|
||||
var p_string = function() {
|
||||
function Parse_String_State(str) {
|
||||
function Parse_State(pos, line, column) {
|
||||
this.pos = pos || 0
|
||||
this.line = line || 1
|
||||
this.column = column || 1 }
|
||||
Parse_State.prototype.str = str
|
||||
Parse_State.prototype.increment = function(c) {
|
||||
var newline = c == '\n'
|
||||
return new Parse_State(this.pos+1, newline?this.line+1:this.line, newline?1:this.column+1) }
|
||||
Parse_State.prototype.getToken = function() {
|
||||
return this.str[this.pos] }
|
||||
Parse_State.prototype.length = function() {
|
||||
return this.str.length }
|
||||
return Parse_State }
|
||||
|
||||
|
||||
function char(x) {
|
||||
return function(t) { return x == t }}
|
||||
function digit() { return function(x){ var cx = x.charCodeAt(0); return cx < 58 && cx > 47 }}
|
||||
function letter() {
|
||||
return function(x) {
|
||||
var cx = x.charCodeAt(0)
|
||||
return (cx < 91 && cx > 64) || (cx < 123 && cx > 96) }}
|
||||
function whitespace() {
|
||||
return function(x) {
|
||||
return x == ' ' || x == '\t' || x == '\r' || x == '\n' }}
|
||||
|
||||
function control() {
|
||||
return function(x) {
|
||||
return x.charCodeAt(0) < 32 }}
|
||||
|
||||
function run(p, input, cb) {
|
||||
var str_state = new (Parse_String_State(input))()
|
||||
function pok(x) {
|
||||
cb(null, x) }
|
||||
function perr(err) {
|
||||
cb(err) }
|
||||
p(str_state, pok, perr, pok, perr) }
|
||||
|
||||
return {
|
||||
Parse_State: Parse_String_State
|
||||
, char: char
|
||||
, digit: digit
|
||||
, letter: letter
|
||||
, whitespace: whitespace
|
||||
, control: control
|
||||
, run: run }}()
|
||||
|
||||
|
||||
var combinators = function() {
|
||||
function always(x) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
eok(x, state) }}
|
||||
|
||||
// avoiding infinite recursions
|
||||
function wrap(f) {
|
||||
return function(x) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
return f(x)(state, cok, cerr, eok, eerr) }}}
|
||||
|
||||
function bind_2(p, f) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
function pcok(item, state) {
|
||||
var q = f(item)
|
||||
q(state, cok, cerr, cok, cerr) }
|
||||
function peok(item, state) {
|
||||
wrap(f)(item)(state, cok, cerr, eok, eerr) }
|
||||
p(state, pcok, cerr, peok, eerr) }}
|
||||
|
||||
function bind() {
|
||||
var ar = arguments, ari, arn = ar.length - 1
|
||||
if (arn == 1) return bind_2(arguments[0], arguments[1])
|
||||
var last = ar[arn]
|
||||
var res = new Array(arn), resi
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
ari = resi = 0;
|
||||
bind(ar[ari++], function bind_arg2(x) {
|
||||
res[resi++] = x
|
||||
if (resi == arn) return last.apply(this, res)
|
||||
else return bind(ar[ari++], bind_arg2) })
|
||||
(state, cok, cerr, eok, eerr)}}
|
||||
|
||||
function next(p, q) {
|
||||
return bind(p, function() { return q }) }
|
||||
|
||||
function nxtnxt() {
|
||||
var ar = Array.prototype.slice.call(arguments)
|
||||
return ar.reduceRight(function(p, c) {
|
||||
return next(c, p) }) }
|
||||
|
||||
function never(err) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
eerr(errors.unknown(state)) } }
|
||||
|
||||
function either(p, q) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
function p_eerr(err_from_p) {
|
||||
function q_eerr(err_from_q) {
|
||||
eerr(errors.merge(err_from_p, err_from_q)) }
|
||||
q(state, cok, cerr, eok, q_eerr) }
|
||||
p(state, cok, cerr, eok, p_eerr) } }
|
||||
|
||||
function attempt(p) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
p(state, cok, eerr, eok, eerr) }}
|
||||
|
||||
function token(consume_p) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
var t = state.getToken()
|
||||
if (!t) eerr(errors.unexpect(state, 'end of input'))
|
||||
else if (consume_p(t)) cok(t, state.increment(t))
|
||||
else eerr(errors.unexpect(state, "token " + t)) } }
|
||||
|
||||
function times(n, p) {
|
||||
if (n == 0) return always(null)
|
||||
return function (state, cok, cerr, eok, eerr) {
|
||||
var res = new Array(n)
|
||||
var resi = 0
|
||||
function pcok(item, state) {
|
||||
function peok(item, state) {
|
||||
while(resi < n) res[resi++] = item
|
||||
cok(res, state) }
|
||||
res[resi++] = item
|
||||
if (resi < n) p(state, pcok, cerr, pcok, eerr)
|
||||
else if (resi == n) cok(res, state) }
|
||||
function peok(item, state) {
|
||||
while(resi < n) res[resi++] = item
|
||||
eok(res, state) }
|
||||
p(state, pcok, cerr, peok, eerr) } }
|
||||
|
||||
function many(p) {
|
||||
function many_err() {
|
||||
throw new TypeError('`many` applied to parser that accepts an empty string') }
|
||||
function safe_p(state, cok, cerr, eok, eerr) {
|
||||
p(state, cok, cerr, many_err, eerr) }
|
||||
return either(
|
||||
bind(safe_p, wrap(many)(p), function(x, xs) {
|
||||
return always([x].concat(xs)) })
|
||||
, always([])) }
|
||||
|
||||
function many1(p) {
|
||||
return bind(p, many(p), function(x, xs) {
|
||||
return always([x].concat(xs) )})}
|
||||
|
||||
function lookahead(p) {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
function ok(t) {
|
||||
eok(t, state) }
|
||||
p(state, ok, cerr, eok, eerr) } }
|
||||
|
||||
function choice(first) {
|
||||
/* either + varargs */
|
||||
var ar = Array.prototype.slice.call(arguments, 1)
|
||||
if (ar.length == 0 && !first) return never
|
||||
if (ar.length == 0 && first) return first
|
||||
return either(first, choice.apply(null, ar)) }
|
||||
|
||||
function eof() {
|
||||
return function(state, cok, cerr, eok, eerr) {
|
||||
if (state.pos == state.length()) eok(null, state)
|
||||
else eerr(errors.expect(state, "end of input")) } }
|
||||
|
||||
function between(open, close, p) {
|
||||
return bind(open, p, close, function(o, x, c) { return always(x) }) }
|
||||
|
||||
return {
|
||||
always: always
|
||||
, bind: bind
|
||||
, next: next
|
||||
, nxtnxt: nxtnxt
|
||||
, never: never
|
||||
, wrap: wrap
|
||||
, either: either
|
||||
, attempt: attempt
|
||||
, many: many
|
||||
, many1: many1
|
||||
, times: times
|
||||
, token: token
|
||||
, lookahead: lookahead
|
||||
, choice: choice
|
||||
, between: between
|
||||
, eof: eof }}()
|
||||
|
||||
var funp = function() {
|
||||
// functional programming helpers, especially useful with token()
|
||||
function not(f) {
|
||||
return function(x) { return !f(x) }}
|
||||
function and(p, q) {
|
||||
return function(x) { return p(x) && q(x) }}
|
||||
function or(p, q) { /* todo more than 2 args */
|
||||
if (arguments.length > 2) return Array.prototype.slice.call(arguments).reduce(function(p,q){return or(p,q)})
|
||||
return function(x) { return p(x) || q(x) }}
|
||||
return {
|
||||
not: not
|
||||
, and: and
|
||||
, or: or }}()
|
||||
|
||||
module.exports = {
|
||||
combinators: combinators
|
||||
, errors: errors
|
||||
, p_string: p_string
|
||||
, funp: funp }
|
|
@ -0,0 +1,12 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var json = require('./json')
|
||||
var parsec = require('./parsec')
|
||||
var fs = require('fs')
|
||||
|
||||
//var contents = fs.readFileSync('test.json').toString()
|
||||
//console.log(contents)
|
||||
parsec.p_string.run(json.value(), '10 /* */ \n', function(e, x) {
|
||||
if (e) console.log(e.errs)
|
||||
else console.log(x)
|
||||
})
|
Loading…
Reference in New Issue