SOURCE

function detect(s, nums = [1, 2, 3, 4, 5]) {

    const REGX = /(\(|\)|\d+|\w+)/g;
    const NUM_REGX = new RegExp('^(' + nums.join('|') + ')$');

    let tokens = [];
    let match;

    // tokens
    while (match = REGX.exec(s)) {
        if (NUM_REGX.test(match[0])) {
            tokens.push({
                type: 'number',
                value: match[0]
            });
        }
        else if (/^(\(|\))$/.test(match[0])) {
            tokens.push({
                type: 'paren',
                value: match[0]
            });
        }
        else if (/^(and|or)$/.test(match[0])) {
            tokens.push({
                type: 'logic',
                value: match[0]
            });
        }
        else {
            console.warn('Error: Bad elements - ', match[0]);

            return false;
        }
    }

    console.log('TOKENS: ', tokens);

    // parser
    let parsed = [];
    let current = 0;

    function walk() {
        let token = tokens[current];

        if (token === undefined) {
            throw new TypeError('Missing right paren.');
        }
        else if (token.type === 'paren') {

            if (token.value === '(') {
                const expression = {
                    type: 'expression',
                    value: []
                };

                token = tokens[++current];

                if (token === undefined) {
                    throw new TypeError();
                }

                while (
                    (token.type !== 'paren')
                    || (
                        token.type === 'paren'
                        && token.value !== ')'
                    )
                ) {
                    expression.value.push(walk());
                    token = tokens[current];

                    if (token === undefined) {
                        throw new TypeError('Missing right paren.');
                    }
                }

                current++;

                return expression;
            }
            else {
                throw new TypeError('Error paren position.');
            }
        }
        else {

            current++;

            return token;
        }
    }

    try {
        while (current < tokens.length) {
            parsed.push(walk());
        }
    } catch (e) {
        console.warn('Error: ', e);

        return false;
    }

    console.log('PARSED: ', parsed);

    // validate
    function validate(s) {
        let hasOr;
        let hasAnd;

        for (let i = 0; i < s.length; i++) {
            const {type, value} = s[i];

            if (type === 'expression') {
                if (!validate(value)) {
                    return false;
                }

                continue;
            }

            const prev = s[i - 1];
            const next = s[i + 1];

            if (type === 'logic') {
                if (
                    (hasOr && value === 'and')
                    || (hasAnd && value === 'or')
                    || (prev === undefined || next === undefined)
                    || (prev.type === 'logic' || next.type === 'logic')
                ) {
                    return false;
                }

                hasOr = hasOr || value === 'or';
                hasAnd = hasAnd || value === 'and';
            }

            if (
                type === 'number'
                && (next && next.type === 'number')
            ) {
                return false;
            }

            // TODO
        }

        return true;
    }

    return validate(parsed);
}

function test(s) {
    console.log('TEST: ', s, detect(s));
}

test('(1 or 2) or (1 and (3 and 5))');
test('(1 or 2 and 3) or (1 and (3 and 5))');
test('(1 or 23) or (1 and (3 and 5))');
test(')1 and 3');
test('(1 and 3');

console 命令行工具 X clear

                    
>
console