oaknut-basic 12.17.1__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.
Files changed (45) hide show
  1. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/PKG-INFO +8 -8
  2. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/README.md +7 -7
  3. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/__init__.py +1 -1
  4. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/cli.py +2 -2
  5. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/detect.py +2 -6
  6. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/dialect.py +1 -3
  7. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/exceptions.py +2 -6
  8. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/float5.py +1 -3
  9. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/tokeniser.py +1 -0
  10. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/PKG-INFO +8 -8
  11. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_data_cli.py +1 -3
  12. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_detect.py +1 -1
  13. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_greedy_crunch.py +1 -3
  14. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_keyword_coverage.py +3 -1
  15. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_numbering.py +2 -2
  16. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_packaging.py +1 -3
  17. oaknut_basic-12.18.0/tests/test_rom_golden.py +100 -0
  18. oaknut_basic-12.18.0/tests/test_rom_golden_detokenise.py +54 -0
  19. oaknut_basic-12.17.1/tests/test_rom_golden.py +0 -100
  20. oaknut_basic-12.17.1/tests/test_rom_golden_detokenise.py +0 -51
  21. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/LICENSE +0 -0
  22. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/pyproject.toml +0 -0
  23. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/setup.cfg +0 -0
  24. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/datafile.py +0 -0
  25. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/detokeniser.py +0 -0
  26. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/linenumber.py +0 -0
  27. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/numbering.py +0 -0
  28. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/scanner.py +0 -0
  29. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut/basic/tokens.py +0 -0
  30. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/SOURCES.txt +0 -0
  31. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/dependency_links.txt +0 -0
  32. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/entry_points.txt +0 -0
  33. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/requires.txt +0 -0
  34. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/src/oaknut_basic.egg-info/top_level.txt +0 -0
  35. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_basic.py +0 -0
  36. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_basic_v.py +0 -0
  37. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_cli.py +0 -0
  38. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_crunch_rules.py +0 -0
  39. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_datafile.py +0 -0
  40. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_detokeniser.py +0 -0
  41. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_float5.py +0 -0
  42. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_linenumber.py +0 -0
  43. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_scanner.py +0 -0
  44. {oaknut_basic-12.17.1 → oaknut_basic-12.18.0}/tests/test_tokeniser.py +0 -0
  45. {oaknut_basic-12.17.1 → 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.17.1
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
@@ -131,9 +131,9 @@ Programs are handled by the functions `tokenise`, `detokenise`, and
131
131
  ```python
132
132
  from oaknut.basic import tokenise, detokenise
133
133
 
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
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
137
137
  ```
138
138
 
139
139
  When the program lives in a disc image, prefer the path-object wrappers
@@ -150,12 +150,12 @@ polymorphic `write` picks the record type from the Python value; typed
150
150
  from oaknut.basic import datafile
151
151
 
152
152
  with datafile.open("scores.dat", "w") as f:
153
- f.write("ALICE") # str -> string record
154
- f.write(42) # int -> integer record
155
- f.write(3.5) # float -> real record
153
+ f.write("ALICE") # str -> string record
154
+ f.write(42) # int -> integer record
155
+ f.write(3.5) # float -> real record
156
156
 
157
157
  with datafile.open("scores.dat", "r") as f:
158
- for value in f: # yields "ALICE", 42, 3.5
158
+ for value in f: # yields "ALICE", 42, 3.5
159
159
  print(value)
160
160
  ```
161
161
 
@@ -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') # str -> bytes
111
- listing = detokenise(program) # bytes -> str
112
- assert tokenise(detokenise(program)) == program # byte-exact
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") # str -> string record
130
- f.write(42) # int -> integer record
131
- f.write(3.5) # float -> real record
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: # yields "ALICE", 42, 3.5
134
+ for value in f: # yields "ALICE", 42, 3.5
135
135
  print(value)
136
136
  ```
137
137
 
@@ -81,7 +81,7 @@ from oaknut.basic.tokens import (
81
81
  TOKEN_TO_KEYWORD,
82
82
  )
83
83
 
84
- __version__ = "12.17.1"
84
+ __version__ = "12.18.0"
85
85
 
86
86
  # Canonical load addresses for BBC BASIC programs on each host.
87
87
  # Programs saved by *SAVE on a real machine use these by default.
@@ -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="Which tokeniser to emulate. \"rom\" is byte-exact to the BBC BASIC "
221
- "ROM. Use \"greedy\" to reproduce the greedier third-party tokeniser "
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.17.1
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
@@ -131,9 +131,9 @@ Programs are handled by the functions `tokenise`, `detokenise`, and
131
131
  ```python
132
132
  from oaknut.basic import tokenise, detokenise
133
133
 
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
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
137
137
  ```
138
138
 
139
139
  When the program lives in a disc image, prefer the path-object wrappers
@@ -150,12 +150,12 @@ polymorphic `write` picks the record type from the Python value; typed
150
150
  from oaknut.basic import datafile
151
151
 
152
152
  with datafile.open("scores.dat", "w") as f:
153
- f.write("ALICE") # str -> string record
154
- f.write(42) # int -> integer record
155
- f.write(3.5) # float -> real record
153
+ f.write("ALICE") # str -> string record
154
+ f.write(42) # int -> integer record
155
+ f.write(3.5) # float -> real record
156
156
 
157
157
  with datafile.open("scores.dat", "r") as f:
158
- for value in f: # yields "ALICE", 42, 3.5
158
+ for value in f: # yields "ALICE", 42, 3.5
159
159
  print(value)
160
160
  ```
161
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("10 PRINT \"HELLO\"\n20 GOTO 10")
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(("keyword", "token"), [(kw, t) for kw, t, _f in KEYWORDS], ids=_ids(KEYWORDS))
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 = "PRINT \"Hello\"\nEND"
20
- assert basic.number_lines(source) == "10 PRINT \"Hello\"\n20 END"
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"
@@ -48,9 +48,7 @@ def test_console_script_is_declared():
48
48
 
49
49
  def test_cli_dependencies_are_unconditional():
50
50
  unconditional = _unconditional_requirements()
51
- missing = [
52
- name for name in _CLI_REQUIREMENTS if name.lower() not in unconditional
53
- ]
51
+ missing = [name for name in _CLI_REQUIREMENTS if name.lower() not in unconditional]
54
52
  assert not missing, (
55
53
  f"{_DISTRIBUTION} ships a console script but these imports of "
56
54
  f"oaknut.basic.cli are not unconditional dependencies: {missing}. "
@@ -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