🐐 String interpolation
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user