UNPKG

@specs-feup/lara

Version:

A js port of the popular framework for building source-to-source compilers

165 lines (131 loc) 4.18 kB
import { LaraJoinPoint } from "../../LaraJoinPoint.js"; import { arrayFromArgs } from "../core/LaraCore.js"; import Mutation from "./Mutation.js"; import MutationResult from "./MutationResult.js"; import Mutator from "./Mutator.js"; /** * Iterative mutator, allows to perform one mutation at a time, and restore the code before each mutation. * */ export default class IterativeMutator extends Mutator { joinPoints: LaraJoinPoint[] = []; mutations: Mutation[]; mutantIterator: Generator<MutationResult, void, unknown> | undefined = undefined; currentOriginalPoint: LaraJoinPoint | undefined = undefined; currentMutatedPoint: LaraJoinPoint | undefined = undefined; hasFinished: boolean = false; constructor(...mutations: Mutation[]) { super("IterativeMutator"); this.mutations = arrayFromArgs(mutations) as Mutation[]; if (this.mutations.length === 0) { throw "IterativeMutator needs at least one mutation"; } } /** * Introduces a single mutation to the code. * If there are no mutations left, does nothing. * * @returns True if a mutation occurred, false otherwise */ mutateSource() { // If no mutations left, do nothing if (!this.hasMutations()) { return false; } // If no iterator, create it if (this.mutantIterator === undefined) { this.mutantIterator = this.generator(); } // Get next element const element = this.mutantIterator.next(); if (!element.done) { return true; } this.hasFinished = true; return false; } protected mutatePrivate(): void { this.mutateSource(); } /** * If the code has been mutated, restores the code to its original state. If not, does nothing. */ restoreSource() { // If not mutated, return if (!this.isMutated) { return; } if (this.currentOriginalPoint === undefined) { throw "Original point is undefined"; } if (this.currentMutatedPoint === undefined) { throw "Mutated point is undefined"; } this.isMutated = false; this.currentMutatedPoint.insert("replace", this.currentOriginalPoint); // Unset mutated point this.currentMutatedPoint = undefined; } protected restorePrivate(): void { this.restoreSource(); } /** * @returns The point in the code where the mutation is occurring, or undefined if there are not more mutations left. */ getMutationPoint(): LaraJoinPoint | undefined { return this.currentOriginalPoint; } /** * @returns The point with currently mutated code, or undefined if the code is not currently mutated. */ getMutatedPoint(): LaraJoinPoint | undefined { return this.currentMutatedPoint; } private *generator() { if (this.isMutated) { throw "Code is currently mutated, cannot create a generator"; } // Iterate over all join points for (const $jp of this.joinPoints) { this.currentOriginalPoint = $jp; // Iterate over all mutations for (const mutation of this.mutations) { // If current mutation does not mutate the point, skip if (!mutation.isMutationPoint($jp)) { continue; } // Found a mutation point, iterate over all mutations for (const mutationResult of mutation.mutate($jp)) { const $mutatedJp = mutationResult.getMutation(); // Mutate code and return this.isMutated = true; this.currentMutatedPoint = $mutatedJp; $jp.insert("replace", $mutatedJp); yield mutationResult; // After resuming, restore this.restoreSource(); } } } // Mark as finished this.hasFinished = true; // Unset current original point this.currentOriginalPoint = undefined; } addJp($joinpoint: LaraJoinPoint): void { this.joinPoints.push($joinpoint); } addJps(...jps: LaraJoinPoint[]) { jps = arrayFromArgs(jps) as LaraJoinPoint[]; for (const $jp of jps) { this.addJp($jp); } } hasMutations(): boolean { return !this.hasFinished; } getCurrentMutation(): LaraJoinPoint | undefined { return this.currentMutatedPoint; } }