9.6 KiB
Undar (Reality Engine Language) Design parameters
What is Undar?
Undar is an permacomputing oriented programming language for 3D games with C style syntax. The compiler is written in C which should make it easy to port to other systems.
It is short for "Undarsċieppan" The name comes from the Proto-West-Germanic word Undar, which means "under" and Sċieppan meaning "to create". It inspired by the idea of "Sub-creation" from Tolkien and C.S. Lewis, that the developer is sub-creating a reality for their users, whether it be a video game, office software, a website, or a embedded driver.
Undar Grammar and Specification
Plexs
- A
plexis a container for primitive types. - Not a class: no inheritance, no vtables
- Methods are functions with implicit
thisargument
plex «token» {
init() {
// values
}
}
// example
plex Vec3 {
init(real x, real y, real z) {
this.x = x;
this.y = z;
this.y = z;
}
}
Substantial Plexs
| Type | Description |
|---|---|
byte |
Character (architecture specific) |
u8 |
exactly 8-bit unsigned int |
i8 |
exactly 8-bit signed int |
u16 |
exactly 16-bit unsigned int |
i16 |
exactly 16-bit signed int |
u32 |
exactly 32-bit unsigned integer |
i32 |
exactly 32-bit signed integer |
int |
1 word signed number |
nat |
1 word unsigned number |
real |
Q16.16 fixed-point real number |
str |
string |
bool |
true/false |
Basic operators
The following is a list of global operators and their effect:
-
//
- comment
-
/* *
- block comment
-
!
- not
-
-
- addition
-
-
-
- subtraction
- negation
-
-
-
- multiplication
-
-
/
- divisor
-
==
- equals
-
<
- less than
-
>
- greater than
-
>=
- greater than or equals
-
<=
- less than or equals
-
.
- accessor
logical / bitwise operators
-
mod- modulo
-
and- logical and
-
or- logical or
-
xor- logical xor
-
srl- bit shift right
-
sll- bit shift left
keywords
is
checks if a atom is of that plex
if («token» is real) {
print("self is a real");
}
also used for letting constants
as
coerces a plex as another plex if possible
nat «token» = 0;
some_functon(«token» as real); // needs a real
in
checks if a atom's plex contains the fields inside of a trait
if («token» in Tunnel, Drawable) {
print("im tunnel-able and draw-able");
}
also used inside of the for loops
for («token» in «collection») { «body» }
Control flow
loops
for («variable» in «collection») { «body» }
iterates through each atom in the collection setting it to variable
for («variable» = initial_value; end_value; increment) { «body» }
loops from initial value to end value by increment value (like a for loop in other languages)
while («boolean expression») { «body» }
loops until the expression is false
branching
if
if («boolean expression») {
} else if («boolean expression») {
} else {
}
switch (WIP)
switch (value) {
case A:
case B:
case C:
default:
}
Memory Management
All memory used by the program exists in one big bump allocator.
When a function gets called, it creates a new arena in memory starting at the end of the previous frame. (or at the beginning of memory if no function has been called)
Variables allocated in the frame's arena are owned by default by the frame they are allocated in.
When variables get modified within the current frame they will get updated in that frame in their current location in the arena.
All complex types (Plex, arrays, strings) use fat pointers where they have a header of the length of that block and a "next" ptr that refers to the next allocated block for that value
If the size of the variable changes (like adding a value to the end of a array), a link is added to the internal list that points to a new block is allocated at the end of the arena of the size of that value, so they are variable sized blocks
When a variable is passed to a child function primitive types are pass by value, complex types (Arrays, Plexes, Strings) are pass by reference.
Variables passed as arguments to the child follow the same rules except when the size of a complex type changes, then a new value is added as a link from the parent to the child and the new value is allocated at the end of the childs arena (but the owner is still the parent).
When a function "returns" the frame is deallocated and all values allocated by it is freed; thus freeing memory deterministically.
Only a single variable may be returned from a function. When a variable is returned if it is primitive it is just passed back to the parent. If it is complex the value is coalesced into a single contiguous values and the link in the parent is updated to point at the "new" end of the arena. If the parents block is at the end the entire block is coalesced into a single contiguous block.
This allows for the low resource usage of a C but the convenience of a garbage collected language like C# or Go but without the GC pauses.
datastructure
Array
Array of a specific plex
«plex»[«length»] «variable» = [val1, val2, ...];
Tunnel WIP
Represents a path to a file, url endpoint, other process endpoint (like a socket, file, etc.)
Tunnels are inspired by translators in gnu/hurd, plan9 9p protocol, and unix sockets
A plex must implement the tunnel trait
tunnels
tunnel? : attach(tunnel_atom) -> open communication
success? : tunnel_atom.clunk() -> close communication
success? : tunnel_atom.flush() -> cancels long operation and dumps
whatever is in buffer
success? : tunnel_atom.open(resource, mode) -> opens a tunnel for
doing operations on
success? : tunnel_atom.create(resource) -> creates the atom from
the database graph/file from file structure
data? : tunnel_atom.read(resource) -> reads from a tunnel
success? : tunnel_atom.write(resource, data) -> writes to a tunnel
success? : tunnel_atom.remove(resource) -> removes the atom from
the database graph/file from file structure
stat_data? : tunnel_atom.stat(resource) -> returns the status of the
file/resource
version? : tunnel_atom.version() -> returns the version code for the
connected tunnel
success? : tunnel_atom.walk(path_or_endpoint) -> moves around the
filesystem or through the graph
// client
Client endpoint("tcp://path/to/source");
Tunnel tunnel = endpoint.attach(user, auth);
str data = tunnel.open("/some/resource").read();
std.write(data);
data.flush();
endpoint.clunk();
// server
Server server("tcp://0.0.0.0:25565");
s.bind("/some/resource", fn () str {
return "hello world";
})
server.start();
Functions
Functions are all typechecked statically at compile time. Since we
always have a "default plex" for all constant values or a developer can
use the as keyword we do not have to define all values like in C,
while keeping the same plex safety as a more strongly typed language.
function «token» («parameter» «plex», ...) «return_plex» {
«body»
}
-
Built in stdlib
- sort
- filter
- trig functions
- calc functions
- statistical functions
Localization
will look up the text of «token» in the linked localization.json file
#«token»
{
"some_token": [
"localization_1": ""
],
"some_other_token": [
"localization_1": "",
"localization_2": ""
]
}
Libraries and "includes"
In most languages the include or use statements get libraries which link to other files and so on.
use "./some_local_file.ul"
Testing
assertion
assert(«expression», «expected output»)
Measurements
-
types
-
time
-
unit
- seconds (s)
-
subtypes
-
date
- Default is ISO 8601
-
-
-
length
-
unit
- metre (m)
-
subtypes
-
angle
- radian (rad)
-
-
-
mass
-
unit
- kilogram (kg)
-
-
electric current
-
unit
- ampere (a)
-
-
temperature
-
unit
- kelvin (K)
-
-
amount of substance
-
unit
- mol (mol)
-
-
luminous intensity
-
unit
- candela (candela)
-
-