P = require('parsimmon')

parser = P.createLanguage({

  atom: () => P.regexp(/[A-Z][A-Z0-9]*/).desc('atom'),

  sexp: (r) => P.seqMap(
    P.string("("),
    r.sexp,
    P.string("."),
    r.sexp,
    P.string(")"),
    (_lpar, l, _dot, r, _rpar) => cons(l, r))
                .or(P.seqMap(
                  P.string("("),
                  P.sepBy(r.sexp, P.string(" ")),
                  P.string(")"),
                  (_lpar, elems, _rpar) => list(...elems)))
                .or(r.atom)
})

ps = (s) => parser.sexp.tryParse(s)

// pretty printer. A bit quick and dirty.
pp_cons_or_list = sexp => sexp == null ? '\b' : atom(sexp) ? ' . ' + sexp : pp_list(sexp, true)
pp_list = (sexp, in_list) => (in_list ? '' : '(') + pp(sexp[0]) + ' ' + pp_cons_or_list(sexp[1]) + (in_list ? '' : ')')
do_pp = sexp => atom(sexp) ? `${sexp}` : pp_list(sexp, false)
pp = sexp => do_pp(sexp).replace(' \b', '')

console.log(pp(ps("(AA B C)")))
console.log(pp(ps("(AA.(B.C))")))
console.log(pp(ps("(A B (C.(D.E)))")))
console.log(pp(ps("((A.B) (C.D))")))

assertEqual(ps("(AA.(B.C))"),
            ['AA', [ 'B', 'C']])
assertEqual(ps("ATOM"),
            'ATOM')
assertEqual(ps("(A B C)"),
            ['A', ['B', ['C', null]]])
