| ast-generator | ||
| cst-test-generator | ||
| dm | ||
| dm_lib_sketching/std | ||
| dm_std_lib | ||
| dm-std-lib | ||
| dmc-lib | ||
| doc | ||
| dvm-lib | ||
| examples | ||
| sketching | ||
| src | ||
| test-data/lexer | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| rust-toolchain.toml | ||
| TODO.md | ||
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
Diagnostics 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.