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

Fixes and improvements on the History API #160

Draft
wants to merge 43 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 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
d99bcf9
test: assuming that replaceWith(JP[]) works, the test is now enabled
racoelhosilva Jul 26, 2024
744deca
feat: assuming that setChild now returns the previously existing valu…
racoelhosilva Jul 26, 2024
7398eb8
test: replaceWith(Joinpoint[]) is now working and being tested
racoelhosilva Jul 28, 2024
ffb8427
docs: added README with History API Overview
racoelhosilva Jul 29, 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.

133 changes: 133 additions & 0 deletions Clava-JS/src-api/clava/events/EventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { EventEmitter } from "events";
import { Event, EventTime } from "./Events.js";
import ophistory from "../history/History.js"
import { DetachOperation, DetachReference, InlineCommentOperation, InsertOperation, RemoveChildrenOperation, ReplaceOperation, SetChildOperation, TypeChangeOperation, ValueOperation } from "../history/Operations.js";
import { Joinpoint } from "../../Joinpoints.js";

const eventListener = new EventEmitter();


eventListener.on("ACTION", (e: Event) => {

switch (e.timing) {
case EventTime.BEFORE:
switch (e.description) {
case "detach":
detachOperationFromEvent(e);
break;
case "removeChildren":
removeChildrenOperationFromEvent(e);
break;
case "setType":
changeTypeFromEvent(e);
break;
case "setInlineComments":
inlineCommentOperationFromEvent(e);
break;
case "setValue":
setValueOperationFromEvent(e);
break;
default:
break;
}
break;
case EventTime.AFTER:
switch (e.description){
case "insertAfter":
case "insertBefore":
insertOperationFromEvent(e);
break;
case "replaceWith":
if (e.inputs.length > 0){
if (typeof e.inputs[0] === 'string' || e.inputs[0] instanceof Joinpoint){
replaceSingleOperationFromEvent(e);
}
else {
replaceMultipleOperationFromEvent(e);
}
}
break;
case "replaceWithStrings":
if (e.inputs.length > 0){
replaceMultipleOperationFromEvent(e);
}
break;
case "toComment":
replaceSingleOperationFromEvent(e);
break;
case "setFirstChild":
setFirstChildFromEvent(e);
break;
case "setLastChild":
setLastChildFromEvent(e);
break;
default:
break;
}
break;
}
});

function insertOperationFromEvent(e: Event) {
if (e.returnValue !== undefined){
ophistory.newOperation(new InsertOperation(e.returnValue))
}
}

function replaceSingleOperationFromEvent(e: Event) {
if (e.returnValue !== undefined){
ophistory.newOperation(new ReplaceOperation(e.mainJP, e.returnValue, 1));
}
}

function replaceMultipleOperationFromEvent(e: Event) {
if (e.returnValue !== undefined){
ophistory.newOperation(new ReplaceOperation(e.mainJP, e.returnValue, (e.inputs[0] as (Joinpoint[] | string[])).length));
}
}

function detachOperationFromEvent(e: Event) {
let refJP: Joinpoint, ref: DetachReference;
if (e.mainJP.siblingsLeft.length >= 1){
refJP = e.mainJP.leftJp;
ref = DetachReference.LEFT;
} else if (e.mainJP.siblingsRight.length >= 1) {
refJP = e.mainJP.rightJp;
ref = DetachReference.RIGHT;
} else {
refJP = e.mainJP.parent;
ref = DetachReference.TOP;
}
ophistory.newOperation(new DetachOperation(e.mainJP, refJP, ref));
}

function setFirstChildFromEvent(e: Event) {
ophistory.newOperation(new SetChildOperation(e.mainJP.firstChild, e.returnValue));
}

function setLastChildFromEvent(e: Event) {
ophistory.newOperation(new SetChildOperation(e.mainJP.lastChild, e.returnValue));
}

function removeChildrenOperationFromEvent(e: Event) {
ophistory.newOperation(new RemoveChildrenOperation(e.mainJP, e.mainJP.children));
}

function changeTypeFromEvent(e: Event) {
ophistory.newOperation(new TypeChangeOperation(e.mainJP, e.mainJP.type));
}

function inlineCommentOperationFromEvent(e: Event) {
const comments = e.mainJP.inlineComments
.map((comment) => comment.text)
.filter((comment): comment is string => comment !== null);
ophistory.newOperation(new InlineCommentOperation(e.mainJP, comments));
}

function setValueOperationFromEvent(e: Event) {
if (e.inputs && e.inputs.at(0)) {
ophistory.newOperation(new ValueOperation(e.mainJP, e.inputs.at(0) as string, e.mainJP.getValue(e.inputs.at(0) as string)));
}
}

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";

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);
});

});
Loading
Loading