Mlang
The Mlang compiler has a traditionnal architecture consisting of various intermediate representations going from the source code to the target backend.
M Frontend
First, the source code is parsed according to the Menhir grammar specified in Mlang.Mparser
. The grammar is not exactly LR(1) so we rely on Mlang.Parse_utils
to backtrack, especially on symbol parsing. The target intermediate representation is Mlang.Mast
, which is very close to the concrete syntax and can be printed using Mlang.Format_mast
.
M Intermediate representation
The M language has a lot of weird syntactic sugars and constructs linked to its usage inside multiple DGFiP applications. Mlang.Mast_to_mir
extracts from the AST the computational core corresponding to a DGFiP application into the M Variable Graph (Mlang.Mir
), which consists basically of a flat map of all the definitions of the variables used in the application. The type system of M is very primitive, and basically all programs typecheck ; however Mlang
.Mir_typechecker provides a top-down typechecking algorithm to split simple variables from tables.
At this point, the Mlang
.Mir_dependency_graph modules interprets the MIR as a first-class graph and computes various reachability and cycle analysis in order to determine the computational flow inside the program. This dependency order is encapsulated with the program in Mlang
.Mir_interface. Some typechecking and macro expansion is performed by Mlang
.Mir_typechecker.
Mlang.Mir
Mlang.Mast_to_mir
Mlang.Format_mir
Mlang
.Mir_typecheckerMlang
.Mir_interfaceMlang
.Mir_dependency_graph
M++ Frontend
The M code can be called several times through a sort of driver that saves some variables between calls. This driver is encoded in the M++ domain-specific language that Mlang handles together with the M. M++ is parsed with Mlang
.Mpp_parser
M++ Intermediate representation
From the M++ AST, Mlang
.Mpp_frontend translates to Mlang
.Mpp_ir by eliminating some syntactic sugars.
M/M++ Common backend intermediate representation
The module Mlang
.Mpp_ir_to_bir performs the inlining of all the M calls in the M++ programs and yields a single program in a new common intermediate representation, Mlang
.Bir. Inputs and outputs for this representation can be specified using Mlang
.Bir_interface.
The BIR representation is equipped with a fully fledged interpreter, Mlang
.Bir_interpreter. The interpreter is instrumented for code coverage via Mlang
.Bir_instrumentation, and can be parametrized via multiple sorts of custom floating point values for precision testing (Mlang
.Bir_number).
Mlang
.Bir_instrumentationMlang
.Bir_interfaceMlang
.Bir_interpreterMlang
.Bir_numberMlang
.BirMlang
.Format_bir
Optimizations
While BIR is an AST-based representation, Mlang
.Oir defines a dual of BIR in a control-flow-graph (CFG) for. You can go back and forth between those two implementations using Mpp
.Bir_to_oir.
OIR is the right place to perform some basic program optimizations. Mlang
.Inlining expands some of the small rules into their full expressions, while Mlang
.Partial_evaluation and Mlang
.Dead_code_removal simplify the program depending on the inputs and outputs. The main optimizing loop is implemented in Mlang
.Oir_optimizations.
Testing M and M++ programs
Mlang comes with a testing framework for M and M++ programs that is based on the test format used by the DGFiP. The test files are parsed with Mlang
.Test_parser into Mlang
.Test_ast. Then, single or batch testing can be performed using Mlang.Test_interpreter
.
Mlang
.Test_astMlang.Test_interpreter
Mlang
.Test_parser
Compiling M and M++ programs
M/M++ programs can be compiled to other programming languages using several backends that take BIR and produce source code files in their respective languages.
Mlang
.Bir_to_pythonMlang
.Bir_to_cMlang.Bir_to_dgfip_c