diff --git a/examples/basic/23_format_spec.peb b/examples/basic/23_format_spec.peb index e9055f5..8e1c646 100644 --- a/examples/basic/23_format_spec.peb +++ b/examples/basic/23_format_spec.peb @@ -55,5 +55,12 @@ print(f"{"test":<<9}") print(f"{"test":>>9}") print(f"{"test":^^9}") print() +print(f"{d2:8}") +print(f"{d2:08}") +print(f"{d2:<<8}") +print(f"{d2:>>8}") +print(f"{d2:^^8}") +print(f"{d2:==8}") +print() print("Complex") \ No newline at end of file diff --git a/src/core/format_spec/lexer.py b/src/core/format_spec/lexer.py index edec7d2..3368eb3 100644 --- a/src/core/format_spec/lexer.py +++ b/src/core/format_spec/lexer.py @@ -83,6 +83,8 @@ class FormatSpecLexer: self.add_token(TokenType.RIGHT) case "^": self.add_token(TokenType.CENTER) + case "=": + self.add_token(TokenType.EQUAL) case "+": self.add_token(TokenType.PLUS) case "-": @@ -113,6 +115,8 @@ class FormatSpecLexer: self.add_token(TokenType.T_PCT) case ".": self.add_token(TokenType.DOT) + case "0": + self.add_token(TokenType.ZERO) case _: if char.isdigit(): self.scan_number() diff --git a/src/core/format_spec/parser.py b/src/core/format_spec/parser.py index 2883630..39a2f59 100644 --- a/src/core/format_spec/parser.py +++ b/src/core/format_spec/parser.py @@ -23,13 +23,14 @@ class FormatSpecParser: ALIGNMENT: set[TokenType] = { TokenType.LEFT, TokenType.RIGHT, - TokenType.CENTER + TokenType.CENTER, + TokenType.EQUAL, } SIGN: set[TokenType] = { TokenType.PLUS, TokenType.MINUS, - TokenType.SPACE + TokenType.SPACE, } def __init__(self, tokens: list[Token]): @@ -93,9 +94,12 @@ class FormatSpecParser: if self.match(*self.SIGN): sign = self.previous() + zfill: bool = self.match(TokenType.ZERO) + return FormatSpecOptions( alignment=alignment, - sign=sign + sign=sign, + zfill=zfill ) def alignment(self) -> Optional[FormatSpecAlignment]: diff --git a/src/core/format_spec/spec.py b/src/core/format_spec/spec.py index e5d21f5..be7f7ab 100644 --- a/src/core/format_spec/spec.py +++ b/src/core/format_spec/spec.py @@ -14,6 +14,7 @@ class FormatSpecAlignment: class FormatSpecOptions: alignment: Optional[FormatSpecAlignment] sign: Optional[Token] + zfill: bool @dataclass(frozen=True) diff --git a/src/core/format_spec/string_formatter.py b/src/core/format_spec/string_formatter.py index fc16874..f6aabd4 100644 --- a/src/core/format_spec/string_formatter.py +++ b/src/core/format_spec/string_formatter.py @@ -69,8 +69,7 @@ class StringFormatter: else: fmt_type, obj = self.guess_type(obj) - align_side: TokenType = TokenType.LEFT - fill_char: str = " " + is_num: bool = False match fmt_type: case None: res = self.stringify(obj) @@ -78,18 +77,27 @@ class StringFormatter: res = obj case TokenType.T_BIN | TokenType.T_DEC | TokenType.T_HEX | TokenType.T_HEX_CAPS | TokenType.T_OCT: res = self.format_int(obj, spec, fmt_type) - align_side = TokenType.RIGHT + is_num = True case TokenType.T_FIX: res = self.format_float_fix(obj, spec, fmt_type) - align_side = TokenType.RIGHT + is_num = True case TokenType.T_PCT: res = self.format_float_fix(obj * 100, spec, TokenType.T_FIX) + "%" - align_side = TokenType.RIGHT + is_num = True case TokenType.T_SCI: res = self.format_float_sci(obj, spec, fmt_type) - align_side = TokenType.RIGHT + is_num = True align_spec: Optional[FormatSpecAlignment] = spec.options.alignment + align_side: TokenType = TokenType.LEFT + fill_char: str = "0" if spec.options.zfill else " " + if is_num: + align_side = TokenType.EQUAL if spec.options.zfill else TokenType.RIGHT + elif align_side == TokenType.EQUAL: + # Should always be true at this point + if align_spec is not None: + raise PebbleRuntimeError(align_spec.align, "Sign-aware alignment is only valid for numbers.") + if align_spec is not None: align_side = align_spec.align.type if align_spec.fill is not None: @@ -190,11 +198,16 @@ class StringFormatter: case TokenType.LEFT: left = 0 right = to_pad - case TokenType.RIGHT: + case TokenType.RIGHT | TokenType.EQUAL: left = to_pad right = 0 case TokenType.CENTER: left = to_pad // 2 right = to_pad - left - return char * left + string + char * right + sign: str = "" + if side == TokenType.EQUAL: + if string.startswith(("+", "-", " ")): + sign = string[0] + string = string[1:] + return sign + char * left + string + char * right diff --git a/src/core/format_spec/token.py b/src/core/format_spec/token.py index fc27c88..10d5283 100644 --- a/src/core/format_spec/token.py +++ b/src/core/format_spec/token.py @@ -10,6 +10,7 @@ class TokenType(Enum): LEFT = auto() RIGHT = auto() CENTER = auto() + EQUAL = auto() # Sign PLUS = auto() @@ -37,6 +38,7 @@ class TokenType(Enum): T_PCT = auto() # Misc + ZERO = auto() NUMBER = auto() DOT = auto() EOF = auto()