commit
4d508f5894
3 changed files with 377 additions and 0 deletions
@ -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