update docs, setup minimal builds for web gui/tui + linux gui/tui

This commit is contained in:
zongor 2025-12-21 16:37:20 -08:00
parent a587cb68e3
commit d9468f2efd
17 changed files with 892 additions and 8 deletions

2
.gitignore vendored
View File

@ -103,3 +103,5 @@ Module.symvers
Mkfile.old
dkms.conf
# project specific
out/

View File

@ -11,7 +11,8 @@
Undâr conforms to permacomputing principles.
Permacomputing as defined by the [[https://wiki.xxiivv.com/site/permacomputing.html][xxiivv wiki]]: "permacomputing encourages the maximization of hardware lifespan, minimization of energy usage and focuses on the use of already available computational resources.
Permacomputing as defined by the [[https://wiki.xxiivv.com/site/permacomputing.html][xxiivv wiki]]:
"permacomputing encourages the maximization of hardware lifespan, minimization of energy usage and focuses on the use of already available computational resources.
it values maintenance and refactoring of systems to keep them efficient, instead of planned obsolescence, permacomputing practices planned longevity.
it is about using computation only when it has a strengthening effect on ecosystems."
@ -27,9 +28,19 @@ Undâr is intended to run on anything with a C compiler. From constrained system
* Getting Started
**
** Requirements
*** Main
- Git
- C compiler
*** Optional
- SDL2
- For GUI linux and web versions
- Emscripten
- For web version
** Build (linux version)
** Build
Note: the linux version is the default (for now)
#+BEGIN_SRC sh
git clone https://git.alfrescocavern.com/zongor/undar-lang.git
@ -46,7 +57,7 @@ cd undar-lang && ./build
* Inspirations
- [[https://plan9.io/][Plan 9]] : 9P, Unified I/O, Tunnels.
- [[https://man.9front.org/][Plan 9]] : 9P, Unified I/O, Tunnels.
- [[https://en.wikipedia.org/wiki/Lisp_(programming_language)][Lisp]] : REPL, introspection.
- [[https://fortran-lang.org/][Fortran]] : Array semantics.
- [[https://en.wikipedia.org/wiki/C_(programming_language)][C]] / [[https://ziglang.org/][Zig]] : Efficentcy, simplicity.

View File

@ -1,2 +1,40 @@
#+TITLE Project Roadmap
** Features
- [ ] Primitive Types
- [ ] Q16.16 fixed point real numbers
- [ ] Arithmetic Opcodes
- [ ] Assembler
- [ ] Device Tunnels
- [ ] Terminal
- [ ] Display
- [ ] Mouse
- [ ] Keyboard
- [ ] File
- [ ] Network
- [ ] Sound
- [ ] Gamepad
- [ ] Joystick
- [ ] Compiler
- [ ] Arrays
- [ ] Plexes
- [ ] Fonts
- [ ] Actor system
- [ ] Localization
** Standard Library
- [ ] Graphics
- [ ] 2D immediate mode UI
- [ ] 3D graphics
- [ ] Physics
- [ ] 3D collisions
- [ ] Trigonometry
- [ ] Unit of measure
** Projects
- [ ] Paint
- [ ] 3D Platformer POC
- [ ] Multiplayer 3D MMO POC
- [ ] Code Editor
- [ ] Self hosted assembler
- [ ] Self hosted compiler

View File

@ -1 +1,45 @@
#+TITLE Project Specification
* Binary interface
The VM does not use floating point numbers, it instead uses fixed point numbers.
This is for portability reasons as some devices might not have a FPU in them
especially microcontrollers and some retro game systems like the PS1.
** Numbers
| type | size (bytes) | description |
|------+--------------+---------------------------------------|
| u8 | 1 | unsigned 8bit, alias =char= and =byte= |
| bool | 1 | unsigned 8bit, =false= or =true= |
| i8 | 1 | signed 8bit for interop |
| u16 | 2 | unsigned 16bit for interop |
| i16 | 2 | signed 16bit for interop |
| u32 | 4 | unsigned 32bit, alias =nat= |
| i32 | 4 | signed 32bit, alias =int= |
| f32 | 4 | signed 32bit fixed number, alias =real= |
* Memory
Uses a harvard style archecture, meaning the code and ram memory
are split up into two seperate blocks.
In the C version you can see these are two seperate arrays 'code' and 'mem'.
During compilation constants and local variables are put onto 'mem'
* Opcodes
Most opcodes are 4 bytes
[opcode][dest][src1][src2]
A small number are multibyte like load-long-immidiate
[multibyte-opcode][0u8][0u8][opcode] . [u32]
These are decoded during runtime and selected.
In theory, bitshift decoding is faster than
accessing an unknown n bytes of memory in the 'code' array.

View File

@ -0,0 +1,20 @@
#include "../../../vm/vm.h"
#include <SDL2/SDL.h>
#include <stdio.h>
int main() {
VM vm = {0};
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL initialization failed: %s\n", SDL_GetError());
return 1;
}
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
while(step_vm(&vm)) {
// do stuff
}
printf("done\n");
return 0;
}

View File

@ -0,0 +1,13 @@
#include "../../../vm/vm.h"
#include <stdio.h>
int main() {
VM vm = {0};
while(step_vm(&vm)) {
// do stuff
}
printf("done\n");
return 0;
}

33
arch/web/gui/main.c Normal file
View File

@ -0,0 +1,33 @@
#include <stdio.h>
#include "../../../vm/vm.h"
#undef true
#undef false
#include <SDL2/SDL.h>
#include <emscripten.h>
#include <emscripten/html5.h>
VM vm = {0};
void mainloop() {
if (!step_vm(&vm)) {
emscripten_cancel_main_loop();
return;
}
}
int main() {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL initialization failed: %s\n", SDL_GetError());
return 1;
}
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
emscripten_set_canvas_element_size("#canvas", 640, 480);
printf("VM loaded successfully\n");
emscripten_set_main_loop(mainloop, 0, 1);
return 0;
}

View File

@ -0,0 +1,288 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #ddd;
background: black;
padding: 0;
margin: 0;
}
main {
max-width: 700px;
margin: 0 auto;
padding: 1.5rem;
}
header {
background: #111;
padding: 1rem 1.5rem;
border-bottom: 1px solid #222;
text-align: center;
}
header h1 {
font-size: 1.5rem;
font-weight: 600;
color: #ddd;
}
nav a {
color: #ddd;
padding: 12px 15px;
text-decoration: none;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
flex-grow: 1;
}
nav a:hover {
background-color: #1b1b1b;
}
a {
color: #ddd;
}
textarea {
background: #111;
padding: 1rem 1.5rem;
border-top: 1px solid #222;
text-align: center;
margin-top: 2rem;
font-size: 0.9rem;
color: #ddd;
}
p {
margin-bottom: 1.25rem;
}
code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
background-color: #1a1a1a;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
pre {
background: #262620;
color: #f8f8f2;
padding: 1rem;
border-radius: 5px;
overflow-x: auto;
margin: 1.5rem 0;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.95rem;
line-height: 1.5;
}
pre code {
background: transparent;
padding: 0;
font-size: 1em;
color: #f8f8f2;
}
@media (min-width: 768px) {
main {
padding: 2rem;
}
header h1 {
font-size: 2rem;
}
}
@media (prefers-color-scheme: light) {
body {
background-color: white;
color: black;
}
header {
background: #ddd;
color: black;
}
a {
color: black;
}
nav a {
color: black;
}
nav a:hover {
background-color: #eee;
color: black;
}
main {
background-color: white;
}
pre {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
pre code {
color: #333;
}
textarea {
background: #ddd;
}
}
</style>
</head>
<body>
<hr/>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<hr/>
<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)">
</div>
<hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var canvasElement = document.getElementById('canvas');
var outputElement = document.getElementById('output');
if (outputElement) outputElement.value = ''; // clear browser cache
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvasElement.addEventListener("webglcontextlost", (e) => {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
var Module = {
print(...args) {
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(...args);
if (outputElement) {
var text = args.join(' ');
outputElement.value += text + "\n";
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
}
},
canvas: canvasElement,
setStatus(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = () => {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>

View File

23
arch/web/tui/main.c Normal file
View File

@ -0,0 +1,23 @@
#include "../../../vm/vm.h"
#undef true
#undef false
#include <stdio.h>
#include <emscripten.h>
#include <emscripten/html5.h>
VM vm = {0};
void mainloop(void) {
if (!step_vm(&vm)) {
emscripten_cancel_main_loop();
return;
}
}
int main(void) {
printf("VM loaded successfully\n");
emscripten_set_main_loop(mainloop, 0, 1);
return 0;
}

View File

@ -0,0 +1,288 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Emscripten-Generated Code</title>
<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: #ddd;
background: black;
padding: 0;
margin: 0;
}
main {
max-width: 700px;
margin: 0 auto;
padding: 1.5rem;
}
header {
background: #111;
padding: 1rem 1.5rem;
border-bottom: 1px solid #222;
text-align: center;
}
header h1 {
font-size: 1.5rem;
font-weight: 600;
color: #ddd;
}
nav a {
color: #ddd;
padding: 12px 15px;
text-decoration: none;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
flex-grow: 1;
}
nav a:hover {
background-color: #1b1b1b;
}
a {
color: #ddd;
}
textarea {
background: #111;
padding: 1rem 1.5rem;
border-top: 1px solid #222;
text-align: center;
margin-top: 2rem;
font-size: 0.9rem;
color: #ddd;
}
p {
margin-bottom: 1.25rem;
}
code {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
background-color: #1a1a1a;
padding: 0.2em 0.4em;
border-radius: 3px;
font-size: 0.9em;
}
pre {
background: #262620;
color: #f8f8f2;
padding: 1rem;
border-radius: 5px;
overflow-x: auto;
margin: 1.5rem 0;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 0.95rem;
line-height: 1.5;
}
pre code {
background: transparent;
padding: 0;
font-size: 1em;
color: #f8f8f2;
}
@media (min-width: 768px) {
main {
padding: 2rem;
}
header h1 {
font-size: 2rem;
}
}
@media (prefers-color-scheme: light) {
body {
background-color: white;
color: black;
}
header {
background: #ddd;
color: black;
}
a {
color: black;
}
nav a {
color: black;
}
nav a:hover {
background-color: #eee;
color: black;
}
main {
background-color: white;
}
pre {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
pre code {
color: #333;
}
textarea {
background: #ddd;
}
}
</style>
</head>
<body>
<hr/>
<figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<hr/>
<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse pointer
&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick="Module.requestFullscreen(document.getElementById('pointerLock').checked,
document.getElementById('resize').checked)">
</div>
<hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr>
<script type='text/javascript'>
var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');
var canvasElement = document.getElementById('canvas');
var outputElement = document.getElementById('output');
if (outputElement) outputElement.value = ''; // clear browser cache
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvasElement.addEventListener("webglcontextlost", (e) => {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault();
}, false);
var Module = {
print(...args) {
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(...args);
if (outputElement) {
var text = args.join(' ');
outputElement.value += text + "\n";
outputElement.scrollTop = outputElement.scrollHeight; // focus on bottom
}
},
canvas: canvasElement,
setStatus(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2])*100;
progressElement.max = parseInt(m[4])*100;
progressElement.hidden = false;
spinnerElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
if (!text) spinnerElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = () => {
Module.setStatus('Exception thrown, see JavaScript console');
spinnerElement.style.display = 'none';
Module.setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>

116
build Normal file → Executable file
View File

@ -1,2 +1,118 @@
#!/bin/sh
# if an error occurs exit
set -e
# set defaults for these if they don't exist
if [ -z $ARCH ]; then
ARCH='linux'
fi
if [ -z $MODE ]; then
MODE='debug'
fi
if [ -z $UI ]; then
UI='tui'
fi
case $ARCH in
"linux")
if [ -z $CC ]; then
CC='gcc'
fi
;;
"web")
CC=emcc
;;
esac
# setup dirs
SRC_DIR=./arch/$ARCH/$UI
BUILD_DIR=./out/$ARCH/$UI
# clean cmd
case $1 in
"clean")
echo "Deleting $BUILD_DIR"
rm -rf $BUILD_DIR
exit 0
esac
# run cmd
case $1 in
"run")
case $ARCH in
"linux")
$BUILD_DIR/undar $2
;;
"web")
emrun $BUILD_DIR/undar.html
;;
esac
exit 0
esac
# create the build dir if it doesnt exist
if [ -d $BUILD_DIR ]; then
echo "Building to $BUILD_DIR"
else
echo "$BUILD_DIR not found, creating"
mkdir -p $BUILD_DIR
fi
# setup the build flags based on the build mode
case $MODE in
"debug")
BUILD_FLAGS="-g -Wall -Wextra -Werror -pedantic"
;;
"release")
BUILD_FLAGS="-O2 -Wall -Wextra -Werror -pedantic"
;;
esac
# set up the flags based on the UI
case $UI in
"tui")
case $CC in
"gcc")
LINK_FLAGS=""
;;
"emcc")
LINK_FLAGS="-s ASYNCIFY --shell-file $SRC_DIR/shell_minimal.html"
;;
esac
;;
"gui")
case $CC in
"gcc")
LINK_FLAGS="$(sdl2-config --libs --cflags)"
;;
"emcc")
LINK_FLAGS="-s USE_SDL=2 -s ASYNCIFY --shell-file $SRC_DIR/shell_minimal.html"
;;
esac
;;
esac
# build the core VM
VM_BUILD_FLAGS="$BUILD_FLAGS -std=c89 -ffreestanding -nostdlib -fno-builtin"
${CC} -c vm/libc.c -o $BUILD_DIR/libc.o $VM_BUILD_FLAGS
${CC} -c vm/vm.c -o $BUILD_DIR/vm.o $VM_BUILD_FLAGS
# Set up the final build command
case $ARCH in
"linux")
BUILD_CMD="$CC -o $BUILD_DIR/undar $SRC_DIR/main.c $LINK_FLAGS $BUILD_DIR/libc.o $BUILD_DIR/vm.o $BUILD_FLAGS $LINK_FLAGS"
;;
"web")
BUILD_CMD="$CC $SRC_DIR/main.c $BUILD_DIR/libc.o $BUILD_DIR/vm.o -o $BUILD_DIR/undar.html $BUILD_FLAGS $LINK_FLAGS"
;;
esac
echo "$BUILD_CMD"
${BUILD_CMD}
echo "Finished building to $BUILD_DIR/undar"

0
vm.c
View File

View File

View File

6
vm/vm.c Normal file
View File

@ -0,0 +1,6 @@
#include "vm.h"
bool step_vm(VM *vm) {
USED(vm);
return false;
}

View File

@ -4,19 +4,21 @@
#include "libc.h"
typedef enum {
NOOP,
NOOP
} Opcode;
typedef struct vm_s VM;
#define CODE_SIZE 4096
#define MEM_SIZE 65536
struct vm_s {
u32 pc;
u8 mem[MEM_SIZE];
u32 pc;
u32 code[CODE_SIZE];
u8 mem[MEM_SIZE];
};
bool init_vm(VM *vm);
extern bool init_vm(VM *vm);
bool step_vm(VM *vm);
#endif