/**
* Universidad de La Laguna
* Escuela Superior de Ingeniería y Tecnología
* Grado en Ingeniería Informática
* Procesadores de Lenguajes
*
* @author Juan Rodríguez Suárez
* @since Mar 04 2024
* @desc Contains functions to analyze the scope of the program and the dependencies of the functions.
*/
const astTypes = require("ast-types");
const support = require("./support-lib.js");
// Build regexp matching the names of the support functions /factorial|power|min|max|.../
const FUNCTION_NAMES = Object.keys(support).map(n => n.replace(/[$*.^]/, '[$&]')); // Escape regexp special characters
const patternIsSupport = new RegExp(FUNCTION_NAMES.join('|'));
/**
* @brief Builds the set of function dependencies
* @param {object} dAst - The decorated AST
* @returns {object} The decorated AST
*/
function dependencies(dAst) {
let usedFunctions = new Set();
astTypes.visit(dAst.ast, {
visitCallExpression(path) {
const NODE = path.node;
if (patternIsSupport.test(NODE.callee.name)) {
usedFunctions.add(NODE.callee.name);
}
this.traverse(path);
}
});
dAst.dependencies = usedFunctions;
return dAst;
}
/**
* @brief Analyzes the scope of the program
* @param {object} dAst - The decorated AST
* @returns {object} The decorated AST
*/
const scopeAnalysis = (dAst) => {
const Scope = require("./scope-class.js");
let scope = new Scope(null); // global scope
let ast = dAst.ast;
astTypes.visit(ast, {
visitFunctionExpression(path) {
let node = path.node;
scope = new Scope(scope);
// mark parameters as initialized
const PARAMETERS = node.params;
for (const PARAM of PARAMETERS) {
scope.setAsInitialized(PARAM.name);
}
this.traverse(path);
if (scope.length > 0) { // insert declarations at the beginning of the function
node.body.body.unshift(scope.buildDeclaration());
}
node.scope = scope;
const NOT_DECLARED_MESSAGE = scope.notDeclaredMessage();
if (NOT_DECLARED_MESSAGE) {
console.error('*** WARNING: ' + NOT_DECLARED_MESSAGE + ' in function scope ***\n');
}
scope = scope.parent;
},
visitAssignmentExpression(path) {
const NODE = path.node;
if (NODE.left.type === 'Identifier') {
let name = NODE.left.name;
if (name && !scope.has(name)) {
if (!dAst.dependencies.has(name)) {
scope.add(name);
}
}
}
this.traverse(path);
},
visitIdentifier(path) {
const NAME = path.node.name;
if (/^[$]/.test(NAME) && !dAst.dependencies.has(NAME)) {
scope.setAsUsed(NAME);
}
this.traverse(path);
}
});
if (scope.length > 0) { // insert declarations at the beginning of the program
ast.body.unshift(scope.buildDeclaration());
}
ast.scope = scope;
const NOT_DECLARED_MESSAGE = scope.notDeclaredMessage();
if (NOT_DECLARED_MESSAGE) {
console.error('*** WARNING: ' + NOT_DECLARED_MESSAGE + ' in global scope ***\n');
}
return dAst;
};
module.exports = {
dependencies,
scopeAnalysis
}