Write docs
This commit is contained in:
parent
cee9f0d95c
commit
a34a2c2db4
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -190,6 +190,27 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-print"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3aa954171903797d5623e047d9ab69d91b493657917bdfb8c2c80ecaf9cdb6f4"
|
||||||
|
dependencies = [
|
||||||
|
"color-print-proc-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-print-proc-macro"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692186b5ebe54007e45a59aea47ece9eb4108e141326c304cdc91699a7118a22"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@ -299,7 +320,7 @@ name = "dust-cli"
|
|||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 4.5.20",
|
"clap 4.5.20",
|
||||||
"colored",
|
"color-print",
|
||||||
"dust-lang",
|
"dust-lang",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
@ -495,6 +516,22 @@ version = "2.7.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
|
514
README.md
514
README.md
@ -15,9 +15,15 @@ optimization strategies and virtual machine are based on Lua. Unlike Rust and ot
|
|||||||
compile to machine code, Dust has a very low time to execution. Unlike Lua and most other
|
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
|
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
|
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
|
approach to syntax that offers flexibility and expressiveness while still being *obvious*, even
|
||||||
audience of programmers, even those who don't know the language. Dust is for programmers who prefer
|
those who know how to code but don't know the language. Dust is developed with an emphasis on
|
||||||
their code to be simple and clear rather than complex and clever.
|
achieving foundational soundness before adding new features. Dust's planned features and design
|
||||||
|
favor programmers who prefer their code to be simple and clear rather than clever and complex.
|
||||||
|
|
||||||
|
**Dust is under active development and is not yet ready for general use.**
|
||||||
|
|
||||||
|
**Features discussed in this README may be unimplemented, partially implemented or temporarily
|
||||||
|
removed**
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
write_line("Enter your name...")
|
write_line("Enter your name...")
|
||||||
@ -29,8 +35,11 @@ write_line("Hello " + name + "!")
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn fib (n: int) -> int {
|
fn fib (n: int) -> int {
|
||||||
if n <= 0 { return 0 }
|
if n <= 0 {
|
||||||
if n == 1 { return 1 }
|
return 0
|
||||||
|
} else if n == 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fib(n - 1) + fib(n - 2)
|
fib(n - 1) + fib(n - 2)
|
||||||
}
|
}
|
||||||
@ -38,80 +47,98 @@ fn fib (n: int) -> int {
|
|||||||
write_line(fib(25))
|
write_line(fib(25))
|
||||||
```
|
```
|
||||||
|
|
||||||
Dust uses a register-based VM with its own set of 32-bit instructions and a custom compiler to emit
|
Dust uses a custom register-based virtual machine with its own set of instructions and a compiler
|
||||||
the instructions. This should not be confused with a machine code compiler. Despite its compile-time
|
based on recursive descet to emit them. This should not be confused with a machine code compiler.
|
||||||
guarantees, Dust falls into the category of interpreted languages. Competing with the runtime
|
Despite having **compile-time guarantees**, Dust falls into the category of interpreted languages.
|
||||||
performance of Rust or C++ *is not* a goal. Competing with the approachability and simplicity of
|
Competing with the runtime performance of Rust or C++ *is not* a goal. Competing with the
|
||||||
those languages *is* a goal. On the other hand Dust *does* intend to be faster than Python, Ruby and
|
approachability and simplicity of those languages *is* a goal. On the other hand Dust *does* intend
|
||||||
NodeJS while also offering a superior development experience and more reliable code due to its
|
to be faster than Python, Ruby and NodeJS while also offering a superior development experience and
|
||||||
static typing. Dust's development approach is informed by some books[^1] and
|
more reliable code due to its static typing. Dust's development approach is informed by some
|
||||||
academic research[^4] as well as practical insight from papers[^2] written by language authors.
|
books[^1] and academic research[^4] as well as practical insight from papers[^2] written by language
|
||||||
See the [Inspiration](README#Inspiration) section for more information or keep reading to learn
|
authors. See the [Inspiration](README#Inspiration) section for more information or keep reading to
|
||||||
about Dust's features.
|
learn about Dust's features.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
This project has lofty goals. In addition to being a wishlist, these goals should be used to provide
|
This project's goal is to deliver a language that not only *works* but that offers genunine value
|
||||||
a framework for driving the project forward and making decisions about what to prioritize.
|
due to a unique combination of design choices and a high-quality implementation. As mentioned in the
|
||||||
|
first sentence, Dust's general aspirations are to be **fast**, **safe** and **easy**.
|
||||||
|
|
||||||
- **Fast Compilation**: Despite its compile-time abstractions, Dust should compile and start
|
- **Easy**
|
||||||
|
- **Simple Syntax** Dust should be easier to learn than most programming languages. 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. Rather than being dumbed down by a lack of features, Dust
|
||||||
|
should be powerful and elegant in its simplicity, seeking a maximum of capability with a minimum
|
||||||
|
of complexity. When advanced features are added, they should never obstruct existing features,
|
||||||
|
including readability. Even the advanced type system should be clear and unintimidating.
|
||||||
|
- **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.
|
||||||
|
- **Relevant Documentation** Users should have the resources they need to learn Dust and write
|
||||||
|
code in it. They should know where to look for answers and how to reach out for help.
|
||||||
|
- **Safe**
|
||||||
|
- **Static Types** Typing should prevent runtime errors and improve code quality, offering a
|
||||||
|
superior development experience despite some additional constraints. Like any good statically
|
||||||
|
typed language, users should feel confident in the type-consistency of their code and not want
|
||||||
|
to go back to a dynamically typed language.
|
||||||
|
- **Memory Safety** Dust should be free of memory bugs. Being implemented in Rust makes this easy
|
||||||
|
but, to accomodate long-running programs, Dust still requires a memory management strategy.
|
||||||
|
Dust's design is to use a separate thread for garbage collection, allowing the main thread to
|
||||||
|
continue executing code while the garbage collector looks for unused memory.
|
||||||
|
- **Fast**
|
||||||
|
- **Fast Compilation** Despite its compile-time abstractions, Dust should compile and start
|
||||||
executing quickly. The compilation time should feel negligible to the user.
|
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
|
- **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.
|
competitive with highly optimized, modern, register-based VM languages like Lua. Dust should
|
||||||
- **Safety**: Static types should prevent runtime errors and improve code quality, offering a
|
be benchmarked during development to inform decisions about performance.
|
||||||
superior development experience despite some additional constraints.
|
- **Low Resource Usage** Despite its performance, Dust's use of memory and CPU power should be
|
||||||
- **Approachability**: Dust should be easier to learn than Rust or C++. Its syntax should be
|
conservative and predictable enough to accomodate a wide range of devices.
|
||||||
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
|
These are the project's general design goals. There are many more implementation goals. Among them
|
||||||
|
are:
|
||||||
|
|
||||||
Some features are simply out of scope for Dust. As a project's design becomes an implementation,
|
- Effortless Concurrency: Dust should offer an excellent experience for writing multi-threaded
|
||||||
decisions about what a project *will not* do are required to clarify the project's direction and
|
programs. The language's native functions should offer an API for spawning threads, sending
|
||||||
purpose for both the developers and the users.
|
messages and waiting for results. When using these features, Dust should be much faster than any
|
||||||
|
single-threaded language. However, Dust should be fast even when running on a single thread.
|
||||||
- **Machine Code Compilation**: Dust is not intended to compete with Rust or C++ in terms of runtime
|
Single-threaded performce is the best predictor of multi-threaded performance so continuing to
|
||||||
performance.
|
optimize how each thread executes instructions, accesses memory and moves pointers is the best
|
||||||
- **Complex Abstractions**: Dust will not introduce users to new, exotic syntax or convoluted
|
way to ensure that Dust is fast in all scenarios.
|
||||||
patterns that reduce the clarity of a program. Dust will not support complex paradigm-specific
|
- Embeddability: The library should be easy to use so that Dust can be built into other
|
||||||
abstractions like inheritance or currying. Dust will remain neither object-oriented nor
|
applications. Dust should compile to WebAssembly and offer examples of how to use it in a web
|
||||||
functional, preferring to expand its features without committing to a single paradigm.
|
application. The user should be able to query the VM for information about the program's state
|
||||||
- **Gradual Typing**: Dust's compiler handles the complexities of *static* typing and all value and
|
and control the program's execution. It should be possible to view and modify the value of a
|
||||||
variable types are known before a program runs. The VM is and should remain type-agnostic, leaving
|
variable and inspect the call stack.
|
||||||
it to the sole responsibility of execution.
|
- Data Fluency: Dust's value type should support conversion to and from arbitrary data in formats
|
||||||
|
like JSON, YAML, TOML and CSV. Pulling data into a Dust program should be easy, with built-in
|
||||||
|
functions offering conversion for the most widely used formats.
|
||||||
|
- Portability: Dust should run on as many architectures and operating systems as possible. Using
|
||||||
|
fewer dependencies and avoiding platform-specific code will help Dust achieve this goal. The
|
||||||
|
Dust library should be available as a WebAssembly module.
|
||||||
|
- Developer Experience: Dust should be fun and easy to use. That implies easy installation and the
|
||||||
|
availability of tutorials and how-to guides. The CLI should be predictable and feature-rich,
|
||||||
|
with features that make it easy to write and debug Dust code like formatting, bytecode
|
||||||
|
disassembly and logging.
|
||||||
|
- Advanced Type System: Dust should implement composite types, aliases and generics. The type
|
||||||
|
system should use a descriptive syntax that is easy to understand. Dust's type system should be
|
||||||
|
static, meaning that types are checked before a program reaches the VM. Dust is not a
|
||||||
|
graduallly typed language, its VM is and should remain type-agnostic.
|
||||||
|
- Thorough Testing: Primarily, the output of Dust's compiler and VM should be tested with programs
|
||||||
|
that cover all of the language's features. The tests should be actively maintained and should be
|
||||||
|
changed frequently to reflect a growing project that is constantly discovering new optimizations
|
||||||
|
and opportunities for improvement.
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
**Dust is under active development and is not yet ready for general use.**
|
This project is maintained by a single developer. For now, its primary home is on a private git
|
||||||
|
server. The GitHub mirror is updated automatically and should carry the latest branches. There are
|
||||||
|
no other contributors at this time but the project is open to feedback and should eventually accept
|
||||||
|
contributions.
|
||||||
|
|
||||||
**Features discussed in this README may be unimplemented, partially implemented, temporarily removed
|
For now, both the library API and the implementation details are freely changed and the CLI has not
|
||||||
or only available on a seperate branch.**
|
been published. Dust is both an ambitious project and a continuous experiment in language design.
|
||||||
|
Features may be redesigned and reimplemented at will when they do not meet the project's performance
|
||||||
Dust is an ambitious project that acts as a continuous experiment in language design. Features may
|
or usability goals. This approach maximizes the development experience as a learning opportunity and
|
||||||
be redesigned and reimplemented at will when they do not meet the project's performance or
|
|
||||||
usability goals. This approach maximizes the development experience as a learning opportunity and
|
|
||||||
enforces a high standard of quality but slows down the process of delivering features to users.
|
enforces a high standard of quality but slows down the process of delivering features to users.
|
||||||
Eventually, Dust will reach a stable release and will be ready for general use. As the project
|
Eventually, Dust will reach a stable release and will be ready for general use. As the project
|
||||||
approaches this milestone, the experimental nature of the project will be reduced and a replaced
|
approaches this milestone, the experimental nature of the project will be reduced and a replaced
|
||||||
@ -119,15 +146,33 @@ with a focus on stability and improvement.
|
|||||||
|
|
||||||
## Language Overview
|
## Language Overview
|
||||||
|
|
||||||
### Syntax
|
This is a quick overview of Dust's syntax features. It skips over the aspects that are familiar to
|
||||||
|
most programmers such as creating variables, using binary operators and printing to the console.
|
||||||
|
Eventually there should be a complete reference for the syntax.
|
||||||
|
|
||||||
|
### Syntax and Evaluation
|
||||||
|
|
||||||
Dust belongs to the C-like family of languages[^5], with an imperative syntax that will be familiar
|
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
|
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
|
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
|
because its imperative code is *obvious by design* and *widely familiar*. Those qualities are
|
||||||
emphasis on safety and usability. However, some differences exist because Dust is a simpler language
|
aligned with Dust's emphasis on usability.
|
||||||
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.
|
However, some differences exist. Dust *evaluates* all of the code in the file while Rust only
|
||||||
|
initiates from a "main" function. Dust's execution model is more like one found in a scripting
|
||||||
|
language. If we put `42 + 42 == 84` into a file and run it, it will return `true` because the outer
|
||||||
|
context is, in a sense, the "main" function.
|
||||||
|
|
||||||
|
So while the syntax is by no means compatible, it is superficially similar, even to the point that
|
||||||
|
syntax highlighting for Rust code works well with Dust code. This is not a design goal but a happy
|
||||||
|
coincidence.
|
||||||
|
|
||||||
|
### Semicolons
|
||||||
|
|
||||||
|
Dust borrowed Rust's approach to semicolons and their effect on evaluation and relaxed the rules to
|
||||||
|
accomated different styles of coding. Rust, for example, isn't design for command lines or REPLs but
|
||||||
|
Dust could be well-suited to those applications. Dust needs to work in a source file or in an ad-hoc
|
||||||
|
one-liner sent to the CLI. Thus, semicolons are optional in most cases.
|
||||||
|
|
||||||
There are two things you need to know about semicolons in Dust:
|
There are two things you need to know about semicolons in Dust:
|
||||||
|
|
||||||
@ -147,7 +192,7 @@ let b = 2;
|
|||||||
write_line("The answer is ", a + b);
|
write_line("The answer is ", a + b);
|
||||||
```
|
```
|
||||||
|
|
||||||
Removing the semicolons does not alter the execution pattern.
|
Removing the semicolons does not alter the execution pattern or the return value.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let x = 10
|
let x = 10
|
||||||
@ -167,15 +212,16 @@ let input = read_line()
|
|||||||
let reward = if input == "42" {
|
let reward = if input == "42" {
|
||||||
write_line("You got it! Here's your reward.")
|
write_line("You got it! Here's your reward.")
|
||||||
|
|
||||||
777
|
777 // <- We need a semicolon here
|
||||||
} else {
|
} else {
|
||||||
write_line(input, " is not the answer.")
|
write_line(input, " is not the answer.")
|
||||||
};
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Understanding that semicolons suppress values is also important for understanding Dust's evaluation
|
### Statements and Expressions
|
||||||
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
|
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.
|
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:
|
This is identical to Rust's evaluation model. That means that the following code will not compile:
|
||||||
|
|
||||||
@ -187,13 +233,14 @@ let a = { 40 + 2; }
|
|||||||
The `a` variable is assigned to the value produced by a block. The block contains an expression that
|
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
|
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
|
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
|
avoids at all costs). We can fix this code by moving 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
|
position it suppresses the value of the entire `let` statement. As we saw above, a `let` statement
|
||||||
`let` statement never evaluates to a value, so the semicolon has no effect on the program's behavior
|
never evaluates to a value, so the semicolon has no effect on the program's behavior and could be
|
||||||
and could be omitted altogether.
|
omitted altogether.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let a = { 40 + 2 }; // This is fine
|
let a = { 40 + 2 }; // This is fine
|
||||||
|
let a = { 40 + 2 } // This is also fine
|
||||||
```
|
```
|
||||||
|
|
||||||
Only the final expression in a block is returned. When a `let` statement is combined with an
|
Only the final expression in a block is returned. When a `let` statement is combined with an
|
||||||
@ -219,11 +266,17 @@ program could be modified to return no value by simply adding a semicolon at the
|
|||||||
|
|
||||||
Compared to JavaScript, Dust's evaluation model is more predictable, less error-prone and will never
|
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
|
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
|
model is more accomodating without sacrificing expressiveness. In Rust, semicolons are *required*
|
||||||
are both *required* and *meaningless*, which is a source of confusion for many developers. In Rust,
|
and *meaningful*, which provides excellent consistency but lacks flexibility. In JavaScript,
|
||||||
they are *required* and *meaningful*, which provides excellent consistency but lacks flexibility.
|
semicolons are *required* and *meaningless*, which is a source of confusion for many developers.
|
||||||
|
|
||||||
### Safety
|
### Control Flow
|
||||||
|
|
||||||
|
-- TODO --
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
-- TODO --
|
||||||
|
|
||||||
#### Type System
|
#### Type System
|
||||||
|
|
||||||
@ -245,11 +298,13 @@ from a function, expression or statement. A variable cannot be assigned to `none
|
|||||||
|
|
||||||
#### Immutability by Default
|
#### Immutability by Default
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
#### Memory Safety
|
#### Memory Safety
|
||||||
|
|
||||||
<!-- TODO: Introduce Dust's approach to memory management and garbage collection. -->
|
TODO
|
||||||
|
|
||||||
### Values, Variables and Types
|
### Basic Values
|
||||||
|
|
||||||
Dust supports the following basic values:
|
Dust supports the following basic values:
|
||||||
|
|
||||||
@ -269,288 +324,19 @@ singular values. Shorter strings are stored on the stack while longer strings ar
|
|||||||
Dust offers built-in native functions that can manipulate strings by accessing their bytes or
|
Dust offers built-in native functions that can manipulate strings by accessing their bytes or
|
||||||
reading them as a sequence of characters.
|
reading them as a sequence of characters.
|
||||||
|
|
||||||
<!-- TODO: Describe Dust's composite values -->
|
### Composite Values
|
||||||
|
|
||||||
## Feature Progress
|
TODO
|
||||||
|
|
||||||
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
|
|
||||||
maintain a docket of what is being worked on, what is coming next and what can be revisited later.
|
|
||||||
|
|
||||||
- [X] Lexer
|
|
||||||
- [X] Compiler
|
|
||||||
- [X] VM
|
|
||||||
- [X] Disassembler (for chunk debugging)
|
|
||||||
- [ ] Formatter
|
|
||||||
- [ ] 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
|
|
||||||
- [X] Tokenize using the lexer and show token list
|
|
||||||
- [ ] Format using a built-in formatter
|
|
||||||
- [ ] Compile to and run from intermediate formats
|
|
||||||
- [ ] JSON
|
|
||||||
- [ ] Postcard
|
|
||||||
- [ ] Integrated REPL
|
|
||||||
- Basic Values
|
|
||||||
- [X] No `null` or `undefined` values
|
|
||||||
- [X] Booleans
|
|
||||||
- [X] Bytes (unsigned 8-bit)
|
|
||||||
- [X] Characters (Unicode scalar value)
|
|
||||||
- [X] Floats (64-bit)
|
|
||||||
- [X] Functions
|
|
||||||
- [X] Integers (signed 64-bit)
|
|
||||||
- [X] Strings (UTF-8)
|
|
||||||
- Composite Values
|
|
||||||
- [X] Concrete lists
|
|
||||||
- [X] Abstract lists (optimization)
|
|
||||||
- [ ] Concrete maps
|
|
||||||
- [ ] Abstract maps (optimization)
|
|
||||||
- [ ] Ranges
|
|
||||||
- [ ] Tuples (fixed-size constant lists)
|
|
||||||
- [ ] Structs
|
|
||||||
- [ ] Enums
|
|
||||||
- Types
|
|
||||||
- [X] Basic types for each kind of basic value
|
|
||||||
- [X] Generalized types: `num`, `any`, `none`
|
|
||||||
- [ ] Type conversion (safe, explicit and coercion-free)
|
|
||||||
- [ ] `struct` types
|
|
||||||
- [ ] `enum` types
|
|
||||||
- [ ] Type aliases
|
|
||||||
- [ ] Type arguments
|
|
||||||
- [ ] Compile-time type checking
|
|
||||||
- [ ] Function returns
|
|
||||||
- [X] If/Else branches
|
|
||||||
- [ ] Instruction arguments
|
|
||||||
- Variables
|
|
||||||
- [X] Immutable by default
|
|
||||||
- [X] Block scope
|
|
||||||
- [X] Statically typed
|
|
||||||
- [X] Copy-free identifiers are stored in the chunk as string constants
|
|
||||||
- Functions
|
|
||||||
- [X] First-class value
|
|
||||||
- [X] Statically typed arguments and returns
|
|
||||||
- [X] Pure (no "closure" of local variables, arguments are the only input)
|
|
||||||
- [ ] Type arguments
|
|
||||||
- Control Flow
|
|
||||||
- [X] If/Else
|
|
||||||
- [ ] Match
|
|
||||||
- [ ] Loops
|
|
||||||
- [ ] `for`
|
|
||||||
- [ ] `loop`
|
|
||||||
- [X] `while`
|
|
||||||
- Native Functions
|
|
||||||
- Assertions
|
|
||||||
- [X] `assert`
|
|
||||||
- [ ] `assert_eq`
|
|
||||||
- [ ] `assert_ne`
|
|
||||||
- [ ] `panic`
|
|
||||||
- I/O
|
|
||||||
- [ ] `read`
|
|
||||||
- [X] `read_line`
|
|
||||||
- [X] `write`
|
|
||||||
- [X] `write_line`
|
|
||||||
- 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
|
|
||||||
|
|
||||||
Dust is implemented in Rust and is divided into several parts, most importantly the lexer, compiler,
|
|
||||||
and virtual machine. All of Dust's components are designed with performance in mind and the codebase
|
|
||||||
uses as few dependencies as possible. The code is tested by integration tests that compile source
|
|
||||||
code and check the compiled chunk, then run the source and check the output of the virtual machine.
|
|
||||||
It is important to maintain a high level of quality by writing meaningful tests and preferring to
|
|
||||||
compile and run programs in an optimal way before adding new features.
|
|
||||||
|
|
||||||
### Command Line Interface
|
|
||||||
|
|
||||||
Dust's command line interface and developer experience are inspired by tools like Bun and especially
|
|
||||||
Cargo, the Rust package manager that includes everything from project creation to documentation
|
|
||||||
generation to code formatting to much more. Dust's CLI has started by exposing the most imporant
|
|
||||||
features for debugging and developing the language itself. Tokenization, compiling, disassembling
|
|
||||||
and running Dust code are currently supported. The CLI will eventually support a REPL, code
|
|
||||||
formatting, linting and other features that enhance the development experience and make Dust more
|
|
||||||
fun and easy to use.
|
|
||||||
|
|
||||||
### Lexer and Tokens
|
|
||||||
|
|
||||||
The lexer emits tokens from the source code. Dust makes extensive use of Rust's zero-copy
|
|
||||||
capabilities to avoid unnecessary allocations when creating tokens. A token, depending on its type,
|
|
||||||
may contain a reference to some data from the source code. The data is only copied in the case of an
|
|
||||||
error. In a successfully executed program, no part of the source code is copied unless it is a
|
|
||||||
string literal or identifier.
|
|
||||||
|
|
||||||
### Compiler
|
|
||||||
|
|
||||||
The compiler creates a chunk, which contains all of the data needed by the virtual machine to run a
|
|
||||||
Dust program. It does so by emitting bytecode instructions, constants and locals while parsing the
|
|
||||||
tokens, which are generated one at a time by the lexer.
|
|
||||||
|
|
||||||
#### Parsing
|
|
||||||
|
|
||||||
Dust's compiler uses a custom Pratt parser, a kind of recursive descent parser, to translate a
|
|
||||||
sequence of tokens into a chunk. Each token is given a precedence and may have a prefix and/or infix
|
|
||||||
parser. The parsers are just functions that modify the compiler and its output. For example, when
|
|
||||||
the compiler encounters a boolean token, its prefix parser is the `parse_boolean` function, which
|
|
||||||
emits a `LoadBoolean` instruction. An integer token's prefix parser is `parse_integer`, which emits
|
|
||||||
a `LoadConstant` instruction and adds the integer to the constants list. Tokens with infix parsers
|
|
||||||
include the math operators, which emit `Add`, `Subtract`, `Multiply`, `Divide`, `Modulo` and `Power`
|
|
||||||
instructions.
|
|
||||||
|
|
||||||
Functions are compiled into their own chunks, which are stored in the constant list. A function's
|
|
||||||
arguments are stored in its locals list. Before the function is run, the VM must bind the arguments
|
|
||||||
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.
|
|
||||||
|
|
||||||
#### 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
|
|
||||||
output optimal code in the first place, it is not always possible. Dust's uses a single-pass
|
|
||||||
compiler and therefore applies optimizations immeadiately after the opportunity becomes available.
|
|
||||||
There is no separate optimization pass and the compiler cannot be run in a mode that disables
|
|
||||||
optimizations.
|
|
||||||
|
|
||||||
#### Type Checking
|
|
||||||
|
|
||||||
Dust's compiler associates each emitted instruction with a type. This allows the compiler to enforce
|
|
||||||
compatibility when values are used in expressions. For example, the compiler will not allow a string
|
|
||||||
to be added to an integer, but it will allow either to be added to another of the same type. Aside
|
|
||||||
from instruction arguments, the compiler also checks the types of function arguments and the blocks
|
|
||||||
of `if`/`else` statements.
|
|
||||||
|
|
||||||
The compiler always checks types on the fly, so there is no need for a separate type-checking pass.
|
|
||||||
Type information is removed from the instructions list before the chunk is created, so the VM (which
|
|
||||||
is entirely type-agnostic) never sees it.
|
|
||||||
|
|
||||||
### Instructions
|
|
||||||
|
|
||||||
Dust's virtual machine uses 32-bit instructions, which encode seven pieces of information:
|
|
||||||
|
|
||||||
Bit | Description
|
|
||||||
----- | -----------
|
|
||||||
0-4 | Operation code
|
|
||||||
5 | Flag indicating if the B field is a constant
|
|
||||||
6 | Flag indicating if the C field is a constant
|
|
||||||
7 | D field (boolean)
|
|
||||||
8-15 | A field (unsigned 8-bit integer)
|
|
||||||
16-23 | B field (unsigned 8-bit integer)
|
|
||||||
24-31 | C field (unsigned 8-bit integer)
|
|
||||||
|
|
||||||
#### Operations
|
|
||||||
|
|
||||||
The 1.0 version of Dust will have more than the current number of operations but cannot exceed 32
|
|
||||||
because of the 5 bit format.
|
|
||||||
|
|
||||||
##### Stack manipulation
|
|
||||||
|
|
||||||
- MOVE: Makes a register's value available in another register by using a pointer. This avoids
|
|
||||||
copying the value or invalidating the original register.
|
|
||||||
- CLOSE: Sets a range of registers to the "empty" state.
|
|
||||||
|
|
||||||
##### Value loaders
|
|
||||||
|
|
||||||
- LOAD_BOOLEAN: Loads a boolean to a register. Booleans known at compile-time are not stored in the
|
|
||||||
constant list. Instead, they are encoded in the instruction itself.
|
|
||||||
- LOAD_CONSTANT: Loads a constant from the constant list to a register. The VM avoids copying the
|
|
||||||
constant by using a pointer with the constant's index.
|
|
||||||
- LOAD_LIST: Creates a list abstraction from a range of registers and loads it to a register.
|
|
||||||
- LOAD_MAP: Creates a map abstraction from a range of registers and loads it to a register.
|
|
||||||
- LOAD_SELF: Creates an abstraction that represents the current function and loads it to a register.
|
|
||||||
|
|
||||||
##### Variable operations
|
|
||||||
|
|
||||||
- GET_LOCAL: Loads a variable's value to a register by using a pointer to point to the variable's
|
|
||||||
canonical register (i.e. the register whose index is stored in the locals list).
|
|
||||||
- SET_LOCAL: Changes a variable's register to a pointer to another register, effectively changing
|
|
||||||
the variable's value.
|
|
||||||
|
|
||||||
##### Arithmetic
|
|
||||||
|
|
||||||
Arithmetic instructions use the A, B and C fields. The A field is the destination register, the B
|
|
||||||
and C fields are the arguments, and the flags indicate whether the arguments are constants.
|
|
||||||
|
|
||||||
- ADD: Adds two values and stores the result in a register. Unlike the other arithmetic operations,
|
|
||||||
the ADD instruction can also be used to concatenate strings and/or characters. Characters are the
|
|
||||||
only type of value that can perform a kind of implicit conversion. Although the character itself
|
|
||||||
is not converted, its underlying bytes are concatenated to the string.
|
|
||||||
- SUBTRACT: Subtracts one argument from another and stores the result in a register.
|
|
||||||
- MULTIPLY: Multiplies one argument by another and stores the result in a register.
|
|
||||||
- DIVIDE: Divides one value by another and stores the result in a register.
|
|
||||||
- MODULO: Calculates the division remainder of two values and stores the result in a register.
|
|
||||||
- POWER: Raises one value to the power of another and stores the result in a register.
|
|
||||||
|
|
||||||
##### Logic and Control Flow
|
|
||||||
|
|
||||||
Logic instructions work differently from arithmetic and comparison instructions, but they are still
|
|
||||||
essentially binary operations with a left and a right argument. These areguments, however, are other
|
|
||||||
instructions. This is reminiscent of a stack-based virtual machine in which the arguments are found
|
|
||||||
in the stack rather than having their location encoded in the instruction. The logic instructions
|
|
||||||
perform a check on the left-hand argument and, based on the result, either skip the right-hand
|
|
||||||
argument or allow it to be executed. A `TEST` is always followed by a `JUMP`. If the left argument
|
|
||||||
passes the test (a boolean equality check), the `JUMP` instruction is skipped and the right argument
|
|
||||||
is executed. If the left argument fails the test, the `JUMP` is not skipped and it jumps past the
|
|
||||||
right argument.
|
|
||||||
|
|
||||||
- TEST
|
|
||||||
- TEST_SET
|
|
||||||
|
|
||||||
<!-- TODO: Discuss control flow using TEST -->
|
|
||||||
|
|
||||||
##### Comparison
|
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
|
|
||||||
- EQUAL
|
|
||||||
- LESS
|
|
||||||
- LESS_EQUAL
|
|
||||||
|
|
||||||
##### Unary operations
|
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
|
|
||||||
- NEGATE
|
|
||||||
- NOT
|
|
||||||
|
|
||||||
##### Execution
|
|
||||||
|
|
||||||
<!-- TODO -->
|
|
||||||
|
|
||||||
- CALL
|
|
||||||
- CALL_NATIVE
|
|
||||||
- JUMP
|
|
||||||
- RETURN
|
|
||||||
|
|
||||||
### Virtual Machine
|
|
||||||
|
|
||||||
The virtual machine is simple and efficient. It uses a stack of registers, which can hold values or
|
|
||||||
pointers. Pointers can point to values in the constant list or the stack itself.
|
|
||||||
|
|
||||||
While the compiler has multiple responsibilities that warrant more complexity, the VM is simple
|
|
||||||
enough to use a very straightforward design. The VM's `run` function uses a simple `while` loop with
|
|
||||||
a `match` statement to execute instructions. When it reaches a `Return` instruction, it breaks the
|
|
||||||
loop and optionally returns a value.
|
|
||||||
|
|
||||||
## Previous Implementations
|
## Previous Implementations
|
||||||
|
|
||||||
Dust has gone through several iterations, each with its own design choices. It was originally
|
Dust has gone through several iterations, each with its own design choices. It was originally
|
||||||
implemented with a syntax tree generated by an external parser, then a parser generator, and finally
|
implemented with a syntax tree generated by an external parser, then a parser generator, and finally
|
||||||
a custom parser. Eventually the language was rewritten to use bytecode instructions and a virtual
|
a custom parser. Eventually the language was rewritten to use bytecode instructions and a virtual
|
||||||
machine. The current implementation is by far the most performant and the general design is unlikely
|
machine. The current implementation: compiling to bytecode with custom lexing and parsing for a
|
||||||
to change.
|
register-based VM, is by far the most performant and the general design is unlikely to change,
|
||||||
|
although it has been optimized and refactored several times. For example, the VM was refactored to
|
||||||
|
manage multiple threads.
|
||||||
|
|
||||||
Dust previously had a more complex type system with type arguments (or "generics") and a simple
|
Dust previously had a more complex type system with type arguments (or "generics") and a simple
|
||||||
model for asynchronous execution of statements. Both of these features were removed to simplify the
|
model for asynchronous execution of statements. Both of these features were removed to simplify the
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
let mut i = 0
|
let mut i = 0
|
||||||
|
|
||||||
while i < 5_000_000 {
|
while i < 5_000_000 {
|
||||||
|
|
||||||
if i % 100000 == 0 {
|
|
||||||
write_line(i)
|
|
||||||
}
|
|
||||||
i += 1
|
i += 1
|
||||||
}
|
}
|
||||||
|
10
bench/addictive_addition/addictive_addition.java
Normal file
10
bench/addictive_addition/addictive_addition.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
class AddictiveAddition {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (i < 5_000_000) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
5
bench/addictive_addition/addictive_addition.rb
Normal file
5
bench/addictive_addition/addictive_addition.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
i = 0
|
||||||
|
|
||||||
|
while i < 5_000_000
|
||||||
|
i += 1
|
||||||
|
end
|
1
bench/addictive_addition/results.md
Normal file
1
bench/addictive_addition/results.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -3,9 +3,12 @@ hyperfine \
|
|||||||
--shell none \
|
--shell none \
|
||||||
--prepare 'sync' \
|
--prepare 'sync' \
|
||||||
--warmup 5 \
|
--warmup 5 \
|
||||||
|
--export-markdown results.md \
|
||||||
'../../target/release/dust addictive_addition.ds' \
|
'../../target/release/dust addictive_addition.ds' \
|
||||||
'node addictive_addition.js' \
|
'node addictive_addition.js' \
|
||||||
'deno addictive_addition.js' \
|
'deno addictive_addition.js' \
|
||||||
'bun addictive_addition.js' \
|
'bun addictive_addition.js' \
|
||||||
'python addictive_addition.py' \
|
'python addictive_addition.py' \
|
||||||
'lua addictive_addition.lua'
|
'lua addictive_addition.lua' \
|
||||||
|
'ruby addictive_addition.rb' \
|
||||||
|
'java addictive_addition.java'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dust-cli"
|
name = "dust-cli"
|
||||||
description = "Dust Programming Language CLI"
|
description = "Tool for running and debugging Dust programs"
|
||||||
authors = ["Jeff Anderson"]
|
authors = ["Jeff Anderson"]
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
@ -14,7 +14,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.14", features = ["cargo", "color", "derive", "help", "wrap_help"] }
|
clap = { version = "4.5.14", features = ["cargo", "color", "derive", "help", "wrap_help"] }
|
||||||
colored = "2.1.0"
|
color-print = "0.3.7"
|
||||||
dust-lang = { path = "../dust-lang" }
|
dust-lang = { path = "../dust-lang" }
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
|
@ -5,44 +5,71 @@ use std::{fs::read_to_string, path::PathBuf};
|
|||||||
use clap::builder::StyledStr;
|
use clap::builder::StyledStr;
|
||||||
use clap::{
|
use clap::{
|
||||||
builder::{styling::AnsiColor, Styles},
|
builder::{styling::AnsiColor, Styles},
|
||||||
ArgAction, Args, ColorChoice, Parser, ValueHint,
|
crate_authors, crate_description, crate_version, ArgAction, Args, ColorChoice, Parser,
|
||||||
|
Subcommand, ValueHint,
|
||||||
};
|
};
|
||||||
use clap::{crate_authors, crate_description, crate_version};
|
use color_print::cstr;
|
||||||
use colored::Colorize;
|
|
||||||
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
|
use dust_lang::{CompileError, Compiler, DustError, DustString, Lexer, Span, Token, Vm};
|
||||||
use log::{Level, LevelFilter};
|
use log::{Level, LevelFilter};
|
||||||
|
|
||||||
const HELP_TEMPLATE: &str = "\
|
const HELP_TEMPLATE: &str = cstr!(
|
||||||
|
"\
|
||||||
|
<bold,bright-magenta>Dust CLI</bold,bright-magenta>
|
||||||
|
────────
|
||||||
{about}
|
{about}
|
||||||
{version}
|
Version: {version}
|
||||||
{author}
|
Author: {author}
|
||||||
|
License: GPL-3.0 ⚖️
|
||||||
|
|
||||||
{usage-heading}
|
<bold,bright-magenta>Usage</bold,bright-magenta>
|
||||||
{usage}
|
─────
|
||||||
|
{tab}{usage}
|
||||||
|
|
||||||
{all-args}
|
<bold,bright-magenta>Options</bold,bright-magenta>
|
||||||
";
|
───────
|
||||||
|
{options}
|
||||||
|
|
||||||
|
<bold,bright-magenta>Modes</bold,bright-magenta>
|
||||||
|
─────
|
||||||
|
{subcommands}
|
||||||
|
|
||||||
|
<bold,bright-magenta>Arguments</bold,bright-magenta>
|
||||||
|
─────────
|
||||||
|
{positionals}
|
||||||
|
|
||||||
|
"
|
||||||
|
);
|
||||||
|
|
||||||
|
const STYLES: Styles = Styles::styled()
|
||||||
|
.header(AnsiColor::BrightMagenta.on_default().bold())
|
||||||
|
.usage(AnsiColor::BrightWhite.on_default().bold())
|
||||||
|
.literal(AnsiColor::BrightCyan.on_default())
|
||||||
|
.placeholder(AnsiColor::BrightMagenta.on_default())
|
||||||
|
.error(AnsiColor::BrightRed.on_default().bold())
|
||||||
|
.valid(AnsiColor::Blue.on_default())
|
||||||
|
.invalid(AnsiColor::BrightRed.on_default());
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(
|
#[clap(
|
||||||
version = crate_version!(),
|
version = crate_version!(),
|
||||||
author = crate_authors!(),
|
author = crate_authors!(),
|
||||||
about = crate_description!(),
|
about = crate_description!(),
|
||||||
term_width = 80,
|
|
||||||
color = ColorChoice::Auto,
|
color = ColorChoice::Auto,
|
||||||
styles = Styles::styled()
|
|
||||||
.header(AnsiColor::BrightMagenta.on_default().bold())
|
|
||||||
.usage(AnsiColor::BrightWhite.on_default().bold())
|
|
||||||
.literal(AnsiColor::BrightCyan.on_default())
|
|
||||||
.placeholder(AnsiColor::BrightGreen.on_default())
|
|
||||||
.error(AnsiColor::BrightRed.on_default().bold())
|
|
||||||
.valid(AnsiColor::Blue.on_default())
|
|
||||||
.invalid(AnsiColor::BrightRed.on_default()),
|
|
||||||
disable_help_flag = true,
|
disable_help_flag = true,
|
||||||
disable_version_flag = true,
|
disable_version_flag = true,
|
||||||
help_template = StyledStr::from(HELP_TEMPLATE.bright_white().bold().to_string()),
|
help_template = StyledStr::from(HELP_TEMPLATE),
|
||||||
|
styles = STYLES,
|
||||||
|
term_width = 80,
|
||||||
)]
|
)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
|
/// Print help information for this or the selected subcommand
|
||||||
|
#[arg(short, long, action = ArgAction::Help)]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
/// Print version information
|
||||||
|
#[arg(short, long, action = ArgAction::Version)]
|
||||||
|
version: bool,
|
||||||
|
|
||||||
/// Log level, overrides the DUST_LOG environment variable
|
/// Log level, overrides the DUST_LOG environment variable
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
@ -50,125 +77,121 @@ struct Cli {
|
|||||||
value_name = "LOG_LEVEL",
|
value_name = "LOG_LEVEL",
|
||||||
value_parser = ["info", "trace", "debug"],
|
value_parser = ["info", "trace", "debug"],
|
||||||
)]
|
)]
|
||||||
#[clap(help_heading = Some("- Options"))]
|
|
||||||
log: Option<LevelFilter>,
|
log: Option<LevelFilter>,
|
||||||
|
|
||||||
#[arg(short, long, action = ArgAction::Help)]
|
/// Source code to run instead of a file
|
||||||
#[clap(help_heading = Some("- Options"))]
|
|
||||||
help: bool,
|
|
||||||
|
|
||||||
#[arg(short, long, action = ArgAction::Version)]
|
|
||||||
#[clap(help_heading = Some("- Options"))]
|
|
||||||
version: bool,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
|
||||||
mode: Modes,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
|
||||||
source: Source,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
#[group(multiple = true, requires = "run")]
|
|
||||||
struct RunOptions {
|
|
||||||
/// Print the time taken for compilation and execution
|
|
||||||
#[arg(long)]
|
|
||||||
#[clap(help_heading = Some("- Run Options"))]
|
|
||||||
time: bool,
|
|
||||||
|
|
||||||
/// Do not print the run result
|
|
||||||
#[arg(long)]
|
|
||||||
#[clap(help_heading = Some("- Run Options"))]
|
|
||||||
no_output: bool,
|
|
||||||
|
|
||||||
/// Custom program name, overrides the file name
|
|
||||||
#[arg(long)]
|
|
||||||
#[clap(help_heading = Some("- Run Options"))]
|
|
||||||
program_name: Option<DustString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
#[group(multiple = false)]
|
|
||||||
struct Modes {
|
|
||||||
/// Run the source code (default)
|
|
||||||
///
|
|
||||||
/// Use the RUN OPTIONS to control this mode
|
|
||||||
#[arg(short, long, default_value = "true")]
|
|
||||||
#[clap(help_heading = Some("- Modes"))]
|
|
||||||
run: bool,
|
|
||||||
|
|
||||||
#[command(flatten)]
|
|
||||||
run_options: RunOptions,
|
|
||||||
|
|
||||||
/// Compile a chunk and show the disassembly
|
|
||||||
#[arg(short, long)]
|
|
||||||
#[clap(help_heading = Some("- Modes"))]
|
|
||||||
disassemble: bool,
|
|
||||||
|
|
||||||
/// Lex and display tokens from the source code
|
|
||||||
#[arg(short, long)]
|
|
||||||
#[clap(help_heading = Some("- Modes"))]
|
|
||||||
tokenize: bool,
|
|
||||||
|
|
||||||
/// Style disassembly or tokenization output
|
|
||||||
#[arg(short, long, default_value = "true")]
|
|
||||||
#[clap(help_heading = Some("- Modes"))]
|
|
||||||
style: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Clone)]
|
|
||||||
#[group(required = true, multiple = false)]
|
|
||||||
struct Source {
|
|
||||||
/// Source code to use instead of a file
|
|
||||||
#[arg(short, long, value_hint = ValueHint::Other, value_name = "SOURCE")]
|
#[arg(short, long, value_hint = ValueHint::Other, value_name = "SOURCE")]
|
||||||
#[clap(help_heading = Some("- Input"))]
|
|
||||||
command: Option<String>,
|
command: Option<String>,
|
||||||
|
|
||||||
/// Read source code from stdin
|
/// Read source code from stdin
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
#[clap(help_heading = Some("- Input"))]
|
|
||||||
stdin: bool,
|
stdin: bool,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
mode: Mode,
|
||||||
|
|
||||||
/// Path to a source code file
|
/// Path to a source code file
|
||||||
#[arg(value_hint = ValueHint::FilePath)]
|
#[arg(value_hint = ValueHint::FilePath)]
|
||||||
#[clap(help_heading = Some("- Input"))]
|
|
||||||
file: Option<PathBuf>,
|
file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
#[clap(
|
||||||
|
help_template = StyledStr::from(HELP_TEMPLATE),
|
||||||
|
styles = STYLES,
|
||||||
|
)]
|
||||||
|
enum Mode {
|
||||||
|
/// Compile and run the program (default)
|
||||||
|
#[command(short_flag = 'r')]
|
||||||
|
Run {
|
||||||
|
#[arg(short, long, action = ArgAction::Help)]
|
||||||
|
#[clap(help_heading = Some("Options"))]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
/// Print the time taken for compilation and execution
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("Run Options"))]
|
||||||
|
time: bool,
|
||||||
|
|
||||||
|
/// Do not print the program's return value
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("Run Options"))]
|
||||||
|
no_output: bool,
|
||||||
|
|
||||||
|
/// Custom program name, overrides the file name
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("Run Options"))]
|
||||||
|
name: Option<DustString>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Compile and print the bytecode disassembly
|
||||||
|
#[command(short_flag = 'd')]
|
||||||
|
Disassemble {
|
||||||
|
#[arg(short, long, action = ArgAction::Help)]
|
||||||
|
#[clap(help_heading = Some("Options"))]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
/// Style disassembly output
|
||||||
|
#[arg(short, long, default_value = "true")]
|
||||||
|
#[clap(help_heading = Some("Disassemble Options"))]
|
||||||
|
style: bool,
|
||||||
|
|
||||||
|
/// Custom program name, overrides the file name
|
||||||
|
#[arg(long)]
|
||||||
|
#[clap(help_heading = Some("Disassemble Options"))]
|
||||||
|
name: Option<DustString>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Lex the source code and print the tokens
|
||||||
|
#[command(short_flag = 't')]
|
||||||
|
Tokenize {
|
||||||
|
#[arg(short, long, action = ArgAction::Help)]
|
||||||
|
#[clap(help_heading = Some("Options"))]
|
||||||
|
help: bool,
|
||||||
|
|
||||||
|
/// Style token output
|
||||||
|
#[arg(short, long, default_value = "true")]
|
||||||
|
#[clap(help_heading = Some("Tokenize Options"))]
|
||||||
|
style: bool,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Clone)]
|
||||||
|
#[group(required = true, multiple = false)]
|
||||||
|
struct Source {}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
let mut logger = env_logger::builder();
|
// let mut logger = env_logger::builder();
|
||||||
|
|
||||||
logger.format(move |buf, record| {
|
// logger.format(move |buf, record| {
|
||||||
let elapsed = format!("T+{:.04}", start_time.elapsed().as_secs_f32()).dimmed();
|
// let elapsed = format!("T+{:.04}", start_time.elapsed().as_secs_f32()).dimmed();
|
||||||
let level_display = match record.level() {
|
// let level_display = match record.level() {
|
||||||
Level::Info => "INFO".bold().white(),
|
// Level::Info => "INFO".bold().white(),
|
||||||
Level::Debug => "DEBUG".bold().blue(),
|
// Level::Debug => "DEBUG".bold().blue(),
|
||||||
Level::Warn => "WARN".bold().yellow(),
|
// Level::Warn => "WARN".bold().yellow(),
|
||||||
Level::Error => "ERROR".bold().red(),
|
// Level::Error => "ERROR".bold().red(),
|
||||||
Level::Trace => "TRACE".bold().purple(),
|
// Level::Trace => "TRACE".bold().purple(),
|
||||||
};
|
// };
|
||||||
let display = format!("[{elapsed}] {level_display:5} {args}", args = record.args());
|
// let display = format!("[{elapsed}] {level_display:5} {args}", args = record.args());
|
||||||
|
|
||||||
writeln!(buf, "{display}")
|
// writeln!(buf, "{display}")
|
||||||
});
|
// });
|
||||||
|
|
||||||
let Cli {
|
let Cli {
|
||||||
log,
|
log,
|
||||||
source: Source {
|
|
||||||
command,
|
command,
|
||||||
file,
|
|
||||||
stdin,
|
stdin,
|
||||||
},
|
|
||||||
mode,
|
mode,
|
||||||
|
file,
|
||||||
..
|
..
|
||||||
} = Cli::parse();
|
} = Cli::parse();
|
||||||
|
|
||||||
if let Some(level) = log {
|
// if let Some(level) = log {
|
||||||
logger.filter_level(level).init();
|
// logger.filter_level(level).init();
|
||||||
} else {
|
// } else {
|
||||||
logger.parse_env("DUST_LOG").init();
|
// logger.parse_env("DUST_LOG").init();
|
||||||
}
|
// }
|
||||||
|
|
||||||
let (source, file_name) = if let Some(source) = command {
|
let (source, file_name) = if let Some(source) = command {
|
||||||
(source, None)
|
(source, None)
|
||||||
@ -190,9 +213,17 @@ fn main() {
|
|||||||
|
|
||||||
(source, file_name)
|
(source, file_name)
|
||||||
};
|
};
|
||||||
let program_name = mode.run_options.program_name.or(file_name);
|
let program_name = match &mode {
|
||||||
|
Mode::Run { name, .. } => name,
|
||||||
|
Mode::Disassemble { name, .. } => name,
|
||||||
|
Mode::Tokenize { .. } => &None,
|
||||||
|
}
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.cloned()
|
||||||
|
.or(file_name);
|
||||||
|
|
||||||
if mode.disassemble {
|
if let Mode::Disassemble { style, .. } = mode {
|
||||||
let lexer = Lexer::new(&source);
|
let lexer = Lexer::new(&source);
|
||||||
let mut compiler = match Compiler::new(lexer) {
|
let mut compiler = match Compiler::new(lexer) {
|
||||||
Ok(compiler) => compiler,
|
Ok(compiler) => compiler,
|
||||||
@ -217,16 +248,16 @@ fn main() {
|
|||||||
|
|
||||||
chunk
|
chunk
|
||||||
.disassembler(&mut stdout)
|
.disassembler(&mut stdout)
|
||||||
.style(mode.style)
|
.style(style)
|
||||||
.source(&source)
|
.source(&source)
|
||||||
.width(70)
|
.width(80)
|
||||||
.disassemble()
|
.disassemble()
|
||||||
.expect("Failed to write disassembly to stdout");
|
.expect("Failed to write disassembly to stdout");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode.tokenize {
|
if let Mode::Tokenize { style, .. } = mode {
|
||||||
let mut lexer = Lexer::new(&source);
|
let mut lexer = Lexer::new(&source);
|
||||||
let mut next_token = || -> Option<(Token, Span, bool)> {
|
let mut next_token = || -> Option<(Token, Span, bool)> {
|
||||||
match lexer.next_token() {
|
match lexer.next_token() {
|
||||||
@ -271,6 +302,10 @@ fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Mode::Run {
|
||||||
|
time, no_output, ..
|
||||||
|
} = mode
|
||||||
|
{
|
||||||
let lexer = Lexer::new(&source);
|
let lexer = Lexer::new(&source);
|
||||||
let mut compiler = match Compiler::new(lexer) {
|
let mut compiler = match Compiler::new(lexer) {
|
||||||
Ok(compiler) => compiler,
|
Ok(compiler) => compiler,
|
||||||
@ -293,7 +328,7 @@ fn main() {
|
|||||||
let chunk = compiler.finish(program_name);
|
let chunk = compiler.finish(program_name);
|
||||||
let compile_end = start_time.elapsed();
|
let compile_end = start_time.elapsed();
|
||||||
|
|
||||||
if mode.run_options.time {
|
if time {
|
||||||
print_time(compile_end);
|
print_time(compile_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,17 +337,18 @@ fn main() {
|
|||||||
let run_end = start_time.elapsed();
|
let run_end = start_time.elapsed();
|
||||||
|
|
||||||
if let Some(value) = return_value {
|
if let Some(value) = return_value {
|
||||||
if !mode.run_options.no_output {
|
if !no_output {
|
||||||
println!("{}", value)
|
println!("{}", value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode.run_options.time {
|
if time {
|
||||||
let run_time = run_end - compile_end;
|
let run_time = run_end - compile_end;
|
||||||
|
|
||||||
print_time(run_time);
|
print_time(run_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn print_time(instant: Duration) {
|
fn print_time(instant: Duration) {
|
||||||
let seconds = instant.as_secs_f64();
|
let seconds = instant.as_secs_f64();
|
||||||
|
Loading…
Reference in New Issue
Block a user