Write README.md.

This commit is contained in:
Jesse Brault 2026-03-02 21:16:18 -06:00
parent 8c1d56dc1a
commit db675b5601

224
README.md Normal file
View File

@ -0,0 +1,224 @@
# Deimos
### Brief History
Deimos lang is a project I properly began in November 2024 while a student at the
University of Wisconsin-Madison. I was learning Rust at the time and was inspired
by its elegance and complexity.
I had had ideas for creating a language as early as 2020, but did not yet have the computer
science skills necessary for designing and implementing it. I was mainly
inspired by the linguistic problems at hand in language design, especially
grammar design and parsing.
After completing an introduction-to-compilers course in spring 2025 (as well as my
summer internship), I returned to work on the project in the fall of that year.
I grew very frustrated with the state of the code base after a few months; my
best software engineering instincts and skills were running up against classic
problems: inflexibility, unmaintainability, and lack of proper testing.
As such, I rebooted the project in February 2026 from scratch. The old project
code all resides under `/src`, whereas one should look in the following directories
for the new, hopefully better, code:
- `/dm`: the main entry program; takes a script and runs it.
- `/dm-std-lib`: the runtime standard library for Deimos (Rust side)
- `/dmc-lib`: the compiler proper
- `/dvm-lib`: the virtual machine
Currently, one can run the `hello.dm` example (from the `/examples` directory)
by running:
`cargo run -p dm -- examples/hello.dm`
You will see `Hello, World!` as well as `None` printed to the console. The `None`
is returned by the Deimos Virtual Machine (dvm) upon completion; I'm planning on
having the `call()` function (in `dvm-lib/src/vm/mod.rs`) return whatever the `main`
(Deimos lang) function returns, possibly limiting it in a manner similar to Rust
(like a `Result<(), Error>` kind of thing).
## Goals
- Fast and embeddable, like Lua
- Scalable, like Java/JVM, but without the complexity
- Elegant syntax, like Ruby/Rust
- Convenient, like Groovy
- Mathematically inspired, like Haskell
## Grammar
Not all of this is implemented yet, nor is this complete by itself.
```
compilation_unit := module_level_declaration*
module_level_declaration := function | extern_function
function := FN identifier L_PARENS parameter_list? R_PARENS statement* END
extern_function := EXTERN FN identifier L_PARENS parameter_list? R_PARENS
identifier := [a-zA-Z_]+
parameter_list := parameter ( COMMA parameter )*
parameter := identifier COLON type_use
type_use := identifier
statement := let_statement | expression_statement
let_statement := LET identifier EQUALS expression
expression_statement := expression
expression := integer_literal | string_literal | call
integer_literal := [0-9]+
string_literal := DOUBLE_QUOTE .* DOUBLE_QUOTE
call := expression L_PARENS expression_list? R_PARENS
expression_list := expression ( COMMA expression )*
```
It's been enough to chew on for now.
## Type System
Again, not all implemented, but these are my ideas:
- Primitives
- Byte, Short, Int, Long (might have unsigned versions, too)
- Char, String (yes, String is primitive; everything UTF-8)
- Boolean
- Arrays
- Interfaces and Classes
- Closures/Function pointers
- Higher-kinded types/Traits like Haskell/Scala
## Examples
The following demonstrate some planned features for the language.
Basic hello world:
```
fn main()
println("Hello, World")
end
```
Map some cli arguments using closures:
```
fn main(args: Array<String>)
args
.map { it.toUppercase() }
.each { println(it) }
end
```
Interface, classes, and some other things:
```
int Animal
fn name() -> String
fn speak() -> Void
end
class Dog(name: String) : Animal
fn name() -> String = self.name
fn speak() -> String = 'Woof'
end
class Cat(name: String) : Animal
fn name() -> String = self.name
fn speak() -> String
self.name == 'Good Kitty' ? 'Meow' : 'MEOW'
end
end
fn main()
let doggo = Dog('Bearo')
let kitty = Cat('Willow')
let animals = [doggo, kitty] // inferred to be List<Animal>
animals.each { println(it.speak) } // Woof | MEOW
end
```
Higher-kinded types and enums:
```
trait Functor[T]
fn <U> map(f: fn (t: T) -> U) -> Self<U>
end
enum Option<T>
Some(T),
None
end
impl<T> Functor[T] for Option<T>
fn map(f: fn: (t: T) -> U) -> Self<U>
if self is Some(t) then
Some(f(t))
else
None
end
end
end
fn main()
let x = Some(42)
let y = x.map { it / 2 }
if y is Some(twentyOne) then
println(twentyOne) // 21
else
throw Exception()
end
end
```
Higher-kinded types can ask for static methods (sort of like Rust):
```
trait Default
static default() -> Self
end
int Animal
fn speak() -> String
end
class Dog(name: String) : Animal, Default
static fn default() -> Self = Dog('Skyeo')
fn speak() -> String = 'My name is ' + self.name
end
fn main()
let sixteenDogs = Array::fill::<Dog>(16) // or something like this
sixteenDogs.each { println(it.speak) } // My name is Skeyo (x16)
end
```
## Implementation
### Compiler
I'm currently using a handwritten lexer and parser. The grammar is embedded
throughout the methods of the Parser implementation; once the grammar is more
stable, I will use a proper predictive-parsing algorithm using a parse table
(built from FIRST and FOLLOW sets), which should be not only faster but much
cleaner.
Each AST node has the following methods:
- `gather_name_declarations`: The first phase of name analysis. This just gathers
all declared names (functions, parameters, variables, etc.), since the plan is
to allow entities that are declared later in the file to be in scope (unlike C,
where forward declarations are required to do so).
- `check_name_usages`: Make sure that usages of names refer to entities that are
in scope.
- `type_check`: Assert that types of parameters/arguments match, etc.
- `assemble`: Convert the AST nodes to Deimos assembly code.
The first three methods must all be called, in the order listed above, as they
constitute distinct, dependent phases of semantic analysis. If there are no
`Diagnostic`s at all from those phases, `assemble` can be safely called.
### Deimos Assembly Code
The `asm_*` modules contain a low-level set of Rust enums/structs which basically
map one-to-one to the actual code used by the virtual machine. I'm trying to
design this language with optimizations and register allocation algorithms in mind.
### Virtual Machine
The virtual machine is inspired by the Lua and Java virtual machines. It is register-based,
and uses a stack for function calls, etc. It supports calling Rust code from Deimos code via
a `PlatformFunction` API.