Enforcing House Rules

The compile-time metaprogram can be used to do all sorts of arbitrary custom compile-time error checking and code modification. The compile-time metaprogram can be used to enforce house rules. Because these house rules apply only to specific instances, it does not make sense to build house rules into a general purpose compiler.

Compile-Time MISRA Checking: Check for Multiple Levels of Pointer Indirection

The compile-time metaprogram can be used, for example, to check that a program adheres to the MISRA coding standards. MISRA coding standards are a set of C and C++ coding standards, developed by the Motor Industry Software Reliability Association (MISRA). These are standards specific to the automotive industry, and these should not be part of a general purpose compiler. However, a custom compile-time metaprogram can check adherence to the MISRA coding standard.

In this example, we want to check that a Jai program adheres to the MISRA coding rule that prevents use multiple levels of pointer indirection (e.g. you cannot do a: ***int = b;).

Let’s create a metaprogram that makes compiler errors when you do multiple levels of pointer indirection:

#import "Basic";
#import "Compiler";

#run build();

build :: () {
    // Create a workspace for the target program.
    w := compiler_create_workspace("Target Program");
    if !w {
        print("Workspace creation failed.\n");
        return;
    }

    target_options := get_build_options(w);
    target_options.output_executable_name = "checks";
    set_build_options(target_options, w);

    compiler_begin_intercept(w);
    add_build_file("main.jai", w);

    while true {
        message := compiler_wait_for_message();
        if !message break;
        misra_checks(message);

        if message.kind == .COMPLETE  break;
    }

    compiler_end_intercept(w);

    // This metaprogram should not generate any output executable:
    set_build_options_dc(.{do_output=false});
}

misra_checks :: (message: *Message) {
    if message.kind != .TYPECHECKED return;
    code := cast(*Message_Typechecked) message;
    for code.declarations {
        decl := it.expression;
        check_pointer_level_misra_17_5(decl);
    }

    for tc: code.all {
        expr := tc.expression;
        if expr.enclosing_load {
            if expr.enclosing_load.enclosing_import.module_type != .MAIN_PROGRAM  continue;
        }
        
        for tc.subexpressions {
            // Check rule 17.5. We already did the pointer-level check for global declarations
            // but, local declarations don't come in separate messages; instead, we check them here.
            if it.kind == .DECLARATION {
                sub_decl := cast(*Code_Declaration) it;
                check_pointer_level_misra_17_5(sub_decl); 
            }
        }
    }

    check_pointer_level_misra_17_5 :: (decl: *Code_Declaration) {
        type := decl.type;
        pointer_level := 0;
    
        while type.type == .POINTER {
            pointer_level += 1;
            p := cast(*Type_Info_Pointer) type;
            type = p.pointer_to;
        }
        if pointer_level > 2 {
            location := make_location(decl);
            compiler_report("Too many levels of pointer indirection.\n", location);
        }
    }

}

Create a main.jai to test that our compile-time metaprogram can correctly check for pointer indirection.

main :: () {
  main :: () {
  a: *int;
  b := *a;
  c := *b; // Too many levels of pointer indirection! c is of Type (***int)
}
}

When we run the metaprogram, we get the following error message:

main.jai:6,3: Error: Too many levels of pointer indirection.

There is a more detailed example in the how_tos under 480_custom_checks