undar-lang/docs/SPECIFICATION.org

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 plex is a container for primitive types.
  • Not a class: no inheritance, no vtables
  • Methods are functions with implicit this argument
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)