Skip to content

Commit 5799ba2

Browse files
authored
feat: add support for string manipulation functions (#161)
Adds support for the branch distance calculation of the following string methods: - startsWith - endsWith - includes
1 parent e7f82ec commit 5799ba2

File tree

4 files changed

+436
-6
lines changed

4 files changed

+436
-6
lines changed

libraries/search-javascript/lib/criterion/BranchDistance.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ export class BranchDistance extends CoreBranchDistance {
5757
let distance = visitor._getDistance(condition);
5858

5959
if (distance > 1 || distance < 0) {
60-
throw new Error("Invalid distance!");
60+
const variables_ = Object.entries(variables)
61+
.map(([key, value]) => `${key}=${value}`)
62+
.join(", ");
63+
throw new Error(
64+
`Invalid distance: ${distance} for ${condition} -> ${trueOrFalse}. Variables: ${variables_}`
65+
);
6166
}
6267

6368
if (Number.isNaN(distance)) {

libraries/search-javascript/lib/criterion/BranchDistanceVisitor.ts

Lines changed: 140 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,141 @@ export class BranchDistanceVisitor extends AbstractSyntaxTreeVisitor {
114114
path.skip();
115115
};
116116

117+
public CallExpression: (path: NodePath<t.CallExpression>) => void = (
118+
path
119+
) => {
120+
const callee = path.get("callee");
121+
122+
if (callee.isMemberExpression()) {
123+
const object = callee.get("object");
124+
object.visit();
125+
const property = callee.get("property");
126+
127+
if (property.isIdentifier()) {
128+
const objectValue = this._valueMap.get(object.toString());
129+
const argument = path.get("arguments")[0];
130+
argument.visit();
131+
const argumentValue = <string>this._valueMap.get(argument.toString());
132+
133+
// TODO should check if the value is actually a string
134+
if (typeof objectValue !== "string") {
135+
return;
136+
}
137+
138+
switch (property.node.name) {
139+
case "endsWith": {
140+
const endOfObject =
141+
objectValue.length > argumentValue.length
142+
? objectValue.slice(-argumentValue.length)
143+
: objectValue;
144+
145+
this._isDistanceMap.set(path.toString(), true);
146+
147+
if (this._inverted) {
148+
if (endOfObject === argumentValue) {
149+
this._valueMap.set(path.toString(), this._normalize(1));
150+
} else {
151+
this._valueMap.set(path.toString(), this._normalize(0));
152+
}
153+
} else {
154+
if (endOfObject === argumentValue) {
155+
this._valueMap.set(path.toString(), this._normalize(0));
156+
} else {
157+
this._valueMap.set(
158+
path.toString(),
159+
this._normalize(
160+
this._realCodedEditDistance(endOfObject, argumentValue)
161+
)
162+
);
163+
}
164+
}
165+
166+
break;
167+
}
168+
case "startsWith": {
169+
const startOfObject =
170+
objectValue.length > argumentValue.length
171+
? objectValue.slice(0, argumentValue.length)
172+
: objectValue;
173+
174+
this._isDistanceMap.set(path.toString(), true);
175+
176+
if (this._inverted) {
177+
if (startOfObject === argumentValue) {
178+
this._valueMap.set(path.toString(), this._normalize(1));
179+
} else {
180+
this._valueMap.set(path.toString(), this._normalize(0));
181+
}
182+
} else {
183+
if (startOfObject === argumentValue) {
184+
this._valueMap.set(path.toString(), this._normalize(0));
185+
} else {
186+
this._valueMap.set(
187+
path.toString(),
188+
this._normalize(
189+
this._realCodedEditDistance(startOfObject, argumentValue)
190+
)
191+
);
192+
}
193+
}
194+
195+
break;
196+
}
197+
case "includes": {
198+
this._isDistanceMap.set(path.toString(), true);
199+
200+
if (this._inverted) {
201+
if (objectValue.includes(argumentValue)) {
202+
this._valueMap.set(path.toString(), this._normalize(1));
203+
} else {
204+
this._valueMap.set(path.toString(), this._normalize(0));
205+
}
206+
} else {
207+
if (objectValue.includes(argumentValue)) {
208+
this._valueMap.set(path.toString(), this._normalize(0));
209+
} else {
210+
// search
211+
if (objectValue.length > argumentValue.length) {
212+
let minValue = Number.MAX_VALUE;
213+
for (
214+
let start = 0;
215+
start < objectValue.length - argumentValue.length;
216+
start++
217+
) {
218+
const substring = objectValue.slice(
219+
start,
220+
argumentValue.length
221+
);
222+
223+
minValue = Math.min(
224+
minValue,
225+
this._realCodedEditDistance(substring, argumentValue)
226+
);
227+
}
228+
229+
this._valueMap.set(
230+
path.toString(),
231+
this._normalize(minValue)
232+
);
233+
} else {
234+
this._valueMap.set(
235+
path.toString(),
236+
this._normalize(
237+
this._realCodedEditDistance(objectValue, argumentValue)
238+
)
239+
);
240+
}
241+
}
242+
}
243+
244+
break;
245+
}
246+
// No default
247+
}
248+
}
249+
}
250+
};
251+
117252
public Literal: (path: NodePath<t.Literal>) => void = (path) => {
118253
switch (path.node.type) {
119254
case "NullLiteral": {
@@ -572,12 +707,12 @@ export class BranchDistanceVisitor extends AbstractSyntaxTreeVisitor {
572707
"|>",
573708
].includes(operator)
574709
) {
575-
this._valueMap.set(path.toString(), this._normalize(<number>value));
710+
value = this._normalize(<number>value);
576711
this._isDistanceMap.set(path.toString(), true);
577712
} else {
578-
this._valueMap.set(path.toString(), value);
579713
this._isDistanceMap.set(path.toString(), false);
580714
}
715+
this._valueMap.set(path.toString(), value);
581716

582717
path.skip();
583718
};
@@ -689,7 +824,7 @@ export class BranchDistanceVisitor extends AbstractSyntaxTreeVisitor {
689824
return mi;
690825
}
691826

692-
protected _realCodedEditDistance(s: string, t: string) {
827+
public _realCodedEditDistance(s: string, t: string) {
693828
const d: number[][] = []; // matrix
694829
let index; // iterates through s
695830
let index_; // iterates through t
@@ -756,7 +891,7 @@ export class BranchDistanceVisitor extends AbstractSyntaxTreeVisitor {
756891
!this._stringAlphabet.includes(s_index)
757892
) {
758893
BranchDistanceVisitor.LOGGER.warn(
759-
`cannot search for character missing from the sampling alphabet one of these is missing: ${t_index}, ${s_index}`
894+
`Cannot search for character missing from the sampling alphabet one of these is missing: ${t_index}, ${s_index}`
760895
);
761896
cost = Number.MAX_VALUE;
762897
} else {
@@ -765,7 +900,7 @@ export class BranchDistanceVisitor extends AbstractSyntaxTreeVisitor {
765900
this._stringAlphabet.indexOf(t_index)
766901
);
767902
}
768-
// cost = this._normalize(cost);
903+
cost = this._normalize(cost);
769904
}
770905

771906
// Step 6

0 commit comments

Comments
 (0)