"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = parse;
const tslib_1 = require("tslib");
const good_enough_parser_1 = require("good-enough-parser");
const logger_1 = require("../../../logger");
const memCache = tslib_1.__importStar(require("../../../util/cache/memory"));
const hash_1 = require("../../../util/hash");
const rules_1 = require("./rules");
function emptyCtx(source) {
    return {
        source,
        results: [],
        stack: [],
    };
}
function currentFragment(ctx) {
    const deepestFragment = ctx.stack[ctx.stack.length - 1];
    return deepestFragment;
}
function extractTreeValue(source, tree, offset) {
    if (tree.type === 'wrapped-tree') {
        const { endsWith } = tree;
        const to = endsWith.offset + endsWith.value.length;
        return source.slice(offset, to);
    }
    // istanbul ignore next
    return '';
}
/**
 * Matches key-value pairs:
 * - `tag = "1.2.3"`
 * - `name = "foobar"`
 * - `deps = ["foo", "bar"]`
 * - `
 *     artifacts = [
         maven.artifact(
           group = "com.example1",
           artifact = "foobar",
           version = "1.2.3",
         )
       ]
     `
 **/
const kwParams = good_enough_parser_1.query
    .sym((ctx, { value: recordKey }) => {
    return { ...ctx, recordKey };
})
    .op('=')
    .alt(
// string
good_enough_parser_1.query.str((ctx, { offset, value }) => {
    const frag = currentFragment(ctx);
    if (frag.type === 'record' && ctx.recordKey) {
        const key = ctx.recordKey;
        frag.children[key] = { type: 'string', value, offset };
    }
    return ctx;
}), 
// array of strings or calls
good_enough_parser_1.query.tree({
    type: 'wrapped-tree',
    maxDepth: 1,
    startsWith: '[',
    endsWith: ']',
    preHandler: (ctx, tree) => {
        const parentRecord = currentFragment(ctx);
        if (parentRecord.type === 'record' &&
            ctx.recordKey &&
            tree.type === 'wrapped-tree') {
            const key = ctx.recordKey;
            parentRecord.children[key] = {
                type: 'array',
                value: '',
                offset: tree.startsWith.offset,
                children: [],
            };
        }
        return ctx;
    },
    search: good_enough_parser_1.query.alt(good_enough_parser_1.query.str((ctx, { value, offset }) => {
        const parentRecord = currentFragment(ctx);
        if (parentRecord.type === 'record' && ctx.recordKey) {
            const key = ctx.recordKey;
            const array = parentRecord.children[key];
            if (array.type === 'array') {
                array.children.push({ type: 'string', value, offset });
            }
        }
        return ctx;
    }), good_enough_parser_1.query
        .sym()
        .handler(recordStartHandler)
        .handler((ctx, { value, offset }) => {
        const ruleFragment = currentFragment(ctx);
        if (ruleFragment.type === 'record') {
            ruleFragment.children._function = {
                type: 'string',
                value,
                offset,
            };
        }
        return ctx;
    })
        .many(good_enough_parser_1.query.op('.').sym((ctx, { value }) => {
        const ruleFragment = currentFragment(ctx);
        if (ruleFragment.type === 'record' &&
            ruleFragment.children._function) {
            ruleFragment.children._function.value += `.${value}`;
        }
        return ctx;
    }), 0, 3)
        .tree({
        type: 'wrapped-tree',
        maxDepth: 1,
        startsWith: '(',
        endsWith: ')',
        search: good_enough_parser_1.query
            .opt(good_enough_parser_1.query
            .sym((ctx, { value: subRecordKey }) => ({
            ...ctx,
            subRecordKey,
        }))
            .op('='))
            .str((ctx, { value: subRecordValue, offset }) => {
            const argIndex = ctx.argIndex ?? 0;
            const subRecordKey = ctx.subRecordKey ?? argIndex.toString();
            const ruleFragment = currentFragment(ctx);
            if (ruleFragment.type === 'record') {
                ruleFragment.children[subRecordKey] = {
                    type: 'string',
                    value: subRecordValue,
                    offset,
                };
            }
            delete ctx.subRecordKey;
            ctx.argIndex = argIndex + 1;
            return ctx;
        }),
        postHandler: (ctx, tree) => {
            delete ctx.argIndex;
            const callFrag = currentFragment(ctx);
            ctx.stack.pop();
            if (callFrag.type === 'record' && tree.type === 'wrapped-tree') {
                callFrag.value = extractTreeValue(ctx.source, tree, callFrag.offset);
                const parentRecord = currentFragment(ctx);
                if (parentRecord.type === 'record' && ctx.recordKey) {
                    const key = ctx.recordKey;
                    const array = parentRecord.children[key];
                    if (array.type === 'array') {
                        array.children.push(callFrag);
                    }
                }
            }
            return ctx;
        },
    })),
    postHandler: (ctx, tree) => {
        const parentRecord = currentFragment(ctx);
        if (parentRecord.type === 'record' &&
            ctx.recordKey &&
            tree.type === 'wrapped-tree') {
            const key = ctx.recordKey;
            const array = parentRecord.children[key];
            if (array.type === 'array') {
                array.value = extractTreeValue(ctx.source, tree, array.offset);
            }
        }
        return ctx;
    },
}))
    .handler((ctx) => {
    delete ctx.recordKey;
    return ctx;
});
/**
 * Matches rule signature:
 *   `git_repository(......)`
 *                  ^^^^^^^^
 *
 * @param search something to match inside parens
 */
function ruleCall(search) {
    return good_enough_parser_1.query.tree({
        type: 'wrapped-tree',
        maxDepth: 1,
        search,
        postHandler: (ctx, tree) => {
            const frag = currentFragment(ctx);
            if (frag.type === 'record' && tree.type === 'wrapped-tree') {
                frag.value = extractTreeValue(ctx.source, tree, frag.offset);
                ctx.stack.pop();
                ctx.results.push(frag);
            }
            return ctx;
        },
    });
}
function recordStartHandler(ctx, { offset }) {
    ctx.stack.push({
        type: 'record',
        value: '',
        offset,
        children: {},
    });
    return ctx;
}
function ruleNameHandler(ctx, { value, offset }) {
    const ruleFragment = currentFragment(ctx);
    if (ruleFragment.type === 'record') {
        ruleFragment.children['rule'] = { type: 'string', value, offset };
    }
    return ctx;
}
/**
 * Matches regular rules:
 * - `git_repository(...)`
 * - `_go_repository(...)`
 */
const regularRule = good_enough_parser_1.query
    .sym(rules_1.supportedRulesRegex, (ctx, token) => ruleNameHandler(recordStartHandler(ctx, token), token))
    .join(ruleCall(kwParams));
/**
 * Matches "maybe"-form rules:
 * - `maybe(git_repository, ...)`
 * - `maybe(_go_repository, ...)`
 */
const maybeRule = good_enough_parser_1.query
    .sym('maybe', recordStartHandler)
    .join(ruleCall(good_enough_parser_1.query.alt(good_enough_parser_1.query.begin().sym(rules_1.supportedRulesRegex, ruleNameHandler).op(','), kwParams)));
const rule = good_enough_parser_1.query.alt(maybeRule, regularRule);
const query = good_enough_parser_1.query.tree({
    type: 'root-tree',
    maxDepth: 16,
    search: rule,
});
function getCacheKey(input) {
    const hashedInput = (0, hash_1.hash)(input);
    return `bazel-parser-${hashedInput}`;
}
const starlark = good_enough_parser_1.lang.createLang('starlark');
function parse(input, packageFile) {
    const cacheKey = getCacheKey(input);
    const cachedResult = memCache.get(cacheKey);
    // istanbul ignore if
    if (cachedResult === null || cachedResult) {
        return cachedResult;
    }
    let result = null;
    try {
        const parsedResult = starlark.query(input, query, emptyCtx(input));
        if (parsedResult) {
            result = parsedResult.results;
        }
    }
    catch (err) /* istanbul ignore next */ {
        logger_1.logger.debug({ err, packageFile }, 'Bazel parsing error');
    }
    memCache.set(cacheKey, result);
    return result;
}
//# sourceMappingURL=parser.js.map