pistondevelopers / dyon Goto Github PK
View Code? Open in Web Editor NEWA rusty dynamically typed scripting language
License: Apache License 2.0
A rusty dynamically typed scripting language
License: Apache License 2.0
Currently, all errors panics, with no file or line number.
Currently it is not possible to call a method on a module that returns a value.
call_ret(module, function, args)
Dyon uses a separate dynamic type for optional values. The optional type has two variants, none
and some
.
none()
creates an empty valuesome(x)
creates a specified value, using deep cloneunwrap(x)
unwraps an optional value, gives an error message if none()
if x != none() { ... }
can be used to check whether value is setThis is designed for:
Currently, a clone(a)
where a
contains internal references is unsafe.
The idea is that clone
always should returns a copy of all data it contains, such that you can use it when you do not want to use lifetimes.
ast::Expression
is the most central node in the AST. Currently it uses a lot of Box
. It might be that reserving some memory upfront and packing the nodes in a Vec
will improve performance a bit.
usize
and Vec<Expression>
This could be a simple markdown file.
Given it's place in the Piston project, I've been assuming that dynamo is intended to be used for game scripting. However, that's not explicitly mentioned anywhere and I don't want to spend time writing a proposal that turns out to be inappropriate for your vision of the language.
If it is game scripting, what kind of level are you expecting it to be best at (keeping in mind that people will contort any language into any situation with enough effort)? Is it intended to be mainly used for large amounts of game logic, or for small bits of custom behaviour?
I'd like to help with the language, but I don't really know anything about the language to do so.
Example code:
fn main() {
println(":")
}
Error message:
Could not find declaration of `"`
2,15: println(":")
2,15: ^
Related to #71
Could live in the same repo for now to get faster iteration on Dyon functionality.
module.rs:
fn lifetime_1(a, b: 'a) {
a.b = b
}
loader.rs:
fn main() {
a := {b: [0]}
loop {
b := [2]
m := load("source/module.dyon")
call(m, "lifetime_1", [a, b])
break
}
println(a.b)
}
This causes unsafe behavior because the lifetime of b
is not checked.
We should do this before people start to contribute.
fn one() -> {return 1} // commenting this function triggers correct lifetime check
fn bar(x: 'return) -> {return [x]}
fn foo() -> {
return bar(0)
}
fn main() {
println(foo())
}
This happens only when the function is first.
Considering that the prelude can change for every module, Dyon should have a way to list all the available functions and their argument lifetimes. This could be added as a standard intrinsic functions()
.
When printing out an object or array, it should use the same syntax as for parsing. This means strings must be escaped with "
in the JSON format that piston-meta uses.
Dyon uses an optional type system, one that complains if the types are proven to be wrong.
bool
(boolean)f64
(number)vec4
(4D vector)[]
/[T]
(array){}
(object)opt[T]
(option)res[T]
(result)thr[T]
(thread)any
- no syntax keyword - (any type, except void
)void
- no syntax keyword - (no value type)fn foo() -> opt[bool] {
return some(true)
}
fn main() {
if foo() {
println("oh?")
}
}
--- ERROR ---
In `source/test.rs`:
Type mismatch: Expected `bool`, found `opt[bool]`
6,8: if foo() {
6,8: ^
An any
type works with any other type except void
.
Consider a simple example like this:
fn foo(a: bool) { ... }
It is not guaranteed that a
has the type bool
inside the function body. This is because the function can be called with an argument that is inferred to any
at type checking.
These rules are designed for:
Someone on reddit pointed out that Dynamo is already used by http://dynamobim.org/.
This needs to be supported to add custom Rust functions.
Example:
fn say__msg_to(msg, person) {
print(person + "! ")
println(msg)
}
fn main() {
// Normal call syntax.
say__msg_to("hi!", "you there")
// Named argument call syntax.
say(msg: "hi!", to: "you there")
}
Two underscores __
separates arguments from the function name. A single underscore _
separates arguments.
This syntax has the following benefits:
Normal call syntax:
<name>(arg0, arg1, ...)
Named arguments sugar call syntax:
<name>(<arg0>: arg0, <arg1>: arg1, ...)
The following rules are used:
<arg0>
, <arg1>
etc. to the name separated by _
The rules are designed for:
Declaration syntax:
<left> := <right>
Mutable assignment syntax:
<left> ?= <right>
Where ?=
can be =
, +=
, *=
etc.
The following rules are used:
:=
does a shallow clone of right value, if it is a reference=
does a shallow clone of right value:=
ignores the type of the assigned item?=
checks the type of assigned item?=
does a shallow clone (copy-on-write) of left value before assigning, if it is a referenceThe rules are designed for:
http://pike.lysator.liu.se/docs/tutorial/oop/programs.xml
The Pike programming language uses classes and programs as a concept for the same thing.
Relevant to Dyon because it uses dynamic modules and programs for the same thing.
Currently there is only if
and else
, but no else if
.
Currently, this does not work:
fn main() {
for i := 0; i < 100; i += 1 {
if i > 14 {
return
}
println(i)
}
}
A technique I use is to reload a script every nth second. To do this safely I need to call the "main" function for every event.
fn main() {
if render() {
// Do rendering.
}
if update() {
// Do update.
}
}
Since Runtime::stack
is separated from the module, it is possible to share state through the stack.
Since variables runs out of scope, there is no state. I tried hacking this using a custom Rust function, but it doesn't work with the current assignment rules (perhaps a good thing). The reason it doesn't work is references are only resolved one step. Assigning to Variable::Ref(0)
means a reference to a reference, which does not get resolved.
One idea is to push an object at the bottom of the stack and call it "root". This object is available everywhere and can be used to store global settings and share state.
:=
should not be allowed with the "root" object=
should work whenever the left side is "root"Currently, the file is not included in error messages.
Could print out stack trace with function names and files.
file: Arc<String>
to ast::Function
.draw_2d
is not designed to be called for each shape. In order to avoid overhead, could use a list of objects that contained the render operations.
draw
which renders draw list commandsShould be similar Javascript, except typeof([])
returns "array".
I want to use typeof(return)
to check whether return value has been set. This makes it more consistent with using return
as a variable.
fn typeof_return() -> {
if typeof(return) == "return" {
println("return OK")
}
return = 2
if typeof(return) == "number" {
println("return is number OK")
}
return
}
fn main() {
if typeof("hi") == "string" {
println("string OK")
}
if typeof(3) == "number" {
println("number OK")
}
if typeof([]) == "array" {
println("array OK")
}
if typeof({}) == "object" {
println("object OK")
}
if typeof(true) == "boolean" {
println("boolean OK")
}
println(typeof_return())
return
}
This requires a special expression rule for arguments, since return
would return from a function when called within a block. Here it is interpreted as a variable.
Dyon uses mut
to declare mutability. This appears in front of a function argument declaration or when calling a function that mutates the argument.
foo(mut a)
mutates the argument a
foo(mut a)
has function name foo(mut)
foo(mut a, b)
has function name foo(mut,_)
foo(a, b)
has function name foo
Local variables are mutable.
Mutability is not part of a type, but added to the function name inside parentheses, e.g.(mut,_)
. When adding an external function, the mutability information must be added as part of the function name.
Mutability propagates for arguments:
fn foo(mut a) { ... }
// `bar` requires `mut a` because it calls `foo(mut a)`.
fn bar(mut a) { foo(mut a) }
Multiple functions can use the same name if they have different mutability information. This reduces the need for "get", "set" and "mut" in function name:
fn title(window: 'return) -> { ... }
fn title(mut window, val) { ... }
t := title(window)
title(mut window, "hello world!")
This is designed for:
I see you're sticking to some rust syntax, I always found loop labels and lifetimes to be too similar at first glance. Any chance you'd reconsider the single quote for something else?
Perhaps:
@a: loop {
break @a
}
Have you already discussed syntax? I'd enjoy reading it if so!
This is frequently written and should be as short as possible.
Lt
is an abbrivation for "lifetime".
Currently, the runtime uses a loop to find the local variable declaration on the local stack.
This could be made faster by resolving the local declaration statically. Instead of a local stack, the item name is replaced by an integer that counts back relative to the end of the stack.
Similar to #5, but designed for arrays.
When using :=
in arrays, it should not allocate a new slot if the index is outside the range, since this might lead to confusing bugs. However, it can be used to overwrite the type, for example replacing a number with a string.
a = [5]
a[0] = "hello" // ERROR: Expected assigning to text
a[0] := "hello" // OK
One idea is to use type checking where it complains if there is a proof that a variable has the wrong type. This makes it possible to specify as much type information you like.
fn foo(arr: [f64], x: f64) {
arr[0] = x
}
fn foo(arr: [f64], x: str) {
arr[0] = x // ERROR: Expected `number`, found `string`
}
array
can optionally specify the inner type, such that the following is ok:
fn foo(arr: [], x: f64) {
arr[0] = x
}
Conversion rules:
[]
<=> [T]
opt
<=> opt[T]
res
<=> res[T]
This is missing from the language at the moment.
a && !b
)Dyon supports a short For loop for counters starting at 0 and incremented until it is greater or equal to a pre-evalutated expression:
for i len(list) { println(list[i]) }
This For loop is approximately 2.9x faster when running on AST than the equivalent traditional For loop:
n := len(list)
for i := 0; i < n; i += 1 { println(list[i]) }
The expression len(list)
can be inferred by the index list[i]
in the body:
for i { println(list[i]) }
When nesting loops this way, you have to order items in a list in the same order, for example:
for i, j, k {
println(list[i][j][k]) // i, j, k must be used in the same order
}
However, as long as you don't depend on the previous indices, you can write it any way you like:
sum i, j {
list[i] - list[j]
}
With index start and end:
for i [2, len(list)) { println(list[i]) }
When nesting loops of same kind, you can pack them together by separating the indices with ",":
for i, j, k {
println(list[i][j][k])
}
You can also write ranges in packed version, like for i n, j [i+1, n) { ... }
.
Computing output for a neural network:
fn run__tensor_input(tensor: [[[f64]]], input: [f64]) -> {
input := input
for i {
input = sift j {
sigmoid(∑ k {
tensor[i][j][k] * input[k]
})
}
}
return clone(input)
}
Compute energy for a system of N physical bodies:
fn energy(bodies: [{}]) -> f64 {
n := len(bodies)
return ∑ i n {
bodies[i].vel · bodies[i].vel * bodies[i].mass / 2.0 -
bodies[i].mass * ∑ j [i+1, n) {
bodies[j].mass / |bodies[i].pos - bodies[j].pos|
}
}
}
Set all weights in a neural network to random values:
fn randomize__tensor(mut tensor: [[[f64]]]) {
for i, j, k {
tensor[i][j][k] = random()
}
}
This is designed for:
This would allow working with native Rust types in the scripting language, for example images or a window. The objects can be shared across threads, and can be shared between a running script and a native Rust environment.
Variable::RustObject(Arc<Mutex<Any>>)
fn bar(b) -> {
return clone(b)
}
fn foo(a, b) {
a[0] = bar(b) // Function `foo` requires `b: 'a`
}
fn main() {
a := [0]
b := 3
foo(a, b)
}
Instead of providing full type checking, one could do a different kind of type checking that is closer to faking it entirely, using argument name substitution. This means that instead of types, one associates an argument name with a variable which is used to append _<type>
when calling a function, if there is no specified argument name. If there are no argument names and the function is not found, then it uses the short name.
Example:
c := [1; 4]
r := [0, 0, 100, 100]
draw(color: c, rectangle: r)
In Dyon, draw(color: c, rectangle: r)
is the same as draw_color_rectangle(c, r)
. With fake type checking, using argument name substitution, the above can be written as:
c: color = [1; 4]
r: rectangle = [0, 0, 100, 100]
draw(c, r)
A function can return a type:
fn blue() -> color {
return [0, 0, 1, 1]
}
fn center(x, y, rad) -> rectangle {
return [x - rad, y - rad, 2 * rad, 2 * rad]
}
c := blue() // Assigned the `color` type
r := center(100, 100, 5) // Assigned the `rectangle` type
draw(c, r)
Alternative:
draw(blue(), center(100, 100, 5))
A function can declare types for argument, but these are not used when calling the function. Therefore, to fake type checking both inside and outside, one must write the type of the argument twice:
fn add_f64_f64(a: f64, b: f64) -> f64 { return a + b }
a: f64 = 0
b: f64 = 2
c := add(a, b)
Objects can have optional fields and functions can return errors. Some syntax sugar could make this convenient to use, specially for propagating errors.
Variable::Option
None
some(x)
, none
, ok(x)
and err(x)
can be built-in functionsThis will insert a key in an object if it is not there, or overwrite if it exists.
Dyon uses a separate dynamic type for result values. The result type has two variants, err
and ok
. It has a lot of common with option value, but there are a few differences.
err(x)
creates an error with message x
, using deep cloneok(x)
creates a successful result with value x
, using deep cloneunwrap(x)
unwraps a result type, when error prints error message plus ?
trace messagesunwrap_err(x)
unwraps error messageis_err(x)
returns true
if result is an errorFor both result and option, the ?
operator propagates the error (requires ->
on function):
x?
, x.a?
, x[0]?.b.c?
etc.foo()?
or following after any expressionWhen a the value is none()
or err(x)
, the ?
operator propagates the error. Returns from the function. A trace message is added to help debugging on unwrap
, describing where the ?
operation happened. some(x)
is converted to ok(x)
.
This is designed for:
fn add_mul(x, y) -> {
return x + y * 3 // ERROR: Requires `x: 'return`
}
Currently, you have to type:
if (i + 1) < n {
}
The i + 1
expression is not allowed in the syntax as argument to comparison operator.
This could be part of the piston_window example. The purpose of this demo is to show how dynamic modules can be used to code on the fly. Currently the piston_window example does this in a hard coded way, but I would like to have this flexibility in Dyon itself.
$ rustc -O source/bench_rust/n_body.rs && time ./n_body
real 0m0.005s
user 0m0.001s
sys 0m0.002s
Edit source/test.rs:
fn main() {
m := load("source/bench/n_body.rs")
call(m, "main", [])
}
$ cargo build --release --example test && time ./target/release/examples/test
real 0m0.252s
user 0m0.247s
sys 0m0.005s
Currently, Rust is 50x faster than Dynamo.
In Rust you write like this:
mod foo; // looks for file "foo.rs" or "foo/mod.rs"
fn main() {
foo::hello_world(); // call function within module
}
I have an idea for a setup that might fit better for scripting. Modules are loaded using the same scripting language, then the functions becomes part of the prelude for a script that is loaded and checked at runtime.
foo.rs:
fn hello_world() {
println("hello world!")
}
loader.rs
fn main() {
foo := load(module: "foo.rs")
my_program := load(source: "my_program.rs", modules: [foo])
call(module: my_program, function: "main", arguments: [])
}
my_program.rs:
fn main() {
hello_world() // hello_world is part of prelude
}
The technique is to use "bootstrapping" as a way of organizing code.
When loading a module with load(source)
or load_source_imports(source, imports)
, it should return result such that one can handle the error.
In mathematics, you have the ∑ symbol that appears all over the place. It can be combined with a body with an arbitrary mathematical expression. Dyon uses this concept, making it easier to write programs in a more mathematical style.
The unicode symbol is be allowed in the syntax instead of the words "sum".
Example:
a := ∑ i len(list) { list[i] }
vs
sum := 0
for i len(list) {
sum += list[i]
}
It can be used within an expression:
n := len(list)
mean := ∑ i n { list[i] } / n
geometric_mean := exp(∑ i n { ln(list[i]) } / n)
You can use continue
and break
, just like a normal for
loop:
continue
skips the current indexbreak
skips the rest of the indicesOther loops:
1
)none()
)none()
)[]
, puts result of block into an array)bool
, starts with false
)bool
, starts with true
)All the loops described here can be composed, which means to nest them and return a value from the inner loop:
a := min i {
min j { ... }
}
This can be written as a packed loop:
a := min i, j { ... }
any
, all
, min
and max
loops, and their compositions, generate a secret. This is used to find out why or where a value was returned from the loop:
a := min i, j { ... }
println(where(a)) // prints `[i, j]` of minimum value.
For more information about secrets, see #266
Technically, this could be an intrinsic, but I like this sugar in Rust.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.