"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = void 0;
const tslib_1 = require("tslib");
const good_enough_parser_1 = require("good-enough-parser");
const hasha_1 = tslib_1.__importDefault(require("hasha"));
const logger_1 = require("../../../logger");
const memCache = tslib_1.__importStar(require("../../../util/cache/memory"));
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"]`
 **/
const kwParams = good_enough_parser_1.query
    .sym((ctx, { value: recordKey }) => ({ ...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
good_enough_parser_1.query.tree({
    type: 'wrapped-tree',
    maxDepth: 1,
    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.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;
    }),
    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 ruleStartHandler(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(ruleStartHandler(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', ruleStartHandler)
    .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 hash = (0, hasha_1.default)(input);
    return `bazel-parser-${hash}`;
}
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;
}
exports.parse = parse;
//# sourceMappingURL=parser.js.map