Source: RuleContext.js

const infernalUtils = require("./infernalUtils");

/**
 * This class is not exposed. Put a rule into its context to streamline rule execution.
 */
class RuleContext {


    /**
     * Creates an instance of the rule context with the engine, the rule and its path.
     * 
     * @param {InfernalEngine} engine The parent InfernalEngine
     * @param {Function} rule  The rule to execute.
     * @param {String} path The path of the rule.
     */
    constructor(engine, rule, path) {
        this.engine = engine;
        this.rule = rule;
        this.path = path;
        this.facts = [];
    }

    /**
     * Execute the rule within its context returning the 
     * resulting object to its contextualized facts.
     */
    async execute() {
        let params = [];
        this.facts.forEach(inputFact => {
            params.push(this.engine._facts.get(inputFact));
        });

        if(this.engine._trace) {
            this.engine._trace({
                action: "execute",
                rule: path,
                inputs: params
            });
        }

        let result = await this.rule.apply(null, params);
        if (!result) {
            return;
        }

        let context = infernalUtils.getContext(this.path);
        for (let key in result) {
            if (!result.hasOwnProperty(key)) continue;

            if (!key.startsWith("#")) {
                let path = key.startsWith("/") ? key : context + key;
                let value = result[key];
                let valueType = infernalUtils.typeof(value);
                if (valueType === "object") {
                    await this.engine.import(value, path);
                } 
                else if (valueType === "function") {
                    await this.engine.defRule(path, value);
                }
                else {
                    await this.engine.assert(path, value);
                }
                continue;
            }

            let action = result[key];
            switch (key) {

                case "#assert":
                    await this.engine.assert(action.path, action.value);
                    break;
                    
                case "#retract":
                    await this.engine.retract(action.path);
                    break;

                case "#def":
                case "#defRule":
                    await this.engine.def(action.path, action.value);
                    break;

                case "#undef":
                case "#undefRule":
                    await this.engine.undef(action.path);
                    break;

                case "#import":
                    await this.engine.import(action.value, action.path);
                    break;

                default:
                    throw new Error(`Invalid action: '${key}'.`);
            }
        }
    }
}

module.exports = RuleContext;