diff --git a/dm-book/.gitignore b/dm-book/.gitignore new file mode 100644 index 0000000..e9c0728 --- /dev/null +++ b/dm-book/.gitignore @@ -0,0 +1 @@ +book \ No newline at end of file diff --git a/dm-book/book.toml b/dm-book/book.toml new file mode 100644 index 0000000..a419574 --- /dev/null +++ b/dm-book/book.toml @@ -0,0 +1,4 @@ +[book] +title = "Deimos Lang" +authors = ["Jesse Brault"] +language = "en" diff --git a/dm-book/src/README.md b/dm-book/src/README.md new file mode 100644 index 0000000..f7af810 --- /dev/null +++ b/dm-book/src/README.md @@ -0,0 +1,43 @@ +# Introduction + +Greetings! This is the manual for **Deimos Lang**, a programming language created by Jesse Brault. Deimos has the +planned following features: + +- Basic object-oriented features and semantics, including interfaces, classes, and records. +- Enumerated (union) types like Rust/OCaml. +- Traits, which function similar to Rust traits or Haskell type-classes. +- Closures, including those with delegates like Groovy or Kotlin. +- Compile-time metaprogramming with template-like constructs. +- The usual imperative constructs: if/else, for/while loops, etc. +- Functional features, borrowed from languages such as OCaml, Rust, Haskell: + pattern matching, persistent data structures, and tail recursion. +- Static name-resolution and type-checking, with compiler type-inference as much as possible. +- Optional fully-dynamic dispatch via `dyn` keyword, providing runtime method/property lookup like Groovy or Ruby. +- A fast foreign-function interface FFI for calling native Rust functions. + +Deimos is compiled for the **Deimos Virtual Machine** (DVM), a VM like those of Java and Lua in spirit but with a Rust +implementation and access to the rest of the Rust (and C, via Rust) ecosystem. The ultimate goal is to be able to run +Deimos and Lua side-by-side on the DVM, allowing applications both a statically typed language like Deimos to function +alongside and interface with a much more relaxed language like Lua. + +## Hello, World! + +Since it's standard practice to offer the classic Hello World program, let's get started! + +```deimos +fn main() + println("Hello, World!") +end +``` + +Sometimes we just need to dump a string to the terminal, so `println` is available by default in all scopes. Indeed, all +items in the `std::core` module are imported by default to each file/module. In this case, the fully-qualified name of +`println` is `std::core::println`, and is located in the `print.dm` file under `std/core`: + +```deimos +pub extern fn println(message: Any) -> Void +``` + +Here, `println` is declared as an `extern` function, meaning its implementation is provided by a native function at +runtime. We can also see that it takes one parameter of type `Any`—where `Any` is the catchall super-type of all types +in the language. `Void` takes its usual meaning. diff --git a/dm-book/src/SUMMARY.md b/dm-book/src/SUMMARY.md new file mode 100644 index 0000000..cd65cd2 --- /dev/null +++ b/dm-book/src/SUMMARY.md @@ -0,0 +1,6 @@ +# Summary + +[Introduction](README.md) + +- [Getting Started](getting_started.md) +- [Implementing Array List](./impl_array_list.md) diff --git a/dm-book/src/getting_started.md b/dm-book/src/getting_started.md new file mode 100644 index 0000000..04403bd --- /dev/null +++ b/dm-book/src/getting_started.md @@ -0,0 +1,159 @@ +# Chapter 1: Getting Started + +Deimos can be run both interactively via a read-eval-print loop (REPL) or by compiling and running a given source file. + +## REPL + +The REPL allows interactive programming similar to classic functional languages as well as Ruby. To start the REPL, run: + +``` +dm repl +``` + +### Basics + +All statements and expressions in the REPL are scoped under a synthetic function; in other words, variables persist +through each input: + +``` +> let x = 2 +> x +2 +> let y = 3 +> x + y +5 +``` + +In addition to statements and expressions, functions can be defined in scope: + +``` +> fn double(x: Int) x * 2 end +> double(4) +8 +``` + +Note that variables in the "top" scope are not available inside defined functions: + +``` +> let x = 7 +> fn doubleX() x * 2 end +Error: Symbol x could not be found. +``` + +This requires the use of closures, which can "capture" variables in their containing scope: + +``` +> let x = 42 +> let doubleX = { x * 2 } +> doubleX() +84 +``` + +Note the difference in syntax between function declarations and closure declarations. As we will later see, closures are +powerful constructs. + +### Introduction to Classes + +Classes can also be defined in the REPL: + +``` +> class Dog \ + pub name: String \ + pub ctor(name: String) \ + self.name = name \ + end \ +end +``` + +A few things are going on here. First, note the use the backslash to indicate that we are not done with our input +(normally, a newline without a preceding backslash will cause the interpreter to consume everything input so far). Next, +note our syntax for declaring a class, with hints of Rust and Ruby: + +- `class` keyword: the usual meaning +- `pub` keyword: makes a property or method available outside the class (i.e., non-private) +- `: Type` annotations: indicates the type of a property, parameter, or variable. +- `ctor` keyword: marks a constructor. *Note: Classes can only have **one** constructor*. +- `self` keyword, followed by property name: like the usual construct in OOP languages. + +Once we've defined our class, we can use it: + +``` +> let bear = Dog("Bear") +> let skye = Dog("Skye") +> bear.name +Bear +> skye.name +Skye +``` + +Note above how we construct instances of the class by "calling" the class, exactly like we do in Kotlin. + +#### Class Declaration Short Form + +The above class example is a bit verbose; Deimos offers the following equivalent, inspired by Kotlin: + +``` +class Dog(pub name: String) end +``` + +This is especially useful in the REPL, where we may just want to declare a data type for doing some data processing. + +``` +> class Point(pub x: Int, pub y: Int) end +> fn double(p: Point) Point(p.x * 2, p.y * 2) end +> let a = Point(3, 4) +> double(a) +Point(6, 8) +> let b = Point(5, 6) +> double(double(b)) +Point(20, 24) +``` + +## Compiling and Running Files + +To compile and run a file, do: + +``` +dm run .dm +``` + +Note that the `.dm` extension is required in the command name (unlike `java`). + +The above command will compile and immediately run the file. In order for a Deimos file to be executable, it must +contain a `main` function like the following: + +``` +fn main() + println("Hello, Deimos!") +end +``` + +Unlike the REPL, statements and expressions are not allowed in the top-level scope; they must be contained in a function +or method: + +``` +let x = 10 // ERROR! Will not parse. +``` + +However, functions, classes, and other top-level constructs are allowed: + +``` +fn double(p: Point) -> Point + Point(p.x * 2, p.y * 2) +end + +class Point(x: Int, y: Int) end + +fn main() + let a = Point(1, 2) + let b = Point(3, 4) + println(double(a)) // Point(2, 4) + println(double(b)) // Point(6, 8) +end +``` + +``` +let f = { a, b -> a + b } << 4 +let x = f(5) +println(x) // 9 +``` diff --git a/dm-book/src/impl_array_list.md b/dm-book/src/impl_array_list.md new file mode 100644 index 0000000..f73ae5e --- /dev/null +++ b/dm-book/src/impl_array_list.md @@ -0,0 +1,296 @@ +# Implementing Array List + +``` +trait Map + fn map(f: fn (T) -> U) -> Self +end + +trait Index + type Output + + op fn [] (index: I) -> Self::Output +end + +trait Cons + fn cons(t: T) -> Self + fn head() -> Option + fn tail() -> Self +end + +trait Default + static fn default() -> Self +end + +int Iterable + fn iter() -> Iterator + + def fn each(f: fn (T) -> Void) + for t in self + f(t) + end + end +end + +int Iterator + fn next() -> Option +end + +#[intrinsic] +class Array : Iterable + pub extern static fn sized(size: Size) -> Array + + #[intrinsic] + pub fn len() -> Size end + + #[intrinsic] + pub fn getAt(index: Size) -> T end + + pub fn iter() + let mut i = 0 + let iterator = { + if i < len() then + let next = Some(getAt(i)) + i++ + next + else + None + end + } + iterator + end +end + +record Range(start: T, end: T) end + +impl Index for Array + type Output = T + + #[inline] + op fn [] (index) = getAt(index) +end + +impl Index> for Array + type Output = Self + + op fn [] (range) + let ts = Self(range.end() - range.start()) + for i in range do + ts[i] = self[i] + end + ts + end +end + +impl Cons for Array + fn cons(t) + let ts = Array::(len() + 1) + ts[0] = t + for i in 0..len() do + ts[i + 1] = self[i] + end + ts + end + + fn head(t) + if len() > 0 then + self[0] + else + [] + end + end + + fn tail() + if len() > 0 then + self[1..] + else + [] + end + end +end + +int List : Iterable + fn len() -> Size + fn getAt(index: Size) -> Option + fn slice(range: Range) -> Self + + mut fn add(t: T) -> Void + mut fn addAll(ts: Iterable) -> Void + + def mut op fn << (t: T) -> Void = add(t) + def mut op fn << (ts: Iterable) -> Void = addAll(ts) + + mut fn insert(t: T, index: Size) -> Void + + def fn iter() + let mut i = 0 + let iterator = { + if i < len() then + let next = getAt(i) + i++ + next + else + None + end + } + iterator + end +end + +impl Map for List + fn map(f) = match self + [] => [], + head :: tail => f(head) :: tail.map(f) + end +end + +impl Cons for List + fn cons(t) + let l = ArrayList(self.len() + 1) + l << t + l += self + l + end +end + +impl Index for List + type Output = Option + + op fn [] (index) = getAt(index) +end + +impl Index> for List + type Output = Self + + op fn [] (range) = slice(range) +end + +int UnsafeIterable + fn iter() -> UnsafeIterator +end + +int UnsafeIterator + unsafe fn next() -> Option throws NullPointerException + + def fn tryNext() -> Result, NullPointerException> + try + Ok(next()) + catch e: NullPointerException + Err(e) + end + end +end + +#[intrinsic] +class MaybeUninitArray : UnsafeIterable + pub extern static fn sized(size: Size) -> Self + + #[intrinsic] + pub fn len() -> Size end + + #[intrinsic] + pub unsafe fn getAt(index: Size) -> T throws NullPointerException end + + class MaybeUninitIterator(parent: MaybeUninitArray) : UnsafeIterator + mut i: Size = 0 + + pub unsafe fn next() -> Option throws NullPointerException + if i < parent.len() + let next = parent.getAt(i) + i++ + next + else + None + end + end + end + + pub unsafe fn iter() + MaybeUninitIterator(self) + end +end + +class ArrayList : List + #[get] + mut len: Size = 0 + + mut ts = {% if T has Default then %} + Array + {% else %} + MaybeUninitArray + {% end %} + + pub ctor(capacity: Size) + ts = {% if T has Default then %} + Array::sized::(capacity) + %{ else %} + MaybeUninitArray::sized::(capacity) + {% end %} + end + + pub fn getAt(index) + {% if T has Default then %} + index < len ? Some(ts[index]) : None + {% else %} + if index < len then + try + Some(ts[index]) + catch e: NullPointerException + None + end + else + None + end + {% end %} + end + + pub fn slice(range) + /* some implementation */ + end + + mut fn maybeGrow() + if len + 1 == ts.len() then + let newSize = len * 2 // or whatever factor + + let newTs = {% if T has Default then %} + Array::sized::(newSize) + {% else %} + MaybeUninitArray::sized::(newSize) + {% end %} + + for i in 0..ts.len() do + newTs[i] = ts[i] + end + ts = newTs + end + end + + pub mut fn add(t) + maybeGrow() + ts[len] = t + len++ + end + + pub mut fn addAll(ts) + ts.each { add(it) } + end + + pub mut fn insert(t, i) + maybeGrow() + let mut previous = None + for i in i..len() do + match previous with + Some(p) => do + let cur = ts[i] + ts[i] = p + previous = Some(cur) + end + None => do + previous = Some(ts[i]) + end + end + end + ts[i] = t + len++ + end +end +``` \ No newline at end of file