🐐 String interpolation

This commit is contained in:
2026-04-21 16:29:40 +02:00
parent 0b8b7c086e
commit 264ea84dfc
7 changed files with 260 additions and 8 deletions

View File

@@ -275,6 +275,7 @@ class DataRangeArray(Node):
class String(Node):
def __init__(self, value) -> None:
self.value = value
self.quote = '"'
def __eq__(self, other):
return type(self) == type(other) and self.value == other.value
@@ -283,12 +284,60 @@ class String(Node):
return f"String({self.value})"
def print(self):
return f'"{self.value}"'
if self.quote == "'":
return f"'{self.value}'"
escaped = self.value.replace('{', '{{').replace('}', '}}')
return f'"{escaped}"'
def _eval(self, vtable):
return vtable, ValStr(self.value)
def _flip_quotes(node, quote):
"""Recursively set quote style on all String nodes in an expression tree."""
if isinstance(node, String):
node.quote = quote
for attr in vars(node).values():
if isinstance(attr, Node):
_flip_quotes(attr, quote)
elif isinstance(attr, list):
for item in attr:
if isinstance(item, Node):
_flip_quotes(item, quote)
class InterpolatedString(Node):
def __init__(self, parts) -> None:
self.parts = parts
def __eq__(self, other):
return type(self) == type(other) and self.parts == other.parts
def __repr__(self):
return f"InterpolatedString([{rep_join(self.parts)}])"
def print(self):
result = '"'
for part in self.parts:
if isinstance(part, String):
result += part.value.replace('{', '{{').replace('}', '}}')
else:
_flip_quotes(part, "'")
result += '{' + part.print() + '}'
_flip_quotes(part, '"')
result += '"'
return result
def _eval(self, vtable):
magnvm = "MAGNVM" in vtable["#modules"]
svbnvlla = "SVBNVLLA" in vtable["#modules"]
pieces = []
for part in self.parts:
vtable, val = part.eval(vtable)
pieces.append(make_string(val, magnvm, svbnvlla))
return vtable, ValStr(''.join(pieces))
class Numeral(Node):
def __init__(self, value: str) -> None:
self.value = value

View File

@@ -1,6 +1,6 @@
from centvrion.errors import CentvrionError
from centvrion.ast_nodes import (
String, Numeral, Fractio, Bool, Nullus, ID,
String, InterpolatedString, Numeral, Fractio, Bool, Nullus, ID,
BinOp, UnaryMinus, UnaryNot,
ArrayIndex, DataArray, DataRangeArray,
BuiltIn, Invoca,
@@ -51,6 +51,25 @@ def emit_expr(node, ctx):
tmp = ctx.fresh_tmp()
return [f'CentValue {tmp} = cent_str("{_escape(node.value)}");'], tmp
if isinstance(node, InterpolatedString):
if len(node.parts) == 0:
tmp = ctx.fresh_tmp()
return [f'CentValue {tmp} = cent_str("");'], tmp
if len(node.parts) == 1:
return emit_expr(node.parts[0], ctx)
l_lines, l_var = emit_expr(node.parts[0], ctx)
r_lines, r_var = emit_expr(node.parts[1], ctx)
lines = l_lines + r_lines
acc = ctx.fresh_tmp()
lines.append(f"CentValue {acc} = cent_concat({l_var}, {r_var});")
for part in node.parts[2:]:
p_lines, p_var = emit_expr(part, ctx)
lines.extend(p_lines)
new_acc = ctx.fresh_tmp()
lines.append(f"CentValue {new_acc} = cent_concat({acc}, {p_var});")
acc = new_acc
return lines, acc
if isinstance(node, Bool):
tmp = ctx.fresh_tmp()
v = "1" if node.value else "0"

View File

@@ -1,10 +1,71 @@
from rply import ParserGenerator
from centvrion.lexer import all_tokens
from centvrion.errors import CentvrionError
from centvrion.lexer import Lexer, all_tokens
from . import ast_nodes
ALL_TOKENS = list(set([i[0] for i in all_tokens]))
def _parse_interpolated(raw_value):
quote_char = raw_value[0]
inner = raw_value[1:-1]
if quote_char == "'" or len(inner) == 0:
return ast_nodes.String(inner)
parts = []
i = 0
current = []
while i < len(inner):
ch = inner[i]
if ch == '{':
if i + 1 < len(inner) and inner[i + 1] == '{':
current.append('{')
i += 2
continue
if current:
parts.append(ast_nodes.String(''.join(current)))
current = []
j = i + 1
depth = 1
while j < len(inner) and depth > 0:
if inner[j] == '{':
depth += 1
elif inner[j] == '}':
depth -= 1
j += 1
if depth != 0:
raise CentvrionError("Unclosed '{' in interpolated string")
expr_src = inner[i + 1:j - 1]
tokens = Lexer().get_lexer().lex(expr_src + "\n")
program = Parser().parse(tokens)
if len(program.statements) != 1:
raise CentvrionError("Interpolation must contain exactly one expression")
stmt = program.statements[0]
if not isinstance(stmt, ast_nodes.ExpressionStatement):
raise CentvrionError("Interpolation must contain an expression, not a statement")
parts.append(stmt.expression)
i = j
elif ch == '}':
if i + 1 < len(inner) and inner[i + 1] == '}':
current.append('}')
i += 2
continue
raise CentvrionError("Unmatched '}' in string (use '}}' for literal '}')")
else:
current.append(ch)
i += 1
if current:
parts.append(ast_nodes.String(''.join(current)))
if len(parts) == 1 and isinstance(parts[0], ast_nodes.String):
return parts[0]
return ast_nodes.InterpolatedString(parts)
class Parser():
def __init__(self):
self.pg = ParserGenerator(
@@ -184,7 +245,7 @@ class Parser():
@self.pg.production('expression : DATA_STRING')
def expression_string(tokens):
return ast_nodes.String(tokens[0].value[1:-1])
return _parse_interpolated(tokens[0].value)
@self.pg.production('expression : DATA_NUMERAL')
def expression_numeral(tokens):