varphi-devkit 2.0.6__tar.gz → 3.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 (25) hide show
  1. varphi_devkit-3.0.0/PKG-INFO +121 -0
  2. varphi_devkit-3.0.0/README.md +111 -0
  3. varphi_devkit-3.0.0/pyproject.toml +22 -0
  4. varphi_devkit-2.0.6/pyproject.toml → varphi_devkit-3.0.0/pyproject.toml.orig +4 -4
  5. {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/__init__.py +17 -12
  6. varphi_devkit-3.0.0/src/varphi_devkit/compiler.py +191 -0
  7. {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/exceptions.py +32 -0
  8. varphi_devkit-3.0.0/src/varphi_devkit/models.py +65 -0
  9. varphi_devkit-3.0.0/src/varphi_devkit/parser/Varphi.interp +49 -0
  10. varphi_devkit-3.0.0/src/varphi_devkit/parser/Varphi.tokens +22 -0
  11. varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.interp +62 -0
  12. varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.py +103 -0
  13. varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.tokens +22 -0
  14. {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/parser/VarphiListener.py +25 -19
  15. varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiParser.py +660 -0
  16. varphi_devkit-2.0.6/PKG-INFO +0 -10
  17. varphi_devkit-2.0.6/README.md +0 -0
  18. varphi_devkit-2.0.6/src/varphi_devkit/compiler.py +0 -185
  19. varphi_devkit-2.0.6/src/varphi_devkit/parser/Varphi.interp +0 -47
  20. varphi_devkit-2.0.6/src/varphi_devkit/parser/Varphi.tokens +0 -21
  21. varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.interp +0 -59
  22. varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.py +0 -1076
  23. varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.tokens +0 -21
  24. varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiParser.py +0 -1379
  25. {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/parser/__init__.py +0 -0
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.3
2
+ Name: varphi-devkit
3
+ Version: 3.0.0
4
+ Summary: A framework for building compilers, interpreters, and analysis tools for the Varphi language.
5
+ Author: Varphi
6
+ Author-email: Varphi <support@varphi-lang.com>
7
+ Requires-Dist: antlr4-python3-runtime==4.13.2
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+
11
+ # The Varphi Compiler Development Kit
12
+
13
+ This is the official frontend parsing and compiler development kit for the Varphi programming language.
14
+
15
+ The devkit is a target-language-agnostic frontent that handles lexical analysis, syntax parsing, semantic validation, and intermediate representation (IR) generation. It is designed so that downstream developers can write backend compilers (e.g., Varphi-to-Python, Varphi-to-C, ...) without needing to worry about all the "dirty work" of compilers, like lexing and parsing.
16
+
17
+ ---
18
+
19
+ ## 📦 Installation
20
+
21
+ Assuming you are using a standard Python environment or a modern manager like `uv`:
22
+
23
+ ```
24
+ # For pip
25
+ pip install varphi-devkit
26
+ # For uv
27
+ uv pip install varphi-devkit
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Quick Start: Building a Downstream Compiler
33
+
34
+ Building a Varphi compiler requires inheriting from the `VarphiCompiler` base class and implementing a single method: `_generate_compiled_program()`.
35
+
36
+ ```python
37
+ from varphi_devkit import VarphiCompiler
38
+
39
+ class VarphiToMyLangCompiler(VarphiCompiler):
40
+
41
+ def _generate_compiled_program(self) -> str:
42
+ # By the time this method is called, the devkit has already parsed, validated, and sorted the Varphi source code.
43
+
44
+ # self.states: A set of all state names (strings) discovered in the code.
45
+ print(f"Total states: {len(self.states)}")
46
+
47
+ # self.initial_state: The entry state (string).
48
+ print(f"Entry point: {self.initial_state}")
49
+
50
+ # self._tape_count: The number of tapes in this machine (int).
51
+ print(f"Tape count: {self._tape_count}")
52
+
53
+ # self.ir: A dictionary mapping state names to a pre-sorted list of VarphiTransitions.
54
+ # The VarphiTransitions are sorted in non-decreasing order of specificity
55
+ for state_name, transitions in self.ir.items():
56
+ for t in transitions:
57
+ # Code generation logic goes here!
58
+ pass
59
+
60
+ return "Compilation Complete!" # You would return your compiled program here
61
+
62
+ # Usage
63
+ compiler = VarphiToMyLangCompiler()
64
+ with open("machine.vp", "r") as f:
65
+ compiled_code = compiler.compile(f.read())
66
+ ```
67
+
68
+ ---
69
+
70
+ ## The Intermediate Representation (IR)
71
+
72
+ The devkit translates raw `.vp` source text into a strictly typed IR. Every transition rule in the user's source code is mapped to a `VarphiTransition` object.
73
+
74
+ Because downstream compilers receive this IR *after* the Devkit has validated it, it has already been totally validated, saving you development time/effort.
75
+
76
+ ### `VarphiTransition`
77
+
78
+ ```python
79
+ @dataclass(frozen=True)
80
+ class VarphiTransition:
81
+ current_state: str
82
+ read_symbols: tuple[ReadWriteTupleElement, ...]
83
+ next_state: str
84
+ write_symbols: tuple[ReadWriteTupleElement, ...]
85
+ shift_directions: tuple[Direction, ...]
86
+ line_number: int
87
+ specificity: tuple[int, int] # (unique variables, total variables)
88
+ ```
89
+
90
+ ---
91
+
92
+ ## The Specificity Engine
93
+
94
+ Varphi is a nondeterministic language with pattern matching. When multiple rules match a tape state, the machine must choose the "most specific" rule, or stochastically branch if there is a tie.
95
+
96
+ The Devkit calculates a specificity score for every transition, accessible through the `specificity` of a `VarphiTransition` object. Its type is a two-element tuple, where the first element gives the number of unique variables in the transition rule and the second gives the total number of variables (including dupicates) in the transition rule.
97
+
98
+ Thus, a rule containing all literals scores lower (i.e., `(0, 0)`) than one containing variables.
99
+
100
+ Before calling your compiler's generation method, the devkit groups all transitions by state and sorts them in non-decreasing order of specificity. Downstream runtimes can simply iterate over a state's transitions, gather the applicable transitions that have the lowest specificity score, then stop once the specificty score increases, which is guaranteed to run in $O(n)$ time, where $n$ is the number of transition rules for a particular state.
101
+
102
+ ---
103
+
104
+ ## Validation and Error Handling
105
+
106
+ The devkit intercepts and formats all ANTLR4 parser errors, throwing descriptive exceptions. You never have to write validation logic in your downstream compiler.
107
+
108
+ * `VarphiGlobalTapeCountError`: Thrown if any line uses a different number of tapes than the first transition line.
109
+ * `VarphiTransitionInconsistentTapeCountError`: Thrown if a single rule attempts to read a different number of tape symbols than it writes.
110
+ * `VarphiUndefinedVariableError`: Thrown if a variable is used in the write tuple without being bound in the read tuple first.
111
+ * `VarphiUnknownSymbolError` & `VarphiUnknownDirectionError`: Lexer fallbacks for unrecognized tokens.
112
+
113
+ ---
114
+
115
+ ## Contributing
116
+
117
+ When modifying the ANTLR4 grammar (`grammar/Varphi.g4`), ensure you regenerate the parser before running the test suite:
118
+
119
+ 1. Modify `.g4` file.
120
+ 2. Run `antlr4 -Dlanguage=Python3 grammar/Varphi.g4 -o src/varphi_devkit/parser/`
121
+ 3. Run the test suite: `pytest tests/`
@@ -0,0 +1,111 @@
1
+ # The Varphi Compiler Development Kit
2
+
3
+ This is the official frontend parsing and compiler development kit for the Varphi programming language.
4
+
5
+ The devkit is a target-language-agnostic frontent that handles lexical analysis, syntax parsing, semantic validation, and intermediate representation (IR) generation. It is designed so that downstream developers can write backend compilers (e.g., Varphi-to-Python, Varphi-to-C, ...) without needing to worry about all the "dirty work" of compilers, like lexing and parsing.
6
+
7
+ ---
8
+
9
+ ## 📦 Installation
10
+
11
+ Assuming you are using a standard Python environment or a modern manager like `uv`:
12
+
13
+ ```
14
+ # For pip
15
+ pip install varphi-devkit
16
+ # For uv
17
+ uv pip install varphi-devkit
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Quick Start: Building a Downstream Compiler
23
+
24
+ Building a Varphi compiler requires inheriting from the `VarphiCompiler` base class and implementing a single method: `_generate_compiled_program()`.
25
+
26
+ ```python
27
+ from varphi_devkit import VarphiCompiler
28
+
29
+ class VarphiToMyLangCompiler(VarphiCompiler):
30
+
31
+ def _generate_compiled_program(self) -> str:
32
+ # By the time this method is called, the devkit has already parsed, validated, and sorted the Varphi source code.
33
+
34
+ # self.states: A set of all state names (strings) discovered in the code.
35
+ print(f"Total states: {len(self.states)}")
36
+
37
+ # self.initial_state: The entry state (string).
38
+ print(f"Entry point: {self.initial_state}")
39
+
40
+ # self._tape_count: The number of tapes in this machine (int).
41
+ print(f"Tape count: {self._tape_count}")
42
+
43
+ # self.ir: A dictionary mapping state names to a pre-sorted list of VarphiTransitions.
44
+ # The VarphiTransitions are sorted in non-decreasing order of specificity
45
+ for state_name, transitions in self.ir.items():
46
+ for t in transitions:
47
+ # Code generation logic goes here!
48
+ pass
49
+
50
+ return "Compilation Complete!" # You would return your compiled program here
51
+
52
+ # Usage
53
+ compiler = VarphiToMyLangCompiler()
54
+ with open("machine.vp", "r") as f:
55
+ compiled_code = compiler.compile(f.read())
56
+ ```
57
+
58
+ ---
59
+
60
+ ## The Intermediate Representation (IR)
61
+
62
+ The devkit translates raw `.vp` source text into a strictly typed IR. Every transition rule in the user's source code is mapped to a `VarphiTransition` object.
63
+
64
+ Because downstream compilers receive this IR *after* the Devkit has validated it, it has already been totally validated, saving you development time/effort.
65
+
66
+ ### `VarphiTransition`
67
+
68
+ ```python
69
+ @dataclass(frozen=True)
70
+ class VarphiTransition:
71
+ current_state: str
72
+ read_symbols: tuple[ReadWriteTupleElement, ...]
73
+ next_state: str
74
+ write_symbols: tuple[ReadWriteTupleElement, ...]
75
+ shift_directions: tuple[Direction, ...]
76
+ line_number: int
77
+ specificity: tuple[int, int] # (unique variables, total variables)
78
+ ```
79
+
80
+ ---
81
+
82
+ ## The Specificity Engine
83
+
84
+ Varphi is a nondeterministic language with pattern matching. When multiple rules match a tape state, the machine must choose the "most specific" rule, or stochastically branch if there is a tie.
85
+
86
+ The Devkit calculates a specificity score for every transition, accessible through the `specificity` of a `VarphiTransition` object. Its type is a two-element tuple, where the first element gives the number of unique variables in the transition rule and the second gives the total number of variables (including dupicates) in the transition rule.
87
+
88
+ Thus, a rule containing all literals scores lower (i.e., `(0, 0)`) than one containing variables.
89
+
90
+ Before calling your compiler's generation method, the devkit groups all transitions by state and sorts them in non-decreasing order of specificity. Downstream runtimes can simply iterate over a state's transitions, gather the applicable transitions that have the lowest specificity score, then stop once the specificty score increases, which is guaranteed to run in $O(n)$ time, where $n$ is the number of transition rules for a particular state.
91
+
92
+ ---
93
+
94
+ ## Validation and Error Handling
95
+
96
+ The devkit intercepts and formats all ANTLR4 parser errors, throwing descriptive exceptions. You never have to write validation logic in your downstream compiler.
97
+
98
+ * `VarphiGlobalTapeCountError`: Thrown if any line uses a different number of tapes than the first transition line.
99
+ * `VarphiTransitionInconsistentTapeCountError`: Thrown if a single rule attempts to read a different number of tape symbols than it writes.
100
+ * `VarphiUndefinedVariableError`: Thrown if a variable is used in the write tuple without being bound in the read tuple first.
101
+ * `VarphiUnknownSymbolError` & `VarphiUnknownDirectionError`: Lexer fallbacks for unrecognized tokens.
102
+
103
+ ---
104
+
105
+ ## Contributing
106
+
107
+ When modifying the ANTLR4 grammar (`grammar/Varphi.g4`), ensure you regenerate the parser before running the test suite:
108
+
109
+ 1. Modify `.g4` file.
110
+ 2. Run `antlr4 -Dlanguage=Python3 grammar/Varphi.g4 -o src/varphi_devkit/parser/`
111
+ 3. Run the test suite: `pytest tests/`
@@ -0,0 +1,22 @@
1
+ [project]
2
+ name = "varphi-devkit"
3
+ version = "3.0.0"
4
+ description = "A framework for building compilers, interpreters, and analysis tools for the Varphi language."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = ["antlr4-python3-runtime==4.13.2"]
8
+
9
+ [[project.authors]]
10
+ name = "Varphi"
11
+ email = "support@varphi-lang.com"
12
+
13
+ [build-system]
14
+ requires = ["uv_build>=0.9.18,<0.10.0"]
15
+ build-backend = "uv_build"
16
+
17
+ [dependency-groups]
18
+ dev = [
19
+ "antlr4-tools>=0.2.2",
20
+ "black",
21
+ "pytest",
22
+ ]
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "varphi-devkit"
3
- version = "2.0.6"
3
+ version = "3.0.0"
4
4
  description = "A framework for building compilers, interpreters, and analysis tools for the Varphi language."
5
5
  readme = "README.md"
6
6
  authors = [
7
7
  { name = "Varphi", email = "support@varphi-lang.com" }
8
8
  ]
9
- requires-python = ">=3.13"
9
+ requires-python = ">=3.10"
10
10
  dependencies = [
11
11
  "antlr4-python3-runtime==4.13.2",
12
12
  ]
@@ -18,6 +18,6 @@ build-backend = "uv_build"
18
18
  [dependency-groups]
19
19
  dev = [
20
20
  "antlr4-tools>=0.2.2",
21
- "black>=25.12.0",
22
- "pytest>=9.0.2",
21
+ "black",
22
+ "pytest",
23
23
  ]
@@ -6,17 +6,21 @@ This package handles the complexity of parsing, validation, and variable canonic
6
6
  convenient abstraction layer for implementing custom Varphi backends.
7
7
 
8
8
  **Core API:**
9
- - `VarphiCompiler`: The abstract base class you must subclass. Override `handle_transition` to process logic.
9
+ - `VarphiCompiler`: The abstract base class you must subclass. Override `_generate_compiled_program()` to implement custom logic for a compiler.
10
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
11
  """
18
12
 
19
- from .compiler import VarphiCompiler, VarphiTransition, BLANK, LEFT, RIGHT, STAY
13
+ __version__ = "3.0.0"
14
+
15
+ from .compiler import (
16
+ VarphiCompiler,
17
+ VarphiTransition,
18
+ Direction,
19
+ BuiltinSymbol,
20
+ Variable,
21
+ Character,
22
+ ReadWriteTupleElement,
23
+ )
20
24
  from .exceptions import (
21
25
  VarphiSyntaxError,
22
26
  VarphiTransitionInconsistentTapeCountError,
@@ -27,10 +31,11 @@ from .exceptions import (
27
31
  __all__ = [
28
32
  "VarphiCompiler",
29
33
  "VarphiTransition",
30
- "BLANK",
31
- "LEFT",
32
- "RIGHT",
33
- "STAY",
34
+ "Direction",
35
+ "BuiltinSymbol",
36
+ "Variable",
37
+ "Character",
38
+ "ReadWriteTupleElement",
34
39
  "VarphiSyntaxError",
35
40
  "VarphiTransitionInconsistentTapeCountError",
36
41
  "VarphiGlobalTapeCountError",
@@ -0,0 +1,191 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Optional
3
+ from collections import defaultdict
4
+ from antlr4 import InputStream, CommonTokenStream, ParseTreeWalker
5
+
6
+ from .parser import VarphiLexer, VarphiParser, VarphiListener
7
+ from .models import (
8
+ ReadWriteTupleElement,
9
+ Character,
10
+ BuiltinSymbol,
11
+ Variable,
12
+ Direction,
13
+ VarphiTransition,
14
+ )
15
+ from .exceptions import (
16
+ VarphiErrorListener,
17
+ VarphiTransitionInconsistentTapeCountError,
18
+ VarphiGlobalTapeCountError,
19
+ VarphiUndefinedVariableError,
20
+ VarphiInvalidUnicodeError,
21
+ VarphiUnknownSymbolError,
22
+ VarphiUnknownDirectionError,
23
+ )
24
+
25
+
26
+ class VarphiCompiler(VarphiListener, ABC):
27
+ """
28
+ An abstract Varphi compiler and IR generator.
29
+
30
+ Concrete subclasses must implement `_generate_compiled_program()`.
31
+ When called, the subclass can safely access:
32
+ - self.states (set[str]): The names of all states in the user's source Varphi program
33
+ - self.initial_state (str): The name of the first state encountered.
34
+ - self.ir (dict[str, list[VarphiTransition]]): A map of states to their transitions, sorted by specificity score.
35
+ """
36
+
37
+ _tape_count: Optional[int]
38
+ _raw_transitions: list[VarphiTransition]
39
+
40
+ states: set[str]
41
+ initial_state: Optional[str]
42
+ ir: dict[str, list[VarphiTransition]]
43
+
44
+ def __init__(self):
45
+ """Initialize this compiler."""
46
+ self._tape_count = None
47
+ self._raw_transitions = []
48
+ self.states = set()
49
+ self.initial_state = None
50
+ self.ir = {}
51
+
52
+ @abstractmethod
53
+ def _generate_compiled_program(self) -> str:
54
+ """Generate the compiled program."""
55
+ pass
56
+
57
+ def compile(self, program: str) -> str:
58
+ """Compile a Varphi program."""
59
+ self._tape_count = None
60
+ self._raw_transitions = []
61
+ self.states = set()
62
+ self.initial_state = None
63
+ self.ir = {}
64
+
65
+ input_stream = InputStream(program)
66
+ error_listener = VarphiErrorListener()
67
+
68
+ lexer = VarphiLexer(input_stream)
69
+ lexer.removeErrorListeners()
70
+ lexer.addErrorListener(error_listener)
71
+
72
+ token_stream = CommonTokenStream(lexer)
73
+ parser = VarphiParser(token_stream)
74
+ parser.removeErrorListeners()
75
+ parser.addErrorListener(error_listener)
76
+
77
+ tree = parser.program()
78
+ walker = ParseTreeWalker()
79
+ walker.walk(self, tree)
80
+
81
+ self._build_ir()
82
+
83
+ return self._generate_compiled_program()
84
+
85
+ def _build_ir(self) -> None:
86
+ """Groups parsed transitions by state and sorts them by specificity."""
87
+ grouped_transitions: defaultdict[str, list[VarphiTransition]] = defaultdict(
88
+ list
89
+ )
90
+ for transition in self._raw_transitions:
91
+ grouped_transitions[transition.current_state].append(transition)
92
+
93
+ for state, transitions in grouped_transitions.items():
94
+ transitions.sort(key=lambda transition: transition.specificity)
95
+ self.ir[state] = transitions
96
+
97
+ def enterTransition(self, ctx: VarphiParser.TransitionContext) -> None:
98
+ """Process and add a transition to the raw IR."""
99
+ current_state = ctx.current_state.getText()
100
+ next_state = ctx.next_state.getText()
101
+ self.states.add(current_state)
102
+ self.states.add(next_state)
103
+
104
+ if self.initial_state is None:
105
+ self.initial_state = current_state
106
+
107
+ next_variable_number = 0
108
+ variable_name_to_variable_object = {}
109
+
110
+ def extract_symbol(
111
+ symbol_ctx, variable_undefined_ok: bool = True
112
+ ) -> ReadWriteTupleElement:
113
+ nonlocal next_variable_number, variable_name_to_variable_object
114
+
115
+ if symbol_ctx.VARIABLE():
116
+ variable_name = symbol_ctx.VARIABLE().getText()
117
+ if variable_name in variable_name_to_variable_object:
118
+ return variable_name_to_variable_object[variable_name]
119
+ if not variable_undefined_ok:
120
+ raise VarphiUndefinedVariableError(symbol_ctx, variable_name)
121
+ variable_name_to_variable_object[variable_name] = Variable(
122
+ next_variable_number
123
+ )
124
+ next_variable_number += 1
125
+ return variable_name_to_variable_object[variable_name]
126
+
127
+ if symbol_ctx.BLANK_KW():
128
+ return BuiltinSymbol.BLANK
129
+
130
+ if symbol_ctx.INT():
131
+ unicode_val = int(symbol_ctx.INT().getText())
132
+ if not (0 <= unicode_val <= 0x10FFFF):
133
+ raise VarphiInvalidUnicodeError(symbol_ctx, unicode_val)
134
+ return Character(chr(unicode_val))
135
+
136
+ if symbol_ctx.CHAR_LITERAL():
137
+ # Strip the outer single quotes (e.g., "'a'" becomes "a")
138
+ text = symbol_ctx.CHAR_LITERAL().getText()[1:-1]
139
+ return Character(text)
140
+
141
+ raise VarphiUnknownSymbolError(symbol_ctx, symbol_ctx.getText())
142
+
143
+ def extract_direction(direction_ctx) -> Direction:
144
+ if direction_ctx.LEFT_KW():
145
+ return Direction.LEFT
146
+ if direction_ctx.RIGHT_KW():
147
+ return Direction.RIGHT
148
+ if direction_ctx.STAY_KW():
149
+ return Direction.STAY
150
+ raise VarphiUnknownDirectionError(direction_ctx, direction_ctx.getText())
151
+
152
+ read_ctx = ctx.read_symbols()
153
+ reads = tuple(extract_symbol(s) for s in read_ctx.symbol()) if read_ctx else ()
154
+
155
+ write_ctx = ctx.write_symbols()
156
+ writes = (
157
+ tuple(
158
+ extract_symbol(s, variable_undefined_ok=False)
159
+ for s in write_ctx.symbol()
160
+ )
161
+ if write_ctx
162
+ else ()
163
+ )
164
+
165
+ shift_ctx = ctx.shift_directions()
166
+ shifts = (
167
+ tuple(extract_direction(d) for d in shift_ctx.direction())
168
+ if shift_ctx
169
+ else ()
170
+ )
171
+
172
+ if len(writes) != len(reads) or len(shifts) != len(reads):
173
+ raise VarphiTransitionInconsistentTapeCountError(
174
+ ctx, len(reads), len(writes), len(shifts)
175
+ )
176
+
177
+ current_tape_count = len(reads)
178
+ if self._tape_count is None:
179
+ self._tape_count = current_tape_count
180
+ elif current_tape_count != self._tape_count:
181
+ raise VarphiGlobalTapeCountError(ctx, self._tape_count, current_tape_count)
182
+
183
+ transition = VarphiTransition(
184
+ current_state=current_state,
185
+ read_symbols=reads,
186
+ next_state=next_state,
187
+ write_symbols=writes,
188
+ shift_directions=shifts,
189
+ line_number=ctx.start.line,
190
+ )
191
+ self._raw_transitions.append(transition)
@@ -120,6 +120,38 @@ class VarphiUndefinedVariableError(VarphiSyntaxError):
120
120
  super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
121
121
 
122
122
 
123
+ class VarphiInvalidSymbolLengthError(VarphiSyntaxError):
124
+ """Raised when an ID token used as a tape symbol is longer than one character."""
125
+
126
+ def __init__(self, ctx, symbol):
127
+ msg = f"invalid symbol '{symbol}': tape symbols must be exactly one character."
128
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
129
+
130
+
131
+ class VarphiInvalidUnicodeError(VarphiSyntaxError):
132
+ """Raised when an integer token falls outside the valid unicode code point range."""
133
+
134
+ def __init__(self, ctx, value):
135
+ msg = f"invalid unicode code point '{value}': must be between 0 and 1114111 (0x10FFFF)."
136
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
137
+
138
+
139
+ class VarphiUnknownSymbolError(VarphiSyntaxError):
140
+ """Raised when an unrecognized token type is encountered in a read/write tuple."""
141
+
142
+ def __init__(self, ctx, symbol_text):
143
+ msg = f"unknown symbol: '{symbol_text}' is not a valid tape symbol."
144
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
145
+
146
+
147
+ class VarphiUnknownDirectionError(VarphiSyntaxError):
148
+ """Raised when an unrecognized token type is encountered in a shift tuple."""
149
+
150
+ def __init__(self, ctx, direction_text):
151
+ msg = f"unknown direction: '{direction_text}'. Must be LEFT, RIGHT, or STAY."
152
+ super().__init__(None, ctx.start, ctx.start.line, ctx.start.column, msg)
153
+
154
+
123
155
  class VarphiErrorListener(ErrorListener):
124
156
  """Custom ANTLR ErrorListener that converts syntax errors into VarphiSyntaxErrors."""
125
157
 
@@ -0,0 +1,65 @@
1
+ from dataclasses import dataclass, field
2
+ from enum import Enum, auto
3
+
4
+
5
+ class ReadWriteTupleElement:
6
+ """Base class for all elements that can appear in the read or write tuple of a Varphi transition."""
7
+
8
+ pass
9
+
10
+
11
+ @dataclass(frozen=True, eq=True)
12
+ class Character(ReadWriteTupleElement):
13
+ """A concrete unicode character."""
14
+
15
+ value: str
16
+
17
+ def __post_init__(self):
18
+ if not isinstance(self.value, str) or len(self.value) != 1:
19
+ raise ValueError(
20
+ f"Character must be exactly 1 string character, got {self.value!r}"
21
+ )
22
+
23
+
24
+ class BuiltinSymbol(ReadWriteTupleElement, Enum):
25
+ """A builtin Varphi symbol."""
26
+
27
+ BLANK = auto()
28
+
29
+
30
+ @dataclass(frozen=True, eq=True)
31
+ class Variable(ReadWriteTupleElement):
32
+ """A variable ID."""
33
+
34
+ id: int
35
+
36
+
37
+ class Direction(Enum):
38
+ """A head direction."""
39
+
40
+ LEFT = auto()
41
+ RIGHT = auto()
42
+ STAY = auto()
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class VarphiTransition:
47
+ """A transition (logically, a single line) in a Varphi program."""
48
+
49
+ current_state: str
50
+ read_symbols: tuple[ReadWriteTupleElement, ...]
51
+ next_state: str
52
+ write_symbols: tuple[ReadWriteTupleElement, ...]
53
+ shift_directions: tuple[Direction, ...]
54
+ line_number: int
55
+ specificity: tuple[int, int] = field(
56
+ init=False
57
+ ) # (unique variables, total variables)
58
+
59
+ def __post_init__(self):
60
+ variables = [s for s in self.read_symbols if isinstance(s, Variable)]
61
+ unique_variables = len(set(variables))
62
+ total_variables = len(variables)
63
+
64
+ # Because the dataclass is frozen, we must bypass normal assignment
65
+ object.__setattr__(self, "specificity", (unique_variables, total_variables))
@@ -0,0 +1,49 @@
1
+ token literal names:
2
+ null
3
+ '('
4
+ ')'
5
+ ','
6
+ null
7
+ null
8
+ 'LEFT'
9
+ 'RIGHT'
10
+ 'STAY'
11
+ 'BLANK'
12
+ null
13
+ null
14
+ null
15
+ null
16
+ null
17
+ null
18
+
19
+ token symbolic names:
20
+ null
21
+ LPAREN
22
+ RPAREN
23
+ COMMA
24
+ CHAR_LITERAL
25
+ VARIABLE
26
+ LEFT_KW
27
+ RIGHT_KW
28
+ STAY_KW
29
+ BLANK_KW
30
+ INT
31
+ ID
32
+ COMMENT
33
+ MULTI_COMMENT
34
+ WHITESPACE
35
+ NEWLINE
36
+
37
+ rule names:
38
+ program
39
+ transition
40
+ read_symbols
41
+ write_symbols
42
+ shift_directions
43
+ state_id
44
+ symbol
45
+ direction
46
+
47
+
48
+ atn:
49
+ [4, 1, 15, 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, 6, 11, 2, 0, 4, 5, 9, 10, 1, 0, 6, 8, 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, 15, 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, 15, 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, 15, 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]