1
0

Break up tests; Write docs

This commit is contained in:
Jeff 2024-12-11 01:22:40 -05:00
parent cdd76618cb
commit 1c32cd0956
28 changed files with 656 additions and 113 deletions

314
Cargo.lock generated
View File

@ -79,12 +79,29 @@ dependencies = [
"critical-section",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -106,12 +123,29 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags 1.3.2",
"textwrap",
"unicode-width",
]
[[package]]
name = "clap"
version = "4.5.20"
@ -174,17 +208,99 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap 2.34.0",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "csv"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]]
name = "dust-cli"
version = "0.5.0"
dependencies = [
"clap",
"clap 4.5.20",
"colored",
"dust-lang",
"env_logger",
@ -198,18 +314,25 @@ name = "dust-lang"
version = "0.5.0"
dependencies = [
"annotate-snippets",
"bitflags",
"bitflags 2.6.0",
"colored",
"env_logger",
"criterion",
"getrandom",
"log",
"rand",
"serde",
"serde_json",
"slab",
"smallvec",
"smartstring",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_filter"
version = "0.1.2"
@ -246,6 +369,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "half"
version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403"
[[package]]
name = "hash32"
version = "0.2.1"
@ -275,6 +404,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "humantime"
version = "2.1.0"
@ -287,6 +425,15 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
@ -336,12 +483,55 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oorandom"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "postcard"
version = "1.0.10"
@ -410,6 +600,26 @@ dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex"
version = "1.11.1"
@ -454,6 +664,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -475,6 +694,16 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.214"
@ -498,6 +727,15 @@ dependencies = [
"serde",
]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]]
name = "smallvec"
version = "1.13.2"
@ -557,6 +795,25 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
@ -581,6 +838,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -642,6 +909,47 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "web-sys"
version = "0.3.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"

209
README.md
View File

@ -2,12 +2,86 @@
A programming language that is **fast**, **safe** and **easy to use**.
Dust's syntax, safety features and evaluation model are inspired by Rust. The instruction set,
optimization strategies and virtual machine are inspired by Lua and academic research in the field
(see the [Inspiration](README#Inspiration). Unlike Rust and most other compiled languages, Dust has
a very low time to execution. Unlike Lua and most other interpreted languages, Dust enforces static
typing during compilation, with a simple yet powerful type system that enhances clarity and prevents
bugs.
Dust is **statically typed** to ensure that each program is valid before it is run. It offers
compile times of less than 100 microseconds on modern hardware. As a **bytecode interpreter** with a
**register-based virtual machine**, Dust leverages compile-time safety guarantees and optimizations
along with beautiful syntax to deliver a unique set of features rarely found in other languages. It
is designed to deliver the best qualities of two disparate categories of programming language: the
highly optimized but slow-to-compile languages like Rust and C++ and the quick-to-start but often
slow and error-prone languages like Python and JavaScript.
Dust's syntax, safety features and evaluation model are based on Rust. Its instruction set,
optimization strategies and virtual machine are based on Lua. Unlike Rust and other languages that
compile to machine code, Dust has a very low time to execution. Unlike Lua and most other
interpreted languages, Dust enforces static typing to improve clarity and prevent bugs. While some
languages currently offer high-level features and strict typing (e.g. TypeScript), Dust has a simple
approach to syntax that offers flexibility and expressiveness while still being *obvious* an
audience of programmers, even those who don't know the language. Dust is for programmers who prefer
their code to be simple and clear rather than complex and clever.
## Goals
This project has lofty goals. In addition to being a wishlist, these goals should be used to provide
a framework for driving the project forward and making decisions about what to prioritize.
- **Fast Compilation**: Despite its compile-time abstractions, Dust should compile and start
executing quickly. The compilation time should feel negligible to the user.
- **Fast Execution**: Dust should be generally faster than Python, Ruby and NodeJS. It should be
competitive with other modern register-based VM languages like Lua and JavaScript Core.
- **Safety**: Static types should prevent runtime errors and improve code quality, offering a
superior development experience despite some additional constraints.
- **Approachability**: Dust should be easier to learn than Rust or C++. Its syntax should be
familiar to users of other C-like languages to the point that even a new user can read Dust code
and understand what it does.
- **Web Assembly Support**: The `dust` executable and, by extension, the `dust-lang` library, should
be able to able to compile to WebAssembly and Dust should be able to run in a browser with WASM
support. While running on the browser offers some fun opportunities, this is primarally a goal
because of WASM's potential to become a general-purpose cross-platform runtime.
- **Extended Type System**: Beyond specifying the types of variables and function arguments, Dust
should offer a rich yet simple type system that allows users to define their own types and compose
them with static guarantees about their identity and behavior.
- **Excellent Errors**: Dust should provide helpful error messages that guide the user to the source
of the problem and suggest a solution. Errors should be a helpful learning ressource for users
rather than a source of frustration.
- **High-Quality Documentation**: Dust's documentation should be easy to locate and understand.
Users should feel confident that the documentation is up-to-date and accurate.
- **All-In-One Binary**: The `dust` executable should aspire to be the only tool a user needs to run
Dust code, visualize Dust programs, compile them to intermediate representations, analyze runtime
behavior, run a REPL, format code and more as the scope of the project grows. Similar CLI tools
like Cargo and Bun have set a high standard for what a single executable can do.
- **Advanced Goals**: Dust could one day grow to the point that users will want to share their
libraries and distribute their programs. In the unlikely event that Dust becomes popular, it could
warrant an ecosystem consisting of package management with a central repository, a standard
library, a community of users and an organization to maintain the language. These are not within
the scope of the project at this time but it may be possible one day if the project is able to
realize its other goals. This is included here for maximum ambitiousness.
## Non-Goals
Some features are simply out of scope for Dust. As a project's design becomes an implementation,
decisions about what a project *will not* do are required to clarify the project's direction and
purpose for both the developers and the users.
- **Machine Code Compilation**: Dust is not intended to compete with Rust or C++ in terms of runtime
performance.
- **Complex Abstractions**: Dust will not introduce users to new, exotic syntax or convoluted
patterns that reduce the clarity of a program. Dust will not support complex paradigm-specific
abstractions like inheritance or currying. Dust will remain neither object-oriented nor
functional, preferring to expand its features without committing to a single paradigm.
- **Gradual Typing**: Dust's compiler handles the complexities of *static* typing and all value and
variable types are known before a program runs. The VM is and should remain type-agnostic, leaving
it to the sole responsibility of execution.
Dust uses a register-based VM with its own set of 32-bit instructions and a custom compiler to emit
the instructions. This should not be confused with a machine code compiler. Despite its compile-time
guarantees, Dust falls into the category of interpreted languages. Competing with the runtime
performance of Rust or C++ *is not* a goal. Competing with the approachability and simplicity of
those languages *is* a goal. On the other hand Dust *does* intend to be faster than Python, Ruby and
NodeJS while also offering a superior development experience and more reliable code due to its
static typing. Dust's development approach is informed by some [books][^1] and
[academic research][^4] as well as practical insight from [papers][^2] written by language authors.
See the [Inspiration](README#Inspiration) section for more information or keep reading to learn
about Dust's features.
```rust
write_line("Enter your name...")
@ -65,17 +139,24 @@ with a focus on stability and improvement.
### Syntax
Dust belongs to the C-like family of languages, with an imperative syntax that will be familiar to
many programmers. Dust code looks a lot like Ruby, JavaScript, TypeScript and other members of the
family but Rust is its primary point of reference for syntax. Rust was chosen as a syntax model
Dust belongs to the C-like family of languages[^5], with an imperative syntax that will be familiar
to many programmers. Dust code looks a lot like Ruby, JavaScript, TypeScript and other members of
the family but Rust is its primary point of reference for syntax. Rust was chosen as a syntax model
because its imperative code is *obvious* and *familiar*. Those qualities are aligned with Dust's
emphasis on safety and usability. However, some differences exist because Dust is a simpler language
that can tolerate more relaxed syntax. For example, Dust has more relaxed rules about semicolons:
they can be used to suppress values (like in Rust) but are not required at the end of every
statement.
that can tolerate more relaxed syntax. The most significant difference between Dust's syntax and
evaluation model and Rust's is the handling of semicolons.
In this example, these semicolons are optional. Because these `let` statements do not return a
value, the semicolons have nothing to suppress and are ignored.
There are two things you need to know about semicolons in Dust:
- Semicolons suppress the value of whatever they follow. The preceding statement or expression will
have the type `none` and will not evaluate to a value.
- If a semicolon does not change how the program runs, it is optional.
This example shows three statements with semicolons. The compiler knows that a `let` statement
cannot produce a value and will always have the type `none`. Thanks to static typing, it also knows
that the `write_line` function has no return value so the function call also has the type `none`.
Therefore, these semicolons are optional.
```rust
let a = 40;
@ -84,7 +165,7 @@ let b = 2;
write_line("The answer is ", a + b);
```
One could write the above program without any semicolons at all.
Removing the semicolons does not alter the execution pattern.
```rust
let x = 10
@ -95,34 +176,71 @@ write_line("The remainder is ", x % y)
The next example produces a compiler error because the `if` block returns a value of type `int` but
the `else` block does not return a value at all. Dust does not allow branches of the same `if/else`
statement to return different types of values. In this case, adding a semicolon after the `777`
expression fixes the error by supressing the value.
statement to have different types. In this case, adding a semicolon after the `777` expression fixes
the error by supressing the value.
```rust
// !!! Compile Error !!!
let input = read_line()
if input == "42" {
let reward = if input == "42" {
write_line("You got it! Here's your reward.")
777
} else {
write_line("That is not the answer.")
}
write_line(input, " is not the answer.")
};
```
Remember that even if some syntax is optional, that does not mean it should always be omitted or is
not useful. Aside from their practical use, semicolons provide a visual barrier between statements
written on the same line. Dust's design philosophy is to provide a balance between strictness and
expressiveness so that the language is applicable to a wide range of use cases. A web server with a
team of developers may prefer a more long-form style of code with lots of line breaks while a user
writing Dust on the command line may prefer a more terse style without sacrificing readability.
Understanding that semicolons suppress values is also important for understanding Dust's evaluation
model. Dust is composed of statements and expressions. If a statement ends in an expression without
a trailing semicolon, the statement evaluates to the value produced by that expression. However, if
the expression's value is suppressed with a semicolon, the statement does not evaluate to a value.
This is identical to Rust's evaluation model. That means that the following code will not compile:
```rust
let a = 0; let b = 1; let c = 2; let list = [a, b, c];
write_line("Here's our list: ", list)
// !!! Compile Error !!!
let a = { 40 + 2; }
```
The `a` variable is assigned to the value produced by a block. The block contains an expression that
is suppressed by a semicolon, so the block does not evaluate to a value. Therefore, the `a` variable
would have to be uninitialized (which Dust does not allow) or result in a runtime error (which Dust
avoids at all costs). We can fix this code by movinf the semicolon to the end of the block. In this
position it suppresses the value of the entire `let` statement. The above examples showed that a
`let` statement never evaluates to a value, so the semicolon has no effect on the program's behavior
and could be omitted altogether.
```rust
let a = { 40 + 2 }; // This is fine
```
Only the final expression in a block is returned. When a `let` statement is combined with an
`if/else` statement, the program can perform side effects before evaluating the value that will be
assigned to the variable.
```rust
let random: int = random(0..100)
let is_even = if random == 99 {
write_line("We got a 99!")
false
} else {
random % 2 == 0
}
is_even
```
If the above example were passed to Dust as a complete program, it would return a boolean value and
might print a message to the console (if the user is especially lucky). However, note that the
program could be modified to return no value by simply adding a semicolon at the end.
Compared to JavaScript, Dust's evaluation model is more predictable, less error-prone and will never
trap the user into a frustating hunt for a missing semicolon. Compared to Rust, Dust's evaluation
model is essentialy the same but with more relaxed rules about semicolons. In JavaScript, semicolons
are both *required* and *meaningless*, which is a source of confusion for many developers. In Rust,
they are *required* and *meaningful*, which provides excellent consistency but lacks flexibility.
### Safety
#### Type System
@ -143,6 +261,8 @@ Dust *does* have a `none` type, which should not be confused for being `null`-li
"unit" type in Rust, `none` exists as a type but not as a value. It indicates the lack of a value
from a function, expression or statement. A variable cannot be assigned to `none`.
#### Immutability by Default
#### Memory Safety
<!-- TODO: Introduce Dust's approach to memory management and garbage collection. -->
@ -172,7 +292,7 @@ reading them as a sequence of characters.
## Feature Progress
This list is a rough outline of the features that are planned to be implemented as soon as possible.
*This is not an exhaustive list of all planned features.* This list is updated and rearranged to
*This is **not** an exhaustive list of all planned features.* This list is updated and rearranged to
maintain a docket of what is being worked on, what is coming next and what can be revisited later.
- [X] Lexer
@ -180,7 +300,9 @@ maintain a docket of what is being worked on, what is coming next and what can b
- [X] VM
- [X] Disassembler (for chunk debugging)
- [ ] Formatter
- [ ] REPL
- [ ] CLI REPL
- [X] Compile dust's binary and library to WASM
- [ ] Browser-based REPL
- CLI
- [X] Run source
- [X] Compile source to a chunk and show disassembly
@ -248,13 +370,19 @@ maintain a docket of what is being worked on, what is coming next and what can b
- [X] `read_line`
- [X] `write`
- [X] `write_line`
- String Functions
- List Functions
- Map Functions
- Math Functions
- Filesystem Functions
- Network Functions
- System Functions
- Miniature Standard Library of Native Functions
- [ ] Byte Functions
- [ ] Character Functions
- [ ] Float Functions
- [ ] Integer Functions
- [ ] String Functions
- [ ] List Functions
- [ ] Map Functions
- [ ] Math Functions
- [ ] Filesystem Functions
- [ ] Network Functions
- [ ] System Functions
- [ ] Randomization Functions
## Implementation
@ -305,7 +433,7 @@ arguments are stored in its locals list. Before the function is run, the VM must
to values by filling locals' corresponding registers. Instead of copying the arguments, the VM uses
a pointer to one of the parent's registers or constants.
#### Optimizing
#### Instruction Optimization
When generating instructions for a register-based virtual machine, there are opportunities to
optimize the generated code by using fewer instructions or fewer registers. While it is best to
@ -481,3 +609,4 @@ Dust is licensed under the GNU General Public License v3.0. See the `LICENSE` fi
[^3]: [A No-Frills Introduction to Lua 5.1 VM Instructions](https://www.mcours.net/cours/pdf/hasclic3/hasssclic818.pdf)
[^4]: [A Performance Survey on Stack-based and Register-based Virtual Machines](https://arxiv.org/abs/1611.00467)
[^5]: [List of C-family programming languages](https://en.wikipedia.org/wiki/List_of_C-family_programming_languages)
[^6]: [ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}](https://blog.burntsushi.net/ripgrep/#mechanics)

9
build.sh Normal file
View File

@ -0,0 +1,9 @@
RUSTFLAGS="\
-C collapse-macro-debuginfo=false \
-C default-linker-libraries=false \
-C embed-bitcode=true \
-C force-frame-pointers=true \
-C force-unwind-tables=false \
-C passes=mem2reg \
-C linker-plugin-lto"
cargo build --release --package dust-cli

View File

@ -196,9 +196,9 @@ fn main() {
}
}
let chunk = compiler.finish(None, None);
let compile_end = start_time.elapsed();
let chunk = compiler.finish(None, None);
let mut vm = Vm::new(&source, &chunk, None);
match vm.run() {

View File

@ -23,6 +23,87 @@ smartstring = { version = "1.0.1", features = [
"serde",
], default-features = false }
bitflags = { version = "2.6.0", features = ["serde"] }
slab = "0.4.9"
[dev-dependencies]
env_logger = "0.11.5"
criterion = { version = "0.3.4", features = ["html_reports"] }
[[bench]]
name = "addictive_addition"
harness = false
[[test]]
name = "logic_and"
path = "tests/logic/and.rs"
[[test]]
name = "logic_and_and"
path = "tests/logic/and_and.rs"
[[test]]
name = "logic_or"
path = "tests/logic/or.rs"
[[test]]
name = "logic_variables"
path = "tests/logic/variables.rs"
[[test]]
name = "math_add"
path = "tests/math/add.rs"
[[test]]
name = "math_add_assign"
path = "tests/math/add_assign.rs"
[[test]]
name = "math_add_errors"
path = "tests/math/add_errors.rs"
[[test]]
name = "math_divide"
path = "tests/math/divide.rs"
[[test]]
name = "math_divide_assign"
path = "tests/math/divide_assign.rs"
[[test]]
name = "math_divide_erros"
path = "tests/math/divide_errors.rs"
[[test]]
name = "math_modulo"
path = "tests/math/modulo.rs"
[[test]]
name = "math_modulo_assign"
path = "tests/math/modulo_assign.rs"
[[test]]
name = "math_modulo_errors"
path = "tests/math/modulo_errors.rs"
[[test]]
name = "math_multiply"
path = "tests/math/multiply.rs"
[[test]]
name = "math_multiply_assign"
path = "tests/math/multiply_assign.rs"
[[test]]
name = "math_multiply_errors"
path = "tests/math/multiply_errors.rs"
[[test]]
name = "math_subtract"
path = "tests/math/subtract.rs"
[[test]]
name = "math_subtract_assign"
path = "tests/math/subtract_assign.rs"
[[test]]
name = "math_subtract_errors"
path = "tests/math/subtract_errors.rs"

View File

@ -0,0 +1,29 @@
use std::time::Duration;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dust_lang::run;
const SOURCE: &str = "
let mut i = 0
while i < 5_000_000 {
i += 1
}
";
fn addictive_addition(source: &str) {
let _ = run(source).unwrap();
}
fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("addictive_addition");
group.measurement_time(Duration::from_secs(15));
group.bench_function("addictive_addition", |b| {
b.iter(|| addictive_addition(black_box(SOURCE)))
});
group.finish();
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -655,7 +655,7 @@ impl Instruction {
if is_positive {
format!("JUMP +{offset}")
} else {
format!("JUMP -{offset}")
format!("JUMP -{}", offset - 1)
}
}
Operation::Call => {

View File

@ -4,7 +4,8 @@ use std::{
io,
};
use smallvec::{smallvec, SmallVec};
use slab::Slab;
use smallvec::SmallVec;
use crate::{
compile, instruction::*, AbstractValue, AnnotatedError, Argument, Chunk, ConcreteValue,
@ -23,7 +24,7 @@ pub fn run(source: &str) -> Result<Option<ConcreteValue>, DustError> {
/// See the [module-level documentation](index.html) for more information.
#[derive(Debug)]
pub struct Vm<'a> {
stack: SmallVec<[Register; 64]>,
stack: Slab<Register>,
chunk: &'a Chunk,
parent: Option<&'a Vm<'a>>,
@ -35,9 +36,15 @@ pub struct Vm<'a> {
impl<'a> Vm<'a> {
pub fn new(source: &'a str, chunk: &'a Chunk, parent: Option<&'a Vm<'a>>) -> Self {
let mut stack = Slab::with_capacity(chunk.stack_size());
for _ in 0..chunk.stack_size() {
stack.insert(Register::Empty);
}
Self {
chunk,
stack: smallvec![Register::Empty; chunk.stack_size()],
stack,
parent,
ip: 0,
last_assigned_register: None,
@ -511,10 +518,10 @@ impl<'a> Vm<'a> {
} = CallNative::from(&instruction);
let first_argument_index = (destination - argument_count) as usize;
let argument_range = first_argument_index..destination as usize;
let argument_registers = &self.stack[argument_range];
let mut arguments: SmallVec<[ValueRef; 4]> = SmallVec::new();
for register in argument_registers {
for register_index in argument_range {
let register = &self.stack[register_index];
let value = match register {
Register::Value(value) => value.to_ref(),
Register::Pointer(pointer) => {
@ -639,23 +646,20 @@ impl<'a> Vm<'a> {
fn open_register(&self, register_index: u8) -> Result<ValueRef, VmError> {
let register_index = register_index as usize;
assert!(
register_index < self.stack.len(),
"VM Error: Register index out of bounds"
);
if register_index < self.stack.len() {
let register = &self.stack[register_index];
let register = &self.stack[register_index];
log::trace!("Open R{register_index} to {register}");
match register {
Register::Value(value) => Ok(value.to_ref()),
Register::Pointer(pointer) => self.follow_pointer(*pointer),
Register::Empty => Err(VmError::EmptyRegister {
index: register_index,
position: self.current_position(),
}),
return match register {
Register::Value(value) => Ok(value.to_ref()),
Register::Pointer(pointer) => self.follow_pointer(*pointer),
Register::Empty => Err(VmError::EmptyRegister {
index: register_index,
position: self.current_position(),
}),
};
}
panic!("VM Error: Register index out of bounds");
}
fn open_register_allow_empty(&self, register_index: u8) -> Result<Option<ValueRef>, VmError> {
@ -688,12 +692,11 @@ impl<'a> Vm<'a> {
}
);
let new_ip = if is_positive {
self.ip + offset
if is_positive {
self.ip += offset
} else {
self.ip - offset - 1
};
self.ip = new_ip;
self.ip -= offset + 1
}
}
/// DRY helper to get a value from an Argument
@ -708,71 +711,55 @@ impl<'a> Vm<'a> {
Ok(value_ref)
}
#[inline(always)]
fn set_register(&mut self, to_register: u8, register: Register) -> Result<(), VmError> {
self.last_assigned_register = Some(to_register);
let to_register = to_register as usize;
assert!(
self.stack.len() > to_register,
"VM Error: Register index out of bounds"
);
if to_register < self.stack.len() {
self.stack[to_register] = register;
self.stack[to_register] = register;
return Ok(());
}
Ok(())
panic!("VM Error: Register index out of bounds");
}
#[inline(always)]
fn get_constant(&self, constant_index: u8) -> &ConcreteValue {
let constant_index = constant_index as usize;
let constants = self.chunk.constants();
let constants = self.chunk.constants().as_slice();
assert!(
constant_index < constants.len(),
"VM Error: Constant index out of bounds"
);
if constant_index < constants.len() {
return &constants[constant_index];
}
&constants[constant_index]
panic!("VM Error: Constant index out of bounds");
}
#[inline(always)]
fn get_local_register(&self, local_index: u8) -> Result<u8, VmError> {
let local_index = local_index as usize;
let locals = self.chunk.locals();
let locals = self.chunk.locals().as_slice();
assert!(
local_index < locals.len(),
"VM Error: Local index out of bounds"
);
if local_index < locals.len() {
let register_index = locals[local_index].register_index;
let register_index = locals[local_index].register_index;
return Ok(register_index);
}
Ok(register_index)
panic!("VM Error: Local index out of bounds");
}
#[inline(always)]
fn read(&mut self) -> Instruction {
let instructions = self.chunk.instructions();
let instructions = self.chunk.instructions().as_slice();
assert!(
self.ip < instructions.len(),
"{}",
DustError::runtime(
VmError::InstructionIndexOutOfBounds {
index: self.ip,
position: self.current_position()
},
self.source,
)
);
if self.ip < instructions.len() {
let (instruction, _) = instructions[self.ip];
let (instruction, _) = instructions[self.ip];
self.ip += 1;
self.ip += 1;
return instruction;
}
instruction
panic!("VM Error: Instruction pointer out of bounds");
}
}

View File

@ -1,12 +1,12 @@
write_line("Guess the number.")
let secret_number = random(0..100);
let secret_number = random(0..100)
loop {
write_line("Input your guess.")
let input = io.read_line();
let guess = int.parse(input);
let input = io.read_line()
let guess = int.parse(input)
if guess < secret_number {
io.write_line("Too low!")