Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

History of Transformations based on operation instances #157

Open
wants to merge 41 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
4aa5edb
draft: initial implementation of event listener for inserts
racoelhosilva Jul 8, 2024
8b61d9e
feat: finished code waypoints for Inserts and Detach
racoelhosilva Jul 8, 2024
e96221b
feat: finished code waypoints for other changes
racoelhosilva Jul 9, 2024
fd55bcf
Merge branch 'feature/clava-js' into feature/history
racoelhosilva Jul 9, 2024
d1c61d7
fix: fixing support for all transformations
racoelhosilva Jul 9, 2024
024d523
feat: changes to event system, new Event object
racoelhosilva Jul 19, 2024
339767b
feat: standardized event calls on JoinPoints.ts
racoelhosilva Jul 19, 2024
8a58471
feat: added History and Operation Interface
racoelhosilva Jul 19, 2024
7ec1d6f
feat: added InsertBefore and InsertAfter events
racoelhosilva Jul 19, 2024
8a2a0bc
test: insertBefore and insertAfter behaviour
racoelhosilva Jul 19, 2024
98c0f8f
feat: replaceWith, replaceWithStrings and toComment operations
racoelhosilva Jul 19, 2024
4b8673a
feat: replaceWith, replaceWithStrings and toComment operations
racoelhosilva Jul 19, 2024
c108726
test: replaceWith with singular string and JoinPoint, replaceWithStri…
racoelhosilva Jul 19, 2024
b2e6f62
feat: setType operation is implemented
racoelhosilva Jul 19, 2024
cf8c41e
test: setType behaviour
racoelhosilva Jul 19, 2024
12e1b0e
feat: setFirstChild and setLastChild operations implemented
racoelhosilva Jul 20, 2024
19a9d88
test: setFirstChild and setLastChild replace/set behaviour
racoelhosilva Jul 20, 2024
c46da77
feat: removeChildren operation implemented
racoelhosilva Jul 20, 2024
d4242ac
test: removeChildren behaviour
racoelhosilva Jul 20, 2024
15689ee
feat: setInlineComments operation implemented
racoelhosilva Jul 20, 2024
b00b872
test: setInlineComments behaviour
racoelhosilva Jul 20, 2024
5e7bc65
Merge branch 'feature/clava-js' into feature/history-rodrigo
racoelhosilva Jul 20, 2024
e12f3d3
feat: detach operation implemented
racoelhosilva Jul 20, 2024
c061a99
test: detach behaviour
racoelhosilva Jul 20, 2024
08cd0de
feat: rollback now receives an optional number of operations to undo …
racoelhosilva Jul 22, 2024
133a0c2
test: more complex tests focused on the History
racoelhosilva Jul 22, 2024
cd948e9
feat: added Checkpoint logic to History
racoelhosilva Jul 22, 2024
1d1844d
test: added tests for checkpoints and checkpoint errors
racoelhosilva Jul 22, 2024
f0dd10e
feat: added setValue operation
racoelhosilva Jul 23, 2024
9a0aab7
test: setValue behaviour
racoelhosilva Jul 23, 2024
4172c53
fix: standardization of event calls and type fix for return values
racoelhosilva Jul 23, 2024
957f904
fix: code cleanup
racoelhosilva Jul 23, 2024
13159e4
feat: standardized Joinpoints.ts
racoelhosilva Jul 23, 2024
8c49046
feat: updated Joinpoints.ts
racoelhosilva Jul 24, 2024
789b726
fix: code cleanup
racoelhosilva Jul 24, 2024
6e87725
fix: restructure the undo operations in the History class
racoelhosilva Jul 24, 2024
b07b0de
feat: added start and stop operations to history to improve performan…
racoelhosilva Jul 26, 2024
51f8691
fix: changed the start, stop, checkpoint behaviour
racoelhosilva Jul 26, 2024
236b31a
fix: changed the Event API into a separate module
racoelhosilva Jul 26, 2024
e7481dc
Merge branch 'staging' into feature/history-rodrigo
racoelhosilva Sep 8, 2024
c363c12
fix: separated the Event Listener from the History Event Handlers
racoelhosilva Sep 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,101 changes: 944 additions & 157 deletions Clava-JS/src-api/Joinpoints.ts

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Clava-JS/src-api/clava/events/EventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EventEmitter } from "events";

const eventListener = new EventEmitter();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a global event listener, instantiated by a class in in the lara-framework repo, and other libraries/code register events to that event listener.

Only a small part of this file should be moved to the lara-framework, the part that imports the EventEmitter and instantiates it, it should provide an EventListener class (or LaraEventListener) with a static method that returns the global event listener, and possibly some utility methods (if it makes sense).


export default eventListener;
28 changes: 28 additions & 0 deletions Clava-JS/src-api/clava/events/Events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Joinpoint } from "../../Joinpoints.js";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file can be moved to the lara-framework repo. It needs to have a generic parameter for the join point that extends LaraJoinPoint, then the weaver generator can specialize to the join point of the specific compiler it is generating code for.


export enum EventTime {
BEFORE = "Before",
AFTER = "After",
}

export class Event {
public timing: EventTime;
public description: string;
public mainJP: Joinpoint;
public returnValue?: any;
public inputs: unknown[];

constructor(
timing: EventTime,
description: string,
mainJP: Joinpoint,
returnJP?: Joinpoint,
...inputs: unknown[]
) {
this.timing = timing;
this.description = description;
this.mainJP = mainJP;
this.returnValue = returnJP;
this.inputs = inputs;
}
}
179 changes: 179 additions & 0 deletions Clava-JS/src-api/clava/history/History.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { registerSourceCode } from "lara-js/jest/jestHelpers";
import Clava from "../Clava";
import { FunctionJp, Joinpoint, Loop, ReturnStmt } from "../../Joinpoints";
import Query from "lara-js/api/weaver/Query";
import ophistory from "./History";
import {jest} from '@jest/globals'


const code: string = `void func() {
for (int i = 0; i < 1; i++){
i++;
}
for (int i = 0; i < 2; i++){
i++;
}
}

void test() {}

int main(int argc, char *argv[]) {
func();
return 0;
}
`;

describe("Transformation History: Multiple operations", () => {
registerSourceCode(code);
ophistory.checkpoint();

it("Inserts and detaches code comparison", () => {
const a: string = Clava.getProgram().code;

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;
const loopStmt2 = Query.search(Loop).get().at(1) as Joinpoint;
loopStmt1.insertBefore(loopStmt2.deepCopy());
loopStmt2.insertAfter(loopStmt1.deepCopy());

loopStmt1.detach();
loopStmt2.detach();

const b: string = Clava.getProgram().code;

ophistory.rollback(4);
const c: string = Clava.getProgram().code;

expect(a).toEqual(c);
expect(b).not.toEqual(c);
});

it("Replaces and detach code comparison", () => {
const a: string = Clava.getProgram().code;

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;
const returnStmt = Query.search(ReturnStmt).get().at(0) as Joinpoint;

let cur = loopStmt1.replaceWith(returnStmt.deepCopy());
cur = cur.replaceWith("aaaaa");
cur = cur.replaceWithStrings(["aaaaa", "bbbbb", "ccccc"]);
cur = cur.toComment();
cur.detach();

const b: string = Clava.getProgram().code;

ophistory.rollback(5);
const c: string = Clava.getProgram().code;

expect(a).toEqual(c);
expect(b).not.toEqual(c);
});

it("Children set and removes code comparison", () => {
const a: string = Clava.getProgram().code;

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;
const testFunc = Query.search(FunctionJp).get().at(1) as FunctionJp;
const returnStmt = Query.search(ReturnStmt).get().at(0) as Joinpoint;

testFunc.body.setFirstChild(loopStmt1.deepCopy());
testFunc.body.setLastChild(returnStmt.deepCopy());
testFunc.body.setFirstChild(loopStmt1.deepCopy());
testFunc.body.setLastChild(returnStmt.deepCopy());

const b: string = Clava.getProgram().code;

ophistory.rollback(4);
const c: string = Clava.getProgram().code;

expect(a).toEqual(c);
expect(b).not.toEqual(c);
});

it("Log an error message on undo operation (single rollback)", () => {
const errorSpy = jest.spyOn(global.console, "error")
.mockImplementation(() => {});

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;

loopStmt1.replaceWith("aaaa");
loopStmt1.detach();

ophistory.rollback(2);

expect(errorSpy).toHaveBeenCalledTimes(1);

errorSpy.mockRestore();
});

it("Checkpoints code comparison", () => {

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;
const loopStmt2 = Query.search(Loop).get().at(1) as Joinpoint;
loopStmt1.insertBefore(loopStmt2.deepCopy());
loopStmt2.insertAfter(loopStmt1.deepCopy());

ophistory.checkpoint();
const a: string = Clava.getProgram().code;

loopStmt1.detach();
loopStmt2.detach();

const b: string = Clava.getProgram().code;

ophistory.returnToLastCheckpoint();
const c: string = Clava.getProgram().code;

expect(a).toEqual(c);
expect(b).not.toEqual(c);
});

it("Log an error message on undo operation (checkpoint rollback)", () => {
const errorSpy = jest.spyOn(global.console, "error")
.mockImplementation(() => {});

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;

ophistory.checkpoint();
loopStmt1.replaceWith("aaaa");
loopStmt1.detach();

ophistory.returnToLastCheckpoint();

expect(errorSpy).toHaveBeenCalledTimes(1);

errorSpy.mockRestore();
});

it("Start and stop history recording", () => {
ophistory.stop();

const a: string = Clava.getProgram().code;

const loopStmt1 = Query.search(Loop).get().at(0) as Joinpoint;
loopStmt1.replaceWith("aaaa");

const b: string = Clava.getProgram().code;

ophistory.start();

const returnStmt = Query.search(Loop).get().at(0) as Joinpoint;
const comment = returnStmt.toComment();
ophistory.checkpoint();
const c: string = Clava.getProgram().code;

comment.detach();
const d: string = Clava.getProgram().code;

ophistory.returnToLastCheckpoint();
const e: string = Clava.getProgram().code;

expect(c).toEqual(e);
expect(a).not.toEqual(b);
expect(a).not.toEqual(c);
expect(a).not.toEqual(d);
expect(b).not.toEqual(c);
expect(b).not.toEqual(d);
expect(c).not.toEqual(d);
});

});
73 changes: 73 additions & 0 deletions Clava-JS/src-api/clava/history/History.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Operation } from "./Operations.js"
import "./OperationEvents.js"

class OperationHistory {
private operations: Operation[];
private locked: boolean;

constructor() {
this.operations = [];
this.locked = true;
}

private lock() {
this.locked = true;
}

private unlock() {
this.locked = false;
}

private undo() {
const op = this.operations.pop();
if (op !== undefined) {
try {
this.lock();
op.undo();
} catch (error) {
console.error("Failed to undo operation:", error);
} finally {
this.unlock();
}
}
}

start() {
this.operations.length = 0;
this.unlock();
}

stop() {
this.lock();
this.operations.length = 0;
}

newOperation(operation: Operation) {
if (!this.locked) {
this.operations.push(operation);
}
}

rollback(n: number = 1) {
if (n > 0){
while (n--){
this.undo();
}
}
}

checkpoint() {
this.operations.length = 0;
this.unlock();
}

returnToLastCheckpoint() {
while (this.operations.length > 0) {
this.undo();
}
}
}

const ophistory = new OperationHistory();

export default ophistory;
Loading