A less ambitious project for a compiled language.
Why 'Rove'?
rove to move or travel around an area (without having a particular place you intend to go to)
This language does not intend to solve any goal and is merely a personal project - hence the 'aimless' aspect of it.
Also because it makes me think of romp and raft (both words for groups of otters and my first failed attempt at a language was called Otter).
Examples
See EXAMPLES.md for runnable examples (at time of writing). The language is informally design-by-contract and allows preconditions and postconditions to be defined in function headers. It is meant to be readable (inspired by Swift on some of its syntax). Once the language is stabilised, I will make a full guide and begin dog-fooding it by rewriting the compiler in Rove itself.
🏷️ v0.0.1
- Type checker
- Functions
- If/else
🏷️ v0.0.2
- Loops
- Constraints (guard, require, ensure)
- Enums
- Switch
- Errors
- enum errors and removing the error union types (
error!T),throws X -
try! x() -
throw -
try x() else yandtry x() else e { ... /* must exit function here */ } -
guardinside the function body- e.g.
guard x > 0 else { return 0 }, orguard x > 0 else ::BiggerThanZero
- e.g.
🏷️ v0.0.3
-
module Xandmodule Y {} - Real booleans
- Unary operators
- Fix float division (int/int should produce an int, truncated. not a float)
🏷️ v0.0.4
-
&&and||with shortcircuiting. -
use X::*,use X::{X, Y} - Make all statements expressions (e.g.
ifandswitchshould return a value)
🏷️ v0.0.5
- Structs (with heap alloc)
- Field access
- ARC memory management
🏷️ v0.0.6
- Defer/errdefer
- Managed/unmanaged strings
- Arrays
🏷️ v0.0.7
- Generics
- Custom linking with C libs (with extern names, or no_mangle)
🏷️ v0.0.8
-
import X, compiling other libraries into the same binary and a (for now unstable) interface to allow linking with other Rove libraries (like rmeta)
Rove is a very young language. For now it still depends on a Rust-based set of runtime helpers. The goal is to eventually weed of this, and even re-write this compiler in this language.
It is not meant to be used yet, or maybe ever in the future. This is a slower, more deliberate attempt at developing a compiled language; I have spent days to weeks trying to build an entire language and ended up with nothing to show for it. There are no tutorials or guides yet (waiting for heap allocation and strings first)
Pass --emit with a comma-separated list to write debug artifacts next to the
input file:
cargo run -- main.rv --emit ast,typed,clif,opt-clif,cfg,objast-__file.rv.ast, the parsed ASTtyped-__file.rv.typed, the AST with type annotationsclif-__file.rv.clif, the unoptimized Cranelift IRopt-clif-__file.rv.opt.clif, the optimized Cranelift IRcfg-__file.rv.cfg, a DOT graph of the Cranelift control flowobj-__file.rv.o, the object file (otherwise deleted after linking)
Every .rv file in tests/cases/ is compiled and run as a snapshot test with
insta. A snapshot records either the program's exit code,
stdout and stderr, or the compiler diagnostics if it fails to compile.
cargo test --test snapshotsWhen output changes, the test fails and shows a diff. To review and accept or reject the changes, install the CLI once and run review:
cargo install cargo-insta
cargo insta reviewTo add a test, drop a new .rv file into tests/cases/ (prefix it with err_
if it is meant to fail), run cargo insta test --review, and accept the new
snapshot. Snapshots live in tests/snapshots/ and should be committed.
The language is evolving and being made, but I will list 'decisions' as I reach the point I need to make one.
Anything that can cause a program to crash must be marked with !, such as
require!, ensure! or otherwise. It's not yet decided if callees must also
obey this rule (so any function with require!/ensure! must be called with !
as well). A hypothetical example:
// NOT RUNNABLE CODE - EXAMPLE
func divide(a: int, b: int) -> int
require! non_zero_divisor: b != 0
{
return a / b
}
func main() -> int
{
// without the transitive `!` rule
let x = divide(1, 0);
// with it
let x = divide!(1, 0)
}If it was transitive, functions could also alternatively prove their own safety
with a safe keyword, which would allow them to be called without !:
// NOT RUNNABLE CODE - EXAMPLE
func divide(a: int, b: int) -> int
require! non_zero_divisor: b != 0
{
return a / b
}
func half(a: int) -> int
safe
{
// SAFETY: divide fails if b is 0, but we know b is 2.
return divide!(a, 2)
}
func main() -> int
{
let x = divide!(2, 2); // must use `!` because divide is unsafe
let x = half(2); // no `!` needed, because half is safe
}If an enums purpose is a flag, you may use a lowercase name. If it represents something else, it should be capitalized.
enum Color {
Red,
Green,
Blue
}
enum Alignment {
left,
center,
right
}Requires the x86_64-pc-windows-gnu toolchain to compile the minimally required
runtime at the minute (runtime). In the future it will not depend
on a precompiled runtime binary.