diff --git a/examples/basic/22_string_formatting.peb b/examples/basic/22_string_formatting.peb index 6965716..9bb7c05 100644 --- a/examples/basic/22_string_formatting.peb +++ b/examples/basic/22_string_formatting.peb @@ -2,7 +2,6 @@ let def = "DEF" let s = f"abc {def} {"ghi"}" print(s) -/* class Person { init(name) { this.name = name @@ -25,5 +24,4 @@ fun meet(person1, person2) { let alice = Person("Alice") let bob = Person("Bob") -meet(alice, bob) -*/ \ No newline at end of file +meet(alice, bob) \ No newline at end of file diff --git a/src/ast/expr.py b/src/ast/expr.py index 1446001..c27dcb8 100644 --- a/src/ast/expr.py +++ b/src/ast/expr.py @@ -45,6 +45,14 @@ class Expr(ABC): def visit_literal_expr(self, expr: LiteralExpr) -> T: ... + @abstractmethod + def visit_fstring_expr(self, expr: FStringExpr) -> T: + ... + + @abstractmethod + def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> T: + ... + @abstractmethod def visit_variable_expr(self, expr: VariableExpr) -> T: ... @@ -129,6 +137,26 @@ class LiteralExpr(Expr): return visitor.visit_literal_expr(self) +@dataclass(frozen=True) +class FStringExpr(Expr): + start: Token + parts: list[LiteralExpr | FStringEmbedExpr] + end: Token + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_fstring_expr(self) + + +@dataclass(frozen=True) +class FStringEmbedExpr(Expr): + start: Token + expression: Expr + end: Token + + def accept(self, visitor: Expr.Visitor[T]) -> T: + return visitor.visit_fstring_embed_expr(self) + + @dataclass(frozen=True) class VariableExpr(Expr): name: Token diff --git a/src/formatter.py b/src/formatter.py index b34d852..fb1a446 100644 --- a/src/formatter.py +++ b/src/formatter.py @@ -2,7 +2,7 @@ from enum import Enum, auto from typing import Any from src.ast.expr import Expr, VariableExpr, LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, AssignExpr, LogicalExpr, \ - CallExpr, SetExpr, GetExpr, ThisExpr, T, SuperExpr + CallExpr, SetExpr, GetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr from src.ast.stmt import Stmt, LetStmt, IfStmt, ExpressionStmt, BlockStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt @@ -77,6 +77,19 @@ class Formatter(Expr.Visitor[str], Stmt.Visitor[str]): return f'"{value}"' return str(value) + def visit_fstring_expr(self, expr: FStringExpr) -> str: + res: str = 'f"' + for part in expr.parts: + if isinstance(part, FStringEmbedExpr): + res += self.format(part) + else: + res += part.value + res += '"' + return res + + def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> str: + return "{" + self.format(expr.expression) + "}" + def visit_variable_expr(self, expr: VariableExpr) -> str: return expr.name.lexeme diff --git a/src/interpreter/interpreter.py b/src/interpreter/interpreter.py index 4473166..f483365 100644 --- a/src/interpreter/interpreter.py +++ b/src/interpreter/interpreter.py @@ -1,7 +1,7 @@ from typing import Any, Optional from src.ast.expr import LiteralExpr, GroupingExpr, UnaryExpr, BinaryExpr, Expr, VariableExpr, AssignExpr, LogicalExpr, \ - CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -179,6 +179,15 @@ class Interpreter(Expr.Visitor[Any], Stmt.Visitor[None]): def visit_literal_expr(self, expr: LiteralExpr) -> Any: return expr.value + def visit_fstring_expr(self, expr: FStringExpr) -> Any: + return "".join([ + self.evaluate(part) + for part in expr.parts + ]) + + def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> Any: + return self.stringify(self.evaluate(expr.expression)) + def visit_variable_expr(self, expr: VariableExpr) -> Any: return self.look_up_variable(expr.name, expr) diff --git a/src/interpreter/resolver.py b/src/interpreter/resolver.py index 84be0a0..54c2592 100644 --- a/src/interpreter/resolver.py +++ b/src/interpreter/resolver.py @@ -4,7 +4,7 @@ from enum import Enum, auto from typing import TYPE_CHECKING from src.ast.expr import Expr, LogicalExpr, VariableExpr, LiteralExpr, GroupingExpr, CallExpr, UnaryExpr, BinaryExpr, \ - AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr + AssignExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr from src.ast.stmt import Stmt, ForStmt, WhileStmt, LetStmt, ReturnStmt, IfStmt, FunctionStmt, \ ExpressionStmt, BlockStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import CONSTRUCTOR_NAME @@ -106,6 +106,13 @@ class Resolver(Expr.Visitor[None], Stmt.Visitor[None]): def visit_literal_expr(self, expr: LiteralExpr) -> None: pass + def visit_fstring_expr(self, expr: FStringExpr) -> None: + for part in expr.parts: + self.resolve(part) + + def visit_fstring_embed_expr(self, expr: FStringEmbedExpr) -> None: + self.resolve(expr.expression) + def visit_variable_expr(self, expr: VariableExpr) -> None: if len(self.scopes) != 0 and self.scopes[-1].get(expr.name.lexeme) is False: Pebble.token_error(expr.name, "Variable is not initialized.") diff --git a/src/parser/parser.py b/src/parser/parser.py index 14c6d83..ffe8983 100644 --- a/src/parser/parser.py +++ b/src/parser/parser.py @@ -1,7 +1,7 @@ from typing import Optional from src.ast.expr import Expr, BinaryExpr, UnaryExpr, LiteralExpr, GroupingExpr, VariableExpr, AssignExpr, LogicalExpr, \ - CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr + CallExpr, GetExpr, SetExpr, ThisExpr, SuperExpr, FStringExpr, FStringEmbedExpr from src.ast.stmt import Stmt, ExpressionStmt, LetStmt, BlockStmt, IfStmt, WhileStmt, ForStmt, FunctionStmt, \ ReturnStmt, BreakStmt, ContinueStmt, ClassStmt from src.consts import MAX_FUNCTION_ARGS @@ -364,6 +364,9 @@ class Parser: if self.match(TokenType.NULL): return LiteralExpr(None) + if self.match(TokenType.FSTRING_START): + return self.fstring() + if self.match(TokenType.NUMBER, TokenType.STRING): return LiteralExpr(self.previous().value) @@ -385,3 +388,20 @@ class Parser: return GroupingExpr(expr) raise self.error(self.peek(), "Expected expression") + + def fstring(self) -> Expr: + start: Token = self.previous() + parts: list[Expr] = [] + + while not self.check(TokenType.FSTRING_END) and not self.is_at_end(): + if self.match(TokenType.LEFT_BRACE): + brace: Token = self.previous() + expr: Expr = self.expression() + self.consume(TokenType.RIGHT_BRACE, "Expected '}' after f-string embed") + parts.append(FStringEmbedExpr(brace, expr, self.previous())) + else: + self.consume(TokenType.FSTRING_TEXT, "Unexpected token") + parts.append(LiteralExpr(self.previous().value)) + + self.consume(TokenType.FSTRING_END, "Unclosed f-string") + return FStringExpr(start, parts, self.previous())