From 6aa0aadf143171a049b9198110414b2974645dcd Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Thu, 22 Feb 2024 12:08:54 +0100 Subject: [PATCH] feat: escape characters in string conversion of string constants --- .../grammar/safe-ds-value-converter.ts | 28 +++++++++++++++++++ .../src/language/partialEvaluation/model.ts | 5 ++-- .../grammar/safe-ds-value-converter.test.ts | 20 +++++++++++++ .../main.sdstest | 2 +- .../template strings/main.sdstest | 2 +- 5 files changed, 53 insertions(+), 4 deletions(-) diff --git a/packages/safe-ds-lang/src/language/grammar/safe-ds-value-converter.ts b/packages/safe-ds-lang/src/language/grammar/safe-ds-value-converter.ts index 01f4ce423..a2a5dd6be 100644 --- a/packages/safe-ds-lang/src/language/grammar/safe-ds-value-converter.ts +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds-value-converter.ts @@ -75,3 +75,31 @@ const handleEscapeSequence = (input: string, index: number, endIndex: number): [ return [current, index + 1]; }; + +const replacements = new Map([ + ['\b', '\\b'], + ['\f', '\\f'], + ['\n', '\\n'], + ['\r', '\\r'], + ['\t', '\\t'], + ['\v', '\\v'], + ['\0', '\\0'], + ['"', '\\"'], + ['{', '\\{'], + ['\\', '\\\\'], +]); + +/** + * Escape a string. + */ +export const escapeString = (input: string): string => { + let result = ''; + + for (let i = 0; i < input.length; i++) { + const current = input.charAt(i); + const replacement = replacements.get(current); + result += replacement ? replacement : current; + } + + return result; +}; diff --git a/packages/safe-ds-lang/src/language/partialEvaluation/model.ts b/packages/safe-ds-lang/src/language/partialEvaluation/model.ts index 90fb8da99..f01bd85b8 100644 --- a/packages/safe-ds-lang/src/language/partialEvaluation/model.ts +++ b/packages/safe-ds-lang/src/language/partialEvaluation/model.ts @@ -11,6 +11,7 @@ import { type SdsParameter, } from '../generated/ast.js'; import { getParameters, streamBlockLambdaResults } from '../helpers/nodeProperties.js'; +import { escapeString } from '../grammar/safe-ds-value-converter.js'; export type ParameterSubstitutions = Map; export type ResultSubstitutions = Map; @@ -131,11 +132,11 @@ export class StringConstant extends Constant { } override toString(): string { - return `"${this.value}"`; + return `"${escapeString(this.value)}"`; } override toInterpolationString(): string { - return this.value; + return escapeString(this.value); } } diff --git a/packages/safe-ds-lang/tests/language/grammar/safe-ds-value-converter.test.ts b/packages/safe-ds-lang/tests/language/grammar/safe-ds-value-converter.test.ts index a6f2de938..53c1f1e41 100644 --- a/packages/safe-ds-lang/tests/language/grammar/safe-ds-value-converter.test.ts +++ b/packages/safe-ds-lang/tests/language/grammar/safe-ds-value-converter.test.ts @@ -11,6 +11,7 @@ import { isSdsTemplateStringInner, isSdsTemplateStringStart, } from '../../../src/language/generated/ast.js'; +import { escapeString } from '../../../src/language/grammar/safe-ds-value-converter.js'; const services = createSafeDsServices(EmptyFileSystem).SafeDs; @@ -215,3 +216,22 @@ describe('runConverter', () => { }); }); }); + +describe('escapeString', () => { + const tests = [ + { unescaped: '\b', escaped: '\\b' }, + { unescaped: '\f', escaped: '\\f' }, + { unescaped: '\n', escaped: '\\n' }, + { unescaped: '\r', escaped: '\\r' }, + { unescaped: '\t', escaped: '\\t' }, + { unescaped: '\v', escaped: '\\v' }, + { unescaped: '\0', escaped: '\\0' }, + { unescaped: '"', escaped: '\\"' }, + { unescaped: '{', escaped: '\\{' }, + { unescaped: '\\', escaped: '\\\\' }, + ]; + + it.each(tests)('should escape $unescaped', ({ escaped, unescaped }) => { + expect(escapeString(unescaped)).toBe(escaped); + }); +}); diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest index d01e24d4e..cfa452d88 100644 --- a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdstest @@ -4,6 +4,6 @@ pipeline test { // $TEST$ serialization "test" »"test"«; - // $TEST$ serialization "test " + // $TEST$ serialization "test\t" »"test\t"«; } diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest b/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest index c5797e0af..df5ab0809 100644 --- a/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdstest @@ -4,7 +4,7 @@ pipeline test { // $TEST$ serialization "start 1 inner1 true inner2 test end" »"start {{ 1 }} inner1 {{ true }} inner2 {{ "test" }} end"«; - // $TEST$ serialization "start 1 inner1 true inner2 test end " + // $TEST$ serialization "start\\t1 inner1\\ttrue inner2\\ttest end\\t" »"start\t{{ 1 }} inner1\t{{ true }} inner2\t{{ "test" }} end\t"«; // $TEST$ serialization ?