Metadata-Version: 2.4
Name: osmk
Version: 0.1.4
Summary: Write x86 operating systems in Python — one instruction at a time
Author-email: Your Name <you@example.com>
License: MIT
Keywords: os,assembly,x86,bootloader,osdev,low-level
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Education
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Operating System Kernels
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"

# osmk

**Write x86 operating systems in Python — one instruction at a time.**

Every function call maps to an x86 assembly instruction. `build()` assembles it into a bootable 512-byte disk image.

## Install

```bash
pip install osmk
```

You also need **NASM** (the assembler) and optionally **QEMU** (to run your OS):

```bash
# Ubuntu/Debian
sudo apt install nasm qemu-system-x86

# macOS
brew install nasm qemu

# Windows
# NASM: https://nasm.us → download & add to PATH
# QEMU: https://qemu.org/download/#windows
```

Or test without installing QEMU at [copy.sh/v86](https://copy.sh/v86/) — upload your `.img` as a floppy disk image.

## Quick Start

```python
import osmk
from osmk import ax, bx

osmk.start("hello.img")    # begin a new OS

osmk.ax(5)                 # mov ax, 5
osmk.bx(5)                 # mov bx, 5
osmk.add(ax, bx)           # add ax, bx  →  ax = 0x000A
osmk.writehex()             # print AX as hex → "000A"

osmk.build()                # assemble → hello.img (512 bytes)
```

Run it:
```bash
qemu-system-i386 -drive format=raw,file=hello.img
```

## API Reference

### Lifecycle

| Function | Description |
|----------|-------------|
| `start(filename, bits=16, org=0x7C00)` | Begin a new OS program |
| `build()` | Assemble and create the bootable image |
| `dump_asm()` | Return the generated NASM source (for debugging) |
| `run(memory=128)` | Build and launch in QEMU |

### Register Setters

Every register is callable. `osmk.ax(5)` → `mov ax, 5`.

```python
osmk.ax(5)          # mov ax, 5
osmk.bx(ax)         # mov bx, ax
osmk.al(0x41)       # mov al, 0x41
osmk.eax(0x1000)    # mov eax, 0x1000
```

Available: `ax bx cx dx si di sp bp` · `al ah bl bh cl ch dl dh` · `eax ebx ecx edx esi edi esp ebp`

### Instructions

```python
# Arithmetic
osmk.add(ax, bx)       osmk.sub(ax, 1)
osmk.mul(bx)            osmk.div(cx)
osmk.inc(ax)            osmk.dec(cx)
osmk.neg(ax)

# Bitwise
osmk.and_(ax, 0xFF)    osmk.or_(ax, bx)
osmk.xor(ax, ax)       osmk.not_(ax)
osmk.shl(ax, 4)        osmk.shr(ax, 1)

# Compare & Jump
osmk.cmp(ax, 0)
osmk.je("done")        osmk.jne("loop")
osmk.jl("less")        osmk.jg("greater")
osmk.jz("zero")        osmk.jnz("nonzero")
osmk.jmp("start")      osmk.jc("carry")

# Stack
osmk.push(ax)           osmk.pop(bx)

# Subroutines
osmk.call("myfunc")     osmk.ret()

# Interrupts
osmk.interrupt(0x10)    # int 0x10
osmk.cli()              osmk.sti()
osmk.hlt()

# Labels
osmk.label("loop")
```

### Labels & Scoping

```python
# Global labels — must be unique
osmk.label("main")

# Local labels — prefix with '.', scoped to nearest global label above
osmk.label("main")
osmk.label(".loop")        # belongs to 'main'
osmk.jnz(".loop")

osmk.label("other")
osmk.label(".loop")        # different .loop, belongs to 'other' — no collision
osmk.jnz(".loop")
```

### Output Helpers

```python
osmk.writehex()         # print AX as 4-digit hex (e.g. "000A")
osmk.writechar("A")     # print character 'A'
osmk.writechar()        # print whatever's in AL
osmk.prints("Hello!")   # print a string
osmk.newline()          # print CR+LF
osmk.readchar()         # wait for keypress → AL
osmk.halt()             # halt CPU (cli + hlt loop)
```

### Functions

Define reusable subroutines with the `function()` context manager. `ret` is appended automatically. Labels inside are auto-scoped as local labels, so multiple functions can use the same label names without collision.

```python
with osmk.function("print_stars") as fn:
    fn.mov(cx, 5)
    fn.label("loop")         # becomes .loop (local to print_stars)
    fn.asm("mov ah, 0x0E")
    fn.asm("mov al, '*'")
    fn.interrupt(0x10)
    fn.dec(cx)
    fn.jnz(".loop")
    # ret is auto-appended here

with osmk.function("print_dashes") as fn:
    fn.mov(cx, 5)
    fn.label("loop")         # different .loop — no collision
    fn.asm("mov ah, 0x0E")
    fn.asm("mov al, '-'")
    fn.interrupt(0x10)
    fn.dec(cx)
    fn.jnz(".loop")

osmk.call("print_stars")
osmk.call("print_dashes")
```

The function builder supports the full DSL: `fn.mov()`, `fn.add()`, `fn.sub()`, `fn.inc()`, `fn.dec()`, `fn.cmp()`, `fn.push()`, `fn.pop()`, `fn.call()`, `fn.interrupt()`, `fn.label()`, `fn.jmp()`, `fn.je()`, `fn.jne()`, `fn.jz()`, `fn.jnz()`, `fn.jl()`, `fn.jg()`, `fn.asm()`, `fn.comment()`.

### Custom / Escape Hatch

When osmk doesn't cover what you need:

```python
# Inject raw NASM assembly
osmk.asm("mov ah, 0x0E")
osmk.asm("""
    mov ah, 0x00
    mov al, 0x03
    int 0x10
""")

# Inject raw bytes
osmk.raw(b"\x90\x90\x90")   # 3x NOP

# Define data
osmk.data("my_var", "dw 0x1234")

# Add comments
osmk.comment("this sets up video mode")
```

### Explicit MOV

```python
osmk.mov(ax, 0x0E)     # same as osmk.ax(0x0E)
osmk.mov(bx, ax)       # same as osmk.bx(ax)
```

## Examples

### Hello World
```python
import osmk

osmk.start("hello.img")
osmk.prints("Hello, World!")
osmk.newline()
osmk.prints("My first OS!")
osmk.halt()
osmk.build()
```

### Keyboard Echo
```python
import osmk
from osmk import al

osmk.start("echo.img")
osmk.prints("Type anything:")
osmk.newline()

osmk.label("loop")
osmk.readchar()        # key → AL
osmk.writechar()       # echo it

osmk.cmp(al, 13)       # Enter key?
osmk.jne("loop")       # no → keep looping
osmk.writechar(10)     # yes → also print line feed

osmk.jmp("loop")
osmk.build()
```

### Counter
```python
import osmk
from osmk import ax, cx

osmk.start("counter.img")

osmk.ax(0)
osmk.cx(16)

osmk.label("count")
osmk.writehex()
osmk.prints(" ")
osmk.inc(ax)
osmk.dec(cx)
osmk.jnz("count")

osmk.halt()
osmk.build()
```

## How It Works

1. Each `osmk.*` call appends an x86 assembly instruction to an internal list
2. `build()` wraps them in a bootloader template (16-bit real mode, MBR)
3. NASM assembles it into a flat binary
4. The binary is exactly 512 bytes with the `0xAA55` boot signature

The generated code runs in **16-bit real mode** using BIOS interrupts for I/O. Use `dump_asm()` to see exactly what assembly your Python generates.

## Resources

- [OSDev Wiki](https://wiki.osdev.org/) — OS development reference
- [NASM Documentation](https://nasm.us/doc/) — assembler docs
- [x86 Instruction Reference](https://www.felixcloutier.com/x86/) — complete instruction set
- [Writing a Simple OS](https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdf) — Nick Blundell's guide
- [BIOS Interrupt List](https://en.wikipedia.org/wiki/BIOS_interrupt_call) — int 0x10, 0x13, 0x16, etc.

## License

MIT
