Getting Started
First of all, thank you for taking an interest in the Lume compiler! The Lume programming language is still very new, so a lot of things have yet to be fleshed out completely. That is to say, if you notice some issues or errors, please let us know!
Asking Questions
Even though the compiler is relatively new, it can still be very complicated - we won't judge for not understanding it immediately. We currently don't have any outside infrastructure to support a forum-like page, so we currently use Github for most back-and-forth between developers and end-users.
If you have found an issue or error within the compiler, feel free to open an issue on the issue tracker. This can be anything from typos and smaller changes to larger issues within the compiler. Since the project is as young as it is, it is likely that you'll find a handful of bugs.
If you have a question about Lume or the compiler, you can create a new post under the discussion board. If you want to learn anything about the language or compiler which you can't find in this book, you can create a post on there. Try to see if anyone has posted a similar question, which may answer you question.
If you want to contribute to the compiler, there's many different ways to do so! Depending on your specific skills, you can make a huge difference for other people trying out the Lume compiler. The following tasks can be done without much background knowledge, but are invaluable for the project:
- Writing documentation, both for this book or within the code itself. A lot of code within the compiler is undocumented, which can make it harder for others to understand. By doing this, you'll likely also learn a lot from the compiler.
- Answering questions on the issue tracker, as well as participating in discussions about the project. It's important to voice your opinion about the project, since it helps shape it's next iterations.
Chapters
- Building and debugging the compiler is useful for all contributors to the Lume compiler, as it explains how to interact with the codebase.
How to build and run the compiler
Testing the compiler
Debugging the compiler
The Lume compiler isn't impervious to bugs - far from it! This chapter exists to outline how one would start with debugging the compiler, given some bug. Most of this should be transferable to most parts of the compiler.
Helpful flags
When using a development build of the compiler, additional flags are possible to pass to lume. Some of them are
listed here, since they can be useful for some specific issues.
Getting a backtrace on errors
It can sometimes be hard to find out where an error is being emitted from. The --panic-on-error and --track-diagnostics flags can be useful, since they can help you place the exact line an error is being pushed.
-
--panic-on-errordoes exactly what the name implies - when a diagnostic is pushed, it panics. This makes it possible for a developer to print a stacktrace, locating where the error was pushed:$ RUST_BACKTRACE=1 lume build ./sample --panic-on-error [track_diagnostics] pushed from compiler/lume_typech/src/query/mod.rs:238:32 thread 'main' (1317499) panicked at compiler/lume_typech/src/query/mod.rs:238:32: error emitted with `panic_on_error` enabled: mismatched types stack backtrace: 0: __rustc::rust_begin_unwind at /rustc/f04e3dfc87d7e2b6ad53e7a52253812cd62eba50/library/std/src/panicking.rs:698:5 1: core::panicking::panic_fmt at /rustc/f04e3dfc87d7e2b6ad53e7a52253812cd62eba50/library/core/src/panicking.rs:80:14 2: lume_errors::DiagCtxInner::push at ./crates/lume_errors/src/lib.rs:71:13 3: lume_errors::DiagCtx::emit at ./crates/lume_errors/src/lib.rs:156:22 4: lume_typech::query::<impl lume_typech::TyCheckCtx>::check_params at ./compiler/lume_typech/src/query/mod.rs:238:32 5: lume_typech::query::<impl lume_typech::TyCheckCtx>::check_signature at ./compiler/lume_typech/src/query/mod.rs:78:14 (~~~~ LINES REMOVED FOR BREVITY ~~~~) -
--track-diagnosticsis very similar, expect it does not panic. Instead, it prints a single line for each diagnostic, outlining where the diagnostic was pushed from:$ lume build ./sample --track-diagnostics [track_diagnostics] pushed from compiler/lume_typech/src/query/mod.rs:238:32 × error[LM4001]: mismatched types ╭─[/Users/max/Documents/Projects/Lume/samples/playground/main.lm:17:17] 16 │ let a = 1_i32; 17 │ let b = a + false; ∶ ^^^^^ expected type Int32, but found type Boolean... 18 │ } ╰── ╭─[int.lm:80:34] 79 │ use Add<Int32> in Int32 { 80 │ fn external add(self, other: Int32) -> Int32 ∶ ^^^^^ ...because of type defined here 81 │ } ╰── help: expected type Int32 found type Boolean
Dumping the MIR
When debugging an issue which only shows itself later in the compilation process, it might be useful to verify
that the MIR looks correct. To dump the MIR to the console output, you can pass the --dump-mir flag:
$ lume build ./sample --dump-mir
(~~~~ LINES REMOVED FOR BREVITY ~~~~)
@!3848283137416547773 fn "std::Range::new" (start: i32 #0, end: i32 #1) -> ptr std::Range {
B0:
let #2: metadata std::Range = metadata std::Range()
#3 = alloc std::Range
mark object(#3)
*#3[+x0] = #2
*#3[+x8] = #0
*#3[+xC] = #1
let #4: ptr std::Range = +(#3, 8_u64)
mark object(#4)
return #4
}
Sometimes, an issue crops up because of a specific optimization pass. You can print the MIR before a specific pass
is invoked by passing the name of the pass to the --dump-mir flag:
$ lume build ./sample --dump-mir=mark_gc_refs
(~~~~ LINES REMOVED FOR BREVITY ~~~~)
@!3848283137416547773 fn "std::Range::new" (start: i32 #0, end: i32 #1) -> ptr std::Range {
B0:
let #2: metadata std::Range = metadata std::Range()
#3 = alloc std::Range
*#3[+x0] = #2
*#3[+x8] = #0
*#3[+xC] = #1
let #4: ptr std::Range = +(#3, 8_u64)
return #4
}
Tracing the compiler
It might sometimes be important to see the execution path of the compiler, to see why a certain operation
is executed. For that reason, the compiler has #[traced] attributes on most methods, which can print out
some information about the method call - passed arguments, return values, whether an error was returned, etc.
To see these logs, you need to enable the tracing feature on the compiler. Tracing is disabled by default, since
it can cause significant performance loss.
To enable it, you must have a local copy of the compiler and have passed the given command when building:
$ cargo build --bin lume --features tracing
You can then pass log filters via the LUMEC_LOG environment variable when running the compiler.
Filtering traces
While you could print all traces to the console, this is likely not what you want. Given a function like this:
#[traced(level = Debug, fields(id))]
fn compiler_function(id: NodeId) -> TypeRef {}
you can print all it's invocations by using:
LUMEC_LOG=compiler_function=debug
which will cause all calls to compiler_function to be logged, along with the passed id argument.
This is under the assumption that type_of_expr exists without any parent module or external crate. You'll more
like see log filters like the following:
LUMEC_LOG=lume_infer::query::type_of=trace
lume_infer::query::type_ofis the full path of the method, including crate and module path.
Filter specific invocations
You can also filter traces by the arguments which are passed to them. Each trace has a list of arguments
which are added as "fields" to the trace. For example, the given method within the compiler
(location in the lume_typech crate, inside the query module):
#[traced(level = Trace, fields(name = expr.name()), err, ret)]
fn check_params(
&self,
expr: lume_hir::CallExpression<'_>,
parameters: &lume_types::Parameters,
arguments: &[&lume_hir::Expression],
) -> Result<bool> {}
has a field name equal to the name of the passed call expression. So, to filter all calls to check_params
which received a call expression to my_function, we can use the following filter:
LUMEC_LOG=lume_typech::query::check_params[name=my_function]=trace
For more information about the filtering, you can read the libftrace documentation about the feature.