/*
 * loquat-token / token.js
 * copyright (c) 2016 Susisu
 */

/**
 * @module token
 */

"use strict";

module.exports = (_core, _prim, _char, _combinators) => {
    function end() {
        return Object.create({
            makeTokenParser
        });
    }

    const lazy = _core.lazy;

    const pure     = _prim.pure;
    const bind     = _prim.bind;
    const then     = _prim.then;
    const mplus    = _prim.mplus;
    const label    = _prim.label;
    const tryParse = _prim.tryParse;
    const skipMany = _prim.skipMany;

    const string  = _char.string;
    const satisfy = _char.satisfy;
    const oneOf   = _char.oneOf;
    const noneOf  = _char.noneOf;

    const between   = _combinators.between;
    const skipMany1 = _combinators.skipMany1;
    const sepBy     = _combinators.sepBy;
    const sepBy1    = _combinators.sepBy1;

    const spaceChars  = new Set(" \f\n\r\t\v");
    const simpleSpace = skipMany1(satisfy(char => spaceChars.has(char)));

    /**
     * @function module:token.makeTokenParser
     * @static
     * @param {module:language.LanguageDef} def
     * @returns {Object}
     */
    function makeTokenParser(def) {
        /*
         * white space
         */
        const whiteSpace = (() => {
            const noOneLineComment = def.commentLine === "" || def.commentLine === undefined;
            const noMultiLineComment = def.commentStart === ""
                || def.commentStart === undefined || def.commentEnd === undefined;
            const oneLineComment = noOneLineComment
                ? undefined
                : then(
                    tryParse(string(def.commentLine)),
                    then(
                        skipMany(satisfy(char => char !== "\n")),
                        pure(undefined)
                    )
                );
            const multiLineComment = noMultiLineComment
                ? undefined
                : (() => {
                    const commentStartEnd = def.commentStart + def.commentEnd;
                    const inCommentMulti = lazy(() => label(
                        mplus(
                            then(
                                tryParse(string(def.commentEnd)),
                                pure(undefined)
                            ),
                            mplus(
                                then(multiLineComment, inCommentMulti),
                                mplus(
                                    then(
                                        skipMany1(noneOf(commentStartEnd)),
                                        inCommentMulti
                                    ),
                                    then(
                                        oneOf(commentStartEnd),
                                        inCommentMulti
                                    )
                                )
                            )
                        ),
                        "end of comment"
                    ));
                    const inCommentSingle = lazy(() => label(
                        mplus(
                            then(
                                tryParse(string(def.commentEnd)),
                                pure(undefined)
                            ),
                            mplus(
                                then(
                                    skipMany1(noneOf(commentStartEnd)),
                                    inCommentSingle
                                ),
                                then(
                                    oneOf(commentStartEnd),
                                    inCommentSingle
                                )
                            )
                        ),
                        "end of comment"
                    ));
                    const inComment = def.nestedComments ? inCommentMulti : inCommentSingle;
                    return then(
                        tryParse(string(def.commentStart)),
                        inComment
                    );
                })();
            return skipMany(label(
                  noOneLineComment && noMultiLineComment ? simpleSpace
                : noOneLineComment                       ? mplus(simpleSpace, multiLineComment)
                : noMultiLineComment                     ? mplus(simpleSpace, oneLineComment)
                : mplus(simpleSpace, mplus(oneLineComment, multiLineComment)),
                ""
            ));
        })();

        function lexeme(parser) {
            return bind(parser, x => then(whiteSpace, pure(x)));
        }

        function symbol(name) {
            return lexeme(string(name));
        }

        /*
         * symbols
         */
        const parens = (() => {
            const lparen = symbol("(");
            const rparen = symbol(")");
            return parser => between(lparen, rparen, parser);
        })();
        const braces = (() => {
            const lbrace = symbol("{");
            const rbrace = symbol("}");
            return parser => between(lbrace, rbrace, parser);
        })();
        const angles = (() => {
            const langle = symbol("<");
            const rangle = symbol(">");
            return parser => between(langle, rangle, parser);
        })();
        const brackets = (() => {
            const lbracket = symbol("[");
            const rbracket = symbol("]");
            return parser => between(lbracket, rbracket, parser);
        })();

        const semi  = symbol(";");
        const comma = symbol(",");
        const colon = symbol(":");
        const dot   = symbol(".");

        function semiSep(parser) {
            return sepBy(parser, semi);
        }

        function semiSep1(parser) {
            return sepBy1(parser, semi);
        }

        function commaSep(parser) {
            return sepBy(parser, comma);
        }

        function commaSep1(parser) {
            return sepBy1(parser, comma);
        }

        return {
            whiteSpace,
            lexeme,
            symbol,
            parens,
            braces,
            angles,
            brackets,
            semi,
            comma,
            colon,
            dot,
            semiSep,
            semiSep1,
            commaSep,
            commaSep1
        };
    }

    return end();
};

@@ -545,16 +545,18 @@
 string  
+  
 = _char.
@@ -581,16 +581,18 @@
 satisfy 
+  
 = _char.
@@ -618,16 +618,18 @@
 oneOf   
+ 
+ 
 = _char.
@@ -653,16 +653,18 @@
 noneOf  
+  
 = _char.
@@ -670,16 +670,166 @@
 .noneOf;
+%0A    const digit     = _char.digit;%0A    const octDigit  = _char.octDigit;%0A    const hexDigit  = _char.hexDigit;%0A    const manyChar1 = _char.manyChar1;
 %0A%0A    co
@@ -6119,32 +6119,400 @@
 ma);%0A        %7D%0A%0A
+        /*%0A         * numbers%0A         */%0A        function number(base, baseDigit) %7B%0A            return bind(manyChar1(baseDigit), digits =%3E pure(parseInt(digits, base)));%0A        %7D%0A%0A        const decimal     = number(10, digit);%0A        const hexadecimal = then(oneOf(%22Xx%22), number(16, hexDigit));%0A        const octal       = then(oneOf(%22Oo%22), number(8, octDigit));%0A%0A
         return %7B
@@ -6817,16 +6817,81 @@
 ommaSep1
+,%0A            decimal,%0A            hexadecimal,%0A            octal
 %0A       

Add `decimal`, `hexadecimal`, and `octal`