A simple close-to-the-metal object-oriented programming language
Currently in concept form, but if you're interested let me know and help build it :)
say Hello(stdout, text)
def run()
stdout.write(text:"Hello, World!\n")
fed
yas
The language aesthetic could be described broadly by the phrases "economy of mechanism" and "low conceptual volume".
To this end, the language uses: composition, immutable non-shadowable variables, garbage collection, object-orientatated programming for encapsulating state, nil global state, structural typing with compile-time checks, plain data types with run-time checks, single-threaded execution and asynchronicity via promises, loop for iteration with iterable data type, outcome data type for handling error cases, pass by reference with the exception of a primitive small integer type, pass parameters by position and/or by name, but no rest or dictionary parameters
Everything expressible in the language is expressed in terms of these concepts.
- Small arena-based memory allocation and collection (a few MB is commensurate with current cache sizes)
- Classes are created at runtime to reduce the need for instance fields, reducing memory cache usage
- 32-bit references reduce memory cache usage
- Aligning memory allocations to 8-byte boundaries allows tagging the lowest 3-bits of a pointer/primitive could be used to indicate which of 8 possible data type options have occurred at runtime
- Idiomatic use of simple data type matching may lend itself better to O(1) jump tables in conditionals, compared to if-else chains.
- 64-bit ARM assembly (Graviton, M1, desktop and servers)
- 64-bit x86 assembly (Intel, AMD, desktop and servers)
- Portable 32-bit/64-bit C89/C++ code (commodity embedded)
- TypeScript (JavaScript and serverless environments)
- GPU ? FPGA ?
It's a non-goal to create libraries for linking into other languages the only is to create application binaries only.
For simplicity the initial goal will be to first to write a compiler in TypeScript, which is very high level and should be quick to achieve. Then port the compiler from TypeScript to be self-hosted, using the TypeScript source code as a bootstrap. Then using that compiler, target portable C code. Then start adding specialisations, eg. custom calling conventions and hand-coded assembly if the target is either aarch64 or amd64.
| Conceptually, these are mutually-exclusive tags around an optional object parameter
Optionally :: <result> none
Outcome :: <result> <error>
say Task(log, optionally, queue, text, integer, box)
def prepare(work, domain)
| boxes are handy for their mutability
res = box.new()
items = queue.new()
| executes and matches work.assess()
| the compiler determines whether work.assess() could return or have created a promise;
| if it's possible then 'prepare' is also an async method which awaits on the work.access() call
| and all promises that have been created during its execution
| 'go' also creates a new memory arena which work.assess() and resulting code will allocate from
| code in work.assess() can reference memory from prior arenas, but memory from prior arenas cannot
| reference into a nested arena - instead memory must be copied or cloned backwards.
go work.assess()
result { res.set(result) }
error { ok }
og
| executes and matches work.assess()
| the compiler determines whether work.assess() only returns a promise;
| if so then the matching occurs asynchronously (out of scope).
| Otherwise it is a compile-time error.
| Other promises could be created but the 'of' statement won't await on them.
of work.assess()
| these blocks are somewhat like a closure
| it is an error to use => return in these blocks;
| that's too confusing because it is no longer in the prepare scope
result {
of work.triage(result)
result <deliverable> { work.plan(deliverable) }
error { log.warn(error) }
fo
}
error { log.warn(error) }
fo
| executes and matches work.assess()
| the compiler determines whether work.assess() could return a promise;
| if so then it is a compile-time error.
| Other promises could be created but the 'if' statement won't await on them.
if work.gather()
result { queue.add(result) }
none { ok }
fi
if integer.compare(items.length(), work.custom())
less { => optionally.result(res) }
greater equal { ok }
fi
| always list the most common cases first; the compiler won't re-order them.
| in the case of primitive like integer, "risks" and "priority" would refer to the same thing.
| this is unlike user types where the former would be the constructed type, and the latter
| would be the parameter for it
if domain.challenges() <risks>
0x40..0xff 0x00..0x29 { => risks }
0x30..0x39 <priority> { => integer.plus(priority, integer:1) }
-0x40000000..-0x01 0x0100..0x3fffffff { log.error(text:'Unexpected outcome {risks}') }
fi
| until does not await, so can't be used directly on promises
| but you could iterate a list of promises and use an inner 'go' statement to await them.
| in that case, until will await each loop in turn.
til none work.gather()
result { queue.add(result) }
lit
=> text:"success"
fed
def makeStuff(Foo, logger)
| Foo is a "class template" (as is Task above)
c1 = Foo.class()
a = c1.new(log: logger, z: integer:1)
b = c1.new(log: logger, z: integer:2)
c2 = Foo.class(log: logger)
x = c2.new(z: integer:1)
y = c2.new(z: integer:2)
| both of these sets of instances are functionally the same,
| but the latter will have lower memory usage due to sharing logger in the context.
| using this technique lowers the amount of data on the heap and by extension uses less cache memory
fed
yas
| user data type: defines one or more type constructors
| class template: natively-defined class template / user-defined class template
| used to create a "class instance" by filling in zero or more context parameters
| class instance: a class which is created at runtime using a class template
| type constructor: a name for the constructor, and designates whether a parameter is optionally accepted
| class constructor: an implicit method of a class instance that accepts parameters and returns a new instance of that class
| instantiation of a type:
| a constructed type whose constructor is runtime-discernable from the other constructors of the same type
| using pattern matching.
| if the constructor does not have a parameter,
| then a default is used instead, which is an instance of a class which has no methods
| instance of a class;
| thing: instance of a class / instantiation of a type / primitive
| user-defined class template:
| describes parameters for the instance and its methods
| natively-defined class template:
| describes the native functions that will be methods of a class, and context parameters of the class
| completely describes its interface to the compiler for type-checking
| primitive: opaque primitive / instance primitive
| opaque primitive: integer (i31)
| these may be used in pattern matching as ranges
| instance primitive:
| any other parsed value, such as a text string or list
| these may or may not be allowed for use in pattern matching (if a full range could be described)
| these may or may not have methods implemented
| pattern matching:
| has cases for exactly all possible outcomes,
| otherwise it is a compile time error.
| (it would be nice to allow only a subset of matching,
| but this adds too much syntactic and semantic complexity)