feat: add f-string interpolation
This commit is contained in:
@@ -2,7 +2,6 @@ let def = "DEF"
|
||||
let s = f"abc {def} {"ghi"}"
|
||||
print(s)
|
||||
|
||||
/*
|
||||
class Person {
|
||||
init(name) {
|
||||
this.name = name
|
||||
@@ -26,4 +25,3 @@ let alice = Person("Alice")
|
||||
let bob = Person("Bob")
|
||||
|
||||
meet(alice, bob)
|
||||
*/
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user