The dos module¶
Portable programs ask for outcomes, not devices.
This page introduces the dos module, the part of d/Python that lets your program draw, play sound, read files and talk to the operating system without knowing what hardware it is running on. After reading it you will know the handful of functions the module offers, which of them are fully tested today, and why d/Python works this way.
d/Python runs on d/OS, the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II, and it also runs in a browser and on a Mac. A program written for it never needs to know which GPU, sound chip, filesystem or browser API sits underneath. Instead it makes an intent: a named request such as "fill this rectangle" or "play this tone" that the machine carries out however it can. The dos module is where those requests are made. Its functions are generated from the official list of intents, so their names, argument types, required permissions and minimum versions come from one source, and d/BASIC, d/OS's other language, which shares the same runtime, reads the same list.
Two verbs and a decorator¶
# fragment: the shape of the module
import dos
result = dos.intent("DOMAIN", "OPERATION", arg1, arg2) # a registry row, by name
value = dos.service("SET_TITLE", "Title") # a host service, by name
@dos.on("WINDOW_DRAW") # a face-entry handler
def draw():
pass
dos.intent(domain, operation, ...) makes one request from the official list, with exactly the argument types that request expects. dos.service(name, ...) asks the host for a small built-in service such as SET_TITLE, INVALIDATE, WIDTH, HEIGHT or READ_LINE. dos.on(name) registers a function to be called when something happens, such as a window needing to be drawn or a key being pressed. There is also dos.app(console=True), which lets a window program be started as a console program too.
Typed buffers and decimals¶
import dos
values = dos.array("i16", [10, 20, 30])
other = values
other[1] = 99
print(values[1])
amount = dos.decimal("12.3456") # the exact four-place carrier
print(amount == dos.decimal("12.3456"))
dos.array creates a fixed-size, fixed-type d/OS buffer. It is not a Python list. The element types are i16, i32, f32, f64, decimal and str. Assigning to an element changes the one buffer that every name refers to, as the example shows, and when you pass a buffer to a helper the helper works on your buffer rather than on a copy. dos.decimal is an exact decimal number with four places after the point. Functions that work on raw bytes expect them packed two per element in an i16 buffer, low byte first, as the hashing example on the console services page shows.
Numbers across the boundary¶
import dos
parsed = dos.intent("MATH", "PARSE", 0, 4, b"2.5")
rounded = dos.intent("MATH", "CONVERT", 0, 0, parsed)
values = dos.array("f64", [1.25, 2.5])
total = dos.intent("MATH", "ARRAY_REDUCE", 0, 4, 2, values, values)
print(parsed == 2.5, rounded, total == 3.75)
print(dos.intent("MATH", "FORMAT", 1, parsed, b"") == b" 2.5")
Every one of the 25 MATH intents has the same test written in both languages, and both pass: parsing and formatting numbers, capability queries, evaluating in batches, changing and aliasing arrays, and random-number state. Numbers are never silently truncated. An integer literal too large for its fixed-width type is a compile error, and an overflow at run time stops the program before the conversion happens. One limit remains: MATH.EVAL and the selectors that pick a numeric result currently accept only format names written as literals in the source.
Multi-result calls¶
# fragment: a call that returns three values
a, b, c = dos.intent("UI", "QUERY_CAPS")
hit, detail = dos.intent("UI", "HIT_TEST", node, x, y)
Some intents return more than one value. You unpack them the way you would any Python tuple.
Strings are bytes on the wire¶
Strings that come back from d/OS or from a shared library, including the elements of dos.array("str", ...), arrive as Python bytes, not str, because those values can carry any binary data. Compare, join and measure them as bytes. Automatic decoding to str is not yet available.
What a program declares before it runs¶
A compiled program file, .dbc, states up front what it needs: FILES, BULK, SOUND, NETWORK, a window, and a minimum version of the host services. Before the first instruction runs, the host checks that list against what it can supply, and if something is missing it declines to start and names the missing item. Two rules keep this honest. Being able to call an intent from Python never makes it work on a host that lacks the part that would carry it out, and a launch option such as --file-mount cannot grant a permission the program did not declare. In the browser, the parts of the host that supply files, sound and the network are not present, while MATH and the bounded SHA-256 hasher are built into the shared runtime and work everywhere. On macOS, the console command line attaches the native MATH, COMPUTE, AUDIO, FILE and NET parts when the program declares them.
How much of the list is covered¶
The official list of intents has 191 callable entries across the CORE, SHELL, SERVICE, DATA, UI, GFX, MATH, AUDIO, FILE, NET and COMPUTE domains. Every one of them can be called from Python, and a test checks each binding. 86 of them also have the same test written in both languages and passing against a real host. dpython bindings prints the list the compiler reads. The intent coverage page shows every entry and how far it has been tested; the conformance page explains d/Python's promise to reach all of them.
Drag and drop, the pasteboard and DOBJ¶
d/OS moves real objects between applications: a pasteboard whose operations complete or roll back as a whole, drag and drop in which the two sides negotiate what is handed over, and DOBJ, the typed object that every message between programs carries. In the list of intents these are 25 SERVICE entries (PASTEBOARD_*, DRAG_*, DOBJ_SCHEMA_*) beside the 40 DATA entries for the database and the artifact store. All of them can be called through dos.intent, but none of them has a passing test in both languages yet, so the intent coverage page marks them not yet supported and this site does not teach them. They are part of d/Python's promises, just not delivered yet: the same rule that keeps a partly supported feature from being called complete keeps an untested chapter from being written.
Why not just call the machine?¶
Because one compiled program has to mean one thing on machines that share no memory map. d/BASIC declines PEEK and POKE for the same reason d/Python has no ctypes: a memory address is the machine, and a program that writes to one means something on one computer and nothing on the next. The intent is the boundary at which meaning survives the move.