const {
buildIdentifier,
buildVariableDeclaration,
buildVariableDeclarator,
} = require('./ast-build');
const { difference } = require('./utils.js');
/**
* Represents a scope in a program.
*/
class Scope {
constructor(parent) {
this.parent = parent;
this.initialized = new Set();
this.used = new Set();
this.VarDeclarators = [];
}
/**
* Adds a variable to the scope.
* @param {string} name - The name of the variable.
*/
add(name) {
this.initialized.add(name);
this.VarDeclarators.push(buildVariableDeclarator(buildIdentifier(name)));
}
/**
* Sets a variable as initialized in the scope.
* @param {string} name - The name of the variable.
*/
setAsInitialized(name) {
this.initialized.add(name);
}
/**
* Sets a variable as used in the scope.
* @param {string} name - The name of the variable.
*/
setAsUsed(name) {
this.used.add(name);
}
/**
* Checks if a variable is declared in the scope.
* @param {string} name - The name of the variable.
* @returns {boolean} - True if the variable is declared, false otherwise.
*/
has(name) {
return this.initialized.has(name);
}
/**
* Builds the variable declaration for the scope.
* @returns {VariableDeclaration} - The built variable declaration.
*/
buildDeclaration() {
return buildVariableDeclaration(this.VarDeclarators);
}
/**
* Looks up a variable in the scope and its parent scopes.
* @param {string} name - The name of the variable.
* @returns {Scope|null} - The scope where the variable is declared, or null if not found.
*/
lookup(name) {
if (this.has(name)) return this;
if (this.parent) return this.parent.lookup(name);
return null;
}
/**
* Finds variables that are used but not declared in the scope.
* @returns {Set<string>} - The set of not declared variables.
*/
notDeclared() {
let notDeclared = difference(this.used, this.initialized);
for(let v of this.used) {
let s = this.lookup(v);
if (s) notDeclared.delete(v);
}
return notDeclared;
}
/**
* Generates a message for not declared variables in the scope.
* @returns {string|null} - The message for not declared variables, or null if all variables are declared.
*/
notDeclaredMessage() {
let d = this.notDeclared();
if (d.size > 0) {
return Array.from(d)
.map(x => x.replace(/^[$]/, ''))
.map(x => `Not declared variable '${x}'`).join(',');
}
return null;
}
/**
* Gets the number of variable declarators in the scope.
* @returns {number} - The number of variable declarators.
*/
get length() {
return this.VarDeclarators.length;
}
}
module.exports = Scope;