This commit is contained in:
Lobo 2026-02-02 12:26:24 -03:00
parent def41120c1
commit 3af4843598
17 changed files with 391 additions and 921 deletions

3
.gitignore vendored
View file

@ -1,6 +1,5 @@
*.rom
*.rom.sym *.rom.sym
/roms
/_opam /_opam
/_build /_build
/utils/uxnmin
/.envrc /.envrc

View file

@ -1,5 +1,42 @@
# Kestrel # Kestrel
**Kestrel** is an implementation of the **Kestrel** is:
[Uxn](https://wiki.xxiivv.com/site/uxn.html) virtual machine and
[Varvara](https://wiki.xxiivv.com/site/varvara.html) ecosystem in OCaml. - an implementation of the [Uxn](https://wiki.xxiivv.com/site/uxn.html) virtual
machine as a library in OCaml
- an implementation of the [Varvara](https://wiki.xxiivv.com/site/varvara.html)
ecosystem using said library, supporting the devices needed for command-line programs.
## Building
**Kestrel** requires OCaml 5.3 or upper, as it uses the newly introduced
effects syntax.
There are no requirements aside from the OCaml standard library, so it can be
built simply using `dune`.
```shell-session
$ dune build
```
It then can be run using `dune exec -- kestrel`.
```shell-session
$ dune exec -- kestrel file.rom
```
The `uxnmin` program, which is similar to upstream's in functionality, can be
built without `dune`:
```shell-session
$ ocamlopt exe/uxnmin.ml -o uxnmin
```
## Bootstrapping
Since **Kestrel** supports the needed Varvara devices for running the Drifblim
assembler, it can be used to bootstrap the ecosystem. Bundled in the repository
are copies of the Drifloon hex dump, the source code for Drifblim, and `dzoe`'s
`xh.rom`.
A bootstrap script is provided that builds the assembler and some test ROMs.

1
boot/xh.rom Normal file
View file

@ -0,0 +1 @@
FAFAFXFZFwfaalFBFAAFFZFZXVGfoAAFXFZFFXXfoKgaam&/$AAAFXJgaam&/AFAAFXFFZZDYJF$/FXAKF|Bgaam/AAFXFZFXgBFAYXFEF|BGGoGAFAFXFZDYFEF|B/DAAFX_gBAGYgDAAFXFXAXZEEZXGPgBAAFXFFZZ_]GQFAPGA^GAQFAPgaamFPGAAFXFXFFXXW

25
bootstrap.sh Normal file → Executable file
View file

@ -1,15 +1,14 @@
#!/bin/sh #!/bin/sh
set -e
echo "======> Building seed Drifloon using uxnmin" kestrel() {
make -sC utils clean dune exec -- kestrel "${@}"
make -sC utils drifloon.rom }
echo "======> Building Drifloon using utils/drifloon.rom (seed)"
dune exec -- ./exe/uxnemu.exe utils/drifloon.rom < utils/drifloon.tal > drifloon-ocaml.rom mkdir -p roms/
echo "======> Comparing ROMs (should be identical)" kestrel boot/xh.rom < boot/drifloon.rom.txt > roms/drifloon.rom
cmp drifloon-ocaml.rom utils/drifloon.rom kestrel roms/drifloon.rom < boot/drifblim.tal > roms/drifblim.rom
echo "======> Building Drifloon using drifloon-ocaml.rom"
dune exec -- ./exe/uxnemu.exe drifloon-ocaml.rom < utils/drifloon.tal > drifloon-boot.rom # Run opctest
echo "======> Comparing ROMs (should be identical)" kestrel roms/drifblim.rom boot/opctest.tal roms/opctest.rom
cmp drifloon-ocaml.rom drifloon-boot.rom kestrel roms/opctest.rom
echo "======> Done!"

View file

@ -2,3 +2,7 @@
(public_name kestrel) (public_name kestrel)
(name main) (name main)
(libraries kestrel kestrel_varvara unix)) (libraries kestrel kestrel_varvara unix))
(executable
(public_name uxnmin)
(name uxnmin))

332
exe/uxnmin.ml Normal file
View file

@ -0,0 +1,332 @@
(* uxnmin.ml -- Javier B. Torres <lobo@quiltro.org>
This file is under the public domain. *)
(* ocamlopt uxnmin.ml -o uxnmin *)
(* Wrapping read/write utilities *)
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)
(* Stack manipulation primitives *)
type stack = { data : bytes; mutable sp : int }
type mode = { short : bool; keep : bool; mutable temp : int }
let peek { short; keep; temp } { 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 get_uint16_wrap data sp else Bytes.get_uint8 data sp
[@@inline]
let pop m s =
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 ({ short; keep; _ } as m) (s : stack) (v : int) =
if short then 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 m s v =
let m' = { m with short = false } in
push m' s v;
let { temp; _ } = m' in
m.temp <- temp
[@@inline]
let popbyte m s =
let m' = { m with short = false } in
let r = pop m' s in
let { temp; _ } = m' in
m.temp <- temp;
r
[@@inline]
let popshort m s =
let m' = { m with short = true } in
let r = pop m' s in
let { temp; _ } = m' in
m.temp <- temp;
r
[@@inline]
let pop1 s = pop { short = false; keep = false; temp = 0 } s [@@inline]
let push1 s v = push { short = false; keep = false; temp = 0 } s v [@@inline]
let push2 s v = push { short = true; keep = false; temp = 0 } s v [@@inline]
(* Machine state *)
let ram = Bytes.create 65536
let dev = Bytes.create 256
let wst = { data = Bytes.create 256; sp = 0 }
let rst = { data = Bytes.create 256; sp = 0 }
let console_vector = ref 0
(* Devices *)
let dei port = Bytes.get_uint8 dev port
let deo port =
match port with
| 0x10 | 0x11 -> console_vector := get_uint16_wrap dev 0x10
| 0x18 ->
print_char (Bytes.get dev 0x18);
Out_channel.flush stdout
| 0x19 ->
prerr_char (Bytes.get dev 0x19);
Out_channel.flush stderr
| _ -> ()
(* Dispatch loop *)
let dispatch (pc : int) =
let pc = ref pc in
try
while true do
let op = Bytes.get_uint8 ram (!pc land 0xffff) in
pc := (!pc + 1) land 0xffff;
let short = op land 0x20 <> 0 in
let keep = op land 0x80 <> 0 in
let return = op land 0x40 <> 0 in
let opcode = op land 0x1f in
match op with
| 0x00 -> raise Exit
| 0x20 (* JCI *) ->
let cond = pop1 wst in
let addr = get_int16_wrap ~wrap:0xffff ram !pc in
if cond != 0 then pc := !pc + addr + 2 else pc := !pc + 2
| 0x40 (* JMI *) ->
let addr = get_int16_wrap ~wrap:0xffff ram !pc in
pc := !pc + addr + 2
| 0x60 (* JSI *) ->
let addr = get_int16_wrap ~wrap:0xffff ram !pc in
push2 rst (!pc + 2);
pc := !pc + addr + 2
| 0x80 (* LIT *) ->
let lit = Bytes.get_uint8 ram !pc in
push1 wst lit;
pc := !pc + 1
| 0xa0 (* LIT2 *) ->
let lit = get_uint16_wrap ~wrap:0xffff ram !pc in
push2 wst lit;
pc := !pc + 2
| 0xc0 (* LITr *) ->
let lit = Bytes.get_uint8 ram !pc in
push1 rst lit;
pc := !pc + 1
| 0xe0 (* LIT2r *) ->
let lit = get_uint16_wrap ~wrap:0xffff ram !pc in
push2 rst lit;
pc := !pc + 2
| _ -> begin
let stk = if return then rst else wst in
let stk' = if return then wst else rst in
let mode = { short; keep; temp = stk.sp } 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
ignore (pop mode stk);
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 = pop mode stk in
push mode stk a;
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 (* LTH *) ->
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 + 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 + uint8_to_int8 addr
| 0x0e (* JSR *) ->
push2 rst !pc;
let addr = pop mode stk in
if short then pc := addr else pc := !pc + uint8_to_int8 addr
| 0x0f (* STH *) ->
let a = pop mode stk in
push mode stk' a
| 0x10 (* LDZ *) ->
let addr = popbyte mode stk in
push mode stk
(if short then get_uint16_wrap ram addr
else Bytes.get_uint8 ram addr)
| 0x11 (* STZ *) ->
let addr = popbyte mode stk in
let v = pop mode stk in
if short then set_uint16_wrap ram addr v
else Bytes.set_uint8 ram addr v
| 0x12 (* LDR *) ->
let addr = !pc + uint8_to_int8 (popbyte mode stk) in
push mode stk
(if short then get_uint16_wrap ~wrap:0xffff ram addr
else Bytes.get_uint8 ram addr)
| 0x13 (* STR *) ->
let addr = !pc + uint8_to_int8 (popbyte mode stk) in
let v = pop mode stk in
if short then set_uint16_wrap ~wrap:0xffff ram addr v
else Bytes.set_uint8 ram addr v
| 0x14 (* LDA *) ->
let addr = popshort mode stk in
push mode stk
(if short then get_uint16_wrap ~wrap:0xffff ram addr
else Bytes.get_uint8 ram addr)
| 0x15 (* STA *) ->
let addr = popshort mode stk in
let v = pop mode stk in
if short then set_uint16_wrap ~wrap:0xffff ram addr v
else Bytes.set_uint8 ram 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 set_uint16_wrap dev port value
else Bytes.set_uint8 dev port value;
deo port
| 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 value = pop mode stk in
push mode stk ((value lsr (sft land 0xf)) lsl sft lsr 4)
end
done
with Exit -> ()
let main () =
(* Initialize machine *)
Bytes.unsafe_fill ram 0 65536 '\x00';
Bytes.unsafe_fill dev 0 256 '\x00';
Bytes.unsafe_fill wst.data 0 256 '\x00';
Bytes.unsafe_fill rst.data 0 256 '\x00';
if Array.length Sys.argv < 2 then (
Printf.eprintf "usage: uxnmin file.rom ...\n";
exit 1);
let code =
In_channel.with_open_bin Sys.argv.(1) (fun i -> In_channel.input_all i)
in
Bytes.blit_string code 0 ram 0x100 (String.length code);
In_channel.set_binary_mode stdin true;
Out_channel.set_binary_mode stdout true;
let has_args = Array.length Sys.argv > 2 in
Bytes.set_uint8 dev 0x17 (if has_args then 1 else 0);
dispatch 0x100;
if !console_vector <> 0 then begin
let console_input ch ty =
Bytes.set_uint8 dev 0x12 ch;
Bytes.set_uint8 dev 0x17 ty;
if Bytes.get_uint8 dev 0x0f = 0 then dispatch !console_vector
in
if has_args then begin
for i = 2 to Array.length Sys.argv - 1 do
let arg = Sys.argv.(i) in
String.iter
(fun c ->
if Bytes.get_uint8 dev 0x0f = 0 then console_input (Char.code c) 2)
arg;
if Bytes.get_uint8 dev 0x0f = 0 then
console_input 0 (if i = Array.length Sys.argv - 1 then 4 else 3)
done
end;
try
while Bytes.get_uint8 dev 0x0f = 0 do
match In_channel.input_byte stdin with
| None -> raise Exit
| Some c -> console_input c 1
done
with Exit -> console_input 0 4
end;
exit (Bytes.get_uint8 dev 0x0f land 0x7f)
let _ = main ()

View file

@ -10,6 +10,7 @@ pkgs.mkShell {
uxn uxn
ocamlPackages.ocaml ocamlPackages.ocaml
ocamlPackages.dune_3 ocamlPackages.dune_3
ocamlPackages.graphics
ocamlPackages.findlib ocamlPackages.findlib
ocamlPackages.odoc ocamlPackages.odoc
ocamlPackages.ocamlformat ocamlPackages.ocamlformat

View file

@ -1,13 +0,0 @@
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.

View file

@ -1,19 +0,0 @@
.PHONY: all clean
.SUFFIXES: .tal .rom
all: uxnmin drifloon.rom opctest.rom varvara.file.rom
clean:
rm -f uxnmin *.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

View file

@ -1,4 +0,0 @@
#!/bin/sh
make -q uxnmin
make -q drifloon.rom
./uxnmin drifloon.rom < $1 > $2

View file

@ -1,732 +0,0 @@
( 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/<set>
;assembly/on-console .Console/vector DEO2
BRK
@assembly/on-console ( -> )
[ LIT2 04 -Console/type ] DEI EQU ?{ .Console/read DEI token/<push-byte>
BRK }
/<resolve>
BRK
@rom/<put> ( byte addr* -- )
;&mem ADD2 STA
JMP2r
@rom/<emit> ( -- )
;dict/assembled err/<print>
;dict/in err/<print>
;head/length LDA2 DUP2 #0100 SUB2 err/<pdec>
;dict/bytes err/<print>
( | write )
;rom/mem ADD2 ;rom/output
&>l
LDAk #18 DEO
INC2 GTH2k ?&>l
POP2 POP2 JMP2r
@runes/concat ( t* -- )
POP2 JMP2r
@err/<emit> ( 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/<resolve> ( -- )
( cap ) #0a token/<push-byte>
,&mode LDR2 ;comment/assemble NEQ2 ?{
( ! ) ;dict/open ;dict/trail ;dict/Comment err/<generic> }
,&mode LDR2 ;macros/assemble NEQ2 ?{
( ! ) ;dict/open ;dict/trail ;dict/Macro err/<generic> }
.System/state DEI ?{
refs/<resolve-all>
.System/state DEI ?{
[ LIT2 80 -System/state ] DEO !syms/<emit> } }
JMP2r
@assembly/apply ( t* -- )
LDZk ?{ POP2 JMP2r }
[ LIT2 &mode =standard/assemble ] JMP2
(
@|Standard )
@standard/<latch> ( -- )
;&assemble ;assembly/mode STA2
JMP2r
@standard/assemble ( t* -- )
( hex ) str/is-hex ?rom/<write-rawhex>
( opc ) opcodes/is-opcode ?rom/<write-opcode>
LDZk runes/find INC2k ORA ?{
POP2
( mac ) DUP2 macros/find-name INC2k ORA ?macros/<write>
POP2
( imm ) !runes/litjsi }
INC2 LDA2 JMP2
(
@|Comment )
@comment/<latch> ( 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/<latch> }
JMP2r
(
@|Macros )
@macros/<latch> ( t* -- )
name/<validate>
/<push-word>
#00 /<push-byte>
;&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 /<push-byte> !standard/<latch> }
/<push-word>
#20 !/<push-byte>
@macros/<push-word> ( t* -- )
;/<push-byte> !hof/<each>
@macros/<push-byte> ( byte -- )
[ LIT2 &ptr =&mem ] INC2k
( | check overflow )
DUP2 ;&memend LTH2 ?{
( ! ) ;dict/exceeded ;dict/Macros err/<token> }
,&ptr STR2
STA
JMP2r
@macros/find-name ( name* -- <addr>* )
STH2
,&ptr LDR2 ;&mem
&>lf
DUP2 STH2kr str/cmp ?{
str/cap str/cap GTH2k ?&>lf
POP2 #ffff }
NIP2 POP2r JMP2r
@macros/<write> ( t* macro* -- )
NIP2 token/<new>
str/cap ;token/<push-byte> !hof/<each>
(
@|Token )
@token/<new> ( -- )
[ LIT2 -&buf _&ptr ] STR
[ LIT2 00 -&buf ] STZ
JMP2r
@token/<push-byte> ( c -- )
DUP #20 GTH ?{
;&buf assembly/apply #0a NEQ ?{
[ LIT2 &line 0001 ] INC2 ,&line STR2 }
!/<new> }
[ LIT2 00 &ptr -&buf ] INCk
( | check overflow )
DUP .&cap LTH ?{
( ! ) ;dict/exceeded ;dict/Name err/<token> }
,&ptr STR
STZ2
JMP2r
(
@|Scope )
@scope/<push-byte> ( c -- )
[ LIT2 00 &ptr -&buf ] INCk
( | check overflow )
DUP .&cap LTH ?{
( ! ) ;dict/exceeded ;dict/Symbol err/<token> }
,&ptr STR
STZ2
JMP2r
@scope/<set> ( name* -- )
[ LIT2 -&buf _&ptr ] STR
&>w
LDAk [ LIT "/ ] EQU ?{
LDAk /<push-byte>
INC2 LDAk ?&>w }
POP2 ,&ptr LDR ,&anchor STR
JMP2r
@scope/make-name ( name* -- scope/label* )
INC2 [ LIT2 &anchor $1 _&ptr ] STR
[ LIT "/ ] /<push-byte>
;&buf SWP2 ;/<push-byte> !hof/<each>
(
@|Runes )
@runes/find ( char -- <addr>* )
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/<latch>
&macros ( t* -- )
/req-name !macros/<latch>
&padabs ( t* -- )
/req-name syms/find-addr !head/<set>
&padrel ( t* -- )
/req-name syms/find-addr !head/<set-rel>
&toplab ( t* -- )
/req-name DUP2 scope/<set> !syms/<new>
&sublab ( t* -- )
scope/make-name !syms/<new>
&litrel ( t* -- )
#80 rom/<write-byte> &rawrel /req-name refs/get-rel !rom/<write-byte>
&litzep ( t* -- )
#80 rom/<write-byte> &rawzep /req-name refs/get-abs !rom/<write-byte>
&litabs ( t* -- )
#a0 rom/<write-byte> &rawabs /req-name refs/get-abs2 !rom/<write-short>
&litjci ( t* -- )
/req-name #20 !rom/<write-call>
&litjmi ( t* -- )
/req-name #40 !rom/<write-call>
&litjsi ( t* -- )
#60 !rom/<write-call>
&lithex ( t* -- )
/req-name !rom/<write-lithex>
&rawstr ( t* -- )
/req-name !rom/<write-str>
@runes/req-name ( str* -- str1* )
INC2 LDAk #20 GTH ?{ ;dict/invalid ;dict/Name !err/<token> }
JMP2r
@runes/lut [
"| =&padabs "$ =&padrel
"@ =&toplab "& =&sublab
", =&litrel "_ =&rawrel
". =&litzep "- =&rawzep
"; =&litabs "= =&rawabs
"! =&litjmi "? =&litjci
"# =&lithex "" =&rawstr
"} =&lambda "~ =&concat
"( =&coment ") =&ignore
"[ =&ignore "] =&ignore "% =&macros ] $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/<new>
,&ptr STR2
JMP2r
&sym cebb
&id ".. $1
(
@|Name )
@name/<validate> ( 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/<token>
&dup ( -- )
;dict/duplicate ;dict/Name !err/<token>
@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/<new> ( name* -- )
DUP2 /find-name INC2k ORA ?{
POP2 ;&ptr LDA2 refs/<record-scope>
.SymType/declared head/get !/<push-sym> }
( | name* sym* -- )
NIP2 DUP2 refs/<record-scope>
/is-declared ?{ head/get OVR2 STA2 !/<declare> }
POP2
( ! ) ;dict/duplicate ;dict/Symbol !err/<token>
@syms/<push-sym> ( name* type addr* -- )
( hb ) SWP /<push-byte>
( lb ) /<push-byte>
( type ) /<push-byte>
name/<validate>
;/<push-byte> hof/<each>
#00
( >> )
@syms/<push-byte> ( byte -- )
[ LIT2 &ptr =&mem ] INC2k
( | check overflow )
DUP2 ;&memend LTH2 ?{
( ! ) ;dict/exceeded ;dict/Symbols err/<token> }
,&ptr STR2
STA
JMP2r
@syms/find-name ( name* -- <sym>* )
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* -- <addr>* )
DUP2 /find-name INC2k ORA ?{
( null* .. next* ) POP2 ,&ptr LDR2
( alloc ) SWP2 .SymType/used #ffff !/<push-sym> }
NIP2 JMP2r
@syms/find-addr ( name* -- <addr>* )
str/is-hex ?str/hex
name/unpack /find-name /is-defined ?{
( ! ) ;dict/invalid ;dict/Symbol err/<token> }
/use LDA2 JMP2r
@syms/<emit> ( -- )
;&ptr LDA2 ;&mem
&>ls
EQU2k ?{
/is-used ?{
LDA2k #0100 EQU2 ?{
DUP2 #0003 ADD2 LDAk [ LIT "A ] SUB #1a LTH ?{
;dict/unused err/<print>
DUP2 err/<print>
#0a err/<emit> }
POP2 } }
#0003 ADD2 str/cap !&>ls }
POP2 POP2 !rom/<emit>
@syms/byte-distance ( addr* -- addr* )
DUP2 #0080 ADD2 POP ?{ JMP2r }
( ! ) ;dict/too-far ;dict/Symbol !err/<token>
@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/<declare> ( 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* ) /<push-short>
( symbol* ) /<push-short>
( type-fn* ) [ LIT2 &type $2 ] /<push-short>
( scope* ) [ LIT2 &scope $2 ] /<push-short>
( line* ) ;token/line LDA2 /<push-short> }
( | mark as used )
syms/use LDA2 JMP2r
@refs/<push-short> ( value* -- )
SWP /<push-byte>
( >> )
@refs/<push-byte> ( byte -- )
[ LIT2 &ptr =&mem ] INC2k
( | check overflow )
DUP2 ;&memend LTH2 ?{
( ! ) ;dict/exceeded ;dict/References err/<token> }
,&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/<resolve-all> ( -- )
,&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/<set>
( ref* sym* ) INC2k INC2 LDA2
( ref* sym/addr* ) LDA2
( ref* sym/addr* ) INC2k ORA ?{
( ! ) ;dict/invalid !err/<resolution> }
( ref* sym/addr* ) JMP2r
@refs/handle-abs ( ref* -- )
/resolve-sym NIP2 NIP !rom/<write-byte>
@refs/handle-abs2 ( ref* -- )
/resolve-sym NIP2 !rom/<write-short>
@refs/handle-rel ( ref* -- )
/resolve-sym SWP2 LDA2 /get-distance /byte-distance NIP !rom/<write-byte>
@refs/handle-rel2 ( ref* -- )
/resolve-sym SWP2 LDA2 /get-distance !rom/<write-short>
@refs/byte-distance ( addr* -- addr* )
DUP2 #0080 ADD2 POP ?{ JMP2r }
( ! ) ;dict/too-far !err/<resolution>
@refs/<record-scope> ( sym* -- )
DUP2 #0003 ADD2 LDA2 #cebb NEQ2 ?{ POP2 JMP2r }
;refs/scope STA2
JMP2r
(
@|Rom )
@rom/<write-str> ( str* -- )
;/<write-byte> !hof/<each>
@rom/<write-opcode> ( str* -- )
opcodes/parse !/<write-byte>
@rom/<write-lithex> ( str* -- )
str/len #02 NEQ #50 SFT #80 ORA /<write-byte>
( >> )
@rom/<write-rawhex> ( str* -- )
str/is-hex #00 EQU ?{
str/len DUP #02 NEQ ?{ POP str/hex NIP !/<write-byte> }
#04 NEQ ?{ str/hex !/<write-short> } }
POP2 ;dict/invalid ;dict/Number !err/<token>
@rom/<write-call> ( str* opc -- )
/<write-byte>
refs/get-rel2
( >> )
@rom/<write-short> ( short* -- )
SWP /<write-byte>
( >> )
@rom/<write-byte> ( byte -- )
head/get-inc
( | test zero-page )
OVR ?{
POP2 POP
( ! ) ;dict/zero-page ;dict/Writing !err/<token> }
!rom/<put>
@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/<set-rel> ( addr* -- )
/get ADD2
( >> )
@head/<set> ( addr* -- )
,&addr STR2
JMP2r
(
@|Stdlib )
@hof/<each> ( 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* <val> )
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/<token> ( adj* topic* -- )
.System/state DEI ?{
[ LIT2 01 -System/state ] DEO
/<print>
#20 /<emit>
/<print>
;dict/spacer /<print>
;token/buf /<print>
;token/line LDA2 ;scope/buf !/<print-location> }
POP2 POP2 JMP2r
@err/<resolution> ( adj* -- )
.System/state DEI ?{
[ LIT2 01 -System/state ] DEO
;dict/Reference /<print>
#20 /<emit>
/<print>
;dict/spacer /<print>
[ LIT2 &ref $2 ] INC2k INC2 LDA2 #0003 ADD2 /<print>
DUP2 #0008 ADD2 LDA2 SWP2 #0006 ADD2 LDA2 #0003 ADD2 !/<print-location> }
POP2 JMP2r
@err/<print-location> ( line* scope* -- )
;dict/in /<print>
/<print>
LIT ": /<emit>
/<pdec>
#0a /<emit>
JMP2r
@err/<generic> ( adj* keyword* topic* -- )
.System/state DEI ?{
[ LIT2 01 -System/state ] DEO
/<print>
#20 /<emit>
SWP2 /<print>
;dict/spacer /<print>
/<print>
#0a /<emit>
JMP2r }
POP2 POP2 POP2 JMP2r
@err/<print> ( str* -- )
;/<emit> !hof/<each>
@err/<pdec> ( short* -- )
[ LIT2r ff00 ]
&>read
#000a DIV2k STH2k MUL2 SUB2 STH2r INCr ORAk ?&>read
POP2
&>write
NIP #30 ADD /<emit>
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

View file

@ -1,135 +0,0 @@
#include <stdio.h>
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;
}