function test(tokens, i, t) {
    let res = {
        match: false,
        j: null // position of child
    };

    let ii = t.shift !== undefined ? i + t.shift : t.position;
    let token = get(tokens, ii); // supports negative ii

    if (token === undefined) {
        return res;
    }

    for (let key in t) {
        if (key === 'shift' || key === 'position') {
            continue;
        }

        if (token[key] === undefined) {
            return res;
        }

        if (key === 'children' && isArrayOfObjects(t.children)) {
            if (token.children.length === 0) {
                return res;
            }
            let match;
            let childTests = t.children;
            let children = token.children;
            if (childTests.every(tt => tt.position !== undefined)) {
                // positions instead of shifts, do not loop all children
                match = childTests.every(tt => test(children, tt.position, tt).match);
                if (match) {
                    // we may need position of child in transform
                    let j = last(childTests).position;
                    res.j = j >= 0 ? j : children.length + j;
                }
            } else {
                for (let j = 0; j < children.length; j++) {
                    match = childTests.every(tt => test(children, j, tt).match);
                    if (match) {
                        res.j = j;
                        // all tests true, continue with next key of pattern t
                        break;
                    }
                }
            }

            if (match === false) {
                return res;
            }

            continue;
        }

        switch (typeof t[key]) {
            case 'boolean':
            case 'number':
            case 'string':
                if (token[key] !== t[key]) {
                    return res;
                }
                break;
            case 'function':
                if (!t[key](token[key])) {
                    return res;
                }
                break;
            case 'object':
                if (isArrayOfFunctions(t[key])) {
                    let r = t[key].every(tt => tt(token[key]));
                    if (r === false) {
                        return res;
                    }
                    break;
                }
            // fall through for objects !== arrays of functions
            default:
                throw new Error(
                    `Unknown type of pattern test (key: ${key}). Test should be of type boolean, number, string, function or array of functions.`
                );
        }
    }

    // no tests returned false -> all tests returns true
    res.match = true;
    return res;
}
Test if t matches token stream.

@param {array} tokens
@param {number} i
@param {object} t Test to match.
@return {object} { match: true|false, j: null|number }