One week ago, I sat down and wrote some ideas about a dynamically typed language:
- Simple but convenient
- Garbage collected (later dropped in favor of lifetime checking)
- Easy to integrate with Rust
The more I thought about it, the more I wanted to make it, so I just stopped writing ideas and started coding…
… and it was so fun that I could not stop.
Say hello to Dynamo!
It is not even newborn yet, but you are welcome to join the project!
About the development
For parsing the grammar I used Piston-Meta, so it was possible to fix the syntax easily while working.
//
for single line comments/* */
for multi line comments that can be nested- operators are built into the grammar
:=
for declaring, =
for mutable assignment
One thing that annoyed me from working in earlier scripting languages is that you can accidentally
assign a string where you want a number. So the idea hit me that =
could check the type:
fn main() {
a := 2
a = "hi" // ERROR: Expected assigning to text
}
Planning to use :=
for inserting a new property in an object, while =
requires it to exist:
fn main() {
a := {x: 0, y: 0}
a.z = 2 // ERROR: Object has no key `z`
}
C/Go-like for loops
fn main() {
for i := j; i < n; i += 1 {
println(i)
}
}
Rusty if-expressions
fn main() {
b := if true { 0 } else { 1 }
}
You can also do this:
fn main() {
b := {
x := 0
x + 5
}
}
Return
Functions that returns something uses ->
in front of the brackets, like Rust.
An idea is to allow return
as a variable:
fn foo() -> {
return = 2 // set returned value without exiting function
return 3 // set returned value and exit function
}
Rusty labels on for
and loop
fn main() {
'outer: loop {
loop {
break 'outer
}
}
}
Javascript-like objects and arrays
So far I had not use for null
. Can use {}
or []
instead.
Since []
is Vec
in Rust under the hood, it does not allocate.
fn main() {
pos := {x: 0, y: 0}
mat := [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
obj := {}
}
Lifetime checking
When figuring out how to make it work without a garbage collector, I decided to write a statically analyzed lifetime checker, like Rust does.
fn foo(a) {
pos := {x: 0, y: 0}
a.pos = pos // ERROR: `pos` does not live long enough
}
You need to clone objects that do not live long enough:
fn foo(a) {
pos := {x: 0, y: 0}
a.pos = clone(pos) // OK
}
Dynamo also understands this for arguments:
fn foo(a, pos) {
a.pos = pos // ERROR: Requires `pos: 'a`
}
Fix it, and it runs:
fn foo(a, pos: 'a) {
a.pos = pos // OK
}
It also understands returns:
fn x(x) -> {
return {x: x, y: 0} // ERROR: Requires `x: 'return`
}
Again, just do as Dynamo says:
fn x(x: 'return) -> { // This means `x` outlives the returned value
return {x: x, y: 0} // OK
}
When you call a such function, you need to reference the argument:
fn x(x: 'return) -> {
return {x: x, y: 0}
}
fn main() {
println(x([0, 1])) // ERROR: Requires reference to variable
}
This happens because the argument need to outlive the returned value, so the way you do that is declaring a variable before passing it to the function:
fn x(x: 'return) -> {
return {x: x, y: 0}
}
fn main() {
foo := [0, 1]
println(x(foo)) // OK
}
How fast is it to parse and run?
In a release build, a simple program takes a few milliseconds:
Empty program:
fn main() {}
$ cargo bench main
Running target/release/dynamo-599221bc629f58af
running 1 test
test tests::bench_main ... bench: 2,018,252 ns/iter (+/- 45,396)
Incrementing a number 100 000 times:
fn main() {
x := 0
for i := 0; i < 100_000; i += 1 {
x += 1
}
}
$ cargo bench add
Running target/release/dynamo-599221bc629f58af
running 1 test
test tests::bench_add_two ... bench: 23,731,639 ns/iter (+/- 627,911)
This is what happens:
- Piston-Meta parses the source with the meta syntax
- The main thread constructs the AST, while another thread does lifetime checking directly on meta data
- If the program passes the lifetime check, it creates a runtime environment
- Walks over the AST and executes the program
Future work
- Booleans
- Better error reporting
- Better way of handling intrinsic functions?
- Modules?
- First class functions?
Want to join working on Dynamo?
Not sure if it is going to be useful yet, but if it does I would like to use it write some small games. Except from that, the goals and suggestions are completely up to the people working on the project.
If you have question or want to work on something, open up an issue here.