Skip to content

Commit aafa2e3

Browse files
feat: value converter for strings & ints (#655)
### Summary of Changes Add conversion for ints and strings, including proper handling of escape sequences. --------- Co-authored-by: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com>
1 parent 594c2c8 commit aafa2e3

File tree

10 files changed

+275
-96
lines changed

10 files changed

+275
-96
lines changed

docs/language/pipeline-language/expressions.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,18 @@ String literals describe text. Their syntax is simply text enclosed by double qu
2323

2424
| Escape sequence | Meaning |
2525
|-----------------|----------------------------------------------------------------------|
26-
| `#!sds \b` | Backspace |
27-
| `#!sds \t` | Tab |
28-
| `#!sds \n` | New line |
29-
| `#!sds \f` | Form feed |
30-
| `#!sds \r` | Carriage return |
31-
| `#!sds \"` | Double quote |
32-
| `#!sds \'` | Single quote |
33-
| `#!sds \\` | Backslash |
34-
| `#!sds \{` | Opening curly brace (used for [template strings](#template-strings)) |
35-
| `#!sds \uXXXX` | Unicode character, where `#!sds XXXX` is its hexadecimal index |
26+
| `\b` | Backspace |
27+
| `\f` | Form feed |
28+
| `\n` | New line |
29+
| `\r` | Carriage return |
30+
| `\t` | Tab |
31+
| `\v` | Vertical tab |
32+
| `\0` | Null character |
33+
| `\'` | Single quote |
34+
| `\"` | Double quote |
35+
| `\{` | Opening curly brace (used for [template strings](#template-strings)) |
36+
| `\\` | Backslash |
37+
| `\uXXXX` | Unicode character, where `XXXX` is its hexadecimal code |
3638

3739
String literals can contain also contain raw line breaks:
3840

@@ -134,7 +136,7 @@ nullableExpression ?: 42
134136

135137
The syntax for template strings is similar to [string literals](#string-literals): They are also delimited by double quotes, the text can contain escape sequences, and raw newlines can be inserted. The additional syntax are _template expressions_, which are any expression enclosed by `#!sds {{` and `#!sds }}`. There must be no space between the curly braces.
136138

137-
These template expressions are evaluated, converted to a string and inserted into the template string at their position. The template string in the example above is, hence, equivalent to the [string literal](#string-literals) "1 + 2 = 3".
139+
These template expressions are evaluated, converted to a string and inserted into the template string at their position. The template string in the example above is, hence, equivalent to the [string literal](#string-literals) `#!sds "1 + 2 = 3"`.
138140

139141
## References
140142

esbuild.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const ctx = await esbuild.context({
5050
entryPoints: ['src/cli/main.ts', 'src/extension/main.ts', 'src/language/main.ts'],
5151
outdir: 'out',
5252
bundle: true,
53-
target: 'ES2017',
53+
target: 'ES2020',
5454
// VSCode's extension host is still using cjs, so we need to transform the code
5555
format: 'cjs',
5656
// To prevent confusing node, we explicitly use the `.cjs` extension

language-configuration.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
1717
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
1818
{ "open": "»", "close": "«", "notIn": ["string", "comment"] },
19-
{ "open": "/*", "close": " */", "notIn": ["string"] }
19+
{ "open": "/*", "close": " */", "notIn": ["string", "comment"] }
2020
],
2121
"surroundingPairs": [
2222
["(", ")"],
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,77 @@
1-
import { convertString, CstNode, DefaultValueConverter, GrammarAST, ValueType } from 'langium';
1+
import { convertBigint, CstNode, DefaultValueConverter, GrammarAST, ValueType } from 'langium';
22

33
export class SafeDsValueConverter extends DefaultValueConverter {
44
protected override runConverter(rule: GrammarAST.AbstractRule, input: string, cstNode: CstNode): ValueType {
55
switch (rule.name.toUpperCase()) {
66
case 'ID':
77
return input.replaceAll('`', '');
8+
case 'INT':
9+
return convertBigint(input);
10+
case 'STRING':
11+
return convertString(input, 1, 1);
812
case 'TEMPLATE_STRING_START':
9-
return convertString(input.substring(0, input.length - 1));
13+
return convertString(input, 1, 2);
1014
case 'TEMPLATE_STRING_INNER':
11-
return convertString(input.substring(1, input.length - 1));
15+
return convertString(input, 2, 2);
1216
case 'TEMPLATE_STRING_END':
13-
return convertString(input.substring(1));
17+
return convertString(input, 2, 1);
1418
default:
1519
return super.runConverter(rule, input, cstNode);
1620
}
1721
}
1822
}
23+
24+
const convertString = (input: string, openingDelimiterLength: number, closingDelimiterLength: number): string => {
25+
let result = '';
26+
const endIndex = input.length - 1 - closingDelimiterLength;
27+
28+
for (let i = openingDelimiterLength; i <= endIndex; i++) {
29+
const current = input.charAt(i);
30+
if (current === '\\' && i < endIndex) {
31+
const [stringToAdd, newIndex] = handleEscapeSequence(input, i + 1, endIndex);
32+
result += stringToAdd;
33+
i = newIndex - 1; // -1 because the loop will increment it
34+
} else {
35+
result += current;
36+
}
37+
}
38+
39+
return result;
40+
};
41+
42+
/**
43+
* Handle an escape sequence.
44+
*
45+
* @param input The entire input string.
46+
* @param index The index of the escape sequence (after the slash).
47+
* @param endIndex The index of the last character of the input string, excluding delimiters.
48+
* @returns An array containing the string to add to the result and the new index.
49+
*/
50+
const handleEscapeSequence = (input: string, index: number, endIndex: number): [string, number] => {
51+
const current = input.charAt(index);
52+
switch (current) {
53+
case 'b':
54+
return ['\b', index + 1];
55+
case 'f':
56+
return ['\f', index + 1];
57+
case 'n':
58+
return ['\n', index + 1];
59+
case 'r':
60+
return ['\r', index + 1];
61+
case 't':
62+
return ['\t', index + 1];
63+
case 'v':
64+
return ['\v', index + 1];
65+
case '0':
66+
return ['\0', index + 1];
67+
}
68+
69+
if (current === 'u' && index + 4 <= endIndex) {
70+
const code = input.substring(index + 1, index + 5);
71+
if (code.match(/[0-9a-fA-F]{4}/gu)) {
72+
return [String.fromCharCode(parseInt(code, 16)), index + 5];
73+
}
74+
}
75+
76+
return [current, index + 1];
77+
};

src/language/grammar/safe-ds.langium

+2-2
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ SdsFloat returns SdsFloat:
766766
;
767767

768768
interface SdsInt extends SdsNumber {
769-
value: number
769+
value: bigint
770770
}
771771

772772
SdsInt returns SdsInt:
@@ -1069,7 +1069,7 @@ terminal FLOAT returns number
10691069
| DECIMAL_DIGIT+ FLOAT_EXPONENT;
10701070
terminal fragment DECIMAL_DIGIT: /[0-9]/;
10711071
terminal fragment FLOAT_EXPONENT: ('e' | 'E' )('+' | '-' )? DECIMAL_DIGIT+;
1072-
terminal INT returns number: DECIMAL_DIGIT+;
1072+
terminal INT returns bigint: DECIMAL_DIGIT+;
10731073
terminal STRING returns string: STRING_START STRING_TEXT* STRING_END;
10741074
terminal fragment STRING_START: STRING_DELIMITER;
10751075
terminal fragment STRING_END: '{'? STRING_DELIMITER;

src/language/partialEvaluation/safe-ds-partial-evaluator.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class SafeDsPartialEvaluator {
100100
} else if (isSdsFloat(node)) {
101101
return new FloatConstant(node.value);
102102
} else if (isSdsInt(node)) {
103-
return new IntConstant(BigInt(node.value));
103+
return new IntConstant(node.value);
104104
} else if (isSdsNull(node)) {
105105
return NullConstant;
106106
} else if (isSdsString(node)) {
@@ -520,4 +520,4 @@ export class SafeDsPartialEvaluator {
520520
}
521521

522522
const NO_SUBSTITUTIONS: ParameterSubstitutions = new Map();
523-
const zeroes = [new IntConstant(BigInt(0)), new FloatConstant(0.0), new FloatConstant(-0.0)];
523+
const zeroes = [new IntConstant(0n), new FloatConstant(0.0), new FloatConstant(-0.0)];

src/language/validation/other/expressions/infixOperations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => {
1010
const partialEvaluator = services.evaluation.PartialEvaluator;
1111
const typeComputer = services.types.TypeComputer;
1212

13-
const zeroInt = new IntConstant(BigInt(0));
13+
const zeroInt = new IntConstant(0n);
1414
const zeroFloat = new FloatConstant(0.0);
1515
const minusZeroFloat = new FloatConstant(-0.0);
1616

syntaxes/safe-ds.tmLanguage.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
},
9393
"string-character-escape": {
9494
"name": "constant.character.escape.safe-ds",
95-
"match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)"
95+
"match": "\\\\(b|f|n|r|t|v|0|'|\"|{|\\\\|u[0-9a-fA-F]{4})"
9696
}
9797
}
9898
}

0 commit comments

Comments
 (0)