pyPILOT
An implementation of the PILOT (Programmed Inquiry, Learning, or Teaching) programming language.
What is PILOT?
PILOT is a very simple interpreted language created by John Amsden Starkweather in the late 1960s and formalised as a machine-independent specification in 1973 ("PILOT-73"). A predecessor to Logo, it was designed for computer-aided instruction: single-letter commands, text typed straight into the program, and a learner answering questions into an accept buffer.
This project targets ATARI PILOT — the 1980/81 dialect shipped in an Atari
cartridge for the 400/800, authored by Harry B. Stewart. It is not Core PILOT
and not Common PILOT, despite superficial similarity. See
SPEC.md §1.1 and the research notes in history/.
Status
1.0.0. Every run-mode command in the Atari vocabulary is implemented, and
pypilotwith no arguments opens the interactive REPL of the original system. The two device-dependent commands —GR:andSO:, real ATARI PILOT with no host equivalent — parse and then raise a clear error naming the sub-command you asked for, rather than being silently ignored.
Zero third-party runtime dependencies, so it runs on a classroom Raspberry Pi with nothing but CPython. 903 tests, 95% branch coverage, mypy strict, and a 3-OS × 2-Python CI matrix that installs the built wheel and runs a program from it.
Install
$ pip install repilot
$ pypilot examples/summary.pilot
The distribution is repilot; the importable package is pypilot:
from pypilot import parse, PilotState
That is deliberate rather than an oversight. pypilot is taken on PyPI by an
unrelated long-standing package, so the distribution needed a different name,
and renaming the import path as well would have broken every import in the
docs and tests for no benefit. PyPI normalises names, so rePILOT in the web
UI is installed as repilot.
The language specification and implementation history live in
SPEC.md; the release notes in CHANGELOG.md. The
language reference is in docs/language.rst, and the API in
docs/api.rst.
Why use it?
Don't. This is purely a bucket-list project. I've never exactly implemented a language, and this likely won't be a repo of my finest code.
Then, why write it?
Primarily? Safer-at-Home, Covid-19, boredom, and avoiding the dishes.
On a deeper level? Because it's crossed my mind repeatedly for years and doing it will stop me from saying, "You know, I've always wanted to write my own PILOT runtime, wouldn't that be fun this rainy weekend?" before I binge more Netflix.
PILOT was magical to me at 6 or 7 years old. It's simple to understand and has minimal command names which make it easier on learners who type at rates approaching 10 words per hour. Added to which, ATARI's implementation came with a step by step course disguised as a cute book of cartoons.
Development
This project uses uv.
uv sync --all-extras --dev # create .venv and install everything
uv run pytest # run the test suite
uv run ruff check . # lint
uv run ruff format . # format
uv run mypy # type check
uv build # build sdist + wheel
uv run --group docs sphinx-build -W -b html docs docs/_build/html
Every one of those is a release gate, and CI runs all of them on Linux, Windows and macOS — plus a clean-environment install of the built wheel, because building a distribution and being able to install it are different claims.
The packaging tests build real archives and inspect their contents.
tools/scan_secrets.py greps everything Git would stage for API tokens and the
like; run it before any bulk git add -A.
Releasing
See RELEASING.md. Publishing is gated on a tag and uses
trusted publishing, so there is no PyPI token in the repository.
Run a program. examples/summary.pilot demonstrates the whole language:
$ uv run pypilot examples/summary.pilot
HELLO, WORLD
THE SUM OF TWO AND THREE IS 5.
HALF OF IT, TRUNCATED, IS 2.
AND THE REMAINDER IS 1.
EXPRESSIONS HAVE NO OPERATOR PRECEDENCE, SO 1+2*3 IS:
9 (not 7)
AN UNDEFINED STRING PRINTS ITS OWN NAME:
NOT_SET_ANYWHERE
AND NUMBERS WRAP AT 16 BITS WITHOUT COMPLAINING:
-32768
JM: BRANCHES ON WHICH MATCH FIELD WON, BECAUSE M: SETS AN
ORDINAL RATHER THAN A BOOLEAN. TRY IT WITH YES, NO OR MAYBE:
yes
YOU SAID YES - THAT WAS FIELD 1.
U: CALLS A MODULE, AND E: COMES BACK FROM ONE. IT IS ALSO HOW
A PROGRAM ENDS WHEN THERE IS NO MODULE TO RETURN FROM.
NOTE THE J: AFTER THE RETURNING E: - WITHOUT IT EXECUTION WOULD
FALL BACK INTO THE MODULE AND CALL IT AGAIN.
THE MODULE HAS BEEN ENTERED 1 TIMES.
AND A J: LOOPS, AS LONG AS ITS CONDITION HOLDS:
COUNT 1
COUNT 2
COUNT 3
COUNT 4
COUNT 5
The interactive REPL
Run pypilot with no program file and you get the immediate mode of the
original system: type a statement and it runs, or build a program with AUTO
and RUN it. Here is a real session — type the lines shown, answer ada and
yes at the two prompts:
$ uv run pypilot
pilot> AUTO 100,10
AUTO-NUMBER INPUT MODE. AN EMPTY LINE EXITS.
auto> 100 T:WHAT IS YOUR NAME?
auto> 110 A:$NAME
auto> 120 T:HELLO, $NAME.
auto> 130 T:ARE YOU A STUDENT? (YES OR NO)
auto> 140 A:$ANSWER
auto> 150 M: YES , NO
auto> 160 JM:*YES,*NO
auto> 170 T:UNRECOGNISED ANSWER.
auto> 180 J:*END
auto> 190 *YES
auto> 200 T:WELCOME TO THE COURSE.
auto> 210 J:*END
auto> 220 *NO
auto> 230 T:COME BACK ANY TIME.
auto> 240 *END
auto> 250 E:
pilot> LIST
1 100 T:WHAT IS YOUR NAME?
2 110 A:$NAME
3 120 T:HELLO, $NAME.
4 130 T:ARE YOU A STUDENT? (YES OR NO)
5 140 A:$ANSWER
6 150 M: YES , NO
7 160 JM:*YES,*NO
8 170 T:UNRECOGNISED ANSWER.
9 180 J:*END
10 190 *YES
11 200 T:WELCOME TO THE COURSE.
12 210 J:*END
13 220 *NO
14 230 T:COME BACK ANY TIME.
15 240 *END
16 250 E:
pilot> RUN
WHAT IS YOUR NAME?
ada
HELLO, ada.
ARE YOU A STUDENT? (YES OR NO)
yes
WELCOME TO THE COURSE.
pilot> QUIT
GOODBYE
Note the colon is optional at the prompt, so T HELLO and T:HELLO are the
same statement (spec 6.2). The transcript above is generated by
tools/readme_session.py, which runs the real REPL — documentation that is
produced rather than typed does not drift.
The whole examples/ corpus runs now, apart from graphics.pilot, which
deliberately fails:
Add --trace to watch each statement; trace goes to stderr, so it never mixes
with the program's own output:
$ uv run pypilot --trace examples/summary.pilot
Parse a program without running it:
uv run pypilot --check examples/greeter.pilot
uv run pypilot --check --list examples/adventure.pilot
$ uv run pypilot --check --list examples/graphics.pilot
examples/graphics.pilot: parsed 6 statement(s), 0 label(s)
1 R:Turtle graphics is real ATARI PILOT, but unimplemented in pyPILOT (SPEC 10.4)
2 R:This program MUST parse cleanly and fail at RUN time with a message
3 R:naming the subcommand - not at parse time.
4 GR:CLEAR (refused at run time)
5 T:THIS LINE IS NEVER REACHED.
6 E:
A syntax error is reported the way a 1980s interpreter would have reported it — line number, message, and the offending line echoed back:
$ printf 'T:HELLO\nFO:1,DATA\n' > bad.pilot
$ uv run pypilot --check bad.pilot
bad.pilot: line 2: 'FO' is not an ATARI PILOT command: ATARI PILOT has no file
handles; use READ: (spec 9.6)
FO:1,DATA
$ echo $?
1
Or from Python:
from pypilot import PilotState, parse
program = parse("*GREET T:HELLO, $NAME\nA:$NAME\n*AGAIN J:*GREET\nE:")
print(len(program), "statements,", len(program.labels), "labels")
print(program.resolve("*GREET")) # -> 0
# An undefined string variable prints *its own name* — the ATARI rule,
# and the opposite of every other PILOT dialect.
state = PilotState()
print(state.expand("HELLO $NAME")) # HELLO NAME
state.set_string("NAME", "ada")
print(state.expand("HELLO $NAME")) # HELLO ada
Three rules worth knowing before you write tests, because Python's defaults are the opposite of every one of them:
from pypilot import Numeric, PilotState
print(Numeric(7) / Numeric(3)) # 2 — truncates toward zero
print(Numeric(-7) / Numeric(3)) # -2 — not Python's -3
print(Numeric(32767) + Numeric(1)) # -32768 — wraps, silently
state = PilotState()
print(state.evaluate("1+2*3")) # 9 — no operator precedence!
print(state.evaluate("1+(2*3)")) # 7 — parens are the only fix
print(state.evaluate("-7\\3")) # 1 — the remainder is always positive
License
MIT. See LICENSE.
Metadata
Release files for rePILOT 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| repilot-1.0.0.tar.gz | 232.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| repilot-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 306.2 kB
Release files / repilot-1.0.0.tar.gz
| Download URL | repilot-1.0.0.tar.gz |
|---|---|
| Size | 232.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3844522b89a9b2dc38f1462e8b7b32e834051f5c728d2dcb53ff9587c2476825
|
|
BLAKE2b-256 checksum How to use checksums |
7a9fd7ac1129a06cb9daefde500e4ee52d91b870e15c79241c7cd61f388c16fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|
Release files / repilot-1.0.0-py3-none-any.whl
| Download URL | repilot-1.0.0-py3-none-any.whl |
|---|---|
| Size | 73.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d1291fed0bb5bcd3e24cb0375e0464af220b78cabc941a8bb2d8543a94460894
|
|
BLAKE2b-256 checksum How to use checksums |
c9bcc78b1ade5a9f86fba9a5c1a2cb3f283bcf736801c64e83f7e29562cbe48d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|