From ad589d28941ec6abfb7e7f2e92f08ab8cd786c27 Mon Sep 17 00:00:00 2001 From: "Javier B. Torres" Date: Thu, 27 Nov 2025 16:39:54 -0300 Subject: [PATCH] initial import --- .gitignore | 4 + .ocamlformat | 2 + LICENSE | 13 + Makefile | 6 + README.md | 5 + dune-project | 19 ++ exe/dune | 4 + exe/uxnemu.ml | 82 +++++ lib/Instr.ml | 73 ++++ lib/Instr.mli | 6 + lib/Machine.ml | 288 ++++++++++++++++ lib/Machine.mli | 36 ++ lib/Util.ml | 17 + lib/Util.mli | 5 + lib/Varvara.ml | 0 lib/dune | 2 + utils/LICENSE | 13 + utils/Makefile | 19 ++ utils/assemble.sh | 4 + utils/console.tal | 82 +++++ utils/drifloon.rom.txt | 155 +++++++++ utils/drifloon.tal | 732 +++++++++++++++++++++++++++++++++++++++++ utils/opctest.tal | 509 ++++++++++++++++++++++++++++ utils/uxnmin | Bin 0 -> 192320 bytes utils/uxnmin.c | 135 ++++++++ uxn.opam | 30 ++ 26 files changed, 2241 insertions(+) create mode 100644 .gitignore create mode 100644 .ocamlformat create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 dune-project create mode 100644 exe/dune create mode 100644 exe/uxnemu.ml create mode 100644 lib/Instr.ml create mode 100644 lib/Instr.mli create mode 100644 lib/Machine.ml create mode 100644 lib/Machine.mli create mode 100644 lib/Util.ml create mode 100644 lib/Util.mli create mode 100644 lib/Varvara.ml create mode 100644 lib/dune create mode 100644 utils/LICENSE create mode 100644 utils/Makefile create mode 100644 utils/assemble.sh create mode 100644 utils/console.tal create mode 100644 utils/drifloon.rom.txt create mode 100644 utils/drifloon.tal create mode 100644 utils/opctest.tal create mode 100755 utils/uxnmin create mode 100644 utils/uxnmin.c create mode 100644 uxn.opam diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d00f41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.rom +*.rom.sym +/_build +/uxn-utils/uxnmin diff --git a/.ocamlformat b/.ocamlformat new file mode 100644 index 0000000..ad02287 --- /dev/null +++ b/.ocamlformat @@ -0,0 +1,2 @@ +version = 0.28.1 +ocaml-version = 5.4.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..530bc67 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2025 Javier B. Torres + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89294a2 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +.PHONY: utils clean + +utils: + $(MAKE) -C utils +clean: + $(MAKE) -C utils clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc59278 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Uxn\_of\_ocaml + +An Uxn emulator library for OCaml 5 (as it uses effect handlers) + + diff --git a/dune-project b/dune-project new file mode 100644 index 0000000..b8c7172 --- /dev/null +++ b/dune-project @@ -0,0 +1,19 @@ +(lang dune 3.20) + +(name uxn) + +(generate_opam_files true) + +(source + (codeberg lobo/uxn)) + +(authors "Javier B. Torres ") +(maintainers "Javier B. Torres ") + +(license LICENSE) + +(package + (name uxn) + (synopsis "Uxn emulator library for OCaml") + (description "Uxn emulator library for OCaml") + (depends ocaml)) diff --git a/exe/dune b/exe/dune new file mode 100644 index 0000000..a95ce30 --- /dev/null +++ b/exe/dune @@ -0,0 +1,4 @@ +(executable + (public_name uxnemu) + (name uxnemu) + (libraries uxn unix fmt)) diff --git a/exe/uxnemu.ml b/exe/uxnemu.ml new file mode 100644 index 0000000..52bcbeb --- /dev/null +++ b/exe/uxnemu.ml @@ -0,0 +1,82 @@ +open Uxn +open Effect.Deep + +let print_stack ?(name = "wst") (Machine.Stack stack) = + if stack.sp != 0 then + let stack = Bytes.to_seq stack.data |> Seq.take stack.sp |> Bytes.of_seq in + Fmt.epr "@[%s: [@[%a@]]@]@." name (Fmt.on_bytes (Fmt.octets ())) stack + +let print_instruction i pc = Fmt.epr "%6s (PC = %04x)@." (Instr.to_string i) pc +let debug = Option.is_some (Sys.getenv_opt "DBG") +let console_vector = ref 0 + +let dispatch = + if debug then + Machine.dispatch + ~dbg: + (Some + (fun m i pc -> + print_instruction i pc; + print_stack (Machine.wst m); + print_stack ~name:"rst" (Machine.rst m))) + else Machine.dispatch ~dbg:None + +let eval m pc = + let console_input mach ch ty k = + Bytes.set_uint8 (Machine.dev mach) 0x12 ch; + Bytes.set_uint8 (Machine.dev mach) 0x17 ty; + if !console_vector != 0 && Bytes.get_uint8 (Machine.dev mach) 0x0f = 0 then + continue k !console_vector + in + try dispatch m pc with + | effect Machine.BRK, k -> + if !console_vector != 0 then ( + try + while Bytes.get_uint8 (Machine.dev m) 0x0f = 0 do + match In_channel.input_char stdin with + | None -> raise Exit + | Some c -> console_input m (Char.code c) 1 k + done + with Exit -> + Bytes.set_uint8 (Machine.dev m) 0x12 0; + Bytes.set_uint8 (Machine.dev m) 0x17 4; + continue k !console_vector) + | effect Machine.DEI port, k -> + continue k (Bytes.get_uint8 (Machine.dev m) port) + | effect Machine.DEI2 port, k -> + continue k (Util.get_uint16_wrap (Machine.dev m) port) + | effect Machine.DEO (port, value), k -> + (match port with + | 0x10 -> console_vector := value + | 0x18 -> + print_char (Char.chr value); + Out_channel.flush stdout + | 0x19 -> + prerr_char (Char.chr value); + Out_channel.flush stderr + | _ -> ()); + continue k () + +let main () = + if Array.length Sys.argv < 2 then ( + Fmt.epr "usage: uxnemu file.rom ...\n"; + exit 1); + + let code = + In_channel.with_open_bin Sys.argv.(1) (fun i -> In_channel.input_all i) + in + In_channel.set_binary_mode stdin true; + Out_channel.set_binary_mode stdout true; + + let mach = Machine.create code in + Bytes.set_uint8 (Machine.dev mach) 0x17 0; + eval mach 0x100; + + if debug then ( + Fmt.epr "Execution ended:@."; + Machine.wst mach |> print_stack; + Machine.rst mach |> print_stack ~name:"rst"); + + Out_channel.flush_all () + +let _ = main () diff --git a/lib/Instr.ml b/lib/Instr.ml new file mode 100644 index 0000000..cb1ae7e --- /dev/null +++ b/lib/Instr.ml @@ -0,0 +1,73 @@ +let short_mask = 0x20 +let return_mask = 0x40 +let keep_mask = 0x80 +let opcode_mask = 0x1f + +let opcode_names = + [| + "BRK"; + "INC"; + "POP"; + "NIP"; + "SWP"; + "ROT"; + "DUP"; + "OVR"; + "EQU"; + "NEQ"; + "GTH"; + "LTH"; + "JMP"; + "JCN"; + "JSR"; + "STH"; + "LDZ"; + "STZ"; + "LDR"; + "STR"; + "LDA"; + "STA"; + "DEI"; + "DEO"; + "ADD"; + "SUB"; + "MUL"; + "DIV"; + "AND"; + "ORA"; + "EOR"; + "SFT"; + |] + +type t = + | Instruction of { short : bool; return : bool; keep : bool; opcode : int } + +let of_int (op : int) : t = + let has_mask mask = op land mask = mask in + let short = has_mask short_mask in + let return = has_mask return_mask in + let keep = has_mask keep_mask in + match op with + | 0x20 | 0x40 | 0x60 -> + Instruction { short = false; return = false; keep = false; opcode = op } + | 0x80 | 0xa0 | 0xc0 | 0xe0 -> + Instruction { short; return; keep = false; opcode = 0x80 } + | _ -> Instruction { short; return; keep; opcode = Int.logand op opcode_mask } + +let to_int (Instruction { opcode; short; keep; return } : t) : int = + let flags = if short then short_mask else 0 in + let flags = if return then Int.logor flags return_mask else flags in + let flags = if keep then Int.logor flags keep_mask else flags in + opcode lor flags + +let to_string (Instruction { opcode; short; keep; return } : t) : string = + Format.sprintf "%s%s%s%s" + (match opcode with + | 0x20 -> "JCI" + | 0x40 -> "JMI" + | 0x60 -> "JSI" + | 0x80 -> "LIT" + | _ -> opcode_names.(opcode)) + (if short then "2" else "") + (if keep then "k" else "") + (if return then "r" else "") diff --git a/lib/Instr.mli b/lib/Instr.mli new file mode 100644 index 0000000..5dd09b3 --- /dev/null +++ b/lib/Instr.mli @@ -0,0 +1,6 @@ +type t = + | Instruction of { short : bool; return : bool; keep : bool; opcode : int } + +val of_int : int -> t +val to_int : t -> int +val to_string : t -> string diff --git a/lib/Machine.ml b/lib/Machine.ml new file mode 100644 index 0000000..e9b5bb3 --- /dev/null +++ b/lib/Machine.ml @@ -0,0 +1,288 @@ +(* Unoptimized Uxn virtual machine. *) + +open Effect + +type stack = Stack of { data : bytes; mutable sp : int } +type mode = Mode of { short : bool; keep : bool; mutable temp : int } + +let stack_create () = Stack { data = Bytes.create 256; sp = 0 } + +let peek (Mode { short; keep; temp }) (Stack { data; sp }) : int = + let amt = if short then 2 else 1 in + let sp = if keep then (temp - amt) land 0xff else (sp - amt) land 0xff in + if short then Util.get_uint16_wrap data sp else Bytes.get_uint8 data sp +[@@inline] + +let pop (Mode m as m' : mode) (Stack s' as s : stack) = + let res = peek m' s in + let amt = if m.short then 2 else 1 in + if m.keep then m.temp <- (m.temp - amt) land 0xff + else s'.sp <- (s'.sp - amt) land 0xff; + res +[@@inline] + +let push (Mode ({ short; keep; _ } as m) : mode) (Stack s : stack) (v : int) = + if short then Util.set_uint16_wrap s.data s.sp (v land 0xffff) + else Bytes.set_uint8 s.data s.sp (v land 0xff); + let amt = if m.short then 2 else 1 in + if keep then m.temp <- (m.temp + amt) land 0xff; + s.sp <- (s.sp + amt) land 0xff +[@@inline] + +let pushbyte (Mode m) s v = + let m' = Mode { m with short = false } in + push m' s v; + let (Mode { temp; _ }) = m' in + m.temp <- temp +[@@inline] + +let pushshort (Mode m) s v = + let m' = Mode { m with short = true } in + push m' s v; + let (Mode { temp; _ }) = m' in + m.temp <- temp +[@@inline] + +let popbyte (Mode m) s = + let m' = Mode { m with short = false } in + let r = pop m' s in + let (Mode { temp; _ }) = m' in + m.temp <- temp; + r +[@@inline] + +let popshort (Mode m) s = + let m' = Mode { m with short = true } in + let r = pop m' s in + let (Mode { temp; _ }) = m' in + m.temp <- temp; + r +[@@inline] + +let pop1 s = pop (Mode { short = false; keep = false; temp = 0 }) s [@@inline] + +let push1 s v = push (Mode { short = false; keep = false; temp = 0 }) s v +[@@inline] + +let push2 s v = push (Mode { short = true; keep = false; temp = 0 }) s v +[@@inline] + +type machine = + | Machine of { data : bytes; dev : bytes; stack : stack; callstack : stack } + +type _ Effect.t += + | BRK : int Effect.t + | DEI : int -> int Effect.t + | DEI2 : int -> int Effect.t + | DEO : (int * int) -> unit Effect.t + +type machine_state = Break | Next of int + +let ram (Machine { data; _ }) = data +let dev (Machine { dev; _ }) = dev +let wst (Machine { stack; _ }) = stack +let rst (Machine { callstack; _ }) = callstack + +let stack (Machine { stack; callstack; _ }) mode = + if mode then callstack else stack + +let create code = + let data = Bytes.create 65536 in + let dev = Bytes.create 256 in + Bytes.unsafe_fill data 0 65536 '\x00'; + Bytes.unsafe_fill dev 0 256 '\x00'; + Bytes.blit_string code 0 data 0x100 (String.length code); + Machine { data; dev; stack = stack_create (); callstack = stack_create () } + +let dispatch ?(dbg = None) ?(cycles = 65536) (Machine m) (pc : int) : unit = + let cycles = ref cycles in + let pc = ref pc in + while !cycles > 0 do + decr cycles; + + pc := !pc land 0xffff; + + let op = Bytes.get_uint8 m.data !pc in + let instr = Instr.of_int op in + (match dbg with Some dbg -> dbg (Machine m) instr !pc | None -> ()); + + pc := (!pc + 1) land 0xffff; + + match op with + | 0x00 -> pc := perform BRK + | 0x20 (* JCI *) -> + let cond = pop1 m.stack in + let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in + if cond != 0 then pc := !pc + addr + 2 else pc := !pc + 2 + | 0x40 (* JMI *) -> + let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in + pc := !pc + addr + 2 + | 0x60 (* JSI *) -> + let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in + push2 m.callstack (!pc + 2); + pc := !pc + addr + 2 + | 0x80 (* LIT *) -> + push1 m.stack (Bytes.get_uint8 m.data !pc); + pc := !pc + 1 + | 0xa0 (* LIT2 *) -> + push2 m.stack (Util.get_uint16_wrap ~wrap:0xffff m.data !pc); + pc := !pc + 2 + | 0xc0 (* LITr *) -> + push1 m.callstack (Bytes.get_uint8 m.data !pc); + pc := !pc + 1 + | 0xe0 (* LIT2r *) -> + push2 m.callstack (Util.get_uint16_wrap ~wrap:0xffff m.data !pc); + pc := !pc + 2 + | _ -> begin + let (Instruction { short; keep; return; opcode }) = Instr.of_int op in + let stk = if return then m.callstack else m.stack in + let stk' = if return then m.stack else m.callstack in + let mode = + Mode { short; keep; temp = (match stk with Stack { sp; _ } -> sp) } + in + let deo port value = perform (DEO (port, value)) in + let dei port = + if short then perform (DEI2 port) else perform (DEI port) + in + match[@warning "-8"] opcode with + | 0x01 (* INC *) -> + let r = pop mode stk in + push mode stk (r + 1) + | 0x02 (* POP *) -> ignore (pop mode stk) + | 0x03 (* NIP *) -> + let b = pop mode stk in + let _ = pop mode stk in + push mode stk b + | 0x04 (* SWP *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk b; + push mode stk a + | 0x05 (* ROT *) -> + let c = pop mode stk in + let b = pop mode stk in + let a = pop mode stk in + push mode stk b; + push mode stk c; + push mode stk a + | 0x06 (* DUP *) -> + let a = peek mode stk in + push mode stk a + | 0x07 (* OVR *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk a; + push mode stk b; + push mode stk a + | 0x08 (* EQU *) -> + let b = pop mode stk in + let a = pop mode stk in + pushbyte mode stk (if a = b then 1 else 0) + | 0x09 (* NEQ *) -> + let b = pop mode stk in + let a = pop mode stk in + pushbyte mode stk (if a != b then 1 else 0) + | 0x0a (* GTH *) -> + let b = pop mode stk in + let a = pop mode stk in + pushbyte mode stk (if a > b then 1 else 0) + | 0x0b (* GTH *) -> + let b = pop mode stk in + let a = pop mode stk in + pushbyte mode stk (if a < b then 1 else 0) + | 0x0c (* JMP *) -> + let addr = pop mode stk in + if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr + | 0x0d (* JCN *) -> + let addr = pop mode stk in + let cond = popbyte mode stk in + if cond != 0 then + if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr + | 0x0e (* JSR *) -> + push2 m.callstack !pc; + let addr = pop mode stk in + if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr + | 0x0f (* STH *) -> ( + let a = pop mode stk in + match mode with + | Mode mode -> + push + (Mode + { + mode with + temp = (match stk' with Stack { sp; _ } -> sp); + }) + stk' a) + | 0x10 (* LDZ *) -> + let addr = popbyte mode stk in + push mode stk + (if short then Util.get_uint16_wrap m.data addr + else Bytes.get_uint8 m.data addr) + | 0x11 (* STZ *) -> + let addr = popbyte mode stk in + let v = pop mode stk in + if short then Util.set_uint16_wrap m.data addr v + else Bytes.set_uint8 m.data addr v + | 0x12 (* LDR *) -> + let addr = !pc + Util.uint8_to_int8 (popbyte mode stk) in + push mode stk + (if short then Util.get_uint16_wrap ~wrap:0xffff m.data addr + else Bytes.get_uint8 m.data addr) + | 0x13 (* STR *) -> + let addr = !pc + Util.uint8_to_int8 (popbyte mode stk) in + let v = pop mode stk in + if short then Util.set_uint16_wrap ~wrap:0xffff m.data addr v + else Bytes.set_uint8 m.data addr v + | 0x14 (* LDA *) -> + let addr = popshort mode stk in + push mode stk + (if short then Util.get_uint16_wrap ~wrap:0xffff m.data addr + else Bytes.get_uint8 m.data addr) + | 0x15 (* STA *) -> + let addr = popshort mode stk in + let v = pop mode stk in + if short then Util.set_uint16_wrap ~wrap:0xffff m.data addr v + else Bytes.set_uint8 m.data addr v + | 0x16 (* DEI *) -> + let port = popbyte mode stk in + push mode stk (dei port) + | 0x17 (* DEO *) -> + let port = popbyte mode stk in + let value = pop mode stk in + if short then Util.set_uint16_wrap m.dev port value + else Bytes.set_uint8 m.dev port value; + deo port value + | 0x18 (* ADD *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a + b) + | 0x19 (* SUB *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a - b) + | 0x1a (* MUL *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a * b) + | 0x1b (* DIV *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (if b = 0 then 0 else a / b) + | 0x1c (* AND *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a land b) + | 0x1d (* ORA *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a lor b) + | 0x1e (* EOR *) -> + let b = pop mode stk in + let a = pop mode stk in + push mode stk (a lxor b) + | 0x1f (* SFT *) -> + let sft = popbyte mode stk in + let a = pop mode stk in + push mode stk ((a lsr (sft land 0xf)) lsl sft lsr 4) + end + done diff --git a/lib/Machine.mli b/lib/Machine.mli new file mode 100644 index 0000000..9837253 --- /dev/null +++ b/lib/Machine.mli @@ -0,0 +1,36 @@ +type stack = Stack of { data : bytes; mutable sp : int } +type mode = Mode of { short : bool; keep : bool; mutable temp : int } + +val stack_create : unit -> stack +val peek : mode -> stack -> int +val pop : mode -> stack -> int +val push : mode -> stack -> int -> unit +val pushbyte : mode -> stack -> int -> unit +val pushshort : mode -> stack -> int -> unit +val popbyte : mode -> stack -> int +val popshort : mode -> stack -> int + +type machine + +val ram : machine -> bytes +val dev : machine -> bytes +val wst : machine -> stack +val rst : machine -> stack +val stack : machine -> bool -> stack + +type machine_state = Break | Next of int + +type _ Effect.t += + | BRK : int Effect.t (* Returns a new PC if handled *) + | DEI : int -> int Effect.t + | DEI2 : int -> int Effect.t + | DEO : (int * int) -> unit Effect.t + +val create : string -> machine + +val dispatch : + ?dbg:(machine -> Instr.t -> int -> unit) option -> + ?cycles:int -> + machine -> + int -> + unit diff --git a/lib/Util.ml b/lib/Util.ml new file mode 100644 index 0000000..81f3531 --- /dev/null +++ b/lib/Util.ml @@ -0,0 +1,17 @@ +let uint8_to_int8 i = (i lsl (Sys.int_size - 8)) asr (Sys.int_size - 8) +let uint16_to_int16 i = (i lsl (Sys.int_size - 16)) asr (Sys.int_size - 16) + +let get_uint16_wrap ?(wrap = 0xff) (bytes : bytes) (position : int) : int = + let i0 = position land wrap in + let hi = Bytes.get_uint8 bytes i0 in + let lo = Bytes.get_uint8 bytes ((i0 + 1) land wrap) in + (hi lsl 8) lor lo + +let get_int16_wrap ?(wrap = 0xff) (bytes : bytes) (position : int) : int = + get_uint16_wrap ~wrap bytes position |> uint16_to_int16 + +let set_uint16_wrap ?(wrap = 0xff) (bytes : bytes) (position : int) + (value : int) : unit = + let i0 = position land wrap in + Bytes.set_uint8 bytes i0 ((value lsr 8) land 0xff); + Bytes.set_uint8 bytes ((i0 + 1) land wrap) (value land 0xff) diff --git a/lib/Util.mli b/lib/Util.mli new file mode 100644 index 0000000..adb8f59 --- /dev/null +++ b/lib/Util.mli @@ -0,0 +1,5 @@ +val uint8_to_int8 : int -> int +val uint16_to_int16 : int -> int +val get_uint16_wrap : ?wrap:int -> bytes -> int -> int +val get_int16_wrap : ?wrap:int -> bytes -> int -> int +val set_uint16_wrap : ?wrap:int -> bytes -> int -> int -> unit diff --git a/lib/Varvara.ml b/lib/Varvara.ml new file mode 100644 index 0000000..e69de29 diff --git a/lib/dune b/lib/dune new file mode 100644 index 0000000..248d9cc --- /dev/null +++ b/lib/dune @@ -0,0 +1,2 @@ +(library + (name uxn)) diff --git a/utils/LICENSE b/utils/LICENSE new file mode 100644 index 0000000..d0716ec --- /dev/null +++ b/utils/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2020-2025 Devine Lu Linvega + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/utils/Makefile b/utils/Makefile new file mode 100644 index 0000000..fbe5a68 --- /dev/null +++ b/utils/Makefile @@ -0,0 +1,19 @@ +.PHONY: all clean +.SUFFIXES: .tal .rom + +all: uxnmin drifloon.rom opctest.rom +clean: + rm -f uxnmin drifloon.rom opctest.rom + +uxnmin: uxnmin.c +drifloon.rom: uxnmin + xxd -r -p drifloon.rom.txt drifloon-seed.rom + ./uxnmin drifloon-seed.rom < drifloon.tal > drifloon.rom + cmp drifloon.rom drifloon-seed.rom + rm drifloon-seed.rom + +.tal.rom: + sh assemble.sh $< $@ + +#opctest.rom: uxnmin drifloon.rom opctest.tal +# ./uxnmin drifloon.rom < opctest.tal > opctest.rom diff --git a/utils/assemble.sh b/utils/assemble.sh new file mode 100644 index 0000000..7bd01d7 --- /dev/null +++ b/utils/assemble.sh @@ -0,0 +1,4 @@ +#!/bin/sh +make -q uxnmin +make -q drifloon.rom +./uxnmin drifloon.rom < $1 > $2 diff --git a/utils/console.tal b/utils/console.tal new file mode 100644 index 0000000..a007f1f --- /dev/null +++ b/utils/console.tal @@ -0,0 +1,82 @@ +( usage: uxncli console.rom foo "bar baz" +| Prints Welcome to Uxn!, and listens for incoming stdin events on enter. ) + +|10 @Console &vector $2 &read $1 &pad $4 &type $1 &write $1 &error $1 + +|000 + + @arg $40 + @std $40 + +|100 + +@on-reset ( -> ) + ;dict/hello + .Console/type DEI ?{ + ( | no arguments ) + ;on-std .Console/vector DEO2 + BRK } + ;on-arg .Console/vector DEO2 + BRK + +@on-arg ( -> ) + [ LIT2 02 -Console/type ] DEI NEQ ?{ + .Console/read DEI [ LIT2 00 &ptr -arg ] INCk ,&ptr STR + STZ2 + BRK } + ;arg ;dict/yousent + [ LIT2 -arg _&ptr ] STR + [ LIT2 04 -Console/type ] DEI NEQ ?{ ;on-std .Console/vector DEO2 } + BRK + +@on-std ( -> ) + [ LIT2 0a -Console/read ] DEI EQU ?{ + .Console/read DEI [ LIT2 00 &ptr -std ] INCk ,&ptr STR + STZ2 + BRK } + ;std DUP2 ;dict/yousaid + ;dict/quit scmp ?{ + [ LIT2 -std _&ptr ] STR + BRK } + ( quit ) #800f DEO + BRK + +@ ( buf* name* -- ) + + [ LIT2 "" 18 ] DEO + / + [ LIT2 "" 18 ] DEO + [ LIT2 00 -Console/type ] DEI DUP ADD ;Types ADD2 LDA2 / + #0a18 DEO + JMP2r + +@ ( str* -- ) + LDAk #18 DEO + INC2 & LDAk ? + POP2 JMP2r + +@scmp ( a* b* -- f ) + STH2 + &l ( a* b* -- f ) + LDAk LDAkr STHr NEQk ?&d + DUP EOR EQUk ?&d + POP2 INC2 INC2r !&l + + &d ( a* c1 c2 b* -- f ) + NIP2 POP2r EQU JMP2r + +( +@|assets ) + +@Types =dict/arg-none =dict/arg-stdin =dict/arg-data =dict/arg-spacer =dict/arg-end + +@dict + &hello "Welcome 20 "to 20 "Uxn! 0a $1 + &yousaid "You 20 "said: 20 $1 + &yousent "You 20 "sent: 20 $1 + &quit "quit $1 + &arg-none " $1 + &arg-stdin " $1 + &arg-data " $1 + &arg-spacer " $1 + &arg-end " $1 diff --git a/utils/drifloon.rom.txt b/utils/drifloon.rom.txt new file mode 100644 index 0000000..d5a1fb3 --- /dev/null +++ b/utils/drifloon.rom.txt @@ -0,0 +1,155 @@ +a001 6380 0637 a00a a560 0233 a001 1380 +1037 00a0 0417 1608 2000 0780 1216 6001 +cf00 6000 7a00 a093 ab38 156c a009 ff60 +08a7 a00a 0960 08a1 a008 8034 26a0 0100 +3960 089b a00a 0e60 088f a093 ab38 a094 +ab94 8018 1721 aa20 fff7 2222 6c22 6c80 +1917 6c00 4472 6966 6c6f 6f6e 0a55 786e +7461 6c20 4173 7365 6d62 6c65 720a 4279 +2044 6576 696e 6520 4c75 204c 696e 7665 +6761 0a38 204e 6f76 2032 3032 3500 0080 +0a60 014c 8047 32a0 022c 2920 000c a00a +9da0 0aa2 a00a 6060 07f6 8031 32a0 026e +2920 000c a00a 9da0 0aa2 a00a 4e60 07e0 +800f 1620 0010 6005 b280 0f16 2000 07a0 +800f 1740 049d 6c90 2000 0222 6ca0 01f9 +2ca0 01f9 a001 ee35 6c60 06f6 2006 2860 +0253 2006 0d90 6001 66a1 1d20 000e 2226 +6000 a7a1 1d20 00c2 2240 01d6 2134 2c22 +a002 2ca0 01ee 35a0 0104 136c 3426 c000 +a028 0028 0f58 a029 0028 0f59 cfc0 ef53 +2000 0340 ffab 6c60 0322 6000 4d80 0060 +004e a002 5aa0 01ee 356c 34a0 7b00 2920 +000b a002 6ea0 01ee 35a0 0104 136c b426 +c000 807b 0804 807b 081d 0f58 a07d 0028 +0f59 cfc0 eb53 2000 0922 8000 6000 1140 +ff5f 6000 0580 2040 0006 a002 a040 05f7 +a00b aba1 26a0 1bab 2b20 0009 a00a 70a0 +0a47 6006 7680 e933 156c 2f80 e332 a00b +ab26 ef60 0647 2000 0e60 0637 6006 34aa +20ff ee22 a0ff ff23 626c 2360 0009 6006 +22a0 02f0 4005 b0a0 4024 13a0 0040 116c +0680 200a 2000 16a0 0040 60fe ea80 0a09 +2000 07a0 0001 2180 fa33 40ff daa0 0040 +8106 807f 0b20 0009 a00a 70a0 0a54 6006 +0a80 eb13 316c a000 0081 0680 3f0b 2000 +09a0 0a70 a00a 4060 05f1 80eb 1331 6ca0 +00e5 1394 802f 0820 0009 9460 ffd8 2194 +20ff f022 80d1 1280 0313 6c21 a000 c813 +802f 60ff c1a0 0000 24a0 0326 4005 280f +a004 1594 cf08 2000 0ca0 0003 3894 20ff +f222 a0ff ff42 6c22 6c22 4001 c840 fe8f +6000 7040 feb1 6000 6a60 02c7 4004 f460 +0061 6002 be40 04e7 6000 5826 60ff 9040 +020f 60ff a640 0209 8080 6004 a560 0043 +6003 a140 049c 8080 6004 9760 0035 6003 +8540 048e 80a0 6004 8960 0027 6003 7f40 +047c 6000 1e80 2040 046e 6000 1680 4040 +0466 8060 4004 6160 0009 4004 1b60 0003 +4004 0921 9480 200a 2000 09a0 0a79 a00a +5440 0517 6c7c 0396 2403 9f40 03a8 2603 +b22c 03b8 5f03 bd2e 03c6 2d03 cb3b 03d4 +3d03 d921 03ea 3f03 e223 03f7 2203 fd7d +0389 7e01 5d28 038d 2903 875b 0387 5d03 +8725 0390 0026 6000 0e80 0009 0f26 a005 +2f60 04a9 4f1d 6ce0 1f00 a004 cfa4 a000 +0324 6004 9f20 000f 41a0 0003 3894 20ff +ec22 2262 8000 6c22 5cb4 a04c 4928 8070 +1f0f 5da0 0003 3894 8021 0b20 002e 9480 +3209 2000 05c0 2040 001d 9480 7209 2000 +05c0 4040 0011 9480 6b09 2000 05c0 8040 +0005 2242 8000 6c5d 2140 ffcb 224f 6c4c +4954 494e 4350 4f50 4e49 5053 5750 524f +5444 5550 4f56 5245 5155 4e45 5147 5448 +4c54 484a 4d50 4a43 4e4a 5352 5354 484c +445a 5354 5a4c 4452 5354 524c 4441 5354 +4144 4549 4445 4f41 4444 5355 424d 554c +4449 5641 4e44 4f52 4145 4f52 5346 5442 +524b 0022 8000 8180 fb13 06a0 0aab a180 +fa33 1506 8004 1f60 035d 0460 0359 8018 +33a0 0567 6c80 e432 a000 0139 9460 ffe3 +6000 5e80 d633 6cce bb2e 2e00 6003 8320 +0027 9480 7b08 2000 2094 60fd f221 1d20 +0017 2660 fd34 211d 2000 1726 6000 9721 +1d20 000e 60fe be0c 6ca0 0a79 a00a 5440 +0389 a00a 81a0 0a54 4003 8094 807b 0820 +ff81 9480 2f08 20fd a294 8026 0820 fd9b +6c26 6000 61a1 1d20 0010 22a0 060d 3460 +0226 8002 6002 b440 001d 2326 6002 1960 +0103 2000 0860 02a3 2735 4001 1022 a00a +81a0 0a40 4003 3404 6000 1160 000e 6000 +0b60 ff68 a006 0c60 028d 8000 a04b aba1 +26a0 93ab 2b20 0009 a00a 70a0 0a38 6003 +0a80 e933 156c 2f80 e332 a04b ab26 a000 +0338 ef60 02d7 2000 0fa0 0003 3860 02c3 +aa20 ffe9 22a0 ffff 2362 6c26 60ff d7a1 +1d20 000d 2280 b532 2480 01a0 ffff 40ff +9623 6c60 028c 2002 6b60 ff3f 60ff b760 +006b 2000 09a0 0a79 a00a 4060 02ad 6000 +7234 6ca0 060d 34a0 4bab a820 0037 6000 +5b20 0027 b4a0 0100 2820 001f 26a0 0003 +3894 8041 1980 1a0b 2000 0fa0 0a17 6003 +2826 6003 2480 0a60 faa5 22a0 0003 3860 +0241 40ff c522 2240 fa62 26a0 0080 3802 +2000 016c a00a 8ba0 0a40 4002 4ea1 1d20 +0003 8000 6ca1 2114 8002 1c6c a121 1480 +011c 6ca1 21af 1480 011d 6f15 6c21 21af +1480 021d 6f15 6c80 1733 60fe 9e60 ff3b +60ff d220 001d 2660 0171 6000 1b60 0018 +a000 0060 0012 a000 0060 000c a003 0434 +6000 0560 ffbd 346c 0460 0000 a01b aba1 +26a0 4bab 2b20 0009 a00a 70a0 0a23 6001 +da80 e933 156c a007 bf60 ffab 036c a007 +c740 ffa3 a007 ce60 ff9d a11d 2000 0422 +8000 6c60 0115 6000 0e60 ff4e 036c a007 +dd60 ff83 6001 0421 2139 6c80 af32 a01b +aba8 2000 1326 a009 7835 a6a0 0004 3834 +2ea0 000a 3840 ffe9 2222 6cb4 6000 e4a1 +2134 34a1 1d20 0006 a00a 7940 019b 6c60 +ffe9 2303 4000 9b60 ffe1 2340 0090 60ff +da24 3460 ffb1 6000 0f03 4000 8560 ffcb +2434 60ff a240 0076 26a0 0080 3802 2000 +016c a00a 8b40 0161 26a0 0003 3834 a0ce +bb29 2000 0222 6ca0 0727 356c a008 6240 +0085 60fc 5240 004a 6000 ce80 0209 8050 +1f80 801d 6000 3b60 00c8 8000 0820 001e +6000 b606 8002 0920 0008 0260 0096 0340 +0020 8004 0920 0006 6000 8940 0010 22a0 +0a79 a00a 5940 00d3 6000 0760 ff20 0460 +0000 6000 1207 2000 0b22 02a0 0a93 a00a +6840 00b7 40f8 afa0 0100 a180 fa33 a1a0 +0100 2b20 0004 a180 f633 6c80 ea32 6c60 +fff9 3880 e233 6c2f 9406 2000 0402 2262 +6cef 2e21 40ff f180 0f1c 800a 8b20 0005 +1980 6118 6c02 8030 186c 9480 3019 0680 +090a 0c6c 8027 1906 800a 1980 050a 0c6c +0280 ff6c e000 00c0 407f 60ff ddc0 000f +7821 9420 fff1 226f 6ca6 6000 1624 2139 +036c 2660 ffc4 0120 0005 1480 0008 6c21 +40ff f094 2000 0221 6c21 40ff f6a6 60ff +f224 3924 2f27 3824 a820 000c 94d4 4f09 +2000 0521 6140 fff0 6228 6c80 0f16 2000 +25a0 010f 1760 00a1 8020 60f8 2260 0099 +a00a 2060 0093 a000 4060 008d a003 0434 +a000 0040 0043 2222 6c80 0f16 2000 38a0 +010f 17a0 0a2e 6000 7080 2060 f7f1 6000 +68a0 0a20 6000 62a0 0000 a121 34a0 0003 +3860 0055 26a0 0008 3834 24a0 0006 3834 +a000 0338 4000 0222 6ca0 0a09 6000 3a60 +0037 803a 60f7 b860 0035 800a 60f7 b06c +800f 1620 001f a001 0f17 6000 1c80 2060 +f79d 2460 0013 a00a 2060 000d 6000 0a80 +0a60 f78b 6c22 2222 6ca0 015f 40fe b8e0 +ff00 a000 0abb af3a 396f 419d 20ff f322 +0380 3018 60f7 6847 58cf 20ff f362 6c41 +7373 656d 626c 6564 0020 696e 2000 2062 +7974 6573 2e0a 002d 2d20 556e 7573 6564 +3a20 0052 6566 6572 656e 6365 7300 5265 +6665 7265 6e63 6500 5379 6d62 6f6c 7300 +5379 6d62 6f6c 004d 6163 726f 7300 4d61 +6372 6f00 4e61 6d65 004e 756d 6265 7200 +436f 6d6d 656e 7400 5772 6974 696e 6700 +6578 6365 6564 6564 0069 6e76 616c 6964 +0064 7570 6c69 6361 7465 0074 6f6f 2066 +6172 007a 6572 6f2d 7061 6765 006f 7065 +6e00 2e2e 0052 4553 4554 diff --git a/utils/drifloon.tal b/utils/drifloon.tal new file mode 100644 index 0000000..bfef597 --- /dev/null +++ b/utils/drifloon.tal @@ -0,0 +1,732 @@ +( cat input.tal | uxncli drifloon.rom > output.rom ) + +|00 @System/vector $2 &expansion $2 &wst $1 &rst $1 &metadata $2 &r $2 &g $2 &b $2 &debug $1 &state $1 +|10 @Console/vector $2 &read $1 &pad $4 &type $1 &write $1 &error $1 + +|000 + + @scope/buf $3f &cap $1 + @token/buf $3f &cap $1 + +|100 + +@on-reset ( -> ) + ;meta #06 DEO2 + ;dict/reset scope/ + ;assembly/on-console .Console/vector DEO2 + BRK + +@assembly/on-console ( -> ) + [ LIT2 04 -Console/type ] DEI EQU ?{ .Console/read DEI token/ + BRK } + / + BRK + +@rom/ ( byte addr* -- ) + ;&mem ADD2 STA + JMP2r + +@rom/ ( -- ) + ;dict/assembled err/ + ;dict/in err/ + ;head/length LDA2 DUP2 #0100 SUB2 err/ + ;dict/bytes err/ + ( | write ) + ;rom/mem ADD2 ;rom/output + &>l + LDAk #18 DEO + INC2 GTH2k ?&>l + POP2 POP2 JMP2r + +@runes/concat ( t* -- ) + POP2 JMP2r + +@err/ ( c -- ) + #19 DEO JMP2r + +@meta $1 + ( name ) "Drifloon 0a + ( desc ) "Uxntal 20 "Assembler 0a + ( auth ) "By 20 "Devine 20 "Lu 20 "Linvega 0a + ( date ) "8 20 "Nov 20 "2025 $2 + + + +( Core ) + +@assembly/ ( -- ) + ( cap ) #0a token/ + ,&mode LDR2 ;comment/assemble NEQ2 ?{ + ( ! ) ;dict/open ;dict/trail ;dict/Comment err/ } + ,&mode LDR2 ;macros/assemble NEQ2 ?{ + ( ! ) ;dict/open ;dict/trail ;dict/Macro err/ } + .System/state DEI ?{ + refs/ + .System/state DEI ?{ + [ LIT2 80 -System/state ] DEO !syms/ } } + JMP2r + +@assembly/apply ( t* -- ) + LDZk ?{ POP2 JMP2r } + [ LIT2 &mode =standard/assemble ] JMP2 + +( +@|Standard ) + +@standard/ ( -- ) + ;&assemble ;assembly/mode STA2 + JMP2r + +@standard/assemble ( t* -- ) + ( hex ) str/is-hex ?rom/ + ( opc ) opcodes/is-opcode ?rom/ + LDZk runes/find INC2k ORA ?{ + POP2 + ( mac ) DUP2 macros/find-name INC2k ORA ?macros/ + POP2 + ( imm ) !runes/litjsi } + INC2 LDA2 JMP2 + +( +@|Comment ) + +@comment/ ( t* -- ) + POP2 ;&assemble ;assembly/mode STA2 + [ LIT2 01 _&depth ] STR + JMP2r + +@comment/assemble ( t* -- ) + LDA2 DUP2 [ LITr &depth $1 ] + ( a ) LIT2 "( $1 EQU2 [ STH ADDr ] + ( b ) LIT2 ") $1 EQU2 [ STH SUBr ] + ( . ) STHkr LITr _&depth STRr + ?{ !standard/ } + JMP2r + +( +@|Macros ) + +@macros/ ( t* -- ) + name/ + / + #00 / + ;&walk ;assembly/mode STA2 + JMP2r + + &walk ( t* -- ) + LDA2 [ LIT2 "{ $1 ] NEQ2 ?{ + ;&assemble ;assembly/mode STA2 + [ LIT2 01 _&depth ] STR } + JMP2r + +@macros/assemble ( t* -- ) + LDA2k DUP2 [ LITr &depth $1 ] + ( a ) LIT "{ EQU SWP LIT "{ EQU ORA [ STH ADDr ] + ( b ) LIT2 "} $1 EQU2 [ STH SUBr ] + ( . ) STHkr LITr _&depth STRr + ?{ POP2 #00 / !standard/ } + / + #20 !/ + +@macros/ ( t* -- ) + ;/ !hof/ + +@macros/ ( byte -- ) + [ LIT2 &ptr =&mem ] INC2k + ( | check overflow ) + DUP2 ;&memend LTH2 ?{ + ( ! ) ;dict/exceeded ;dict/Macros err/ } + ,&ptr STR2 + STA + JMP2r + +@macros/find-name ( name* -- * ) + STH2 + ,&ptr LDR2 ;&mem + &>lf + DUP2 STH2kr str/cmp ?{ + str/cap str/cap GTH2k ?&>lf + POP2 #ffff } + NIP2 POP2r JMP2r + +@macros/ ( t* macro* -- ) + NIP2 token/ + str/cap ;token/ !hof/ + +( +@|Token ) + +@token/ ( -- ) + [ LIT2 -&buf _&ptr ] STR + [ LIT2 00 -&buf ] STZ + JMP2r + +@token/ ( c -- ) + DUP #20 GTH ?{ + ;&buf assembly/apply #0a NEQ ?{ + [ LIT2 &line 0001 ] INC2 ,&line STR2 } + !/ } + [ LIT2 00 &ptr -&buf ] INCk + ( | check overflow ) + DUP .&cap LTH ?{ + ( ! ) ;dict/exceeded ;dict/Name err/ } + ,&ptr STR + STZ2 + JMP2r + +( +@|Scope ) + +@scope/ ( c -- ) + [ LIT2 00 &ptr -&buf ] INCk + ( | check overflow ) + DUP .&cap LTH ?{ + ( ! ) ;dict/exceeded ;dict/Symbol err/ } + ,&ptr STR + STZ2 + JMP2r + +@scope/ ( name* -- ) + [ LIT2 -&buf _&ptr ] STR + &>w + LDAk [ LIT "/ ] EQU ?{ + LDAk / + INC2 LDAk ?&>w } + POP2 ,&ptr LDR ,&anchor STR + JMP2r + +@scope/make-name ( name* -- scope/label* ) + INC2 [ LIT2 &anchor $1 _&ptr ] STR + [ LIT "/ ] / + ;&buf SWP2 ;/ !hof/ + +( +@|Runes ) + +@runes/find ( char -- * ) + STH + ;&lut + &>w + LDAk STHkr EQU ?{ + #0003 ADD2 LDAk ?&>w + POP2 #ffff } + POPr JMP2r + +@runes/ignore ( t* -- ) + POP2 JMP2r + + &lambda ( t* -- ) + POP2 !lambda/pop + + &coment ( t* -- ) + !comment/ + + ¯os ( t* -- ) + /req-name !macros/ + + &padabs ( t* -- ) + /req-name syms/find-addr !head/ + + &padrel ( t* -- ) + /req-name syms/find-addr !head/ + + &toplab ( t* -- ) + /req-name DUP2 scope/ !syms/ + + &sublab ( t* -- ) + scope/make-name !syms/ + + &litrel ( t* -- ) + #80 rom/ &rawrel /req-name refs/get-rel !rom/ + + &litzep ( t* -- ) + #80 rom/ &rawzep /req-name refs/get-abs !rom/ + + &litabs ( t* -- ) + #a0 rom/ &rawabs /req-name refs/get-abs2 !rom/ + + &litjci ( t* -- ) + /req-name #20 !rom/ + + &litjmi ( t* -- ) + /req-name #40 !rom/ + + &litjsi ( t* -- ) + #60 !rom/ + + &lithex ( t* -- ) + /req-name !rom/ + + &rawstr ( t* -- ) + /req-name !rom/ + +@runes/req-name ( str* -- str1* ) + INC2 LDAk #20 GTH ?{ ;dict/invalid ;dict/Name !err/ } + JMP2r + +@runes/lut [ + "| =&padabs "$ =&padrel + "@ =&toplab "& =&sublab + ", =&litrel "_ =&rawrel + ". =&litzep "- =&rawzep + "; =&litabs "= =&rawabs + "! =&litjmi "? =&litjci + "# =&lithex "" =&rawstr + "} =&lambda "~ =&concat + "( =&coment ") =&ignore + "[ =&ignore "] =&ignore "% =¯os ] $1 + +( +@|Opcodes ) + +@opcodes/is-opcode ( str* -- str* bool ) + DUP2 /parse #00 NEQ STH + DUP2 ;&brk str/cmp STHr ORA JMP2r + +@opcodes/parse ( str* -- byte ) + [ LIT2r 1f00 ] ;&lut + &>w1 + SWP2k #0003 SWP2 mem/cmp ?{ + INCr #0003 ADD2 LDAk ?&>w1 + POP2 POP2 POP2r #00 JMP2r } + POP2 + ( mask ) ANDr + ( litk ) LDA2k [ LIT2 "LI ] EQU2 #70 SFT [ STH ORAr ] + ( move ) #0003 ADD2 + &>w2 + LDAk #21 LTH ?{ + ( | parse modes ) + LDAk [ LIT "2 ] NEQ ?{ LITr 20 !&r } + LDAk [ LIT "r ] NEQ ?{ LITr 40 !&r } + LDAk [ LIT "k ] NEQ ?{ LITr 80 !&r } + POP2 POPr #00 JMP2r + &r ORAr INC2 !&>w2 } + POP2 STHr JMP2r + +@opcodes/lut [ + "LIT "INC "POP "NIP "SWP "ROT "DUP "OVR + "EQU "NEQ "GTH "LTH "JMP "JCN "JSR "STH + "LDZ "STZ "LDR "STR "LDA "STA "DEI "DEO + "ADD "SUB "MUL "DIV "AND "ORA "EOR "SFT ] + &brk "BRK $1 + +( +@|Lambda ) + +@lambda/make-name ( token* -- name* ) + POP2 [ LIT &count $1 ] INCk ,&count STR + DUP [ LIT2 &ptr =&mem ] INC2k ,&ptr STR2 + STA + ( >> ) + +@lambda/name ( id -- str* ) + DUP #04 SFT hexc SWP hexc ,&id STR2 + ;&sym JMP2r + +@lambda/pop ( -- ) + ,&ptr LDR2 #0001 SUB2 LDAk /name syms/ + ,&ptr STR2 + JMP2r + &sym cebb + &id ".. $1 + +( +@|Name ) + +@name/ ( name* -- name* ) + ( not hex ) str/is-hex ?&fail + ( not lambda ) LDAk LIT "{ EQU ?&fail + ( not runic ) LDAk runes/find INC2 ORA ?&fail + ( dup macros ) DUP2 macros/find-name INC2 ORA ?&dup + ( dup symbol ) DUP2 syms/find-name INC2 ORA ?&dup + ( not opcode ) opcodes/is-opcode [ JMP JMP2r ] + &fail ( -- ) + ;dict/invalid ;dict/Name !err/ + + &dup ( -- ) + ;dict/duplicate ;dict/Name !err/ + +@name/unpack ( name* -- name* ) + LDAk [ LIT "{ ] EQU ?lambda/make-name + LDAk [ LIT "/ ] EQU ?scope/make-name + LDAk [ LIT "& ] EQU ?scope/make-name + JMP2r + +( +@|Syms ) + +@syms/ ( name* -- ) + DUP2 /find-name INC2k ORA ?{ + POP2 ;&ptr LDA2 refs/ + .SymType/declared head/get !/ } + ( | name* sym* -- ) + NIP2 DUP2 refs/ + /is-declared ?{ head/get OVR2 STA2 !/ } + POP2 + ( ! ) ;dict/duplicate ;dict/Symbol !err/ + +@syms/ ( name* type addr* -- ) + ( hb ) SWP / + ( lb ) / + ( type ) / + name/ + ;/ hof/ + #00 + ( >> ) + +@syms/ ( byte -- ) + [ LIT2 &ptr =&mem ] INC2k + ( | check overflow ) + DUP2 ;&memend LTH2 ?{ + ( ! ) ;dict/exceeded ;dict/Symbols err/ } + ,&ptr STR2 + STA + JMP2r + +@syms/find-name ( name* -- * ) + STH2 + ,&ptr LDR2 ;&mem + &>lfn + DUP2 #0003 ADD2 STH2kr str/cmp ?{ + #0003 ADD2 str/cap GTH2k ?&>lfn + POP2 #ffff } + NIP2 POP2r JMP2r + +@syms/find-alloc ( name* -- * ) + DUP2 /find-name INC2k ORA ?{ + ( null* .. next* ) POP2 ,&ptr LDR2 + ( alloc ) SWP2 .SymType/used #ffff !/ } + NIP2 JMP2r + +@syms/find-addr ( name* -- * ) + str/is-hex ?str/hex + name/unpack /find-name /is-defined ?{ + ( ! ) ;dict/invalid ;dict/Symbol err/ } + /use LDA2 JMP2r + +@syms/ ( -- ) + ;&ptr LDA2 ;&mem + &>ls + EQU2k ?{ + /is-used ?{ + LDA2k #0100 EQU2 ?{ + DUP2 #0003 ADD2 LDAk [ LIT "A ] SUB #1a LTH ?{ + ;dict/unused err/ + DUP2 err/ + #0a err/ } + POP2 } } + #0003 ADD2 str/cap !&>ls } + POP2 POP2 !rom/ + +@syms/byte-distance ( addr* -- addr* ) + DUP2 #0080 ADD2 POP ?{ JMP2r } + ( ! ) ;dict/too-far ;dict/Symbol !err/ + +@syms/is-defined ( sym* -- sym* t ) + INC2k ORA ?{ #00 JMP2r } + ( >> ) + +@syms/is-declared ( sym* -- sym* t ) + INC2k INC2 LDA .SymType/declared AND JMP2r + +@syms/is-used ( sym* -- sym* t ) + INC2k INC2 LDA .SymType/used AND JMP2r + +@syms/use ( sym* -- sym* ) + INC2k INC2 STH2k LDA .SymType/used ORA STH2r STA + JMP2r + +@syms/ ( sym* -- ) + INC2 INC2 STH2k LDA .SymType/declared ORA STH2r STA + JMP2r + +( +@|References ) + +@refs/get-type ( token* type* -- addr* ) + ,&type STR2 + name/unpack syms/find-alloc syms/is-declared ?{ + DUP2 head/get + ( addr* ) / + ( symbol* ) / + ( type-fn* ) [ LIT2 &type $2 ] / + ( scope* ) [ LIT2 &scope $2 ] / + ( line* ) ;token/line LDA2 / } + ( | mark as used ) + syms/use LDA2 JMP2r + +@refs/ ( value* -- ) + SWP / + ( >> ) + +@refs/ ( byte -- ) + [ LIT2 &ptr =&mem ] INC2k + ( | check overflow ) + DUP2 ;&memend LTH2 ?{ + ( ! ) ;dict/exceeded ;dict/References err/ } + ,&ptr STR2 + STA + JMP2r + +@refs/get-abs ( label* -- addr ) + ;&handle-abs /get-type NIP JMP2r + +@refs/get-abs2 ( label* -- addr* ) + ;&handle-abs2 !/get-type + +@refs/get-rel ( label* -- distance ) + ;&handle-rel /get-type INC2k ORA ?{ + ( undefined ) POP2 #00 JMP2r } + head/get /get-distance syms/byte-distance NIP JMP2r + +@refs/get-rel2 ( label* -- distance* ) + ;&handle-rel2 /get-type head/get + ( >> ) + +@refs/get-distance ( a* b* -- distance* ) + INC2 INC2 SUB2 JMP2r + +@refs/ ( -- ) + ,&ptr LDR2 ;&mem + &>l + EQU2k ?{ + DUP2 ;err/ref STA2 + DUP2k #0004 ADD2 LDA2 JSR2 + ( ) #000a ADD2 !&>l } + POP2 POP2 JMP2r + +@refs/resolve-sym ( ref* -- ref* sym/addr* ) + LDA2k head/ + ( ref* sym* ) INC2k INC2 LDA2 + ( ref* sym/addr* ) LDA2 + ( ref* sym/addr* ) INC2k ORA ?{ + ( ! ) ;dict/invalid !err/ } + ( ref* sym/addr* ) JMP2r + +@refs/handle-abs ( ref* -- ) + /resolve-sym NIP2 NIP !rom/ + +@refs/handle-abs2 ( ref* -- ) + /resolve-sym NIP2 !rom/ + +@refs/handle-rel ( ref* -- ) + /resolve-sym SWP2 LDA2 /get-distance /byte-distance NIP !rom/ + +@refs/handle-rel2 ( ref* -- ) + /resolve-sym SWP2 LDA2 /get-distance !rom/ + +@refs/byte-distance ( addr* -- addr* ) + DUP2 #0080 ADD2 POP ?{ JMP2r } + ( ! ) ;dict/too-far !err/ + +@refs/ ( sym* -- ) + DUP2 #0003 ADD2 LDA2 #cebb NEQ2 ?{ POP2 JMP2r } + ;refs/scope STA2 + JMP2r + +( +@|Rom ) + +@rom/ ( str* -- ) + ;/ !hof/ + +@rom/ ( str* -- ) + opcodes/parse !/ + +@rom/ ( str* -- ) + str/len #02 NEQ #50 SFT #80 ORA / + ( >> ) + +@rom/ ( str* -- ) + str/is-hex #00 EQU ?{ + str/len DUP #02 NEQ ?{ POP str/hex NIP !/ } + #04 NEQ ?{ str/hex !/ } } + POP2 ;dict/invalid ;dict/Number !err/ + +@rom/ ( str* opc -- ) + / + refs/get-rel2 + ( >> ) + +@rom/ ( short* -- ) + SWP / + ( >> ) + +@rom/ ( byte -- ) + head/get-inc + ( | test zero-page ) + OVR ?{ + POP2 POP + ( ! ) ;dict/zero-page ;dict/Writing !err/ } + !rom/ + +@head/get-inc ( -- addr* ) + [ LIT2 &addr 0100 ] INC2k ,&addr STR2 + INC2k [ LIT2 &length 0100 ] LTH2 ?{ INC2k ,&length STR2 } + JMP2r + +@head/get ( -- addr* ) + ,&addr LDR2 JMP2r + +@head/ ( addr* -- ) + /get ADD2 + ( >> ) + +@head/ ( addr* -- ) + ,&addr STR2 + JMP2r + +( +@|Stdlib ) + +@hof/ ( data* byte-fn* -- ) + STH2 + &>w + LDAk DUP ?{ POP POP2 POP2r JMP2r } + STH2kr JSR2 INC2 !&>w + +@hexc ( hex -- char ) + #0f AND #0a LTHk ?{ + SUB [ LIT "a ] ADD JMP2r } + POP [ LIT "0 ] ADD JMP2r + +@chex ( addr* -- addr* ) + LDAk + ( dec ) [ LIT "0 ] SUB DUP #09 GTH [ JMP JMP2r ] + ( hex ) #27 SUB DUP #0a SUB #05 GTH [ JMP JMP2r ] + ( nil ) POP #ff JMP2r + +@str/hex ( str* -- value* ) + [ LIT2r 0000 ] + &>wh + [ LITr 40 ] SFT2r chex [ LITr 00 ] STH + ADD2r INC2 LDAk ?&>wh + POP2 STH2r JMP2r + +@str/len ( str* -- str* length ) + DUP2k /cap SWP2 INC2 SUB2 NIP JMP2r + +@str/is-hex ( str* -- str* f ) + DUP2 + &>wih + chex INC ?{ LDA #00 EQU JMP2r } + INC2 !&>wih + +@str/cap ( str* -- end* ) + LDAk ?{ INC2 JMP2r } + INC2 !/cap + +@str/cmp ( a* b* -- bool ) + DUP2k /cap SWP2 SUB2 SWP2 + ( >> ) + +@mem/cmp ( a* length* b* -- t ) + STH2 + OVR2 ADD2 SWP2 + &>l + EQU2k ?{ + LDAk LDAkr STHr NEQ ?{ INC2 INC2r !&>l } } + POP2r EQU2 JMP2r + +( +@|Error ) + +@err/ ( adj* topic* -- ) + .System/state DEI ?{ + [ LIT2 01 -System/state ] DEO + / + #20 / + / + ;dict/spacer / + ;token/buf / + ;token/line LDA2 ;scope/buf !/ } + POP2 POP2 JMP2r + +@err/ ( adj* -- ) + .System/state DEI ?{ + [ LIT2 01 -System/state ] DEO + ;dict/Reference / + #20 / + / + ;dict/spacer / + [ LIT2 &ref $2 ] INC2k INC2 LDA2 #0003 ADD2 / + DUP2 #0008 ADD2 LDA2 SWP2 #0006 ADD2 LDA2 #0003 ADD2 !/ } + POP2 JMP2r + +@err/ ( line* scope* -- ) + ;dict/in / + / + LIT ": / + / + #0a / + JMP2r + +@err/ ( adj* keyword* topic* -- ) + .System/state DEI ?{ + [ LIT2 01 -System/state ] DEO + / + #20 / + SWP2 / + ;dict/spacer / + / + #0a / + JMP2r } + POP2 POP2 POP2 JMP2r + +@err/ ( str* -- ) + ;/ !hof/ + +@err/ ( short* -- ) + [ LIT2r ff00 ] + &>read + #000a DIV2k STH2k MUL2 SUB2 STH2r INCr ORAk ?&>read + POP2 + &>write + NIP #30 ADD / + OVRr ADDr STHkr ?&>write + POP2r JMP2r + +@dict/assembled "Assembled $1 &in 20 "in 20 $1 &bytes 20 "bytes. 0a $1 + &unused "-- 20 "Unused + &spacer ": 20 $1 + &References "References $1 + &Reference "Reference $1 + &Symbols "Symbols $1 + &Symbol "Symbol $1 + &Macros "Macros $1 + &Macro "Macro $1 + &Name "Name $1 + &Number "Number $1 + &Comment "Comment $1 + &Writing "Writing $1 + &exceeded "exceeded $1 + &invalid "invalid $1 + &duplicate "duplicate $1 + &too-far "too 20 "far $1 + &zero-page "zero-page $1 + &open "open $1 + &trail ".. $1 + &reset "RESET $1 + +( +@|Buffers ) + +@lambda/mem $100 + +@macros/mem ( name\0, value\0 ) + $1000 &memend + +@refs/mem ( addr*, symbol*, type-fn*, scope*, line* ) + $3000 &memend + +@syms/mem ( addr*, SymType, name\0 ) + $4800 &memend + +@rom/mem ( zeropage ) + $100 + &output +( +@|Enums ) + + +|00 @SymType/empty $1 &used $1 &declared diff --git a/utils/opctest.tal b/utils/opctest.tal new file mode 100644 index 0000000..fe90997 --- /dev/null +++ b/utils/opctest.tal @@ -0,0 +1,509 @@ +|0013 + + @Zeropage &byte $1 &short $2 + @id $1 + +|100 + +@on-reset ( -> ) + + ( part 1 + > LIT2: Puts a short on the stack + > LIT: Puts a byte on the stack + > #06 DEO: Write to metadata ports + > #18 DEO: Write a letter in terminal ) + + ;meta #06 DEO2 + [ LIT2 "kO ] #18 DEO #18 DEO + [ LIT2 "1 18 ] DEO #0a18 DEO + + ( part 2 + > LITr: Put a byte on return stack + > STH: Move a byte from working stack to return stack + > STH2r: Move a short from return stack to working stack ) + + [ LITr "k ] [ LIT "O ] STH STH2r #18 DEO #18 DEO + [ LIT2 "2 18 ] DEO #0a18 DEO + + ( part 3 + > LIT2r: Put a short on return stack + > DUP: Duplicate byte + > ADDr: Add bytes on return stack ) + + [ LIT2r "k 4d ] #01 DUP STH ADDr STH ADDr STH2r #18 DEO #18 DEO + [ LIT2 "3 18 ] DEO #0a18 DEO + + ( part 4 + > JSI: Subroutine to relative short address + > JMP2r: Jumps to absolute address on return stack ) + + subroutine + [ LIT2 "4 18 ] DEO #0a18 DEO + + ( part 5 + > POP2: Removes a short from the stack + > INC2: Increments short on stack + > DUP2: Duplicate short + > LDA: load byte from absolute address + > JCI: Conditional subroutine to relative short address ) + + ;Dict/ok pstr + [ LIT2 "5 18 ] DEO #0a18 DEO + + ( part 6 + > JSR2: Jump to subroutine from short pointer + > LDAk: Non-destructive load byte from absolute address ) + + { "Ok $1 } STH2r ;pstr-jcn JSR2 + [ LIT2 "6 18 ] DEO #0a18 DEO + + ( part 7 + > Relative distance bytes ) + + rel-distance/entry SWP #18 DEO #18 DEO + [ LIT2 "7 18 ] DEO #0a18 DEO + + ( part xx + > GTH2k: Non-destructive greater-than short + > LDA2k: Non-destructive load short from absolute address + > STA2: Store short at absolute address ) + + [ LIT2r 0000 ] + ;tests/end ;tests + &l + run-test [ LITr 00 ] STH ADD2r + INC2 INC2 GTH2k ?&l + POP2 POP2 + STH2r ;tests/end ;tests SUB2 #01 SFT2 + EQU2 ;Dict/opctests test-part + + ( Part xx + > Testing that stacks are circular and wrapping + > Storing 12 at -1 and 34 at 0 ) + + POP #12 #34 ADD #46 EQU STH + POP #1234 ADD #46 EQU STH + POP2 #1111 #2222 ADD2 #3333 EQU2 + STHr AND STHr AND + ;Dict/stack-wrap test-part + + ( restore stack ) #0000 #0000 + + ( Part xx + > Testing RAM wrapping + > Storing 12 in 0xffff, and 34 in 0x0000 ) + + #1234 #ffff STA2 + ( LDA ) #0000 LDA #ffff LDA ADD #46 EQU + ( LDA2 ) #ffff LDA2 ADD #46 EQU + AND ;Dict/ram-wrap test-part + + ( Part xx + > Testing PC wrapping: split instruction + > Storing 80[LIT] in 0xffff, and 55[val8] in 0x0000 + > Storing 6c[JMP2r] in 0x0001 ) + + #8055 #ffff STA2 #6c #01 STZ #ffff JSR2 #55 EQU + ;Dict/pc-wrap test-part + + ( Part xx + > Testing PC wrapping: split value after instruction + > Storing a0[LIT2] in 0xfffe, + > and 0x55[hi-byte] in 0xffff, and 0xaa[lo-byte] in 0x0000 + > Storing 6c[JMP2r] in 0x0001 ) + + #a055 #fffe STA2 #aa6c #0000 STA2 #fffe JSR2 #55aa EQU2 + ;Dict/pc2-wrap test-part + + ( Part xx + > Testing that zero-page is wrapping ) + + #5678 #ff STZ2 + ( LDZ ) #00 LDZ #ff LDZ ADD #ce EQU + ( LDZ2 ) #ff LDZ2 ADD #ce EQU + AND ;Dict/zp-wrap test-part + + ( Part xx + > Testing that device page is wrapping ) + + #1234 #ff DEO2 + ( DEI ) #00 DEI #ff DEI ADD #46 EQU + ( DEI2 ) #ff DEI2 ADD #46 EQU + AND ;Dict/dev-wrap test-part + #0000 DEO #00ff DEO + + ( end ) + + [ LIT &fail 80 ] + DUP #80 EQU ;Dict/result test-part + #0f DEO + + #0a18 DEO + #010e DEO + +BRK + +( +@|metadata ) + +@meta 00 + ( name ) "Opctest 0a + ( details ) "A 20 "Testing 20 "Program 0a + ( author ) "By 20 "Devine 20 "Lu 20 "Linvega 0a + ( date ) "24 20 "Jun 20 "2025 $2 + +@test-part ( f name* -- ) + pstr ?{ + #01 ;on-reset/fail STA + ;Dict/failed !pstr } + ;Dict/passed !pstr + +@run-test ( addr* -- addr* f ) + + LDA2k JSR2 DUP ?&pass + ;Dict/missed pstr + [ LIT2 &name $2 ] pstr + [ LIT2 "# 18 ] DEO + [ LIT2 "a -id ] LDZ ADD #18 DEO + #0a18 DEO + #01 ;on-reset/fail STA + &pass + .id LDZ INC .id STZ + +JMP2r + +@set ( name* -- f ) + + ;run-test/name STA2 #01 + [ LIT2 ff -id ] STZ + +JMP2r + +@pstr ( str* -- ) + DUP2 LDA + DUP ?{ POP POP2 JMP2r } + #18 DEO + INC2 !pstr + +@pstr-jcn ( str* -- ) + LDAk #18 DEO + INC2 LDAk ,pstr-jcn JCN + POP2 + JMP2r + +@tests +=op-equ [ + =op-equ/a =op-equ/b =op-equ/c =op-equ/d + =op-equ/e =op-equ/f =op-equ/g =op-equ/h ] +=op-neq [ + =op-neq/a =op-neq/b =op-neq/c =op-neq/d + =op-neq/e =op-neq/f =op-neq/g =op-neq/h ] +=op-gth [ + =op-gth/a =op-gth/b =op-gth/c =op-gth/d + =op-gth/e =op-gth/f =op-gth/g =op-gth/h ] +=op-lth [ + =op-lth/a =op-lth/b =op-lth/c =op-lth/d + =op-lth/e =op-lth/f =op-lth/g =op-lth/h ] +=op-add [ + =op-add/a =op-add/b =op-add/c =op-add/d + =op-add/e =op-add/f =op-add/g =op-add/h ] +=op-sub [ + =op-sub/a =op-sub/b =op-sub/c =op-sub/d + =op-sub/e =op-sub/f =op-sub/g =op-sub/h ] +=op-mul [ + =op-mul/a =op-mul/b =op-mul/c =op-mul/d + =op-mul/e =op-mul/f =op-mul/g =op-mul/h ] +=op-div [ + =op-div/a =op-div/b =op-div/c =op-div/d =op-div/e + =op-div/f =op-div/g =op-div/h =op-div/i =op-div/j ] +=op-inc [ + =op-inc/a =op-inc/b =op-inc/c =op-inc/d + =op-inc/e =op-inc/f =op-inc/g =op-inc/h ] +=op-pop [ + =op-pop/a =op-pop/b =op-pop/c =op-pop/d + =op-pop/e =op-pop/f =op-pop/g =op-pop/h ] +=op-dup [ + =op-dup/a =op-dup/b ] +=op-nip [ + =op-nip/a =op-nip/b =op-nip/c =op-nip/d ] +=op-swp [ + =op-swp/a =op-swp/b ] +=op-ovr [ + =op-ovr/a =op-ovr/b ] +=op-rot [ + =op-rot/a =op-rot/b ] +=op-and [ + =op-and/a =op-and/b =op-and/c =op-and/d + =op-and/e =op-and/f =op-and/g =op-and/h ] +=op-ora [ + =op-ora/a =op-ora/b =op-ora/c =op-ora/d + =op-ora/e =op-ora/f =op-ora/g =op-ora/h ] +=op-eor [ + =op-eor/a =op-eor/b =op-eor/c =op-eor/d + =op-eor/e =op-eor/f =op-eor/g =op-eor/h ] +=op-sft [ + =op-sft/a =op-sft/b =op-sft/c =op-sft/d + =op-sft/e =op-sft/f =op-sft/g =op-sft/h ] +=op-stz [ + =op-stz/a =op-stz/b =op-stz/c =op-stz/d ] +=op-str [ + =op-str/a =op-str/b =op-str/c =op-str/d ] +=op-sta [ + =op-sta/a =op-sta/b =op-sta/c =op-sta/d ] +=op-jmp [ + =op-jmp/a =op-jmp/b ] +=op-jcn [ + =op-jcn/a =op-jcn/b =op-jcn/c =op-jcn/d ] +=op-jsr [ + =op-jsr/a =op-jsr/b ] +=op-sth [ + =op-sth/a =op-sth/b ] +=op-jci [ + =op-jci/a =op-jci/b =op-jci/c ] +=op-jmi [ + =op-jmi/a ] +=op-jsi [ + =op-jsi/a =op-jsi/b =op-jsi/c =op-jsi/d ] + &end + +@op-equ ;Dict/equ !set + &a #f8 #f8 EQU [ #01 ] EQU JMP2r + &b #01 #01 EQU [ #01 ] EQU JMP2r + &c #f8 #01 EQU [ #00 ] EQU JMP2r + &d #00 #ff EQU [ #00 ] EQU JMP2r + &e #f801 #f801 EQU2 [ #01 ] EQU JMP2r + &f #01f8 #01f8 EQU2 [ #01 ] EQU JMP2r + &g #f801 #01f8 EQU2 [ #00 ] EQU JMP2r + &h #01f8 #f801 EQU2 [ #00 ] EQU JMP2r +@op-neq ;Dict/neq !set + &a #f8 #f8 NEQ [ #00 ] EQU JMP2r + &b #01 #01 NEQ [ #00 ] EQU JMP2r + &c #f8 #01 NEQ [ #01 ] EQU JMP2r + &d #01 #f8 NEQ [ #01 ] EQU JMP2r + &e #f801 #f801 NEQ2 [ #00 ] EQU JMP2r + &f #01f8 #01f8 NEQ2 [ #00 ] EQU JMP2r + &g #f801 #01f8 NEQ2 [ #01 ] EQU JMP2r + &h #01f8 #f801 NEQ2 [ #01 ] EQU JMP2r +@op-gth ;Dict/gth !set + &a #f8 #f8 GTH [ #00 ] EQU JMP2r + &b #01 #01 GTH [ #00 ] EQU JMP2r + &c #f8 #01 GTH [ #01 ] EQU JMP2r + &d #01 #f8 GTH [ #00 ] EQU JMP2r + &e #f801 #f801 GTH2 [ #00 ] EQU JMP2r + &f #01f8 #01f8 GTH2 [ #00 ] EQU JMP2r + &g #f801 #01f8 GTH2 [ #01 ] EQU JMP2r + &h #01f8 #f801 GTH2 [ #00 ] EQU JMP2r +@op-lth ;Dict/lth !set + &a #f8 #f8 LTH [ #00 ] EQU JMP2r + &b #01 #01 LTH [ #00 ] EQU JMP2r + &c #f8 #01 LTH [ #00 ] EQU JMP2r + &d #01 #ff LTH [ #01 ] EQU JMP2r + &e #f801 #f801 LTH2 [ #00 ] EQU JMP2r + &f #01f8 #01f8 LTH2 [ #00 ] EQU JMP2r + &g #f801 #01f8 LTH2 [ #00 ] EQU JMP2r + &h #01f8 #f801 LTH2 [ #01 ] EQU JMP2r +@op-add ;Dict/add !set + &a #ff #00 ADD [ #ff ] EQU JMP2r + &b #01 #ff ADD [ #00 ] EQU JMP2r + &c #ff #ff ADD [ #fe ] EQU JMP2r + &d #12 #34 ADDk ADD ADD [ #8c ] EQU JMP2r + &e #ffff #0000 ADD2 [ #ffff ] EQU2 JMP2r + &f #0001 #ffff ADD2 [ #0000 ] EQU2 JMP2r + &g #ffff #ffff ADD2 [ #fffe ] EQU2 JMP2r + &h #fffe #ffff ADD2 [ #fffd ] EQU2 JMP2r +@op-sub ;Dict/sub !set + &a #ff #00 SUB [ #ff ] EQU JMP2r + &b #01 #ff SUB [ #02 ] EQU JMP2r + &c #ff #ff SUB [ #00 ] EQU JMP2r + &d #fe #ff SUB [ #ff ] EQU JMP2r + &e #ffff #0000 SUB2 [ #ffff ] EQU2 JMP2r + &f #0001 #ffff SUB2 [ #0002 ] EQU2 JMP2r + &g #ffff #ffff SUB2 [ #0000 ] EQU2 JMP2r + &h #fffe #ffff SUB2 [ #ffff ] EQU2 JMP2r +@op-mul ;Dict/mul !set + &a #00 #01 MUL [ #00 ] EQU JMP2r + &b #3f #e7 MUL [ #d9 ] EQU JMP2r + &c #37 #3f MUL [ #89 ] EQU JMP2r + &d #10 #02 MUL [ #20 ] EQU JMP2r + &e #1000 #0003 MUL2 [ #3000 ] EQU2 JMP2r + &f #abcd #1234 MUL2 [ #4fa4 ] EQU2 JMP2r + &g #8000 #0200 MUL2 [ #0000 ] EQU2 JMP2r + &h #2222 #0003 MUL2 [ #6666 ] EQU2 JMP2r +@op-div ;Dict/div !set + &a #10 #06 DIV [ #02 ] EQU JMP2r + &b #20 #20 DIV [ #01 ] EQU JMP2r + &c #34 #01 DIV [ #34 ] EQU JMP2r + &d #02 #ef DIV [ #00 ] EQU JMP2r + &e #02 #00 DIV [ #00 ] EQU JMP2r + &f #03e8 #0006 DIV2 [ #00a6 ] EQU2 JMP2r + &g #abcd #1234 DIV2 [ #0009 ] EQU2 JMP2r + &h #8000 #0200 DIV2 [ #0040 ] EQU2 JMP2r + &i #2222 #0003 DIV2 [ #0b60 ] EQU2 JMP2r + &j #0202 #0000 DIV2 [ #0000 ] EQU2 JMP2r +@op-inc ;Dict/inc !set + &a #01 INC [ #02 ] EQU JMP2r + &b #ff INC [ #00 ] EQU JMP2r + &c #fe INC [ #ff ] EQU JMP2r + &d #00 INC [ #01 ] EQU JMP2r + &e #0001 INC2 [ #0002 ] EQU2 JMP2r + &f #ffff INC2 [ #0000 ] EQU2 JMP2r + &g #fffe INC2 [ #ffff ] EQU2 JMP2r + &h #0000 INC2 [ #0001 ] EQU2 JMP2r +@op-pop ;Dict/pop !set + &a #0a #0b POP [ #0a ] EQU JMP2r + &b #0a #0b #0c POP POP [ #0a ] EQU JMP2r + &c #0a #0b #0c ADD POP [ #0a ] EQU JMP2r + &d #0a #0b #0c POP ADD [ #15 ] EQU JMP2r + &e #0a0b #0c0d POP2 [ #0a0b ] EQU2 JMP2r + &f #0a0b #0c0d #0e0f POP2 POP2 [ #0a0b ] EQU2 JMP2r + &g #0a0b #0c0d #0e0f ADD2 POP2 [ #0a0b ] EQU2 JMP2r + &h #0a0b #0c0d #0e0f POP2 ADD2 [ #1618 ] EQU2 JMP2r +@op-dup ;Dict/dup !set + &a #0a #0b DUP ADD ADD [ #20 ] EQU JMP2r + &b #0a0b DUP2 ADD2 [ #1416 ] EQU2 JMP2r +@op-nip ;Dict/nip !set + &a #12 #34 #56 NIP ADD [ #68 ] EQU JMP2r + &b #12 #34 #56 NIPk ADD2 ADD [ #f2 ] EQU JMP2r + &c #1234 #5678 #9abc NIP2 ADD2 [ #acf0 ] EQU2 JMP2r + &d #1234 #5678 #9abc NIP2k ADD2 ADD2 ADD2 [ #9e24 ] EQU2 JMP2r +@op-swp ;Dict/swp !set + &a #02 #10 SWP DIV [ #08 ] EQU JMP2r + &b #0a0b #0c0d SWP2 NIP2 [ #0a0b ] EQU2 JMP2r +@op-ovr ;Dict/ovr !set + &a #02 #10 OVR DIV ADD [ #0a ] EQU JMP2r + &b #0a0b #0c0d OVR2 NIP2 ADD2 [ #1416 ] EQU2 JMP2r +@op-rot ;Dict/rot !set + &a #02 #04 #10 ROT DIV ADD [ #0c ] EQU JMP2r + &b #0a0b #0c0d #0c0f ROT2 ADD2 NIP2 [ #161a ] EQU2 JMP2r +@op-and ;Dict/and !set + &a #fc #3f AND [ #3c ] EQU JMP2r + &b #f0 #0f AND [ #00 ] EQU JMP2r + &c #ff #3c AND [ #3c ] EQU JMP2r + &d #02 #03 AND [ #02 ] EQU JMP2r + &e #f0f0 #00f0 AND2 [ #00f0 ] EQU2 JMP2r + &f #aaaa #5555 AND2 [ #0000 ] EQU2 JMP2r + &g #ffff #1234 AND2 [ #1234 ] EQU2 JMP2r + &h #abcd #0a0c AND2 [ #0a0c ] EQU2 JMP2r +@op-ora ;Dict/ora !set + &a #0f #f0 ORA [ #ff ] EQU JMP2r + &b #ab #cd ORA [ #ef ] EQU JMP2r + &c #12 #34 ORA [ #36 ] EQU JMP2r + &d #88 #10 ORA [ #98 ] EQU JMP2r + &e #0f0f #f0f0 ORA2 [ #ffff ] EQU2 JMP2r + &f #abab #cdcd ORA2 [ #efef ] EQU2 JMP2r + &g #1122 #1234 ORA2 [ #1336 ] EQU2 JMP2r + &h #8888 #1000 ORA2 [ #9888 ] EQU2 JMP2r +@op-eor ;Dict/eor !set + &a #00 #00 EOR [ #00 ] EQU JMP2r + &b #ff #00 EOR [ #ff ] EQU JMP2r + &c #aa #55 EOR [ #ff ] EQU JMP2r + &d #ff #ff EOR [ #00 ] EQU JMP2r + &e #ffff #ff00 EOR2 [ #00ff ] EQU2 JMP2r + &f #aaaa #5555 EOR2 [ #ffff ] EQU2 JMP2r + &g #1122 #1234 EOR2 [ #0316 ] EQU2 JMP2r + &h #8888 #1000 EOR2 [ #9888 ] EQU2 JMP2r +@op-sft ;Dict/sft !set + &a #ff #08 SFT [ #00 ] EQU JMP2r + &b #ff #e0 SFT [ #00 ] EQU JMP2r + &c #ff #11 SFT [ #fe ] EQU JMP2r + &d #ff #12 SFT [ #7e ] EQU JMP2r + &e #ffff #01 SFT2 [ #7fff ] EQU2 JMP2r + &f #ffff #70 SFT2 [ #ff80 ] EQU2 JMP2r + &g #ffff #7e SFT2 [ #0180 ] EQU2 JMP2r + &h #ffff #e3 SFT2 [ #c000 ] EQU2 JMP2r +@op-stz ;Dict/stz !set + &a #ab .Zeropage/byte STZ .Zeropage/byte LDZ [ #ab ] EQU JMP2r + &b #cd .Zeropage/byte STZ .Zeropage/byte LDZ [ #cd ] EQU JMP2r + &c #1234 .Zeropage/short STZ2 .Zeropage/short LDZ2 [ #1234 ] EQU2 JMP2r + &d #5678 .Zeropage/short STZ2 .Zeropage/short LDZ2 [ #5678 ] EQU2 JMP2r +@op-str ;Dict/str !set + [ LIT &before1 $1 ] [ LIT2 &before2 $2 ] + &a #22 ,&before1 STR ,&before1 LDR [ #22 ] EQU JMP2r + &b #ef ,&after1 STR ,&after1 LDR [ #ef ] EQU JMP2r + &c #1234 ,&before2 STR2 ,&before2 LDR2 [ #1234 ] EQU2 JMP2r + &d #5678 ,&after2 STR2 ,&after2 LDR2 [ #5678 ] EQU2 JMP2r + [ LIT &after1 $1 ] [ LIT2 &after2 $2 ] +@op-sta ;Dict/sta !set + &a #34 ;Absolute/byte STA ;Absolute/byte LDA [ #34 ] EQU JMP2r + &b #56 ;Absolute/byte STA ;Absolute/byte LDA [ #56 ] EQU JMP2r + &c #1234 ;Absolute/short STA2 ;Absolute/short LDA2 [ #1234 ] EQU2 JMP2r + &d #5678 ;Absolute/short STA2 ;Absolute/short LDA2 [ #5678 ] EQU2 JMP2r +@op-jmp ;Dict/jmp !set + &a #12 #34 ,&reljmp JMP SWP &reljmp POP [ #12 ] EQU JMP2r + &b #56 #78 ;&absjmp JMP2 SWP &absjmp POP [ #56 ] EQU JMP2r +@op-jcn ;Dict/jcn !set + &a #23 #01 ,&reljcn-y JCN INC &reljcn-y [ #23 ] EQU JMP2r + &b #23 #00 ,&reljcn-n JCN INC &reljcn-n [ #24 ] EQU JMP2r + &c #23 #01 ;&absjcn-y JCN2 INC &absjcn-y [ #23 ] EQU JMP2r + &d #23 #00 ;&absjcn-n JCN2 INC &absjcn-n [ #24 ] EQU JMP2r +@op-jsr ;Dict/jsr !set + &a #1234 #5678 ,&routine JSR [ #68ac ] EQU2 JMP2r + &b #12 #34 ;routine JSR2 [ #46 ] EQU JMP2r + &routine ADD2 JMP2r +@op-sth ;Dict/sth !set + &a #0a STH #0b STH ADDr STHr [ #15 ] EQU JMP2r + &b #000a STH2 #000b STH2 ADD2r STH2r [ #0015 ] EQU2 JMP2r +@op-jci ;Dict/jci !set + &before #01 JMP2r + &a #01 ?&skip-a #00 JMP2r &skip-a #01 JMP2r + &b #00 ?&skip-b #01 JMP2r &skip-b #00 JMP2r + &c #01 ?&before #00 JMP2r +@op-jmi ;Dict/jmi !set + &a !&skip-a #00 JMP2r &skip-a #01 JMP2r +@op-jsi ;Dict/jsi !set + &a #02 #04 routine #06 EQU JMP2r + &b ;&return special &return JMP2r + &c ,&skip-c JMP &routine-c ADD JMP2r &skip-c #02 #04 op-jsi/routine-c #06 EQU JMP2r + &d ,&skip-d JMP &routine-d ADD JMP2r &skip-d #02 #04 op-jsi-far-routine-d #06 EQU JMP2r + +@special ( routine* -- f ) + + ( test that the stack order is LIFO ) + DUP2 STH2kr EQU2 + ROT ROT DUP2r STHr STHr SWP EQU2 AND + +JMP2r + +@routine ( a b -- c ) ADD JMP2r +@subroutine ( -- ) [ LIT2 "kO ] #18 DEO #18 DEO JMP2r +@Absolute &byte $1 &short $2 + +@Dict [ + &ok "Ok $1 + &done "Tests 20 "Complete. 0a $1 + &opctests "Opcodes $1 + &stack-wrap "Stack-wrap $1 + &ram-wrap "RAM-wrap $1 + &pc-wrap "PC-wrap $1 + &pc2-wrap "PC2-wrap $1 + &zp-wrap "Zeropage-wrap $1 + &dev-wrap "Devices-wrap $1 + &result "Result: $1 + &passed 20 "passed! 0a $1 + &missed "Opcode 20 "Failed 20 "-- 20 $1 + &failed 20 "failed. 0a $1 + &equ "EQU $1 &neq "NEQ $1 >h "GTH $1 <h "LTH $1 + &add "ADD $1 &sub "SUB $1 &mul "MUL $1 &div "DIV $1 + &inc "INC $1 &pop "POP $1 &dup "DUP $1 &nip "NIP $1 + &swp "SWP $1 &ovr "OVR $1 &rot "ROT $1 + &and "AND $1 &ora "ORA $1 &eor "EOR $1 &sft "SFT $1 + &stz "STZ $1 &str "STR $1 &sta "STA $1 + &jmp "JMP $1 &jcn "JCN $1 &jsr "JSR $1 &sth "STH $1 + &jmi "JMI $1 &jci "JCI $1 &jsi "JSI $1 +] + +( +@|Relative Distance Bytes ) + +@rel-distance +&back "O $7c +&entry + ,&back LDR + ,&forw LDR + JMP2r +$7e +&forw "k + +@op-jsi-far-routine-d + op-jsi/routine-d JMP2r + diff --git a/utils/uxnmin b/utils/uxnmin new file mode 100755 index 0000000000000000000000000000000000000000..93e70d3a0c7263e133e927219e3e502219012795 GIT binary patch literal 192320 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≪C>HC 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 NqZrJgP)Rf{0{|Sr1U~=( literal 0 HcmV?d00001 diff --git a/utils/uxnmin.c b/utils/uxnmin.c new file mode 100644 index 0000000..d29a854 --- /dev/null +++ b/utils/uxnmin.c @@ -0,0 +1,135 @@ +#include + +static unsigned int console_vector; +static unsigned char ram[0x10000], dev[0x100], ptr[2], stk[2][0x100]; + +static unsigned char +emu_dei(const unsigned char port) +{ + return dev[port]; +} + +static void +emu_deo(const unsigned char port, const unsigned char value) +{ + dev[port] = value; + switch(port) { + case 0x11: console_vector = dev[0x10] << 8 | value; return; + case 0x18: fputc(value, stdout); return; + case 0x19: fputc(value, stderr); return; + } +} + +#define REM ptr[_r] -= 1 + _2; +#define DEC(m) stk[m][--ptr[m]] +#define INC(m) stk[m][ptr[m]++] +#define IMM(r) { r = ram[pc++] << 8, r |= ram[pc++]; } +#define MOV(x) { if(_2) pc = x; else pc += (signed char)x; } +#define PO1(o) o = DEC(_r); +#define PO2(o) { PO1(o) o |= DEC(_r) << 8; } +#define POx(o) if(_2) PO2(o) else PO1(o) +#define GOT(o) if(_2) PO1(o[1]) PO1(o[0]) +#define DEO(o,r) emu_deo(o, r[0]); if(_2) emu_deo(o + 1, r[1]); +#define POK(o,r,m) ram[o] = r[0]; if(_2) ram[(o + 1) & m] = r[1]; +#define RP1(i) INC(!_r) = i; +#define PU1(i) INC(_r) = i; +#define PUx(i) if(_2) { c = (i); PU1(c >> 8) PU1(c) } else PU1(i) +#define PUT(i) PU1(i[0]) if(_2) PU1(i[1]) +#define DEI(i,r) r[0] = emu_dei(i); if(_2) r[1] = emu_dei(i + 1); PUT(r) +#define PEK(i,r,m) r[0] = ram[i]; if(_2) r[1] = ram[(i + 1) & m]; PUT(r) + +#define NEXT if(--cycles) goto step; else return 0; +#define OPC(opc, A, B) {\ + case 0x00|opc: {const int _2=0,_r=0;A B} NEXT\ + case 0x20|opc: {const int _2=1,_r=0;A B} NEXT\ + case 0x40|opc: {const int _2=0,_r=1;A B} NEXT\ + case 0x60|opc: {const int _2=1,_r=1;A B} NEXT\ + case 0x80|opc: {const int _2=0,_r=0;int k=ptr[0];A ptr[0]=k;B} NEXT\ + case 0xa0|opc: {const int _2=1,_r=0;int k=ptr[0];A ptr[0]=k;B} NEXT\ + case 0xc0|opc: {const int _2=0,_r=1;int k=ptr[1];A ptr[1]=k;B} NEXT\ + case 0xe0|opc: {const int _2=1,_r=1;int k=ptr[1];A ptr[1]=k;B} NEXT } + +static unsigned int +uxn_eval(unsigned short pc) +{ + unsigned int a, b, c, x[2], y[2], z[2], cycles = 0x80000000; +step: + switch(ram[pc++]) { + /* BRK */ case 0x00: return 1; + /* JCI */ case 0x20: if(DEC(0)) { IMM(c) pc += c; } else pc += 2; NEXT + /* JMI */ case 0x40: IMM(c) pc += c; NEXT + /* JSI */ case 0x60: IMM(c) INC(1) = pc >> 8, INC(1) = pc, pc += c; NEXT + /* LI2 */ case 0xa0: INC(0) = ram[pc++]; /* fall-through */ + /* LIT */ case 0x80: INC(0) = ram[pc++]; NEXT + /* L2r */ case 0xe0: INC(1) = ram[pc++]; /* fall-through */ + /* LIr */ case 0xc0: INC(1) = ram[pc++]; NEXT + /* INC */ OPC(0x01,POx(a),PUx(a + 1)) + /* POP */ OPC(0x02,REM,{}) + /* NIP */ OPC(0x03,GOT(x) REM,PUT(x)) + /* SWP */ OPC(0x04,GOT(x) GOT(y),PUT(x) PUT(y)) + /* ROT */ OPC(0x05,GOT(x) GOT(y) GOT(z),PUT(y) PUT(x) PUT(z)) + /* DUP */ OPC(0x06,GOT(x),PUT(x) PUT(x)) + /* OVR */ OPC(0x07,GOT(x) GOT(y),PUT(y) PUT(x) PUT(y)) + /* EQU */ OPC(0x08,POx(a) POx(b),PU1(b == a)) + /* NEQ */ OPC(0x09,POx(a) POx(b),PU1(b != a)) + /* GTH */ OPC(0x0a,POx(a) POx(b),PU1(b > a)) + /* LTH */ OPC(0x0b,POx(a) POx(b),PU1(b < a)) + /* JMP */ OPC(0x0c,POx(a),MOV(a)) + /* JCN */ OPC(0x0d,POx(a) PO1(b),if(b) MOV(a)) + /* JSR */ OPC(0x0e,POx(a),RP1(pc >> 8) RP1(pc) MOV(a)) + /* STH */ OPC(0x0f,GOT(x),RP1(x[0]) if(_2) RP1(x[1])) + /* LDZ */ OPC(0x10,PO1(a),PEK(a, x, 0xff)) + /* STZ */ OPC(0x11,PO1(a) GOT(y),POK(a, y, 0xff)) + /* LDR */ OPC(0x12,PO1(a),PEK(pc + (signed char)a, x, 0xffff)) + /* STR */ OPC(0x13,PO1(a) GOT(y),POK(pc + (signed char)a, y, 0xffff)) + /* LDA */ OPC(0x14,PO2(a),PEK(a, x, 0xffff)) + /* STA */ OPC(0x15,PO2(a) GOT(y),POK(a, y, 0xffff)) + /* DEI */ OPC(0x16,PO1(a),DEI(a, x)) + /* DEO */ OPC(0x17,PO1(a) GOT(y),DEO(a, y)) + /* ADD */ OPC(0x18,POx(a) POx(b),PUx(b + a)) + /* SUB */ OPC(0x19,POx(a) POx(b),PUx(b - a)) + /* MUL */ OPC(0x1a,POx(a) POx(b),PUx(b * a)) + /* DIV */ OPC(0x1b,POx(a) POx(b),PUx(a ? b / a : 0)) + /* AND */ OPC(0x1c,POx(a) POx(b),PUx(b & a)) + /* ORA */ OPC(0x1d,POx(a) POx(b),PUx(b | a)) + /* EOR */ OPC(0x1e,POx(a) POx(b),PUx(b ^ a)) + /* SFT */ OPC(0x1f,PO1(a) POx(b),PUx(b >> (a & 0xf) << (a >> 4))) + } + return 0; +} + +static void +console_input(int c, unsigned int type) +{ + dev[0x12] = c, dev[0x17] = type; + if(console_vector && !dev[0x0f]) + uxn_eval(console_vector); +} + +int +main(int argc, char **argv) +{ + FILE *f; + if(argc < 2) + return fprintf(stdout, "usage: %s file.rom [args..]\n", argv[0]); + else if(!(f = fopen(argv[1], "rb"))) + return fprintf(stderr, "%s: %s not found.\n", argv[0], argv[1]); + fread(&ram[0x100], 0xff00, 1, f), fclose(f); + dev[0x17] = argc > 2; + if(uxn_eval(0x100) && console_vector) { + int i = 2; + for(; i < argc; i++) { + char c, *p = argv[i]; + while(!dev[0x0f] && (c = *p++)) + console_input(c, 2); + console_input(0, 3 + (i == argc - 1)); + } + while(!dev[0x0f]) { + char c = fgetc(stdin); + if(feof(stdin)) break; + console_input(c, 1); + } + console_input(0, 4); + } + return dev[0x0f] & 0x7f; +} diff --git a/uxn.opam b/uxn.opam new file mode 100644 index 0000000..35bd3c7 --- /dev/null +++ b/uxn.opam @@ -0,0 +1,30 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "Uxn emulator library for OCaml" +description: "Uxn emulator library for OCaml" +maintainer: ["Javier B. Torres "] +authors: ["Javier B. Torres "] +license: "LICENSE" +homepage: "https://codeberg.org/lobo/uxn" +bug-reports: "https://codeberg.org/lobo/uxn/issues" +depends: [ + "dune" {>= "3.20"} + "ocaml" + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://codeberg.org/lobo/uxn.git" +x-maintenance-intent: ["(latest)"]