diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..3e2a7dd --- /dev/null +++ b/.gdbinit @@ -0,0 +1,2 @@ +target remote :1234 +symbol-file bin/sys.o \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..abd69b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.exe +*.rdi +*.lib +*.ilk +*.pdb + +bin \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7aa2de6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/umka"] + path = src/umka + url = https://github.com/vtereshkov/umka-lang +[submodule "src/qoi"] + path = src/qoi + url = https://github.com/phoboslab/qoi diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6410e16 --- /dev/null +++ b/Makefile @@ -0,0 +1,35 @@ +CFLAGS= -I src/qoi -I src/mock_libc -g -Isrc/umka/src -m32 -march=i686 -Os -fno-builtin -fcf-protection=none -nostdlib -ffreestanding -mno-red-zone -fno-exceptions +EMU=qemu-system-x86_64 + +all: pre-build do-build + +pre-build: + mkdir -p bin + python3 mkfs.py fs bin/fs.bin + +do-build: bin/unity.o bin/umka.o bin/boot.o + ld -melf_i386 -r -b binary -o bin/fs.o bin/fs.bin + gcc $(CFLAGS) bin/unity.o bin/umka.o bin/fs.o bin/boot.o -o bin/sys.o -T src/sys/link.ld + objcopy -O binary bin/sys.o bin/sys.img + +run: all + $(EMU) -no-shutdown -no-reboot -m 128 -drive file=bin/sys.img,format=raw + +debug: all + @$(EMU) -no-shutdown -no-reboot -s -S -m 128 -drive file=bin/sys.img,format=raw & + sleep 1 + gdb -tui + +bin/unity.o: $(wildcard src/sys/*.c) $(wildcard src/mock_libc/*.c) + gcc $(CFLAGS) -c src/unity.c -o bin/unity.o + +bin/umka.o: src/umka.c $(wildcard src/umka/src/*.c) + gcc $(CFLAGS) -c src/umka.c -o bin/umka.o + +bin/boot.o: $(wildcard ./src/sys/*.s) + nasm -f elf32 src/sys/boot.s -o bin/boot.o + +clean: + rm -f bin/*.o + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..15974b8 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# Umka OS + +A proof of concept operating system written in Umka. (With blackjack and hookers (maybe just blackjack)) + +![Windows in the OS](cover.png) + +# Table of Contents + +* [Usage and Installation](#usage-and-installation) +* [Building](#building) +* [Why and how](#why-and-how) +* [Technology](#technology) +* [Contributing](#contributing) + +# Usage and Installation + +Currently, this OS uses a legacy BIOS bootloader, so it's unlikely to run on actual hardware. +Later I'll make an effort to port this to UEFI. + +To try it out, I recommend to go to the release pages, download the latest sys.img, and run the following command: + +``` +qemu-system-x86_64 -m 128 -drive file=sys.img,format=raw +``` + +You need to install QEMU for this to work. Alternatively you can try any other virtual machine software such as VirtualBox (but I did not test it). + +# Building + +To build, use the Makefile provided in the repository. Dependencies are: + +* gcc +* binutils +* python3 +* nasm + +Upon clone, install submodules: `git submodule init --recursive` + +# Why and how + +Because I can :P + +It started as a random idea, it was going to be a lot of work from the start. + +I started with re-implementing libc subset used by Umka, which you can find under `sys/mock_libc`. +A few basic things had to be implemented, for the most part a simple allocator and a virtual file system. +After that was done, I implemented the formatting functions (which took me longer than I expected.) + +When it came to the OS, I scraped some of my old code, found my old bootloader, fixed it, and booted into Umka. +And it didn't go all that smooth, there was a lot of trouble. I'm going to need to write a dedicated post for that later. This took me about a week to make this, it's a bit rushed, but it works. + +# Technology + +The standard library: + +* `alloc.c` Custom allocator. No brainer, since I can't use libc provided by the system. The allocator is pretty inefficient but a good enough free-list allocator. +* `vfs.c` Virtual file system. It's just a tree data structure, it's somewhat hacky - for example, files can contain other files, because there's not distinction between directories. The virtual file system is needed for file operations. The file `mkfs.py` creates an embedded image of the filesystem from the `fs/` directory. +* `fmt.c` This was probably the most work out of the C standard library, mostly the scanning and formatting functions, and yes in the end they did trip me up during the OS development, even after testing. +* `arith.c` Not written by me, these are public domain floating point functions, otherwise it wouldn't compile. + +Moving on to the system: + +The system has a kernel, written mostly in Umka, with exception for IRQ handling (due to multithreading constraints). + +* `main.c` Bootstraps the VFS image, sets up Umka and graphics. +* `gfx.c` A primitve graphics output library, it uses [rxi's cached software rendering](https://rxi.github.io/cached_software_rendering.html) to increase the render performance significantly. +* `umka.c` Where most of the stuff happens, here the IRQ's are handled and sent to Umka. + +From this point, most of the code is handled in Umka, you can find it in `fs/` + +* `main.um` Is mostly just a relay to call other functions. +* `kernel.um` `idt.um` `input.um` `pic.um` a simple kernel, it handles the IDT initialization, and the mouse driver. +* `microui.um` a very nice recreation of microui for Umka by @thacuber2a03 +* `user.um` and the programs ... the provided example software, it even includes a blackjack game, check it out! + +And extra: + +* `qoi.c`, I use QOI for icons and the background. + +Limitations: + +* It's limited by 480kb, since it can not address more memory in the bootloader. Could potentially be solved later by using UEFI instead. + +There's not much advanced things here, but it all somehow works, and that feels really good. + +# Contributing + +Always welcome =) Please mind the 480 KB limit. \ No newline at end of file diff --git a/cover.png b/cover.png new file mode 100644 index 0000000..a87efaf Binary files /dev/null and b/cover.png differ diff --git a/fs/api/asm.um b/fs/api/asm.um new file mode 100644 index 0000000..b3064fc --- /dev/null +++ b/fs/api/asm.um @@ -0,0 +1,29 @@ +fn api__asm_in8(port: uint16): uint8 +fn api__asm_out8(port: uint16, data: uint8) +fn api__asm_lidt(addr: ^void, limit: uint16) +fn api__asm_sti() +fn api__asm_cli() + +fn in8*(port: uint16): uint8 { + return api__asm_in8(port) +} + +fn out8*(port: uint16, data: uint8) { + api__asm_out8(port, data) +} + +fn lidt*(addr: ^void, limit: uint16) { + api__asm_lidt(addr, limit) +} + +fn sti*() { + api__asm_sti() +} + +fn cli*() { + api__asm_cli() +} + +fn iowait*() { + out8(0x80, 0) +} \ No newline at end of file diff --git a/fs/api/gfx.um b/fs/api/gfx.um new file mode 100644 index 0000000..993b2e9 --- /dev/null +++ b/fs/api/gfx.um @@ -0,0 +1,40 @@ +fn api__gfxSetColor(r, g, b, a: uint8) +fn api__gfxDrawRect(x, y, w, h: int) +fn api__gfxDrawClip(x, y, w, h: int) +fn api__gfxDrawChar(x, y: int, c: char) +fn api__gfxDrawBackground() +fn api__gfxDrawIcon(x, y: int, c: char) +fn api__gfxDims(w, h: ^int) +fn api__gfxSwap() + +fn setColor*(r, g, b, a: uint8) { + api__gfxSetColor(r, g, b, a) +} + +fn drawRect*(x, y, w, h: int) { + api__gfxDrawRect(x, y, w, h) +} + +fn drawClip*(x, y, w, h: int) { + api__gfxDrawClip(x, y, w, h) +} + +fn drawChar*(x, y: int, c: char) { + api__gfxDrawChar(x, y, c) +} + +fn drawIcon*(x, y: int, c: char) { + api__gfxDrawIcon(x, y, c) +} + +fn drawBackground*() { + api__gfxDrawBackground() +} + +fn dims*(w, h: ^int) { + api__gfxDims(w, h) +} + +fn swap*() { + api__gfxSwap() +} diff --git a/fs/api/rw.um b/fs/api/rw.um new file mode 100644 index 0000000..93d013e --- /dev/null +++ b/fs/api/rw.um @@ -0,0 +1,31 @@ +import ("std.um") + +fn api__writes8(data: []uint8, addr: ^void) +fn api__reads8(addr: ^void, count: uint): ^[]uint8 +fn api__reads(addr: ^void, count: uint): str +fn api__getaddr(addr: ^void): uint +fn api__getptr(addr: uint): ^void + +fn getaddr*(addr: ^void): uint { + return api__getaddr(addr) +} + +fn getptr*(addr: uint): ^void { + return api__getptr(addr) +} + +fn writes8*(addr: ^void, data: []uint8) { + api__writes8(data, addr) +} + +fn write*(addr: ^void, data: any) { + writes8(addr, std::tobytes(data)) +} + +fn reads8*(addr: ^void, count: uint): []uint8 { + return api__reads8(addr, count)^ +} + +fn reads*(addr: ^void, count: uint): str { + return api__reads(addr, count) +} diff --git a/fs/api/sym.um b/fs/api/sym.um new file mode 100644 index 0000000..fc1b922 --- /dev/null +++ b/fs/api/sym.um @@ -0,0 +1,16 @@ +import ( + "std.um" +) + +var symbols: map[str]^void + +fn register*(symbol: str, addr: ^void) { + symbols[symbol] = addr +} + +fn get*(symbol: str): ^void { + if symbols[symbol] == null { + std::assert(false, "Symbol not found: " + symbol) + } + return symbols[symbol] +} \ No newline at end of file diff --git a/fs/api/vfs.um b/fs/api/vfs.um new file mode 100644 index 0000000..b80a39a --- /dev/null +++ b/fs/api/vfs.um @@ -0,0 +1,40 @@ +import "std.um" + +type FSO* = [1]^void + +fn api__vfsHookFlush(to: std::File, handler: fn(data: ^void, size: uint)) +fn api__vfsRoot(): ^void +fn api__vfsNext(of: ^void): ^void +fn api__vfsChild(of: ^void): ^void +fn api__vfsFSOOpen(fso: ^void): std::File +fn api__vfsFSOName(fso: ^void): str + +fn hookFlush*(to: std::File, handler: fn(data: ^void, size: uint)) { + api__vfsHookFlush(to, handler) +} + +fn root*(): FSO { + return {api__vfsRoot()} +} + +fn (fso: ^FSO) valid*(): bool { + return fso[0] != null +} + +fn (fso: ^FSO) next*(): FSO { + return {api__vfsNext(fso[0])} +} + +fn (fso: ^FSO) child*(): FSO { + return {api__vfsChild(fso[0])} +} + +fn (fso: ^FSO) open*(): std::File { + return api__vfsFSOOpen(fso[0]) +} + +fn (fso: ^FSO) name*(): str { + return api__vfsFSOName(fso[0]) +} + + diff --git a/fs/kernel/idt.um b/fs/kernel/idt.um new file mode 100644 index 0000000..eabe420 --- /dev/null +++ b/fs/kernel/idt.um @@ -0,0 +1,33 @@ +import ( + "/api/asm.um" + "/api/rw.um" +) + +type IdtEntry = struct { + offsetLow: uint16 + selector: uint16 + zero: uint8 + kind: uint8 + offsetHigh: uint16 +} + +var idt: [256]IdtEntry + +fn init*() { + printf("[IDT] Loading at %v (%x bytes)\n", &idt, sizeof(idt)-1) + asm::lidt(&idt, sizeof(idt)-1) +} + +fn register*(entry: uint8, fnaddr: ^void) { + addr := rw::getaddr(fnaddr) + + printf("[IDT] Registering entry %d at %x\n", entry, addr) + + idt[entry] = { + offsetLow: addr & 0xFFFF, + selector: 0x08, + zero: 0, + kind: 0x8E, + offsetHigh: (addr >> 16) & 0xFFFF + } +} diff --git a/fs/kernel/input.um b/fs/kernel/input.um new file mode 100644 index 0000000..3124d29 --- /dev/null +++ b/fs/kernel/input.um @@ -0,0 +1,150 @@ +import ( + "std.um" + "idt.um" + "pic.um" + "/api/asm.um" + "/api/sym.um" +) + +type ( + Packet* = any + + MousePacket* = struct { + dx: int + dy: int + lmb: bool + rmb: bool + mmb: bool + } +) + +const ( + ps2cmd = 0x64 + ps2data = 0x60 + + keyboardInt = 33 + mouseInt = 44 + + keyboard = 1 << 1 + slave = 1 << 2 + mouse = 1 << 12 +) + +var ( + packets: []Packet +) + +fn mouseWaitWrite() { + for i := 0; i < 100000; i++ { + if asm::in8(ps2cmd) & 2 == 0 { + return + } + } + + std::assert(false, "Mouse write timeout") +} + +fn mouseWaitRead() { + for i := 0; i < 100000; i++ { + if asm::in8(ps2cmd) & 1 == 1 { + return + } + } + + std::assert(false, "Mouse read timeout") +} + +fn mouseWriteCmd(cmd: uint8) { + mouseWaitWrite() + asm::out8(ps2cmd, cmd) +} + +fn mouseWrite(data: uint8) { + mouseWaitWrite() + asm::out8(ps2cmd, 0xD4) + mouseWaitWrite() + asm::out8(ps2data, data) +} + +fn mouseRead(): uint8 { + mouseWaitRead() + return asm::in8(ps2data) +} + +fn initMouse() { + mouseWriteCmd(0xA8) // A8h - Enable + mouseWriteCmd(0x20) // 20h - Get config + mouseWaitRead() + status := asm::in8(ps2data) | 2 // Enable IRQ bit + mouseWriteCmd(0x60) // 60h - Set config + mouseWaitWrite() + asm::out8(ps2data, status) + mouseWrite(0xF6) // Set defaults + mouseRead() + mouseWrite(0xF4) // Enable packets + mouseRead() +} + +mouseCycle := 0 +mouseBytes := [3]uint8{0, 0, 0} +fn handleMouseIrq(bytes: []uint8) { + for i, byte in bytes { + switch mouseCycle { + case 0: + if byte & 0x8 == 0 { + break + } + + mouseBytes[0] = byte + mouseCycle++ + case 1: + mouseBytes[1] = byte + mouseCycle++ + case 2: + mouseBytes[2] = byte + mouseCycle = 0 + + if (mouseBytes[0] & 0x80) > 0 || (mouseBytes[0] & 0x40) > 0 { + break + } + + dx := int16(mouseBytes[1]) - ((mouseBytes[0] << 4) & 0x100) + dy := int16(mouseBytes[2]) - ((mouseBytes[0] << 3) & 0x100) + + packet := MousePacket{ + dx: dx, + dy: dy, + lmb: mouseBytes[0] & 1 > 0, + rmb: mouseBytes[0] & 2 > 0, + mmb: mouseBytes[0] & 4 > 0 + } + + packets = append(packets, packet) + } + } +} + +fn init*() { + idt::register(keyboardInt, sym::get("irq1")) + idt::register(mouseInt, sym::get("irq12")) + idt::init() + + initMouse() + + pic::init() + pic::setMask(0xFFFF ~ (mouse | keyboard | slave)) +} + +fn irq*(irq: uint8, bytes: []uint8) { + switch irq { + case 12: handleMouseIrq(bytes) + } +} + +fn relay*(f: fn(event: Packet)) { + for i, packet in packets { + f(packet) + } + + packets = {} +} diff --git a/fs/kernel/kernel.um b/fs/kernel/kernel.um new file mode 100644 index 0000000..61e784b --- /dev/null +++ b/fs/kernel/kernel.um @@ -0,0 +1,26 @@ +import ( + "std.um" + "input.um" + "/api/vfs.um" + "/api/rw.um" + "/user/user.um" +) + +fn outputConsole(data: ^void, size: uint) { + user::output(rw::reads(data, size)) +} + +fn init*() { + vfs::hookFlush(std::stdout(), outputConsole) + vfs::hookFlush(std::stderr(), outputConsole) + input::init() + user::init() +} + +fn irq*(irq: uint8, bytes: []uint8) { + input::irq(irq, bytes) +} + +fn schedule*() { + user::schedule() +} \ No newline at end of file diff --git a/fs/kernel/pic.um b/fs/kernel/pic.um new file mode 100644 index 0000000..1d7ac9c --- /dev/null +++ b/fs/kernel/pic.um @@ -0,0 +1,50 @@ +import ( + "/api/asm.um" +) + +const ( + masterCommand = 0x20 + masterData = 0x21 + slaveCommand = 0xA0 + slaveData = 0xA1 + + icw1Icw4 = 0x01 + icw1Init = 0x10 + icw48086 = 0x01 + + masterIrqs = 0x20 + slaveIrqs = 0x28 + slavePin = 2 +) + +fn init*() { + asm::iowait() + asm::out8(masterCommand, icw1Init | icw1Icw4) + asm::iowait() + asm::out8(slaveCommand, icw1Init | icw1Icw4) + asm::iowait() + asm::out8(masterData, masterIrqs) + asm::iowait() + asm::out8(slaveData, slaveIrqs) + asm::iowait() + asm::out8(masterData, 1 << slavePin) + asm::iowait() + asm::out8(slaveData, slavePin) + asm::iowait() + asm::out8(masterData, icw48086) + asm::iowait() + asm::out8(slaveData, icw48086) +} + +fn getMask*(): uint16 { + master := asm::in8(masterData) + slave := asm::in8(slaveData) + return (uint16(slave) << 8) | uint16(master) +} + +fn setMask*(mask: uint16) { + master := mask & 0xFF + slave := mask >> 8 + asm::out8(masterData, master) + asm::out8(slaveData, slave) +} \ No newline at end of file diff --git a/fs/main.um b/fs/main.um new file mode 100644 index 0000000..ad2b71d --- /dev/null +++ b/fs/main.um @@ -0,0 +1,28 @@ +import ( + "api/sym.um" + "kernel/kernel.um" + "api/rw.um" +) + + +fn system__register*(name: str, addr: ^void) { + sym::register(name, addr) +} + +fn api__setupTypePtr(i: int, t: ^void) +fn system__init*() { + api__setupTypePtr(0, typeptr(uint8)) + api__setupTypePtr(1, typeptr(uint16)) + api__setupTypePtr(2, typeptr(uint32)) + api__setupTypePtr(3, typeptr([]uint8)) + kernel::init() +} + +fn system__irq*(irq: uint8, data: ^void, count: uint) { + bytes := rw::reads8(data, count) + kernel::irq(irq, bytes) +} + +fn system__schedule*() { + kernel::schedule() +} diff --git a/fs/res/background.qoi b/fs/res/background.qoi new file mode 100644 index 0000000..a514d0c Binary files /dev/null and b/fs/res/background.qoi differ diff --git a/fs/res/icons.qoi b/fs/res/icons.qoi new file mode 100644 index 0000000..83fad8c Binary files /dev/null and b/fs/res/icons.qoi differ diff --git a/fs/subdir/test.txt b/fs/subdir/test.txt new file mode 100644 index 0000000..a0c9e5b --- /dev/null +++ b/fs/subdir/test.txt @@ -0,0 +1 @@ +This is loaded from the virtual file system :) \ No newline at end of file diff --git a/fs/user/about.um b/fs/user/about.um new file mode 100644 index 0000000..69f911e --- /dev/null +++ b/fs/user/about.um @@ -0,0 +1,30 @@ +import ( + "tasks.um" +) + +fn register*() { + tasks::createClass("About", { + if ctx.header("Umka OS") == .Active { + ctx.layoutRow({120, -1}, 20) + ctx.label("@skejeton") + ctx.label("(https://github.com/skejeton)") + } + if ctx.header("Umka") == .Active { + ctx.layoutRow({120, -1}, 20) + ctx.label("@vtereshkov") + ctx.label("(https://github.com/vtereshkov)") + } + if ctx.header("QOI") == .Active { + ctx.layoutRow({120, -1}, 20) + ctx.label("@phoboslab") + ctx.label("(https://github.com/phoboslab)") + } + if ctx.header("Umka MicroUI") == .Active { + ctx.layoutRow({120, -1}, 20) + ctx.label("@thacuber2a03") + ctx.label("(https://github.com/thacuber2a03)") + } + return true + }) +} + diff --git a/fs/user/blackjack.um b/fs/user/blackjack.um new file mode 100644 index 0000000..32db95f --- /dev/null +++ b/fs/user/blackjack.um @@ -0,0 +1,474 @@ +import ( + "tasks.um" + "microui.um" + "std.um" +) + +type Suit = enum { + hearts + diamonds + clubs + spades + count +} + +type Value = enum { + ace + two + three + four + five + six + seven + eight + nine + ten + jack + queen + king + count +} + +type Card = struct { + suit: Suit + value: Value +} + +type BlackjackInstance = struct { + firsthit: bool + insured: bool + over: bool + forfeited: bool + betReal: real + bet: int + rules: bool + reveal: bool + started: bool + deck: [52]Card + dealer: []^Card + player: []^Card + hand: []^Card + stash: []^Card +} + +fn generateDeck(): [52]Card { + var deck: [52]Card + index := 0 + + for i := 0; i < int(Suit.count); i++ { + for j := 0; j < int(Value.count); j++ { + deck[index] = Card{Suit(i), Value(j)} + index++ + } + } + + return deck +} + +fn shuffleDeck(deck: [52]^Card) { + for i := 0; i < 52; i++ { + j := tasks::rand()%52 + deck[i]^, deck[j]^ = deck[j]^, deck[i]^ + } +} + +fn putCard(ctx: ^microui::Context, card: Card, worth: int, hidden: bool = false) { + rect := ctx.layoutNext() + ctx.drawRect(rect, {255, 255, 255, 255}) + if hidden { + ctx.drawText(ctx.style.font, "?", {rect.x, rect.y}, {0, 0, 255, 255}) + return + } + + name := "" + switch card.suit { + case .hearts: name = "hearts" + case .diamonds: name = "diamonds" + case .clubs: name = "clubs" + case .spades: name = "spades" + } + + value := "" + switch card.value { + case .ace: value = "ace" + case .two: value = "two" + case .three: value = "three" + case .four: value = "four" + case .five: value = "five" + case .six: value = "six" + case .seven: value = "seven" + case .eight: value = "eight" + case .nine: value = "nine" + case .ten: value = "ten" + case .jack: value = "jack" + case .queen: value = "queen" + case .king: value = "king" + } + + color := microui::Color{0, 0, 0, 255} + if card.suit == Suit.hearts || card.suit == Suit.diamonds { + color = {255, 0, 0, 255} + } + + ctx.drawText(ctx.style.font, value, {rect.x, rect.y}, color) + ctx.drawText(ctx.style.font, "of", {rect.x, rect.y+8}, color) + ctx.drawText(ctx.style.font, name, {rect.x, rect.y+16}, color) + if worth == 0 { + ctx.drawText(ctx.style.font, "(+?)", {rect.x, rect.y+24}, color) + } else { + ctx.drawText(ctx.style.font, "(+"+std::itoa(worth)+")", {rect.x, rect.y+24}, color) + } +} + +fn getValues(cards: []^Card): ([]int, int) { + var values: []int + sum := 0 + + for _, card in cards { + start := sum + + switch card.value { + case .two: sum += 2 + case .three: sum += 3 + case .four: sum += 4 + case .five: sum += 5 + case .six: sum += 6 + case .seven: sum += 7 + case .eight: sum += 8 + case .nine: sum += 9 + case .ten: sum += 10 + case .jack: sum += 10 + case .queen: sum += 10 + case .king: sum += 10 + } + + values = append(values, sum - start) + } + + for i, card in cards { + if card.value == Value.ace { + if sum == 10 { + values[i] = 11 + sum += 11 + } else if sum + 10 <= 21 { + values[i] = 10 + sum += 10 + } else { + values[i] = 1 + sum += 1 + } + } + } + + return values, sum +} + +fn drawCard(inst: ^BlackjackInstance): ^Card { + if len(inst.hand) == 0 { + inst.hand = inst.stash + inst.stash = {} + shuffleDeck(inst.hand) + } + + card := inst.hand[0] + inst.hand = delete(inst.hand, 0) + inst.stash = append(inst.stash, card) + return card +} + + +fn drawRound(inst: ^BlackjackInstance) { + inst.firsthit = true + inst.insured = false + inst.over = false + inst.reveal = false + inst.forfeited = false + inst.dealer = {} + inst.player = {} + inst.dealer = append(inst.dealer, drawCard(inst)) + inst.player = append(inst.player, drawCard(inst)) + inst.dealer = append(inst.dealer, drawCard(inst)) + inst.player = append(inst.player, drawCard(inst)) +} + +fn shouldHideDealersCard(inst: ^BlackjackInstance): bool { + _, dealerSum := getValues(inst.dealer) + _, playerSum := getValues(inst.player) + + if dealerSum > 21 || playerSum >= 21 || inst.forfeited { + inst.reveal = true + } + return !inst.reveal +} + +fn isGameOver(inst: ^BlackjackInstance): bool { + _, dealerSum := getValues(inst.dealer) + _, playerSum := getValues(inst.player) + + return inst.forfeited || playerSum >= 21 || dealerSum > 21 || (inst.reveal && dealerSum >= 17) +} + +fn determineStatus(inst: ^BlackjackInstance): str { + _, dealerSum := getValues(inst.dealer) + _, playerSum := getValues(inst.player) + + if inst.forfeited { + return "Player forfeited" + } + + if !inst.reveal { + return "Awaiting action" + } + + if playerSum == dealerSum { + return "Draw" + } + + if playerSum > 21 { + return "Player bust" + } + + if dealerSum > 21 { + return "Dealer bust" + } + + if playerSum == 21 && len(inst.player) == 2 { + return "Player blackjack" + } + + if dealerSum == 21 && len(inst.dealer) == 2 { + return "Dealer blackjack" + } + + if isGameOver(inst) { + if playerSum > dealerSum { + return "Player win" + } + + if dealerSum > playerSum { + return "Dealer win" + } + } + + + return "Awaiting action" +} + +fn stand(inst: ^BlackjackInstance) { + inst.reveal = true + _, dealerSum := getValues(inst.dealer) + + if dealerSum < 17 { + inst.dealer = append(inst.dealer, drawCard(inst)) + stand(inst) + } +} + +fn hit(inst: ^BlackjackInstance) { + inst.player = append(inst.player, drawCard(inst)) +} + +fn whoWon(inst: ^BlackjackInstance): int { + if inst.forfeited { + return -1 + } + + _, dealerSum := getValues(inst.dealer) + _, playerSum := getValues(inst.player) + + if playerSum == dealerSum { + return 0 + } + + if playerSum > 21 { + return -1 + } + + if dealerSum > 21 { + return 1 + } + + if playerSum == 21 { + return 1 + } + + if dealerSum == 21 { + return -1 + } + + if playerSum > dealerSum { + return 1 + } + + if dealerSum > playerSum { + return -1 + } + + return 0 +} + +rulestxt := "The goal of blackjack is to beat the dealer's hand without going over 21.\n"+ + "The game starts by dealing two cards to the player and two cards to the dealer.\n"+ + "Then, the game prompts the player to Hit or Stand. If the player chooses to Hit,\n"+ + "the player gets another card. If the player chooses to Stand, the game moves to the\n"+ + "dealer's turn. The dealer must hit until the sum of their cards is 17 or higher.\n"+ + "The player or the dealer wins if their sum is higher than the opponent's sum without going over 21.\n"+ + "If the player and the dealer have the same sum, it's a draw. If the player goes over 21, it's a bust.\n"+ + "Aces have special rules: if you have a card of value 10 + an Ace, you automatically win (blackjack)\n"+ + "If you have an Ace and the sum of your cards is 11 or 9 or less, the Ace is worth 10. Otherwise, it's worth 1.\n"+ + "To memorize: the ace is worth 11 or 10 unless it would make the sum go over 21. The smallest value before going over.\n"+ + "If the dealer has an Ace, you can choose to buy insurance, it costs half the bet, in that case, if the dealer has a blackjack, you get your bet back; otherwise, you only lose your insurance money.\n"; + +balance := 1000 + +fn register*() { + tasks::createClass("Blackjack", { + if inst := ^BlackjackInstance(task.data); inst == null { + task.data = BlackjackInstance{ + deck: generateDeck(), + over: true + } + inst = ^BlackjackInstance(task.data) + + for i, card^ in inst.deck { + inst.hand = append(inst.hand, card) + } + shuffleDeck(inst.hand) + } + + inst := ^BlackjackInstance(task.data) + + ctx.layoutRow({74, 70, 30, 50, 60, -1}, 20) + if ctx.button("New round") == .Submit { + if balance < 0 { + if inst.bet > 5 { + inst.bet = 5 + } + } + inst.rules = false + inst.started = true + balance -= inst.bet + drawRound(inst) + } + + ctx.checkbox("Rules", &inst.rules) + + if inst.rules { + ctx.text(rulestxt) + return true + } + + if inst.started { + if !isGameOver(inst) { + if ctx.button("Hit") == .Submit { + inst.firsthit = false + hit(inst) + } + if ctx.button("Stand") == .Submit { + inst.firsthit = false + stand(inst) + } + } else { + ctx.button("Hit", .None, .NoInteract) + ctx.button("Stand", .None, .NoInteract) + } + + if !isGameOver(inst) && inst.firsthit { + if ctx.button("Double") == .Submit { + hit(inst) + balance -= inst.bet + inst.bet *= 2 + if !isGameOver(inst) { + stand(inst) + } + } + } else { + ctx.button("Double", .None, .NoInteract) + } + + if inst.dealer[0].value == .ace && !isGameOver(inst) && !inst.insured { + if ctx.button("Insurance") == .Submit { + balance -= inst.bet/2 + _, dealerSum := getValues(inst.dealer) + if dealerSum == 21 { + inst.reveal = true + balance += inst.bet + } + inst.insured = true + } + } + } + + ctx.layoutRow({120, 120, 120}, 20) + if ctx.button("Forfeit") == .Submit { + if !isGameOver(inst) { + inst.forfeited = true + } + } + ctx.label("Balance $"+std::itoa(balance)) + if !inst.started || isGameOver(inst) { + if balance < 10 { + ctx.slider(&inst.betReal, 5, 10, 5, "Bet $%g") + } else { + ctx.slider(&inst.betReal, 5, balance, 5, "Bet $%g") + } + if inst.over { + inst.bet = trunc(inst.betReal) + } + } else { + ctx.label("Bet $"+std::itoa(inst.bet)) + } + + if !inst.started { + return true + } + + shouldHide := shouldHideDealersCard(inst) + dealerValues, dealerSum := getValues(inst.dealer) + ctx.layoutRow({-1}, 15) + if shouldHide { + if inst.dealer[0].value == .ace { + ctx.label("Dealer sum: ?+?") + } else { + ctx.label("Dealer sum: ?+"+std::itoa(dealerValues[0])) + } + } else { + ctx.label(sprintf("Dealer sum: %d", dealerSum)) + } + + ctx.layoutRow({62, 62, 62, 62, 62, 62}, 80) + for i := 0; i < len(inst.dealer); i++ { + if shouldHide && i == 0 && inst.dealer[0].value == .ace { + putCard(ctx, inst.dealer[i]^, 0) + } else if shouldHide && i == 1 { + putCard(ctx, inst.dealer[i]^, dealerValues[i], true) + } else { + putCard(ctx, inst.dealer[i]^, dealerValues[i]) + } + } + playerValues, playerSum := getValues(inst.player) + ctx.layoutRow({-1}, 15) + ctx.label(sprintf("Player sum: %d", playerSum)) + ctx.layoutRow({62, 62, 62, 62, 62, 62}, 80) + for i := 0; i < len(inst.player); i++ { + putCard(ctx, inst.player[i]^, playerValues[i]) + } + ctx.layoutRow({-1}, 15) + ctx.label("Status: "+determineStatus(inst)+", "+std::itoa(len(inst.hand))+" cards left in hand") + + if isGameOver(inst) { + if !inst.over { + switch whoWon(inst) { + case 1: + balance += inst.bet*2 + case -1: + case 0: + balance += inst.bet + } + inst.over = true + } + } + + return true + }) +} \ No newline at end of file diff --git a/fs/user/console.um b/fs/user/console.um new file mode 100644 index 0000000..580758c --- /dev/null +++ b/fs/user/console.um @@ -0,0 +1,32 @@ +import ( + "tasks.um" +) + +string := "" + +fn write*(text: str) { + string += text +} + +fn register*() { + tasks::createClass("Console", { + ctx.layoutRow({-1}, 10) + + lines := []str{} + s := "" + for i, c in string { + if (c == '\n') { + lines = append(lines, s) + s = "" + } else { + s += c + } + } + + for i, line in lines { + ctx.label(line) + } + return true + }) +} + diff --git a/fs/user/filesystem.um b/fs/user/filesystem.um new file mode 100644 index 0000000..ff5a34c --- /dev/null +++ b/fs/user/filesystem.um @@ -0,0 +1,35 @@ +import ( + "tasks.um" + "/api/vfs.um" + "microui.um" +) + +fn displayTree(ctx: ^microui::Context, node: vfs::FSO) { + ctx.layoutRow({-1}, 10) + if !node.child().valid() { + ctx.label(node.name()) + return + } + + ok := false + if (node.name() == "") { + ok = ctx.beginTreenode("/") == .Active + } else { + ok = ctx.beginTreenode(node.name()) == .Active + } + + if ok { + for child := node.child(); child.valid(); child = child.next() { + displayTree(ctx, child) + } + ctx.endTreenode() + } +} + +fn register*() { + tasks::createClass("Filesystem", { + root := vfs::root() + displayTree(ctx, root) + return true + }) +} \ No newline at end of file diff --git a/fs/user/microui.um b/fs/user/microui.um new file mode 100644 index 0000000..c153426 --- /dev/null +++ b/fs/user/microui.um @@ -0,0 +1,1293 @@ +import ( "std.um" ) + +const VERSION* = "0.1.0" + +const ROOTLIST_SIZE* = 32 +const CONTAINERSTACK_SIZE* = 32 +const CLIPSTACK_SIZE* = 32 +const IDSTACK_SIZE* = 32 +const RETAINEDPOOL_SIZE* = 48 + +const REAL_FMT* = "%.3g" +const SLIDER_FMT* = "%.2f" + +type ( + Id* = uint + Font* = any + Real* = real +) + +fn min*(a, b: int): int { return a < b ? a : b } +fn max*(a, b: int): int { return a > b ? a : b } +fn clamp*(x, a, b: Real): Real { return x < a ? a : x > b ? b : x } + +type ClipState* = enum { None; Part; All } + +type ControlColor* = enum { + Text + Border + WindowBG + TitleBG + TitleText + PanelBG + Button + ButtonHover + ButtonFocus + Base + BaseHover + BaseFocus + ScrollBase + ScrollThumb + Max +} + +type Icon* = enum { + None + Close + Check + Collapsed + Expanded + Max +} + +type Cursor* = enum { + None + Arrow + Hand + Size + IBeam +} + +type Result* = enum { + Nothing = 0 + Active = 1 << 0 + Submit = 1 << 1 + Change = 1 << 2 +} + +fn (r: ^Result) set*(b: Result) { ^int(r)^ |= int(b) } +fn (r: ^Result) has*(b: Result): bool { return int(r^) & int(b) != 0 } + +type Option* = enum { + None = 0 + AlignCenter = 1 << 0 + AlignRight = 1 << 1 + NoInteract = 1 << 2 + NoFrame = 1 << 3 + NoResize = 1 << 4 + NoScroll = 1 << 5 + NoClose = 1 << 6 + NoTitle = 1 << 7 + HoldFocus = 1 << 8 + AutoSize = 1 << 9 + Popup = 1 << 10 + Closed = 1 << 11 + Expanded = 1 << 12 +} + +fn (o: ^Option) has*(opt: Option): bool { return int(o^) & int(opt) != 0 } + +fn optAnd*(o: ..Option): Option { + std::assert(len(o) >= 2) + i := 0 + for _, p^ in o { i |= int(p^) } + return Option(i) +} + +fn (o: ^Option) and*(opt: ..Option): Option { + return optAnd(append(opt, o^)) +} + +type MouseButton* = enum { + None = 0 + Left = 1 << 0 + Right = 1 << 1 + Middle = 1 << 2 +} + +fn (m: ^MouseButton) set*(b: MouseButton) { ^int(m)^ |= int(b) } +fn (m: ^MouseButton) unset*(b: MouseButton) { ^int(m)^ &= ~int(b) } +fn (m: ^MouseButton) has*(b: MouseButton): bool { return int(m^) & int(b) != 0 } +fn (m: ^MouseButton) is*(b: MouseButton): bool { return int(m^) == int(b) } +fn (m: ^MouseButton) any*(): bool { return int(m^) != 0 } + +type KeyboardKey* = enum { + None = 0 + Shift = 1 << 0 + Ctrl = 1 << 1 + Alt = 1 << 2 + Backspace = 1 << 3 + Return = 1 << 4 +} + +fn (k: ^KeyboardKey) set*(b: KeyboardKey) { ^int(k)^ |= int(b) } +fn (k: ^KeyboardKey) unset*(b: KeyboardKey) { ^int(k)^ &= ~int(b) } +fn (k: ^KeyboardKey) has*(b: KeyboardKey): bool { return int(k^) & int(b) != 0 } +fn (k: ^KeyboardKey) is*(b: KeyboardKey): bool { return int(k^) == int(b) } +fn (k: ^KeyboardKey) any*(): bool { return int(k^) != 0 } + +type ( + Vec2* = struct { x, y : int } + Rect* = struct { x, y, w, h: int } + Color* = struct { r, g, b, a: uint8 } +) + +type ( + Command* = interface { _cmd() } + JumpCommand* = struct { dst: uint } + ClipCommand* = struct { rect: Rect } + RectCommand* = struct { rect: Rect; color: Color } + TextCommand* = struct { font: Font; pos: Vec2; color: Color; text: str } + IconCommand* = struct { rect: Rect; id: Icon; color: Color } + CursorCommand* = struct { id: Cursor } +) + +fn (_: ^JumpCommand) _cmd() { std::assert(false) } +fn (_: ^ClipCommand) _cmd() { std::assert(false) } +fn (_: ^RectCommand) _cmd() { std::assert(false) } +fn (_: ^TextCommand) _cmd() { std::assert(false) } +fn (_: ^IconCommand) _cmd() { std::assert(false) } +fn (_: ^CursorCommand) _cmd() { std::assert(false) } + +type ( + LayoutNextType* = enum { None; Absolute; Relative } + + Layout* = struct { + body, next: Rect + position, size, max: Vec2 + widths: []int + itemIndex: int + nextRow: int + nextType: LayoutNextType + indent: int + } +) + +type Container* = struct { + head, tail: uint // Command list indexes + rect, body: Rect + contentSize, scroll: Vec2 + zindex: int + open: bool +} + +type Style* = struct { + font: Font + size: Vec2 + padding, spacing, indent: int + titleHeight: int + scrollbarSize, thumbSize: int + colors: [uint(ControlColor.Max)]Color +} + +fn (s: ^Style) getColor(c: ControlColor): Color { return s.colors[uint(c)] } + +type ( + PoolItem = struct { id: Id; lastUpdate: int } + Pool = [RETAINEDPOOL_SIZE]PoolItem + + TextWidthFn* = fn (font: Font, text: str): uint + TextHeightFn* = fn (font: Font): uint + DrawFrameFn* = fn (ctx: ^Context, rect: Rect, colorid: ControlColor) + + FixedStack = struct { items: []any; idx: uint } + + Context* = struct { + textWidth: TextWidthFn + textHeight: TextHeightFn + drawFrame: DrawFrameFn + + _style: Style + style: ^Style + hover, focus: Id + lastId: Id + lastRect: Rect + lastZIndex: int + updatedFocus: bool + frame: uint + hoverRoot, nextHoverRoot: ^Container + scrollTarget: ^Container + numberEditBuf: str + numberEdit: Id + lastCursor: Cursor + + commandList: []Command + rootList: FixedStack // [ROOTLIST_SIZE]weak ^Container + containerStack: FixedStack // [CONTAINERSTACK_SIZE]weak ^Container + clipStack: FixedStack // [CLIPSTACK_SIZE]Rect + idStack: FixedStack // [IDSTACK_SIZE]Id + layoutStack: []Layout + + containerPool, treenodePool: Pool + containers: [RETAINEDPOOL_SIZE]Container + + mousePos, lastMousePos: Vec2 + mouseDelta, scrollDelta: Vec2 + + mouseDown, mousePressed: MouseButton + keyDown, keyPressed: KeyboardKey + inputText: str + } +) + +fn (s: ^FixedStack) init(l: uint) { + s.items = make([]any, l) + s.idx = 0 +} + +fn (s: ^FixedStack) push(val: any) { + std::assert(valid(s.items), "invalid stack") + std::assert(s.idx < len(s.items)) + s.items[s.idx] = val + s.idx++ +} + +fn (s: ^FixedStack) pop() { + std::assert(valid(s.items), "invalid stack") + s.idx-- +} + +fn (s: ^FixedStack) size(): uint { + std::assert(valid(s.items), "invalid stack") + return s.idx +} + +const unclippedRect = Rect { 0, 0, 0x1000000, 0x1000000 } + +const defaultStyle = Style { + // font | size | padding | spacing | indent + null, { 68, 10 }, 5, 4, 24, + // title_height | scrollbar_size | thumb_size + 24, 12, 8, + { + { 230, 230, 230, 255 }, // Text + { 25, 25, 25, 255 }, // Border + { 50, 50, 50, 255 }, // WindowBG + { 25, 25, 25, 255 }, // TitleBG + { 240, 240, 240, 255 }, // TitleText + { 0, 0, 0, 0 }, // PanelBG + { 75, 75, 75, 255 }, // Button + { 95, 95, 95, 255 }, // ButtonHover + { 115, 115, 115, 255 }, // ButtonFocus + { 30, 30, 30, 255 }, // Base + { 35, 35, 35, 255 }, // BaseHover + { 40, 40, 40, 255 }, // BaseFocus + { 43, 43, 43, 255 }, // ScrollBase + { 30, 30, 30, 255 } // ScrollThumb + } +} + +fn (r: ^Rect) expanded(n: uint): Rect { + return { + r.x - n, r.y - n, + r.w + n * 2, r.h + n * 2 + } +} + +fn (r1: ^Rect) intersectedWith(r2: Rect): Rect { + x1 := max(r1.x, r2.x) + y1 := max(r1.y, r2.y) + x2 := min(r1.x + r1.w, r2.x + r2.w) + y2 := min(r1.y + r1.h, r2.y + r2.h) + if x2 < x1 { x2 = x1 } + if y2 < y1 { y2 = y1 } + return { x1, y1, x2 - x1, y2 - y1 } +} + +fn (r: ^Rect) overlaps(p: Vec2): bool { + return p.x >= r.x && p.x < r.x + r.w && p.y >= r.y && p.y < r.y + r.h +} + +fn poolGet(items: ^Pool, length: int, id: Id): int { + for i := 0; i < length; i++ { + if items[i].id == id { return i } + } + return -1 +} + +fn poolUpdate(ctx: ^Context, items: ^Pool, idx: int) { + items[idx].lastUpdate = ctx.frame +} + +fn poolInit(ctx: ^Context, items: ^Pool, length: int, id: Id): int { + n, f := -1, ctx.frame + for i := 0; i < len(items^); i++ { + if items[i].lastUpdate < f { + f, n = items[i].lastUpdate, i + } + } + + std::assert(n > -1) + items[n].id = id + poolUpdate(ctx, items, n) + return n +} + +fn (ctx: ^Context) drawRect*(rect: Rect, color: Color) +fn (ctx: ^Context) drawBox* (rect: Rect, color: Color) + +fn drawFrame(ctx: ^Context, rect: Rect, colorid: ControlColor) { + ctx.drawRect(rect, ctx.style.getColor(colorid)) + + if colorid == .ScrollBase || + colorid == .ScrollThumb || + colorid == .TitleBG { return; } + + b := &ctx.style.getColor(.Border) + if b.a != 0 { ctx.drawBox(rect.expanded(1), b^) } +} + +fn (ctx: ^Context) init*() { + ctx.drawFrame = drawFrame + ctx._style = defaultStyle + ctx.style = &ctx._style + + ctx.commandList = {} + ctx.rootList.init(ROOTLIST_SIZE) + ctx.containerStack.init(CONTAINERSTACK_SIZE) + ctx.clipStack.init(CLIPSTACK_SIZE) + ctx.idStack.init(IDSTACK_SIZE) + ctx.layoutStack = make([]Layout, 0) + ctx.containers = []Container{} +} + +fn (ctx: ^Context) begin*() { + std::assert( + valid(ctx.textWidth) && valid(ctx.textHeight), + "either ctx.textWidth or ctx.textHeight aren't initialized" + ) + + ctx.commandList = {} + ctx.rootList.init(ROOTLIST_SIZE) + ctx.scrollTarget = null + ctx.hoverRoot = ctx.nextHoverRoot + ctx.lastCursor = .None + ctx.nextHoverRoot = null + ctx.mouseDelta.x = ctx.mousePos.x - ctx.lastMousePos.x + ctx.mouseDelta.y = ctx.mousePos.y - ctx.lastMousePos.y + ctx.frame++ +} + +fn partition(a: FixedStack, lo, hi: int): int { + p := ^Container(a.items[hi]).zindex + i := lo - 1 + for j := lo; j < hi; j++ { + if ^Container(a.items[j]).zindex < p { + i++ + t := a.items[i] + a.items[i] = a.items[j] + a.items[j] = t + } + } + t := a.items[i+1] + a.items[i+1] = a.items[hi] + a.items[hi] = t + return i + 1 +} + +// Root-list specific, sorts by zindex +fn qsort(a: FixedStack, lo, hi: int) { + if lo < hi { + p := partition(a, lo, hi) + qsort(a, lo, p-1) + qsort(a, p+1, hi) + } +} + +fn (ctx: ^Context) bringToFront*(cnt: ^Container) + +fn (ctx: ^Context) end*() { + std::assert(ctx.containerStack.size() == 0, "container stack isn't empty") + std::assert(ctx.clipStack.size() == 0, "clip stack isn't empty") + std::assert(ctx.idStack.size() == 0, "id stack isn't empty") + std::assert(len(ctx.layoutStack) == 0, "layout stack isn't empty") + + if ctx.scrollTarget != null { + ctx.scrollTarget.scroll.x += ctx.scrollDelta.x + ctx.scrollTarget.scroll.y += ctx.scrollDelta.y + } + + if !ctx.updatedFocus { ctx.focus = 0 } + ctx.updatedFocus = false + + if ctx.mousePressed.any() && + ctx.nextHoverRoot != null && + ctx.nextHoverRoot.zindex < ctx.lastZIndex && + ctx.nextHoverRoot.zindex >= 0 { + ctx.bringToFront(ctx.nextHoverRoot) + } + + ctx.keyPressed = .None + ctx.inputText = "" + ctx.mousePressed = .None + ctx.scrollDelta = { 0, 0 } + ctx.lastMousePos = ctx.mousePos + + n := ctx.rootList.size() + qsort(ctx.rootList, 0, n-1) + + for i := 0; i < n; i++ { + cnt := ^Container(ctx.rootList.items[i]) + if i == 0 { + j := ^JumpCommand(ctx.commandList[0]) + std::assert(j != null) + j.dst = cnt.head + 1 + } else { + prev := ^Container(ctx.rootList.items[i-1]) + j := ^JumpCommand(ctx.commandList[prev.tail]) + std::assert(j != null) + j.dst = cnt.head + 1 + } + + if i == n - 1 { + j := ^JumpCommand(ctx.commandList[cnt.tail]) + std::assert(j != null) + j.dst = len(ctx.commandList) + } + } +} + +fn (ctx: ^Context) setFocus*(id: Id) { + ctx.focus = id + ctx.updatedFocus = true +} + +// *64-bit* fnv-1a hash this time round +const HASH_INITIAL = 0xcbf29ce484222325 + +fn hash(h: ^Id, data: []uint8) { + for _, b in data { h^ = (h^ ~ b) * 0x00000100000001B3 } +} + +fn (ctx: ^Context) getId*(data: []uint8): Id { + idx := ctx.idStack.size() + res := idx > 0 ? Id(ctx.idStack.items[idx-1]) : HASH_INITIAL + hash(&res, data) + ctx.lastId = res + return res +} + +fn (ctx: ^Context) getIdFromStr*(s: str): uint { return ctx.getId([]uint8([]char(s))) } + +fn (ctx: ^Context) getIdFromPtr*(p: ^void): uint { + s := sprintf("%v", p); return ctx.getIdFromStr(s) +} + +fn (ctx: ^Context) pushId*(data: []uint8) { ctx.idStack.push(ctx.getId(data)) } +fn (ctx: ^Context) pushStr*(s: []char) { ctx.idStack.push(ctx.getIdFromStr(s)) } +fn (ctx: ^Context) pushPtr*(p: ^void) { ctx.idStack.push(ctx.getIdFromPtr(p)) } + +fn (ctx: ^Context) popId*() { ctx.idStack.pop() } + +fn (ctx: ^Context) getClipRect*(): Rect + +fn (ctx: ^Context) pushClipRect*(rect: Rect) { + last := ctx.getClipRect() + ctx.clipStack.push(rect.intersectedWith(last)) +} + +fn (ctx: ^Context) popClipRect*() { ctx.clipStack.pop() } + +fn (ctx: ^Context) getClipRect*(): Rect { + return Rect(ctx.clipStack.items[ctx.clipStack.size()-1]) +} + +fn (ctx: ^Context) checkClip*(r: Rect): ClipState { + cr := ctx.getClipRect() + + if r.x > cr.x + cr.w || r.x + r.w < cr.x || + r.y > cr.y + cr.h || r.y + r.h < cr.y { return .All } + + if r.x >= cr.x && r.x + r.w <= cr.x + cr.w && + r.y >= cr.y && r.y + r.h <= cr.y + cr.h { return .None } + + return .Part +} + +fn (ctx: ^Context) layoutRow*(widths: []int, height: int) + +fn (ctx: ^Context) pushLayout(body: Rect, scroll: Vec2) { + var layout: Layout + width := []int{0} + layout.body = { body.x - scroll.x, body.y - scroll.y, body.w, body.h } + layout.max = { -0x1000000, -0x1000000 } + ctx.layoutStack = append(ctx.layoutStack, layout) + ctx.layoutRow(width, 0) +} + +fn (ctx: ^Context) getLayout(): ^Layout { + return &ctx.layoutStack[min(len(ctx.layoutStack)-1, 0)] +} + +fn (ctx: ^Context) getCurrentContainer*(): ^Container + +fn (ctx: ^Context) popContainer() { + cnt := ctx.getCurrentContainer() + layout := ctx.getLayout() + cnt.contentSize.x = layout.max.x - layout.body.x + cnt.contentSize.y = layout.max.y - layout.body.y + // pop container, layout and id + ctx.containerStack.pop() + ctx.layoutStack = delete(ctx.layoutStack, len(ctx.layoutStack)-1) + ctx.popId() +} + + +fn (ctx: ^Context) getCurrentContainer*(): ^Container { + std::assert(ctx.containerStack.size() > 0) + return &Container(ctx.containerStack.items[ctx.containerStack.size()-1]) +} + +fn (ctx: ^Context) getContainer_(id: Id, opt: Option): ^Container { + idx := poolGet(&ctx.containerPool, len(ctx.containerPool), id) + if idx >= 0 { + if ctx.containers[idx].open || !opt.has(.Closed) { + poolUpdate(ctx, &ctx.containerPool, idx) + } + + return &ctx.containers[idx] + } + + if opt.has(.Closed) { return null } + idx = poolInit(ctx, &ctx.containerPool, len(ctx.containerPool), id) + cnt := &ctx.containers[idx] + cnt.head, cnt.tail = 0, 0 + cnt.rect, cnt.body = {}, {} + cnt.contentSize = {} + cnt.scroll = {} + cnt.zindex = 0 + cnt.open = true + ctx.bringToFront(cnt) + return cnt +} + +fn (ctx: ^Context) getContainer*(name: str): ^Container { + id := ctx.getIdFromStr(name) + return ctx.getContainer_(id, .None) +} + +fn (ctx: ^Context) bringToFront*(cnt: ^Container) { + ctx.lastZIndex++ + cnt.zindex = ctx.lastZIndex +} + +/*================** +** Input handlers ** +**================*/ + +fn (ctx: ^Context) input_mouseMove*(x, y: int) { ctx.mousePos = { x, y } } + +fn (ctx: ^Context) input_mouseDown*(x, y: int, btn: MouseButton) { + ctx.input_mouseMove(x, y) + ctx.mouseDown.set(btn) + ctx.mousePressed.set(btn) +} + +fn (ctx: ^Context) input_mouseUp*(x, y: int, btn: MouseButton) { + ctx.input_mouseMove(x, y) + ctx.mouseDown.unset(btn) +} + +fn (ctx: ^Context) input_scroll*(x, y: int) { + ctx.scrollDelta.x += x + ctx.scrollDelta.y += y +} + +fn (ctx: ^Context) input_keyDown*(key: KeyboardKey) { + ctx.keyPressed.set(key) + ctx.keyDown.set(key) +} + +fn (ctx: ^Context) input_keyUp*(key: KeyboardKey) { + ctx.keyDown.unset(key) +} + +fn (ctx: ^Context) input_text*(text: str) { ctx.inputText += text } + +/*==============** +** Command list ** +**==============*/ + +fn (ctx: ^Context) nextCommand*(idx: uint = 0): (^Command, uint) { + l := len(ctx.commandList) + + for idx < l { + cmd := &ctx.commandList[idx] + j := ^JumpCommand(cmd^) + if j == null { return cmd, idx + 1 } + idx = j.dst + } + + return null, idx +} + +fn (ctx: ^Context) pushJump(dst: int): uint { + i := len(ctx.commandList) + ctx.commandList = append(ctx.commandList, JumpCommand { dst }) + return i +} + +fn (ctx: ^Context) setClip*(rect: Rect) { + ctx.commandList = append(ctx.commandList, ClipCommand { rect }) +} + +fn (ctx: ^Context) drawRect*(rect: Rect, color: Color) { + rect = rect.intersectedWith(ctx.getClipRect()) + if rect.w > 0 && rect.h > 0 { + ctx.commandList = append(ctx.commandList, RectCommand { rect, color }) + } +} + +fn (ctx: ^Context) drawBox*(rect: Rect, color: Color) { + ctx.drawRect({ rect.x + 1, rect.y, rect.w - 2, 1 }, color) + ctx.drawRect({ rect.x + 1, rect.y + rect.h - 1, rect.w - 2, 1 }, color) + ctx.drawRect({ rect.x, rect.y, 1, rect.h }, color) + ctx.drawRect({ rect.x + rect.w - 1, rect.y, 1, rect.h }, color) +} + +fn (ctx: ^Context) drawText*(font: Font, text: str, pos: Vec2, color: Color) { + rect := Rect { pos.x, pos.y, ctx.textWidth(font, text), ctx.textHeight(font) } + clipped := ctx.checkClip(rect) + if clipped == .All { return } + if clipped == .Part { ctx.setClip(ctx.getClipRect()) } + ctx.commandList = append(ctx.commandList, TextCommand { font, pos, color, text }) + if clipped != .None { ctx.setClip(unclippedRect) } +} + +fn (ctx: ^Context) drawIcon*(id: Icon, rect: Rect, color: Color) { + clipped := ctx.checkClip(rect) + if clipped == .All { return } + if clipped == .Part { ctx.setClip(ctx.getClipRect())} + ctx.commandList = append(ctx.commandList, IconCommand { rect, id, color }) + if clipped != .None { ctx.setClip(unclippedRect) } +} + +fn (ctx: ^Context) setCursor*(c: Cursor) { + if ctx.lastCursor == c { + ctx.lastCursor = .None + return + } + + ctx.commandList = append(ctx.commandList, CursorCommand { c }) + ctx.lastCursor = c +} + +/*========** +** layout ** +**========*/ + +fn (ctx: ^Context) layoutNext*(): Rect + +fn (ctx: ^Context) layoutBeginColumn*() { + ctx.pushLayout(ctx.layoutNext(), { 0, 0 }) +} + +fn (ctx: ^Context) layoutEndColumn*() { + b := ctx.getLayout() + ctx.layoutStack = delete(ctx.layoutStack, len(ctx.layoutStack)-1) + a := ctx.getLayout() + a.position.x = max(a.position.x, b.position.x + b.body.x - a.body.x) + a.position.y = max(a.position.y, b.position.y + b.body.y - a.body.y) + a.max.x = max(a.max.x, b.max.x) + a.max.y = max(a.max.y, b.max.y) +} + +fn (ctx: ^Context) layoutRow*(widths: []int, height: int) { + layout := ctx.getLayout() + if len(widths) != 0 { layout.widths = widths } + layout.position = { layout.indent, layout.nextRow } + layout.size.y = height + layout.itemIndex = 0 +} + +fn (ctx: ^Context) layoutWidth* (width: int) { ctx.getLayout().size.x = width } +fn (ctx: ^Context) layoutHeight*(height: int) { ctx.getLayout().size.y = height } + +fn (ctx: ^Context) layoutSetNext*(r: Rect, relative: bool = false) { + layout := ctx.getLayout() + layout.next = r + layout.nextType = relative ? LayoutNextType.Relative : LayoutNextType.Absolute +} + +fn (ctx: ^Context) layoutNext*(): Rect { + layout := ctx.getLayout() + style := ctx.style + var res: Rect + + if layout.nextType != .None { + nextType := layout.nextType + layout.nextType = .None + res = layout.next + if nextType == .Absolute { + ctx.lastRect = res + return res + } + } else { + if layout.itemIndex == len(layout.widths) { + ctx.layoutRow({}, layout.size.y) + } + + res.x = layout.position.x + res.y = layout.position.y + + res.w = len(layout.widths) > 0 ? layout.widths[layout.itemIndex] : layout.size.x + res.h = layout.size.y + if res.w == 0 { res.w = style.size.x + style.padding * 2 } + if res.h == 0 { res.h = style.size.y + style.padding * 2 } + if res.w < 0 { res.w += layout.body.w - res.x + 1 } + if res.h < 0 { res.h += layout.body.h - res.y + 1 } + + layout.itemIndex++ + } + + layout.position.x += res.w + style.spacing + layout.nextRow = max(layout.nextRow, res.y + res.h + style.spacing) + + res.x += layout.body.x + res.y += layout.body.y + + layout.max.x = max(layout.max.x, res.x + res.w) + layout.max.y = max(layout.max.y, res.y + res.h) + + ctx.lastRect = res + return res +} + +/*==========** +** controls ** +**==========*/ + +fn (ctx: ^Context) inHoverRoot(): bool { + length := ctx.containerStack.size() + for i := 0; i < length; i++ { + c := ^Container(ctx.containerStack.items[length - i - 1]) + if c == ctx.hoverRoot { return true } + /* only root containers have their `head` field set; stop searching if we've + ** reached the current root container */ + if c.head != -1 { break } + } + return false +} + +fn (c: ^ControlColor) offsetByState(ctx: ^Context, id: Id): ControlColor { + switch c^ { + case .Button: + return ctx.focus == id ? ControlColor.ButtonFocus : + ctx.hover == id ? ControlColor.ButtonHover : + ControlColor.Button + + case .Base: + return ctx.focus == id ? ControlColor.BaseFocus : + ctx.hover == id ? ControlColor.BaseHover : + ControlColor.Base + } + + return c^ +} + +fn (ctx: ^Context) drawControlFrame*(id: Id, rect: Rect, colorid: ControlColor, opt: Option) { + if opt.has(.NoFrame) { return } + ctx.drawFrame(ctx, rect, colorid.offsetByState(ctx, id)) +} + +fn (ctx: ^Context) drawControlText*(text: str, rect: Rect, colorid: ControlColor, opt: Option) { + var pos: Vec2 + font := ctx.style.font + tw := ctx.textWidth(font, text) + ctx.pushClipRect(rect) + pos.y = rect.y + (rect.h - ctx.textHeight(font)) / 2 + + if opt.has(.AlignCenter) { + pos.x = rect.x + (rect.w - tw) / 2 + } else if opt.has(.AlignCenter) { + pos.x = rect.x + rect.w - tw - ctx.style.padding + } else { + pos.x = rect.x + ctx.style.padding + } + + ctx.drawText(font, text, pos, ctx.style.getColor(colorid)) + ctx.popClipRect() +} + +fn (ctx: ^Context) mouseOver(rect: Rect): bool { + return rect.overlaps(ctx.mousePos) && + ctx.getClipRect().overlaps(ctx.mousePos) && + ctx.inHoverRoot() +} + +fn (ctx: ^Context) updateControl*(id: Id, rect: Rect, opt: Option) { + mouseover := ctx.mouseOver(rect) + + if ctx.focus == id { ctx.updatedFocus = true } + if opt.has(.NoInteract) { return } + if mouseover && !ctx.mouseDown.any() { ctx.hover = id } + + if ctx.focus == id { + if ctx.mousePressed.any() && !mouseover { ctx.setFocus(0) } + if !ctx.mouseDown.any() && !opt.has(.HoldFocus) { ctx.setFocus(0) } + } + + if ctx.hover == id { + if ctx.mousePressed.any() { + ctx.setFocus(id) + } else if !mouseover { + ctx.hover = 0 + } + } +} + +fn (ctx: ^Context) text*(text: str) { + p := 0 + font := ctx.style.font + color := ctx.style.colors[uint(ControlColor.Text)] + ctx.layoutBeginColumn() + ctx.layoutRow([]int{-1}, ctx.textHeight(font)) + for true { + r := ctx.layoutNext() + w, start, end := 0, p, p + for true { + word := p + for p < len(text) && text[p] != ' ' && text[p] != '\n' { p++ } + w += ctx.textWidth(font, slice(text, word, p)) + if w > r.w && end != start { break } + if p < len(text) { w += ctx.textWidth(font, text[p]) } + end = p + p++ + if end >= len(text) || text[end] == '\n' { break } + } + + ctx.drawText(font, slice(text, start, end), { r.x, r.y }, color) + p = end + 1 + if end >= len(text) { break } + } + ctx.layoutEndColumn() +} + +fn (ctx: ^Context) label*(text: str) { + ctx.drawControlText(text, ctx.layoutNext(), .Text, .None) +} + +fn (ctx: ^Context) button*(label: str, + icon: Icon = .None, opt: Option = .AlignCenter): Result { + res := Result.Nothing + id := label != "" ? ctx.getIdFromStr(label) : + ctx.getId([]uint8{uint8(icon)}) // temporary + r := ctx.layoutNext() + + ctx.updateControl(id, r, opt) + if ctx.hover == id { ctx.setCursor(.Hand) } + if ctx.mousePressed.is(.Left) && ctx.focus == id { res.set(.Submit) } + + ctx.drawControlFrame(id, r, .Button, opt) + if label != "" { ctx.drawControlText(label, r, .Text, opt) } + if icon == .None { ctx.drawIcon(icon, r, ctx.style.getColor(.Text)) } + return res +} + +fn (ctx: ^Context) checkbox*(label: str, state: ^bool): Result { + res := Result.Nothing + id := ctx.getIdFromPtr(state) + r := ctx.layoutNext() + box := Rect { r.x, r.y, r.h, r.h } + ctx.updateControl(id, r, .None) + + if ctx.mousePressed.is(.Left) && ctx.focus == id { + res.set(.Change) + state^ = !state^ + } + + ctx.drawControlFrame(id, box, .Base, .None) + if state^ { ctx.drawIcon(.Check, box, ctx.style.getColor(.Text)) } + r = { r.x + box.w, r.y, r.w - box.w, r.h } + ctx.drawControlText(label, r, .Text, .None) + + return res +} + +fn (ctx: ^Context) textboxRaw(buf: ^str, bufsz: uint, id: Id, r: Rect, opt: Option): Result { + res := Result.Nothing + + ctx.updateControl(id, r, opt.and(.HoldFocus)) + + if ctx.hover == id { ctx.setCursor(.IBeam) } + + if ctx.focus == id { + l := len(buf^) + n := min(bufsz - l - 1, len(ctx.inputText)) + if n > 0 { + buf^ += ctx.inputText + res.set(.Change) + } + + if ctx.keyPressed.has(.Backspace) && l > 0 { + l-- + for l > 0 && (int(buf^[l]) & 0xc0) == 0x80; l-- {} + buf^ = slice(buf^, 0, l) + res.set(.Change) + } + + if ctx.keyPressed.has(.Return) { + ctx.setFocus(0) + res.set(.Submit) + } + } + + ctx.drawControlFrame(id, r, .Base, opt) + if ctx.focus == id { + color := ctx.style.getColor(.Text) + font := ctx.style.font + textw := ctx.textWidth(font, buf^) + texth := ctx.textHeight(font) + ofx := r.w - ctx.style.padding - textw - 1 + textx := r.x + min(ofx, ctx.style.padding) + texty := r.y + (r.h - texth) / 2 + + ctx.pushClipRect(r) + ctx.drawText(font, buf^, { textx, texty }, color) + ctx.drawRect({ textx + textw, texty, 1, texth }, color) + ctx.popClipRect() + } else { + ctx.drawControlText(buf^, r, .Text, opt) + } + + return res +} + +fn (ctx: ^Context) numberTextbox(value: ^Real, r: Rect, id: Id): bool { + if ctx.mousePressed.is(.Left) && ctx.keyDown.has(.Shift) && ctx.hover == id { + ctx.numberEdit = id + ctx.numberEditBuf = sprintf(REAL_FMT, value^) + } + + if ctx.numberEdit == id { + res := ctx.textboxRaw( + &ctx.numberEditBuf, 17, // LDBL_DECIMAL_DIG + id, r, .None + ) + + if res.has(.Submit) || ctx.focus != id { + value^ = std::atof(ctx.numberEditBuf) + ctx.numberEdit = 0 + } else { + return true + } + } + + return false +} + +fn (ctx: ^Context) textbox*(buf: ^str, bufsz: uint = 0, opt: Option = .None): Result { + if bufsz == 0 { bufsz = len(buf^) } + id := ctx.getIdFromPtr(&buf) + r := ctx.layoutNext() + return ctx.textboxRaw(buf, bufsz, id, r, opt) +} + +fn (ctx: ^Context) slider*(value: ^Real, low, high: Real, + step: Real = 0, fmt: str = SLIDER_FMT, opt: Option = .AlignCenter): Result { + res := Result.Nothing + + last := value^ + v := last + + id := ctx.getIdFromPtr(value) + base := ctx.layoutNext() + + if ctx.numberTextbox(&v, base, id) { return res } + + ctx.updateControl(id, base, opt) + + if ctx.focus == id && + (ctx.mouseDown.is(.Left) || ctx.mousePressed.is(.Left)) { + v = low + Real(ctx.mousePos.x - base.x) * (high - low) / Real(base.w) + if step != 0 { v = Real(floor((v + step / 2.0) / step) * step) } + } + + v = clamp(v, low, high) + value^ = v + if last != v { res.set(.Change) } + + ctx.drawControlFrame(id, base, .Base, opt) + w := ctx.style.thumbSize + x := Real(v - low) * Real(base.w - w) / Real(high - low) + thumb := Rect { floor(base.x + x), base.y, w, base.h } + ctx.drawControlFrame(id, thumb, .Button, opt) + ctx.drawControlText(sprintf(fmt, v), base, .Text, opt) + + return res +} + +fn (ctx: ^Context) header_(label: str, istreenode: bool, opt: Option): Result { + id := ctx.getIdFromStr(label) + idx := poolGet(&ctx.treenodePool, len(ctx.treenodePool), id) + ctx.layoutRow([]int{-1}, 0) + + active := idx >= 0 + expanded := opt.has(.Expanded) ? !active : active + r := ctx.layoutNext() + ctx.updateControl(id, r, .None) + + active = bool(int(active) ~ int(ctx.mousePressed.is(.Left) && ctx.focus == id)) + if ctx.hover == id { ctx.setCursor(.Hand) } + + if idx >= 0 { + if active { + poolUpdate(ctx, &ctx.treenodePool, idx) + } else { + t := &ctx.treenodePool[idx] + t.lastUpdate = 0 + t.id = 0 + } + } else if active { + poolInit(ctx, &ctx.treenodePool, len(ctx.treenodePool), id) + } + + if istreenode { + if ctx.hover == id { ctx.drawFrame(ctx, r, .ButtonHover) } + } else { + ctx.drawControlFrame(id, r, .Button, .None) + } + + ctx.drawIcon( + expanded ? Icon.Expanded : Icon.Collapsed, + { r.x, r.y, r.h, r.h }, + ctx.style.getColor(.Text) + ) + + r.x += r.h - ctx.style.padding + r.w -= r.h - ctx.style.padding + + ctx.drawControlText(label, r, .Text, .None) + + return expanded ? Result.Active : Result.Nothing +} + +fn (ctx: ^Context) header*(label: str, opt: Option = .None): Result { + return ctx.header_(label, false, opt) +} + +fn (ctx: ^Context) beginTreenode*(label: str, opt: Option = .None): Result { + res := ctx.header_(label, true, opt) + if res.has(.Active) { + ctx.getLayout().indent += ctx.style.indent + ctx.idStack.push(ctx.lastId) + } + return res +} + +fn (ctx: ^Context) endTreenode*() { + ctx.getLayout().indent -= ctx.style.indent + ctx.popId() +} + +fn (ctx: ^Context) scrollbar(cnt: ^Container, b: ^Rect, cs: Vec2, hor: bool) { + if hor { + maxscroll := cs.x - b.w + + if maxscroll > 0 && b.w > 0 { + var base, thumb: Rect + + id := ctx.getIdFromStr("!scrollbarx") + + base = b^ + base.y = b.y + b.h; + base.h = ctx.style.scrollbarSize; + + ctx.updateControl(id, base, .None); + if ctx.focus == id && ctx.mouseDown.has(.Left) { + cnt.scroll.x += ctx.mouseDelta.x * cs.x / base.w; + } + + cnt.scroll.x = trunc(clamp(cnt.scroll.x, 0, maxscroll)); + + ctx.drawFrame(ctx, base, .ScrollBase); + thumb = base; + thumb.w = max(ctx.style.thumbSize, base.w * b.w / cs.x); + thumb.x += cnt.scroll.x * (base.w - thumb.w) / maxscroll; + ctx.drawFrame(ctx, thumb, .ScrollThumb); + + if ctx.mouseOver(b^) { ctx.scrollTarget = cnt; } + } else { + cnt.scroll.x = 0; + } + } else { + maxscroll := cs.y - b.h + + if maxscroll > 0 && b.h > 0 { + var base, thumb: Rect + + id := ctx.getIdFromStr("!scrollbary") + + base = b^ + base.x = b.x + b.w; + base.w = ctx.style.scrollbarSize; + + ctx.updateControl(id, base, .None); + if ctx.focus == id && ctx.mouseDown.has(.Left) { + cnt.scroll.y += ctx.mouseDelta.y * cs.y / base.h; + } + + cnt.scroll.y = trunc(clamp(cnt.scroll.y, 0, maxscroll)); + + ctx.drawFrame(ctx, base, .ScrollBase); + thumb = base; + thumb.h = max(ctx.style.thumbSize, base.h * b.h / cs.y); + thumb.y += cnt.scroll.y * (base.h - thumb.h) / maxscroll; + ctx.drawFrame(ctx, thumb, .ScrollThumb); + + if ctx.mouseOver(b^) { ctx.scrollTarget = cnt; } + } else { + cnt.scroll.y = 0; + } + } +} + +fn (ctx: ^Context) scrollbars(cnt: ^Container, body: ^Rect) { + sz := ctx.style.scrollbarSize + cs := cnt.contentSize + cs.x += ctx.style.padding * 2 + cs.y += ctx.style.padding * 2 + ctx.pushClipRect(body^) + + if cs.y > cnt.body.h { body.w -= sz } + if cs.x > cnt.body.w { body.h -= sz } + + ctx.scrollbar(cnt, body, cs, true) + ctx.scrollbar(cnt, body, cs, false) + + ctx.popClipRect() +} + +fn (ctx: ^Context) pushContainerBody(cnt: ^Container, body: Rect, opt: Option) { + if !opt.has(.NoScroll) { ctx.scrollbars(cnt, &body) } + ctx.pushLayout(body.expanded(-ctx.style.padding), cnt.scroll) + cnt.body = body +} + +fn (ctx: ^Context) beginRootContainer(cnt: ^Container) { + ctx.containerStack.push(cnt) + ctx.rootList.push(cnt) + + cnt.head = ctx.pushJump(-1) + + if cnt.rect.overlaps(ctx.mousePos) && + (ctx.nextHoverRoot == null || cnt.zindex > ctx.nextHoverRoot.zindex) { + ctx.nextHoverRoot = cnt + } + + ctx.clipStack.push(unclippedRect) +} + +fn (ctx: ^Context) endRootContainer() { + cnt := ctx.getCurrentContainer() + cnt.tail = ctx.pushJump(-1) + + j := ^JumpCommand(ctx.commandList[cnt.head]) + std::assert(j != null) + j.dst = len(ctx.commandList) + + ctx.popClipRect() + ctx.popContainer() +} + +fn (ctx: ^Context) beginWindow*(title: str, rect: Rect, opt: Option = .None): Result { + id := ctx.getIdFromStr(title) + cnt := ctx.getContainer_(id, opt) + if cnt == null || !cnt.open { return .Nothing } + ctx.idStack.push(id) + + if cnt.rect.w == 0 { cnt.rect = rect } + ctx.beginRootContainer(cnt) + body := cnt.rect + rect = body + + ctx.setCursor(.Arrow) + + if !opt.has(.NoFrame) { ctx.drawFrame(ctx, rect, .WindowBG) } + + if !opt.has(.NoTitle) { + tr := rect + tr.h = ctx.style.titleHeight + ctx.drawFrame(ctx, tr, .TitleBG) + + id := ctx.getIdFromStr("!title") + ctx.updateControl(id, tr, opt) + ctx.drawControlText(title, tr, .TitleText, opt) + + if ctx.hover == id { ctx.setCursor(.Arrow) } + if id == ctx.focus && ctx.mouseDown.has(.Left) { + cnt.rect.x += ctx.mouseDelta.x + cnt.rect.y += ctx.mouseDelta.y + } + + body.y += tr.h + body.h -= tr.h + + if !opt.has(.NoClose) { + id := ctx.getIdFromStr("!close") + r := Rect { tr.x + tr.w - tr.h, tr.y, tr.h, tr.h } + tr.w -= r.w + ctx.drawIcon(.Close, r, ctx.style.getColor(.TitleText)) + ctx.updateControl(id, r, opt) + + if ctx.hover == id { ctx.setCursor(.Hand) } + + if ctx.mousePressed.has(.Left) && id == ctx.focus { cnt.open = false } + } + } + + ctx.pushContainerBody(cnt, body, opt) + + if !opt.has(.NoResize) { + sz := ctx.style.titleHeight + id := ctx.getIdFromStr("!resize") + r := Rect { rect.x + rect.w - sz, rect.y + rect.h - sz, sz, sz } + ctx.updateControl(id, r, opt) + + if ctx.hover == id { ctx.setCursor(.Size) } + + if id == ctx.focus && ctx.mouseDown.has(.Left) { + ctx.setCursor(.Size) + cnt.rect.w = max(96, cnt.rect.w + ctx.mouseDelta.x) + cnt.rect.h = max(64, cnt.rect.h + ctx.mouseDelta.y) + } + } + + if opt.has(.AutoSize) { + r := ctx.getLayout().body + cnt.rect.w = cnt.contentSize.x + (cnt.rect.w - r.w) + cnt.rect.h = cnt.contentSize.y + (cnt.rect.h - r.h) + } + + if opt.has(.Popup) && + ctx.mousePressed.any() && ctx.hoverRoot != cnt { cnt.open = false } + + ctx.pushClipRect(cnt.body) + return .Active +} + +fn (ctx: ^Context) endWindow*() { + ctx.popClipRect() + ctx.endRootContainer() +} + +fn (ctx: ^Context) openPopup*(name: str) { + cnt := ctx.getContainer(name) + ctx.nextHoverRoot = cnt + ctx.hoverRoot = ctx.nextHoverRoot + + cnt.rect = { ctx.mousePos.x, ctx.mousePos.y, 1, 1 } + cnt.open = true + ctx.bringToFront(cnt) +} + +fn (ctx: ^Context) beginPopup*(name: str): Result { + opt := optAnd(.Popup, .AutoSize, .NoResize, .NoScroll, .NoTitle, .Closed) + return ctx.beginWindow(name, {}, opt) +} + +fn (ctx: ^Context) endPopup*() { + ctx.endWindow() +} + +// vim: ts=4 sw=4 et nosmartindent \ No newline at end of file diff --git a/fs/user/progman.um b/fs/user/progman.um new file mode 100644 index 0000000..6392162 --- /dev/null +++ b/fs/user/progman.um @@ -0,0 +1,24 @@ +import ( + "tasks.um" +) + +offs := 0 + +fn register*() { + tasks::createClass("Progman", { + sw := trunc(ctx.getCurrentContainer().body.w * 0.5) + + ctx.layoutRow({sw, -1}, 40) + for i, _ in tasks::classes { + if ctx.button(i) == .Submit { + tasks::createTask(i, {offs, offs, 400, 305}) + offs += 50 + if offs > 500 { + offs = 0 + } + } + } + + return true + }) +} \ No newline at end of file diff --git a/fs/user/tasks.um b/fs/user/tasks.um new file mode 100644 index 0000000..0b2cb87 --- /dev/null +++ b/fs/user/tasks.um @@ -0,0 +1,77 @@ +import ("microui.um") + +type ( + TaskFn* = fn(task: ^Task, ctx: ^microui::Context): bool + + Task* = struct { + data: any + dead: bool + class: str + name: str + window: microui::Rect + task: TaskFn + noClose: bool + } +) + +var classes*: map[str]TaskFn + +var tasks*: map[int]Task + +var lastTaskId: int + +fn xorshift64(seed: uint): uint { + seed ~= seed << 13 + seed ~= seed >> 7 + seed ~= seed << 17 + return seed +} + +var seed: uint = 0x5794FA12 + +fn rand*(): uint { + seed = xorshift64(seed) + return seed +} + +fn createClass*(class: str, task: TaskFn) { + classes[class] = task +} + +fn killTask*(id: int): bool { + if !validkey(tasks, id) { + return false + } + + tasks = delete(tasks, id) + return true +} + +fn createTask*(class: str, window: microui::Rect): int { + noClose := false + if len(tasks) == 0 { + noClose = true + } + lastTaskId++ + tasks[lastTaskId] = {null, false, class, sprintf("%s (%d)", class, lastTaskId), window, classes[class], noClose} + return lastTaskId +} + +fn doTasks*(ctx: ^microui::Context) { + for i, task^ in tasks { + flags := task.noClose ? microui::Option.NoClose : microui::Option.None + if ctx.beginWindow(task.name, task.window, flags) == .Active { + if !ctx.getCurrentContainer().open || !task.task(task, ctx) { + task.dead = true + } + ctx.endWindow() + } + } + + for i, task in tasks { + if task.dead { + tasks = delete(tasks, i) + } + } +} + diff --git a/fs/user/taskview.um b/fs/user/taskview.um new file mode 100644 index 0000000..19c596a --- /dev/null +++ b/fs/user/taskview.um @@ -0,0 +1,22 @@ +import ( + "tasks.um" +) + +fn register*() { + tasks::createClass("Taskview", { + sw := ctx.getCurrentContainer().body.w - 100 + ctx.layoutRow({sw, -1}, 20) + for i, task^ in tasks::tasks { + ctx.pushId({i}) + ctx.label(sprintf("%s #%d", task.name, i)) + if task.noClose { + ctx.label("") + } else if ctx.button("Kill") == .Submit { + task.dead = true + } + ctx.popId() + } + + return true + }) +} \ No newline at end of file diff --git a/fs/user/test.um b/fs/user/test.um new file mode 100644 index 0000000..38a4f06 --- /dev/null +++ b/fs/user/test.um @@ -0,0 +1,39 @@ +import ( + "tasks.um" +) + +fn register*() { + tasks::createClass("Test", { + ctx.label("Hello, from task "+task.name) + + ctx.layoutRow({86, -111, -1}, 0) + ctx.label("Test buttons 1:") + if ctx.button("Button 1") == .Submit { + printf("Submit button 1\n") + } + if ctx.button("Button 2") == .Submit { + printf("Submit button 2\n") + } + ctx.label("Test buttons 2:") + if ctx.button("Button 3") == .Submit { + printf("Submit button 3\n") + } + if ctx.button("Popup") == .Submit { + // ctx.openPopup("Test Popup") + } + /* + if ctx.beginPopup("Test Popup") { + if ctx.button("Hello") == .Submit { + printf("Submit Hello\n") + } + if ctx.button("World") == .Submit { + printf("Submit World\n") + } + ctx.endPopup() + } + */ + + return true + }) +} + diff --git a/fs/user/user.um b/fs/user/user.um new file mode 100644 index 0000000..a656806 --- /dev/null +++ b/fs/user/user.um @@ -0,0 +1,158 @@ +import ( + "microui.um" + "tasks.um" + "console.um" + "about.um" + "progman.um" + "taskview.um" + "filesystem.um" + "blackjack.um" + "/kernel/input.um" + "/api/gfx.um" +) + +var ctx: microui::Context + +var mouseX, mouseY: int + +fn init*() { + ctx.init() + + ctx.textWidth = { + return len(text)*8 + } + + ctx.textHeight = { + return 8 + } + + console::register() + about::register() + progman::register() + taskview::register() + filesystem::register() + blackjack::register() + tasks::createTask("Progman", {50, 50, 400, 200}) + + var w, h: int + gfx::dims(&w, &h) + printf("Screen dimensions: %d by %d\n", w, h) +} + +consoleText := "" + +fn drawConsole*() { + gfx::setColor(255, 255, 255, 255) + line := 0 + column := 0 + + for i, c in consoleText { + if c == '\n' { + line += 1 + column = 0 + } else { + if column > 80 { + column = 0 + line += 1 + } + gfx::drawChar(column*8, line*8, c) + column += 1 + } + } +} + +fn output*(s: str) { + console::write(s) +} + +wasLmb := false + +fn schedule*() { + tasks::rand() + + var w, h: int + gfx::dims(&w, &h) + + input::relay(|w, h| { + switch v := type(event) { + case input::MousePacket: + tasks::rand() + mouseX += v.dx + mouseY += -v.dy + + if mouseX < 0 { + mouseX = 0 + } + if mouseY < 0 { + mouseY = 0 + } + if mouseX > w { + mouseX = w + } + if mouseY > h { + mouseY = h + } + + if v.lmb && !wasLmb { + ctx.input_mouseDown(mouseX, mouseY, .Left) + } else if wasLmb { + ctx.input_mouseMove(mouseX, mouseY) + } else { + ctx.input_mouseUp(mouseX, mouseY, .Left) + } + wasLmb = v.lmb + + } + }) + + ctx.begin() + + tasks::doTasks(&ctx) + + gfx::setColor(32, 32, 127, 255) + gfx::drawRect(0, 0, w, h) + gfx::drawBackground() + + ctx.end() + + cmd, idx := ctx.nextCommand() + for cmd != null { + switch v := type(cmd^) { + case microui::RectCommand: + gfx::setColor(v.color.r, v.color.g, v.color.b, v.color.a) + gfx::drawRect(v.rect.x, v.rect.y, v.rect.w, v.rect.h) + case microui::TextCommand: + gfx::setColor(v.color.r, v.color.g, v.color.b, v.color.a) + for i := 0; i < len(v.text); i++ { + gfx::drawChar(v.pos.x + i*8, v.pos.y, v.text[i]) + } + case microui::IconCommand: + gfx::setColor(v.color.r, v.color.g, v.color.b, v.color.a) + v.rect.x = v.rect.x+(v.rect.w-12)/2 + v.rect.y = v.rect.y+(v.rect.h-16)/2 + switch v.id { + case .Check: + gfx::drawIcon(v.rect.x, v.rect.y, '\x01') + case .Close: + gfx::drawIcon(v.rect.x, v.rect.y, '\x04') + case .Collapsed: + gfx::drawIcon(v.rect.x, v.rect.y, '\x05') + case .Expanded: + gfx::drawIcon(v.rect.x, v.rect.y, '\x06') + case .Max: + gfx::drawIcon(v.rect.x, v.rect.y, '\x07') + } + case microui::ClipCommand: + gfx::drawClip(v.rect.x, v.rect.y, v.rect.w, v.rect.h) + } + cmd, idx = ctx.nextCommand(idx) + } + + // draw mouse + gfx::setColor(255, 255, 255, 255) + gfx::drawIcon(mouseX, mouseY, '\x02') + gfx::setColor(0, 0, 0, 255) + gfx::drawIcon(mouseX, mouseY, '\x03') + + gfx::swap() +} \ No newline at end of file diff --git a/mkfs.py b/mkfs.py new file mode 100644 index 0000000..60be3c0 --- /dev/null +++ b/mkfs.py @@ -0,0 +1,45 @@ +# Creates an image of the virutual FS + +import sys +import os +import ctypes + +# Check for correct number of arguments +if len(sys.argv) != 3: + print("Usage: python mkfs.py dir img") + sys.exit() + +# Get the directory +dire = sys.argv[1] +img = sys.argv[2] + +# Check if the directory exists +if not os.path.exists(dire): + print("Directory does not exist") + sys.exit() + +image = bytearray() + +def encodeDir(path): + global image + directory = os.scandir(path) + + for entry in directory: + if entry.is_dir(): + image += b'd' + image += entry.name.encode() + image += b'\0' + encodeDir(entry.path) + image += b'p' + else: + image += b'f' + image += entry.name.encode() + image += b'\0' + image += bytes(ctypes.c_int32(os.path.getsize(entry.path))) + with open(entry.path, "rb") as file: + image += file.read() + +encodeDir(dire) + +with open(img, "wb") as file: + file.write(image) \ No newline at end of file diff --git a/src/compile_flags.txt b/src/compile_flags.txt new file mode 100644 index 0000000..8d17d5b --- /dev/null +++ b/src/compile_flags.txt @@ -0,0 +1,6 @@ +-I +mock_libc +-I +umka/src +-I +qoi diff --git a/src/mock_libc/alloc.c b/src/mock_libc/alloc.c new file mode 100644 index 0000000..1db5dcb --- /dev/null +++ b/src/mock_libc/alloc.c @@ -0,0 +1,96 @@ +#include "alloc.h" +#include + +struct MemNode { + union { + struct { + uint32_t check; + struct MemNode *next; + size_t size; + }; + + char _padding[16]; + }; + + char data[]; +} typedef MemNode; + +struct { + MemNode *tail; + MemNode *free; +} typedef Allocator; + +Allocator alloc; + +void allocatorInit(void *start) { + alloc.tail = start; + alloc.free = 0; + + *alloc.tail = (MemNode){0}; +} + +void *allocatorRealloc(void *ptr, size_t size) { + if (ptr == 0) { + return allocatorAlloc(size); + } + + MemNode *node = (MemNode *)((char *)ptr - sizeof(MemNode)); + + if (node->size >= size) { + return ptr; + } + + void *new = allocatorAlloc(size); + for (size_t i = 0; i < node->size; i++) { + ((char *)new)[i] = node->data[i]; + } + allocatorFree(ptr); + + return new; +} + +void *allocatorAlloc(size_t size) { + if (alloc.free) { + MemNode *node = alloc.free; + MemNode *prev = 0; + while (node->size < size) { + prev = node; + if (!node->next) { + goto normal; + } + node = node->next; + } + + if (node->size >= size) { + if (node == alloc.free) { + alloc.free = node->next; + } else { + assert(prev); + prev->next = node->next; + } + assert(node->check == 0x11FACADE); + return node->data; + } + } + +normal: + MemNode *node = + (MemNode *)(((char *)alloc.tail) + alloc.tail->size + sizeof(MemNode)); + + *node = (MemNode){0}; + node->check = 0x11FACADE; + node->size = size + (16 - (size % 16)); + alloc.tail = node; + return node->data; +} + +void allocatorFree(void *ptr) { + if (ptr == 0) + return; + + MemNode *node = (MemNode *)((char *)ptr - sizeof(MemNode)); + assert(node->check == 0x11FACADE); + + node->next = alloc.free; + alloc.free = node; +} diff --git a/src/mock_libc/alloc.h b/src/mock_libc/alloc.h new file mode 100644 index 0000000..225f207 --- /dev/null +++ b/src/mock_libc/alloc.h @@ -0,0 +1,7 @@ +#pragma once +#include + +void allocatorInit(void *start); +void *allocatorRealloc(void *ptr, size_t size); +void *allocatorAlloc(size_t size); +void allocatorFree(void *ptr); \ No newline at end of file diff --git a/src/mock_libc/arith.c b/src/mock_libc/arith.c new file mode 100644 index 0000000..6ea8694 --- /dev/null +++ b/src/mock_libc/arith.c @@ -0,0 +1,292 @@ +// GCC 32/64-bit integer arithmetic support for 32-bit systems that can't link +// to libgcc. + +// Function prototypes and descriptions are taken from +// https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html. + +// This file may be #include'd by another file, so we try not to pollute the +// namespace and we don't import any headers. + +// All functions must be resolvable by the linker and therefore can't be inline +// or static, even if they're #included into the file where they'll be used. + +// For best performance we try to avoid branching. This makes the code a little +// weird in places. + +// See https://github.com/glitchub/arith64 for more information. +// This software is released as-is into the public domain, as described at +// https://unlicense.org. Do whatever you like with it. + +#define arith64_u64 unsigned long long int +#define arith64_s64 signed long long int +#define arith64_u32 unsigned int +#define arith64_s32 int + +typedef union { + arith64_u64 u64; + arith64_s64 s64; + struct { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + arith64_u32 hi; + arith64_u32 lo; +#else + arith64_u32 lo; + arith64_u32 hi; +#endif + } u32; + struct { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + arith64_s32 hi; + arith64_s32 lo; +#else + arith64_s32 lo; + arith64_s32 hi; +#endif + } s32; +} arith64_word; + +// extract hi and lo 32-bit words from 64-bit value +#define arith64_hi(n) (arith64_word){.u64 = n}.u32.hi +#define arith64_lo(n) (arith64_word){.u64 = n}.u32.lo + +// Negate a if b is negative, via invert and increment. +#define arith64_neg(a, b) \ + (((a) ^ ((((arith64_s64)(b)) >= 0) - 1)) + (((arith64_s64)(b)) < 0)) +#define arith64_abs(a) arith64_neg(a, a) + +// Return the absolute value of a. +// Note LLINT_MIN cannot be negated. +arith64_s64 __absvdi2(arith64_s64 a) { return arith64_abs(a); } + +// Return the result of shifting a left by b bits. +arith64_s64 __ashldi3(arith64_s64 a, int b) { + arith64_word w = {.s64 = a}; + + b &= 63; + + if (b >= 32) { + w.u32.hi = w.u32.lo << (b - 32); + w.u32.lo = 0; + } else if (b) { + w.u32.hi = (w.u32.lo >> (32 - b)) | (w.u32.hi << b); + w.u32.lo <<= b; + } + return w.s64; +} + +// Return the result of arithmetically shifting a right by b bits. +arith64_s64 __ashrdi3(arith64_s64 a, int b) { + arith64_word w = {.s64 = a}; + + b &= 63; + + if (b >= 32) { + w.s32.lo = w.s32.hi >> (b - 32); + w.s32.hi >>= 31; // 0xFFFFFFFF or 0 + } else if (b) { + w.u32.lo = (w.u32.hi << (32 - b)) | (w.u32.lo >> b); + w.s32.hi >>= b; + } + return w.s64; +} + +// These functions return the number of leading 0-bits in a, starting at the +// most significant bit position. If a is zero, the result is undefined. +int __clzsi2(arith64_u32 a) { + int b, n = 0; + b = !(a & 0xffff0000) << 4; + n += b; + a <<= b; + b = !(a & 0xff000000) << 3; + n += b; + a <<= b; + b = !(a & 0xf0000000) << 2; + n += b; + a <<= b; + b = !(a & 0xc0000000) << 1; + n += b; + a <<= b; + return n + !(a & 0x80000000); +} + +int __clzdi2(arith64_u64 a) { + int b, n = 0; + b = !(a & 0xffffffff00000000ULL) << 5; + n += b; + a <<= b; + b = !(a & 0xffff000000000000ULL) << 4; + n += b; + a <<= b; + b = !(a & 0xff00000000000000ULL) << 3; + n += b; + a <<= b; + b = !(a & 0xf000000000000000ULL) << 2; + n += b; + a <<= b; + b = !(a & 0xc000000000000000ULL) << 1; + n += b; + a <<= b; + return n + !(a & 0x8000000000000000ULL); +} + +// These functions return the number of trailing 0-bits in a, starting at the +// least significant bit position. If a is zero, the result is undefined. +int __ctzsi2(arith64_u32 a) { + int b, n = 0; + b = !(a & 0x0000ffff) << 4; + n += b; + a >>= b; + b = !(a & 0x000000ff) << 3; + n += b; + a >>= b; + b = !(a & 0x0000000f) << 2; + n += b; + a >>= b; + b = !(a & 0x00000003) << 1; + n += b; + a >>= b; + return n + !(a & 0x00000001); +} + +int __ctzdi2(arith64_u64 a) { + int b, n = 0; + b = !(a & 0x00000000ffffffffULL) << 5; + n += b; + a >>= b; + b = !(a & 0x000000000000ffffULL) << 4; + n += b; + a >>= b; + b = !(a & 0x00000000000000ffULL) << 3; + n += b; + a >>= b; + b = !(a & 0x000000000000000fULL) << 2; + n += b; + a >>= b; + b = !(a & 0x0000000000000003ULL) << 1; + n += b; + a >>= b; + return n + !(a & 0x0000000000000001ULL); +} + +// Calculate both the quotient and remainder of the unsigned division of a by +// b. The return value is the quotient, and the remainder is placed in variable +// pointed to by c (if it's not NULL). +arith64_u64 __divmoddi4(arith64_u64 a, arith64_u64 b, arith64_u64 *c) { + if (b > a) // divisor > numerator? + { + if (c) + *c = a; // remainder = numerator + return 0; // quotient = 0 + } + if (!arith64_hi(b)) // divisor is 32-bit + { + if (b == 0) // divide by 0 + { + volatile char x = 0; + x = 1 / x; // force an exception + } + if (b == 1) // divide by 1 + { + if (c) + *c = 0; // remainder = 0 + return a; // quotient = numerator + } + if (!arith64_hi(a)) // numerator is also 32-bit + { + if (c) // use generic 32-bit operators + *c = arith64_lo(a) % arith64_lo(b); + return arith64_lo(a) / arith64_lo(b); + } + } + + // let's do long division + char bits = __clzdi2(b) - __clzdi2(a) + + 1; // number of bits to iterate (a and b are non-zero) + arith64_u64 rem = a >> bits; // init remainder + a <<= 64 - bits; // shift numerator to the high bit + arith64_u64 wrap = 0; // start with wrap = 0 + while (bits-- > 0) // for each bit + { + rem = (rem << 1) | (a >> 63); // shift numerator MSB to remainder LSB + a = (a << 1) | (wrap & 1); // shift out the numerator, shift in wrap + wrap = + ((arith64_s64)(b - rem - 1) >> + 63); // wrap = (b > rem) ? 0 : 0xffffffffffffffff (via sign extension) + rem -= b & wrap; // if (wrap) rem -= b + } + if (c) + *c = rem; // maybe set remainder + return (a << 1) | (wrap & 1); // return the quotient +} + +// Return the quotient of the signed division of a by b. +arith64_s64 __divdi3(arith64_s64 a, arith64_s64 b) { + arith64_u64 q = __divmoddi4(arith64_abs(a), arith64_abs(b), (void *)0); + return arith64_neg(q, a ^ b); // negate q if a and b signs are different +} + +// Return the index of the least significant 1-bit in a, or the value zero if a +// is zero. The least significant bit is index one. +int __ffsdi2(arith64_u64 a) { return a ? __ctzdi2(a) + 1 : 0; } + +// Return the result of logically shifting a right by b bits. +arith64_u64 __lshrdi3(arith64_u64 a, int b) { + arith64_word w = {.u64 = a}; + + b &= 63; + + if (b >= 32) { + w.u32.lo = w.u32.hi >> (b - 32); + w.u32.hi = 0; + } else if (b) { + w.u32.lo = (w.u32.hi << (32 - b)) | (w.u32.lo >> b); + w.u32.hi >>= b; + } + return w.u64; +} + +// Return the remainder of the signed division of a by b. +arith64_s64 __moddi3(arith64_s64 a, arith64_s64 b) { + arith64_u64 r; + __divmoddi4(arith64_abs(a), arith64_abs(b), &r); + return arith64_neg(r, a); // negate remainder if numerator is negative +} + +// Return the number of bits set in a. +int __popcountsi2(arith64_u32 a) { + // collect sums into two low bytes + a = a - ((a >> 1) & 0x55555555); + a = ((a >> 2) & 0x33333333) + (a & 0x33333333); + a = (a + (a >> 4)) & 0x0F0F0F0F; + a = (a + (a >> 16)); + // add the bytes, return bottom 6 bits + return (a + (a >> 8)) & 63; +} + +// Return the number of bits set in a. +int __popcountdi2(arith64_u64 a) { + // collect sums into two low bytes + a = a - ((a >> 1) & 0x5555555555555555ULL); + a = ((a >> 2) & 0x3333333333333333ULL) + (a & 0x3333333333333333ULL); + a = (a + (a >> 4)) & 0x0F0F0F0F0F0F0F0FULL; + a = (a + (a >> 32)); + a = (a + (a >> 16)); + // add the bytes, return bottom 7 bits + return (a + (a >> 8)) & 127; +} + +// Return the quotient of the unsigned division of a by b. +arith64_u64 __udivdi3(arith64_u64 a, arith64_u64 b) { + return __divmoddi4(a, b, (void *)0); +} + +// Return the remainder of the unsigned division of a by b. +arith64_u64 __umoddi3(arith64_u64 a, arith64_u64 b) { + arith64_u64 r; + __divmoddi4(a, b, &r); + return r; +} + +arith64_u64 __udivmoddi4(arith64_u64 a, arith64_u64 b, arith64_u64 *c) { + return __divmoddi4(a, b, c); +} diff --git a/src/mock_libc/assert.h b/src/mock_libc/assert.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/assert.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/ctype.h b/src/mock_libc/ctype.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/ctype.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/dlfcn.h b/src/mock_libc/dlfcn.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/dlfcn.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/errno.h b/src/mock_libc/errno.h new file mode 100644 index 0000000..f38fb4d --- /dev/null +++ b/src/mock_libc/errno.h @@ -0,0 +1 @@ +#include "mock_libc.h" diff --git a/src/mock_libc/fcntl.h b/src/mock_libc/fcntl.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/fcntl.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/fmt.c b/src/mock_libc/fmt.c new file mode 100644 index 0000000..99120e7 --- /dev/null +++ b/src/mock_libc/fmt.c @@ -0,0 +1,802 @@ +#include "fmt.h" +#include +#include +#include +#include + +size_t _vfsWrite(RWStream *stream, const void *data, size_t size) { + stream->supposedoffs += size; + + return vfsWrite((Stream *)stream->data, data, size); +} + +size_t _vfsPeek(RWStream *stream, void *dest, size_t size) { + size_t at = vfsTell((Stream *)stream->data); + size_t wrote = vfsRead((Stream *)stream->data, dest, size); + vfsSeek((Stream *)stream->data, at, 0); + return wrote; +} + +size_t _vfsTell(RWStream *stream) { return vfsTell((Stream *)stream->data); } + +size_t _vfsRead(RWStream *stream, void *dest, size_t size) { + return vfsRead((Stream *)stream->data, dest, size); +} + +size_t _strWrite(RWStream *stream, const void *data_, size_t size) { + stream->supposedoffs += size; + + if (stream->size == 0) { + return 0; + } + + size_t written = 0; + const char *data = (const char *)data_; + char *str = (char *)stream->data; + while (size && stream->offs < stream->size - 1) { + str[stream->offs++] = *data++; + size--; + written++; + } + str[stream->offs] = 0; + return written; +} + +size_t _strPeek(RWStream *stream, void *dest_, size_t size) { + size_t written = 0; + char *dest = dest_; + const char *str = (const char *)stream->data + stream->offs; + while (size && *str) { + *dest++ = *str++; + size--; + written++; + } + return written; +} + +size_t _strTell(RWStream *stream) { return stream->offs; } + +size_t _strRead(RWStream *stream, void *dest_, size_t size) { + size_t written = 0; + char *dest = dest_; + const char *str = (const char *)stream->data; + while (size && stream->offs < stream->size && str[stream->offs]) { + *dest++ = str[stream->offs++]; + size--; + written++; + } + return written; +} + +RWStream rwStreamMkStr(char *str) { + RWStream rw = {0}; + rw.size = 1 << 31; + rw.offs = 0; + rw.data = (void *)str; + rw.write = _strWrite; + rw.peek = _strPeek; + rw.tell = _strTell; + rw.read = _strRead; + return rw; +} + +RWStream rwStreamMkStrS(char *str, size_t size) { + RWStream rw = {0}; + rw.size = size; + rw.offs = 0; + rw.data = (void *)str; + rw.write = _strWrite; + rw.peek = _strPeek; + rw.tell = _strTell; + rw.read = _strRead; + return rw; +} + +RWStream rwStreamMkFile(Stream *stream) { + RWStream rw = {0}; + rw.data = stream; + rw.write = _vfsWrite; + rw.peek = _vfsPeek; + rw.tell = _vfsTell; + rw.read = _vfsRead; + return rw; +} + +//////////////////////////////////////////////////////////////////////////////// + +void _skipSpaces(RWStream *s) { + char ch = 0; + while (s->peek(s, &ch, 1) && ch == ' ') { + s->read(s, &ch, 1); + } +} + +void _skipWhitespaces(RWStream *s) { + char ch = 0; + while (s->peek(s, &ch, 1) && + (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r')) { + s->read(s, &ch, 1); + } +} + +const char basetbl[256] = { + ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, + ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9, ['A'] = 10, ['B'] = 11, + ['C'] = 12, ['D'] = 13, ['E'] = 14, ['F'] = 15, ['a'] = 10, ['b'] = 11, + ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15}; + +unsigned long long fmtParseNum(RWStream *s, int base) { + char buf[64] = {0}; + s->peek(s, buf, 64); + char *nptr = buf; + + if (base == 0) { + if (*nptr == '0') { + if (nptr[1] == 'x' || nptr[1] == 'X') { + base = 16; + nptr += 2; + } else { + base = 8; + nptr += 1; + } + } else { + base = 10; + } + } + + unsigned long long n = 0; + while (*nptr) { + if (*nptr == '0' || (basetbl[*nptr] && basetbl[*nptr] < base)) { + n = n * base + basetbl[*nptr]; + } else { + break; + } + nptr++; + } + + s->read(s, buf, nptr - buf); + return n; +} + +unsigned long long fmtParseULL(RWStream *s, int base) { + _skipSpaces(s); + char ch; + + s->peek(s, &ch, 1); + if (ch == '+') + s->read(s, &ch, 1); + + return fmtParseNum(s, base); +} + +long long fmtParseLL(RWStream *s, int base) { + _skipSpaces(s); + char ch = 0; + + s->peek(s, &ch, 1); + if (ch == '-') { + s->read(s, &ch, 1); + return -fmtParseNum(s, base); + } + if (ch == '+') + s->read(s, &ch, 1); + return fmtParseNum(s, base); +} + +double fmtParseD(RWStream *s) { + char ch = 0; + _skipSpaces(s); + + double sign = 1; + s->peek(s, &ch, 1); + if (ch == '-') { + s->read(s, &ch, 1); + sign = -1; + } + if (ch == '+') + s->read(s, &ch, 1); + + size_t point = s->tell(s); + + double val = 0; + unsigned long long whole = fmtParseNum(s, 10); + + if (s->tell(s) == point) { + return 0; + } + + val = whole; + s->peek(s, &ch, 1); + if (ch == '.') { + s->read(s, &ch, 1); + unsigned long long frac = 0; + double div = 1; + while (s->peek(s, &ch, 1) && (ch >= '0' && ch <= '9')) { + div *= 10; + frac = frac * 10 + (ch - '0'); + s->read(s, &ch, 1); + } + val += frac / div; + } + + s->peek(s, &ch, 1); + if (ch == 'e' || ch == 'E') { + s->read(s, &ch, 1); + + double expsign = 1; + s->peek(s, &ch, 1); + if (ch == '-') { + s->read(s, &ch, 1); + expsign = -1; + } + if (ch == '+') + s->read(s, &ch, 1); + + double e = fmtParseNum(s, 10) * expsign; + return val * exp(log(10) * e); + } + + return val * sign; +} + +//////////////////////////////////////////////////////////////////////////////// + +// TODO: Scanset +// TODO: Width +// +size_t fmtvscanf(RWStream *s, const char *fmt, va_list ap) { + size_t num = 0; + size_t start = s->tell(s); + + while (*fmt) { + // char data[1024]; + // data[s->peek(s, data, 1024)] = 0; + // printf("%s\n", data); + + _skipWhitespaces(s); + if (*fmt == '%') { + fmt++; + size_t point = s->tell(s); + + switch (*fmt) { + case '%': { + char ch = 0; + s->read(s, &ch, 1); + num--; + break; + } + case 'i': { + int *i = va_arg(ap, int *); + *i = fmtParseLL(s, 0); + break; + } + case 'd': { + int *i = va_arg(ap, int *); + *i = fmtParseLL(s, 10); + break; + } + case 'u': { + unsigned int *i = va_arg(ap, unsigned int *); + *i = fmtParseULL(s, 10); + break; + } + case 'o': { + unsigned int *i = va_arg(ap, unsigned int *); + *i = fmtParseULL(s, 8); + break; + } + case 'x': { + unsigned int *i = va_arg(ap, unsigned int *); + *i = fmtParseULL(s, 16); + break; + } + case 'e': + case 'g': + case 'f': { + double *d = va_arg(ap, double *); + *d = fmtParseD(s); + break; + } + case 'c': { + char *c = va_arg(ap, char *); + s->read(s, c, 1); + break; + } + case 'p': { + void **p = va_arg(ap, void **); + *p = (void *)(size_t)fmtParseULL(s, 16); + break; + } + case 's': { + char *buf = va_arg(ap, char *); + size_t i = 0; + char ch = 0; + while (s->peek(s, &ch, 1) && ch != ' ' && ch != '\n' && ch != '\t' && + ch != '\r') { + s->read(s, &buf[i++], 1); + } + buf[i] = 0; + break; + } + case 'n': { + int *i = va_arg(ap, int *); + *i = s->tell(s) - start; + break; + } + } + if (point == s->tell(s) && *fmt != 'n') { + return num; + } else { + num++; + } + } else if (*fmt == ' ' || *fmt == '\n' || *fmt == '\t' || *fmt == '\r') { + // Skip whitespace + } else { + char ch = 0; + s->peek(s, &ch, 1); + if (ch != *fmt) { + return num; + } + s->read(s, &ch, 1); + } + + fmt++; + } + + return num; +} + +size_t fmtscanf(RWStream *s, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + size_t num = fmtvscanf(s, fmt, ap); + va_end(ap); + return num; +} + +//////////////////////////////////////////////////////////////////////////////// + +void fmtStringifyULL(RWStream *s, unsigned long long n, int base, bool upper) { + char buf[64] = {0}; + char *ptr = buf + 63; + *--ptr = 0; + + if (n == 0) { + *--ptr = '0'; + } + + while (n) { + *--ptr = (upper ? "0123456789ABCDEF" : "0123456789abcdef")[n % base]; + n /= base; + } + + s->write(s, ptr, buf + 63 - ptr - 1); +} + +void fmtStringifyLL(RWStream *s, long long n, int base, bool upper, bool sign, + bool padsign) { + if (n < 0) + sign = true; + + if (sign) { + s->write(s, n < 0 ? "-" : "+", 1); + } else if (padsign) { + s->write(s, n < 0 ? "-" : " ", 1); + } + + fmtStringifyULL(s, n < 0 ? -n : n, base, upper); +} + +void fmtStringifyD(RWStream *s, double n, bool upper, bool sign, + bool scientific, int prec, bool padsign) { + char buf[64] = {0}; + char *ptr = buf + 64; + + if (n < 0) + sign = true; + + if (sign) { + s->write(s, n < 0 ? "-" : "+", 1); + } else if (padsign) { + s->write(s, n < 0 ? "-" : " ", 1); + } + + // TODO: Scientific + if (n < 0) { + n = -n; + } + + unsigned long long whole = n; + double frac = n - whole; + + fmtStringifyULL(s, whole, 10, upper); + + if (prec == 0) + return; + + s->write(s, ".", 1); + + for (int i = 0; i < prec; i++) { + frac *= 10; + s->write(s, &"0123456789"[((int)frac % 10)], 1); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +size_t fmtvprintf(RWStream *s, const char *fmt, va_list ap) { + size_t start = s->supposedoffs; + while (*fmt) { + if (*fmt == '%') { + fmt++; + enum { XNONE, XHH, XH, XL, XLL, XJ, XZ, XT }; + enum { NNONE, NULONGLONG, NLONGLONG, NDOUBLE }; + + bool leftjustify = false; + bool forcesign = false; + bool padsign = false; + bool decorated = false; + bool padzero = false; + + while (true) { + switch (*fmt) { + case '-': + leftjustify = true; + break; + case '+': + forcesign = true; + break; + case ' ': + padsign = true; + break; + case '#': + decorated = true; + break; + case '0': + padzero = true; + break; + default: + goto stop; + } + fmt++; + } + stop: + + int width = 0; + if (*fmt >= '0' && *fmt <= '9') { + while (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + (*fmt - '0'); + fmt++; + } + } else if (*fmt == '*') { + width = va_arg(ap, int); + fmt++; + } + + int precision = -1; + if (*fmt == '.') { + fmt++; + if (*fmt >= '0' && *fmt <= '9') { + precision = 0; + while (*fmt >= '0' && *fmt <= '9') { + precision = precision * 10 + (*fmt - '0'); + fmt++; + } + } else if (*fmt == '*') { + precision = va_arg(ap, int); + fmt++; + } + } + + int length = XNONE; + if (*fmt == 'h') { + fmt++; + if (*fmt == 'h') { + length = XHH; + fmt++; + } else { + length = XH; + } + } else if (*fmt == 'l') { + fmt++; + if (*fmt == 'l') { + length = XLL; + fmt++; + } else { + length = XL; + } + } else if (*fmt == 'j') { + length = XJ; + fmt++; + } else if (*fmt == 'z') { + length = XZ; + fmt++; + } else if (*fmt == 't') { + length = XT; + fmt++; + } + + bool uppercase = false; + int numtype = NNONE; + int base; + unsigned long long ull; + long long ll; + double d; + + switch (*fmt) { + case 'i': + case 'd': + base = 10; + numtype = NLONGLONG; + switch (length) { + case XHH: + ll = (char)va_arg(ap, int); + break; + case XH: + ll = (short)va_arg(ap, int); + break; + case XL: + ll = va_arg(ap, long); + break; + case XLL: + ll = va_arg(ap, long long); + break; + case XJ: + ll = va_arg(ap, intmax_t); + break; + case XZ: + ll = va_arg(ap, size_t); + break; + case XT: + ll = va_arg(ap, ptrdiff_t); + break; + default: + ll = va_arg(ap, int); + break; + } + break; + case 'X': + base = 16; + uppercase = true; + goto decide; + case 'x': + base = 16; + goto decide; + case 'o': + base = 8; + goto decide; + case 'u': + base = 10; + decide: + numtype = NULONGLONG; + switch (length) { + case XHH: + ull = va_arg(ap, int); + break; + case XH: + ull = va_arg(ap, int); + break; + case XL: + ull = va_arg(ap, unsigned long); + break; + case XLL: + ull = va_arg(ap, unsigned long long); + break; + case XJ: + ull = va_arg(ap, uintmax_t); + break; + case XZ: + ull = va_arg(ap, size_t); + break; + case XT: + ull = va_arg(ap, ptrdiff_t); + break; + default: + ull = va_arg(ap, int); + break; + } + break; + case 'E': + case 'e': + case 'G': + case 'g': + case 'f': { + d = va_arg(ap, double); + if (precision < 0) + precision = 1; + numtype = NDOUBLE; + break; + } + case 'P': + uppercase = true; + case 'p': { + numtype = NULONGLONG; + base = 16; + decorated = true; + ull = (size_t)va_arg(ap, void *); + break; + } + case '%': { + if (!leftjustify) + for (int i = 0; i < width - 1; i++) + s->write(s, " ", 1); + + s->write(s, "%", 1); + + if (leftjustify) + for (int i = 0; i < width - 1; i++) + s->write(s, " ", 1); + + goto end; + } + case 'c': { + char c = va_arg(ap, int); + + if (!leftjustify) + for (int i = 0; i < width - 1; i++) + s->write(s, " ", 1); + + s->write(s, &c, 1); + + if (leftjustify) + for (int i = 0; i < width - 1; i++) + s->write(s, " ", 1); + + goto end; + } + case 's': { + // NOTE: No wchar. + char *str = va_arg(ap, char *); + int strl = strlen(str); + int size = strl; + if (precision != -1 && precision < strl) + size = precision; + + if (!leftjustify) + for (int i = 0; i < width - size; i++) + s->write(s, " ", 1); + + s->write(s, str, size); + + if (leftjustify) + for (int i = 0; i < width - size; i++) + s->write(s, " ", 1); + goto end; + } + case 'n': { + if (!leftjustify) + for (int i = 0; i < width; i++) + s->write(s, " ", 1); + + switch (length) { + case XHH: + *va_arg(ap, signed char *) = s->tell(s) - start; + case XH: + *va_arg(ap, short *) = s->tell(s) - start; + case XL: + *va_arg(ap, long *) = s->tell(s) - start; + case XLL: + *va_arg(ap, long long *) = s->tell(s) - start; + case XJ: + *va_arg(ap, intmax_t *) = s->tell(s) - start; + case XZ: + *va_arg(ap, size_t *) = s->tell(s) - start; + case XT: + *va_arg(ap, ptrdiff_t *) = s->tell(s) - start; + default: + *va_arg(ap, int *) = s->tell(s) - start; + } + + if (leftjustify) + for (int i = 0; i < width; i++) + s->write(s, " ", 1); + + goto end; + } + } + + char numbuf[128] = {0}; + RWStream numstream = rwStreamMkStrS(numbuf, 128); + + switch (numtype) { + case NULONGLONG: + fmtStringifyULL(&numstream, ull, base, uppercase); + break; + case NLONGLONG: + fmtStringifyLL(&numstream, ll, base, uppercase, forcesign, padsign); + break; + case NDOUBLE: + fmtStringifyD(&numstream, d, uppercase, forcesign, false, precision, + padsign); + break; + default: + break; + } + + int numlen = numstream.tell(&numstream); + int padding = width - numlen; + + if (numlen < width) { + if (leftjustify) { + if (decorated) { + if (base == 16) { + s->write(s, "0x", 2); + padding -= 2; + } else if (base == 8) { + s->write(s, "0", 1); + padding -= 1; + } + } + if (precision > 0 && numtype != NDOUBLE) { + for (int i = 0; i < precision - numlen; i++) { + s->write(s, "0", 1); + padding -= 1; + } + } + s->write(s, numbuf, numlen); + for (int i = 0; i < padding; i++) { + s->write(s, " ", 1); + } + } else { + if (decorated) { + if (base == 16) { + if (padzero) + s->write(s, "0x", 2); + padding -= 2; + } else if (base == 8) { + if (padzero) + s->write(s, "0", 1); + padding -= 1; + } + } + if (precision > 0 && numtype != NDOUBLE) { + padding -= precision - numlen; + } + for (int i = 0; i < padding; i++) { + s->write(s, padzero ? "0" : " ", 1); + } + if (decorated && !padzero) { + if (base == 16) { + s->write(s, "0x", 2); + } else if (base == 8) { + s->write(s, "0", 1); + } + } + if (precision > 0 && numtype != NDOUBLE) { + for (int i = 0; i < precision - numlen; i++) + s->write(s, "0", 1); + } + s->write(s, numbuf, numlen); + } + } else { + if (decorated) { + if (base == 16) { + s->write(s, "0x", 2); + } else if (base == 8) { + s->write(s, "0", 1); + } + } + if (precision > 0 && numtype != NDOUBLE) { + for (int i = 0; i < precision - numlen; i++) + s->write(s, "0", 1); + } + s->write(s, numbuf, numlen); + } + end:; + fmt++; + } else { + s->write(s, fmt, 1); + fmt++; + } + } + + return s->supposedoffs - start; +} + +size_t fmtprintf(RWStream *s, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + size_t num = fmtvprintf(s, fmt, ap); + va_end(ap); + return num; +} diff --git a/src/mock_libc/fmt.h b/src/mock_libc/fmt.h new file mode 100644 index 0000000..b8750ff --- /dev/null +++ b/src/mock_libc/fmt.h @@ -0,0 +1,36 @@ +#pragma once +#include "vfs.h" +#include +#include + +struct RWStream { + // optional fields, but useful + size_t supposedoffs; + size_t offs; + size_t size; + void *data; + size_t (*write)(struct RWStream *s, const void *data, size_t size); + size_t (*peek)(struct RWStream *s, void *dest, size_t size); + size_t (*tell)(struct RWStream *s); + size_t (*read)(struct RWStream *s, void *dest, size_t size); +} typedef RWStream; + +RWStream rwStreamMkStr(char *str); +RWStream rwStreamMkStrS(char *str, size_t size); +RWStream rwStreamMkFile(Stream *stream); + +unsigned long long fmtParseULL(RWStream *s, int base); +long long fmtParseLL(RWStream *s, int base); +double fmtParseD(RWStream *s); + +size_t fmtvscanf(RWStream *s, const char *fmt, va_list ap); +size_t fmtscanf(RWStream *s, const char *fmt, ...); + +void fmtStringifyULL(RWStream *s, unsigned long long n, int base, bool upper); +void fmtStringifyLL(RWStream *s, long long n, int base, bool upper, bool sign, + bool padsign); +void fmtStringifyD(RWStream *s, double n, bool upper, bool sign, + bool scientific, int prec, bool padsign); + +size_t fmtvprintf(RWStream *s, const char *fmt, va_list ap); +size_t fmtprintf(RWStream *s, const char *fmt, ...); \ No newline at end of file diff --git a/src/mock_libc/math.h b/src/mock_libc/math.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/math.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/mock_libc.c b/src/mock_libc/mock_libc.c new file mode 100644 index 0000000..31b9105 --- /dev/null +++ b/src/mock_libc/mock_libc.c @@ -0,0 +1,511 @@ +#include "mock_libc.h" +#include "alloc.h" +#include "fmt.h" +#include "vfs.h" +#include +#include + +#ifndef _WIN32 +int _errno; +#else +int *_errno() { + static int e; + return &e; +} +#endif + +// ALLOC +#ifdef USE_VIRTUALALLOC +#include +void free(void *ptr) { VirtualFree(ptr, 0, MEM_RELEASE); } + +void *realloc(void *ptr, size_t size) { + void *new = malloc(size); + memcpy(new, ptr, size); + free(ptr); + return new; +} + +void *malloc(size_t size) { + return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); +} +void *calloc(size_t nmemb, size_t size) { + void *ptr = malloc(nmemb * size); + for (size_t i = 0; i < nmemb * size; i++) { + ((char *)ptr)[i] = 0; + } + return ptr; +} +#else +void free(void *ptr) { allocatorFree(ptr); } +void *realloc(void *ptr, size_t size) { return allocatorRealloc(ptr, size); } +void *malloc(size_t size) { return allocatorAlloc(size); } +void *calloc(size_t nmemb, size_t size) { + void *ptr = allocatorAlloc(nmemb * size); + for (size_t i = 0; i < nmemb * size; i++) { + ((char *)ptr)[i] = 0; + } + return ptr; +} +#endif + +// MEMORY +int memcmp(const void *s1, const void *s2, size_t n) { + const unsigned char *p1 = s1; + const unsigned char *p2 = s2; + for (int i = 0; i < n; i++) { + if (p1[i] != p2[i]) { + return p1[i] - p2[i]; + } + } + return 0; +} + +void *memcpy(void *dest, const void *src, size_t n) { + char *d = dest; + const char *s = src; + while (n--) { + *d++ = *s++; + } + return dest; +} + +void *memset(void *s, int c, size_t n) { + unsigned char *p = s; + while (n--) { + *p++ = c; + } + return s; +} + +void *memmove(void *dest, const void *src, size_t n) { + char *d = dest; + const char *s = src; + if (d < s) { + while (n--) { + *d++ = *s++; + } + } else { + d += n; + s += n; + while (n--) { + *--d = *--s; + } + } + return dest; +} + +// FILE IO +typedef void FILE; +FILE *stdin; +FILE *stdout; +FILE *stderr; + +int open(const char *pathname, int flags) { + if (flags & O_RDONLY) { + return (int)(size_t)vfsOpen(pathname, "rb"); + } else { + return (int)(size_t)vfsOpen(pathname, "wb"); + } +} + +int close(int fd) { + vfsClose((FILE *)(size_t)fd); + return 0; +} +int read(int fd, void *buf, size_t count) { + return vfsRead((FILE *)(size_t)fd, buf, count); +} +int lseek(int fd, int offset, int whence) { + return vfsSeek((FILE *)(size_t)fd, offset, whence), 0; +} + +FILE *fopen(const char *path, const char *mode) { return vfsOpen(path, mode); } + +int fclose(FILE *stream) { + vfsClose(stream); + return 0; +} + +int fgetc(FILE *stream) { + char c; + if (vfsRead(stream, &c, 1) == 0) { + return EOF; + } + return c; +} + +int fputc(char c, FILE *stream) { + if (vfsWrite(stream, &c, 1) == 0) { + return EOF; + } + return c; +} + +int fseek(FILE *stream, long int offset, int whence) { + vfsSeek(stream, offset, whence); + return 0; +} + +long int ftell(FILE *stream) { return vfsTell(stream); } + +size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { + return vfsRead(stream, ptr, size * nmemb) / size; +} + +size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { + return vfsWrite(stream, ptr, size * nmemb) / size; +} + +int feof(FILE *stream) { return vfsEof(stream); } + +int fflush(FILE *stream) { + vfsFlush(stream); + return 0; +} + +int ferror(FILE *stream) { return 0; } + +int rewind(FILE *stream) { + vfsSeek(stream, 0, 0); + return 0; +} + +int remove(const char *filename) { return vfsRemove(filename); } + +int puts(const char *s) { + fwrite(s, 1, strlen(s), stdout); + fputc('\n', stdout); + return 0; +} + +// FORMATTING +char *strncpy(char *d, const char *s, size_t n) { + size_t i = 0; + while (i < n && s[i]) { + d[i] = s[i]; + i++; + } + d[i] = 0; + return d; +} + +char *strcpy(char *d, const char *s) { + size_t i = 0; + while (s[i]) { + d[i] = s[i]; + i++; + } + d[i] = 0; + return d; +} + +int strcmp(const char *a, const char *b) { + while (*a && *b && *a == *b) { + a++; + b++; + } + return *a - *b; +} + +int strncmp(const char *a, const char *b, size_t n) { + while (n-- && *a && *b && *a == *b) { + a++; + b++; + } + return n ? *a - *b : 0; +} + +size_t strlen(const char *s) { + size_t i = 0; + while (s[i]) { + i++; + } + return i; +} + +char *strcat(char *d, const char *s) { + size_t i = 0; + while (d[i]) { + i++; + } + size_t j = 0; + while (s[j]) { + d[i++] = s[j++]; + } + d[i] = 0; + return d; +} + +char *strncat(char *d, const char *s, size_t c) { + size_t i = 0; + while (d[i]) { + i++; + } + size_t j = 0; + while (s[j] && j < c) { + d[i++] = s[j++]; + } + d[i] = 0; + return d; +} + +char *strrchr(const char *s, int ch) { + char *p = NULL; + while (*s) { + if (*s == ch) { + p = (char *)s; + } + s++; + } + return p; +} + +unsigned long long strtoull(const char *nptr, char **endptr, int base) { + RWStream stream = rwStreamMkStr((char *)nptr); + + unsigned long long num = fmtParseULL(&stream, base); + + if (endptr) { + *endptr = (char *)nptr + stream.offs; + } + + return num; +} + +long long strtoll(const char *nptr, char **endptr, int base) { + RWStream stream = rwStreamMkStr((char *)nptr); + + long long num = fmtParseLL(&stream, base); + + if (endptr) { + *endptr = (char *)nptr + stream.offs; + } + + return num; +} + +long strtol(const char *nptr, char **endptr, int base) { + RWStream stream = rwStreamMkStr((char *)nptr); + + long num = fmtParseLL(&stream, base); + + if (endptr) { + *endptr = (char *)nptr + stream.offs; + } + + return num; +} + +double strtod(const char *nptr, char **endptr) { + RWStream stream = rwStreamMkStr((char *)nptr); + + double num = fmtParseD(&stream); + + if (endptr) { + *endptr = (char *)nptr + stream.offs; + } + + return num; +} + +int vsscanf(const char *s, const char *fmt, va_list ap) { + RWStream stream = rwStreamMkStr((char *)s); + size_t n = fmtvscanf(&stream, fmt, ap); + return n; +} + +int vfscanf(FILE *f, const char *fmt, va_list ap) { + RWStream stream = rwStreamMkFile(f); + size_t n = fmtvscanf(&stream, fmt, ap); + return n; +} + +int sscanf(const char *s, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsscanf(s, fmt, ap); + va_end(ap); + return n; +} + +int vsnprintf(char *dest, size_t n, const char *fmt, va_list ap) { + RWStream stream = rwStreamMkStrS(dest, n); + size_t written = fmtvprintf(&stream, fmt, ap); + + return written; +} + +int vfprintf(FILE *f, const char *fmt, va_list ap) { + RWStream stream = rwStreamMkFile(f); + size_t written = fmtvprintf(&stream, fmt, ap); + + return written; +} + +int sprintf(char *dest, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(dest, 0xFFFFFFFF, fmt, ap); + va_end(ap); + return n; +} + +int snprintf(char *dest, size_t n, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + n = vsnprintf(dest, n, fmt, ap); + va_end(ap); + return n; +} + +int fprintf(FILE *f, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vfprintf(f, fmt, ap); + va_end(ap); + return n; +} + +// MATH + +double trunc(double x) { + if (x < 0) { + return ceil(x); + } + return floor(x); +} + +double fmod(double x, double y) { return x - y * floor(x / y); } +double fabs(double x) { return x < 0 ? -x : x; } + +double sin(double x) { + x *= 180 / M_PI; + x = fmod(x, M_2PI); + if (x / 2 == 1) { + x -= M_PI; + return -(4 * x * (180 - x) / (40500 - x * (180 - x))); + } + + return 4 * x * (180 - x) / (40500 - x * (180 - x)); +} + +double cos(double x) { return sin(x + M_PI_2); } +double atan(double x) { + return M_PI_4 * x - x * (fabs(x) - 1) * (0.2447 + 0.0663 * fabs(x)); +} +double atan2(double x, double y) { + if (x == 0) { + return y > 0 ? M_PI_2 : -M_PI_2; + } + if (y == 0) { + return x > 0 ? 0 : M_PI; + } + double z = x / y; + if (z < 0) { + return atan(z) - M_PI; + } + return atan(z); +} +double sqrt(double x) { + double z = x; + for (int i = 0; i < 10; i++) { + z = (z + x / z) / 2; + } + return z; +} +double floor(double x) { + int n = (int)x; + return n > x ? n - 1 : n; +} +double ceil(double x) { + int n = (int)x; + return n < x ? n + 1 : n; +} +double round(double x) { + int n = (int)x; + return x - n >= 0.5 ? n + 1 : n; +} + +// taken from here +// https://gist.github.com/jrade/293a73f89dfef51da6522428c857802d +double exp(double x) { + double a = (1ll << 52) / 0.6931471805599453; + double b = (1ll << 52) * (1023 - 0.04367744890362246); + x = a * x + b; + + double c = (1ll << 52); + double d = (1ll << 52) * 2047; + if (x < c || x > d) + x = (x < c) ? 0.0 : d; + + uint64_t n = (uint64_t)x; + memcpy(&x, &n, 8); + return x; +} + +double log(double _x) { + float x = _x; + + unsigned int bx = *(unsigned int *)(&x); + unsigned int ex = bx >> 23; + signed int t = (signed int)ex - (signed int)127; + unsigned int s = (t < 0) ? (-t) : t; + bx = 1065353216 | (bx & 8388607); + x = *(float *)(&bx); + return -1.49278 + (2.11263 + (-0.729104 + 0.10969 * x) * x) * x + + 0.6931471806 * t; +} + +// STUBS +int system(const char *cmd) { return 0; } +void *dlopen(const char *dll, int flags) { return NULL; } +int dlclose(void *dll) { return 0; } +void *dlsym(void *dll, const char *sym) { return NULL; } +char *getcwd(char *to, size_t n) { + if (n >= 2) { + to[0] = '/'; + to[1] = '\0'; + } else if (n == 1) { + to[0] = '\0'; + } + return to; +}; +int time(int *d) { return *d = 0, 0; } +int clock_gettime(int clock, struct timespec *t) { + *t = (struct timespec){0}; + return 0; +} + +struct tm *localtime(const time_t *timer) { + static struct tm tm; + return &tm; +} + +struct tm *gmtime(const time_t *timer) { + static struct tm tm; + return &tm; +} + +time_t mktime(struct tm *tm) { return 0; } + +char *getenv(const char *k) { return NULL; } +clock_t clock() { + static clock_t c; + return c; +} + +// setjmp/longjmp +#ifdef _WIN32 +// imported from msvcrt +#else +// implemented in lib.s +#endif + +void rtlInit(void *mem) { + allocatorInit(mem); + vfsInit(); + stdin = fopen("$stdin", "rb"); + stdout = fopen("$stdout", "wb"); + stderr = fopen("$stderr", "wb"); +} diff --git a/src/mock_libc/mock_libc.h b/src/mock_libc/mock_libc.h new file mode 100644 index 0000000..d8fc284 --- /dev/null +++ b/src/mock_libc/mock_libc.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#define EOF 0 +#define M_PI 3.14159265 +#define M_2PI (3.14159265 * 2) +#define M_PI_2 (M_PI / 2) +#define M_PI_4 (M_PI / 4) +#define ERANGE 34 +#define CLOCKS_PER_SEC 1000 +#define CLOCK_REALTIME 0 +#define O_RDONLY 1 +#ifndef _WIN32 +#define errno _errno +#define HUGE_VAL (__builtin_huge_val()) +#else +static const union { + unsigned char __c[8]; + double __d; +} __huge_val = {{0, 0, 0, 0, 0, 0, 0xf0, 0x7f}}; +#define HUGE_VAL (__huge_val.__d) +#endif + +#ifndef _WIN32 +typedef struct __attribute__((aligned(4))) _aligned4 { + int32_t x; +} _aligned4; + +typedef _aligned4 jmp_buf[6]; +#else +typedef struct __declspec(align(16)) _aligned16 { + uint64_t x[2]; +} _aligned16; + +typedef _aligned16 jmp_buf[16]; +#endif +#ifndef _WIN32 +extern int _errno; +#endif + +void free(void *ptr); +void *realloc(void *ptr, size_t size); +void *malloc(size_t size); +void *calloc(size_t nmemb, size_t size); + +int memcmp(const void *s1, const void *s2, size_t n); +void *memcpy(void *dest, const void *src, size_t n); +void *memset(void *s, int c, size_t n); +void *memmove(void *dest, const void *src, size_t n); + +typedef void FILE; +extern FILE *stdin; +extern FILE *stdout; +extern FILE *stderr; + +int open(const char *pathname, int flags); +int close(int fd); +int read(int fd, void *buf, size_t count); +int lseek(int fd, int offset, int whence); + +FILE *fopen(const char *path, const char *mode); +int fclose(FILE *stream); +int fgetc(FILE *stream); +int fseek(FILE *stream, long int offset, int whence); +long int ftell(FILE *stream); +size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); +size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); +int feof(FILE *stream); +int fflush(FILE *stream); +int ferror(FILE *stream); +int rewind(FILE *stream); +int remove(const char *filename); +int puts(const char *s); + +static inline bool isalnum(int c) { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); +} + +static inline bool isalpha(int c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +static inline bool isspace(int c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || + c == '\f'; +} + +long strtol(const char *nptr, char **endptr, int base); +unsigned long long strtoull(const char *nptr, char **endptr, int base); +double strtod(const char *nptr, char **endptr); +int strcmp(const char *, const char *); +int strncmp(const char *, const char *, size_t n); +size_t strlen(const char *); +char *strcat(char *, const char *); +char *strncat(char *, const char *, size_t); +int sprintf(char *, const char *, ...); +int snprintf(char *, size_t, const char *, ...); +int vsnprintf(char *, size_t, const char *, va_list); +int sscanf(const char *, const char *, ...); +int vsscanf(const char *, const char *, va_list); +char *strncpy(char *, const char *, size_t); +char *strcpy(char *, const char *); +char *strrchr(const char *, int ch); +int fprintf(FILE *, const char *, ...); +int vfprintf(FILE *, const char *, va_list); +int vfscanf(FILE *, const char *, va_list); + +double fabs(double x); +double trunc(double x); +double sin(double x); +double cos(double x); +double atan(double x); +double atan2(double x, double y); +double sqrt(double x); +double floor(double x); +double ceil(double x); +double round(double x); +double exp(double x); +double log(double x); +double fmod(double x, double y); + +typedef long long time_t; + +struct timespec { + time_t tv_sec; + long tv_nsec; +}; + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +typedef long clock_t; + +int system(const char *); +void *dlopen(const char *, int); +int dlclose(void *); +void *dlsym(void *, const char *); +char *getcwd(char *, size_t); +int time(int *); +int clock_gettime(int clock, struct timespec *t); +struct tm *localtime(const time_t *timer); +struct tm *gmtime(const time_t *timer); +time_t mktime(struct tm *); +char *getenv(const char *); +clock_t clock(); + +#ifndef _WIN32 +void longjmp(jmp_buf, int); +int setjmp(jmp_buf); +#endif + +void rtlInit(void *mem); + +#ifndef _WIN32 +void panic(const char *s, ...); +#define assert(x) \ + if (!(x)) \ + panic("?! %s:%d (%s) ", __FILE__, __LINE__, (#x)) +#else +#define assert(x) \ + if (!(x)) \ + ; +#endif + +#if __USE_MINGW_ANSI_STDIO == 1 +#endif \ No newline at end of file diff --git a/src/mock_libc/setjmp.h b/src/mock_libc/setjmp.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/setjmp.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/stdio.h b/src/mock_libc/stdio.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/stdio.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/stdlib.h b/src/mock_libc/stdlib.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/stdlib.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/string.h b/src/mock_libc/string.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/string.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/sys/stat.h b/src/mock_libc/sys/stat.h new file mode 100644 index 0000000..7b9637e --- /dev/null +++ b/src/mock_libc/sys/stat.h @@ -0,0 +1 @@ +#pragma once \ No newline at end of file diff --git a/src/mock_libc/sys/types.h b/src/mock_libc/sys/types.h new file mode 100644 index 0000000..c417cf4 --- /dev/null +++ b/src/mock_libc/sys/types.h @@ -0,0 +1,2 @@ +#pragma once +typedef long off_t; \ No newline at end of file diff --git a/src/mock_libc/time.h b/src/mock_libc/time.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/time.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/unistd.h b/src/mock_libc/unistd.h new file mode 100644 index 0000000..223e65c --- /dev/null +++ b/src/mock_libc/unistd.h @@ -0,0 +1 @@ +#include "mock_libc.h" \ No newline at end of file diff --git a/src/mock_libc/vfs.c b/src/mock_libc/vfs.c new file mode 100644 index 0000000..14b2b0b --- /dev/null +++ b/src/mock_libc/vfs.c @@ -0,0 +1,392 @@ +#include "vfs.h" +#include +#include +#include +#include +#include +#include +#include + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +struct Node { + size_t datasize; + void *data; + char filename[32]; + struct Node *parent; + struct Node *child; + struct Node *sibling; +} typedef Node; + +struct { + Node *root; +} typedef VFS; + +struct Stream { + Node *node; + void *flushdata; + void (*flush)(void *, void *, size_t); + size_t flushindex; + size_t i; +}; + +VFS vfs; + +const char *vfsBasename(const char *path) { + const char *base = path; + for (int i = 0; path[i]; i++) { + if (path[i] == '/') { + base = path + i + 1; + } + } + return base; +} + +Node *vfsAlloc(Node *node, const char *file) { + Node *new = calloc(sizeof(Node), 1); + new->data = malloc(1); + new->datasize = 0; + new->parent = node; + new->child = NULL; + new->sibling = NULL; + strcpy(new->filename, file); + + if (node->child == NULL) { + node->child = new; + } else { + Node *child = node->child; + while (child->sibling) { + child = child->sibling; + } + child->sibling = new; + } + return new; +} + +void vfsSetData(Node *node, void *data, size_t size) { + free(node->data); + node->data = malloc(size); + memcpy(node->data, data, size); + node->datasize = size; +} + +void vfsClearData(Node *node) { node->datasize = 0; } + +void vfsAppendData(Node *node, void *data, size_t size) { + node->data = realloc(node->data, node->datasize + size); + memcpy(((uint8_t *)node->data) + node->datasize, data, size); + node->datasize += size; +} + +void vfsFree(Node *node) { + // free from parent + if (node->parent) { + Node *prev = NULL; + Node *child = node->parent->child; + while (child) { + if (child == node) { + if (prev) { + prev->sibling = node->sibling; + } else { + node->parent->child = node->sibling; + } + break; + } + prev = child; + child = child->sibling; + } + } + + // free children + if (node->child) { + Node *child = node->child; + while (child) { + Node *next = child->sibling; + vfsFree(child); + child = next; + } + } + free(node); +} + +Node *vfsGetPath(const char *path, bool base) { + char *sub[32]; + size_t sub_idx = 0; + + // split by '/' + int n = 0; + for (int i = 0; path[i]; i++) { + if (path[i] == '/') { + if (n > 0) { + sub[sub_idx] = malloc(n + 1); + sub[sub_idx][n] = 0; + memcpy(sub[sub_idx++], path + i - n, n); + n = 0; + } + } else { + n++; + } + } + + // last + if (n > 0) { + sub[sub_idx] = malloc(n + 1); + sub[sub_idx][n] = 0; + memcpy(sub[sub_idx++], path + strlen(path) - n, n); + } + + if (base && sub_idx >= 1) { + free(sub[sub_idx - 1]); + sub[--sub_idx] = NULL; + } + + // find path + Node *node = vfs.root; + for (int i = 0; i < sub_idx; i++) { + if (strcmp(sub[i], "..") == 0) { + if (node->parent == NULL) { + node = NULL; + goto cleanup; + } + node = node->parent; + continue; + } + + if (strcmp(sub[i], ".") == 0) { + continue; + } + + if (strcmp(sub[i], "") == 0) { + continue; + } + + Node *child = node->child; + while (child) { + if (strcmp(child->filename, sub[i]) == 0) { + node = child; + break; + } + child = child->sibling; + } + + if (child == NULL) { + node = NULL; + goto cleanup; + } + } + +cleanup: + for (int i = 0; i < sub_idx; i++) { + free(sub[i]); + } + + return node; +} + +bool vfsExists(const char *path) { return vfsGetPath(path, false) != NULL; } + +Node *vfsCreate_(const char *path) { + Node *node = vfsGetPath(path, false); + if (node != NULL) { + return node; + } + + node = vfsGetPath(path, true); + if (node == NULL) { + return NULL; + } + + Node *new = vfsAlloc(node, vfsBasename(path)); + + return new; +} + +int vfsCreate(const char *path) { return vfsCreate_(path) ? 0 : -1; } + +void vfsInit() { + // EDGE CASE: Since data is NULL here, do *NOT* bind vfs.root to a flush hook + vfs.root = calloc(sizeof(Node), 1); + vfsCreate("$stdin"); + vfsCreate("$stdout"); + vfsCreate("$stderr"); +} + +Stream *vfsOpen(const char *path, const char *perm) { + bool write = false; + for (int i = 0; perm[i]; i++) { + if (perm[i] == 'w') { + write = true; + } + if (perm[i] == 'a') { + return NULL; + } + } + + Node *node = vfsGetPath(path, false); + if (node == NULL) { + if (write) { + node = vfsCreate_(path); + if (node == NULL) { + return NULL; + } + } else { + return NULL; + } + } + if (write) { + vfsClearData(node); + } + + Stream *stream = malloc(sizeof(Stream)); + assert(stream); + *stream = (Stream){0}; + stream->node = node; + stream->i = 0; + + return stream; +} + +void vfsClose(Stream *stream) { + assert(stream); + memset(stream, 0, sizeof(Stream)); + free(stream); +} + +size_t vfsRead(Stream *stream, void *dest, size_t size) { + assert(stream); + size_t readsize = stream->node->datasize - stream->i; + if (readsize > size) { + readsize = size; + } + + memcpy(dest, (uint8_t *)stream->node->data + stream->i, readsize); + stream->i += readsize; + return readsize; +} + +size_t vfsWrite(Stream *stream, const void *data, size_t size) { + assert(stream); + vfsAppendData(stream->node, (void *)data, size); + stream->i += size; + vfsFlush(stream); + return size; +} + +void vfsSeek(Stream *stream, size_t pos, size_t whence) { + assert(stream); + if (whence == SEEK_SET) { + stream->i = pos; + } else if (whence == SEEK_CUR) { + stream->i += pos; + } else if (whence == SEEK_END) { + stream->i = stream->node->datasize + pos; + } +} + +size_t vfsTell(Stream *stream) { + assert(stream); + return stream->i; +} + +int vfsEof(Stream *stream) { + assert(stream); + return stream->i >= stream->node->datasize; +} + +void vfsHookFlush(Stream *stream, void *data, + void (*flush)(void *, void *, size_t)) { + assert(stream); + assert(flush); + stream->flush = flush; + stream->flushdata = data; + stream->flushindex = 0; +} + +void vfsFlush(Stream *stream) { + assert(stream); + if (stream->flush == NULL) { + return; + } + + if (stream->flushindex < stream->i) { + + stream->flush(stream->flushdata, + (uint8_t *)stream->node->data + stream->flushindex, + stream->i - stream->flushindex); + stream->flushindex = stream->i; + } +} + +int vfsRemove(const char *path) { + assert(path); + Node *node = vfsGetPath(path, false); + if (node == NULL) { + return -1; + } + + vfsFree(node); + return 0; +} + +char *vfsFSOName(FSO *fso) { + assert(fso); + if (fso == NULL) { + return NULL; + } + + return ((Node *)fso)->filename; +} + +Stream *vfsFSOOpen(FSO *fso, const char *perm) { + assert(fso); + for (int i = 0; perm[i]; i++) { + if (perm[i] == 'a') { + return NULL; + } + } + + Stream *stream = malloc(sizeof(Stream)); + *stream = (Stream){0}; + stream->node = (Node *)fso; + stream->i = 0; + + return stream; +} + +FSO *vfsRoot() { return (FSO *)vfs.root; } + +FSO *vfsNext(FSO *of) { + assert(of); + Node *node = (Node *)of; + + if (node->sibling == NULL) { + return NULL; + } + + node = node->sibling; + + if (node->filename[0] == '$') { + return vfsNext((FSO *)node); + } + + return (FSO *)node; +} + +FSO *vfsChild(FSO *of) { + assert(of); + Node *node = (Node *)of; + + if (node->child == NULL) { + return NULL; + } + + node = node->child; + + if (node->filename[0] == '$') { + return vfsNext((FSO *)node); + } + + return (FSO *)node; +} diff --git a/src/mock_libc/vfs.h b/src/mock_libc/vfs.h new file mode 100644 index 0000000..a0db928 --- /dev/null +++ b/src/mock_libc/vfs.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +typedef struct Stream Stream; +typedef struct FSO FSO; + +void vfsInit(); +Stream *vfsOpen(const char *path, const char *perm); +void vfsClose(Stream *); +size_t vfsRead(Stream *, void *, size_t); +size_t vfsWrite(Stream *, const void *, size_t); +void vfsSeek(Stream *, size_t pos, size_t whence); +size_t vfsTell(Stream *); +int vfsEof(Stream *); +void vfsHookFlush(Stream *, void *, void (*)(void *, void *, size_t)); +void vfsFlush(Stream *); +bool vfsExists(const char *); +int vfsCreate(const char *); +int vfsRemove(const char *); + +char *vfsFSOName(FSO *fso); +Stream *vfsFSOOpen(FSO *fso, const char *perm); +FSO *vfsRoot(); +FSO *vfsNext(FSO *of); +FSO *vfsChild(FSO *of); diff --git a/src/qoi b/src/qoi new file mode 160000 index 0000000..bf7b41c --- /dev/null +++ b/src/qoi @@ -0,0 +1 @@ +Subproject commit bf7b41c2ff3f24a2031193b62aa76d35e8842b5a diff --git a/src/sys/boot.s b/src/sys/boot.s new file mode 100644 index 0000000..3622f46 --- /dev/null +++ b/src/sys/boot.s @@ -0,0 +1,272 @@ +; This is reused from my previous OS project, not written during this time. + +; ---***--- +; @authors ske +; @created 12/25/2022 +; @note X86 Bootloader. +; ---***--- + +; @note Size of the image in sectors * 63 +%define KBOOT_SIZE_SEC 15 +; @note In protected mode segments become offsets into the GDT. +; This is different from real mode. +%define CODE_SEG gdt_code-gdt_start +%define DATA_SEG gdt_data-gdt_start +%define VESA_MODE_NUBMER 0x118 + +; @note Bootloaders start addressing at 0x7C00. +; The 0x7C00 offset is defined in the linker script not here. +section .boot + +; @note Bootloaders start in 16 bit. +bits 16 + +; @note Export start to the linker +global start + +start: + mov ax, 0x0 + mov ds, ax + mov es, ax + mov ss, ax + mov sp, 0x7C00 + + ; @note Enable A20 to access even Mb's of RAM. + mov ax, 2403h + int 15h + + .loop: + mov ah, 2h + mov al, [sectors] + mov ch, 0 ; cylinder + mov cl, [sector] + mov dh, [head] + mov bx, [osegment] + mov es, bx + mov bx, 0 + int 13h + + mov byte[sectors], 63 + mov byte[sector], 1 + mov bx, [toadd] + add word [osegment], bx + mov word [toadd], 0x7E0 + add byte[head], 1 + add dword [cnt], 1 + cmp dword [cnt], KBOOT_SIZE_SEC + jne .loop + + mov ax, 0x0 + mov ds, ax + mov es, ax + + jmp 0x0000:0x7E00 + +align 4 + +toadd: + dw 0x7C0 +osegment: + dw 0x7E0 +sectors: + db 62 +sector: + db 2 +head: + db 0 +sectors_per_track: + db 0 +cnt: + dd 0 +drive_number: + db 0 + +; @note { +; Limit = ending memory location +; Base = starting memory location +; } +; The base/limit are spread out, +; but they are all very simple integers. +; @note Limit is 20 bit which doens't cover whole 32 bit space, but is fixed +; with the 4 kilobyte granularity flag. +; +; Format: +; Limit (low word): u16 +; Base (low word): u16 +; Base (middle byte): u8 +; Access (present?, privilege ring hi, privilege ring lo, +; is not task segment (code, data)?, is executable?, DC[0], RW[1], +; has been accessed?): u8 +; Limit (high nibble): u4 +; Flags (1 byte or 4kb limit multiplier, protected mode segment?, +; long mode segment?[2], reserved) +; Base (high byte): u8 +gdt_start: + ; @note null entry + dq 0 +gdt_code: + dw 0xFFFF ; Limit + dw 0x0000 ; Base + db 0x00 ; Base + db 10011010b ; Access + db 11001111b ; Flags + Limit + db 0x00 ; Base +gdt_data: + dw 0xFFFF ; Limit + dw 0x0000 ; Base + db 0x00 ; Base + db 10010010b ; Access + db 11001111b ; Flags + Limit + db 0x00 ; Base +gdt_end: + +gdt_desc: + dw gdt_end - gdt_start ; GDT size + dd gdt_start ; GDT address + +%assign space_taken $-$$ +%if space_taken > 510 + %warning Space taken space_taken + %error Bootloader is too big. +%endif + +; @note Fill the rest with zeros to align bootloader magic. +times 510-($-$$) db 0 + +; @note Bootloader magic. +dw 0xAA55 + +load2: + sti + + mov ax, 0x4F00 ; Get VESA hardware information + mov di, vesa_vbe_info ; Move VESA struct to DI + int 0x10 + + + mov ax, 0x4F01 ; Load VESA mode information + mov di, vesa_mode_info_struct + mov cx, VESA_MODE_NUBMER + int 0x10 + + mov ax, 0x4F02 ; Enable VESA mode + mov bx, VESA_MODE_NUBMER + or bx, 0x4000 ; Set some flag + mov di, 0 + int 0x10 + + cli + + ; @note LGDT identity memory map + lgdt [gdt_desc] + + ; @note Enable protected mode by setting first bit in control register 0. + mov eax, cr0 + or eax, 1 + mov cr0, eax + + jmp CODE_SEG:after + +bits 32 + +after: + mov esp, 0xE00000 ; Set the stack pointer + ; @note Setup segments. + mov ax, DATA_SEG + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + +extern kmain + call kmain + cli + hlt + +; [0] (https://wiki.osdev.org/Global_Descriptor_Table) +; DC: Direction bit/Conforming bit. +; For data selectors: Direction bit. If clear (0) the segment grows up. +; If set (1) the segment grows down, ie. the Offset has to be greater than +; the Limit. +; For code selectors: Conforming bit. +; If clear (0) code in this segment can only be executed from the ring set +; in DPL. If set (1) code in this segment can be executed from an equal or +; lower privilege level. For example, code in ring 3 can far-jump to +; conforming code in a ring 2 segment. The DPL field represent the highest +; privilege level that is allowed to execute the segment. For example, code +; in ring 0 cannot far-jump to a conforming code segment where DPL is 2, +; while code in ring 2 and 3 can. Note that thehttps://youtu.be/25zdbdxTgaE privilege level remains the +; same, ie. a far-jump from ring 3 to a segment with a DPL of 2 remains in +; ring 3 after the jump. +; +; [1] (https://wiki.osdev.org/Global_Descriptor_Table) +; RW: Readable bit/Writable bit. +; For code segments: Readable bit. +; If clear (0), read access for this segment is not allowed. +; If set (1) read access is allowed. Write access is never allowed for code +; segments. +; For data segments: Writeable bit. +; If clear (0), write access for this segment is not allowed. +; If set (1) write access is allowed. Read access is always allowed for +; data segments. +; +; [2] Protected mode segment flag (bit 3) must be disabled for long mode segments. + +vesa_vbe_info: + .VbeSignature db 'VESA' ; VBE Signature + .VbeVersion dw 0200h ; VBE Version + .OemStringPtr dd 0 ; Pointer to OEM String + .Capabilities db 4 dup (0) ; Capabilities of graphics controller + .VideoModePtr dd 0 ; Pointer to VideoModeList + .TotalMemory dw 0 ; Number of 64kb memory blocks + ; Added for VBE 2.0 + .OemSoftwareRev dw 0 ; VBE implementation Software revision + .OemVendorNamePtr dd 0 ; Pointer to Vendor Name String + .OemProductNamePtr dd 0 ; Pointer to Product Name String + .OemProductRevPtr dd 0 ; Pointer to Product Revision String + .Reserved db 222 dup (0) ; Reserved for VBE implementation scratch + ; area + .OemData db 256 dup (0) ; Data Area for OEM Strings + +global vesa_mode_info_struct +vesa_mode_info_struct: + .ModeAttributes dw 0 ; 0 mode attributes + .WinAAttributes db 0 ; 2 window A attributes + .WinBAttributes db 0 ; 3 window B attributes + .WinGranularity dw 0 ; 4 window granularity + .WinSize dw 0 ; 6 window size + .WinASegment dw 0 ; 8 window A start segment + .WinBSegment dw 0 ; 10 window B start segment + .WinFuncPtr dd 0 ; 12 pointer to window function + .BytesPerScanLine dw 0 ; 16 bytes per scan line + ; Mandatory information for VBE 1.2 and above + .XResolution dw 0 ; 18 horizontal resolution in pixels or characters + .YResolution dw 0 ; 20 vertical resolution in pixels or characters + .XCharSize db 0 ; 21 character cell width in pixels + .YCharSize db 0 ; 22 character cell height in pixels + .NumberOfPlanes db 0 ; number of memory planes + .BitsPerPixel db 0 ; bits per pixel + .NumberOfBanks db 0 ; number of banks + .MemoryModel db 0 ; memory model type + .BankSize db 0 ; bank size in KB + .NumberOfImagePages db 0 ; number of images + .Reserved db 1 ; reserved for page function + ; Direct Color fields (required for direct/6 and YUV/7 memory models) + .RedMaskSize db 0 ; size of direct color red mask in bits + .RedFieldPosition db 0 ; bit position of lsb of red mask + .GreenMaskSize db 0 ; size of direct color green mask in bits + .GreenFieldPosition db 0 ; bit position of lsb of green mask + .BlueMaskSize db 0 ; size of direct color blue mask in bits + .BlueFieldPosition db 0 ; bit position of lsb of blue mask + .RsvdMaskSize db 0 ; size of direct color reserved mask in bits + .RsvdFieldPosition db 0 ; bit position of lsb of reserved mask + .DirectColorModeInfo db 0 ; direct color mode attributes + ; Mandatory information for VBE 2.0 and above + .PhysBasePtr dd 0 ; physical address for flat memory frame buffer + .OffScreenMemOffset dd 0 ; pointer to start of off screen memory + .OffScreenMemSize dw 0 ; amount of off screen memory in 1k + resb 206 + +%include "src/sys/lib.s" +%include "src/sys/irq.s" diff --git a/src/sys/font8x8.c b/src/sys/font8x8.c new file mode 100644 index 0000000..0185040 --- /dev/null +++ b/src/sys/font8x8.c @@ -0,0 +1,153 @@ +/** + * 8x8 monochrome bitmap fonts for rendering + * Author: Daniel Hepper + * + * License: Public Domain + * + * Based on: + * // Summary: font8x8.h + * // 8x8 monochrome bitmap fonts for rendering + * // + * // Author: + * // Marcel Sondaar + * // International Business Machines (public domain VGA fonts) + * // + * // License: + * // Public Domain + * + * Fetched from: + *http://dimensionalrift.homelinux.net/combuster/mos3/?p=viewsource&file=/modules/gfx/font8_8.asm + **/ + +// Constant: font8x8_basic +// Contains an 8x8 font map for unicode points U+0000 - U+007F (basic latin) +char font8x8_basic[128][8] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + {0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + {0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + {0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + {0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + {0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + {0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + {0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + {0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + {0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + {0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + {0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + {0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + {0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + {0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + {0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + {0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + {0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + {0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + {0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + {0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + {0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + {0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + {0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + {0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + {0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + {0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + {0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + {0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + {0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + {0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + {0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + {0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + {0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + {0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + {0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + {0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + {0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + {0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + {0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + {0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + {0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + {0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + {0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + {0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + {0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + {0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + {0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + {0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + {0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + {0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + {0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + {0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + {0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + {0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + {0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + {0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + {0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + {0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + {0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + {0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + {0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + {0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + {0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + {0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + {0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + {0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + {0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + {0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + {0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + {0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + {0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + {0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + {0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + {0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + {0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + {0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + {0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + {0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + {0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + {0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + {0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + {0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + {0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + {0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + {0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + {0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + {0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // U+007F +}; \ No newline at end of file diff --git a/src/sys/gfx.c b/src/sys/gfx.c new file mode 100644 index 0000000..16e76da --- /dev/null +++ b/src/sys/gfx.c @@ -0,0 +1,340 @@ +#define QOI_IMPLEMENTATION +#include "qoi.h" +#include +#include + +struct { + char whatever1[18]; + uint16_t width; + uint16_t height; + char whatever2[18]; + uint8_t *pixels; +} __attribute__((packed)) typedef VesaModeInfo; + +struct { + uint32_t width, height; + uint8_t *pixels; +} typedef VesaBlitInfo; + +extern VesaModeInfo vesa_mode_info_struct; + +VesaBlitInfo vesaLoadBlitInfo() { + return (VesaBlitInfo){vesa_mode_info_struct.width, + vesa_mode_info_struct.height, + vesa_mode_info_struct.pixels}; +} + +#define FNV_32_INIT ((uint32_t)0x811c9dc5) +uint32_t fnv32buf(void *buf, size_t len, uint32_t hval) { + unsigned char *bp = (unsigned char *)buf; + unsigned char *be = bp + len; + while (bp < be) { + hval ^= (uint32_t)*bp++; + hval += + (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); + } + return hval; +} + +#define CELL_SIZE 16 +#define CELLS_X 64 +#define CELLS_Y 48 + +uint32_t hashes1[CELLS_X * CELLS_Y]; +uint32_t hashes2[CELLS_X * CELLS_Y]; + +enum { + CMD_DRAW_BCKG = 0, + CMD_DRAW_ICON = 1, + CMD_DRAW_CHAR = 2, + CMD_DRAW_RECT = 3, + CMD_DRAW_CLIP = 4 +}; + +struct Command { + int type; + char data; + uint32_t color; + int x, y, w, h; +}; + +static struct Command *commands; +static int command_count = 0; + +//------------------------------------------------------------------------------ + +static qoi_desc icon_desc; +static uint32_t *icon_data; +static qoi_desc background_desc; +static uint32_t *background_data; +static uint32_t *backbuffer; +static uint32_t color = 0xFFFFFF; +static int cx, cy, cw, ch; + +void gfxInit() { + commands = malloc(4096 * sizeof(struct Command)); + + backbuffer = + malloc(vesa_mode_info_struct.width * vesa_mode_info_struct.height * 4); + + memset(hashes1, 0, sizeof(hashes1)); + memset(hashes2, 1, sizeof(hashes2)); + + icon_data = qoi_read("res/icons.qoi", &icon_desc, 0); + background_data = qoi_read("res/background.qoi", &background_desc, 0); + + // Swap red and blue + for (int i = 0; i < background_desc.width * background_desc.height; i++) { + uint32_t c = background_data[i]; + background_data[i] = + (c & 0xFF00FF00) | ((c & 0xFF) << 16) | ((c & 0xFF0000) >> 16); + } +} + +void rectIntersect(int x1, int y1, int w1, int h1, int x2, int y2, int w2, + int h2, int *x, int *y, int *w, int *h) { + *x = x1 > x2 ? x1 : x2; + *y = y1 > y2 ? y1 : y2; + *w = (x1 + w1 < x2 + w2 ? x1 + w1 : x2 + w2) - *x; + *h = (y1 + h1 < y2 + h2 ? y1 + h1 : y2 + h2) - *y; +} + +void gfxSetPixel(int x, int y, uint32_t color) { + if (x < cx || y < cy || x >= (cx + cw) || y >= (cy + ch)) + return; + + backbuffer[x + y * vesa_mode_info_struct.width] = color; +} + +void gfxDoDrawIcon(int px, int py, char c) { + for (int x = 0; x < 12; x++) { + for (int y = 0; y < 16; y++) { + int ix = x + c * 12; + int iy = y; + + uint32_t ccolor = icon_data[ix + iy * icon_desc.width]; + if (ccolor & 0xFF) + gfxSetPixel(px + x, py + y, ccolor & color); + } + } +} + +extern char font8x8_basic[128][8]; +void gfxDoDrawChar(int x, int y, char c) { + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + if (font8x8_basic[c][i] & (1 << j)) { + gfxSetPixel(x + j, y + i, color); + } + } + } +} + +void gfxDoDrawRect(int xs, int ys, int ws, int hs) { + int x, y, w, h; + + rectIntersect(cx, cy, cw, ch, xs, ys, ws, hs, &x, &y, &w, &h); + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + backbuffer[(x + j) + (y + i) * vesa_mode_info_struct.width] = color; + } + } +} + +bool rectOverlap(int x1, int y1, int w1, int h1, int x2, int y2, int w2, + int h2) { + return x1 < x2 + w2 && x1 + w1 > x2 && y1 < y2 + h2 && y1 + h1 > y2; +} + +void gfxRedrawRegion(int x, int y, int w, int h) { + cx = 0; + cy = 0; + cw = vesa_mode_info_struct.width; + ch = vesa_mode_info_struct.height; + + for (int i = 0; i < command_count; i++) { + struct Command cmd = commands[i]; + if (!rectOverlap(x, y, w, h, cmd.x, cmd.y, cmd.w, cmd.h)) + continue; + color = cmd.color; + int x1, y1, w1, h1; + switch (cmd.type) { + case CMD_DRAW_BCKG: + rectIntersect(x, y, w, h, cmd.x, cmd.y, cmd.w, cmd.h, &x1, &y1, &w1, &h1); + + for (int y = 0; y < h1; y++) { + int texY = y + y1 - cmd.y; + if (texY < 0 || texY >= background_desc.height) + continue; + + memcpy(backbuffer + (x1 + (y1 + y) * vesa_mode_info_struct.width), + background_data + ((x1 - cmd.x) + texY * background_desc.width), + w1 * 4); + } + break; + case CMD_DRAW_ICON: + gfxDoDrawIcon(cmd.x, cmd.y, cmd.data); + break; + case CMD_DRAW_CHAR: + gfxDoDrawChar(cmd.x, cmd.y, cmd.data); + break; + case CMD_DRAW_RECT: + rectIntersect(x, y, w, h, cmd.x, cmd.y, cmd.w, cmd.h, &x1, &y1, &w1, &h1); + gfxDoDrawRect(x1, y1, w1, h1); + break; + case CMD_DRAW_CLIP: + cx = cmd.x; + cy = cmd.y; + cw = cmd.w; + ch = cmd.h; + break; + } + } + + int px = x; + int py = y; + + // copy + VesaBlitInfo vesa_blit_info = vesaLoadBlitInfo(); + for (int y = py; y < py + h; y++) { + for (int x = px; x < px + w; x++) { + vesa_blit_info.pixels[(x + y * vesa_blit_info.width) * 3] = + backbuffer[x + y * vesa_blit_info.width]; + vesa_blit_info.pixels[(x + y * vesa_blit_info.width) * 3 + 1] = + backbuffer[x + y * vesa_blit_info.width] >> 8; + vesa_blit_info.pixels[(x + y * vesa_blit_info.width) * 3 + 2] = + backbuffer[x + y * vesa_blit_info.width] >> 16; + } + } +} + +void panic(const char *, ...); +extern bool gfxEnable; + +void gfxSwap() { + for (int i = 0; i < command_count; i++) { + uint32_t cmdhash = FNV_32_INIT; + struct Command cmd = commands[i]; + cmdhash = fnv32buf(&cmd, sizeof(cmd), FNV_32_INIT); + + int x1 = cmd.x / CELL_SIZE; + int y1 = cmd.y / CELL_SIZE; + int x2 = (cmd.x + cmd.w) / CELL_SIZE; + int y2 = (cmd.y + cmd.h) / CELL_SIZE; + + if (x1 < 0) + x1 = 0; + if (y1 < 0) + y1 = 0; + if (x1 >= CELLS_X) + x1 = CELLS_X - 1; + if (y1 >= CELLS_Y) + y1 = CELLS_Y - 1; + + if (x2 < 0) + x2 = 0; + if (y2 < 0) + y2 = 0; + if (x2 >= CELLS_X) + x2 = CELLS_X - 1; + if (y2 >= CELLS_Y) + y2 = CELLS_Y - 1; + + for (int x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y++) { + hashes1[x + y * CELLS_X] = + fnv32buf(&cmdhash, sizeof(cmdhash), hashes1[x + y * CELLS_X]); + } + } + } + + for (int y = 0; y < CELLS_Y; y++) { + for (int x = 0; x < CELLS_X; x++) { + int start = x; + while (x < CELLS_X && + hashes1[x + y * CELLS_X] != hashes2[x + y * CELLS_X]) + x++; + + if (start != x) { + gfxRedrawRegion(start * CELL_SIZE, y * CELL_SIZE, + CELL_SIZE * (x - start), CELL_SIZE); + } + } + } + + command_count = 0; + + for (int i = 0; i < CELLS_X * CELLS_Y; i++) { + hashes2[i] = hashes1[i]; + hashes1[i] = FNV_32_INIT; + } +} + +void gfxDrawBackground() { + if (command_count >= 4096) { + return; + } + + commands[command_count++] = (struct Command){ + CMD_DRAW_BCKG, + 0, + 0xFFFFFFFF, + (vesa_mode_info_struct.width - background_desc.width) / 2, + (vesa_mode_info_struct.height - background_desc.height) / 2, + background_desc.width, + background_desc.height}; +} + +void gfxSetColor(int r, int g, int b, int a) { + color = (r << 16) | (g << 8) | b; +} + +void gfxDrawIcon(int px, int py, char c) { + if (command_count >= 4096) { + return; + } + + commands[command_count++] = (struct Command){ + CMD_DRAW_ICON, c, color, px, py, 12, 16, + }; +} + +void gfxDrawChar(int x, int y, char c) { + if (command_count >= 4096) { + return; + } + + commands[command_count++] = (struct Command){ + CMD_DRAW_CHAR, c, color, x, y, 8, 8, + }; +} + +void gfxDrawRect(int x, int y, int w, int h) { + if (command_count >= 4096) { + return; + } + + commands[command_count++] = (struct Command){ + CMD_DRAW_RECT, 0, color, x, y, w, h, + }; +} + +void gfxDrawClip(int x, int y, int w, int h) { + if (command_count >= 4096) { + return; + } + + int x1, y1, w1, h1; + rectIntersect(x, y, w, h, 0, 0, vesa_mode_info_struct.width, + vesa_mode_info_struct.height, &x1, &y1, &w1, &h1); + + commands[command_count++] = (struct Command){ + CMD_DRAW_CLIP, 0, 0, x1, y1, w1, h1, + }; +} + +void gfxDims(int *w, int *h) { + *w = vesa_mode_info_struct.width; + *h = vesa_mode_info_struct.height; +} diff --git a/src/sys/gfx.h b/src/sys/gfx.h new file mode 100644 index 0000000..58b4687 --- /dev/null +++ b/src/sys/gfx.h @@ -0,0 +1,13 @@ +#pragma once + +void gfxInit(); +void gfxSwap(); + +void gfxSetColor(int r, int g, int b, int a); + +void gfxDrawBackground(); +void gfxDrawIcon(int x, int y, char c); +void gfxDrawChar(int x, int y, char c); +void gfxDrawRect(int x, int y, int w, int h); +void gfxDrawClip(int x, int y, int w, int h); +void gfxDims(int *w, int *h); \ No newline at end of file diff --git a/src/sys/irq.s b/src/sys/irq.s new file mode 100644 index 0000000..a1ea51b --- /dev/null +++ b/src/sys/irq.s @@ -0,0 +1,25 @@ +; ---***--- +; @authors ske +; @created 7/1/2024 +; @note IRQ wrappers. +; ---***--- + +section .text + +align 4 +extern handleIrq1 +global irq1 +irq1: + pushad + call handleIrq1 + popad + iret + +align 4 +extern handleIrq12 +global irq12 +irq12: + pushad + call handleIrq12 + popad + iret diff --git a/src/sys/lib.s b/src/sys/lib.s new file mode 100644 index 0000000..6ab4a20 --- /dev/null +++ b/src/sys/lib.s @@ -0,0 +1,33 @@ +; ---***--- +; @authors ske +; @created 7/1/2024 +; @note Assembly-implemented functions. +; ---***--- + +section .text + +; NOTE: Doesn't save floating point registers. +global setjmp +setjmp: + mov ecx, [esp+4] + mov [ecx], ebp + mov [ecx+4], ebx + mov [ecx+8], edi + mov [ecx+12], esi + mov [ecx+16], esp + mov eax, [esp] + mov [ecx+20], eax + xor eax, eax + ret + +global longjmp +longjmp: + mov ecx, [esp+4] + mov eax, [esp+8] + mov ebp, [ecx] + mov ebx, [ecx+4] + mov edi, [ecx+8] + mov esi, [ecx+12] + mov esp, [ecx+16] + add esp, 4 + jmp [ecx+20] \ No newline at end of file diff --git a/src/sys/link.ld b/src/sys/link.ld new file mode 100644 index 0000000..ec03c8d --- /dev/null +++ b/src/sys/link.ld @@ -0,0 +1,22 @@ +ENTRY(start) +SECTIONS { + . = 0x7C00; + + .text : + { + *(.boot) + *(.text) + } + .data : + { + *(.data) + } + .rodata : + { + *(.rodata) + } + .bss : + { + *(.bss) + } +} \ No newline at end of file diff --git a/src/sys/main.c b/src/sys/main.c new file mode 100644 index 0000000..fb26651 --- /dev/null +++ b/src/sys/main.c @@ -0,0 +1,66 @@ +#include "umka.h" +#include "vfs.h" +#include +#include +#include + +extern char _binary_bin_fs_bin_start[]; +extern char _binary_bin_fs_bin_end[]; + +char *loadVfsImage(char *p, char *path) { + char temp[512]; + int l; + Stream *s; + + while (p < _binary_bin_fs_bin_end) { + switch (*p) { + case 'p': + return p + 1; + case 'd': + p++; + l = strlen(p); + sprintf(temp, "%s/%s", path, p); + vfsCreate(temp); + p = loadVfsImage(p + l + 1, temp); + break; + case 'f': + p++; + l = strlen(p); + sprintf(temp, "%s/%s", path, p); + vfsCreate(temp); + p += l + 1; + uint32_t size = *(uint32_t *)(p); + p += 4; + s = vfsOpen(temp, "wb"); + vfsWrite(s, p, size); + vfsClose(s); + p += size; + break; + default: + asm("cli\nhlt"); + } + } + + return p; +} + +extern void irq1(); +extern void irq12(); +void handleIrq1() { umkaRuntimeHandleIRQ(1); } +void handleIrq12() { umkaRuntimeHandleIRQ(12); } + +extern void kmain() { + rtlInit((void *)0x1000000); + loadVfsImage(_binary_bin_fs_bin_start, ""); + umkaRuntimeCompile("main.um"); + umkaRuntimeRegister("irq1", irq1); + umkaRuntimeRegister("irq12", irq12); + umkaRuntimeInit(); + umkaRuntimeSchedule(); + asm("sti"); + + while (1) { + asm("hlt"); + umkaRuntimeSchedule(); + } +} \ No newline at end of file diff --git a/src/sys/panic.c b/src/sys/panic.c new file mode 100644 index 0000000..c2b073c --- /dev/null +++ b/src/sys/panic.c @@ -0,0 +1,41 @@ +#include "gfx.h" +#include + +void panic(const char *msg, ...) { + asm("cli"); + + char buf[4096]; + va_list args; + va_start(args, msg); + buf[0] = 'O'; + buf[1] = 'o'; + buf[2] = 'p'; + buf[3] = 's'; + buf[4] = '!'; + buf[5] = '\n'; + vsnprintf(buf + 6, 4090, msg, args); + va_end(args); + + int col = 0; + int row = 0; + int w, h; + gfxDims(&w, &h); + + gfxSetColor(0, 0, 0, 255); + gfxDrawRect(0, 0, w, h); + + gfxSetColor(255, 0, 0, 255); + for (int i = 0; buf[i]; i++) { + if (buf[i] == '\n') { + col = 0; + row++; + } else { + gfxDrawChar(col * 8, row * 10, buf[i]); + col++; + } + } + + gfxSwap(); + + asm("hlt"); +} \ No newline at end of file diff --git a/src/sys/umka.c b/src/sys/umka.c new file mode 100644 index 0000000..a6d8cd5 --- /dev/null +++ b/src/sys/umka.c @@ -0,0 +1,371 @@ +#include "umka.h" +#include "gfx.h" +#include "umka_api.h" +#include "vfs.h" +#include +#include + +void panic(const char *fmt, ...); + +static void *umka; +enum { TYPE_UINT8, TYPE_UINT16, TYPE_UINT32, TYPE_UINT8ARRAY }; +static void *umkaTypes[32]; +static int umkaRegisterFn; +static int umkaInitFn; +static int umkaIRQFn; +static int umkaScheduleFn; +static _Atomic int umkaLock = 0; + +void umkaPrintError() { + panic("Error: %s:%d %s\n", umkaGetError(umka)->fileName, + umkaGetError(umka)->line, umkaGetError(umka)->msg); +} + +void warning(UmkaError *error) { + panic("Warning: %s:%d %s\n", error->fileName, error->line, error->msg); +} + +//////////////////////////////////////////////////////////////////////////////// + +static void api__panic(UmkaStackSlot *p, UmkaStackSlot *r) { + panic("(Umka) %s\n", (char *)p[0].ptrVal); +} + +static void api__asm_in8(UmkaStackSlot *p, UmkaStackSlot *r) { + uint8_t result; + uint16_t port = p[0].uintVal; + asm volatile("inb %1, %0" : "=a"(result) : "Nd"(port)); + + r->uintVal = result; +} + +static void api__asm_out8(UmkaStackSlot *p, UmkaStackSlot *r) { + uint8_t val = p[0].uintVal; + uint16_t port = p[1].uintVal; + + asm volatile("outb %0, %1" : : "a"(val), "Nd"(port)); +} + +static void api__asm_lidt(UmkaStackSlot *p, UmkaStackSlot *r) { + uint16_t limit = p[0].uintVal; + uint32_t base = p[1].uintVal; + + struct { + uint16_t limit; + uint32_t base; + } __attribute__((packed)) idtr = {limit, base}; + + asm volatile("lidt %0" : : "m"(idtr)); +} + +static void api__asm_cli(UmkaStackSlot *p, UmkaStackSlot *r) { asm("cli"); } +static void api__asm_sti(UmkaStackSlot *p, UmkaStackSlot *r) { asm("sti"); } + +static void api__setupTypePtr(UmkaStackSlot *p, UmkaStackSlot *r) { + umkaTypes[p[1].uintVal] = p[0].ptrVal; +} + +static void api__getaddr(UmkaStackSlot *p, UmkaStackSlot *r) { + r->uintVal = p[0].uintVal; +} + +static void api__getptr(UmkaStackSlot *p, UmkaStackSlot *r) { + r->ptrVal = p[0].ptrVal; +} + +static void api__writes8(UmkaStackSlot *p, UmkaStackSlot *r) { + memcpy(p[0].ptrVal, ((UmkaDynArray(uint8_t) *)(&p[1]))->data, + umkaGetDynArrayLen(&p[1])); +} + +static void api__reads8(UmkaStackSlot *p, UmkaStackSlot *r) { + size_t len = p[0].uintVal; + UmkaDynArray(uint8_t) *dest = + umkaAllocData(umka, sizeof(UmkaDynArray(uint8_t)), NULL); + + umkaMakeDynArray(umka, dest, umkaTypes[TYPE_UINT8ARRAY], len); + memcpy(dest->data, p[1].ptrVal, len); + + r->ptrVal = dest; +} + +static void api__reads(UmkaStackSlot *p, UmkaStackSlot *r) { + size_t len = p[0].uintVal; + static int i = 0; + + char *str = malloc(len + 1); + memcpy(str, p[1].ptrVal, len); + str[len] = '\0'; + + r->ptrVal = umkaMakeStr(umka, str); + free(str); +} + +static void api__gfxSetColor(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxSetColor(p[3].intVal, p[2].intVal, p[1].intVal, p[0].intVal); +} + +static void api__gfxDrawBackground(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxDrawBackground(); +} + +static void api__gfxDrawRect(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxDrawRect(p[3].intVal, p[2].intVal, p[1].intVal, p[0].intVal); +} + +static void api__gfxDrawClip(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxDrawClip(p[3].intVal, p[2].intVal, p[1].intVal, p[0].intVal); +} + +static void api__gfxDrawIcon(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxDrawIcon(p[2].intVal, p[1].intVal, p[0].intVal); +} + +static void api__gfxDrawChar(UmkaStackSlot *p, UmkaStackSlot *r) { + gfxDrawChar(p[2].intVal, p[1].intVal, p[0].intVal); +} + +static void api__gfxDims(UmkaStackSlot *p, UmkaStackSlot *r) { + int w, h; + gfxDims(&w, &h); + ((UmkaStackSlot *)p[1].ptrVal)->intVal = w; + ((UmkaStackSlot *)p[0].ptrVal)->intVal = h; +} + +static void api__gfxSwap(UmkaStackSlot *p, UmkaStackSlot *r) { gfxSwap(); } + +static void umka__flush(void *data, void *buf, size_t size) { + UmkaStackSlot slots[2] = {0}; + slots[1].ptrVal = buf; + slots[0].uintVal = size; + + umkaCall(umka, (size_t)data, 2, slots, NULL); + if (!umkaAlive(umka)) { + umkaPrintError(); + } +} + +static void api__testIndirect(UmkaStackSlot *p, UmkaStackSlot *r) { + umkaCall(umka, p[0].intVal, 0, NULL, NULL); +} + +static void api__vfsHookFlush(UmkaStackSlot *p, UmkaStackSlot *r) { + vfsHookFlush(p[2].ptrVal, p[0].ptrVal, umka__flush); +} + +static void api__vfsRoot(UmkaStackSlot *p, UmkaStackSlot *r) { + r->uintVal = (size_t)vfsRoot(); +} + +static void api__vfsNext(UmkaStackSlot *p, UmkaStackSlot *r) { + r->uintVal = (size_t)vfsNext((FSO *)p[0].ptrVal); +} + +static void api__vfsChild(UmkaStackSlot *p, UmkaStackSlot *r) { + r->uintVal = (size_t)vfsChild((FSO *)p[0].ptrVal); +} + +static void api__vfsFSOName(UmkaStackSlot *p, UmkaStackSlot *r) { + r->ptrVal = umkaMakeStr(umka, vfsFSOName((FSO *)p[0].ptrVal)); +} + +static void api__vfsFSOOpen(UmkaStackSlot *p, UmkaStackSlot *r) { + r->ptrVal = vfsFSOName((FSO *)p[0].ptrVal); +} + +//////////////////////////////////////////////////////////////////////////////// + +void umkaRuntimeCompile(const char *path) { + umka = umkaAlloc(); + if (!umka) { + panic("Failed to allocate Umka context."); + } + + if (!umkaInit(umka, path, NULL, 1024 * 1024, NULL, 0, NULL, true, false, + warning)) { + umkaPrintError(); + } + + gfxInit(); + + umkaAddFunc(umka, "api__panic", api__panic); + + umkaAddFunc(umka, "api__asm_in8", api__asm_in8); + umkaAddFunc(umka, "api__asm_out8", api__asm_out8); + umkaAddFunc(umka, "api__asm_lidt", api__asm_lidt); + umkaAddFunc(umka, "api__asm_cli", api__asm_cli); + umkaAddFunc(umka, "api__asm_sti", api__asm_sti); + + umkaAddFunc(umka, "api__setupTypePtr", api__setupTypePtr); + umkaAddFunc(umka, "api__getaddr", api__getaddr); + umkaAddFunc(umka, "api__getptr", api__getptr); + umkaAddFunc(umka, "api__writes8", api__writes8); + umkaAddFunc(umka, "api__reads8", api__reads8); + umkaAddFunc(umka, "api__reads", api__reads); + + umkaAddFunc(umka, "api__gfxSetColor", api__gfxSetColor); + umkaAddFunc(umka, "api__gfxDrawRect", api__gfxDrawRect); + umkaAddFunc(umka, "api__gfxDrawClip", api__gfxDrawClip); + umkaAddFunc(umka, "api__gfxDrawChar", api__gfxDrawChar); + umkaAddFunc(umka, "api__gfxDrawIcon", api__gfxDrawIcon); + umkaAddFunc(umka, "api__gfxDrawBackground", api__gfxDrawBackground); + umkaAddFunc(umka, "api__gfxDims", api__gfxDims); + umkaAddFunc(umka, "api__gfxSwap", api__gfxSwap); + + umkaAddFunc(umka, "api__vfsRoot", api__vfsRoot); + umkaAddFunc(umka, "api__vfsNext", api__vfsNext); + umkaAddFunc(umka, "api__vfsChild", api__vfsChild); + umkaAddFunc(umka, "api__vfsFSOName", api__vfsFSOName); + umkaAddFunc(umka, "api__vfsFSOOpen", api__vfsFSOOpen); + umkaAddFunc(umka, "api__vfsHookFlush", api__vfsHookFlush); + umkaAddFunc(umka, "api__testIndirect", api__testIndirect); + + if (!umkaCompile(umka)) { + umkaPrintError(); + } + + umkaRegisterFn = umkaGetFunc(umka, NULL, "system__register"); + if (umkaRegisterFn == -1) { + panic("Function 'system__register' not found."); + } + + umkaInitFn = umkaGetFunc(umka, NULL, "system__init"); + if (umkaInitFn == -1) { + panic("Function 'system__init' not found."); + } + + umkaIRQFn = umkaGetFunc(umka, NULL, "system__irq"); + if (umkaIRQFn == -1) { + panic("Function 'system__irq' not found."); + } + + umkaScheduleFn = umkaGetFunc(umka, NULL, "system__schedule"); + if (umkaScheduleFn == -1) { + panic("Function 'system__schedule' not found."); + } +} + +void umkaRuntimeRegister(const char *name, void *addr) { + UmkaStackSlot params[2]; + params[1].ptrVal = umkaMakeStr(umka, name); + params[0].ptrVal = addr; + + umkaCall(umka, umkaRegisterFn, 2, params, NULL); + if (!umkaAlive(umka)) { + umkaPrintError(); + } +} + +void umkaRuntimeInit() { + umkaCall(umka, umkaInitFn, 0, NULL, NULL); + if (!umkaAlive(umka)) { + umkaPrintError(); + } +} + +static inline void out8(uint16_t port, uint8_t data) { + asm volatile("outb %0, %1" : : "a"(data), "d"(port)); +} + +static inline uint8_t in8(uint16_t port) { + uint8_t data; + asm volatile("inb %1, %0" : "=a"(data) : "d"(port)); + return data; +} + +struct { + int irq; + uint8_t bytes[128]; + size_t length; +} typedef Packet; + +void packetAddByte(Packet *packet, uint8_t byte) { + if (packet->length < sizeof(packet->bytes)) { + packet->bytes[packet->length++] = byte; + } else { + panic("Packet overflow."); + } +} + +static Packet packets[128]; +static size_t packet_count; + +Packet *makePacket(int irq) { + Packet packet; + + packet.irq = irq; + packet.length = 0; + + if (packet_count >= sizeof(packets) / sizeof(packets[0])) { + panic("Packet queue overflow."); + } + + packets[packet_count++] = packet; + + return &packets[packet_count - 1]; +} + +void umkaRuntimeHandleIRQ(uint8_t irq) { + // Umka does not handle the IRQ by itself, instead, it will be delayed, + // this is because Umka can't handle being ran on multiple threads so far. + if (umkaLock) + goto end; + + if (irq == 12 || irq == 1) { + umkaLock = 1; + Packet *p = makePacket(irq); + + while ((in8(0x64) & 1) == 1) { + packetAddByte(p, in8(0x60)); + } + umkaLock = 0; + + goto irqend; + } + +end: + while ((in8(0x64) & 1) == 1) { + in8(0x60); + } + +irqend: + if (irq >= 8) + out8(0xA0, 0x20); + out8(0x20, 0x20); +} + +void umkaRuntimeSchedule() { + if (umkaLock) + return; + + umkaLock = 1; + Packet lpackets[128]; + size_t lpacket_count = packet_count; + memcpy(lpackets, packets, sizeof(packets)); + packet_count = 0; + umkaLock = 0; + + for (size_t i = 0; i < lpacket_count; i++) { + Packet packet = lpackets[i]; + + if (packet.length == 0) { + continue; + } + + UmkaStackSlot p[3] = {0}; + p[2].uintVal = packet.irq; + p[1].ptrVal = packet.bytes; + p[0].uintVal = packet.length; + + umkaCall(umka, umkaIRQFn, 3, p, NULL); + + if (!umkaAlive(umka)) { + umkaPrintError(); + } + } + + umkaCall(umka, umkaScheduleFn, 0, NULL, NULL); + if (!umkaAlive(umka)) { + umkaPrintError(); + } +} diff --git a/src/sys/umka.h b/src/sys/umka.h new file mode 100644 index 0000000..bcc22dc --- /dev/null +++ b/src/sys/umka.h @@ -0,0 +1,7 @@ +void umkaRuntimeCompile(const char *path); + +// System functions. +void umkaRuntimeRegister(const char *symbol, void *addr); +void umkaRuntimeInit(); +void umkaRuntimeHandleIRQ(unsigned char irq); +void umkaRuntimeSchedule(); diff --git a/src/umka b/src/umka new file mode 160000 index 0000000..796fd74 --- /dev/null +++ b/src/umka @@ -0,0 +1 @@ +Subproject commit 796fd74f37c726cf81c12e263269f0b816a248c0 diff --git a/src/umka.c b/src/umka.c new file mode 100644 index 0000000..a08a91a --- /dev/null +++ b/src/umka.c @@ -0,0 +1,15 @@ +#include "umka_api.c" +#include "umka_common.c" +#include "umka_compiler.c" +#include "umka_const.c" +#include "umka_decl.c" +#include "umka_expr.c" +#include "umka_gen.c" +#include "umka_ident.c" +#include "umka_lexer.c" +#include "umka_runtime.c" +#include "umka_stmt.c" +#define spelling spellingtypes +#include "umka_types.c" +#undef spelling +#include "umka_vm.c" \ No newline at end of file diff --git a/src/unity.c b/src/unity.c new file mode 100644 index 0000000..8956169 --- /dev/null +++ b/src/unity.c @@ -0,0 +1,16 @@ +// clang-format off +#include "mock_libc/mock_libc.c" +#include "mock_libc/fmt.c" +#define Node ALLOC_NODE +#include "mock_libc/alloc.c" +#undef Node +#define Node VFS_NODE +#include "mock_libc/vfs.c" +#undef Node +#include "mock_libc/arith.c" +#include "sys/main.c" +#include "sys/font8x8.c" +#include "sys/gfx.c" +#include "sys/umka.c" +#include "sys/panic.c" +// #include "sys/idt.c" diff --git a/test/compile_flags.txt b/test/compile_flags.txt new file mode 100644 index 0000000..951ae16 --- /dev/null +++ b/test/compile_flags.txt @@ -0,0 +1,2 @@ +-I../src/mock_libc +-xc \ No newline at end of file diff --git a/test/integr.bat b/test/integr.bat new file mode 100644 index 0000000..dcce028 --- /dev/null +++ b/test/integr.bat @@ -0,0 +1,10 @@ +@echo off + +set files=../src/umka/src/umka_api.c ../src/umka/src/umka_common.c ../src/umka/src/umka_compiler.c ../src/umka/src/umka_const.c ../src/umka/src/umka_decl.c ../src/umka/src/umka_expr.c ../src/umka/src/umka_gen.c ../src/umka/src/umka_ident.c ../src/umka/src/umka_lexer.c ../src/umka/src/umka_runtime.c ../src/umka/src/umka_stmt.c ../src/umka/src/umka_types.c ../src/umka/src/umka_vm.c +lib /def:jmp.def /out:jmp.lib +cl /I ../src/mock_libc/ integr.c %files% /Z7 ../src/mock_libc/*.c jmp.lib user32.lib kernel32.lib /I ../src /DUMKA_BUILD /GS- /Gs90000000 /Fe:integr.exe /link /STACK:0x64000000,0x64000000 /NODEFAULTLIB /SUBSYSTEM:CONSOLE +integr.exe +rem del integr.exe +del *.obj +del *.exp +del *.lib \ No newline at end of file diff --git a/test/integr.c b/test/integr.c new file mode 100644 index 0000000..0066ab3 --- /dev/null +++ b/test/integr.c @@ -0,0 +1,61 @@ +#include "../src/umka/src/umka_api.h" +#include "mock_libc.h" +#include "vfs.h" +#define WIN32_LEAN_AND_MEAN +#include + +// why microsoft +int _fltused; + +void outputdbg(void *data, void *buf, size_t size) { + WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), buf, size, NULL, NULL); +} + +const char source[] = "import \"std.um\"\n" + "fn indirect() { printf(\"Hello umka!\\n\"); }\n" + "fn test(f: fn())\n" + "fn init*() { test(indirect); }\n"; + +void testextern(UmkaStackSlot *p, UmkaStackSlot *r) { + fprintf(stderr, "OK enter\n"); + umkaCall(umkaGetAPI(r->ptrVal), p[0].intVal, 0, NULL, NULL); + fprintf(stderr, "OK leave\n"); +} + +int __stdcall mainCRTStartup() { + rtlInit(VirtualAlloc(0, 1 << 26, MEM_COMMIT, PAGE_READWRITE)); + vfsHookFlush(stdout, NULL, outputdbg); + vfsHookFlush(stderr, NULL, outputdbg); + FILE *s = fopen("main.um", "wb"); + fwrite(source, strlen(source) + 1, 1, s); + fclose(s); + + void *umka = umkaAlloc(); + + bool umkaOk = umkaInit(umka, "main.um", NULL, 1024 * 1024, NULL, 0, NULL, + true, false, NULL); + + umkaAddFunc(umka, "test", testextern); + + if (!umkaOk) { + fprintf(stderr, "umkaInit() failed: %s\n", umkaGetError(umka)->msg); + return 1; + } + + if (!umkaCompile(umka)) { + fprintf(stderr, "umkaCompile() failed: %s\n", umkaGetError(umka)->msg); + return 1; + } + + fprintf(stdout, "%s", umkaAsm(umka)); + + umkaCall(umka, umkaGetFunc(umka, NULL, "init"), 0, NULL, NULL); + fprintf(stderr, "OK exit\n"); + + if (umkaRun(umka) != 0) { + fprintf(stderr, "umkaRun() failed: %s\n", umkaGetError(umka)->msg); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/test/jmp.def b/test/jmp.def new file mode 100644 index 0000000..5023bfa --- /dev/null +++ b/test/jmp.def @@ -0,0 +1,4 @@ +LIBRARY msvcrt.dll +EXPORTS + setjmp = setjmp + longjmp = longjmp \ No newline at end of file diff --git a/test/testalloc.bat b/test/testalloc.bat new file mode 100644 index 0000000..480e21a --- /dev/null +++ b/test/testalloc.bat @@ -0,0 +1,5 @@ +@echo off +cl /I ../src/mock_libc/ testalloc.c ../src/mock_libc/alloc.c /Fe:testalloc.exe +testalloc.exe +del testalloc.exe +del *.obj \ No newline at end of file diff --git a/test/testalloc.c b/test/testalloc.c new file mode 100644 index 0000000..6ada379 --- /dev/null +++ b/test/testalloc.c @@ -0,0 +1,41 @@ +#include "alloc.h" +#include +#include +#include + +int main() { + allocatorInit(VirtualAlloc(NULL, 1 << 16, MEM_COMMIT, PAGE_READWRITE)); + + char *data = allocatorAlloc(50); + char *origdata = data; + memcpy(data, "Hello world", 12); + data[12] = 0; + char *oldata = data; + assert(strcmp(data, "Hello world") == 0); + data = allocatorRealloc(data, 1000); + assert(oldata != data); + assert(strcmp(data, "Hello world") == 0); + char *data2 = allocatorAlloc(50); + assert(data2 == origdata); + memcpy(data2, "Cool", 5); + data2[5] = 0; + assert(strcmp(data2, "Cool") == 0); + assert(strcmp(data, "Hello world") == 0); + allocatorFree(data); + allocatorFree(data2); + char *data3 = allocatorAlloc(50); + char *data4 = allocatorAlloc(50); + char *data5 = allocatorAlloc(50); + char *data6 = allocatorAlloc(50); + char *data7 = allocatorAlloc(50); + char *data8 = allocatorAlloc(50); + data8 = allocatorAlloc(10000); + assert(data3 == origdata); + assert(data4 == data); + assert(data5 != data && data5 != origdata); + allocatorFree(data7); + allocatorFree(data8); + assert(allocatorAlloc(50) == data8); + assert(allocatorAlloc(50) == data7); + printf("OK\n"); +} \ No newline at end of file diff --git a/test/testfmt.bat b/test/testfmt.bat new file mode 100644 index 0000000..8fa3af3 --- /dev/null +++ b/test/testfmt.bat @@ -0,0 +1,5 @@ +@echo off +cl /I ../src/mock_libc/ testfmt.c ../src/mock_libc/fmt.c ../src/mock_libc/vfs.c ../src/mock_libc/alloc.c /Fe:testfmt.exe +testfmt.exe +del testfmt.exe +del *.obj \ No newline at end of file diff --git a/test/testfmt.c b/test/testfmt.c new file mode 100644 index 0000000..3f2946d --- /dev/null +++ b/test/testfmt.c @@ -0,0 +1,204 @@ +#include "fmt.h" +#include +#include +#include + +int main() { + // String read stream + + char dest[13] = {0}; + RWStream rw1 = rwStreamMkStr("Hello world!"); + assert(rw1.read(&rw1, dest, 5) == 5); + assert(strcmp(dest, "Hello") == 0); + assert(rw1.read(&rw1, dest, 1) == 1); + assert(rw1.read(&rw1, dest, 2344572) == 6); + assert(strcmp(dest, "world!") == 0); + assert(rw1.read(&rw1, dest, 123) == 0); + printf("OK READ\n"); + + // String write stream + + RWStream rw2 = rwStreamMkStrS(dest, 13); + assert(rw2.write(&rw2, "Hello", 5) == 5); + assert(strcmp(dest, "Hello") == 0); + assert(rw2.write(&rw2, " ", 1) == 1); + assert(rw2.write(&rw2, "world!", 6) == 6); + assert(rw2.write(&rw2, "gjfalskdfjl", 11) == 0); + assert(strcmp(dest, "Hello world!") == 0); + printf("OK WRITE\n"); + + // Format functions + + rw1 = rwStreamMkStr("123 4A6 666 010101"); + + assert(fmtParseULL(&rw1, 10) == 123); + assert(fmtParseULL(&rw1, 16) == 0x4A6); + assert(fmtParseULL(&rw1, 8) == 0666); + assert(fmtParseULL(&rw1, 2) == 0b010101); + + rw1 = rwStreamMkStr("123 0x4A6 0666 0X123F"); + + assert(fmtParseULL(&rw1, 0) == 123); + assert(fmtParseULL(&rw1, 0) == 0x4A6); + assert(fmtParseULL(&rw1, 0) == 0666); + assert(fmtParseULL(&rw1, 0) == 0x123F); + + printf("OK ULL\n"); + + rw1 = rwStreamMkStr("-534 324 324 -ABC +10101"); + assert(fmtParseLL(&rw1, 10) == -534); + assert(fmtParseLL(&rw1, 10) == 324); + assert(fmtParseLL(&rw1, 16) == 0x324); + assert(fmtParseLL(&rw1, 16) == -0xABC); + assert(fmtParseLL(&rw1, 2) == 0b10101); + + rw1 = rwStreamMkStr("-534 +324 0x324 -0xABC"); + assert(fmtParseLL(&rw1, 0) == -534); + assert(fmtParseLL(&rw1, 0) == 324); + assert(fmtParseLL(&rw1, 0) == 0x324); + assert(fmtParseLL(&rw1, 0) == -0xABC); + + printf("OK LL\n"); + + rw1 = rwStreamMkStr(" 3.14159 -0.1234 1.0e3 0.1 0.0001 -0.0001e010 " + "0e200 1e-4 +0001e+0 0000012e-0 0.0"); + printf("exp 3.14159 got %g\n", fmtParseD(&rw1)); + printf("exp -0.1234 got %g\n", fmtParseD(&rw1)); + printf("exp 1000 got %g\n", fmtParseD(&rw1)); + printf("exp 0.1 got %g\n", fmtParseD(&rw1)); + printf("exp 0.0001 got %g\n", fmtParseD(&rw1)); + printf("exp -1e+6 got %g\n", -fmtParseD(&rw1)); + printf("exp 0 got %g\n", fmtParseD(&rw1)); + printf("exp 0.0001 got %g\n", fmtParseD(&rw1)); + printf("exp 1 got %g\n", fmtParseD(&rw1)); + printf("exp 12 got %g\n", fmtParseD(&rw1)); + printf("exp 0 got %g\n", fmtParseD(&rw1)); + + printf("CHECK VALUES\n"); + + // Test scanf + rw1 = rwStreamMkStr("HELLOWORLD \t -1234"); + char str[100] = {0}; + int num = 0; + int x = 0; + + assert(fmtscanf(&rw1, "%s %d", str, &num) == 2); + assert(strcmp(str, "HELLOWORLD") == 0); + assert(num == -1234); + rw1 = rwStreamMkStr("HELLOWORLD \t JDFKDJF"); + assert(fmtscanf(&rw1, "%s %d", str, &num) == 1); + rw1 = rwStreamMkStr("0xDEADBEEF"); + assert(fmtscanf(&rw1, "0x%x%n", &x, &num) == 2); + assert(x == 0xDEADBEEF); + assert(num == 10); + rw1 = rwStreamMkStr( + "% -0x012345, +45321, 23421, 666 ABCD123 | 0.123 : A1234 NiceString"); + + int p1 = 0; + int p2 = 0; + int p3 = 0; + int p4 = 0; + int p5 = 0; + double p6 = 0; + char p7 = 0; + void *p8 = 0; + char p9[100] = {0}; + + int cnt = 0; + assert((cnt = fmtscanf(&rw1, "%% %i,%d, %u, %o %x|%g %c %p %s", &p1, &p2, &p3, + &p4, &p5, &p6, &p7, &p8, &p9))); + + assert(p1 == -0x012345); + assert(p2 == 45321); + assert(p3 == 23421); + assert(p4 == 0666); + assert(p5 == 0xABCD123); + assert(p6 == 0.123); + assert(p7 == ':'); + assert(p8 == (void *)0xA1234); + assert(strcmp(p9, "NiceString") == 0); + + printf("OK SCANF\n"); + + // Test number formatting + + char buf[1024]; + rw1 = rwStreamMkStrS(buf, 1024); + fmtStringifyULL(&rw1, 1235423, 10, true); + assert(strcmp(buf, "1235423") == 0); + int p = rw1.tell(&rw1); + fmtStringifyULL(&rw1, 0x123AB, 16, true); + assert(strcmp(buf + p, "123AB") == 0); + p = rw1.tell(&rw1); + fmtStringifyULL(&rw1, 0561, 8, true); + assert(strcmp(buf + p, "561") == 0); + p = rw1.tell(&rw1); + fmtStringifyULL(&rw1, 0b1110001010, 2, true); + assert(strcmp(buf + p, "1110001010") == 0); + + p = rw1.tell(&rw1); + fmtStringifyLL(&rw1, 123, 10, true, false, false); + assert(strcmp(buf + p, "123") == 0); + p = rw1.tell(&rw1); + fmtStringifyLL(&rw1, -123, 10, true, false, false); + assert(strcmp(buf + p, "-123") == 0); + p = rw1.tell(&rw1); + fmtStringifyLL(&rw1, 0xabc, 16, false, true, false); + assert(strcmp(buf + p, "+abc") == 0); + p = rw1.tell(&rw1); + fmtStringifyLL(&rw1, -0666, 8, false, true, false); + assert(strcmp(buf + p, "-666") == 0); + + p = rw1.tell(&rw1); + fmtStringifyD(&rw1, 123.456, true, true, false, 2, false); + assert(strcmp(buf + p, "+123.45") == 0); + p = rw1.tell(&rw1); + fmtStringifyD(&rw1, -123.4556, true, true, false, 4, false); + assert(strcmp(buf + p, "-123.4556") == 0); + p = rw1.tell(&rw1); + fmtStringifyD(&rw1, 123.4556, true, false, false, 1, false); + assert(strcmp(buf + p, "123.4") == 0); + p = rw1.tell(&rw1); + fmtStringifyD(&rw1, 123, true, false, false, 3, false); + assert(strcmp(buf + p, "123.000") == 0); + + printf("OK STRINGIFY\n"); + + // Format functions + + rw1 = rwStreamMkStrS(buf, 1024); + int n = 0; + fmtprintf(&rw1, "Hello %s %d %x %o %g %c %p %s%n", "world", 123, 0x123, 0123, + 123.456, '!', (void *)0x123, "string", &n); + assert(strcmp(buf, "Hello world 123 123 123 123.4 ! 0x123 string") == 0); + assert(n == strlen("Hello world 123 123 123 123.4 ! 0x123 string")); + + // Test flags + rw1 = rwStreamMkStrS(buf, 1024); + fmtprintf(&rw1, "%+d %#x %#X %#o %#p %-6d % d % d %04d", 123, 123, 123, 123, + (void *)0x123, 456, 123, -123, 123); + + // printf("%s\n", buf); + assert(strcmp(buf, "+123 0x7b 0x7B 0173 0x123 456 123 -123 0123") == 0); + + // Test width + rw1 = rwStreamMkStrS(buf, 1024); + fmtprintf(&rw1, "%10d %-10d %6x %-6x %10s %-10s %-5c %5c", 123, 123, 0xABC, + 0xDEF123, "Hello", "world", 'a', 'b'); + // printf("`%10d %-10d %6x %-6x %10s %-10s %-5c %5c`\n", 123, 123, 0xABC, + // 0xDEF123, "Hello", "world", 'a', 'b'); + // printf("`%s`\n", buf); + assert(strcmp(buf, " 123 123 abc def123 Hello world " + " a b") == 0); + + // Test precision + rw1 = rwStreamMkStrS(buf, 1024); + fmtprintf(&rw1, "%.3f %.5f %.0f %#-5.8x %.5d %.*s", 123.456, 123.456, 123.456, + 0xC001, 123, 4, "Hello"); + // printf("`%.3f %.5f %.0f %#5.8x %.5d %.*s`\n", 123.456, 123.456, 123.456, + // 0xC001, 123, 4, "Hello"); + // printf("`%s`\n", buf); + assert(strcmp(buf, "123.456 123.45600 123 0x0000c001 00123 Hell") == 0); + + printf("OK FORMAT"); +} \ No newline at end of file diff --git a/test/testvfs.bat b/test/testvfs.bat new file mode 100644 index 0000000..7085750 --- /dev/null +++ b/test/testvfs.bat @@ -0,0 +1,5 @@ +@echo off +cl /I ../src/mock_libc/ testvfs.c ../src/mock_libc/vfs.c /Fe:testvfs.exe +testvfs.exe +del testvfs.exe +del *.obj \ No newline at end of file diff --git a/test/testvfs.c b/test/testvfs.c new file mode 100644 index 0000000..6f23b57 --- /dev/null +++ b/test/testvfs.c @@ -0,0 +1,47 @@ +#include "vfs.h" +#include +#include + +int main() { + vfsInit(); + Stream *s = vfsOpen("test.txt", "wb"); + assert(s); + vfsWrite(s, "hello", 5); + vfsClose(s); + + // now read + s = vfsOpen("./test.txt", "rb"); + char buf[10]; + vfsSeek(s, 0, 2); + size_t size = vfsTell(s); + vfsSeek(s, 0, 0); + vfsRead(s, buf, size); + assert(vfsEof(s)); + buf[size] = '\0'; + printf("%s\n", buf); + assert(strcmp(buf, "hello") == 0); + vfsClose(s); + + assert(vfsRemove("./test.txt") == 0); + assert(vfsRemove("./test.txt") == -1); + assert(!vfsOpen("./test.txt", "r")); + assert(vfsOpen("./test.txt", "w")); + assert(vfsOpen("./test.txt", "r")); + assert(vfsRemove("./././test.txt") == 0); + + s = vfsOpen("test.txt", "rb"); + assert(s == NULL); + + assert(vfsCreate("test1.txt") == 0); + assert(vfsCreate("test2.txt") == 0); + assert(vfsCreate("subdir") == 0); + assert(vfsCreate("subdir/test1.txt") == 0); + assert(vfsExists("test1.txt")); + assert(vfsExists("test2.txt")); + assert(vfsExists("subdir")); + assert(vfsExists("subdir/test1.txt")); + assert(!vfsExists("subdir/test2.txt")); + assert(vfsExists("subdir/../subdir/test1.txt")); + + printf("OK\n"); +} \ No newline at end of file