Go to file
2026-03-08 13:33:06 -05:00
ast-generator Fmt all the old code. 2026-03-04 22:15:39 -06:00
cst-test-generator WIP on enum generation and solving generated errors. 2025-09-14 21:06:58 -05:00
dm Add return mechanism to dvm code, and related fixes to dmc. 2026-03-08 13:33:06 -05:00
dm_lib_sketching/std Rename dm_lib to dm_lib_sketching 2024-12-31 12:07:48 -06:00
dm_std_lib Set up namespace declarations as desired. 2025-02-04 13:05:52 -06:00
dm-std-lib Work on calling conventions and instructions. 2026-03-04 12:13:05 -06:00
dmc-lib Add return mechanism to dvm code, and related fixes to dmc. 2026-03-08 13:33:06 -05:00
doc Clean up of references and Rcs; better information hiding. 2024-12-30 16:27:33 -06:00
dvm-lib Add return mechanism to dvm code, and related fixes to dmc. 2026-03-08 13:33:06 -05:00
examples Add return mechanism to dvm code, and related fixes to dmc. 2026-03-08 13:33:06 -05:00
sketching More json sketching. 2025-11-10 22:08:09 -06:00
src Fmt all the old code. 2026-03-04 22:15:39 -06:00
test-data/lexer Initial commit. Very rudimentary lexer. 2024-11-24 18:07:16 -06:00
.gitignore Finally building an AST with Pest. 2025-01-30 18:36:35 -06:00
build.rs Fmt all the old code. 2026-03-04 22:15:39 -06:00
Cargo.lock Yay! Hello world via platform function is working. 2026-03-02 18:22:24 -06:00
Cargo.toml Yay! Hello world via platform function is working. 2026-03-02 18:22:24 -06:00
README.md Write README.md. 2026-03-02 21:16:18 -06:00
rust-toolchain.toml Add codespan-reporting to project for awesome error reporting. 2025-05-16 09:09:25 -05:00
TODO.md Add todos. 2025-05-22 15:02:29 -05:00

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.