Skip to content

Commit

Permalink
feat: value converter for strings & ints (#655)
Browse files Browse the repository at this point in the history
### 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>
  • Loading branch information
lars-reimann and megalinter-bot authored Oct 20, 2023
1 parent 594c2c8 commit aafa2e3
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 96 deletions.
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

0 comments on commit aafa2e3

Please sign in to comment.