dt31 0.1.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.
dt31-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,546 @@
1
+ Metadata-Version: 2.3
2
+ Name: dt31
3
+ Version: 0.1.0
4
+ Summary: A toy computer in Python.
5
+ Keywords: emulator,assembly,computer,virtual-machine,instruction-set,toy-computer,cpu,interpreter
6
+ Author: Dan Turkel
7
+ Author-email: Dan Turkel <daturkel@gmail.com>
8
+ License: MIT License
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Programming Language :: Python
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Software Development :: Interpreters
18
+ Maintainer: Dan Turkel
19
+ Maintainer-email: Dan Turkel <daaturkel@gmail.com>
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+
23
+ # dt31
24
+
25
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) [![pdoc](https://img.shields.io/badge/docs-pdoc.dev-green)](https://pdoc.dev/docs/pdoc.html) [![Ruff](https://img.shields.io/badge/style-ruff-green.svg)](https://github.com/astral-sh/ruff) ![Coverage Badge](coverage-badge.svg)
26
+
27
+ A toy computer emulator and assembly language written in Python. Build programs with 60+ built-in instructions for interacting with registers, memory, and the stack. Write your programs in the native assembly syntax or directly with the Python API.
28
+
29
+ <table>
30
+ <tr>
31
+ <th>countdown.dt</th>
32
+ <th>countdown.py</th>
33
+ </tr>
34
+ <tr>
35
+ <td>
36
+
37
+ ```nasm
38
+ ; countdown.dt
39
+
40
+ ; copy 5 to register a
41
+ CP 5, R.a
42
+ loop:
43
+ ; print register a
44
+ NOUT R.a, 1
45
+ ; a = a - 1
46
+ SUB R.a, 1
47
+ ; jump to loop if a > 0
48
+ JGT loop, R.a, 0
49
+
50
+ ; run with: `dt31 countdown.dt`
51
+ ; output: 5 4 3 2 1
52
+ ```
53
+ </td>
54
+ <td>
55
+
56
+ ```python
57
+ from dt31 import DT31, I, L, Label, R
58
+
59
+ # create vm with default settings
60
+ cpu = DT31()
61
+
62
+ program = [
63
+ I.CP(5, R.a),
64
+ loop := Label("loop"),
65
+ I.NOUT(R.a, L[1]),
66
+ I.SUB(R.a, L[1]),
67
+ I.JGT(loop, R.a, L[0]),
68
+ ]
69
+
70
+ cpu.run(program)
71
+ ```
72
+
73
+ </td>
74
+
75
+ </tr>
76
+ </table>
77
+
78
+ ## Features
79
+
80
+ - **Simple CPU Architecture**: Configurable registers, fixed-size memory, and stack-based operations
81
+ - **Rich Instruction Set**: 60+ instructions including arithmetic, bitwise operations, logic, control flow, and I/O
82
+ - **Assembly Support**: Two-pass assembler with label resolution for jumps and function calls
83
+ - **Assembly Parser**: Parse and execute `.dt` assembly files with text-based syntax
84
+ - **Command-Line Interface**: Execute `.dt` files directly with the `dt31` command
85
+ - **Python API**: Build and run programs programmatically with an intuitive API
86
+ - **Debug Mode**: Step-by-step execution with state inspection and breakpoints
87
+ - **Pure Python**: Zero dependencies
88
+
89
+ ## Installation
90
+
91
+ ```shell
92
+ pip install dt31
93
+ ```
94
+
95
+ ## Getting Started
96
+
97
+ ### Hello World
98
+
99
+ Create a file `hello.dt`
100
+
101
+ ```nasm
102
+ ; Output "Hi!"
103
+ OOUT 'H', 0
104
+ OOUT 'i', 0
105
+ OOUT '!', 0
106
+ ```
107
+
108
+ and run it with the `dt31` interpreter
109
+
110
+ ```shell
111
+ dt31 hello.dt
112
+ # Hi!
113
+ ```
114
+
115
+ ### Basic Arithmetic
116
+
117
+ ```nasm
118
+ ; Add two numbers
119
+ CP 10, R.a ; Copy 10 into register a
120
+ CP 5, R.b ; Copy 5 into register b
121
+ ADD R.a, R.b ; a = a + b
122
+ NOUT R.a, 1 ; Output a with newline
123
+ ```
124
+
125
+ Save as `add.dt` and run: `dt31 add.dt` to output `15`.
126
+
127
+ ### Loops with Labels
128
+
129
+ ```nasm
130
+ ; Count from 1 to 10
131
+ CP 1, R.a ; Start counter at 1
132
+ loop:
133
+ NOUT R.a, 1 ; Print counter
134
+ ADD R.a, 1 ; Increment counter
135
+ JLT loop, R.a, 11 ; Jump to loop if a < 11
136
+ ```
137
+
138
+ Save as `count.dt` and run: `dt31 count.dt` to output `1 2 3 4 5 6 7 8 9 10`.
139
+
140
+ ### Function Calls
141
+
142
+ ```nasm
143
+ ; Print a greeting multiple times
144
+ CP 3, R.a ; Counter: print 3 times
145
+ print_loop:
146
+ CALL greet ; Call the greeting function
147
+ SUB R.a, 1. ; R.a -= 1
148
+ JGT print_loop, R.a, 0 ; loop if R.a > 0
149
+ JMP end
150
+
151
+ greet:
152
+ ; Reusable greeting function
153
+ OOUT 'H', 0
154
+ OOUT 'i', 0
155
+ OOUT '!', 0
156
+ OOUT ' ', 0
157
+ RET
158
+
159
+ end:
160
+ ; output: `Hi! Hi! Hi! `
161
+ ```
162
+
163
+ Functions use the stack for return addresses and can be called multiple times. See the [examples](https://github.com/daturkel/dt31/tree/main/examples) directory for more complex examples.
164
+
165
+ ## Core Concepts
166
+
167
+ ### Operands
168
+
169
+ dt31 provides several operand types for referencing values:
170
+
171
+ - **Literals**: Constant values `L[42]`, or `LC["a"]` as a shortcut for `L[ord("a")]`
172
+ - **Registers**: CPU registers `R.a`, `R.b`, `R.c`
173
+ - **Memory**: Memory addresses `M[100]`, indirect addressing `M[R.a]`
174
+ - **Labels**: Named jump targets `Label("loop")`
175
+
176
+ See the [operands documentation](https://daturkel.github.io/dt31/dt31/operands.html) for details.
177
+
178
+ ### Instructions
179
+
180
+ The instruction set includes:
181
+
182
+ - **Arithmetic**: `ADD`, `SUB`, `MUL`, `DIV`, `MOD`
183
+ - **Bitwise**: `BAND`, `BOR`, `BXOR`, `BNOT`, `BSL`, `BSR`
184
+ - **Comparisons**: `LT`, `GT`, `LE`, `GE`, `EQ`, `NE`
185
+ - **Logic**: `AND`, `OR`, `XOR`, `NOT`
186
+ - **Control Flow**: `JMP`, `RJMP`, `JEQ`, `JNE`, `JGT`, `JGE`, `JIF`
187
+ - **Functions**: `CALL`, `RCALL`, `RET`
188
+ - **Stack**: `PUSH`, `POP`, `SEMP`
189
+ - **I/O**: `NOUT`, `OOUT`, `NIN`, `OIN`
190
+ - **Data Movement**: `CP`
191
+
192
+ Users can easily define their own custom instructions by subclassing `dt31.instructions.Instruction`.
193
+
194
+ See the [instructions documentation](https://daturkel.github.io/dt31/dt31/instructions.html) for the complete reference.
195
+
196
+ ### CPU Architecture
197
+
198
+ The DT31 CPU includes:
199
+
200
+ - **Registers**: General-purpose registers (default: `a`, `b`, and `c`)
201
+ - **Memory**: Fixed-size byte array (default: 256 slots)
202
+ - **Stack**: For temporary values and function calls (default: 256 slots)
203
+ - **Instruction Pointer**: Tracks current instruction in register `ip`
204
+
205
+ See the [CPU documentation](https://daturkel.github.io/dt31/dt31/cpu.html) for API details.
206
+
207
+ ## Command-Line Interface
208
+
209
+ ### Basic Usage
210
+
211
+ Execute `.dt` assembly files directly:
212
+
213
+ ```shell
214
+ dt31 program.dt
215
+ ```
216
+
217
+ ### CLI Options
218
+
219
+ - `--debug` or `-d`: Enable step-by-step debug output
220
+ - `--parse-only` or `-p`: Validate syntax without executing
221
+ - `--registers a,b,c,d`: Specify custom registers (auto-detected by default)
222
+ - `--memory 512`: Set memory size in bytes (default: 256)
223
+ - `--stack-size 512`: Set stack size (default: 256)
224
+ - `--custom-instructions PATH` or `-i PATH`: Load custom instruction definitions from a Python file
225
+ - `--dump {none,error,success,all}`: When to dump CPU state (default: none)
226
+ - `--dump-file FILE`: File path for CPU state dump (auto-generates timestamped filename if not specified)
227
+
228
+ **Examples:**
229
+
230
+ ```shell
231
+ # Parse and validate only (no execution)
232
+ dt31 --parse-only program.dt
233
+
234
+ # Run with debug output
235
+ dt31 --debug program.dt
236
+
237
+ # Use custom memory size
238
+ dt31 --memory 1024 program.dt
239
+
240
+ # Specify registers explicitly
241
+ dt31 --registers a,b,c,d,e program.dt
242
+
243
+ # Use custom instructions
244
+ dt31 --custom-instructions my_instructions.py program.dt
245
+
246
+ # Dump CPU state on error (for debugging crashes)
247
+ dt31 --dump error program.dt # Auto-generates program_crash_TIMESTAMP.json
248
+ ```
249
+
250
+ See the [CLI documentation](https://daturkel.github.io/dt31/dt31/cli.html) for complete details.
251
+
252
+ ### Custom Instructions
253
+
254
+ Define custom instructions in a Python file and load them with `--custom-instructions`. Your file must export an `INSTRUCTIONS` dict and instruction names must be all-caps:
255
+
256
+ ```python
257
+ # my_instructions.py
258
+ from dt31.instructions import UnaryOperation
259
+ from dt31.operands import Operand, Reference
260
+
261
+ class TRIPLE(UnaryOperation):
262
+ """Triple a value."""
263
+ def __init__(self, a: Operand, out: Reference | None = None):
264
+ super().__init__("TRIPLE", a, out)
265
+
266
+ def _calc(self, cpu: "DT31") -> int:
267
+ return self.a.resolve(cpu) * 3
268
+
269
+ INSTRUCTIONS = {"TRIPLE": TRIPLE}
270
+ ```
271
+
272
+ Use in assembly:
273
+
274
+ ```nasm
275
+ CP 5, R.a
276
+ TRIPLE R.a
277
+ NOUT R.a, 1 ; Outputs 15
278
+ ```
279
+
280
+ Run with: `dt31 --custom-instructions my_instructions.py program.dt`
281
+
282
+ **Security Warning**: Loading custom instruction files executes arbitrary Python code. Only load files from trusted sources.
283
+
284
+ See the [instructions documentation](https://daturkel.github.io/dt31/dt31/instructions.html) for more details on creating custom instructions.
285
+
286
+ ### CPU State Dumps
287
+
288
+ The `--dump` option captures complete CPU state to JSON for debugging. Dumps include:
289
+
290
+ - **CPU state**: registers, memory, stack, and loaded program (as assembly text)
291
+ - **Error information** (on error dumps): exception type, message, traceback, and the instruction that caused the error
292
+
293
+ Error dumps include both `repr` and `str` formats of the failing instruction for easier debugging:
294
+
295
+ ```json
296
+ {
297
+ "cpu_state": {
298
+ "registers": {"a": 10, "b": 0, "ip": 2},
299
+ "memory": [...],
300
+ "stack": [],
301
+ "program": "CP 10, R.a\nCP 0, R.b\nDIV R.a, R.b",
302
+ "config": {"memory_size": 256, "stack_size": 256, "wrap_memory": false}
303
+ },
304
+ "error": {
305
+ "type": "ZeroDivisionError",
306
+ "message": "integer division or modulo by zero",
307
+ "instruction": {
308
+ "repr": "DIV(a=R.a, b=R.b, out=R.a)",
309
+ "str": "DIV R.a, R.b, R.a"
310
+ },
311
+ "traceback": "..."
312
+ }
313
+ }
314
+ ```
315
+
316
+ ## Assembly Language Reference
317
+
318
+ ### Syntax Rules
319
+
320
+ **Instructions and Operands:**
321
+ ```nasm
322
+ INSTRUCTION operand1, operand2, operand3
323
+ ```
324
+
325
+ - Instructions are case-insensitive (`ADD`, `add`, and `Add` are all valid)
326
+ - Register names and label names are case-*sensitive*
327
+ - Operands are separated by commas (spaces around commas are optional)
328
+ - Comments start with `;` and continue to end of line
329
+ - Blank lines and indentation are ignored
330
+
331
+ ### Operand Types
332
+
333
+ The assembly text syntax differs from Python syntax:
334
+
335
+ | Operand Type | Assembly Syntax | Python Syntax | Example |
336
+ |--------------|---------------|-------------|---------|
337
+ | **Numeric Literal** | `42`, `-5` | `L[42]`, `L[-5]` | `CP 42, R.a` |
338
+ | **Character Literal** | `'A'` | `LC["A"]` | `OOUT 'H', 0` |
339
+ | **Register** | `R.a` | `R.a` | `ADD R.a, R.b` |
340
+ | **Memory (direct)** | `[100]` or `M[100]` | `M[100]` | `CP 42, [100]` |
341
+ | **Memory (indirect)** | `[R.a]` or `M[R.a]` | `M[R.a]` | `CP [R.a], R.b` |
342
+ | **Label** | `loop` | `Label("loop")` | `JMP loop` |
343
+
344
+ **Key Differences:**
345
+
346
+ 1. **Literals**: In text syntax, bare numbers are literals (no `L[...]` wrapper needed)
347
+ 2. **Characters**: Use single quotes `'A'` instead of `LC["A"]`
348
+ 3. **Memory**: The `M` prefix is optional (both `[100]` and `M[100]` work)
349
+ 4. **Labels**: Bare identifiers are labels (no `Label(...)` constructor needed)
350
+ 5. **Registers**: **Must** use `R.` prefix in both syntaxes
351
+
352
+ ### Label Definition
353
+
354
+ Labels mark positions in code:
355
+
356
+ ```nasm
357
+ ; Label on its own line
358
+ loop:
359
+ ADD R.a, 1
360
+ JLT loop, R.a, 10
361
+
362
+ ; Label on same line as instruction
363
+ start: CP 0, R.a
364
+ ```
365
+
366
+ Label names must contain only alphanumeric characters and underscores.
367
+
368
+ ## Python API
369
+
370
+ While assembly syntax is the primary way to use dt31, you can also build and run programs programmatically using the Python API.
371
+
372
+ ### Creating and Running Programs
373
+
374
+ ```python
375
+ from dt31 import DT31, I, L, M, R
376
+
377
+ # Create CPU instance
378
+ cpu = DT31()
379
+
380
+ # Write program as list of instructions
381
+ program = [
382
+ I.CP(42, R.a),
383
+ I.CP(100, M[R.a]),
384
+ I.NOUT(R.a, L[1]),
385
+ I.NOUT(M[R.a], L[1])
386
+ ]
387
+
388
+ # Run the program
389
+ cpu.run(program)
390
+ # 42
391
+ # 100
392
+ ```
393
+
394
+ ### Parsing Assembly from Python
395
+
396
+ ```python
397
+ from dt31 import DT31
398
+ from dt31.parser import parse_program
399
+
400
+ cpu = DT31()
401
+
402
+ assembly = """
403
+ CP 5, R.a
404
+ loop:
405
+ NOUT R.a, 1
406
+ SUB R.a, 1
407
+ JGT loop, R.a, 0
408
+ """
409
+
410
+ program = parse_program(assembly)
411
+ cpu.run(program)
412
+ # 5 4 3 2 1
413
+ ```
414
+
415
+ ### Converting Programs to Text
416
+
417
+ Convert Python programs to assembly text format:
418
+
419
+ ```python
420
+ from dt31 import I, L, Label, LC, R
421
+ from dt31.assembler import program_to_text
422
+
423
+ # Create a program in Python
424
+ program = [
425
+ I.CP(5, R.a),
426
+ loop := Label("loop"),
427
+ I.OOUT(LC["*"]),
428
+ I.SUB(R.a, L[1]),
429
+ I.JGT(loop, R.a, L[0]),
430
+ ]
431
+
432
+ # Convert to assembly text
433
+ text = program_to_text(program)
434
+ print(text)
435
+ # CP 5, R.a
436
+ # loop:
437
+ # OOUT '*', 0
438
+ # SUB R.a, 1, R.a
439
+ # JGT loop, R.a, 0
440
+ ```
441
+
442
+ ### Labels and Function Calls
443
+
444
+ Labels offer a great use-case for the Python walrus operator.
445
+
446
+ ```python
447
+ from dt31.operands import I, LC, Label
448
+
449
+ program = [
450
+ I.CALL(print_hi := Label("print_hi")),
451
+ I.JMP(end := Label("end")),
452
+
453
+ print_hi,
454
+ I.OOUT(LC['H']),
455
+ I.OOUT(LC['i']),
456
+ I.RET(),
457
+
458
+ end,
459
+ ]
460
+ cpu.run(program)
461
+ # Hi
462
+ ```
463
+
464
+ ### Debugging with Step Execution
465
+
466
+ ```python
467
+ cpu = DT31()
468
+ cpu.load(program)
469
+
470
+ # Execute one instruction at a time
471
+ cpu.step(debug=True) # Prints instruction and state
472
+ print(cpu.state) # Inspect CPU state
473
+
474
+ # Execute a full program one instruction at a time
475
+ cpu.run(program, debug=True)
476
+ ```
477
+
478
+ ### Accessing CPU State
479
+
480
+ ```python
481
+ # Get register values
482
+ value = cpu.get_register('a')
483
+
484
+ # Set register values
485
+ cpu.set_register('b', 42)
486
+
487
+ # Access memory
488
+ cpu.set_memory(100, 255)
489
+ byte = cpu.get_memory(100)
490
+
491
+ # Get full state snapshot
492
+ state = cpu.state # Returns dict with registers, memory, stack, ip
493
+ ```
494
+
495
+ ## Documentation
496
+
497
+ Full API documentation is available at [the docs site](https://daturkel.github.io/dt31/dt31.html). Generate the latest docs with:
498
+
499
+ ```bash
500
+ uv run invoke docs
501
+ uv run invoke serve-docs # Serve locally at http://localhost:8080
502
+ ```
503
+
504
+ Key documentation pages:
505
+
506
+ - [DT31 CPU Class](https://daturkel.github.io/dt31/dt31/cpu.html) - CPU methods and state management
507
+ - [Instructions](https://daturkel.github.io/dt31/dt31/instructions.html) - Complete instruction reference
508
+ - [Operands](https://daturkel.github.io/dt31/dt31/operands.html) - Operand types and usage
509
+ - [Parser](https://daturkel.github.io/dt31/dt31/parser.html) - Assembly text parsing
510
+ - [Assembler](https://daturkel.github.io/dt31/dt31/assembler.html) - Label resolution and assembly
511
+ - [CLI](https://daturkel.github.io/dt31/dt31/cli.html) - Command-line interface
512
+
513
+ ## Development
514
+
515
+ ```bash
516
+ # Install dependencies
517
+ uv sync --dev
518
+
519
+ # Set up pre-commit hooks
520
+ pre-commit install
521
+ pre-commit install --hook-type pre-push
522
+
523
+ # Run tests
524
+ uv run invoke test
525
+ ```
526
+
527
+ DT31 is open-source and contributors are welcome on [Github](https://github.com/daturkel/dt31).
528
+
529
+ ## Roadmap
530
+
531
+ - [x] Character literals?
532
+ - [x] Parse and execute `.dt` files
533
+ - [x] Breakpoint instruction
534
+ - [x] Clearer handing of input during debug mode
535
+ - [x] Python to text output
536
+ - [ ] User-definable macros (in both python and assembly syntax)
537
+ - [ ] File I/O
538
+ - [ ] Data handling
539
+ - [ ] Input error-handling
540
+ - [ ] Preserve comments in parser
541
+ - [ ] Formatter
542
+ - [ ] Interpreter resume from dump
543
+
544
+ ## License
545
+
546
+ MIT License