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

feat: value converter for strings & ints #655

Merged
merged 9 commits into from
Oct 20, 2023
24 changes: 13 additions & 11 deletions docs/language/pipeline-language/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,18 @@ String literals describe text. Their syntax is simply text enclosed by double qu

| Escape sequence | Meaning |
|-----------------|----------------------------------------------------------------------|
| `#!sds \b` | Backspace |
| `#!sds \t` | Tab |
| `#!sds \n` | New line |
| `#!sds \f` | Form feed |
| `#!sds \r` | Carriage return |
| `#!sds \"` | Double quote |
| `#!sds \'` | Single quote |
| `#!sds \\` | Backslash |
| `#!sds \{` | Opening curly brace (used for [template strings](#template-strings)) |
| `#!sds \uXXXX` | Unicode character, where `#!sds XXXX` is its hexadecimal index |
| `\b` | Backspace |
| `\f` | Form feed |
| `\n` | New line |
| `\r` | Carriage return |
| `\t` | Tab |
| `\v` | Vertical tab |
| `\0` | Null character |
| `\'` | Single quote |
| `\"` | Double quote |
| `\{` | Opening curly brace (used for [template strings](#template-strings)) |
| `\\` | Backslash |
| `\uXXXX` | Unicode character, where `XXXX` is its hexadecimal code |

String literals can contain also contain raw line breaks:

Expand Down Expand Up @@ -134,7 +136,7 @@ nullableExpression ?: 42

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.

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".
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"`.

## References

Expand Down
2 changes: 1 addition & 1 deletion esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const ctx = await esbuild.context({
entryPoints: ['src/cli/main.ts', 'src/extension/main.ts', 'src/language/main.ts'],
outdir: 'out',
bundle: true,
target: 'ES2017',
target: 'ES2020',
// VSCode's extension host is still using cjs, so we need to transform the code
format: 'cjs',
// To prevent confusing node, we explicitly use the `.cjs` extension
Expand Down
2 changes: 1 addition & 1 deletion language-configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
{ "open": "`", "close": "`", "notIn": ["string", "comment"] },
{ "open": "»", "close": "«", "notIn": ["string", "comment"] },
{ "open": "/*", "close": " */", "notIn": ["string"] }
{ "open": "/*", "close": " */", "notIn": ["string", "comment"] }
],
"surroundingPairs": [
["(", ")"],
Expand Down
67 changes: 63 additions & 4 deletions src/language/grammar/safe-ds-value-converter.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,77 @@
import { convertString, CstNode, DefaultValueConverter, GrammarAST, ValueType } from 'langium';
import { convertBigint, CstNode, DefaultValueConverter, GrammarAST, ValueType } from 'langium';

export class SafeDsValueConverter extends DefaultValueConverter {
protected override runConverter(rule: GrammarAST.AbstractRule, input: string, cstNode: CstNode): ValueType {
switch (rule.name.toUpperCase()) {
case 'ID':
return input.replaceAll('`', '');
case 'INT':
return convertBigint(input);
case 'STRING':
return convertString(input, 1, 1);
case 'TEMPLATE_STRING_START':
return convertString(input.substring(0, input.length - 1));
return convertString(input, 1, 2);
case 'TEMPLATE_STRING_INNER':
return convertString(input.substring(1, input.length - 1));
return convertString(input, 2, 2);
case 'TEMPLATE_STRING_END':
return convertString(input.substring(1));
return convertString(input, 2, 1);
default:
return super.runConverter(rule, input, cstNode);
}
}
}

const convertString = (input: string, openingDelimiterLength: number, closingDelimiterLength: number): string => {
let result = '';
const endIndex = input.length - 1 - closingDelimiterLength;

for (let i = openingDelimiterLength; i <= endIndex; i++) {
const current = input.charAt(i);
if (current === '\\' && i < endIndex) {
const [stringToAdd, newIndex] = handleEscapeSequence(input, i + 1, endIndex);
result += stringToAdd;
i = newIndex - 1; // -1 because the loop will increment it
} else {
result += current;
}
}

return result;
};

/**
* Handle an escape sequence.
*
* @param input The entire input string.
* @param index The index of the escape sequence (after the slash).
* @param endIndex The index of the last character of the input string, excluding delimiters.
* @returns An array containing the string to add to the result and the new index.
*/
const handleEscapeSequence = (input: string, index: number, endIndex: number): [string, number] => {
const current = input.charAt(index);
switch (current) {
case 'b':
return ['\b', index + 1];
case 'f':
return ['\f', index + 1];
case 'n':
return ['\n', index + 1];
case 'r':
return ['\r', index + 1];
case 't':
return ['\t', index + 1];
case 'v':
return ['\v', index + 1];
case '0':
return ['\0', index + 1];
}

if (current === 'u' && index + 4 <= endIndex) {
const code = input.substring(index + 1, index + 5);
if (code.match(/[0-9a-fA-F]{4}/gu)) {
return [String.fromCharCode(parseInt(code, 16)), index + 5];
}
}

return [current, index + 1];
};
4 changes: 2 additions & 2 deletions src/language/grammar/safe-ds.langium
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ SdsFloat returns SdsFloat:
;

interface SdsInt extends SdsNumber {
value: number
value: bigint
}

SdsInt returns SdsInt:
Expand Down Expand Up @@ -1069,7 +1069,7 @@ terminal FLOAT returns number
| DECIMAL_DIGIT+ FLOAT_EXPONENT;
terminal fragment DECIMAL_DIGIT: /[0-9]/;
terminal fragment FLOAT_EXPONENT: ('e' | 'E' )('+' | '-' )? DECIMAL_DIGIT+;
terminal INT returns number: DECIMAL_DIGIT+;
terminal INT returns bigint: DECIMAL_DIGIT+;
terminal STRING returns string: STRING_START STRING_TEXT* STRING_END;
terminal fragment STRING_START: STRING_DELIMITER;
terminal fragment STRING_END: '{'? STRING_DELIMITER;
Expand Down
4 changes: 2 additions & 2 deletions src/language/partialEvaluation/safe-ds-partial-evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class SafeDsPartialEvaluator {
} else if (isSdsFloat(node)) {
return new FloatConstant(node.value);
} else if (isSdsInt(node)) {
return new IntConstant(BigInt(node.value));
return new IntConstant(node.value);
} else if (isSdsNull(node)) {
return NullConstant;
} else if (isSdsString(node)) {
Expand Down Expand Up @@ -520,4 +520,4 @@ export class SafeDsPartialEvaluator {
}

const NO_SUBSTITUTIONS: ParameterSubstitutions = new Map();
const zeroes = [new IntConstant(BigInt(0)), new FloatConstant(0.0), new FloatConstant(-0.0)];
const zeroes = [new IntConstant(0n), new FloatConstant(0.0), new FloatConstant(-0.0)];
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const divisionDivisorMustNotBeZero = (services: SafeDsServices) => {
const partialEvaluator = services.evaluation.PartialEvaluator;
const typeComputer = services.types.TypeComputer;

const zeroInt = new IntConstant(BigInt(0));
const zeroInt = new IntConstant(0n);
const zeroFloat = new FloatConstant(0.0);
const minusZeroFloat = new FloatConstant(-0.0);

Expand Down
2 changes: 1 addition & 1 deletion syntaxes/safe-ds.tmLanguage.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
},
"string-character-escape": {
"name": "constant.character.escape.safe-ds",
"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]?|.|$)"
"match": "\\\\(b|f|n|r|t|v|0|'|\"|{|\\\\|u[0-9a-fA-F]{4})"
}
}
}
Loading