update docs

This commit is contained in:
zongor 2025-10-06 19:42:27 -07:00
parent aea1dba6a2
commit 7644144356
2 changed files with 250 additions and 235 deletions

View File

@ -17,175 +17,28 @@
* Undâr
Undâr is a permacomputing oriented, statically-typed language with **first-class arrays**, **immediate-mode semantics**, and **symbolic clarity**
- =Constrained systems=: microcontrollers, retro consoles (PS1, N64, Mac Classic)
- =Portable environments=: Web (Emscripten), embedded, CLI Tui
- =Permacomputing=: long-term survivability, sustainability, minimalism
- =3D world-building=: built-in primitives for PS1/N64-style rendering
- =Live development=: hot reloading, REPL, shadowing, symbol versioning
Undâr is a programming language for the purpose of creating three dimensional video games and graphical user interfaces that work on constrained systems, microcontrollers, retro consoles, and the using emscripten. The language emphasizes hardware longevity, energy efficiency, and the preservation of digital art and games for future generations.
It runs on the =Reality Engine=, a minimal C89 VM inspired by Uxn, Plan 9, and Forth - but built for =spatial software=, =deterministic execution=, and =software that lasts=.
It has an internal REPL that allows for quick development as well as the ability to dump the program to a binary rom for preserving that program/game/etc.
Sċieppan is a minimal macro assembler that uses s-expressions.
You can view some examples in the =.lisp= files in =/test=
It runs on the =Reality Engine=, a VM written in freestanding C89, has a CISC like instruction format of one byte opcode and a variable byte operand. 32 general purpose registers.
* Philosophy
The =Reality Engine= is a register-based virtual machine designed to render not just graphics, but persistent, inspectable, reproducible computational worlds.
Undâr it conforms to permacomputing principles.
It is:
- Written in **C89** for maximum portability
- **No dynamic allocation** - memory is static, frame-managed, zero-initialized
- **Deterministic by design** - identical input -> identical output
- **Self-inspectable** - symbol table, memory, and state are always accessible
- Inspired by Uxn, Dis VM, Dusk OS, and Plan 9
"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." [[https://wiki.xxiivv.com/site/permacomputing.html][source]]
**VM Architecture**
Undâr is designed to ensure that programs created today will remain executable for a very long time, even through technological collapse.
| Feature | Specification |
|--------------------+----------------------------------------------------|
| Instruction Format | 1-byte opcode, variable length operand (CISC-like) |
| Register Set | 32 general-purpose registers (R0-R31) |
| Initialization | **ZII**: Zero Is Initialization |
| Memory Model | Frame-based arenas (function scope = frame) |
| Heap Behavior | Copy-on-write; allocations append to frame |
| Frame Exit | Pointer resets on return (stack-GC style) |
| Error Handling | Returns stub pointers to zeroed memory |
This ensures:
- No =malloc=, no =free=, no GC
- Predictable memory use
- Perfect reproducibility
- Safe failure modes
**Core Types**
| Type | Description |
|--------+-------------------------------------------|
| =int= | 32-bit signed integer |
| =nat= | 32-bit natural number |
| =real= | Q16.16 fixed-point real number |
| =str= | fat pointer [length + data] string |
| =bool= | Compile-time flag |
| =char= | Character |
| =ref= | Reference prefix for passing by reference |
**Array Semantics (Fortran-Style)**
Arrays are **first-class values**:
#+BEGIN_SRC ul
real[3] pos = [1.0, 2.0, 3.0];
real[3][3] mat = identity(3);
real[3] result = mat * pos; ! compiler generates matrix-vector multiply
#+END_SRC
- Row-major order
- Fat pointers for slices and strings
- No =vec3=/=mat4x4= structs needed - math is generated
- Supports composition, slicing, element-wise ops
**Boolean/Enum Strategy**
Flags are compiled into **parallel arrays** (like =MultiArrayList=):
#+BEGIN_SRC ul
bool[] player_alive;
real[3][] player_pos;
str[] player_name;
#+END_SRC
This enables:
- Cache-friendly data layout
- No polymorphism tax
- High-performance iteration
* Plex: The Form of Being
A =plex= is a **Platonic form** - a structured definition of a kind of being in your program.
#+BEGIN_SRC ul
plex Player {
str name;
real[3] pos;
init(str name, real[3] pos) {
this.name = name;
this.pos = pos;
}
update() {}
logout() {}
}
#+END_SRC
- Not a class: no inheritance, no vtables
- Methods are functions with implicit =this= argument
- Instances are **atoms**
- A plex defines what a thing is. An atom is its instance.
* Graphics & Devices
**Memory-Mapped I/O**
Devices are memory-mapped:
- SDL surface -> direct memory access
- GPU registers -> memory addresses
- All rendering done by writing to memory
**Texture System**
- Default format: RGB332 (1 byte per pixel)
- Tile sizes: 8x8 to 32x32
- Automatic mipmapping for textures >128px
- Manual override available
**Immediate Mode GUI**
- Function calls are equivelent to redraws
- Child calls define spacial location in code as well as the UI
**3D Primitives + Constructive solid geometry**
Built-in support for:
- =Cube(pos, size)=
- =Plane(pos, radius)=
- =Model(mesh, texture)=
- Simple PS1/N64-style rendering pipeline
No shaders, no pipelines - just direct manipulation of memory and math.
* Tunnels: Unified I/O (Plan 9 / 9P-Inspired)
A =Tunnel= unifies:
- Files
- Sockets
- Web APIs
- Devices
- Databases
#+BEGIN_SRC ul
Tunnel server = Tunnel("tcp://localhost:25565");
if (server.attach(auth)) {
Player[] players = server.read("players");
server.write("me", me.update());
server.clunk();
}
#+END_SRC
**Tunnel Operations**
| Op | Meaning |
|------------+-----------------------|
| =.attach()= | Authenticate and open |
| =.open()= | Open resource |
| =.read()= | Transfer data |
| =.write()= | Transfer data |
| =.walk()= | Navigate hierarchy |
| =.flush()= | Cancel long operation |
| =.clunk()= | Close connection |
| =.stat()= | Get metadata |
| =.version()= | Get protocol version |
Tunnels make I/O **uniform, composable, and archival**.
**Live Coding Features**
- REPL-style interaction: inspect memory, call functions, test logic
This is achieved through:
- A standardized bytecode format that maps 1:1 to human-readable assembly (Sċieppan)
- A VM specification that can be implemented easily
- Hardware abstractions for the VM implementation
- ROM files that contain all necessary information for execution
- A friendly syntax which focuses on maintaining code without obscuring functionality.
* Getting Started
@ -196,107 +49,110 @@ git clone https://git.alfrescocavern.com/zongor/undar-lang.git
cd undar-lang && make
#+END_SRC
=Sċieppan= is a minimal assembler that uses s-expressions.
You can view some examples in the =.asm.lisp= files in =/test=
The Undâr compiler will be written in Sċieppan, as well as core VM tests.
**Sample Program: =hello.asm.lisp=**
#+BEGIN_SRC lisp
((code
(label main
(load $0 &terminal-str)
(load $1 &hello-str)
(string-length $2 $1)
(syscall DEVICE_WRITE $0 $1 $2)
(load $3 &new-line)
(string-length $4 $3)
(syscall WRITE, $0, $3, $4)
(halt)))
(load-immediate $0 &terminal-namespace) ; load terminal namespace
(load-immediate $1 &hello-str) ; load hello string ptr
(string-length $2 $1) ; get length to write to stdout
(syscall WRITE $0 $1 $2) ; do the write syscall
(halt))) ; done
(data
(label terminal-str "/dev/term/0")
(label new-line "\n")
(label hello-str "nuqneH 'u'?")))
(label terminal-namespace "/dev/term/0")
(label hello-str "nuqneH 'u'?\n")))
#+END_SRC
#+BEGIN_SRC sh
./build/linux/undar-linux-debug ./test/hello.asm.lisp
#+END_SRC
* Example: 3D Client (=client.ul=)
Running the compiler without arguments will put it in "REPL" mode. It will function similar to a LISP repl.
#+BEGIN_SRC ul
use "common.ul";
* Memory Management
fn256 main(int argc, str[] argv) {
nat w = 800, h = 450;
Player me(argv[1], [0.0, 1.0, 2.0], PURPLE);
memory is managed via frame based arenas. function scopes defines a memory frame.
bool running = true;
while (running) {
window("Client", w, h) {
splitbox(parent.size, 0.25) {
canvas() {
if (button("Logout")) {
me.logout();
running = false;
}
}
}
splitbox(parent.size, 0.75) {
canvas() {
model(Floor([0, 0, 0], 30));
me.update();
model(Cube(me.pos, [0.5,0.5,0.5], me.color));
if (Player[] others = me.server.read("players")) {
for (p in others) {
model(Cube(p.pos, [0.5,0.5,0.5], p.color));
}
}
}
}
}
}
exits("Client Closed Successfully");
}
heap allocations using the internal malloc opcode push pointers within this frame. when a frame exits, the pointer is reset like stack based gc.
#+BEGIN_SRC lisp
((code
(label main ; this example adds 2 numbers together
(load-immediate $0 1) ; pushes 1 onto the stack for the function call
(push $0)
(load-immediate $0 1)
(push $0)
(call &add) ; here a new frame is generated
(pop $0) ; the element is returned and the memory for the println is "freed" automatically because the child frame is done
(halt))
(label add
(pop $0)
(pop $1)
(add-int $2 $1 $0) ; add the arguments
(int-to-string $3 $2) ; convert to a string (heap allocation)
(push $3)
(call &println) ; call print function
(push $2)
(return)) ; return to main function
(label println
(load-immediate $0 &terminal-namespace) ; load the namespace for the terminal
(load-immediate $3 &new-line) ; and a newline char
(pop $1) ; pointer to string
(string-length $2 $1) ; get the length
(syscall WRITE $0 $1 $2) ; write the string
(string-length $4 $3)
(syscall WRITE $0 $3 $4)
(return))) ; return back to add function
(data ; allocates strings at compile time
(label terminal-namespace "/dev/term/0")
(label new-line "\n")))
#+END_SRC
* Future & Preservation
values passed to functions must be explicitly returned to propagate. heap values are copy on write, so if a value is modified in a child function it will change the parents value, unless the size of the structure changes then it will copy the parents value and append it to its own frame with the modification. this allows for the low resource usage of a C but the convenience of a Java/Go without the garbage collection.
**Goals**
- Run on hardware from **1980 to 2080+**
- Be **hand-readable in 3024**
- Preserve digital art and games **forever**
**Core Types**
**Preservation Plan**
- Text-based source: no binary blobs
- Versioned plexes: forward/backward compatibility
- Self-documenting syntax: just enough magic
- Open standard: no vendor lock-in
- Archive formats: =.ul=, =.rom=
| Type | Description |
|------+-------------------------------------------|
| =int= | 32-bit signed integer |
| =nat= | 32-bit natural number |
| =real= | Q16.16 fixed-point real number |
| =str= | fat pointer [length + data] string |
| =bool= | true/false |
| =char= | Character |
| =ref= | Reference prefix for passing by reference |
A ref type allows pass by reference similar to a pointer in c. most types will be pass by value with some types being explicitly pass by reference.
primitive types like int, nat, real, etc. will always be safe to change in child frames.
* Roadmap
[[./ROADMAP.org][Coroutines, Compiler, Memory Refactor, Plex, Immidate mode GUI, Constructive solid geometry, tunnels]]
* License
**MIT-0** - No restrictions, no warranty.
MIT-0
* Inspirations
- [[https://wiki.xxiivv.com/site/uxn.html][Uxn]] - Minimalism, elegance
- [[https://wiki.xxiivv.com/site/uxn.html][Uxn]] - The ideal system for permacomputing
- [[https://plan9.io/][Plan 9]] / 9P - Unified I/O, Tunnels
- [[https://forth-standard.org/][Forth]] - Shadowing, interactivity, immediacy
- [[https://forth-standard.org/][Forth]] - Shadowing
- [[https://en.wikipedia.org/wiki/Lisp_(programming_language)][Lisp]] - Live coding, REPL, introspection
- [[https://fortran-lang.org/][Fortran]] - Array semantics, scientific clarity
- [[https://en.wikipedia.org/wiki/C_(programming_language)][C]] / [[https://ziglang.org/][Zig]] - Control, minimalism
- [[https://lua.org][Lua]] - Languages can be portable and expressive without being complicated.
- [[https://www.craftinginterpreters.com/the-lox-language.html][Lox]] - Small languages are not impossible to create
- [[https://www.permacomputing.net][Permacomputing wiki]] - Sustainability, longevity
- [[http://duskos.org/][Dusk OS]] - Languages can survive the test of time.
- [[https://www.craftinginterpreters.com/the-lox-language.html][Lox]] - The start of my programming language creation journey
- [[https://www.permacomputing.net][Permacomputing wiki]] - Core ideology
- [[http://duskos.org/][Dusk OS]] - A much better system for doing permacomputing
- [[https://doc.cat-v.org/inferno/4th_edition/dis_VM_specification][Dis VM]] - CISC VM structure
- Retro Systems - N64, PS1, Mac Classic, Windows 95 - proof that beauty needs no bloat
- Retro Systems - N64, PS1, Mac Classic, Windows 95 - UI esthetics
* Join the Effort
The Reality Engine is a community project. We welcome:
- Port developers (Web, Game Boy, etc.)
- Artists and game designers
- Archivists and historians
* Contact
- Website: https://undar-lang.org
- Email: archive@undar-lang.org

159
ROADMAP.org Normal file
View File

@ -0,0 +1,159 @@
#+TITLE: Undâr Roadmap
#+AUTHOR: Zongor
#+EMAIL: archive@undar-lang.org
#+DATE: [2025-04-05]
#+LANGUAGE: en
#+OPTIONS: H:4 num:t toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t <:t
#+STARTUP: align fold nodlcheck hidestars oddeven lognotestate
#+TAGS: { TODO(t) NEXT(n) DONE(d) | HOLD(h) WAITING(w) CANCELLED(c) }
#+PROPERTY: header-args :tangle-mode (identity #o0644)
* Roadmap
** Memory System Improvements
in the beta phase the bump allocator will be removed and replaced with the following system: instead of the bump allocator memory and registers being separate, the frames are in vm memory and then dynamically allocate a frame and register with a size each time a function gets called of a particular arena size. the syntax would be fnN where N is the number is the size in kb for that particular frame, however the compiler will split up the available memory for that architecture into function arenas of the defined "theoretical maximum" of running coroutines. the "maximum" is going to be whatever is hardcoded for the VM, like 1MB or 100MB or 1GB or whatever. The true max will be <4GB because that is all that can be addressed by a u32. the sizes we can say that for a particular architecture has n many 128kb function and y many 8kb functions and so on; then the compiler could partition the system and report that the program requires x of this many slots of this size and y slots of another size. for strings we will extend the syntax we can use str8 is 8 bytes or str16 is 16 bytes or whatever, that way we have a shorthand that is consistent with the rest of the language and is friendly to new developers; however we would keep array syntax the same as in c-like languages `<type>[size]`. the stack and return stack need to be removed as well; like imagine you push some value onto the stack and then switch coroutines and then in that one it pops or pushes that would break. instead we will have a tiny stack per frame for function arguments and have a "return register" to allocate where the function should return to when it finishes. if it gets called recursively it will just take up a new block of the same size. recursive calls should be discouraged in general for this reason, its not a good technique for embedded development or permacomputing. so yes, it *will* compile, but guarantee the compiler *will* make fun of you. The compiler output in general should have an [[esolangs.org/wiki/INTERCAL][INTERCAL]] feel to it; the compiler needs to not take itself too seriously, but that does not mean it should not give useful advice to the programmer.
** Coroutine System
coroutine system that uses the yield keyword so that functions can run out of order. make all functions coroutines i.e. they should be first class objects in the vm. the system will work similar to a real time microkernel based os like qnx. when a coroutine yields the system will continue from the last function that was running, or it will continue in the "main function" which may spawn new coroutines. each call will allocate a new block in memory. there will also be a "behavior" system where a specific coroutine might have "restart on fail" where others might have "exit on fail" or still others might have "crash on fail", this allows for systems to react to changing external factors. in the long long term (post 1.0) i want to make coroutines similar to beam where they run mutithreaded an use mailboxes and message passing to communicate.
** Example: Hello world (=hello.ul=)
*WIP syntax, not final implementation**
#+BEGIN_SRC ul
fn main(int argc, str[] argv) {
print("nuqneH 'u'?");
}
#+END_SRC
** Example: 3D Client (=client.ul=)
**WIP syntax, not final implementation**
#+BEGIN_SRC ul
use "common.ul";
fn256 main(int argc, str[] argv) {
nat w = 800, h = 450;
Player me(argv[1], [0.0, 1.0, 2.0], PURPLE);
bool running = true;
while (running) {
window("Client", w, h) {
splitbox(parent.size, 0.25) {
canvas() {
if (button("Logout")) {
me.logout();
running = false;
}
}
}
splitbox(parent.size, 0.75) {
canvas() {
model(Floor([0, 0, 0], 30));
me.update();
model(Cube(me.pos, [0.5,0.5,0.5], me.color));
if (Player[] others = me.server.read("players")) {
for (p in others) {
model(Cube(p.pos, [0.5,0.5,0.5], p.color));
}
}
}
}
}
}
exits("Client Closed Successfully");
}
#+END_SRC
** Plex
A =plex= is a structure which, works like a struct but syntactically looks like a class. the naming of "plex" comes from douglas ross's paper "a generalized technique for symbol manipulation and numerical calculation". It is used instead of "class" or "struct" is to make a break from the historical baggage of "classes". unlike classes it does not describe a object in real life and copy it, but it allows for a convenient way to mediate states and handling. i.e. compositions + encodings instead of oop/polymorphisms. for example, instead of having a struct with a bool flag in it (like for a game alive/dead), we can create a multilist where the structs move from a "alive" list to a "dead" list; or things like that. instances of a plex in memory are called "atoms".
Plexes support permacomputing by allowing the developer to use lower level interfaces with a friendly syntax.
**WIP syntax, not final implementation**
#+BEGIN_SRC ul
plex Player {
str name;
real[3] pos;
init(str name, real[3] pos) {
this.name = name;
this.pos = pos;
}
update() {}
logout() {}
}
#+END_SRC
the language is statically typed and similar to c but with some array semantic ideas from fortran like row major, fortran style replaces need for vec or mat. arrays are first class values, the compiler uses optimized opcodes for array manipulation.
**WIP syntax, not final implementation**
#+BEGIN_SRC ul
real[3] pos = [1.0, 2.0, 3.0];
real[3][3] mat = identity(3);
real[3] result = mat * pos; ! compiler generates matrix-vector multiply
#+END_SRC
- Row-major order
- Fat pointers for slices and strings
- No =vec3=/=mat4x4= structs needed
- Supports composition, slicing, element-wise ops
it has a abstraction layer for devices that work as system calls that can open, read, write, close, and use ioclt for extra system calls that are beyond ops.
** Devices
(partially implemented)
core devices include
- screen
- mouse
- keyboard
- terminal
- tunnel
Devices are accessed via a namespace "path/to/device" and are implemented outside of the VM's runtime. This allows for the interface of the system to be the same within the VM but allow for specific variations on a concept depending on the device it is running on.
*** Immediate Mode GUI
UI elements are draw directly on the canvas and syntaically are spacial to show which elements are child elements of each other. Calling a function is the equivelent of a redraw of that element
*** 3D Modeling
3D Primitives + Constructive solid geometry (Simialr to OpenSCAD or .kkrieger) allow for the construction of more complex 3D models from the manipulation of primitives. It also has the ablity to create textures dynamically similar to .kkrieger, textures can be imported and auto mipmaped from larger sizes down to the internal RGB332 + 8x8 - 32x32 tile system.
** Tunnels: Unified I/O (Plan 9 / 9P-Inspired)
Tunnels are an abstraction called a tunnel which acts like a device like a screen, mouse, etc. that is inspired by Plan9. Plan 9 was an operating system developed at Bell Labs that treated all resources (including network connections and UI elements) as files in a hierarchical namespace. it allows files, web requests, sockets, etc. to be viewed through a simple unified interface. it is very similar to the 9p protocol where everything is a file in a filesystem, it just might be a file located on a server halfway across the world or on another planet. the only thing that is the difference is that it takes longer to read the file on mars compared to the file on your local system. the way the device system works is that it is written by a developer in another language like c, but from Undâr's pov the interface remains the same, its just a namespaced device like all the other devices, so that it is easy to understand by a new developer. so it is comparable with tcp/udp sockets or something esoteric like a lorawan network, or some new communication method that hasn't been invented yet.
**WIP syntax, not final implementation**
#+BEGIN_SRC ul
Tunnel server = Tunnel("tcp://localhost:25565");
if (server.attach(auth)) {
Player[] players = server.read("players");
server.write("me", me.update());
server.clunk();
}
#+END_SRC
*** Tunnel Operations
| Op | Meaning |
|------------+-------------------------------------------------------------------|
| =.attach()= | Authenticate and open communication |
| =.open()= | opens a tunnel for doing operations on |
| =.create()= | creates the plex from the database graph file from file structure |
| =.remove()= | removes the plex from the database graph file from file structure |
| =.read()= | reads from a tunnel |
| =.write()= | writes to a tunnel |
| =.walk()= | moves around the filesystem or through the graph |
| =.flush()= | cancels long operation and dumps whatever is in buffer |
| =.clunk()= | Close communication |
| =.stat()= | returns the status of the file resource |
| =.version()= | returns the version code for the connected tunnel |