Skip to content

Commit a6901c7

Browse files
Merge pull request #631 from GuillaumeGomez/concat-rules
Handle precedence of expression operators depending on operators and not depending on the global kind of the expression
2 parents f6da125 + e6c00e9 commit a6901c7

File tree

15 files changed

+110
-109
lines changed

15 files changed

+110
-109
lines changed

goml-script.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,16 @@ store-value: (effect, ":hover")
6666
set-text: ("element" + |variable|, "something " + 2 + " something else")
6767
```
6868

69-
Rules of concatenation are simple: if any of the element is a string, then it'll concatenate as a string. Examples:
69+
Operations are handled from the left to the right. Operations between parens are performed first:
7070

7171
```ignore
72-
1 + 2 + "a" // gives string "12a"
73-
1 + 2 // gives number 3
72+
1 + 2 + "a" + (4 * 3)
73+
// first the parens
74+
1 + 2 + "a" + 12
75+
// then from left to right
76+
3 + "a" + 12
77+
"3a" + 12
78+
"3a12"
7479
```
7580

7681
This is just a sub-part of expressions which allow more things.

src/ast.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ function replaceVariable(elem, variables, functionArgs, forceVariableAsString, e
3535
if (associatedValue instanceof Element) {
3636
// Nothing to be done in here.
3737
return associatedValue;
38-
} else if (['number', 'string', 'boolean'].includes(typeof associatedValue)) {
39-
if (typeof associatedValue === 'boolean') {
40-
return new IdentElement(
41-
associatedValue.toString(), startPos, endPos, lineNumber);
42-
} else if (typeof associatedValue === 'number' ||
43-
// eslint-disable-next-line no-extra-parens
44-
(!forceVariableAsString && matchInteger(associatedValue) === true)) {
45-
return new NumberElement(associatedValue, startPos, endPos, lineNumber);
46-
}
38+
} else if (typeof associatedValue === 'boolean') {
39+
return new IdentElement(
40+
associatedValue.toString(), startPos, endPos, lineNumber);
41+
} else if (typeof associatedValue === 'number' ||
42+
// eslint-disable-next-line no-extra-parens
43+
(!forceVariableAsString && matchInteger(associatedValue) === true)) {
44+
return new NumberElement(associatedValue, startPos, endPos, lineNumber);
45+
} else if (typeof associatedValue === 'string') {
4746
return new StringElement(
4847
associatedValue,
4948
startPos,

src/commands/navigation.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Commands changing the current location or reloading the page.
22

3-
const { cleanString } = require('../parser.js');
43
const { hasError } = require('../utils.js');
54
const { validator } = require('../validator.js');
65

@@ -22,7 +21,8 @@ function parseGoTo(parser) {
2221
return ret;
2322
}
2423

25-
const path = ret.value.value.trim();
24+
const path = ret.value.getStringValue();
25+
const code = ret.value.displayInCode();
2626

2727
let goto_arg;
2828
const permissions = 'await arg.browser.overridePermissions(page.url(), arg.permissions);';
@@ -32,11 +32,11 @@ function parseGoTo(parser) {
3232
|| path.startsWith('www.') === true
3333
|| path.startsWith('file://') === true
3434
) {
35-
goto_arg = `"${cleanString(path)}"`;
35+
goto_arg = code;
3636
} else if (path.startsWith('.')) {
37-
goto_arg = `page.url().split("/").slice(0, -1).join("/") + "/${cleanString(path)}"`;
37+
goto_arg = `page.url().split("/").slice(0, -1).join("/") + "/" + ${code}`;
3838
} else if (path.startsWith('/')) {
39-
goto_arg = `page.url().split("/").slice(0, -1).join("/") + "${cleanString(path)}"`;
39+
goto_arg = `page.url().split("/").slice(0, -1).join("/") + ${code}`;
4040
} else {
4141
return {'error': `a relative path or a full URL was expected, found \`${path}\``};
4242
}

src/parser.js

Lines changed: 30 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function isLetter(c) {
2121
function matchInteger(s) {
2222
if (typeof s === 'number' || s instanceof Number) {
2323
return true;
24-
} else if (typeof s === 'string' || s instanceof String) {
24+
} else if ((typeof s === 'string' || s instanceof String) && s.length !== 0) {
2525
for (let i = s.length - 1; i >= 0; --i) {
2626
if (!isNumber(s.charAt(i))) {
2727
return false;
@@ -158,39 +158,6 @@ function isArrayElementCompatible(expected, elem) {
158158
return expected.kind === elem.kind;
159159
}
160160

161-
// Used to concatenate all elements into a string, like `1 + "a" + 12` -> "1a12".
162-
function concatExprAsString(elems) {
163-
let out = '';
164-
for (const elem of elems) {
165-
if (elem.kind === 'operator') {
166-
continue;
167-
} else if (['number', 'string', 'boolean'].includes(elem.kind)) {
168-
out += elem.value;
169-
} else {
170-
out += concatExprAsString(elem.value);
171-
}
172-
}
173-
return out;
174-
}
175-
176-
function concatExprAsObjectPath(elems) {
177-
let s = '';
178-
const parts = [];
179-
180-
for (const elem of elems) {
181-
if (elem.kind === 'operator') {
182-
continue;
183-
} else if (elem.kind !== 'object-path') {
184-
s += concatExprAsString([elem]);
185-
} else {
186-
elem.value[0].value = s + elem.value[0].value;
187-
parts.push(...elem.value);
188-
s = '';
189-
}
190-
}
191-
return parts;
192-
}
193-
194161
// This function is used when generating an expression interpreted as a boolean.
195162
function concatExprAsExpr(elems) {
196163
const out = [];
@@ -261,24 +228,19 @@ function canBeCompared(kind1, kind2) {
261228
}
262229

263230
function convertAsString(elem) {
264-
if (elem.value.some(v => v.kind === 'object-path')) {
265-
return new ObjectPathElement(
266-
concatExprAsObjectPath(elem.value),
267-
elem.startPos,
268-
elem.endPos,
269-
elem.fullText,
270-
elem.line,
271-
elem.error,
272-
);
273-
}
274-
return new StringElement(
275-
concatExprAsString(elem.value),
276-
elem.startPos,
277-
elem.endPos,
278-
elem.fullText,
279-
elem.line,
280-
elem.error,
281-
);
231+
const pos = elem.value.findIndex(v => v.kind === 'object-path');
232+
elem.kind = 'string';
233+
if (pos !== -1) {
234+
// We remove the object-path from the expression.
235+
const objPath = elem.value.pop();
236+
// We remove the first item of the object path and push it at the end of the expression.
237+
elem.value.push(...objPath.value.splice(0, 1));
238+
// We put the expression as the first element of the object path.
239+
objPath.value.splice(0, 0, elem);
240+
// All done!
241+
return objPath;
242+
}
243+
return elem;
282244
}
283245

284246
function convertExprAs(elem, convertAs) {
@@ -363,7 +325,8 @@ class Element {
363325
}
364326

365327
isRecursive() {
366-
// Only Tuple and JSON elements are "recursive" (meaning they can contain sub-levels).
328+
// Expression, Array, Tuple and JSON elements are "recursive" (meaning they can contain
329+
// sub-levels).
367330
return false;
368331
}
369332

@@ -433,6 +396,18 @@ class ExpressionElement extends Element {
433396
super('expression', value, startPos, endPos, fullText, line, error);
434397
}
435398

399+
isRecursive() {
400+
return true;
401+
}
402+
403+
displayInCode() {
404+
return this.value.map(v => v.displayInCode()).join('');
405+
}
406+
407+
getStringValue() {
408+
return this.value.map(v => v.getStringValue()).join('');
409+
}
410+
436411
clone() {
437412
const elems = this.value.map(elem => elem.clone());
438413
return new this.constructor(
@@ -537,8 +512,8 @@ class ObjectPathElement extends Element {
537512
super('object-path', value, startPos, endPos, fullText, line, error);
538513
}
539514

540-
getStringValue(trim, clean = true) {
541-
const content = this.value.map(v => `"${v.getStringValue(clean)}"`).join(',');
515+
getStringValue() {
516+
const content = this.value.map(v => `${v.displayInCode()}`).join(',');
542517
return `[${content}]`;
543518
}
544519

tests/api-output/parseGoTo/basic-4.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
instructions = [
2-
"""const url = page.url().split(\"/\").slice(0, -1).join(\"/\") + \"/./a\";
2+
"""const url = page.url().split(\"/\").slice(0, -1).join(\"/\") + \"/\" + \"./a\";
33
try {
44
await page.goto(url);
55
} catch(exc) {

tests/api-output/parseGoTo/var-1.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
instructions = [
2-
"""const url = \"file://foo/a\";
2+
"""const url = \"file://\"+\"foo\"+\"/a\";
33
try {
44
await page.goto(url);
55
} catch(exc) {

tests/api-output/parseGoTo/var-3.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
instructions = [
2-
"""const url = \"http://foo/tadam/fa\";
2+
"""const url = \"http://foo/\"+\"tadam/\"+\"fa\";
33
try {
44
await page.goto(url);
55
} catch(exc) {

tests/api-output/parseGoTo/var-4.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
instructions = [
2-
"""const url = \"http://foo/tadam/fa\";
2+
"""const url = \"http://foo/\"+\"tadam\"+\"/fa\";
33
try {
44
await page.goto(url);
55
} catch(exc) {

tests/ui/assert-expr.goml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ assert: ["1", "2"] != ["1", "2"]
1818
store-value: (var, (1 + 2) * 4 + 1)
1919
assert: |var| == 13
2020
assert: |var| != 12 && 1 < 2
21+
assert: 1 + 2 + "a" == "3a"
22+
assert: 1 + 2 + "a" + (4 * 3) == "3a12"
2123

2224
// Should fail.
2325
assert: |var| != 13

tests/ui/assert-expr.output

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
assert-expr... FAILED
44
[ERROR] `tests/ui/assert-expr.goml` line 15: Condition `!compareArrayLike(["1", "2"], ["1", "2"])` was evaluated as false: for command `assert: ["1", "2"] != ["1", "2"]`
5-
[ERROR] `tests/ui/assert-expr.goml` line 23: Condition `13 != 13` was evaluated as false: for command `assert: |var| != 13`
6-
[ERROR] `tests/ui/assert-expr.goml` line 24: assert didn't fail: for command `assert-false: |var| == 13`
7-
[ERROR] `tests/ui/assert-expr.goml` line 25: Condition `"a" == 13` was evaluated as false: for command `assert: |var2| == |var|`
8-
[ERROR] `tests/ui/assert-expr.goml` line 26: Condition `compareJson({"b": 3}, {"a": 2})` was evaluated as false: for command `assert: {"b": 3} == |var3|`
9-
[ERROR] `tests/ui/assert-expr.goml` line 27: Condition `compareArrayLike([1, "a"], [2, 3])` was evaluated as false: for command `assert: (1, "a") == (2, 3)`
10-
[ERROR] `tests/ui/assert-expr.goml` line 28: Condition `compareArrayLike([1, 2], ["a", "b"])` was evaluated as false: for command `assert: [1, 2] == ["a", "b"]`
5+
[ERROR] `tests/ui/assert-expr.goml` line 25: Condition `13 != 13` was evaluated as false: for command `assert: |var| != 13`
6+
[ERROR] `tests/ui/assert-expr.goml` line 26: assert didn't fail: for command `assert-false: |var| == 13`
7+
[ERROR] `tests/ui/assert-expr.goml` line 27: Condition `"a" == 13` was evaluated as false: for command `assert: |var2| == |var|`
8+
[ERROR] `tests/ui/assert-expr.goml` line 28: Condition `compareJson({"b": 3}, {"a": 2})` was evaluated as false: for command `assert: {"b": 3} == |var3|`
9+
[ERROR] `tests/ui/assert-expr.goml` line 29: Condition `compareArrayLike([1, "a"], [2, 3])` was evaluated as false: for command `assert: (1, "a") == (2, 3)`
10+
[ERROR] `tests/ui/assert-expr.goml` line 30: Condition `compareArrayLike([1, 2], ["a", "b"])` was evaluated as false: for command `assert: [1, 2] == ["a", "b"]`
1111

1212

1313
<= doc-ui tests done: 0 succeeded, 1 failed

0 commit comments

Comments
 (0)