diff --git a/docs/pipeline-language/expressions/literals.md b/docs/pipeline-language/expressions/literals.md index 915f8ae2b..b2e0155d6 100644 --- a/docs/pipeline-language/expressions/literals.md +++ b/docs/pipeline-language/expressions/literals.md @@ -28,7 +28,9 @@ String literals describe text. Their syntax is simply text enclosed by double qu | `\0` | Null character | | `\'` | Single quote | | `\"` | Double quote | +| `` \` `` | Backtick | | `\{` | Opening curly brace (used for [template strings][template-strings]) | +| `\}` | Closing curly brace (used for [template strings][template-strings]) | | `\\` | Backslash | | `\uXXXX` | Unicode character, where `XXXX` is its hexadecimal code | diff --git a/docs/pipeline-language/expressions/template-strings.md b/docs/pipeline-language/expressions/template-strings.md index 17b577937..312742302 100644 --- a/docs/pipeline-language/expressions/template-strings.md +++ b/docs/pipeline-language/expressions/template-strings.md @@ -3,10 +3,10 @@ [String literals][string-literals] can only be used to denote a fixed string. Sometimes, however, parts of the string have to be computed and then interpolated into the remaining text. This is done with template strings. Here is an example: ```sds -"1 + 2 = {{ 1 + 2 }}" +`1 + 2 = { 1 + 2 }` ``` -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. +Template strings are also delimited by backticks, 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 }`. 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"`. diff --git a/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py b/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py index be31bfc58..89092eb0d 100644 --- a/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py +++ b/docs/src/lexer/safe_ds_lexer/_safe_ds_lexer.py @@ -1,4 +1,4 @@ -from pygments.lexer import RegexLexer, words +from pygments.lexer import RegexLexer, include, words from pygments.token import Comment, Keyword, Name, Number, Operator, String, Whitespace keywords_annotation = ("annotation",) @@ -88,7 +88,8 @@ class SafeDsLexer(RegexLexer): "root": [ # Literals (r"\b([0-9]+(\.[0-9]+)?([eE][+-]?[0-9]+)?)\b", Number), - (r'"|}}', String, "string"), + (r'"', String, "string"), + (r"`", String, "template_string"), # Keywords ( words(keywords_annotation, prefix=r"\b", suffix=r"\b"), @@ -121,6 +122,9 @@ class SafeDsLexer(RegexLexer): (r"/\*[\s\S]*?\*/", Comment.Multiline), # Whitespace (r"\s+", Whitespace), + # Block (needed to highlight curly braces in template string expressions) + (r"{", Operator, "block"), + (r"}", Operator, "#pop"), ], "annotation": [ (identifier_regex, Name.Decorator, "#pop"), @@ -138,7 +142,20 @@ class SafeDsLexer(RegexLexer): (identifier_regex, Name.Constant, "#pop"), ], "string": [ - (r'([^"{]|\{(?!\{))+', String), - (r'\{\{|"', String, "#pop"), + (r'(\\"|[^"])+', String), + (r'"', String, "#pop"), + ], + "template_string": [ + (r"(\\{|\\`|[^`{])+", String), + (r"{", String, "template_expression"), + (r"`", String, "#pop"), + ], + "template_expression": [ + # Order matters + (r"}", String, "#pop"), + include("root"), + ], + "block": [ + include("root"), ], } diff --git a/packages/safe-ds-lang/src/language/grammar/safe-ds-token-builder.ts b/packages/safe-ds-lang/src/language/grammar/safe-ds-token-builder.ts new file mode 100644 index 000000000..2d9873721 --- /dev/null +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds-token-builder.ts @@ -0,0 +1,75 @@ +import { DefaultTokenBuilder, GrammarAST, isTokenTypeArray } from 'langium'; +import { TokenType, TokenVocabulary } from 'chevrotain'; + +// Inspired by https://eclipse-langium.github.io/langium-previews/pr-previews/pr-132/guides/multi-mode-lexing/ + +// Lexer modes +const DEFAULT_MODE = 'default'; +const TEMPLATE_STRING_MODE = 'template-string'; + +// Tokens +const BLOCK_START = '{'; +const BLOCK_END = '}'; +const TEMPLATE_STRING_START = 'TEMPLATE_STRING_START'; +const TEMPLATE_STRING_INNER = 'TEMPLATE_STRING_INNER'; +const TEMPLATE_STRING_END = 'TEMPLATE_STRING_END'; + +export class SafeDsTokenBuilder extends DefaultTokenBuilder { + override buildTokens(grammar: GrammarAST.Grammar, options?: { caseInsensitive?: boolean }): TokenVocabulary { + const tokenTypes = super.buildTokens(grammar, options); + + if (isTokenTypeArray(tokenTypes)) { + const defaultModeTokens = tokenTypes.filter( + (token) => ![TEMPLATE_STRING_INNER, TEMPLATE_STRING_END].includes(token.name), + ); + const templateStringModeTokens = tokenTypes.filter((token) => ![BLOCK_END].includes(token.name)); + + return { + modes: { + [DEFAULT_MODE]: defaultModeTokens, + [TEMPLATE_STRING_MODE]: templateStringModeTokens, + }, + defaultMode: DEFAULT_MODE, + }; + } else { + /* c8 ignore next 2 */ + throw new Error('Invalid TokenVocabulary received from DefaultTokenBuilder.'); + } + } + + protected override buildKeywordToken( + keyword: GrammarAST.Keyword, + terminalTokens: TokenType[], + caseInsensitive: boolean, + ): TokenType { + let tokenType = super.buildKeywordToken(keyword, terminalTokens, caseInsensitive); + + if (tokenType.name === BLOCK_START) { + // Enter default mode (for map literals and block lambdas) + tokenType.PUSH_MODE = DEFAULT_MODE; + } else if (tokenType.name === BLOCK_END) { + // Return to previous mode + tokenType.POP_MODE = true; + + // BLOCK_END has TEMPLATE_STRING_INNER and TEMPLATE_STRING_END as longer alternatives, which are not valid + // in the default mode. + delete tokenType.LONGER_ALT; + } + + return tokenType; + } + + protected override buildTerminalToken(terminal: GrammarAST.TerminalRule): TokenType { + let tokenType = super.buildTerminalToken(terminal); + + if (tokenType.name === TEMPLATE_STRING_START) { + // Enter template string mode + tokenType.PUSH_MODE = TEMPLATE_STRING_MODE; + } else if (tokenType.name === TEMPLATE_STRING_END) { + // Return to previous mode + tokenType.POP_MODE = true; + } + + return tokenType; + } +} 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 a9a1e8379..58162774d 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 @@ -9,12 +9,14 @@ export class SafeDsValueConverter extends DefaultValueConverter { return ValueConverter.convertBigint(input); case 'STRING': return convertString(input, 1, 1); + case 'TEMPLATE_STRING_FULL': + return convertTemplateStringPart(input); case 'TEMPLATE_STRING_START': - return convertString(input, 1, 2); + return convertTemplateStringPart(input); case 'TEMPLATE_STRING_INNER': - return convertString(input, 2, 2); + return convertTemplateStringPart(input); case 'TEMPLATE_STRING_END': - return convertString(input, 2, 1); + return convertTemplateStringPart(input); default: return super.runConverter(rule, input, cstNode); } @@ -39,6 +41,10 @@ const convertString = (input: string, openingDelimiterLength: number, closingDel return result; }; +const convertTemplateStringPart = (input: string): string => { + return convertString(input, 1, 1); +}; + /** * Handle an escape sequence. * @@ -85,12 +91,11 @@ const replacements = new Map([ ['\v', '\\v'], ['\0', '\\0'], ['"', '\\"'], - ['{', '\\{'], ['\\', '\\\\'], ]); /** - * Escape a string. + * Escape a string. Not applicable to template strings. */ export const escapeString = (input: string): string => { let result = ''; diff --git a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium index e14d6f3d9..8ac35f201 100644 --- a/packages/safe-ds-lang/src/language/grammar/safe-ds.langium +++ b/packages/safe-ds-lang/src/language/grammar/safe-ds.langium @@ -873,16 +873,27 @@ interface SdsTemplateString extends SdsExpression { } SdsTemplateString returns SdsTemplateString: - expressions+=SdsTemplateStringStart - expressions+=SdsExpression? - (expressions+=SdsTemplateStringInner expressions+=SdsExpression?)* - expressions+=SdsTemplateStringEnd + ( + expressions+=SdsTemplateStringFull + ) | ( + expressions+=SdsTemplateStringStart + expressions+=SdsExpression? + (expressions+=SdsTemplateStringInner expressions+=SdsExpression?)* + expressions+=SdsTemplateStringEnd + ) ; interface SdsTemplateStringPart extends SdsLiteral { value: string } +interface SdsTemplateStringFull extends SdsTemplateStringPart {} + +SdsTemplateStringFull returns SdsExpression: + {SdsTemplateStringFull} + value=TEMPLATE_STRING_FULL +; + interface SdsTemplateStringStart extends SdsTemplateStringPart {} SdsTemplateStringStart returns SdsExpression: @@ -1081,20 +1092,20 @@ terminal FLOAT returns number terminal fragment DECIMAL_DIGIT: /[0-9]/; terminal fragment FLOAT_EXPONENT: ('e' | 'E' )('+' | '-' )? 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; -terminal fragment STRING_DELIMITER: '"'; +terminal STRING returns string: '"' STRING_TEXT* '"'; terminal fragment STRING_TEXT - : '{'? ESCAPE_SEQUENCE - | /{?[^\\"{]/ + : ESCAPE_SEQUENCE + | /[^\\"]/ ; terminal fragment ESCAPE_SEQUENCE: '\\' .; -terminal fragment TEMPLATE_EXPRESSION_START: '{{'; -terminal fragment TEMPLATE_EXPRESSION_END: '}}'; -terminal TEMPLATE_STRING_START returns string: STRING_START STRING_TEXT* TEMPLATE_EXPRESSION_START; -terminal TEMPLATE_STRING_INNER returns string: TEMPLATE_EXPRESSION_END STRING_TEXT* TEMPLATE_EXPRESSION_START; -terminal TEMPLATE_STRING_END returns string: TEMPLATE_EXPRESSION_END STRING_TEXT* STRING_END; +terminal TEMPLATE_STRING_FULL returns string: '`' TEMPLATE_STRING_TEXT* '`'; +terminal TEMPLATE_STRING_START returns string: '`' TEMPLATE_STRING_TEXT* '{'; +terminal TEMPLATE_STRING_INNER returns string: '}' TEMPLATE_STRING_TEXT* '{'; +terminal TEMPLATE_STRING_END returns string: '}' TEMPLATE_STRING_TEXT* '`'; +terminal fragment TEMPLATE_STRING_TEXT + : ESCAPE_SEQUENCE + | /[^\\`{]/ +; hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; diff --git a/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts b/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts index 1ec2f93cd..3752d773b 100644 --- a/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts +++ b/packages/safe-ds-lang/src/language/partialEvaluation/safe-ds-partial-evaluator.ts @@ -30,9 +30,7 @@ import { isSdsSegment, isSdsString, isSdsTemplateString, - isSdsTemplateStringEnd, - isSdsTemplateStringInner, - isSdsTemplateStringStart, + isSdsTemplateStringPart, isSdsThis, isSdsTypeCast, isSdsUnknown, @@ -214,11 +212,7 @@ export class SafeDsPartialEvaluator { return NullConstant; } else if (isSdsString(node)) { return new StringConstant(node.value); - } else if (isSdsTemplateStringStart(node)) { - return new StringConstant(node.value); - } else if (isSdsTemplateStringInner(node)) { - return new StringConstant(node.value); - } else if (isSdsTemplateStringEnd(node)) { + } else if (isSdsTemplateStringPart(node)) { return new StringConstant(node.value); } else if (isSdsThis(node) || isSdsUnknown(node)) { return UnknownEvaluatedNode; diff --git a/packages/safe-ds-lang/src/language/safe-ds-module.ts b/packages/safe-ds-lang/src/language/safe-ds-module.ts index 66b455228..e6fdd4e9c 100644 --- a/packages/safe-ds-lang/src/language/safe-ds-module.ts +++ b/packages/safe-ds-lang/src/language/safe-ds-module.ts @@ -59,6 +59,7 @@ import { SafeDsSyntheticProperties } from './helpers/safe-ds-synthetic-propertie import { SafeDsLinker } from './scoping/safe-ds-linker.js'; import { SafeDsCodeActionProvider } from './codeActions/safe-ds-code-action-provider.js'; import { SafeDsQuickfixProvider } from './codeActions/quickfixes/safe-ds-quickfix-provider.js'; +import { SafeDsTokenBuilder } from './grammar/safe-ds-token-builder.js'; /** * Declaration of custom services - add your own service classes here. @@ -184,6 +185,7 @@ export const SafeDsModule: Module new SafeDsTypeHierarchyProvider(services), }, parser: { + TokenBuilder: () => new SafeDsTokenBuilder(), ValueConverter: () => new SafeDsValueConverter(), }, purity: { diff --git a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts index 77a6feba7..d0f555ee8 100644 --- a/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts +++ b/packages/safe-ds-lang/src/language/typing/safe-ds-type-computer.ts @@ -349,7 +349,8 @@ export class SafeDsTypeComputer { return this.computeType(node.type); } - // Partial evaluation (definitely handles SdsBoolean, SdsFloat, SdsInt, SdsNull, and SdsString) + // Partial evaluation. This definitely handles SdsBoolean, SdsFloat, SdsInt, SdsNull, SdsString, and + // SdsTemplateStringPart. const evaluatedNode = this.partialEvaluator.evaluate(node); if (evaluatedNode instanceof Constant) { return this.factory.createLiteralType(evaluatedNode); 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 558f3d895..18721bf3c 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 @@ -8,6 +8,7 @@ import { isSdsModule, isSdsString, isSdsTemplateStringEnd, + isSdsTemplateStringFull, isSdsTemplateStringInner, isSdsTemplateStringStart, } from '../../../src/language/generated/ast.js'; @@ -144,11 +145,35 @@ describe('runConverter', () => { }); }); + describe('TEMPLATE_STRING_FULL', () => { + it('should remove delimiters', async () => { + const code = ` + pipeline myPipeline { + \`full\`; + } + `; + + const firstTemplateStringStart = await getNodeOfType(services, code, isSdsTemplateStringFull); + expect(firstTemplateStringStart.value).toBe('full'); + }); + + it.each(escapeSequences)('should unescape $escaped', async ({ escaped, unescaped }) => { + const code = ` + pipeline myPipeline { + \`${escaped}\`; + } + `; + + const firstTemplateStringStart = await getNodeOfType(services, code, isSdsTemplateStringFull); + expect(firstTemplateStringStart.value).toBe(unescaped); + }); + }); + describe('TEMPLATE_STRING_START', () => { it('should remove delimiters', async () => { const code = ` pipeline myPipeline { - "start{{ 1 }}inner{{ 2 }}end"; + \`start{ 1 }inner{ 2 }end\`; } `; @@ -159,7 +184,7 @@ describe('runConverter', () => { it.each(escapeSequences)('should unescape $escaped', async ({ escaped, unescaped }) => { const code = ` pipeline myPipeline { - "${escaped}{{ 1 }}inner{{ 2 }}end"; + \`${escaped}{ 1 }inner{ 2 }end\`; } `; @@ -172,7 +197,7 @@ describe('runConverter', () => { it('should remove delimiters', async () => { const code = ` pipeline myPipeline { - "start{{ 1 }}inner{{ 2 }}end"; + \`start{ 1 }inner{ 2 }end\`; } `; @@ -183,7 +208,7 @@ describe('runConverter', () => { it.each(escapeSequences)('should unescape $escaped', async ({ escaped, unescaped }) => { const code = ` pipeline myPipeline { - "start{{ 1 }}${escaped}{{ 2 }}end"; + \`start{ 1 }${escaped}{ 2 }end\`; } `; @@ -196,7 +221,7 @@ describe('runConverter', () => { it('should remove delimiters', async () => { const code = ` pipeline myPipeline { - "start{{ 1 }}inner{{ 2 }}end"; + \`start{ 1 }inner{ 2 }end\`; } `; @@ -207,7 +232,7 @@ describe('runConverter', () => { it.each(escapeSequences)('should unescape $escaped', async ({ escaped, unescaped }) => { const code = ` pipeline myPipeline { - "start{{ 1 }}inner{{ 2 }}${escaped}"; + \`start{ 1 }inner{ 2 }${escaped}\`; } `; @@ -227,7 +252,6 @@ describe('escapeString', () => { { unescaped: '\v', escaped: '\\v' }, { unescaped: '\0', escaped: '\\0' }, { unescaped: '"', escaped: '\\"' }, - { unescaped: '{', escaped: '\\{' }, { unescaped: '\\', escaped: '\\\\' }, ]; diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template expression.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template expression.sdsdev index ca55879dd..eec75953f 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template expression.sdsdev @@ -1,9 +1,9 @@ pipeline myPipeline { - "{{ }}"; + `{ }`; } // ----------------------------------------------------------------------------- pipeline myPipeline { - "{{ }}"; + `{ }`; } diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/closing template expression delimiter.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template string.sdsdev similarity index 86% rename from packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/closing template expression delimiter.sdsdev rename to packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template string.sdsdev index fa870d84b..36ab0c4cf 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/closing template expression delimiter.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/empty template string.sdsdev @@ -1,9 +1,9 @@ pipeline myPipeline { - "}}"; + ``; } // ----------------------------------------------------------------------------- pipeline myPipeline { - "}}"; + ``; } diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with two expressions.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with all parts.sdsdev similarity index 55% rename from packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with two expressions.sdsdev rename to packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with all parts.sdsdev index 36fba2cf1..fb80e439a 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with two expressions.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with all parts.sdsdev @@ -1,9 +1,10 @@ pipeline myPipeline { - "{{ 1 }} {{ 2 }}"; + `start { 1 } inner1 { 2 } inner2 { 3 } end`; } + // ----------------------------------------------------------------------------- pipeline myPipeline { - "{{ 1 }} {{ 2 }}"; + `start { 1 } inner1 { 2 } inner2 { 3 } end`; } diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with basic expression.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with basic expression.sdsdev index d8f58815a..b0d8eecf7 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with basic expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string with basic expression.sdsdev @@ -1,9 +1,9 @@ pipeline myPipeline { - "{{ 1 }}"; + `{ 1 }`; } // ----------------------------------------------------------------------------- pipeline myPipeline { - "{{ 1 }}"; + `{ 1 }`; } diff --git a/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/curly braces separated by space.sdsdev b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string without expressions.sdsdev similarity index 83% rename from packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/curly braces separated by space.sdsdev rename to packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string without expressions.sdsdev index ee0f4f4b8..36ec7b4e0 100644 --- a/packages/safe-ds-lang/tests/resources/formatting/expressions/literals/string/curly braces separated by space.sdsdev +++ b/packages/safe-ds-lang/tests/resources/formatting/expressions/template strings/template string without expressions.sdsdev @@ -1,9 +1,9 @@ pipeline myPipeline { - "{ {"; + `hello`; } // ----------------------------------------------------------------------------- pipeline myPipeline { - "{ {"; + `hello`; } diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/constant/input.sdsdev b/packages/safe-ds-lang/tests/resources/generation/python/expressions/constant/input.sdsdev index 14c636007..5ea4805d2 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/constant/input.sdsdev +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/constant/input.sdsdev @@ -7,5 +7,5 @@ pipeline test { f(1.0 - 1.0); f(1 + 1); f(null); - f("person: {{ "me" }}"); + f(`person: { "me" }`); } diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/side effects/input.sdsdev b/packages/safe-ds-lang/tests/resources/generation/python/expressions/side effects/input.sdsdev index df48228aa..1af5391be 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/side effects/input.sdsdev +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/side effects/input.sdsdev @@ -12,5 +12,5 @@ pipeline test { mySegment(1.0 - 1.0); mySegment(1 + 1); mySegment(null); - mySegment("person: {{ "me" }}"); + mySegment(`person: { "me" }`); } diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/generated/tests/generator/templateString/gen_input.py.map b/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/generated/tests/generator/templateString/gen_input.py.map index f53e59969..2b4dc1ac0 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/generated/tests/generator/templateString/gen_input.py.map +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/generated/tests/generator/templateString/gen_input.py.map @@ -1 +1 @@ -{"version":3,"sources":["input.sdsdev"],"names":["test","f","g"],"mappings":"AAAA;;;;;;AAMA,IAASA,IAAI;IACTC,CAAC,CAAC,EAAA,SACHC,CAAC,EAAG,YACEA,CAAC,EAAG","file":"gen_input.py"} \ No newline at end of file +{"version":3,"sources":["input.sdsdev"],"names":["test","f","g"],"mappings":"AAAA;;;;;;AAMA,IAASA,IAAI;IACTC,CAAC,CAAC,EAAA,SACJC,CAAC,EAAG,YACEA,CAAC,EAAG","file":"gen_input.py"} \ No newline at end of file diff --git a/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/input.sdsdev b/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/input.sdsdev index 2e8645428..8e4c55575 100644 --- a/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/input.sdsdev +++ b/packages/safe-ds-lang/tests/resources/generation/python/expressions/template string/input.sdsdev @@ -5,8 +5,8 @@ package tests.generator.templateString @Pure fun g() -> result: Int pipeline test { - f("start -{{ g() }} -inner {{ g() }} -end"); + f(`start +{ g() } +inner { g() } +end`); } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-escaped curly brace.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-escaped curly brace.sdsdev index cd8f0e481..1130a1f05 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-escaped curly brace.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-escaped curly brace.sdsdev @@ -1,5 +1,5 @@ // $TEST$ no_syntax_error pipeline myPipeline { - "\{{"; + "\{"; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdsdev index a34d0a562..5dddd3d99 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-template string with invalid expression.sdsdev @@ -1,5 +1,5 @@ // $TEST$ syntax_error pipeline myPipeline { - "{{ ??? }}"; + `{ ??? }`; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdsdev index 6c4e7f084..15d23dc5d 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/bad-unclosed template expression.sdsdev @@ -1,5 +1,5 @@ // $TEST$ syntax_error pipeline myPipeline { - "{{"; + `{`; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template expression.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template expression.sdsdev index 064cfd448..5e739191f 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template expression.sdsdev @@ -1,5 +1,5 @@ // $TEST$ no_syntax_error pipeline myPipeline { - "{{ }}"; + `{ }`; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-closing template expression delimiter.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template string.sdsdev similarity index 83% rename from packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-closing template expression delimiter.sdsdev rename to packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template string.sdsdev index 7f7c2dcae..59064b7ef 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-closing template expression delimiter.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-empty template string.sdsdev @@ -1,5 +1,5 @@ // $TEST$ no_syntax_error pipeline myPipeline { - "}}"; + ``; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with all parts.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with all parts.sdsdev new file mode 100644 index 000000000..a9891109f --- /dev/null +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with all parts.sdsdev @@ -0,0 +1,5 @@ +// $TEST$ no_syntax_error + +pipeline myPipeline { + `start { 1 } inner1 { 2 } inner2 { 3 } end`; +} diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdsdev index fc8d3666a..5c212cc9c 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string with basic expression.sdsdev @@ -1,5 +1,5 @@ // $TEST$ no_syntax_error pipeline myPipeline { - "{{ 1 }}"; + `{ 1 }`; } diff --git a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-curly braces separated by space.sdsdev b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string without expressions.sdsdev similarity index 79% rename from packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-curly braces separated by space.sdsdev rename to packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string without expressions.sdsdev index f12997d16..5c3526506 100644 --- a/packages/safe-ds-lang/tests/resources/grammar/expressions/literals/string/good-curly braces separated by space.sdsdev +++ b/packages/safe-ds-lang/tests/resources/grammar/expressions/template strings/good-template string without expressions.sdsdev @@ -1,5 +1,5 @@ // $TEST$ no_syntax_error pipeline myPipeline { - "{ {"; + `hello`; } diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdsdev b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals/main.sdsdev similarity index 62% rename from packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdsdev rename to packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals/main.sdsdev index cfa452d88..3055e7de6 100644 --- a/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals (without interpolation)/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/base cases/string literals/main.sdsdev @@ -1,4 +1,4 @@ -package tests.partialValidation.baseCases.stringLiteralsWithoutInterpolation +package tests.partialValidation.baseCases.stringLiterals pipeline test { // $TEST$ serialization "test" diff --git a/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdsdev b/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdsdev index df5ab0809..25d76501b 100644 --- a/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/partial evaluation/recursive cases/template strings/main.sdsdev @@ -1,12 +1,18 @@ package tests.partialValidation.recursiveCases.templateStrings pipeline test { + // $TEST$ serialization "" + »``«; + + // $TEST$ serialization "full" + »`full`«; + // $TEST$ serialization "start 1 inner1 true inner2 test end" - »"start {{ 1 }} inner1 {{ true }} inner2 {{ "test" }} end"«; + »`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"«; + »`start\t{ 1 } inner1\t{ true } inner2\t{ "test" } end\t`«; // $TEST$ serialization ? - »"start {{ call() }} end"«; + »`start { call() } end`«; } diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev index 0d5e041e8..760f59e2f 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in annotation/main.sdsdev @@ -8,23 +8,23 @@ annotation MyAnnotation( ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ unresolved // $TEST$ unresolved - »before« < 0 else "{{ »before« }}", + »before« < 0 else `{ »before« }`, // $TEST$ unresolved // $TEST$ unresolved - »after« < 0 else "{{ »after« }}", + »after« < 0 else `{ »after« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } fun myFunction2(after: Int) diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev index b9cdcdbb5..d0e030097 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in enum variant in nested enum/main.sdsdev @@ -10,27 +10,27 @@ class MyClass(container: Int) { ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ unresolved // $TEST$ unresolved - »container« < 0 else "{{ »container« }}", + »container« < 0 else `{ »container« }`, // $TEST$ unresolved // $TEST$ unresolved - »beforeEnum« < 0 else "{{ »beforeEnum« }}", + »beforeEnum« < 0 else `{ »beforeEnum« }`, // $TEST$ unresolved // $TEST$ unresolved - »afterEnum« < 0 else "{{ »afterEnum« }}", + »afterEnum« < 0 else `{ »afterEnum« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } } diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev index ccdceab67..edcb4fb16 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global class/main.sdsdev @@ -8,23 +8,23 @@ class MyClass( ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ unresolved // $TEST$ unresolved - »before« < 0 else "{{ »before« }}", + »before« < 0 else `{ »before« }`, // $TEST$ unresolved // $TEST$ unresolved - »after« < 0 else "{{ »after« }}", + »after« < 0 else `{ »after« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } fun myFunction2(after: Int) diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev index 5f000feba..f3a05e651 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in global function/main.sdsdev @@ -8,23 +8,23 @@ fun myFunction2( ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ unresolved // $TEST$ unresolved - »before« < 0 else "{{ »before« }}", + »before« < 0 else `{ »before« }`, // $TEST$ unresolved // $TEST$ unresolved - »after« < 0 else "{{ »after« }}", + »after« < 0 else `{ »after« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } fun myFunction3(after: Int) diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev index 198006d39..0ab0c8cd8 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in method/main.sdsdev @@ -14,39 +14,39 @@ class MyClass1(container: Int, overridden: Int) { ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ references overridden // $TEST$ references overridden - »overridden« < 0 else "{{ »overridden« }}", + »overridden« < 0 else `{ »overridden« }`, // $TEST$ unresolved // $TEST$ unresolved - »container« < 0 else "{{ »container« }}", + »container« < 0 else `{ »container« }`, // $TEST$ unresolved // $TEST$ unresolved - »beforeMember« < 0 else "{{ »beforeMember« }}", + »beforeMember« < 0 else `{ »beforeMember« }`, // $TEST$ unresolved // $TEST$ unresolved - »afterMember« < 0 else "{{ »afterMember« }}", + »afterMember« < 0 else `{ »afterMember« }`, // $TEST$ unresolved // $TEST$ unresolved - »beforeGlobal« < 0 else "{{ »beforeGlobal« }}", + »beforeGlobal« < 0 else `{ »beforeGlobal« }`, // $TEST$ unresolved // $TEST$ unresolved - »afterGlobal« < 0 else "{{ »afterGlobal« }}", + »afterGlobal« < 0 else `{ »afterGlobal« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } fun myFunction5(afterMember: Int) diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev index 73c8a0484..a53704282 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in nested class/main.sdsdev @@ -14,39 +14,39 @@ class MyClass1(container: Int, overridden: Int){ ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ references overridden // $TEST$ references overridden - »overridden« < 0 else "{{ »overridden« }}", + »overridden« < 0 else `{ »overridden« }`, // $TEST$ unresolved // $TEST$ unresolved - »container« < 0 else "{{ »container« }}", + »container« < 0 else `{ »container« }`, // $TEST$ unresolved // $TEST$ unresolved - »beforeMember« < 0 else "{{ »beforeMember« }}", + »beforeMember« < 0 else `{ »beforeMember« }`, // $TEST$ unresolved // $TEST$ unresolved - »afterMember« < 0 else "{{ »afterMember« }}", + »afterMember« < 0 else `{ »afterMember« }`, // $TEST$ unresolved // $TEST$ unresolved - »beforeGlobal« < 0 else "{{ »beforeGlobal« }}", + »beforeGlobal« < 0 else `{ »beforeGlobal« }`, // $TEST$ unresolved // $TEST$ unresolved - »afterGlobal« < 0 else "{{ »afterGlobal« }}", + »afterGlobal« < 0 else `{ »afterGlobal« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } fun myFunction3(afterMember: Int) diff --git a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev index e5e7dccc7..248090013 100644 --- a/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/scoping/parameter bounds/in segment/main.sdsdev @@ -8,23 +8,23 @@ segment mySegment2( ) where { // $TEST$ references own // $TEST$ references own - »own« < 0 else "{{ »own« }}", + »own« < 0 else `{ »own« }`, // $TEST$ unresolved // $TEST$ unresolved - »before« < 0 else "{{ »before« }}", + »before« < 0 else `{ »before« }`, // $TEST$ unresolved // $TEST$ unresolved - »after« < 0 else "{{ »after« }}", + »after« < 0 else `{ »after« }`, // $TEST$ unresolved // $TEST$ references notAParameter - »notAParameter« < 0 else "{{ »notAParameter« }}", + »notAParameter« < 0 else `{ »notAParameter« }`, // $TEST$ unresolved // $TEST$ unresolved - »unresolved« < 0 else "{{ »unresolved« }}" + »unresolved« < 0 else `{ »unresolved« }` } {} segment mySegment3(after: Int) {} diff --git a/packages/safe-ds-lang/tests/resources/typing/expressions/template strings/main.sdsdev b/packages/safe-ds-lang/tests/resources/typing/expressions/template strings/main.sdsdev index bf878f09f..cb7821112 100644 --- a/packages/safe-ds-lang/tests/resources/typing/expressions/template strings/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/typing/expressions/template strings/main.sdsdev @@ -2,8 +2,8 @@ package tests.typing.expressions.templateStrings pipeline myPipeline { // $TEST$ serialization literal<"1 + 2 = 3"> - val valid = »"1 + 2 = {{ 1 + 2 }}"«; + val valid = »`1 + 2 = { 1 + 2 }`«; // $TEST$ serialization String - val invalid = »"1 + 2 = {{ unresolved }}"«; + val invalid = »`1 + 2 = { unresolved }`«; } diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev index 6366ccc1c..0242723e3 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/constraints/message must only reference constant parameters/template strings.sdsdev @@ -5,13 +5,13 @@ class MyClass2( nonConstant: Int, ) where { // $TEST$ no error "The message of a constraint must only reference constant parameters." - constant < 0 else "{{ »constant« }}", + constant < 0 else `{ »constant« }`, // $TEST$ error "The message of a constraint must only reference constant parameters." - constant < 1 else "{{ »nonConstant« }}", + constant < 1 else `{ »nonConstant« }`, // $TEST$ error "The message of a constraint must only reference constant parameters." - constant < 2 else "{{ »NotAParameter()« }}", + constant < 2 else `{ »NotAParameter()« }`, // $TEST$ error "The message of a constraint must only reference constant parameters." - constant < 3 else "{{ »NotAParameter.a« }}", + constant < 3 else `{ »NotAParameter.a« }`, } class NotAParameter() { diff --git a/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev index 9efe21b61..b1326931a 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/other/declarations/parameter bounds/arguments must match parameter bounds/custom message.sdsdev @@ -5,7 +5,7 @@ package tests.validation.other.declarations.parameterBounds.argumentsMustMatchPa const p2: Int = -2, ) where { p1 >= 0 else "This parameter must be non-negative.", - p1 > p2 else "p1 must be greater than p2, but p1 was {{ p1 }} and p2 was {{ p2 }}.", + p1 > p2 else `p1 must be greater than p2, but p1 was { p1 } and p2 was { p2 }.`, } @Pure fun f7( @@ -17,8 +17,8 @@ package tests.validation.other.declarations.parameterBounds.argumentsMustMatchPa const p3: Int = »-3«, ) where { p1 >= 0 else "This parameter must be non-negative.", - p2 >= 0 else "This parameter must be non-negative, but was {{ p2 }}.", - p3 >= 0 else "This parameter must be non-negative, but was {{ p3 }}. p2 was {{ p2 }} by the way.", + p2 >= 0 else `This parameter must be non-negative, but was { p2 }.`, + p3 >= 0 else `This parameter must be non-negative, but was { p3 }. p2 was { p2 } by the way.`, } segment mySegment(p: Int) { diff --git a/packages/safe-ds-lang/tests/resources/validation/other/expressions/template strings/missing template expression/main.sdsdev b/packages/safe-ds-lang/tests/resources/validation/other/expressions/template strings/missing template expression/main.sdsdev index 4e394d564..2df950a42 100644 --- a/packages/safe-ds-lang/tests/resources/validation/other/expressions/template strings/missing template expression/main.sdsdev +++ b/packages/safe-ds-lang/tests/resources/validation/other/expressions/template strings/missing template expression/main.sdsdev @@ -3,9 +3,9 @@ package tests.validation.other.expressions.templateStrings.missingTemplateExpres pipeline test { // $TEST$ error "There must be an expression between two string parts." // $TEST$ error "There must be an expression between two string parts." - "start {{ »}} inner {{« »}} end"«; + `start { »} inner {« »} end`«; // $TEST$ no error "There must be an expression between two string parts." // $TEST$ no error "There must be an expression between two string parts." - "start {{ 1 »}} inner {{« 1 »}} end"«; + `start { 1 »} inner {« 1 »} end`«; } diff --git a/packages/safe-ds-vscode/language-configuration.json b/packages/safe-ds-vscode/language-configuration.json index 9f958dac6..350fc8ad1 100644 --- a/packages/safe-ds-vscode/language-configuration.json +++ b/packages/safe-ds-vscode/language-configuration.json @@ -14,6 +14,7 @@ { "open": "{", "close": "}", "notIn": ["string", "comment"] }, { "open": "[", "close": "]", "notIn": ["string", "comment"] }, { "open": "\"", "close": "\"", "notIn": ["string", "comment"] }, + { "open": "`", "close": "`", "notIn": ["string", "comment"] }, { "open": "»", "close": "«", "notIn": ["string", "comment"] }, { "open": "/*", "close": " */", "notIn": ["string", "comment"] } ], @@ -23,6 +24,7 @@ ["[", "]"], ["<", ">"], ["\"", "\""], + ["`", "`"], ["»", "«"] ] } diff --git a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json index dee90d9b3..8a6c9ac89 100644 --- a/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json +++ b/packages/safe-ds-vscode/syntaxes/safe-ds.tmLanguage.json @@ -36,14 +36,15 @@ "match": "\\b(as|else|from|import|literal|union|where|yield)\\b" }, { + "comment": "Using a keyword as an identifier.", "name": "meta.safe-ds", "begin": "\\^", "end": "\\b" }, { - "name": "string.interpolated.safe-ds", - "begin": "\"|}}", - "end": "{{|\"", + "name": "string.quoted.double.safe-ds", + "begin": "\"", + "end": "\"", "patterns": [ { "include": "#string-character-escape" @@ -51,20 +52,42 @@ ] }, { - "name": "string.quoted.double.safe-ds", - "begin": "\"", - "end": "\"", + "name": "string.interpolated.safe-ds", + "begin": "`", + "end": "`", "patterns": [ { "include": "#string-character-escape" + }, + { + "include": "#block" } ] }, + { + "include": "#block" + }, { "include": "#comments" } ] }, + "block": { + "patterns": [ + { + "comment": "Go back to regular mode from string interpolation. Including it in the string interpolation rule only matches up to the first closing curly brace, which fails for nested map literals or block lambdas.", + "name": "meta.embedded.safe-ds", + "contentName": "source.safe-ds", + "begin": "\\{", + "end": "\\}", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, "comments": { "patterns": [ { @@ -118,7 +141,7 @@ }, "string-character-escape": { "name": "constant.character.escape.safe-ds", - "match": "\\\\(b|f|n|r|t|v|0|'|\"|{|\\\\|u[0-9a-fA-F]{4})" + "match": "\\\\(b|f|n|r|t|v|0|'|\"|`|\\{|\\}|\\\\|u[0-9a-fA-F]{4})" } } }