varphi-devkit 1.4.0__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. varphi_devkit-2.0.0/.github/workflows/pipeline.yml +58 -0
  2. varphi_devkit-2.0.0/.gitignore +13 -0
  3. varphi_devkit-2.0.0/.python-version +1 -0
  4. varphi_devkit-2.0.0/PKG-INFO +7 -0
  5. varphi_devkit-2.0.0/README.md +0 -0
  6. varphi_devkit-2.0.0/grammar/Varphi.g4 +54 -0
  7. varphi_devkit-2.0.0/pyproject.toml +33 -0
  8. varphi_devkit-2.0.0/scripts/build.py +69 -0
  9. varphi_devkit-2.0.0/src/varphi_devkit/__init__.py +38 -0
  10. varphi_devkit-2.0.0/src/varphi_devkit/compiler.py +185 -0
  11. varphi_devkit-2.0.0/src/varphi_devkit/exceptions.py +130 -0
  12. varphi_devkit-2.0.0/src/varphi_devkit/parser/Varphi.interp +47 -0
  13. varphi_devkit-2.0.0/src/varphi_devkit/parser/Varphi.tokens +21 -0
  14. varphi_devkit-2.0.0/src/varphi_devkit/parser/VarphiLexer.interp +59 -0
  15. varphi_devkit-2.0.0/src/varphi_devkit/parser/VarphiLexer.py +97 -0
  16. varphi_devkit-2.0.0/src/varphi_devkit/parser/VarphiLexer.tokens +21 -0
  17. varphi_devkit-2.0.0/src/varphi_devkit/parser/VarphiListener.py +84 -0
  18. varphi_devkit-2.0.0/src/varphi_devkit/parser/VarphiParser.py +655 -0
  19. varphi_devkit-2.0.0/src/varphi_devkit/parser/__init__.py +5 -0
  20. varphi_devkit-2.0.0/src/varphi_devkit/py.typed +0 -0
  21. varphi_devkit-2.0.0/tests/test_devkit.py +155 -0
  22. varphi_devkit-2.0.0/uv.lock +92 -0
  23. varphi_devkit-1.4.0/LICENSE +0 -28
  24. varphi_devkit-1.4.0/PKG-INFO +0 -200
  25. varphi_devkit-1.4.0/README.md +0 -178
  26. varphi_devkit-1.4.0/pyproject.toml +0 -110
  27. varphi_devkit-1.4.0/src/varphi_devkit/__init__.py +0 -33
  28. varphi_devkit-1.4.0/src/varphi_devkit/compilation.py +0 -125
  29. varphi_devkit-1.4.0/src/varphi_devkit/model.py +0 -55
  30. varphi_devkit-1.4.0/src/varphi_devkit/syntax/Varphi.g4 +0 -24
  31. varphi_devkit-1.4.0/src/varphi_devkit/syntax/__init__.py +0 -28
  32. varphi_devkit-1.4.0/src/varphi_devkit/syntax/antlr/VarphiLexer.py +0 -76
  33. varphi_devkit-1.4.0/src/varphi_devkit/syntax/antlr/VarphiListener.py +0 -30
  34. varphi_devkit-1.4.0/src/varphi_devkit/syntax/antlr/VarphiParser.py +0 -180
  35. varphi_devkit-1.4.0/src/varphi_devkit/syntax/antlr/__init__.py +0 -21
  36. varphi_devkit-1.4.0/src/varphi_devkit/syntax/error_listener.py +0 -79
@@ -0,0 +1,58 @@
1
+ name: CI/CD
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ jobs:
11
+ test:
12
+ name: Test
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-java@v4
18
+ with:
19
+ distribution: 'temurin'
20
+ java-version: '17'
21
+
22
+ - uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: true
25
+
26
+ - name: Generate ANTLR Parser
27
+ run: uv run python scripts/build.py
28
+
29
+ - name: Run Pytest
30
+ run: uv run pytest
31
+
32
+ release:
33
+ name: Build & Publish
34
+ needs: test
35
+ if: startsWith(github.ref, 'refs/tags/v')
36
+ runs-on: ubuntu-latest
37
+ environment: pypi
38
+ permissions:
39
+ id-token: write
40
+
41
+ steps:
42
+ - uses: actions/checkout@v4
43
+
44
+ - uses: actions/setup-java@v4
45
+ with:
46
+ distribution: 'temurin'
47
+ java-version: '17'
48
+
49
+ - uses: astral-sh/setup-uv@v5
50
+
51
+ - name: Generate ANTLR Parser
52
+ run: uv run python scripts/build.py
53
+
54
+ - name: Build Package
55
+ run: uv build
56
+
57
+ - name: Publish to PyPI
58
+ run: uv publish
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ src/varphi_devkit/parser/*
13
+ !src/varphi_devkit/parser/__init__.py
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: varphi-devkit
3
+ Version: 2.0.0
4
+ Summary: Development kit for the Varphi language
5
+ Author-email: Hassan El-Sheikha <hassan.elsheikha@utoronto.ca>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: antlr4-python3-runtime>=4.13.2
File without changes
@@ -0,0 +1,54 @@
1
+ grammar Varphi;
2
+
3
+ // ======================================================
4
+ // PARSER RULES
5
+ // ======================================================
6
+
7
+ program : NEWLINE* transition (NEWLINE+ transition)* NEWLINE* EOF;
8
+
9
+ transition : current_state=state_id read_symbols next_state=state_id write_symbols shift_directions;
10
+
11
+ read_symbols : LPAREN symbol (COMMA symbol)* RPAREN;
12
+
13
+ write_symbols : LPAREN symbol (COMMA symbol)* RPAREN;
14
+
15
+ shift_directions : LPAREN direction (COMMA direction)* RPAREN;
16
+
17
+ state_id : ID | ALPHANUM | LEFT_KW | RIGHT_KW | STAY_KW | BLANK_KW;
18
+
19
+ // Updated: Symbol can now be a Variable instead of STAR
20
+ symbol : ALPHANUM | BLANK_KW | VARIABLE;
21
+
22
+ direction : LEFT_KW | RIGHT_KW | STAY_KW;
23
+
24
+ // ======================================================
25
+ // LEXER RULES
26
+ // ======================================================
27
+
28
+ LPAREN : '(';
29
+ RPAREN : ')';
30
+ COMMA : ',';
31
+ // STAR rule removed
32
+
33
+ // New Lexer Rule for Variables (e.g., $x, $val_1)
34
+ VARIABLE : '$' [a-zA-Z0-9_]+;
35
+
36
+ // Keywords
37
+ LEFT_KW : 'LEFT';
38
+ RIGHT_KW : 'RIGHT';
39
+ STAY_KW : 'STAY';
40
+ BLANK_KW : 'BLANK';
41
+
42
+ // ------------------------------------------------------
43
+ // PRIORITY RULES
44
+ // ------------------------------------------------------
45
+
46
+ ALPHANUM : [a-zA-Z0-9];
47
+ ID : [a-zA-Z0-9_]+;
48
+
49
+ // ------------------------------------------------------
50
+
51
+ COMMENT : '//' ~[\r\n]* -> skip;
52
+ MULTI_COMMENT : '/*' .*? '*/' -> skip;
53
+ WS : [ \t]+ -> skip;
54
+ NEWLINE : '\r'? '\n';
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "varphi-devkit"
3
+ version = "2.0.0"
4
+ description = "Development kit for the Varphi language"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "antlr4-python3-runtime>=4.13.2",
9
+ ]
10
+ authors = [
11
+ { name = "Hassan El-Sheikha", email = "hassan.elsheikha@utoronto.ca" }
12
+ ]
13
+
14
+ [build-system]
15
+ requires = ["hatchling"]
16
+ build-backend = "hatchling.build"
17
+
18
+
19
+ [tool.hatch.build.targets.wheel.force-include]
20
+ "src/varphi_devkit/parser" = "varphi_devkit/parser"
21
+
22
+ [tool.hatch.build.targets.sdist.force-include]
23
+ "src/varphi_devkit/parser" = "src/varphi_devkit/parser"
24
+
25
+
26
+ [tool.pytest.ini_options]
27
+ testpaths = ["tests"]
28
+ pythonpath = ["src"]
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "pytest>=8.0.0",
33
+ ]
@@ -0,0 +1,69 @@
1
+ import subprocess
2
+ import sys
3
+ import shutil
4
+ import urllib.request
5
+ import tempfile
6
+ import os
7
+ from pathlib import Path
8
+
9
+ # Configuration
10
+ ANTLR_DOWNLOAD_URL = "https://www.antlr.org/download/antlr-4.13.2-complete.jar"
11
+
12
+ # Paths
13
+ ROOT = Path(__file__).parent.parent
14
+ GRAMMAR_FILE = ROOT / "grammar" / "Varphi.g4"
15
+ # Using the path from your snippet:
16
+ OUTPUT_DIR = ROOT / "src" / "varphi_devkit" / "parser"
17
+
18
+ def check_java_installed():
19
+ if not shutil.which("java"):
20
+ print("Error: 'java' executable not found in PATH.")
21
+ print("Please install Java and ensure it is added to your environment variables.")
22
+ sys.exit(1)
23
+
24
+ def generate_parser():
25
+ check_java_installed()
26
+
27
+ if not GRAMMAR_FILE.exists():
28
+ print(f"Error: Grammar file not found at {GRAMMAR_FILE}")
29
+ sys.exit(1)
30
+
31
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
32
+ (OUTPUT_DIR / "__init__.py").touch()
33
+
34
+ print(f"Downloading ANTLR jar to tempfile...")
35
+
36
+ # Create a temp file. delete=False is required for Windows compatibility
37
+ # so we can close the file before Java tries to open it.
38
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".jar") as tmp_jar:
39
+ try:
40
+ # 1. Download
41
+ with urllib.request.urlopen(ANTLR_DOWNLOAD_URL) as response:
42
+ shutil.copyfileobj(response, tmp_jar)
43
+
44
+ # Close the file handle so Java can read it
45
+ tmp_jar.close()
46
+
47
+ # 2. Run Java
48
+ cmd = [
49
+ "java",
50
+ "-jar", tmp_jar.name,
51
+ "-Dlanguage=Python3",
52
+ "-o", str(OUTPUT_DIR),
53
+ str(GRAMMAR_FILE)
54
+ ]
55
+
56
+ print(f"Generating parser...")
57
+ subprocess.run(cmd, check=True)
58
+ print("Success. Parser generated in src/varphi_devkit/parser/")
59
+
60
+ except subprocess.CalledProcessError as e:
61
+ print("Error during ANTLR generation.")
62
+ sys.exit(e.returncode)
63
+ finally:
64
+ # 3. Cleanup: Delete the temp file manually
65
+ if os.path.exists(tmp_jar.name):
66
+ os.remove(tmp_jar.name)
67
+
68
+ if __name__ == "__main__":
69
+ generate_parser()
@@ -0,0 +1,38 @@
1
+ """
2
+ The Varphi Development Kit.
3
+
4
+ A framework for building compilers, interpreters, and analysis tools for the Varphi language.
5
+ This package handles the complexity of parsing, validation, and variable canonicalization, providing a
6
+ convenient abstraction layer for implementing custom Varphi backends.
7
+
8
+ **Core API:**
9
+ - `VarphiCompiler`: The abstract base class you must subclass. Override `handle_transition` to process logic.
10
+ - `VarphiTransition`: A validated, canonicalized representation of a single transition line.
11
+
12
+ **Constants:**
13
+ - `BLANK`, `LEFT`, `RIGHT`, `STAY`: Primitives for tape operations.
14
+
15
+ **Exceptions:**
16
+ - `VarphiSyntaxError`: Base class for rich error reporting with source code context.
17
+ """
18
+
19
+ from .compiler import VarphiCompiler, VarphiTransition, BLANK, LEFT, RIGHT, STAY
20
+ from .exceptions import (
21
+ VarphiSyntaxError,
22
+ VarphiTransitionInconsistentTapeCountError,
23
+ VarphiGlobalTapeCountError,
24
+ VarphiUndefinedVariableError,
25
+ )
26
+
27
+ __all__ = [
28
+ "VarphiCompiler",
29
+ "VarphiTransition",
30
+ "BLANK",
31
+ "LEFT",
32
+ "RIGHT",
33
+ "STAY",
34
+ "VarphiSyntaxError",
35
+ "VarphiTransitionInconsistentTapeCountError",
36
+ "VarphiGlobalTapeCountError",
37
+ "VarphiUndefinedVariableError",
38
+ ]
@@ -0,0 +1,185 @@
1
+ from abc import ABC, abstractmethod
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+ from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker
5
+
6
+ from .parser import VarphiLexer
7
+ from .parser import VarphiParser
8
+ from .parser import VarphiListener
9
+
10
+ from .exceptions import (
11
+ VarphiErrorListener,
12
+ VarphiTransitionInconsistentTapeCountError,
13
+ VarphiGlobalTapeCountError,
14
+ VarphiUndefinedVariableError,
15
+ )
16
+
17
+ BLANK = "_"
18
+ LEFT = "LEFT"
19
+ RIGHT = "RIGHT"
20
+ STAY = "STAY"
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class VarphiTransition:
25
+ """
26
+ Represents a transition (a single line) in a Varphi program.
27
+ Attributes:
28
+ - current_state (str): String containing the name of the state the machine must be on to trigger this transition.
29
+ - read_symbols (tuple[str, ...]): Tuple of strings containing the values the heads must read to trigger this transition. Strings starting with $ are variables and are guaranteed to be ordered ($1, $2, ...). Otherwise, members may be BLANK or alphanumericals.
30
+ - next_state (str): The name of the next state to transition to when this transition is triggered.
31
+ - write_symbols (tuple[str, ...]): Tuple of strings containing the values the heads will write when this transition is triggered. Follows the same format as read_symbols, and variable names appearing here are guaranteed to appear in read_symbols.
32
+ - shift_directions (tuple[str, ...]): A tuple of strings containing the directions the heads will move in when this transition is triggered. Each element is guaranteed to be one of LEFT, RIGHT, or STAY
33
+ - line_number (int): The line number in the source code this transition corresponds to.
34
+ """
35
+
36
+ current_state: str
37
+ read_symbols: tuple[str, ...]
38
+ next_state: str
39
+ write_symbols: tuple[str, ...]
40
+ shift_directions: tuple[str, ...]
41
+ line_number: int
42
+
43
+
44
+ class VarphiCompiler(VarphiListener, ABC):
45
+ """
46
+ An abstract Varphi compiler.
47
+
48
+ Concrete Varphi compiler implementations must subclass this class and implement the following methods:
49
+ - handle_transition(self, transition: VarphiTransition) -> None: Automatically called at compile-time on each transition in the Varphi program
50
+ - generate_compiled_program(self) -> str: Returns the compiled Varphi program after all transitions have been handled via handle_transition()
51
+ If __init__() is overridden to add additional attributes (e.g., the compiled program so far), then
52
+ - super().__init__() must be called
53
+ - compile(self, program: str) -> str must be overridden to reset the state, followed by a call to super().__init__()
54
+ """
55
+
56
+ _expected_tape_count: Optional[int]
57
+
58
+ def __init__(self):
59
+ """Initialize this compiler."""
60
+ self._expected_tape_count = None
61
+
62
+ @abstractmethod
63
+ def handle_transition(self, transition: VarphiTransition) -> None:
64
+ """Handle a single transition in a Varphi program."""
65
+ pass
66
+
67
+ @abstractmethod
68
+ def generate_compiled_program(self) -> str:
69
+ """Generate the compiled program after all transitions have been handled."""
70
+ pass
71
+
72
+ def compile(self, program: str) -> str:
73
+ """Compile a Varphi program."""
74
+ # Reset state (subclasses must do so for their own state too)
75
+ self._expected_tape_count = None
76
+
77
+ input_stream = InputStream(program)
78
+ error_listener = VarphiErrorListener()
79
+
80
+ lexer = VarphiLexer(input_stream)
81
+ lexer.removeErrorListeners()
82
+ lexer.addErrorListener(error_listener)
83
+
84
+ token_stream = CommonTokenStream(lexer)
85
+ parser = VarphiParser(token_stream)
86
+ parser.removeErrorListeners()
87
+ parser.addErrorListener(error_listener)
88
+
89
+ tree = parser.program()
90
+ walker = ParseTreeWalker()
91
+ walker.walk(self, tree)
92
+
93
+ return self.generate_compiled_program()
94
+
95
+ def enterTransition(self, ctx: VarphiParser.TransitionContext) -> None:
96
+ """Extract information from a raw transition context, and delegate to handle_transition()."""
97
+ current_state = ctx.current_state.getText()
98
+ next_state = ctx.next_state.getText()
99
+
100
+ def extract_symbol(symbol_ctx) -> str:
101
+ """Given a symbol context, extract the corresponding symbol string."""
102
+ if symbol_ctx.VARIABLE():
103
+ return symbol_ctx.VARIABLE().getText()
104
+ if symbol_ctx.BLANK_KW():
105
+ return BLANK
106
+ if symbol_ctx.ALPHANUM():
107
+ return symbol_ctx.ALPHANUM().getText()
108
+ raise ValueError(f"Unknown symbol type: {symbol_ctx.getText()}")
109
+
110
+ def extract_direction(symbol_ctx) -> str:
111
+ """Given a direction context, extract the corresponding direction string."""
112
+ if symbol_ctx.LEFT_KW():
113
+ return LEFT
114
+ if symbol_ctx.RIGHT_KW():
115
+ return RIGHT
116
+ if symbol_ctx.STAY_KW():
117
+ return STAY
118
+ raise ValueError(f"Unknown direction: {symbol_ctx.getText()}")
119
+
120
+ read_ctx = ctx.read_symbols()
121
+ reads = tuple(extract_symbol(s) for s in read_ctx.symbol()) if read_ctx else ()
122
+
123
+ write_ctx = ctx.write_symbols()
124
+ writes = (
125
+ tuple(extract_symbol(s) for s in write_ctx.symbol()) if write_ctx else ()
126
+ )
127
+
128
+ shift_ctx = ctx.shift_directions()
129
+ shifts = (
130
+ tuple(extract_direction(d) for d in shift_ctx.direction())
131
+ if shift_ctx
132
+ else ()
133
+ )
134
+
135
+ # Check if the tuple-lengths of the transition are all the same
136
+ if len(writes) != len(reads) or len(shifts) != len(reads):
137
+ raise VarphiTransitionInconsistentTapeCountError(
138
+ ctx, len(reads), len(writes), len(shifts)
139
+ )
140
+
141
+ # Use the tuple lengths of the first transition as ground truth and make sure all other transitions are consistent
142
+ current_tape_count = len(reads)
143
+ if self._expected_tape_count is None:
144
+ self._expected_tape_count = current_tape_count
145
+ elif current_tape_count != self._expected_tape_count:
146
+ raise VarphiGlobalTapeCountError(
147
+ ctx, self._expected_tape_count, current_tape_count
148
+ )
149
+
150
+ # Map user variables to $1, $2, $3... based on appearance in the read tuple
151
+ # NOTE: This is just an optimization so that equivalent patterns ($x, $y) and ($y, $x) are easy call "equivalent"
152
+ variable_map = {}
153
+ next_var_id = 1
154
+ canonical_reads = []
155
+ for sym in reads:
156
+ if sym.startswith("$"):
157
+ # Check if we have already assigned an ID to this variable (e.g. read($x, $x))
158
+ if sym not in variable_map:
159
+ variable_map[sym] = f"${next_var_id}"
160
+ next_var_id += 1
161
+ canonical_reads.append(variable_map[sym])
162
+ else:
163
+ canonical_reads.append(sym)
164
+
165
+ # Map write variables to their canonical versions
166
+ canonical_writes = []
167
+ for i, sym in enumerate(writes):
168
+ if sym.startswith("$"):
169
+ if sym not in variable_map:
170
+ # This variable is not defined in the read tuple
171
+ specific_ctx = write_ctx.symbol(i)
172
+ raise VarphiUndefinedVariableError(specific_ctx, sym)
173
+ canonical_writes.append(variable_map[sym])
174
+ else:
175
+ canonical_writes.append(sym)
176
+
177
+ transition = VarphiTransition(
178
+ current_state=current_state,
179
+ read_symbols=tuple(canonical_reads),
180
+ next_state=next_state,
181
+ write_symbols=tuple(canonical_writes),
182
+ shift_directions=shifts,
183
+ line_number=ctx.start.line,
184
+ )
185
+ self.handle_transition(transition)
@@ -0,0 +1,130 @@
1
+ from antlr4.error.ErrorListener import ErrorListener
2
+ from antlr4 import Token
3
+
4
+ RESET = "\033[0m"
5
+ BOLD = "\033[1m"
6
+ RED = "\033[91m"
7
+ BLUE = "\033[34m"
8
+ CYAN = "\033[96m"
9
+ WHITE = "\033[97m"
10
+
11
+
12
+ class VarphiSyntaxError(Exception):
13
+ """
14
+ Base exception class for all Varphi compilation errors.
15
+ Captures context (line, column, symbol).
16
+ """
17
+
18
+ def __init__(self, recognizer, offendingSymbol, line, column, msg):
19
+ self.recognizer = recognizer
20
+ self.offendingSymbol = offendingSymbol
21
+ self.line = line
22
+ self.column = column
23
+ self.msg = msg
24
+ super().__init__(msg)
25
+
26
+ def __str__(self) -> str:
27
+ # Start the error message with a bold red "error:" label
28
+ result = [f"\n{BOLD}{RED}error:{RESET} {BOLD}{WHITE}{self.msg}{RESET}"]
29
+ result.append(f"{BLUE} -->{RESET} line {self.line}:{self.column + 1}")
30
+
31
+ # Attempt to retrieve the input stream to show the code snippet
32
+ stream = None
33
+ try:
34
+ # Try getting the stream from the recognizer (parser/lexer)
35
+ if self.recognizer:
36
+ temp_stream = self.recognizer.getInputStream()
37
+ if hasattr(temp_stream, "tokenSource"):
38
+ stream = temp_stream.tokenSource.inputStream
39
+ else:
40
+ stream = temp_stream
41
+
42
+ # If that fails, try getting it from the offending symbol (token)
43
+ elif self.offendingSymbol:
44
+ if hasattr(self.offendingSymbol, "getInputStream"):
45
+ stream = self.offendingSymbol.getInputStream()
46
+ elif hasattr(self.offendingSymbol, "tokenSource"):
47
+ stream = self.offendingSymbol.tokenSource.inputStream
48
+
49
+ # If we successfully found the stream, generate the code preview
50
+ if stream:
51
+ lines = str(stream).splitlines()
52
+ # Ensure the line number is valid within the source
53
+ if 0 <= self.line - 1 < len(lines):
54
+ code_line = lines[self.line - 1]
55
+
56
+ # Formatting constants for the "gutter"
57
+ gutter_width = 4
58
+ line_num_str = str(self.line)
59
+
60
+ # Print the empty pipe above the code line
61
+ result.append(f"{BLUE}{' ' * gutter_width} |{RESET}")
62
+
63
+ # Print the actual line of code with the line number
64
+ result.append(
65
+ f"{BLUE}{line_num_str:>{gutter_width}} |{RESET} {code_line}"
66
+ )
67
+
68
+ # Calculate the length of the error squiggle (^)
69
+ token_len = 1
70
+ if self.offendingSymbol and isinstance(self.offendingSymbol, Token):
71
+ start = self.offendingSymbol.start
72
+ stop = self.offendingSymbol.stop
73
+ if start is not None and stop is not None:
74
+ token_len = stop - start + 1
75
+
76
+ # Create the pointer line (e.g., " | ^~~~")
77
+ padding = " " * self.column
78
+ pointer = f"{BOLD}{RED}^{'~' * (token_len - 1)}{RESET}"
79
+ result.append(
80
+ f"{BLUE}{' ' * gutter_width} |{RESET} {padding}{pointer}"
81
+ )
82
+
83
+ except Exception:
84
+ # Fallback: If anything fails during error generation, just return the basic message
85
+ pass
86
+
87
+ return "\n".join(result)
88
+
89
+
90
+ class VarphiTransitionInconsistentTapeCountError(VarphiSyntaxError):
91
+ """Raised when a single transition has mismatched read/write/shift tuple lengths."""
92
+
93
+ def __init__(self, ctx, r_len, w_len, s_len):
94
+ msg = (
95
+ f"local tape count mismatch: read {r_len} symbols, "
96
+ f"but wrote {w_len} and shifted {s_len}"
97
+ )
98
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
99
+
100
+
101
+ class VarphiGlobalTapeCountError(VarphiSyntaxError):
102
+ """Raised when a transition's tape count doesn't match the machine's global tape count."""
103
+
104
+ def __init__(self, ctx, expected, actual):
105
+ msg = (
106
+ f"global tape count mismatch: previous transitions used {expected} tapes, "
107
+ f"but this one uses {actual}"
108
+ )
109
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
110
+
111
+
112
+ class VarphiUndefinedVariableError(VarphiSyntaxError):
113
+ """Raised when a variable is used in a write tuple without being defined in the read tuple."""
114
+
115
+ def __init__(self, ctx, variable_name):
116
+ msg = (
117
+ f"Undefined variable: '{variable_name}' is used in the write tuple "
118
+ f"but was not defined in the read tuple."
119
+ )
120
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
121
+
122
+
123
+ class VarphiErrorListener(ErrorListener):
124
+ """Custom ANTLR ErrorListener that converts syntax errors into VarphiSyntaxErrors."""
125
+
126
+ def __init__(self):
127
+ super().__init__()
128
+
129
+ def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
130
+ raise VarphiSyntaxError(recognizer, offendingSymbol, line, column, msg)
@@ -0,0 +1,47 @@
1
+ token literal names:
2
+ null
3
+ '('
4
+ ')'
5
+ ','
6
+ null
7
+ 'LEFT'
8
+ 'RIGHT'
9
+ 'STAY'
10
+ 'BLANK'
11
+ null
12
+ null
13
+ null
14
+ null
15
+ null
16
+ null
17
+
18
+ token symbolic names:
19
+ null
20
+ LPAREN
21
+ RPAREN
22
+ COMMA
23
+ VARIABLE
24
+ LEFT_KW
25
+ RIGHT_KW
26
+ STAY_KW
27
+ BLANK_KW
28
+ ALPHANUM
29
+ ID
30
+ COMMENT
31
+ MULTI_COMMENT
32
+ WS
33
+ NEWLINE
34
+
35
+ rule names:
36
+ program
37
+ transition
38
+ read_symbols
39
+ write_symbols
40
+ shift_directions
41
+ state_id
42
+ symbol
43
+ direction
44
+
45
+
46
+ atn:
47
+ [4, 1, 14, 88, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 1, 0, 5, 0, 18, 8, 0, 10, 0, 12, 0, 21, 9, 0, 1, 0, 1, 0, 4, 0, 25, 8, 0, 11, 0, 12, 0, 26, 1, 0, 5, 0, 30, 8, 0, 10, 0, 12, 0, 33, 9, 0, 1, 0, 5, 0, 36, 8, 0, 10, 0, 12, 0, 39, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 5, 2, 53, 8, 2, 10, 2, 12, 2, 56, 9, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 5, 3, 64, 8, 3, 10, 3, 12, 3, 67, 9, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 5, 4, 75, 8, 4, 10, 4, 12, 4, 78, 9, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 0, 0, 8, 0, 2, 4, 6, 8, 10, 12, 14, 0, 3, 1, 0, 5, 10, 2, 0, 4, 4, 8, 9, 1, 0, 5, 7, 86, 0, 19, 1, 0, 0, 0, 2, 42, 1, 0, 0, 0, 4, 48, 1, 0, 0, 0, 6, 59, 1, 0, 0, 0, 8, 70, 1, 0, 0, 0, 10, 81, 1, 0, 0, 0, 12, 83, 1, 0, 0, 0, 14, 85, 1, 0, 0, 0, 16, 18, 5, 14, 0, 0, 17, 16, 1, 0, 0, 0, 18, 21, 1, 0, 0, 0, 19, 17, 1, 0, 0, 0, 19, 20, 1, 0, 0, 0, 20, 22, 1, 0, 0, 0, 21, 19, 1, 0, 0, 0, 22, 31, 3, 2, 1, 0, 23, 25, 5, 14, 0, 0, 24, 23, 1, 0, 0, 0, 25, 26, 1, 0, 0, 0, 26, 24, 1, 0, 0, 0, 26, 27, 1, 0, 0, 0, 27, 28, 1, 0, 0, 0, 28, 30, 3, 2, 1, 0, 29, 24, 1, 0, 0, 0, 30, 33, 1, 0, 0, 0, 31, 29, 1, 0, 0, 0, 31, 32, 1, 0, 0, 0, 32, 37, 1, 0, 0, 0, 33, 31, 1, 0, 0, 0, 34, 36, 5, 14, 0, 0, 35, 34, 1, 0, 0, 0, 36, 39, 1, 0, 0, 0, 37, 35, 1, 0, 0, 0, 37, 38, 1, 0, 0, 0, 38, 40, 1, 0, 0, 0, 39, 37, 1, 0, 0, 0, 40, 41, 5, 0, 0, 1, 41, 1, 1, 0, 0, 0, 42, 43, 3, 10, 5, 0, 43, 44, 3, 4, 2, 0, 44, 45, 3, 10, 5, 0, 45, 46, 3, 6, 3, 0, 46, 47, 3, 8, 4, 0, 47, 3, 1, 0, 0, 0, 48, 49, 5, 1, 0, 0, 49, 54, 3, 12, 6, 0, 50, 51, 5, 3, 0, 0, 51, 53, 3, 12, 6, 0, 52, 50, 1, 0, 0, 0, 53, 56, 1, 0, 0, 0, 54, 52, 1, 0, 0, 0, 54, 55, 1, 0, 0, 0, 55, 57, 1, 0, 0, 0, 56, 54, 1, 0, 0, 0, 57, 58, 5, 2, 0, 0, 58, 5, 1, 0, 0, 0, 59, 60, 5, 1, 0, 0, 60, 65, 3, 12, 6, 0, 61, 62, 5, 3, 0, 0, 62, 64, 3, 12, 6, 0, 63, 61, 1, 0, 0, 0, 64, 67, 1, 0, 0, 0, 65, 63, 1, 0, 0, 0, 65, 66, 1, 0, 0, 0, 66, 68, 1, 0, 0, 0, 67, 65, 1, 0, 0, 0, 68, 69, 5, 2, 0, 0, 69, 7, 1, 0, 0, 0, 70, 71, 5, 1, 0, 0, 71, 76, 3, 14, 7, 0, 72, 73, 5, 3, 0, 0, 73, 75, 3, 14, 7, 0, 74, 72, 1, 0, 0, 0, 75, 78, 1, 0, 0, 0, 76, 74, 1, 0, 0, 0, 76, 77, 1, 0, 0, 0, 77, 79, 1, 0, 0, 0, 78, 76, 1, 0, 0, 0, 79, 80, 5, 2, 0, 0, 80, 9, 1, 0, 0, 0, 81, 82, 7, 0, 0, 0, 82, 11, 1, 0, 0, 0, 83, 84, 7, 1, 0, 0, 84, 13, 1, 0, 0, 0, 85, 86, 7, 2, 0, 0, 86, 15, 1, 0, 0, 0, 7, 19, 26, 31, 37, 54, 65, 76]
@@ -0,0 +1,21 @@
1
+ LPAREN=1
2
+ RPAREN=2
3
+ COMMA=3
4
+ VARIABLE=4
5
+ LEFT_KW=5
6
+ RIGHT_KW=6
7
+ STAY_KW=7
8
+ BLANK_KW=8
9
+ ALPHANUM=9
10
+ ID=10
11
+ COMMENT=11
12
+ MULTI_COMMENT=12
13
+ WS=13
14
+ NEWLINE=14
15
+ '('=1
16
+ ')'=2
17
+ ','=3
18
+ 'LEFT'=5
19
+ 'RIGHT'=6
20
+ 'STAY'=7
21
+ 'BLANK'=8