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

  1. 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-error does 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-diagnostics is 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_of is 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.