Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/bpp/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import TYPE_CHECKING, Self

from .exceptions import BrainfuckSyntaxError
from .tokens import Token, tokenize
from .tokens import Token, _tokenize_with_positions

if TYPE_CHECKING:
from collections.abc import Sequence
Expand All @@ -20,7 +20,7 @@ class ResultState(Enum):
JUMP_BACKWARD = auto()


def validate_syntax(syntax: Sequence[Sequence[Token]]) -> None:
def validate_syntax(syntax: Sequence[Sequence[tuple[Token, int]]]) -> None:
"""Validate the given syntax.

Raises
Expand All @@ -32,16 +32,16 @@ def validate_syntax(syntax: Sequence[Sequence[Token]]) -> None:
loop_started_at_line = 0
loop_started_at_character = 0
for line_index, line in enumerate(syntax):
for character_index, token in enumerate(line):
for token, char_position in line:
if token == Token.LOOP_START:
loop_started_at_line = line_index
loop_started_at_character = character_index
loop_started_at_character = char_position
depth += 1
if token == Token.LOOP_END:
if depth == 0:
msg = (
"Syntax error: Unexpected closing bracket in line "
f"{line_index + 1} at char {character_index + 1}!"
f"{line_index + 1} at char {char_position + 1}!"
)
raise BrainfuckSyntaxError(msg)
depth -= 1
Expand Down Expand Up @@ -140,9 +140,9 @@ def run(self: Self, code: str) -> str:
-------
The output of the code.
"""
syntax = [tokenize(line) for line in code.split("\n")]
syntax = [_tokenize_with_positions(line) for line in code.split("\n")]
validate_syntax(syntax)
tokens = [token for line in syntax for token in line]
tokens = [token for line in syntax for token, _ in line]

# Precompute matching bracket pairs to support nested loops.
# validate_syntax guarantees brackets are balanced, so the stack is
Expand Down
17 changes: 17 additions & 0 deletions src/bpp/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,20 @@ def tokenize(code: str) -> Sequence[Token]:
continue
tokens.append(token)
return tokens


def _tokenize_with_positions(code: str) -> Sequence[tuple[Token, int]]:
"""Convert text to (token, original_char_index) tuples.

Returns
-------
A sequence of (token, original_char_index) tuples preserving the
character's position in the original source string.
"""
tokens = []
for position, character in enumerate(code):
token = Token.from_character(character)
if token is None:
continue
tokens.append((token, position))
return tokens
3 changes: 3 additions & 0 deletions tests/test_interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ def test_can_input_letter(monkeypatch) -> None: # type: ignore[no-untyped-def]
[
("[", "Syntax error: Unclosed bracket in line 1 at char 1!"),
("]", "Syntax error: Unexpected closing bracket in line 1 at char 1!"),
# Comments before brackets should not affect reported position
(">comment[", r"Syntax error: Unclosed bracket in line 1 at char 9!"),
(">comment]", r"Syntax error: Unexpected closing bracket in line 1 at char 9!"),
],
)
def test_invalid_syntax_fails(code: str, error_message: str) -> None:
Expand Down
Loading