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.
- varphi_devkit-3.0.0/PKG-INFO +121 -0
- varphi_devkit-3.0.0/README.md +111 -0
- varphi_devkit-3.0.0/pyproject.toml +22 -0
- varphi_devkit-2.0.6/pyproject.toml → varphi_devkit-3.0.0/pyproject.toml.orig +4 -4
- {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/__init__.py +17 -12
- varphi_devkit-3.0.0/src/varphi_devkit/compiler.py +191 -0
- {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/exceptions.py +32 -0
- varphi_devkit-3.0.0/src/varphi_devkit/models.py +65 -0
- varphi_devkit-3.0.0/src/varphi_devkit/parser/Varphi.interp +49 -0
- varphi_devkit-3.0.0/src/varphi_devkit/parser/Varphi.tokens +22 -0
- varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.interp +62 -0
- varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.py +103 -0
- varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiLexer.tokens +22 -0
- {varphi_devkit-2.0.6 → varphi_devkit-3.0.0}/src/varphi_devkit/parser/VarphiListener.py +25 -19
- varphi_devkit-3.0.0/src/varphi_devkit/parser/VarphiParser.py +660 -0
- varphi_devkit-2.0.6/PKG-INFO +0 -10
- varphi_devkit-2.0.6/README.md +0 -0
- varphi_devkit-2.0.6/src/varphi_devkit/compiler.py +0 -185
- varphi_devkit-2.0.6/src/varphi_devkit/parser/Varphi.interp +0 -47
- varphi_devkit-2.0.6/src/varphi_devkit/parser/Varphi.tokens +0 -21
- varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.interp +0 -59
- varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.py +0 -1076
- varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiLexer.tokens +0 -21
- varphi_devkit-2.0.6/src/varphi_devkit/parser/VarphiParser.py +0 -1379
- {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 = "
|
|
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.
|
|
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
|
|
22
|
-
"pytest
|
|
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 `
|
|
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
|
-
|
|
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
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
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]
|