oaknut-basic 12.17.0__tar.gz → 12.18.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.
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/PKG-INFO +15 -14
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/README.md +11 -11
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/pyproject.toml +16 -10
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/__init__.py +1 -1
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/cli.py +2 -2
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/detect.py +2 -6
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/dialect.py +1 -3
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/exceptions.py +2 -6
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/float5.py +1 -3
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/tokeniser.py +1 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/PKG-INFO +15 -14
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/SOURCES.txt +1 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/requires.txt +2 -2
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_data_cli.py +1 -3
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_detect.py +1 -1
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_greedy_crunch.py +1 -3
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_keyword_coverage.py +3 -1
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_numbering.py +2 -2
- oaknut_basic-12.18.0/tests/test_packaging.py +56 -0
- oaknut_basic-12.18.0/tests/test_rom_golden.py +100 -0
- oaknut_basic-12.18.0/tests/test_rom_golden_detokenise.py +54 -0
- oaknut_basic-12.17.0/tests/test_rom_golden.py +0 -100
- oaknut_basic-12.17.0/tests/test_rom_golden_detokenise.py +0 -51
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/LICENSE +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/setup.cfg +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/datafile.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/detokeniser.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/linenumber.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/numbering.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/scanner.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut/basic/tokens.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/dependency_links.txt +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/entry_points.txt +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/top_level.txt +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_basic.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_basic_v.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_cli.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_crunch_rules.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_datafile.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_detokeniser.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_float5.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_linenumber.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_scanner.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_tokeniser.py +0 -0
- {oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/tests/test_tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oaknut-basic
|
|
3
|
-
Version: 12.
|
|
3
|
+
Version: 12.18.0
|
|
4
4
|
Summary: BBC BASIC tools: program tokeniser/de-tokeniser and PRINT#/INPUT# data-file reader/writer
|
|
5
5
|
Author-email: Robert Smallshire <robert@smallshire.org.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,8 +17,9 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: oaknut-exception>=10.0
|
|
19
19
|
Requires-Dist: oaknut-codecs>=12.7
|
|
20
|
-
|
|
21
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: click>=8.1.7
|
|
21
|
+
Requires-Dist: asyoulikeit>=1.5.1
|
|
22
|
+
Requires-Dist: oaknut-cli>=10.0
|
|
22
23
|
Dynamic: license-file
|
|
23
24
|
|
|
24
25
|
<p align="center">
|
|
@@ -67,12 +68,12 @@ and a data file round-trips through Python values **byte-for-byte**.
|
|
|
67
68
|
|
|
68
69
|
## Installation
|
|
69
70
|
|
|
70
|
-
Install
|
|
71
|
-
library
|
|
71
|
+
Install as a tool for the `oaknut-basic` command, or add it to a project for
|
|
72
|
+
the library:
|
|
72
73
|
|
|
73
74
|
```
|
|
74
|
-
uv tool install
|
|
75
|
-
uv add oaknut-basic
|
|
75
|
+
uv tool install oaknut-basic # the command-line tool
|
|
76
|
+
uv add oaknut-basic # the importable library
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
`pip` works identically with the same names. `oaknut-basic` requires Python
|
|
@@ -130,9 +131,9 @@ Programs are handled by the functions `tokenise`, `detokenise`, and
|
|
|
130
131
|
```python
|
|
131
132
|
from oaknut.basic import tokenise, detokenise
|
|
132
133
|
|
|
133
|
-
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n')
|
|
134
|
-
listing = detokenise(program)
|
|
135
|
-
assert tokenise(detokenise(program)) == program
|
|
134
|
+
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n') # str -> bytes
|
|
135
|
+
listing = detokenise(program) # bytes -> str
|
|
136
|
+
assert tokenise(detokenise(program)) == program # byte-exact
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
When the program lives in a disc image, prefer the path-object wrappers
|
|
@@ -149,12 +150,12 @@ polymorphic `write` picks the record type from the Python value; typed
|
|
|
149
150
|
from oaknut.basic import datafile
|
|
150
151
|
|
|
151
152
|
with datafile.open("scores.dat", "w") as f:
|
|
152
|
-
f.write("ALICE")
|
|
153
|
-
f.write(42)
|
|
154
|
-
f.write(3.5)
|
|
153
|
+
f.write("ALICE") # str -> string record
|
|
154
|
+
f.write(42) # int -> integer record
|
|
155
|
+
f.write(3.5) # float -> real record
|
|
155
156
|
|
|
156
157
|
with datafile.open("scores.dat", "r") as f:
|
|
157
|
-
for value in f:
|
|
158
|
+
for value in f: # yields "ALICE", 42, 3.5
|
|
158
159
|
print(value)
|
|
159
160
|
```
|
|
160
161
|
|
|
@@ -44,12 +44,12 @@ and a data file round-trips through Python values **byte-for-byte**.
|
|
|
44
44
|
|
|
45
45
|
## Installation
|
|
46
46
|
|
|
47
|
-
Install
|
|
48
|
-
library
|
|
47
|
+
Install as a tool for the `oaknut-basic` command, or add it to a project for
|
|
48
|
+
the library:
|
|
49
49
|
|
|
50
50
|
```
|
|
51
|
-
uv tool install
|
|
52
|
-
uv add oaknut-basic
|
|
51
|
+
uv tool install oaknut-basic # the command-line tool
|
|
52
|
+
uv add oaknut-basic # the importable library
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
`pip` works identically with the same names. `oaknut-basic` requires Python
|
|
@@ -107,9 +107,9 @@ Programs are handled by the functions `tokenise`, `detokenise`, and
|
|
|
107
107
|
```python
|
|
108
108
|
from oaknut.basic import tokenise, detokenise
|
|
109
109
|
|
|
110
|
-
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n')
|
|
111
|
-
listing = detokenise(program)
|
|
112
|
-
assert tokenise(detokenise(program)) == program
|
|
110
|
+
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n') # str -> bytes
|
|
111
|
+
listing = detokenise(program) # bytes -> str
|
|
112
|
+
assert tokenise(detokenise(program)) == program # byte-exact
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
When the program lives in a disc image, prefer the path-object wrappers
|
|
@@ -126,12 +126,12 @@ polymorphic `write` picks the record type from the Python value; typed
|
|
|
126
126
|
from oaknut.basic import datafile
|
|
127
127
|
|
|
128
128
|
with datafile.open("scores.dat", "w") as f:
|
|
129
|
-
f.write("ALICE")
|
|
130
|
-
f.write(42)
|
|
131
|
-
f.write(3.5)
|
|
129
|
+
f.write("ALICE") # str -> string record
|
|
130
|
+
f.write(42) # int -> integer record
|
|
131
|
+
f.write(3.5) # float -> real record
|
|
132
132
|
|
|
133
133
|
with datafile.open("scores.dat", "r") as f:
|
|
134
|
-
for value in f:
|
|
134
|
+
for value in f: # yields "ALICE", 42, 3.5
|
|
135
135
|
print(value)
|
|
136
136
|
```
|
|
137
137
|
|
|
@@ -20,16 +20,22 @@ classifiers = [
|
|
|
20
20
|
"Programming Language :: Python :: 3.12",
|
|
21
21
|
"Programming Language :: Python :: 3.13",
|
|
22
22
|
]
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
dependencies
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
# This package ships the standalone `oaknut-basic` console script (see
|
|
24
|
+
# [project.scripts] below), and that script's module imports Click,
|
|
25
|
+
# asyoulikeit and oaknut.cli at load time. A console script is installed
|
|
26
|
+
# unconditionally, so those must be unconditional dependencies too —
|
|
27
|
+
# otherwise `pip install oaknut-basic` / `uvx oaknut-basic` yields an
|
|
28
|
+
# executable that dies with ModuleNotFoundError before it runs. The
|
|
29
|
+
# tokeniser and de-tokeniser also raise categorised errors from the
|
|
30
|
+
# shared oaknut-exception hierarchy so the CLI boundary can render them
|
|
31
|
+
# without a traceback.
|
|
32
|
+
dependencies = [
|
|
33
|
+
"oaknut-exception>=10.0",
|
|
34
|
+
"oaknut-codecs>=12.7",
|
|
35
|
+
"click>=8.1.7",
|
|
36
|
+
"asyoulikeit>=1.5.1",
|
|
37
|
+
"oaknut-cli>=10.0",
|
|
38
|
+
]
|
|
33
39
|
|
|
34
40
|
[project.scripts]
|
|
35
41
|
oaknut-basic = "oaknut.basic.cli:cli"
|
|
@@ -217,8 +217,8 @@ def _listing_to_bytes(listing: str, encoding: str) -> bytes:
|
|
|
217
217
|
type=click.Choice(["rom", "greedy"]),
|
|
218
218
|
default="rom",
|
|
219
219
|
show_default=True,
|
|
220
|
-
help=
|
|
221
|
-
|
|
220
|
+
help='Which tokeniser to emulate. "rom" is byte-exact to the BBC BASIC '
|
|
221
|
+
'ROM. Use "greedy" to reproduce the greedier third-party tokeniser '
|
|
222
222
|
"behind some early-1980s commercial programs, so their de-tokenised "
|
|
223
223
|
"source re-tokenises byte-identically.",
|
|
224
224
|
)
|
|
@@ -162,8 +162,7 @@ def detect(data: bytes | bytearray) -> Detection:
|
|
|
162
162
|
if line_count == 0:
|
|
163
163
|
return Detection(
|
|
164
164
|
Verdict.NOT_BASIC,
|
|
165
|
-
f"&0D &{marker:02X} at offset 0 — terminator with no lines, "
|
|
166
|
-
"not a program",
|
|
165
|
+
f"&0D &{marker:02X} at offset 0 — terminator with no lines, not a program",
|
|
167
166
|
)
|
|
168
167
|
program_length = pos + 2
|
|
169
168
|
trailing = n - program_length
|
|
@@ -195,10 +194,7 @@ def detect(data: bytes | bytearray) -> Detection:
|
|
|
195
194
|
line_no = (marker << 8) | data[pos + 2]
|
|
196
195
|
length = data[pos + 3]
|
|
197
196
|
if length < HEADER_LENGTH:
|
|
198
|
-
return broke(
|
|
199
|
-
f"line {line_no} at offset {pos} has length {length} "
|
|
200
|
-
f"(< {HEADER_LENGTH})"
|
|
201
|
-
)
|
|
197
|
+
return broke(f"line {line_no} at offset {pos} has length {length} (< {HEADER_LENGTH})")
|
|
202
198
|
if pos + length > n:
|
|
203
199
|
return broke(f"line {line_no} at offset {pos} runs {length} bytes past end of data")
|
|
204
200
|
|
|
@@ -142,9 +142,7 @@ _BASIC_V_SINGLE_BYTE_OVERRIDES = {
|
|
|
142
142
|
# (so a bare prefix never resolves to AUTO/DELETE/LOAD), then apply the
|
|
143
143
|
# re-purposed single-byte slots.
|
|
144
144
|
_basic_v_single_byte = {
|
|
145
|
-
token: keyword
|
|
146
|
-
for token, keyword in TOKEN_TO_KEYWORD.items()
|
|
147
|
-
if token not in (0xC6, 0xC7, 0xC8)
|
|
145
|
+
token: keyword for token, keyword in TOKEN_TO_KEYWORD.items() if token not in (0xC6, 0xC7, 0xC8)
|
|
148
146
|
}
|
|
149
147
|
_basic_v_single_byte.update(_BASIC_V_SINGLE_BYTE_OVERRIDES)
|
|
150
148
|
|
|
@@ -96,8 +96,7 @@ class LineNumberRangeError(TokeniseError):
|
|
|
96
96
|
|
|
97
97
|
def __init__(self, line_index: int, line_number: int, line_text: str) -> None:
|
|
98
98
|
super().__init__(
|
|
99
|
-
f"line {line_index}: line number {line_number} is out of range "
|
|
100
|
-
f"(0-{MAX_LINE_NUMBER})",
|
|
99
|
+
f"line {line_index}: line number {line_number} is out of range (0-{MAX_LINE_NUMBER})",
|
|
101
100
|
line_index=line_index,
|
|
102
101
|
line_text=line_text,
|
|
103
102
|
)
|
|
@@ -300,10 +299,7 @@ class IntegerRangeError(DataFileError):
|
|
|
300
299
|
"""
|
|
301
300
|
|
|
302
301
|
def __init__(self, value: int) -> None:
|
|
303
|
-
super().__init__(
|
|
304
|
-
f"{value} is out of range for a BBC integer "
|
|
305
|
-
f"(must be -2**31 .. 2**31 - 1)"
|
|
306
|
-
)
|
|
302
|
+
super().__init__(f"{value} is out of range for a BBC integer (must be -2**31 .. 2**31 - 1)")
|
|
307
303
|
self.value = value
|
|
308
304
|
|
|
309
305
|
|
|
@@ -58,9 +58,7 @@ def unpack_float5(packed: bytes) -> float:
|
|
|
58
58
|
return 0.0
|
|
59
59
|
sign_and_msb = packed[1]
|
|
60
60
|
negative = bool(sign_and_msb & 0x80)
|
|
61
|
-
significand = (
|
|
62
|
-
((sign_and_msb | 0x80) << 24) | (packed[2] << 16) | (packed[3] << 8) | packed[4]
|
|
63
|
-
)
|
|
61
|
+
significand = ((sign_and_msb | 0x80) << 24) | (packed[2] << 16) | (packed[3] << 8) | packed[4]
|
|
64
62
|
value = math.ldexp(significand, exponent - _EXPONENT_FOLD)
|
|
65
63
|
return -value if negative else value
|
|
66
64
|
|
|
@@ -72,6 +72,7 @@ Crunch = Literal["rom", "greedy"]
|
|
|
72
72
|
def _split_source_lines(source: str) -> list[str]:
|
|
73
73
|
return LINE_SEPARATOR_RE.split(source)
|
|
74
74
|
|
|
75
|
+
|
|
75
76
|
# Keyword entries grouped by first character, preserving ROM order within
|
|
76
77
|
# each group, so the crunch only scans the relevant group.
|
|
77
78
|
_KEYWORDS_BY_FIRST: dict[str, list[tuple[str, int, int]]] = {}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oaknut-basic
|
|
3
|
-
Version: 12.
|
|
3
|
+
Version: 12.18.0
|
|
4
4
|
Summary: BBC BASIC tools: program tokeniser/de-tokeniser and PRINT#/INPUT# data-file reader/writer
|
|
5
5
|
Author-email: Robert Smallshire <robert@smallshire.org.uk>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,8 +17,9 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
License-File: LICENSE
|
|
18
18
|
Requires-Dist: oaknut-exception>=10.0
|
|
19
19
|
Requires-Dist: oaknut-codecs>=12.7
|
|
20
|
-
|
|
21
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: click>=8.1.7
|
|
21
|
+
Requires-Dist: asyoulikeit>=1.5.1
|
|
22
|
+
Requires-Dist: oaknut-cli>=10.0
|
|
22
23
|
Dynamic: license-file
|
|
23
24
|
|
|
24
25
|
<p align="center">
|
|
@@ -67,12 +68,12 @@ and a data file round-trips through Python values **byte-for-byte**.
|
|
|
67
68
|
|
|
68
69
|
## Installation
|
|
69
70
|
|
|
70
|
-
Install
|
|
71
|
-
library
|
|
71
|
+
Install as a tool for the `oaknut-basic` command, or add it to a project for
|
|
72
|
+
the library:
|
|
72
73
|
|
|
73
74
|
```
|
|
74
|
-
uv tool install
|
|
75
|
-
uv add oaknut-basic
|
|
75
|
+
uv tool install oaknut-basic # the command-line tool
|
|
76
|
+
uv add oaknut-basic # the importable library
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
`pip` works identically with the same names. `oaknut-basic` requires Python
|
|
@@ -130,9 +131,9 @@ Programs are handled by the functions `tokenise`, `detokenise`, and
|
|
|
130
131
|
```python
|
|
131
132
|
from oaknut.basic import tokenise, detokenise
|
|
132
133
|
|
|
133
|
-
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n')
|
|
134
|
-
listing = detokenise(program)
|
|
135
|
-
assert tokenise(detokenise(program)) == program
|
|
134
|
+
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10\n') # str -> bytes
|
|
135
|
+
listing = detokenise(program) # bytes -> str
|
|
136
|
+
assert tokenise(detokenise(program)) == program # byte-exact
|
|
136
137
|
```
|
|
137
138
|
|
|
138
139
|
When the program lives in a disc image, prefer the path-object wrappers
|
|
@@ -149,12 +150,12 @@ polymorphic `write` picks the record type from the Python value; typed
|
|
|
149
150
|
from oaknut.basic import datafile
|
|
150
151
|
|
|
151
152
|
with datafile.open("scores.dat", "w") as f:
|
|
152
|
-
f.write("ALICE")
|
|
153
|
-
f.write(42)
|
|
154
|
-
f.write(3.5)
|
|
153
|
+
f.write("ALICE") # str -> string record
|
|
154
|
+
f.write(42) # int -> integer record
|
|
155
|
+
f.write(3.5) # float -> real record
|
|
155
156
|
|
|
156
157
|
with datafile.open("scores.dat", "r") as f:
|
|
157
|
-
for value in f:
|
|
158
|
+
for value in f: # yields "ALICE", 42, 3.5
|
|
158
159
|
print(value)
|
|
159
160
|
```
|
|
160
161
|
|
|
@@ -58,9 +58,7 @@ class TestEncode:
|
|
|
58
58
|
|
|
59
59
|
def test_encode_pound_sign_uses_acorn(self, tmp_path):
|
|
60
60
|
out = tmp_path / "out.dat"
|
|
61
|
-
result = CliRunner().invoke(
|
|
62
|
-
cli, ["data", "encode", "-", str(out)], input=json.dumps(["£"])
|
|
63
|
-
)
|
|
61
|
+
result = CliRunner().invoke(cli, ["data", "encode", "-", str(out)], input=json.dumps(["£"]))
|
|
64
62
|
assert result.exit_code == 0, result.output
|
|
65
63
|
# 00 (string tag) 01 (length) 60 (pound) — single char, reversal a no-op.
|
|
66
64
|
assert out.read_bytes() == bytes.fromhex("000160")
|
|
@@ -23,7 +23,7 @@ def _program(*lines: tuple[int, bytes]) -> bytes:
|
|
|
23
23
|
|
|
24
24
|
class TestClean:
|
|
25
25
|
def test_tokenised_program_is_basic(self):
|
|
26
|
-
program = tokenise(
|
|
26
|
+
program = tokenise('10 PRINT "HELLO"\n20 GOTO 10')
|
|
27
27
|
result = detect(program)
|
|
28
28
|
assert result.verdict is Verdict.BASIC
|
|
29
29
|
assert result.is_basic
|
|
@@ -61,9 +61,7 @@ _VECTORS = [
|
|
|
61
61
|
# Rule 2 — an FN/PROC name breaks at a FLAG_START keyword (THEN/ELSE).
|
|
62
62
|
(
|
|
63
63
|
"IFA THENPROCWTKEYELSEPROCBKKEY",
|
|
64
|
-
bytes.fromhex(
|
|
65
|
-
"e7 41 20 8c f2 57 54 4b 45 59 45 4c 53 45 50 52 4f 43 42 4b 4b 45 59"
|
|
66
|
-
),
|
|
64
|
+
bytes.fromhex("e7 41 20 8c f2 57 54 4b 45 59 45 4c 53 45 50 52 4f 43 42 4b 4b 45 59"),
|
|
67
65
|
bytes.fromhex("e7 41 20 8c f2 57 54 4b 45 59 8b f2 42 4b 4b 45 59"),
|
|
68
66
|
),
|
|
69
67
|
# Rule 2 — a function keyword embedded in a name is not a break point.
|
|
@@ -66,7 +66,9 @@ def test_every_keyword_tokenises_to_its_token(keyword, token, flags):
|
|
|
66
66
|
assert _body(prefix + keyword) == prefix.encode("latin-1") + bytes([token])
|
|
67
67
|
|
|
68
68
|
|
|
69
|
-
@pytest.mark.parametrize(
|
|
69
|
+
@pytest.mark.parametrize(
|
|
70
|
+
("keyword", "token"), [(kw, t) for kw, t, _f in KEYWORDS], ids=_ids(KEYWORDS)
|
|
71
|
+
)
|
|
70
72
|
def test_every_token_detokenises_to_its_keyword(keyword, token):
|
|
71
73
|
program = b"\x0d\x00\x0a\x05" + bytes([token]) + b"\x0d\xff"
|
|
72
74
|
assert detokenise(program) == f"10{keyword}\n"
|
|
@@ -16,8 +16,8 @@ _DATA_DIRPATH = Path(__file__).parent / "data"
|
|
|
16
16
|
|
|
17
17
|
class TestNumberLines:
|
|
18
18
|
def test_numbers_each_line_from_ten_in_tens(self):
|
|
19
|
-
source =
|
|
20
|
-
assert basic.number_lines(source) ==
|
|
19
|
+
source = 'PRINT "Hello"\nEND'
|
|
20
|
+
assert basic.number_lines(source) == '10 PRINT "Hello"\n20 END'
|
|
21
21
|
|
|
22
22
|
def test_custom_step(self):
|
|
23
23
|
source = "A\nB\nC"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Packaging-metadata guards for the standalone ``oaknut-basic`` command.
|
|
2
|
+
|
|
3
|
+
Unlike the filesystem plugin packages (``oaknut-dfs``/``oaknut-adfs`` …),
|
|
4
|
+
which only contribute ``disc`` subcommands via entry points and ship no
|
|
5
|
+
console script of their own, ``oaknut-basic`` declares its own
|
|
6
|
+
``[project.scripts]`` entry point. A console script that is always
|
|
7
|
+
installed must have everything it imports as an *unconditional* runtime
|
|
8
|
+
dependency, otherwise ``pip install oaknut-basic`` / ``uvx oaknut-basic``
|
|
9
|
+
produces an executable that dies with ``ModuleNotFoundError`` at import
|
|
10
|
+
(see issue #49).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from importlib.metadata import entry_points, requires
|
|
14
|
+
|
|
15
|
+
_DISTRIBUTION = "oaknut-basic"
|
|
16
|
+
|
|
17
|
+
# Everything oaknut/basic/cli.py imports at module load, mapped to the
|
|
18
|
+
# distribution that supplies it.
|
|
19
|
+
_CLI_REQUIREMENTS = ("oaknut-cli", "click", "asyoulikeit")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _unconditional_requirements():
|
|
23
|
+
"""Distribution names required without an ``extra ==`` marker."""
|
|
24
|
+
names = set()
|
|
25
|
+
for requirement in requires(_DISTRIBUTION) or []:
|
|
26
|
+
# "oaknut-cli>=10.0; extra == 'cli'" -> skip; extras are optional.
|
|
27
|
+
spec, _, marker = requirement.partition(";")
|
|
28
|
+
if "extra" in marker:
|
|
29
|
+
continue
|
|
30
|
+
name = (
|
|
31
|
+
spec.strip()
|
|
32
|
+
.split("[")[0]
|
|
33
|
+
.split("<")[0]
|
|
34
|
+
.split(">")[0]
|
|
35
|
+
.split("=")[0]
|
|
36
|
+
.split("!")[0]
|
|
37
|
+
.split("~")[0]
|
|
38
|
+
.strip()
|
|
39
|
+
)
|
|
40
|
+
names.add(name.lower())
|
|
41
|
+
return names
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_console_script_is_declared():
|
|
45
|
+
scripts = entry_points(group="console_scripts")
|
|
46
|
+
assert any(ep.name == "oaknut-basic" for ep in scripts)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_cli_dependencies_are_unconditional():
|
|
50
|
+
unconditional = _unconditional_requirements()
|
|
51
|
+
missing = [name for name in _CLI_REQUIREMENTS if name.lower() not in unconditional]
|
|
52
|
+
assert not missing, (
|
|
53
|
+
f"{_DISTRIBUTION} ships a console script but these imports of "
|
|
54
|
+
f"oaknut.basic.cli are not unconditional dependencies: {missing}. "
|
|
55
|
+
"Promote them out of the [cli] extra into [project.dependencies]."
|
|
56
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Golden tokeniser vectors from the genuine BBC BASIC II ROM.
|
|
2
|
+
|
|
3
|
+
Generated by scripts (basictool's 6502 emulation of the BASIC II ROM)
|
|
4
|
+
and committed as static data: the ROM is the authority for what the
|
|
5
|
+
tokeniser must produce, and this test holds it to that. basictool is
|
|
6
|
+
NOT a test dependency — regenerate with scripts/gen_rom_golden.py if the
|
|
7
|
+
vector set changes.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from oaknut.basic import tokenise
|
|
12
|
+
|
|
13
|
+
# (source after the line number, tokenised body the ROM produces).
|
|
14
|
+
_GOLDEN = [
|
|
15
|
+
('PRINT "Hi"', b' \xf1 "Hi"'),
|
|
16
|
+
("END", b" \xe0"),
|
|
17
|
+
("GOTO 100", b" \xe5 \x8dDd@"),
|
|
18
|
+
("FOR I=1 TO 10 STEP 2", b" \xe3 I=1 \xb8 10 \x88 2"),
|
|
19
|
+
("NEXT I", b" \xed I"),
|
|
20
|
+
("REPEAT", b" \xf5"),
|
|
21
|
+
("UNTIL X>5", b" \xfd X>5"),
|
|
22
|
+
("IF A=1 THEN PRINT A", b" \xe7 A=1 \x8c \xf1 A"),
|
|
23
|
+
("X=INT(RND(1)*6)+1", b" X=\xa8(\xb3(1)*6)+1"),
|
|
24
|
+
("PROCdraw(x%,y%)", b" \xf2draw(x%,y%)"),
|
|
25
|
+
("DEF PROCgbpb(A$,T%)", b" \xdd \xf2gbpb(A$,T%)"),
|
|
26
|
+
("DEF FNsq(x)=x*x", b" \xdd \xa4sq(x)=x*x"),
|
|
27
|
+
("=FNsq(3)", b" =\xa4sq(3)"),
|
|
28
|
+
("P.", b" \xf1"),
|
|
29
|
+
("PR.", b" \xf1"),
|
|
30
|
+
("PRO.", b" \xf2"),
|
|
31
|
+
("F.", b" \xe3"),
|
|
32
|
+
("N.", b" \xed"),
|
|
33
|
+
("G.100", b" \xe5\x8dDd@"),
|
|
34
|
+
("REP.", b" \xf5"),
|
|
35
|
+
("AND0", b" \x80\x8dT@@"),
|
|
36
|
+
("TO1", b" \xb8\x8dTA@"),
|
|
37
|
+
("PRINT1", b" \xf11"),
|
|
38
|
+
("DIV9", b" \x81\x8dTI@"),
|
|
39
|
+
("SIN1", b" \xb5\x8dTA@"),
|
|
40
|
+
("TAB(1", b" \x8a\x8dTA@"),
|
|
41
|
+
("X=2", b" X=2"),
|
|
42
|
+
("5", b" \x8dTE@"),
|
|
43
|
+
("GOTO0", b" \xe5\x8dT@@"),
|
|
44
|
+
("GOTO1", b" \xe5\x8dTA@"),
|
|
45
|
+
("GOTO 10", b" \xe5 \x8dTJ@"),
|
|
46
|
+
("GOTO 32767", b" \xe5 \x8d`\x7f\x7f"),
|
|
47
|
+
("GOSUB 100", b" \xe4 \x8dDd@"),
|
|
48
|
+
("RESTORE 100,200", b" \xf7 \x8dDd@,\x8ddH@"),
|
|
49
|
+
("ON X GOTO 10,20,30", b" \xee X \xe5 \x8dTJ@,\x8dTT@,\x8dT^@"),
|
|
50
|
+
("IF A THEN 100 ELSE 200", b" \xe7 A \x8c \x8dDd@ \x8b \x8ddH@"),
|
|
51
|
+
("GOTO X", b" \xe5 X"),
|
|
52
|
+
("GOTO -5", b" \xe5 -5"),
|
|
53
|
+
("RESTORE", b" \xf7"),
|
|
54
|
+
("TRACE 500", b" \xfc \x8ddtA"),
|
|
55
|
+
("RENUMBER 100,10", b" \xcc \x8dDd@,\x8dTJ@"),
|
|
56
|
+
("PAGE=&2000", b" \xd0=&2000"),
|
|
57
|
+
("X=PAGE", b" X=\x90"),
|
|
58
|
+
("PRINT PAGE", b" \xf1 \x90"),
|
|
59
|
+
("TIME=0", b" \xd1=0"),
|
|
60
|
+
("X=TIME", b" X=\x91"),
|
|
61
|
+
("PTR#3=0", b" \xcf#3=0"),
|
|
62
|
+
("X=PTR#3", b" X=\x8f#3"),
|
|
63
|
+
("HIMEM=&7C00", b" \xd3=&7C00"),
|
|
64
|
+
("LOMEM=PAGE", b" \xd2=\x90"),
|
|
65
|
+
("? ERR PAGE", b" ? \x9f \x90"),
|
|
66
|
+
("GET$;,PTR", b" \xbe;,\x8f"),
|
|
67
|
+
("INSTR(A$,B$) PTR", b" \xa7A$,B$) \x8f"),
|
|
68
|
+
("OPENOUT LOMEM", b" \xae \xd2"),
|
|
69
|
+
("SQR RAD PTR", b" \xb6 \xb2 \xcf"),
|
|
70
|
+
("RND * SQR ENDPROC", b" \xb3 * SQR ENDPROC"),
|
|
71
|
+
('A$="he""llo"', b' A$="he""llo"'),
|
|
72
|
+
('PRINT "GOTO 10"', b' \xf1 "GOTO 10"'),
|
|
73
|
+
("X=&FF+&1A2B", b" X=&FF+&1A2B"),
|
|
74
|
+
("Y=1.5E3+.25", b" Y=1.5E3+.25"),
|
|
75
|
+
('PRINT "unterminated GOTO', b' \xf1 "unterminated GOTO'),
|
|
76
|
+
("REM this has PRINT GOTO 10", b" \xf4 this has PRINT GOTO 10"),
|
|
77
|
+
("DATA 1,2,PRINT,3:X=4", b" \xdc 1,2,PRINT,3:X=4"),
|
|
78
|
+
("*CAT", b" *CAT"),
|
|
79
|
+
("*FX 21,0", b" *FX 21,0"),
|
|
80
|
+
("X=3*4", b" X=3*4"),
|
|
81
|
+
("PRINT~&FF", b" \xf1~&FF"),
|
|
82
|
+
("VDU 26,12;0;", b" \xef 26,12;0;"),
|
|
83
|
+
("TIMER", b" TIMER"),
|
|
84
|
+
("TRUEELSE", b" TRUEELSE"),
|
|
85
|
+
("GDIV40", b" GDIV40"),
|
|
86
|
+
("TOTAL", b" \xb8TAL"),
|
|
87
|
+
("DIVMOD", b" \x81\x83"),
|
|
88
|
+
("TRUE+", b" \xb9+"),
|
|
89
|
+
("text%?p%=ASC(MID$(A$,i%,1))", b" text%?p%=\x97(\xc1A$,i%,1))"),
|
|
90
|
+
("?(a%+1)=&FF", b" ?(a%+1)=&FF"),
|
|
91
|
+
("A=1:B=2:PRINT A,B", b" A=1:B=2:\xf1 A,B"),
|
|
92
|
+
("COLOUR 129:CLS", b" \xfb 129:\xdb"),
|
|
93
|
+
("MODE 7:VDU 23", b" \xeb 7:\xef 23"),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.parametrize(("source", "expected_body"), _GOLDEN, ids=[v[0] for v in _GOLDEN])
|
|
98
|
+
def test_tokenises_like_the_basic_ii_rom(source, expected_body):
|
|
99
|
+
program = tokenise("10 " + source)
|
|
100
|
+
assert program[4 : program[3]] == expected_body
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Golden de-tokeniser vectors from the genuine BBC BASIC II ROM.
|
|
2
|
+
|
|
3
|
+
Generated by scripts/gen_rom_detok_golden.py from basictool's 6502
|
|
4
|
+
emulation of the BASIC II ROM and committed as static data: the ROM's
|
|
5
|
+
LIST output is the authority for what de-tokenising must produce (the
|
|
6
|
+
expected text is rebuilt in our plain line-number style from the ROM's
|
|
7
|
+
LIST bodies). basictool is NOT a test dependency.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from oaknut.basic import detokenise
|
|
12
|
+
|
|
13
|
+
# (tokenised program bytes, expected de-tokenised text).
|
|
14
|
+
_GOLDEN = [
|
|
15
|
+
(b'\r\x00\n\x16 \xf1 "Hello, world!"\r\xff', '10 PRINT "Hello, world!"\n'),
|
|
16
|
+
(b"\r\x00\n\x0b \xe5 \x8dDd@\r\xff", "10 GOTO 100\n"),
|
|
17
|
+
(b"\r\x00\n\r \xe4 \x8ddH@:\xf8\r\xff", "10 GOSUB 200:RETURN\n"),
|
|
18
|
+
(b"\r\x00\n\x19 \xee X \xe5 \x8dTJ@,\x8dTT@,\x8dT^@\r\xff", "10 ON X GOTO 10,20,30\n"),
|
|
19
|
+
(b"\r\x00\n\x18 \xe7 A=1 \x8c \x8dTr@ \x8b \x8dT|@\r\xff", "10 IF A=1 THEN 50 ELSE 60\n"),
|
|
20
|
+
(b"\r\x00\n\x11 \xf7 \x8dDd@:\xf3 A,B\r\xff", "10 RESTORE 100:READ A,B\n"),
|
|
21
|
+
(b"\r\x00\n\x17 \xe3 I=1 \xb8 10 \x88 2:\xed I\r\xff", "10 FOR I=1 TO 10 STEP 2:NEXT I\n"),
|
|
22
|
+
(b"\r\x00\n\x0c \xf5:\xfd X>5\r\xff", "10 REPEAT:UNTIL X>5\n"),
|
|
23
|
+
(b"\r\x00\n\x0c \xd0=&2000\r\xff", "10 PAGE=&2000\n"),
|
|
24
|
+
(b"\r\x00\n\x08 X=\x90\r\xff", "10 X=PAGE\n"),
|
|
25
|
+
(b"\r\x00\n\n \xcf#3=0\r\xff", "10 PTR#3=0\n"),
|
|
26
|
+
(b"\r\x00\n\x0c \xd1=0:X=\x91\r\xff", "10 TIME=0:X=TIME\n"),
|
|
27
|
+
(b"\r\x00\n\x10 \xd3=&7C00:\xd2=\x90\r\xff", "10 HIMEM=&7C00:LOMEM=PAGE\n"),
|
|
28
|
+
(b'\r\x00\n\x15 A$=\xbd(34)+"quote"\r\xff', '10 A$=CHR$(34)+"quote"\n'),
|
|
29
|
+
(b'\r\x00\n\x1d \xf1 "GOTO 10 is text here"\r\xff', '10 PRINT "GOTO 10 is text here"\n'),
|
|
30
|
+
(b"\r\x00\n\x16 X=&FF+&1A2B \x80 &F0\r\xff", "10 X=&FF+&1A2B AND &F0\n"),
|
|
31
|
+
(b"\r\x00\n\x10 Y=1.5E3+.25\r\xff", "10 Y=1.5E3+.25\n"),
|
|
32
|
+
(
|
|
33
|
+
b"\r\x00\n$ \xf4 this REM has PRINT GOTO in it\r\xff",
|
|
34
|
+
"10 REM this REM has PRINT GOTO in it\n",
|
|
35
|
+
),
|
|
36
|
+
(b"\r\x00\n\x18 \xdc 1,2,PRINT,3,hello\r\xff", "10 DATA 1,2,PRINT,3,hello\n"),
|
|
37
|
+
(b"\r\x00\n\t *CAT\r\xff", "10 *CAT\n"),
|
|
38
|
+
(b"\r\x00\n\x14 \xef 23,255;0;0;0;\r\xff", "10 VDU 23,255;0;0;0;\n"),
|
|
39
|
+
(b"\r\x00\n\x13 \xf2draw(x%,y%):\xe1\r\xff", "10 PROCdraw(x%,y%):ENDPROC\n"),
|
|
40
|
+
(b"\r\x00\n\x11 \xdd \xa4sq(x)=x*x\r\xff", "10 DEF FNsq(x)=x*x\n"),
|
|
41
|
+
(b"\r\x00\n\x11 L%=\xa9(A$)+\xb1-\x9c\r\xff", "10 L%=LEN(A$)+POS-COUNT\n"),
|
|
42
|
+
(b"\r\x00\n\x12 X=\xa8(\xb3(1)*6)+1\r\xff", "10 X=INT(RND(1)*6)+1\n"),
|
|
43
|
+
(b"\r\x00\n\n \xf1~&AB\r\xff", "10 PRINT~&AB\n"),
|
|
44
|
+
(b"\r\x00\n\x14 @%=&90A:\xf1 \x8a5);X\r\xff", "10 @%=&90A:PRINT TAB(5);X\n"),
|
|
45
|
+
(b"\r\x00\n\x12 \xfb 129:\xe6 0,1:\xdb\r\xff", "10 COLOUR 129:GCOL 0,1:CLS\n"),
|
|
46
|
+
(b"\r\x00\n\x10 \xeb 7:\xf1'\x8a10)'\r\xff", "10 MODE 7:PRINT'TAB(10)'\n"),
|
|
47
|
+
(b"\r\x00\n\x17 ?(addr%+1)=base%?2\r\xff", "10 ?(addr%+1)=base%?2\n"),
|
|
48
|
+
(b'\r\x00\n\x19 \xf1 "unterminated GOTO\r\xff', '10 PRINT "unterminated GOTO\n'),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.parametrize(("program", "expected"), _GOLDEN, ids=range(len(_GOLDEN)))
|
|
53
|
+
def test_detokenises_like_the_basic_ii_rom(program, expected):
|
|
54
|
+
assert detokenise(program) == expected
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"""Golden tokeniser vectors from the genuine BBC BASIC II ROM.
|
|
2
|
-
|
|
3
|
-
Generated by scripts (basictool's 6502 emulation of the BASIC II ROM)
|
|
4
|
-
and committed as static data: the ROM is the authority for what the
|
|
5
|
-
tokeniser must produce, and this test holds it to that. basictool is
|
|
6
|
-
NOT a test dependency — regenerate with scripts/gen_rom_golden.py if the
|
|
7
|
-
vector set changes.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
from oaknut.basic import tokenise
|
|
12
|
-
|
|
13
|
-
# (source after the line number, tokenised body the ROM produces).
|
|
14
|
-
_GOLDEN = [
|
|
15
|
-
('PRINT "Hi"', b' \xf1 "Hi"'),
|
|
16
|
-
('END', b' \xe0'),
|
|
17
|
-
('GOTO 100', b' \xe5 \x8dDd@'),
|
|
18
|
-
('FOR I=1 TO 10 STEP 2', b' \xe3 I=1 \xb8 10 \x88 2'),
|
|
19
|
-
('NEXT I', b' \xed I'),
|
|
20
|
-
('REPEAT', b' \xf5'),
|
|
21
|
-
('UNTIL X>5', b' \xfd X>5'),
|
|
22
|
-
('IF A=1 THEN PRINT A', b' \xe7 A=1 \x8c \xf1 A'),
|
|
23
|
-
('X=INT(RND(1)*6)+1', b' X=\xa8(\xb3(1)*6)+1'),
|
|
24
|
-
('PROCdraw(x%,y%)', b' \xf2draw(x%,y%)'),
|
|
25
|
-
('DEF PROCgbpb(A$,T%)', b' \xdd \xf2gbpb(A$,T%)'),
|
|
26
|
-
('DEF FNsq(x)=x*x', b' \xdd \xa4sq(x)=x*x'),
|
|
27
|
-
('=FNsq(3)', b' =\xa4sq(3)'),
|
|
28
|
-
('P.', b' \xf1'),
|
|
29
|
-
('PR.', b' \xf1'),
|
|
30
|
-
('PRO.', b' \xf2'),
|
|
31
|
-
('F.', b' \xe3'),
|
|
32
|
-
('N.', b' \xed'),
|
|
33
|
-
('G.100', b' \xe5\x8dDd@'),
|
|
34
|
-
('REP.', b' \xf5'),
|
|
35
|
-
('AND0', b' \x80\x8dT@@'),
|
|
36
|
-
('TO1', b' \xb8\x8dTA@'),
|
|
37
|
-
('PRINT1', b' \xf11'),
|
|
38
|
-
('DIV9', b' \x81\x8dTI@'),
|
|
39
|
-
('SIN1', b' \xb5\x8dTA@'),
|
|
40
|
-
('TAB(1', b' \x8a\x8dTA@'),
|
|
41
|
-
('X=2', b' X=2'),
|
|
42
|
-
('5', b' \x8dTE@'),
|
|
43
|
-
('GOTO0', b' \xe5\x8dT@@'),
|
|
44
|
-
('GOTO1', b' \xe5\x8dTA@'),
|
|
45
|
-
('GOTO 10', b' \xe5 \x8dTJ@'),
|
|
46
|
-
('GOTO 32767', b' \xe5 \x8d`\x7f\x7f'),
|
|
47
|
-
('GOSUB 100', b' \xe4 \x8dDd@'),
|
|
48
|
-
('RESTORE 100,200', b' \xf7 \x8dDd@,\x8ddH@'),
|
|
49
|
-
('ON X GOTO 10,20,30', b' \xee X \xe5 \x8dTJ@,\x8dTT@,\x8dT^@'),
|
|
50
|
-
('IF A THEN 100 ELSE 200', b' \xe7 A \x8c \x8dDd@ \x8b \x8ddH@'),
|
|
51
|
-
('GOTO X', b' \xe5 X'),
|
|
52
|
-
('GOTO -5', b' \xe5 -5'),
|
|
53
|
-
('RESTORE', b' \xf7'),
|
|
54
|
-
('TRACE 500', b' \xfc \x8ddtA'),
|
|
55
|
-
('RENUMBER 100,10', b' \xcc \x8dDd@,\x8dTJ@'),
|
|
56
|
-
('PAGE=&2000', b' \xd0=&2000'),
|
|
57
|
-
('X=PAGE', b' X=\x90'),
|
|
58
|
-
('PRINT PAGE', b' \xf1 \x90'),
|
|
59
|
-
('TIME=0', b' \xd1=0'),
|
|
60
|
-
('X=TIME', b' X=\x91'),
|
|
61
|
-
('PTR#3=0', b' \xcf#3=0'),
|
|
62
|
-
('X=PTR#3', b' X=\x8f#3'),
|
|
63
|
-
('HIMEM=&7C00', b' \xd3=&7C00'),
|
|
64
|
-
('LOMEM=PAGE', b' \xd2=\x90'),
|
|
65
|
-
('? ERR PAGE', b' ? \x9f \x90'),
|
|
66
|
-
('GET$;,PTR', b' \xbe;,\x8f'),
|
|
67
|
-
('INSTR(A$,B$) PTR', b' \xa7A$,B$) \x8f'),
|
|
68
|
-
('OPENOUT LOMEM', b' \xae \xd2'),
|
|
69
|
-
('SQR RAD PTR', b' \xb6 \xb2 \xcf'),
|
|
70
|
-
('RND * SQR ENDPROC', b' \xb3 * SQR ENDPROC'),
|
|
71
|
-
('A$="he""llo"', b' A$="he""llo"'),
|
|
72
|
-
('PRINT "GOTO 10"', b' \xf1 "GOTO 10"'),
|
|
73
|
-
('X=&FF+&1A2B', b' X=&FF+&1A2B'),
|
|
74
|
-
('Y=1.5E3+.25', b' Y=1.5E3+.25'),
|
|
75
|
-
('PRINT "unterminated GOTO', b' \xf1 "unterminated GOTO'),
|
|
76
|
-
('REM this has PRINT GOTO 10', b' \xf4 this has PRINT GOTO 10'),
|
|
77
|
-
('DATA 1,2,PRINT,3:X=4', b' \xdc 1,2,PRINT,3:X=4'),
|
|
78
|
-
('*CAT', b' *CAT'),
|
|
79
|
-
('*FX 21,0', b' *FX 21,0'),
|
|
80
|
-
('X=3*4', b' X=3*4'),
|
|
81
|
-
('PRINT~&FF', b' \xf1~&FF'),
|
|
82
|
-
('VDU 26,12;0;', b' \xef 26,12;0;'),
|
|
83
|
-
('TIMER', b' TIMER'),
|
|
84
|
-
('TRUEELSE', b' TRUEELSE'),
|
|
85
|
-
('GDIV40', b' GDIV40'),
|
|
86
|
-
('TOTAL', b' \xb8TAL'),
|
|
87
|
-
('DIVMOD', b' \x81\x83'),
|
|
88
|
-
('TRUE+', b' \xb9+'),
|
|
89
|
-
('text%?p%=ASC(MID$(A$,i%,1))', b' text%?p%=\x97(\xc1A$,i%,1))'),
|
|
90
|
-
('?(a%+1)=&FF', b' ?(a%+1)=&FF'),
|
|
91
|
-
('A=1:B=2:PRINT A,B', b' A=1:B=2:\xf1 A,B'),
|
|
92
|
-
('COLOUR 129:CLS', b' \xfb 129:\xdb'),
|
|
93
|
-
('MODE 7:VDU 23', b' \xeb 7:\xef 23'),
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@pytest.mark.parametrize(("source", "expected_body"), _GOLDEN, ids=[v[0] for v in _GOLDEN])
|
|
98
|
-
def test_tokenises_like_the_basic_ii_rom(source, expected_body):
|
|
99
|
-
program = tokenise("10 " + source)
|
|
100
|
-
assert program[4 : program[3]] == expected_body
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""Golden de-tokeniser vectors from the genuine BBC BASIC II ROM.
|
|
2
|
-
|
|
3
|
-
Generated by scripts/gen_rom_detok_golden.py from basictool's 6502
|
|
4
|
-
emulation of the BASIC II ROM and committed as static data: the ROM's
|
|
5
|
-
LIST output is the authority for what de-tokenising must produce (the
|
|
6
|
-
expected text is rebuilt in our plain line-number style from the ROM's
|
|
7
|
-
LIST bodies). basictool is NOT a test dependency.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
from oaknut.basic import detokenise
|
|
12
|
-
|
|
13
|
-
# (tokenised program bytes, expected de-tokenised text).
|
|
14
|
-
_GOLDEN = [
|
|
15
|
-
(b'\r\x00\n\x16 \xf1 "Hello, world!"\r\xff', '10 PRINT "Hello, world!"\n'),
|
|
16
|
-
(b'\r\x00\n\x0b \xe5 \x8dDd@\r\xff', '10 GOTO 100\n'),
|
|
17
|
-
(b'\r\x00\n\r \xe4 \x8ddH@:\xf8\r\xff', '10 GOSUB 200:RETURN\n'),
|
|
18
|
-
(b'\r\x00\n\x19 \xee X \xe5 \x8dTJ@,\x8dTT@,\x8dT^@\r\xff', '10 ON X GOTO 10,20,30\n'),
|
|
19
|
-
(b'\r\x00\n\x18 \xe7 A=1 \x8c \x8dTr@ \x8b \x8dT|@\r\xff', '10 IF A=1 THEN 50 ELSE 60\n'),
|
|
20
|
-
(b'\r\x00\n\x11 \xf7 \x8dDd@:\xf3 A,B\r\xff', '10 RESTORE 100:READ A,B\n'),
|
|
21
|
-
(b'\r\x00\n\x17 \xe3 I=1 \xb8 10 \x88 2:\xed I\r\xff', '10 FOR I=1 TO 10 STEP 2:NEXT I\n'),
|
|
22
|
-
(b'\r\x00\n\x0c \xf5:\xfd X>5\r\xff', '10 REPEAT:UNTIL X>5\n'),
|
|
23
|
-
(b'\r\x00\n\x0c \xd0=&2000\r\xff', '10 PAGE=&2000\n'),
|
|
24
|
-
(b'\r\x00\n\x08 X=\x90\r\xff', '10 X=PAGE\n'),
|
|
25
|
-
(b'\r\x00\n\n \xcf#3=0\r\xff', '10 PTR#3=0\n'),
|
|
26
|
-
(b'\r\x00\n\x0c \xd1=0:X=\x91\r\xff', '10 TIME=0:X=TIME\n'),
|
|
27
|
-
(b'\r\x00\n\x10 \xd3=&7C00:\xd2=\x90\r\xff', '10 HIMEM=&7C00:LOMEM=PAGE\n'),
|
|
28
|
-
(b'\r\x00\n\x15 A$=\xbd(34)+"quote"\r\xff', '10 A$=CHR$(34)+"quote"\n'),
|
|
29
|
-
(b'\r\x00\n\x1d \xf1 "GOTO 10 is text here"\r\xff', '10 PRINT "GOTO 10 is text here"\n'),
|
|
30
|
-
(b'\r\x00\n\x16 X=&FF+&1A2B \x80 &F0\r\xff', '10 X=&FF+&1A2B AND &F0\n'),
|
|
31
|
-
(b'\r\x00\n\x10 Y=1.5E3+.25\r\xff', '10 Y=1.5E3+.25\n'),
|
|
32
|
-
(b'\r\x00\n$ \xf4 this REM has PRINT GOTO in it\r\xff', '10 REM this REM has PRINT GOTO in it\n'),
|
|
33
|
-
(b'\r\x00\n\x18 \xdc 1,2,PRINT,3,hello\r\xff', '10 DATA 1,2,PRINT,3,hello\n'),
|
|
34
|
-
(b'\r\x00\n\t *CAT\r\xff', '10 *CAT\n'),
|
|
35
|
-
(b'\r\x00\n\x14 \xef 23,255;0;0;0;\r\xff', '10 VDU 23,255;0;0;0;\n'),
|
|
36
|
-
(b'\r\x00\n\x13 \xf2draw(x%,y%):\xe1\r\xff', '10 PROCdraw(x%,y%):ENDPROC\n'),
|
|
37
|
-
(b'\r\x00\n\x11 \xdd \xa4sq(x)=x*x\r\xff', '10 DEF FNsq(x)=x*x\n'),
|
|
38
|
-
(b'\r\x00\n\x11 L%=\xa9(A$)+\xb1-\x9c\r\xff', '10 L%=LEN(A$)+POS-COUNT\n'),
|
|
39
|
-
(b'\r\x00\n\x12 X=\xa8(\xb3(1)*6)+1\r\xff', '10 X=INT(RND(1)*6)+1\n'),
|
|
40
|
-
(b'\r\x00\n\n \xf1~&AB\r\xff', '10 PRINT~&AB\n'),
|
|
41
|
-
(b'\r\x00\n\x14 @%=&90A:\xf1 \x8a5);X\r\xff', '10 @%=&90A:PRINT TAB(5);X\n'),
|
|
42
|
-
(b'\r\x00\n\x12 \xfb 129:\xe6 0,1:\xdb\r\xff', '10 COLOUR 129:GCOL 0,1:CLS\n'),
|
|
43
|
-
(b"\r\x00\n\x10 \xeb 7:\xf1'\x8a10)'\r\xff", "10 MODE 7:PRINT'TAB(10)'\n"),
|
|
44
|
-
(b'\r\x00\n\x17 ?(addr%+1)=base%?2\r\xff', '10 ?(addr%+1)=base%?2\n'),
|
|
45
|
-
(b'\r\x00\n\x19 \xf1 "unterminated GOTO\r\xff', '10 PRINT "unterminated GOTO\n'),
|
|
46
|
-
]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@pytest.mark.parametrize(("program", "expected"), _GOLDEN, ids=range(len(_GOLDEN)))
|
|
50
|
-
def test_detokenises_like_the_basic_ii_rom(program, expected):
|
|
51
|
-
assert detokenise(program) == expected
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oaknut_basic-12.17.0 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|