import { generateBytecodeMap, IdentifierResolutionType, OpcodesBchSpec } from '@bitauth/libauth'; import { CashAssemblyIdentifierCollisionError } from './errors.ts'; import type { CompileCashAssemblyContext } from './types.ts'; /** * Set of opcode names that the compiler recognizes. */ const COMPILER_OPCODE_NAMES: ReadonlySet = new Set(Object.keys(generateBytecodeMap(OpcodesBchSpec))); /** * Parameters for {@link assertNoIdentifierCollisions} function. */ export type AssertNoIdentifierCollisionsParameters = Pick; /** * Libauth resolves opcodes, then variables, then scripts, and the first match silently hides the * rest, leading to unexpected compilation results and no errors. This function throws when one identifier matches more than one resolution type. * * @param {AssertNoIdentifierCollisionsParameters} parameters - Provided variables and optional template scripts. * @throws {@link CashAssemblyIdentifierCollisionError} - When one identifier matches more than one resolution type. */ export const assertNoIdentifierCollisions = (parameters: AssertNoIdentifierCollisionsParameters): void => { const { variables, templateScripts } = parameters; // Create a set of the script identifiers from the template scripts map. const scriptIdentifiers = new Set(Object.keys(templateScripts ?? {})); const variableNames = new Set(Object.keys(variables)); // Combine both, the variableNames and the scriptIdentifiers, to get a set of all identifiers. const declaredIdentifiers = new Set([ ...variableNames, ...scriptIdentifiers ]); // This loop will check for more than one occurrence of an identifier in OP_CODES, variables, and scripts for (const identifier of declaredIdentifiers) { // Resolution types is a native construct from libauth. const resolutionTypes: IdentifierResolutionType[] = []; // If the identifier is an opcode, then push it to the resolution types array, this will also be // detected by either or both of the variableNames and scriptIdentifiers checks. if (COMPILER_OPCODE_NAMES.has(identifier) === true) { resolutionTypes.push(IdentifierResolutionType.opcode); } // If the identifier is in variables, then push it to the resolution types array. if (variableNames.has(identifier) === true) { resolutionTypes.push(IdentifierResolutionType.variable); } // If the identifier is a script identifier, then push it to the resolution types array. if (scriptIdentifiers.has(identifier) === true) { resolutionTypes.push(IdentifierResolutionType.script); } // If there are more than one resolution types, then throw an error. if (resolutionTypes.length > 1) { throw new CashAssemblyIdentifierCollisionError(identifier, resolutionTypes); } } };