oaknut.basic¶
The BBC BASIC II tokeniser, de-tokeniser, and line-numbering facade.
Conversion functions¶
- oaknut.basic.tokenise(source, *, start=None, step=None)¶
Tokenise BBC BASIC II source text into a stored program.
- Parameters:
source (str) – BBC BASIC source. By default every non-blank line must begin with a line number, as a saved
LISTwould.start (int | None) – First line number for auto-numbering. Supplying start and/or step turns auto-numbering on (as typing under
AUTOwould); the source must then have no line numbers. Defaults to 10 when only step is given.step (int | None) – Increment for auto-numbering. Defaults to 10 when only start is given.
- Returns:
The tokenised program bytes, terminated by
&0D &FF.- Raises:
TokeniseError – The source is malformed — an unnumbered line (or, under auto-numbering, an already-numbered one), a line number out of range or out of order, or a line that tokenises beyond the maximum storable length.
- Return type:
- oaknut.basic.detokenise(data, *, dialect=Dialect(name='BBC BASIC II', single_byte=mappingproxy({128: 'AND', 148: 'ABS', 149: 'ACS', 150: 'ADVAL', 151: 'ASC', 152: 'ASN', 153: 'ATN', 198: 'AUTO', 154: 'BGET', 213: 'BPUT', 251: 'COLOUR', 214: 'CALL', 215: 'CHAIN', 189: 'CHR$', 216: 'CLEAR', 217: 'CLOSE', 218: 'CLG', 219: 'CLS', 155: 'COS', 156: 'COUNT', 220: 'DATA', 157: 'DEG', 221: 'DEF', 199: 'DELETE', 129: 'DIV', 222: 'DIM', 223: 'DRAW', 225: 'ENDPROC', 224: 'END', 226: 'ENVELOPE', 139: 'ELSE', 160: 'EVAL', 158: 'ERL', 133: 'ERROR', 197: 'EOF', 130: 'EOR', 159: 'ERR', 161: 'EXP', 162: 'EXT', 227: 'FOR', 163: 'FALSE', 164: 'FN', 229: 'GOTO', 190: 'GET$', 165: 'GET', 228: 'GOSUB', 230: 'GCOL', 147: 'HIMEM', 232: 'INPUT', 231: 'IF', 191: 'INKEY$', 166: 'INKEY', 168: 'INT', 167: 'INSTR(', 201: 'LIST', 134: 'LINE', 200: 'LOAD', 146: 'LOMEM', 234: 'LOCAL', 192: 'LEFT$(', 169: 'LEN', 233: 'LET', 171: 'LOG', 170: 'LN', 193: 'MID$(', 235: 'MODE', 131: 'MOD', 236: 'MOVE', 237: 'NEXT', 202: 'NEW', 172: 'NOT', 203: 'OLD', 238: 'ON', 135: 'OFF', 132: 'OR', 142: 'OPENIN', 174: 'OPENOUT', 173: 'OPENUP', 255: 'OSCLI', 241: 'PRINT', 144: 'PAGE', 143: 'PTR', 175: 'PI', 240: 'PLOT', 176: 'POINT(', 242: 'PROC', 177: 'POS', 248: 'RETURN', 245: 'REPEAT', 246: 'REPORT', 243: 'READ', 244: 'REM', 249: 'RUN', 178: 'RAD', 247: 'RESTORE', 194: 'RIGHT$(', 179: 'RND', 204: 'RENUMBER', 136: 'STEP', 205: 'SAVE', 180: 'SGN', 181: 'SIN', 182: 'SQR', 137: 'SPC', 195: 'STR$', 196: 'STRING$(', 212: 'SOUND', 250: 'STOP', 183: 'TAN', 140: 'THEN', 184: 'TO', 138: 'TAB(', 252: 'TRACE', 145: 'TIME', 185: 'TRUE', 253: 'UNTIL', 186: 'USR', 239: 'VDU', 187: 'VAL', 188: 'VPOS', 254: 'WIDTH', 208: 'PAGE', 207: 'PTR', 209: 'TIME', 210: 'LOMEM', 211: 'HIMEM'}), escape=mappingproxy({})))¶
De-tokenise a stored BBC BASIC program into source text.
- Parameters:
data (bytes) – A tokenised program, as stored on disc or in memory, terminated by the
&0D &FFend marker.dialect (Dialect) – The BBC BASIC variant whose token tables to use. Defaults to
BASIC_II; passBASIC_Vfor Archimedes / RISC OS programs so the&C6/&C7/&C8escape tokens and the re-purposed single-byte tokens decode correctly.
- Returns:
The program as numbered source text, lines separated by
"\n"with a trailing newline. Empty for an empty program.- Raises:
DetokeniseError – The token stream is malformed — a missing line marker, a truncated header or reference, or an impossible length byte.
- Return type:
- oaknut.basic.detokenise_body(data, *, dialect=Dialect(name='BBC BASIC II', single_byte=mappingproxy({128: 'AND', 148: 'ABS', 149: 'ACS', 150: 'ADVAL', 151: 'ASC', 152: 'ASN', 153: 'ATN', 198: 'AUTO', 154: 'BGET', 213: 'BPUT', 251: 'COLOUR', 214: 'CALL', 215: 'CHAIN', 189: 'CHR$', 216: 'CLEAR', 217: 'CLOSE', 218: 'CLG', 219: 'CLS', 155: 'COS', 156: 'COUNT', 220: 'DATA', 157: 'DEG', 221: 'DEF', 199: 'DELETE', 129: 'DIV', 222: 'DIM', 223: 'DRAW', 225: 'ENDPROC', 224: 'END', 226: 'ENVELOPE', 139: 'ELSE', 160: 'EVAL', 158: 'ERL', 133: 'ERROR', 197: 'EOF', 130: 'EOR', 159: 'ERR', 161: 'EXP', 162: 'EXT', 227: 'FOR', 163: 'FALSE', 164: 'FN', 229: 'GOTO', 190: 'GET$', 165: 'GET', 228: 'GOSUB', 230: 'GCOL', 147: 'HIMEM', 232: 'INPUT', 231: 'IF', 191: 'INKEY$', 166: 'INKEY', 168: 'INT', 167: 'INSTR(', 201: 'LIST', 134: 'LINE', 200: 'LOAD', 146: 'LOMEM', 234: 'LOCAL', 192: 'LEFT$(', 169: 'LEN', 233: 'LET', 171: 'LOG', 170: 'LN', 193: 'MID$(', 235: 'MODE', 131: 'MOD', 236: 'MOVE', 237: 'NEXT', 202: 'NEW', 172: 'NOT', 203: 'OLD', 238: 'ON', 135: 'OFF', 132: 'OR', 142: 'OPENIN', 174: 'OPENOUT', 173: 'OPENUP', 255: 'OSCLI', 241: 'PRINT', 144: 'PAGE', 143: 'PTR', 175: 'PI', 240: 'PLOT', 176: 'POINT(', 242: 'PROC', 177: 'POS', 248: 'RETURN', 245: 'REPEAT', 246: 'REPORT', 243: 'READ', 244: 'REM', 249: 'RUN', 178: 'RAD', 247: 'RESTORE', 194: 'RIGHT$(', 179: 'RND', 204: 'RENUMBER', 136: 'STEP', 205: 'SAVE', 180: 'SGN', 181: 'SIN', 182: 'SQR', 137: 'SPC', 195: 'STR$', 196: 'STRING$(', 212: 'SOUND', 250: 'STOP', 183: 'TAN', 140: 'THEN', 184: 'TO', 138: 'TAB(', 252: 'TRACE', 145: 'TIME', 185: 'TRUE', 253: 'UNTIL', 186: 'USR', 239: 'VDU', 187: 'VAL', 188: 'VPOS', 254: 'WIDTH', 208: 'PAGE', 207: 'PTR', 209: 'TIME', 210: 'LOMEM', 211: 'HIMEM'}), escape=mappingproxy({})))¶
De-tokenise an unframed line body into text.
Unlike
detokenise(), this expects a line body — inline token bytes with no&0Dframing and no leading line number, the form BBC Micro Bot / Owlet programs and AUTO-style entry arrive in. It is the text join ofoaknut.basic.scan(); when keyword adjacency matters, consume that token stream directly instead.- Parameters:
- Returns:
The body as source text, using latin-1/code-point semantics so it round-trips byte-exactly through
oaknut.basic.tokenise().- Raises:
DetokeniseError – A
&8Dline-number reference is truncated.- Return type:
- oaknut.basic.number_lines(source, *, start=10, step=10)¶
Prepend ascending line numbers to unnumbered BBC BASIC source.
Each line of source receives a line number, beginning at start and increasing by step, in the manner of the BBC’s
AUTO start,stepcommand. Internal references (GOTO,GOSUB, …) are left untouched; the caller is responsible for ensuring they already match the numbering produced here.- Parameters:
- Returns:
The source with a line number prepended to every line, lines joined by
"\n". A trailing newline in source is preserved.- Raises:
ValueError – start is negative or step is less than 1.
- Return type:
Token stream¶
De-tokenising to text loses keyword adjacency — the body ?Q <PLOT>
?Q renders as ?QPLOT?Q, which the ROM tokeniser re-reads as the
variable QPLOT. A consumer that must recover the program (a faithful
front end, a re-tokeniser) needs the bytes as a token stream, not text.
scan() provides it for an unframed line body, and
scan_program() walks the &0D-framed stored form.
The text de-tokenisers above are the join of these streams’ values.
- oaknut.basic.scan(data, *, base_offset=0, dialect=Dialect(name='BBC BASIC II', single_byte=mappingproxy({128: 'AND', 148: 'ABS', 149: 'ACS', 150: 'ADVAL', 151: 'ASC', 152: 'ASN', 153: 'ATN', 198: 'AUTO', 154: 'BGET', 213: 'BPUT', 251: 'COLOUR', 214: 'CALL', 215: 'CHAIN', 189: 'CHR$', 216: 'CLEAR', 217: 'CLOSE', 218: 'CLG', 219: 'CLS', 155: 'COS', 156: 'COUNT', 220: 'DATA', 157: 'DEG', 221: 'DEF', 199: 'DELETE', 129: 'DIV', 222: 'DIM', 223: 'DRAW', 225: 'ENDPROC', 224: 'END', 226: 'ENVELOPE', 139: 'ELSE', 160: 'EVAL', 158: 'ERL', 133: 'ERROR', 197: 'EOF', 130: 'EOR', 159: 'ERR', 161: 'EXP', 162: 'EXT', 227: 'FOR', 163: 'FALSE', 164: 'FN', 229: 'GOTO', 190: 'GET$', 165: 'GET', 228: 'GOSUB', 230: 'GCOL', 147: 'HIMEM', 232: 'INPUT', 231: 'IF', 191: 'INKEY$', 166: 'INKEY', 168: 'INT', 167: 'INSTR(', 201: 'LIST', 134: 'LINE', 200: 'LOAD', 146: 'LOMEM', 234: 'LOCAL', 192: 'LEFT$(', 169: 'LEN', 233: 'LET', 171: 'LOG', 170: 'LN', 193: 'MID$(', 235: 'MODE', 131: 'MOD', 236: 'MOVE', 237: 'NEXT', 202: 'NEW', 172: 'NOT', 203: 'OLD', 238: 'ON', 135: 'OFF', 132: 'OR', 142: 'OPENIN', 174: 'OPENOUT', 173: 'OPENUP', 255: 'OSCLI', 241: 'PRINT', 144: 'PAGE', 143: 'PTR', 175: 'PI', 240: 'PLOT', 176: 'POINT(', 242: 'PROC', 177: 'POS', 248: 'RETURN', 245: 'REPEAT', 246: 'REPORT', 243: 'READ', 244: 'REM', 249: 'RUN', 178: 'RAD', 247: 'RESTORE', 194: 'RIGHT$(', 179: 'RND', 204: 'RENUMBER', 136: 'STEP', 205: 'SAVE', 180: 'SGN', 181: 'SIN', 182: 'SQR', 137: 'SPC', 195: 'STR$', 196: 'STRING$(', 212: 'SOUND', 250: 'STOP', 183: 'TAN', 140: 'THEN', 184: 'TO', 138: 'TAB(', 252: 'TRACE', 145: 'TIME', 185: 'TRUE', 253: 'UNTIL', 186: 'USR', 239: 'VDU', 187: 'VAL', 188: 'VPOS', 254: 'WIDTH', 208: 'PAGE', 207: 'PTR', 209: 'TIME', 210: 'LOMEM', 211: 'HIMEM'}), escape=mappingproxy({})))¶
Scan an unframed line body into a stream of
Token.- Parameters:
data (bytes) – A line body — inline token bytes with no
&0Dline framing and no leading line number.base_offset (int) – Added to every token’s
start, so offsets can be reported against a larger buffer (for example the line body’s position within a whole program).dialect (Dialect) – The BBC BASIC variant whose token tables to use. Defaults to
BASIC_II; passBASIC_Vto resolve the&C6/&C7/&C8two-byte escape tokens and the re-purposed single-byte tokens of the Archimedes language.
- Yields:
One
Tokenper keyword, literal run, string literal, and&8Dline-number reference, in source order. For a two-byte escape keyword the token’stokenis the prefix byte and itsstartis the prefix’s offset.- Raises:
DetokeniseError – A
&8Dreference is truncated.- Return type:
- oaknut.basic.scan_program(data, *, dialect=Dialect(name='BBC BASIC II', single_byte=mappingproxy({128: 'AND', 148: 'ABS', 149: 'ACS', 150: 'ADVAL', 151: 'ASC', 152: 'ASN', 153: 'ATN', 198: 'AUTO', 154: 'BGET', 213: 'BPUT', 251: 'COLOUR', 214: 'CALL', 215: 'CHAIN', 189: 'CHR$', 216: 'CLEAR', 217: 'CLOSE', 218: 'CLG', 219: 'CLS', 155: 'COS', 156: 'COUNT', 220: 'DATA', 157: 'DEG', 221: 'DEF', 199: 'DELETE', 129: 'DIV', 222: 'DIM', 223: 'DRAW', 225: 'ENDPROC', 224: 'END', 226: 'ENVELOPE', 139: 'ELSE', 160: 'EVAL', 158: 'ERL', 133: 'ERROR', 197: 'EOF', 130: 'EOR', 159: 'ERR', 161: 'EXP', 162: 'EXT', 227: 'FOR', 163: 'FALSE', 164: 'FN', 229: 'GOTO', 190: 'GET$', 165: 'GET', 228: 'GOSUB', 230: 'GCOL', 147: 'HIMEM', 232: 'INPUT', 231: 'IF', 191: 'INKEY$', 166: 'INKEY', 168: 'INT', 167: 'INSTR(', 201: 'LIST', 134: 'LINE', 200: 'LOAD', 146: 'LOMEM', 234: 'LOCAL', 192: 'LEFT$(', 169: 'LEN', 233: 'LET', 171: 'LOG', 170: 'LN', 193: 'MID$(', 235: 'MODE', 131: 'MOD', 236: 'MOVE', 237: 'NEXT', 202: 'NEW', 172: 'NOT', 203: 'OLD', 238: 'ON', 135: 'OFF', 132: 'OR', 142: 'OPENIN', 174: 'OPENOUT', 173: 'OPENUP', 255: 'OSCLI', 241: 'PRINT', 144: 'PAGE', 143: 'PTR', 175: 'PI', 240: 'PLOT', 176: 'POINT(', 242: 'PROC', 177: 'POS', 248: 'RETURN', 245: 'REPEAT', 246: 'REPORT', 243: 'READ', 244: 'REM', 249: 'RUN', 178: 'RAD', 247: 'RESTORE', 194: 'RIGHT$(', 179: 'RND', 204: 'RENUMBER', 136: 'STEP', 205: 'SAVE', 180: 'SGN', 181: 'SIN', 182: 'SQR', 137: 'SPC', 195: 'STR$', 196: 'STRING$(', 212: 'SOUND', 250: 'STOP', 183: 'TAN', 140: 'THEN', 184: 'TO', 138: 'TAB(', 252: 'TRACE', 145: 'TIME', 185: 'TRUE', 253: 'UNTIL', 186: 'USR', 239: 'VDU', 187: 'VAL', 188: 'VPOS', 254: 'WIDTH', 208: 'PAGE', 207: 'PTR', 209: 'TIME', 210: 'LOMEM', 211: 'HIMEM'}), escape=mappingproxy({})))¶
Walk a
&0D-framed stored program, scanning each line body.- Parameters:
- Yields:
One
LineRecordper line, in storage order, each carrying the line number and its bodyTokenstream.- Raises:
DetokeniseError – The framing is malformed — a missing line marker, a truncated header or reference, or an impossible length byte.
- Return type:
- class oaknut.basic.Token(kind, value, start, token=None)¶
One unit of a scanned BBC BASIC line body.
- value¶
The keyword spelling (
KEYWORD), the literal run (TEXT), the quoted string including its quotes (STRING), or the decimal line number (LINENUM).- Type:
- class oaknut.basic.TokenKind(*values)¶
The kind of a scanned
Token.- KEYWORD = 'KEYWORD'¶
A keyword token byte, resolved to its spelling via the token table.
- TEXT = 'TEXT'¶
A run of literal bytes — identifiers, numbers, operators, punctuation, spaces, and any unknown high byte — for the consumer to sub-lex. Each byte
bappears aschr(b).
- STRING = 'STRING'¶
A
"..."string literal, copied verbatim through the closing quote (or the end of the body, if unterminated).
- LINENUM = 'LINENUM'¶
A
&8Dthree-byte line-number reference, decoded to decimal.
Dialects¶
BBC BASIC II is the BBC Micro’s 8-bit language ROM. BBC BASIC V — the
Archimedes / RISC OS ARM BASIC — keeps that token table but re-uses the
&C6/&C7/&C8 command bytes as two-byte escape prefixes (the
following byte selects an extended keyword such as SUM, RENUMBER,
CASE, SYS or ORIGIN) and re-purposes several single-byte
slots (&7F becomes OTHERWISE; &C9-&CE become the block
keywords WHEN/OF/ENDCASE/ELSE/ENDIF/ENDWHILE).
De-tokenising a BASIC V program with the BASIC II tables corrupts every
extended keyword, so detokenise(),
detokenise_body(), scan() and
scan_program() take a dialect argument.
BASIC_II is the default everywhere.
- class oaknut.basic.Dialect(name, single_byte, escape)¶
A BBC BASIC variant’s token-to-keyword mapping.
- single_byte¶
Maps a single token byte to its keyword spelling. Covers the whole one-byte keyword range, including the few sub-
&80tokens such as&7F(OTHERWISE).- Type:
- escape¶
Maps a two-byte escape prefix (
&C6/&C7/&C8in BASIC V) to a table from the following byte to its extended keyword. Empty for dialects without escape tokens.- Type:
collections.abc.Mapping[int, collections.abc.Mapping[int, str]]
Token table¶
The BBC BASIC II keyword/token table and flag definitions, for a byte-level consumer that resolves tokens itself. The mapping is exposed read-only; mutating it would corrupt the tokeniser and de-tokeniser.
- oaknut.basic.TOKEN_TO_KEYWORD¶
Read-only proxy of a mapping.
- oaknut.basic.KEYWORDS¶
Built-in immutable sequence.
If no argument is given, the constructor returns an empty tuple. If iterable is specified the tuple is initialized from iterable’s items.
If the argument is a tuple, the return value is the same object.
- oaknut.basic.LINE_NUMBER_TOKEN = 141¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.decode_line_number(payload)¶
Decode the 3 payload bytes following
&8Dback to a line number.- Parameters:
payload (bytes) – Exactly the three bytes after the
&8Dtoken.- Returns:
The decoded line number.
- Raises:
ValueError – payload is not exactly three bytes.
- Return type:
The crunch flag bits, as read from the disassembly’s bit-by-bit dispatch:
- oaknut.basic.FLAG_CONDITIONAL = 1¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_MIDDLE = 2¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_START = 4¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_FN_PROC = 8¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_LINE_NUMBER = 16¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_STOP_LINE = 32¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.FLAG_PSEUDO_VAR = 64¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
Data files¶
The channel-based files BBC BASIC creates with OPENOUT and writes
with PRINT# / BPUT#. The module-level open()
mirrors the built-in open: a mode string selects a reader, a
writer, or a combined read/write object, and accepts a path or an
already-open binary stream.
- oaknut.basic.datafile.open(file, mode='r', *, encoding='acorn', newline=None, errors='strict')¶
Open a BBC BASIC data file, styled on the built-in
open.- Parameters:
file (str | PathLike | IO[bytes]) – A filesystem path, or an already-open binary stream (e.g.
io.BytesIO). A path is opened and owned (closed on exit); a stream is borrowed and left open, but is repositioned to suit mode (rewound for read/update, truncated forw, moved to the end for append).mode (str) – One of
r(read),w(create/truncate for write),a(append), or the+update variantsr+/w+/a+(read and write). Binary is implicit.encoding (str) – Codec for string records; the BBC
acornset by default.newline (str | None) – Newline translation, as for the built-in
open. The default (None) translates\nto the Acorn\ron write and any of\r\n/\r/\nto\non read.""disables translation.errors (str) – Codec error policy for string records.
- Returns:
A
BbcBasicDataReader,BbcBasicDataWriterorBbcBasicDataFileaccording to mode.- Raises:
ValueError – mode or newline is not recognised.
- Return type:
The three classes form a diamond: a shared base carries positioning and lifecycle, the reader and writer add their record vocabularies, and the combined class inherits both for the update modes.
- class oaknut.basic.BbcBasicDataReader(stream, *, owns_stream, encoding, newline, errors)¶
A BBC BASIC data file open for reading (
OPENIN).- read()¶
Read the next tagged record, adapting to its type.
Returns the value as an
int,floatorstraccording to the record’s type tag, orNoneat end of file (so a read loop can be writtenwhile (v := f.read()) is not None).- Raises:
UnknownTagError – The next byte is not a known type tag.
TruncatedRecordError – A record ran past the end of the file.
- Return type:
- read_int()¶
Read the next record, which must be an integer.
- Raises:
EOFError – At end of file.
DataFileTypeMismatchError – The next record is not an integer (the position is restored to the start of the record).
TruncatedRecordError – The record ran past the end of the file.
- Return type:
- read_float()¶
Read the next record, which must be a real. See
read_int().- Return type:
- read_str()¶
Read the next record, which must be a string. See
read_int().- Return type:
- class oaknut.basic.BbcBasicDataWriter(stream, *, owns_stream, encoding, newline, errors)¶
A BBC BASIC data file open for writing (
OPENOUT/ append).- write(value)¶
Write value, choosing the record type from its Python type.
int→ integer record,float→ real record,str→ string record,bytes/bytearray→ raw bytes (untagged, asBPUT#would).booland any other type raiseTypeError; BBC BASIC has no boolean type.
- write_int(value)¶
Write an integer record. Raises
TypeErrorfor a non-int.- Raises:
IntegerRangeError – The value is outside the signed 32-bit range.
- Parameters:
value (int)
- Return type:
None
- write_float(value)¶
Write a real record. Raises
TypeErrorfor a non-float.- Raises:
Float5RangeError – The value is too large for the 5-byte format.
- Parameters:
value (float)
- Return type:
None
- write_str(value)¶
Write a string record. Raises
TypeErrorfor a non-str.- Raises:
StringTooLongError – The encoded string exceeds 255 bytes.
- Parameters:
value (str)
- Return type:
None
- write_bytes(value)¶
Write raw bytes with no tag or framing (
BPUT#).
- write_byte(value)¶
Write one raw byte (
BPUT#).- Raises:
TypeError – value is not an int.
ValueError – value is not in the range 0-255.
- Parameters:
value (int)
- Return type:
None
- class oaknut.basic.BbcBasicDataFile(stream, *, owns_stream, encoding, newline, errors)¶
A BBC BASIC data file open for both reading and writing (
OPENUP).
- class oaknut.basic.BbcBasicDataFileBase(stream, *, owns_stream, encoding, newline, errors)¶
Common state and positioning for a BBC BASIC data file.
Holds the underlying binary stream, the string encoding and newline policy, and ownership of the stream (a path opened by
open()is owned and closed on exit; a caller-supplied stream is borrowed and left open). Subclasses add the read and/or write vocabulary.- close()¶
Close the file, closing the stream only if this object owns it.
- Return type:
None
- seek(position, whence=0)¶
Set the current byte position (
PTR#=); return the new position.
A real number is stored in the BBC’s packed 5-byte format. These two
functions convert between that format and a Python float in the
natural exponent-first byte order, for reuse beyond data files:
- oaknut.basic.pack_float5(value)¶
Encode a Python float into five exponent-first packed bytes.
The 32-bit significand is rounded to nearest, ties to even. A tie that rounds the significand up to
1.0carries into the exponent rather than overflowing. Magnitudes below the smallest normal underflow to zero (the format has no denormals).- Parameters:
value (float) – A finite Python float.
- Returns:
Five bytes in natural (exponent-first) order.
0.0(and-0.0) encode to five zero bytes.- Raises:
Float5RangeError – If value is non-finite or its magnitude is too large for the excess-128 exponent.
- Return type:
- oaknut.basic.unpack_float5(packed)¶
Decode five exponent-first packed bytes into a Python float.
- Parameters:
packed (bytes) – Exactly five bytes in natural (exponent-first) order.
- Returns:
The decoded value. A zero exponent decodes to
0.0regardless of the remaining bytes.- Raises:
ValueError – If packed is not exactly five bytes long.
- Return type:
Constants¶
Canonical load addresses for a tokenised program on each host machine —
the addresses *SAVE uses by default:
- oaknut.basic.BBC_BASIC_LOAD_ADDRESS = 6400¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.ELECTRON_BASIC_LOAD_ADDRESS = 3584¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
The default AUTO numbering parameters, shared by
number_lines() and tokenise():
- oaknut.basic.DEFAULT_LINE_NUMBER = 10¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
- oaknut.basic.DEFAULT_LINE_STEP = 10¶
int([x]) -> integer int(x, base=10) -> integer
Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.
If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer iteral. >>> int(‘0b100’, base=0) 4
Exceptions¶
Every error derives from BASICError, itself a
oaknut.exception.DataError. The two families mirror the two
directions; see Error handling for the hierarchy and how to
catch them.
- exception oaknut.basic.BASICError(*args, exit_code=None)¶
Base for every BBC BASIC tokeniser and de-tokeniser error.
- Parameters:
args (object)
exit_code (Optional[ExitCode])
- Return type:
None
Tokenising — source text in:
- exception oaknut.basic.TokeniseError(message, *, line_index, line_text)¶
Base for an error in BBC BASIC source being tokenised.
- line_index¶
1-based position of the offending line in the source.
- line_text¶
The offending line, verbatim (no trailing newline).
- exception oaknut.basic.UnnumberedLineError(line_index, line_text)¶
A source line has no leading line number.
Raised when tokenising without auto-numbering: every line must begin with a line number (as a saved
LISTwould).
- exception oaknut.basic.AlreadyNumberedError(line_index, line_number, line_text)¶
Auto-numbering was requested but the source is already numbered.
- line_number¶
The line number already present on the line.
- exception oaknut.basic.LineNumberRangeError(line_index, line_number, line_text)¶
A line number is outside the valid range.
- line_number¶
The out-of-range line number.
- exception oaknut.basic.LineNumberOrderError(line_index, line_number, previous_line_number, line_text)¶
A line number does not exceed the line before it.
BBC BASIC stores lines in strictly ascending order; a listing that repeats or rewinds a line number could not have come from
LIST.- Parameters:
- Return type:
None
- line_number¶
The offending line number.
- previous_line_number¶
The line number of the preceding line.
- exception oaknut.basic.LineTooLongError(line_index, line_number, length, line_text)¶
A tokenised line exceeds the maximum storable length.
- line_number¶
The BASIC line number that overflowed.
- length¶
The tokenised body length in bytes.
De-tokenising — token stream in:
- exception oaknut.basic.DetokeniseError(message, *, offset)¶
Base for an error in a tokenised program being de-tokenised.
- offset¶
Byte offset into the program where the fault was found.
- exception oaknut.basic.MissingLineMarkerError(offset, found)¶
A line did not begin with the expected
&0Dmarker.- found¶
The byte found where the marker was expected.
- exception oaknut.basic.TruncatedProgramError(offset, detail)¶
The token stream ended in the middle of a structure.
- detail¶
What was being read when the bytes ran out.
- exception oaknut.basic.InvalidLineLengthError(offset, length)¶
A line’s length byte is impossible.
- length¶
The offending length byte.
Data files — reading and writing records:
- exception oaknut.basic.DataFileError(message, *, offset=None)¶
Base for a fault reading or writing a BBC BASIC data file.
- offset¶
Byte offset of the record where the fault was found, or
Nonewhen the fault is not tied to a position.
- exception oaknut.basic.UnknownTagError(tag, offset)¶
A tagged record began with a byte that is not a known type tag.
A
PRINT#record starts with&00(string),&40(integer) or&FF(real); anything else means the stream is not tagged data at this point (often rawBPUT#bytes read as if tagged).- tag¶
The offending tag byte.
- exception oaknut.basic.DataFileTypeMismatchError(expected, tag, offset)¶
A typed read found a record of a different type.
Mirrors BASIC’s Type mismatch on
INPUT#: the record’s tag did not match the type the caller asked for. The stream position is left at the start of the record so the caller can re-read it.- expected¶
Human-readable name of the requested type.
- tag¶
The tag byte actually found.
- exception oaknut.basic.TruncatedRecordError(offset, detail)¶
A tagged record ran past the end of the stream.
Distinct from a clean end of file (which a typed read signals with
EOFError): the tag, or part of the payload, was present but the rest of the record was missing — the stream is corrupt.- detail¶
What was being read when the bytes ran out.
- exception oaknut.basic.IntegerRangeError(value)¶
An integer is outside the signed 32-bit range a BBC integer holds.
- Parameters:
value (int)
- Return type:
None
- value¶
The offending integer.
- exception oaknut.basic.StringTooLongError(length)¶
A string is longer than the single length byte can describe.
A
PRINT#string is prefixed by one length byte, so it can hold at most 255 characters.- Parameters:
length (int)
- Return type:
None
- length¶
The offending string length.
- exception oaknut.basic.Float5RangeError(value)¶
A Python float is too large for the 5-byte BBC REAL format.
The packed format carries an excess-128 exponent, so the largest representable magnitude is just under
2**127. Underflow is not an error — the format has no denormals, so tiny magnitudes flush to zero — but overflow (and any non-finite value) cannot be stored.- Parameters:
value (float)
- Return type:
None
- value¶
The offending Python float.