Purely-functional language with linear types
Find a file
2022-08-22 21:33:24 -06:00
drimc_rs Introduce untyped IR 2022-08-22 21:33:24 -06:00
examples Rename the language to Drim 2022-08-10 23:07:33 -05:00
.gitignore Initial commit 2022-07-30 20:56:44 -06:00
README.md Rename the language to Drim 2022-08-10 23:07:33 -05:00

Drim

Drim is a programming language designed to have the very high-level ergonomics and provable correctness of a purely functional language, while maintaining speed using strictly-controlled, deterministic memory management. The language is capable of compiling to C (and possibly other languages in the future), allowing for maximum portability without having to write a new backend for the compiler for every possible target; also, the compiler and tooling will eventually be rewritten in Drim to allow for maximum portability.

Syntactically, the language primarily resembles a mixture between standard ML and Rust; the language always ignores whitespace.

Example

// Calculates the nth fibonacci number.
def fibonacci (n: U32) : U32 = match n {
  0 => 0,
  1 => 1,
  n => fibonacci (n - 2) + fibonacci (n - 1),
};

// Prompts the user for a number n, and prints the Fibonacci numbers up
// to n.
def fibPrompt = do {
  print "Enter a number: ";
  num <- read <$> getLine;
  sequence_ (print . fibonacci <$> (0 .. num));
};

// Program entry point.
def main : IO ()
  = fibPrompt;

Note that type annotations are always optional; here they're given for fibonacci and main for illustrative purposes, but omitted for fibPrompt (for which the compiler infers the return type IO ()).

Tools

This repository contains the following tools:

  • drimc-rs, the Stage-1 Drim compiler, written in Rust. This can be used as a binary with a fairly standard compiler CLI, or as a library for use in other Rust programs.

The following tools do not exist yet, but are planned:

  • drimc, the main Drim compiler written in Drim. This program supports a superset of the behavior of drimc-rs, and exposes a library that can be used by other Drim programs in addition to a the compiler CLI.
  • drim, the interactive Drim interpreter, a wrapper around drimc.
  • drimd, the Language Server Protocol (LSP) server for Drim code support in editors, supporting definition peeking and lookup, renaming variables and modules, etc.
  • drimfmt, the standard formatter for Drim code; all Drim code used in this repository must be formatted with drimfmt, and its use is recommended for other projects.
  • drimdoc, the documentation generator.
  • drim-mode, an Emacs mode for editing Drim code, supporting syntax highlighting, automatic indentation, some basic keybindings for common tasks, Emacs-side LSP integration for communicating with drimd, and a collection of yasnippet snippets for inserting common Drim constructs.
  • drim-vsc, Visual Studio Code plugins and tools for editing AlexScript code.
  • drim-vim, tools and configuration files for optimizing Vim and Neovim for editing Drim code.

Language features

The language is mostly influenced by Rust and Haskell: it has strict safety requirements and borrow-checked memory management like that of Rust, but its syntax and type system are similar to those of Haskell.

Some features the language will most likely have:

  • All functions are pure by default; side effects are chained together using an IO monad.
  • Despite the language's purity, expressions will be strictly evaluated to provide more programmer control.
  • Different monads represent different levels of safety, and can be converted using functions marked as UNSAFE. The intention is that code can be audited by manually checking that all the UNSAFE transformations are sound, and code that contains no UNSAFE function calls are guaranteed to satisfy varying definitions of soundness:
    • The IO monad represents computations that might have side effects on the real world. If a computation of type IO is known by the programmer to not have side effects on the real world, then it can be converted to a pure computation using the standard library function UNSAFE_assertPure : IO a -> a.
    • The MemoryUnsafe monad represents computations that might read from or write to memory that is not allocated correctly: for example, readPtr, which reads from a raw pointer, is of type MemoryUnsafe a because the pointer is not known to be valid. If a computation has been confirmed to be safe by the programmer, it can be converted to an IO computation using UNSAFE_assertMemorySafe : MemoryUnsafe a -> IO a.
    • Further safety monads may be added in the future.
  • The language achieves good performance by guaranteeing a number of optimizations:
    • Since the language uses a linear type system, garbage collection is not done; instead, values are stored on the stack unless explicitly declared to be on the heap, and heap-stored values are cleaned up at deterministic points as calculated at compile time.
    • Functions of type Fn t -> t are optimized into functions that operate on pointers to t, i.e., notionally, Fn (*mut t) -> (), where *mut t is a mutable pointer to a type t.
    • Types that contain an optional, non-null pointer like Option (Box a), Option (Ref a), etc., are optimized into nullable pointers.
    • Since the language has no loops, the compiler guarantees optimization of tail-call recursion to loops on all functions, as is standard in functional languages.

Compilation

When invoked with no flags, Drim by default compiles source code directly to code that is valid C and C++, then calls the system C compiler to generate an object file, then calls the system linker to generate an executable file.