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