Name | spectrum-basic JSON |
Version |
0.6.6
JSON |
| download |
home_page | None |
Summary | A parser and language tool for ZX Spectrum BASIC |
upload_time | 2025-02-08 22:26:50 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.10 |
license | MIT |
keywords |
zx-spectrum
basic
retro
compiler
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Spectrum BASIC Tools
A Python toolkit for parsing, transforming, and manipulating ZX Spectrum BASIC programs. This tool can help you work with both classic Spectrum BASIC and an enhanced dialect that supports modern programming constructs.
## Features
- Full parser for ZX Spectrum BASIC
- Support for an enhanced dialect with:
- Optional line numbers
- Labels (e.g., `@loop:`)
- Label references in expressions and GOTOs
- Additional control structures from Spectrum Next BASIC
- Program transformations:
- Line numbering and renumbering
- Variable name minimization
- Label elimination (for Spectrum compatibility)
- Detailed variable analysis
- Pretty printing with authentic Spectrum BASIC formatting
- TAP file generation for loading programs on a real Spectrum
- Run a subset of BASIC programs locally for algorithm testing
## Installation
For developer mode, clone the repository and install the package in editable mode:
```bash
git clone https://github.com/imneme/spectrum-basic.git
cd spectrum-basic
pip install -e .
```
Install from PyPI:
```bash
pip install spectrum-basic
```
Requires Python 3.10 or later.
## Usage
### Command Line
The package installs a command-line tool called `speccy-basic`:
```bash
# Show the parsed and pretty-printed program
speccy-basic program.bas --show
# Number unnumbered lines and remove labels
speccy-basic program.bas --delabel
# Convert Spectrum Next control structures to GOTOs
speccy-basic program.bas --decontrol
# Minimize variable names
speccy-basic program.bas --minimize
# Combine transformations
speccy-basic program.bas --delabel --minimize
# Analyze variables
speccy-basic program.bas --find-vars
# Generate a TAP file
speccy-basic program.bas --tap output.tap --tap-name "My Program"
# Run a program locally
speccy-basic program.bas --run
```
### As a Library
```python
from spectrum_basic import parse_file, number_lines, minimize_variables, make_program_tap, write_tap
# Parse a program
program = parse_file("my_program.bas")
# Apply transformations
number_lines(program, remove_labels=True)
minimize_variables(program)
# Output the result
str(program)
# Program image and tape generation
binary_code = bytes(program)
tap = make_program_tap(binary_code, name="My Program", autostart=9000)
write_tap(tap, "output.tap")
```
## Enhanced BASIC Features
The tool supports an enhanced dialect of BASIC that's compatible with ZX Spectrum BASIC. Additional features include:
### Labels
```basic
@loop:
FOR I = 1 TO 10
PRINT I
NEXT I
GOTO @loop
```
Label names are written `@identifier`. Lines are labeled by putting the label at the start of the line, followed by a colon. They can be used anywhere where you would write a line number, including:
- `GOTO`/`GOSUB` statements
- Arithmetic expressions (e.g., `(@end - @start)/10`)
### Spectrum Next Control Structures
The version of Spectrum BASIC used on the Spectrum Next includes a variety of additional new features. This tool can supports _some_ of those features, converting them to equivalent ZX Spectrum BASIC constructs.
The option `--decontrol` will convert these constructs to ZX Spectrum BASIC, using auto-generated labels, and adding `--delabel` will remove the labels to make the program compatible with classic ZX Spectrum BASIC.
#### Multi-line `IF` statements
On the Spectrum Next, if we omit `THEN`, we can write multi-line `IF` statements:
```basic
IF A = 1
PRINT "One"
ELSE IF A = 2
PRINT "Two"
ELSE
PRINT "Not one or two"
END IF
PRINT "Done"
```
becomes :
```basic
10 IF A <> 1 THEN GOTO 40
20 PRINT "One"
30 GOTO 80
40 IF A <> 2 THEN GOTO 70
50 PRINT "Two"
60 GOTO 80
70 PRINT "Not one or two"
80 PRINT "Done"
```
(`ELSE IF` can also be written as `ELSEIF`, `ELSIF` or `ELIF`.)
#### `REPEAT` loops
Spectrum Next BASIC has a `REPEAT` loop construct (where the loop ends with `REPEAT UNTIL` and not just `UNTIL`; an infinite loop would be `REPEAT UNTIL 0`):
```basic
LET I = 0
REPEAT
PRINT "Hello ";I
LET I = I * 2
REPEAT UNTIL I = 16
```
Translated to Classic ZX Spectrum BASIC, this becomes:
```basic
10 LET I = 0
20 PRINT "Hello ";I
30 LET I = I * 2
40 IF I <> 16 THEN GOTO 20
```
#### Adding an exit condition with `WHILE`
`WHILE` is not a looping construct itself, it's a way to add an exit condition to a `REPEAT` loop:
```basic
LET I = 0
REPEAT
LET I = I * 2
WHILE I < 16
PRINT "Hello ";I
REPEAT UNTIL 0
```
is transformed to:
```
10 LET I = 0
20 LET I = I * 2
30 IF I >= 16 THEN GOTO 60
40 PRINT "Hello ";I
50 GOTO 20
```
#### Exiting a loop with `EXIT`
Both `FOR` and `REPEAT` loops can be exited early with `EXIT`:
```basic
FOR I = 1 TO 10
FOR J = 1 TO 10
IF I * J > 50 THEN EXIT
PRINT I * J
NEXT J
NEXT I
```
becomes:
```basic
10 FOR I = 1 TO 10
20 FOR J = 1 TO 10
30 IF I * J > 50 THEN GOTO 60
40 PRINT I * J
50 NEXT J
60 NEXT I
```
To exit from multiple levels of loop, just write multiple `EXIT` statements. Changing the above code to use `EXIT:EXIT` would exit both loops.
You can also use `EXIT` like a `GOTO` statement to exit the loop and continue execution at a specific line number. This is just translated to a `GOTO` statement when translating code to classic ZX Spectrum BASIC, but is necessary on the Spectrum Next to allow the BASIC interpreter to know that the loop is being exited.
#### Continuing a loop with `GOTO NEXT`
Many languages have a `continue` statement to skip the rest of the loop and go to the next iteration. Spectrum Next BASIC does not have this feature, but the translation tool does support it. You can write:
```basic
DEF FN M(x,d) = x - d*INT(x/d)
FOR I = 1 TO 10
IF M(I,2) = 0 THEN GOTO NEXT
PRINT I
NEXT I
```
becomes:
```basic
10 DEF FN M(x, d) = x - d * INT (x / d)
20 FOR I = 1 TO 10
30 IF M(I, 2) = 0 THEN GOTO 50
40 PRINT I
50 NEXT I
```
(You can also use `GOTO NEXT NEXT` to the continuation of an outer loop, and so on.)
## Working with the AST
If you want to analyze or transform BASIC programs, you'll need to work with the Abstract Syntax Tree (AST) that represents the program's structure. Import the AST nodes from the ast module:
```python
from spectrum_basic.ast import Variable, Number, Label, BuiltIn
```
The AST nodes have attributes that correspond to the fields of the original BASIC code. For example:
```text
>>> from spectrum_basic import *
>>> prog = parse_string('10 PRINT "Hello World!";')
>>> len(prog.lines)
1
>>> (stmt := prog.lines[0].statements[0])
PRINT "Hello World!";
>>> (arg := stmt.args[0])
"Hello World!";
>>> arg.value
"Hello World!"
>>> arg.sep
';'
```
However, for many applications where you want to traverse syntax tree, you may prefer to use the AST walking API described below.
### AST Walking
The AST can be traversed using the `walk()` generator, which yields tuples of `(event, node)`. Events are:
```python
class Walk(Enum):
ENTERING = auto() # Entering a compound node
VISITING = auto() # At a leaf node or simple value
LEAVING = auto() # Leaving a compound node
```
Example usage:
```python
def find_variables(program):
"""Find all variables in a program"""
variables = set()
for event, obj in walk(program):
if event == Walk.VISITING and isinstance(obj, Variable):
variables.add(obj.name)
return sorted(variables)
```
You can control traversal by sending `Walk.SKIP` back to the generator to skip processing a node's children. You can also just abandon the generator at any time.
### Key AST Nodes
Common patterns for matching AST nodes:
```python
# Basic nodes
Variable(name=str) # Variable reference (e.g., "A" or "A$")
Number(value=int|float) # Numeric literal
Label(name=str) # Label reference (e.g., "@loop")
# Built-in commands/functions (most statements)
BuiltIn(action=str, # Command name (e.g., "PRINT", "GOTO")
args=tuple) # Command arguments
# Statements that don't just take expressions usually have their own AST nodes
# for example:
Let(var=Variable|ArrayRef, # Assignment statement
expr=Expression) # Expression to assign
# Program structure
Program(lines=list) # Complete program
SourceLine( # Single line of code
line_number=int|None,
label=Label|None,
statements=list)
```
Example pattern matching:
```python
match obj:
case BuiltIn(action="GOTO", args=[target]) if isinstance(target, Number):
# Handle simple GOTO with numeric line number
line_num = target.value
...
case Variable(name=name) if name.endswith("$"):
# Handle string variable
...
```
## License
MIT License. Copyright (c) 2024 Melissa O'Neill
## Requirements
- Python 3.10 or later
- TextX parsing library
Raw data
{
"_id": null,
"home_page": null,
"name": "spectrum-basic",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "zx-spectrum, basic, retro, compiler",
"author": null,
"author_email": "\"Melissa E. O'Neill\" <oneill@acm.org>",
"download_url": "https://files.pythonhosted.org/packages/0f/a1/58586558201acc65549fb7607e5c34ead40538941b68bc369d3e5203a896/spectrum_basic-0.6.6.tar.gz",
"platform": null,
"description": "# Spectrum BASIC Tools\n\nA Python toolkit for parsing, transforming, and manipulating ZX Spectrum BASIC programs. This tool can help you work with both classic Spectrum BASIC and an enhanced dialect that supports modern programming constructs.\n\n## Features\n\n- Full parser for ZX Spectrum BASIC\n- Support for an enhanced dialect with:\n - Optional line numbers\n - Labels (e.g., `@loop:`)\n - Label references in expressions and GOTOs\n - Additional control structures from Spectrum Next BASIC\n- Program transformations:\n - Line numbering and renumbering\n - Variable name minimization\n - Label elimination (for Spectrum compatibility)\n- Detailed variable analysis\n- Pretty printing with authentic Spectrum BASIC formatting\n- TAP file generation for loading programs on a real Spectrum\n- Run a subset of BASIC programs locally for algorithm testing\n\n## Installation\n\nFor developer mode, clone the repository and install the package in editable mode:\n\n```bash\ngit clone https://github.com/imneme/spectrum-basic.git\ncd spectrum-basic\npip install -e .\n```\n\nInstall from PyPI:\n\n```bash\npip install spectrum-basic\n``` \n\n\nRequires Python 3.10 or later. \n\n## Usage\n\n### Command Line\n\nThe package installs a command-line tool called `speccy-basic`:\n\n```bash\n# Show the parsed and pretty-printed program\nspeccy-basic program.bas --show\n\n# Number unnumbered lines and remove labels\nspeccy-basic program.bas --delabel\n\n# Convert Spectrum Next control structures to GOTOs\nspeccy-basic program.bas --decontrol\n\n# Minimize variable names\nspeccy-basic program.bas --minimize\n\n# Combine transformations\nspeccy-basic program.bas --delabel --minimize\n\n# Analyze variables\nspeccy-basic program.bas --find-vars\n\n# Generate a TAP file\nspeccy-basic program.bas --tap output.tap --tap-name \"My Program\"\n\n# Run a program locally\nspeccy-basic program.bas --run\n```\n\n### As a Library\n\n```python\nfrom spectrum_basic import parse_file, number_lines, minimize_variables, make_program_tap, write_tap\n\n# Parse a program\nprogram = parse_file(\"my_program.bas\")\n\n# Apply transformations\nnumber_lines(program, remove_labels=True)\nminimize_variables(program)\n\n# Output the result\nstr(program)\n\n# Program image and tape generation\nbinary_code = bytes(program)\ntap = make_program_tap(binary_code, name=\"My Program\", autostart=9000)\nwrite_tap(tap, \"output.tap\")\n```\n\n## Enhanced BASIC Features\n\nThe tool supports an enhanced dialect of BASIC that's compatible with ZX Spectrum BASIC. Additional features include:\n\n### Labels\n```basic\n@loop:\nFOR I = 1 TO 10\n PRINT I\nNEXT I\nGOTO @loop\n```\n\nLabel names are written `@identifier`. Lines are labeled by putting the label at the start of the line, followed by a colon. They can be used anywhere where you would write a line number, including:\n\n- `GOTO`/`GOSUB` statements\n- Arithmetic expressions (e.g., `(@end - @start)/10`)\n\n### Spectrum Next Control Structures\n\nThe version of Spectrum BASIC used on the Spectrum Next includes a variety of additional new features. This tool can supports _some_ of those features, converting them to equivalent ZX Spectrum BASIC constructs.\n\nThe option `--decontrol` will convert these constructs to ZX Spectrum BASIC, using auto-generated labels, and adding `--delabel` will remove the labels to make the program compatible with classic ZX Spectrum BASIC.\n\n#### Multi-line `IF` statements\n\nOn the Spectrum Next, if we omit `THEN`, we can write multi-line `IF` statements:\n\n```basic\nIF A = 1\n PRINT \"One\"\nELSE IF A = 2\n PRINT \"Two\"\nELSE\n PRINT \"Not one or two\"\nEND IF\nPRINT \"Done\"\n```\n\nbecomes :\n\n```basic\n10 IF A <> 1 THEN GOTO 40\n20 PRINT \"One\"\n30 GOTO 80\n40 IF A <> 2 THEN GOTO 70\n50 PRINT \"Two\"\n60 GOTO 80\n70 PRINT \"Not one or two\"\n80 PRINT \"Done\"\n```\n\n(`ELSE IF` can also be written as `ELSEIF`, `ELSIF` or `ELIF`.)\n\n#### `REPEAT` loops\n\nSpectrum Next BASIC has a `REPEAT` loop construct (where the loop ends with `REPEAT UNTIL` and not just `UNTIL`; an infinite loop would be `REPEAT UNTIL 0`):\n\n```basic\nLET I = 0\nREPEAT\n PRINT \"Hello \";I\n LET I = I * 2\nREPEAT UNTIL I = 16\n```\n\nTranslated to Classic ZX Spectrum BASIC, this becomes:\n\n```basic\n10 LET I = 0\n20 PRINT \"Hello \";I\n30 LET I = I * 2\n40 IF I <> 16 THEN GOTO 20\n```\n\n#### Adding an exit condition with `WHILE`\n\n`WHILE` is not a looping construct itself, it's a way to add an exit condition to a `REPEAT` loop:\n\n```basic\nLET I = 0\nREPEAT\n LET I = I * 2\n WHILE I < 16\n PRINT \"Hello \";I\nREPEAT UNTIL 0\n```\n\nis transformed to:\n\n```\n10 LET I = 0\n20 LET I = I * 2\n30 IF I >= 16 THEN GOTO 60\n40 PRINT \"Hello \";I\n50 GOTO 20\n```\n\n#### Exiting a loop with `EXIT`\n\nBoth `FOR` and `REPEAT` loops can be exited early with `EXIT`:\n\n```basic\nFOR I = 1 TO 10\n FOR J = 1 TO 10\n IF I * J > 50 THEN EXIT\n PRINT I * J\n NEXT J\nNEXT I\n```\n\nbecomes:\n\n```basic\n10 FOR I = 1 TO 10\n20 FOR J = 1 TO 10\n30 IF I * J > 50 THEN GOTO 60\n40 PRINT I * J\n50 NEXT J\n60 NEXT I\n```\n\nTo exit from multiple levels of loop, just write multiple `EXIT` statements. Changing the above code to use `EXIT:EXIT` would exit both loops.\n\nYou can also use `EXIT` like a `GOTO` statement to exit the loop and continue execution at a specific line number. This is just translated to a `GOTO` statement when translating code to classic ZX Spectrum BASIC, but is necessary on the Spectrum Next to allow the BASIC interpreter to know that the loop is being exited.\n\n#### Continuing a loop with `GOTO NEXT`\n\nMany languages have a `continue` statement to skip the rest of the loop and go to the next iteration. Spectrum Next BASIC does not have this feature, but the translation tool does support it. You can write:\n\n```basic\nDEF FN M(x,d) = x - d*INT(x/d)\nFOR I = 1 TO 10\n IF M(I,2) = 0 THEN GOTO NEXT\n PRINT I\nNEXT I\n```\n\nbecomes:\n\n```basic\n10 DEF FN M(x, d) = x - d * INT (x / d)\n20 FOR I = 1 TO 10\n30 IF M(I, 2) = 0 THEN GOTO 50\n40 PRINT I\n50 NEXT I\n```\n\n(You can also use `GOTO NEXT NEXT` to the continuation of an outer loop, and so on.)\n\n## Working with the AST\n\nIf you want to analyze or transform BASIC programs, you'll need to work with the Abstract Syntax Tree (AST) that represents the program's structure. Import the AST nodes from the ast module:\n\n```python\nfrom spectrum_basic.ast import Variable, Number, Label, BuiltIn\n```\n\nThe AST nodes have attributes that correspond to the fields of the original BASIC code. For example:\n\n```text\n>>> from spectrum_basic import *\n>>> prog = parse_string('10 PRINT \"Hello World!\";')\n>>> len(prog.lines)\n1\n>>> (stmt := prog.lines[0].statements[0])\nPRINT \"Hello World!\";\n>>> (arg := stmt.args[0])\n\"Hello World!\";\n>>> arg.value\n\"Hello World!\"\n>>> arg.sep\n';'\n```\n\nHowever, for many applications where you want to traverse syntax tree, you may prefer to use the AST walking API described below.\n\n### AST Walking\n\nThe AST can be traversed using the `walk()` generator, which yields tuples of `(event, node)`. Events are:\n\n```python\nclass Walk(Enum):\n ENTERING = auto() # Entering a compound node\n VISITING = auto() # At a leaf node or simple value\n LEAVING = auto() # Leaving a compound node\n```\n\nExample usage:\n\n```python\ndef find_variables(program):\n \"\"\"Find all variables in a program\"\"\"\n variables = set()\n for event, obj in walk(program):\n if event == Walk.VISITING and isinstance(obj, Variable):\n variables.add(obj.name)\n return sorted(variables)\n```\n\nYou can control traversal by sending `Walk.SKIP` back to the generator to skip processing a node's children. You can also just abandon the generator at any time.\n\n### Key AST Nodes\n\nCommon patterns for matching AST nodes:\n\n```python\n# Basic nodes\nVariable(name=str) # Variable reference (e.g., \"A\" or \"A$\")\nNumber(value=int|float) # Numeric literal\nLabel(name=str) # Label reference (e.g., \"@loop\")\n\n# Built-in commands/functions (most statements)\nBuiltIn(action=str, # Command name (e.g., \"PRINT\", \"GOTO\")\n args=tuple) # Command arguments\n\n# Statements that don't just take expressions usually have their own AST nodes\n# for example:\nLet(var=Variable|ArrayRef, # Assignment statement\n expr=Expression) # Expression to assign\n\n# Program structure\nProgram(lines=list) # Complete program\nSourceLine( # Single line of code\n line_number=int|None,\n label=Label|None,\n statements=list)\n```\n\nExample pattern matching:\n\n```python\nmatch obj:\n case BuiltIn(action=\"GOTO\", args=[target]) if isinstance(target, Number):\n # Handle simple GOTO with numeric line number\n line_num = target.value\n ...\n case Variable(name=name) if name.endswith(\"$\"):\n # Handle string variable\n ...\n```\n\n## License\n\nMIT License. Copyright (c) 2024 Melissa O'Neill\n\n## Requirements\n\n- Python 3.10 or later\n- TextX parsing library\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A parser and language tool for ZX Spectrum BASIC",
"version": "0.6.6",
"project_urls": {
"Bug Tracker": "https://github.com/imneme/spectrum-basic/issues",
"Documentation": "https://github.com/imneme/spectrum-basic#readme",
"Homepage": "https://github.com/imneme/spectrum-basic"
},
"split_keywords": [
"zx-spectrum",
" basic",
" retro",
" compiler"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "de43d7587aabbef340c8c0c8cc45818a17434a6129c1b97e3047e52fde918e31",
"md5": "b7c8a917ddaae4ca3ed71b4695667424",
"sha256": "3eb8da0eb091582926d9a71c502cc34d56280817d9d309de4ae9f64ec067573e"
},
"downloads": -1,
"filename": "spectrum_basic-0.6.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b7c8a917ddaae4ca3ed71b4695667424",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 50741,
"upload_time": "2025-02-08T22:26:48",
"upload_time_iso_8601": "2025-02-08T22:26:48.458988Z",
"url": "https://files.pythonhosted.org/packages/de/43/d7587aabbef340c8c0c8cc45818a17434a6129c1b97e3047e52fde918e31/spectrum_basic-0.6.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "0fa158586558201acc65549fb7607e5c34ead40538941b68bc369d3e5203a896",
"md5": "13d85a6d0f17258fa12bcf528c8ee8c0",
"sha256": "58691dcd1e54e8bf35128eec0b298baa4c64aba372616f01137ffa3aae61c510"
},
"downloads": -1,
"filename": "spectrum_basic-0.6.6.tar.gz",
"has_sig": false,
"md5_digest": "13d85a6d0f17258fa12bcf528c8ee8c0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 51647,
"upload_time": "2025-02-08T22:26:50",
"upload_time_iso_8601": "2025-02-08T22:26:50.869565Z",
"url": "https://files.pythonhosted.org/packages/0f/a1/58586558201acc65549fb7607e5c34ead40538941b68bc369d3e5203a896/spectrum_basic-0.6.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-02-08 22:26:50",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "imneme",
"github_project": "spectrum-basic",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "spectrum-basic"
}