Overview

A directory’s configuration script is the file named config.mufl located within the directory. This file must evaluate to a MUFL structure compliant with the following format:

(
    $exports -> (
        $libraries -> (,), 
        $applications -> (,)
    ),
    $imports -> (
        $libraries -> (,),
        $applications -> (,)
    )
).

The structure lists the files exported from the directory. Exported files are those that can be load-ed by the code outside.

Generally, add <object_name> -> <file_absolute_path> key-value pairs of the files to export to the exports section, where <object_name> refers to the MUFL name of an application or library.

All files compiled inside the current directory use the imports section of the structure to search for dependencies.

Generally, add <object_name> -> <file_absolute_path | directory_absolute_path> key-value pairs of the files to import to the imports section. When a file in the directory needs access to an application or library, this record is used to retrieve the path to either the file itself or the directory containing the application or library that is being sought.

Only record fields of corresponding types ($exports, $imports) and ($libraries, $applications) are allowed on the corresponding depth levels, but they are not required to be present.

For imports, both libraries and applications, the corresponding dictionaries are combined with sets of all local libraries/applications, if present.
For exports, both libraries and applications, the corresponding dictionaries are combined with empty sets.

Source files do not have automatic visibility to the files in their own directory; they use the same standard search procedure. Therefore, you must either add all desired files in the directory to the directory’s configuration script or use the default configuration structure.

Standard Search Procedure

Searching for dependencies loaded via loads <application_or_library> <object_name> is performed according to this standard search procedure:

  1. If the required file belongs to the ADAPT infrastructure (the meta.mm library of system types or a module needed for transactions functionality loaded by the uses transactions directive), then the meta search path (specified by the -mp <path> compiler flag) is used instead of the configuration script.
  2. If the file search is initiated from a configuration file or its dependencies, then the MUFL root directory is searched.
  3. The configuration script of the file’s parent directory is loaded and evaluated.
    • If the configuration script file is missing, a default configuration structure is generated that imports and exports every application and library in the directory.
    • Configuration load cycles are forbidden and lead to a compilation error.
    • If the configuration script has been evaluated previously, the results are loaded from cache.
    • Otherwise, the MUFL compiler is called recursively, and the configuration script is compiled and then evaluated.
    • The result is tested for validity and, if valid, cached.
  4. The structure obtained from the configuration script is reduced by an imports -> (libraries | applications) -> name sequence. The reduction product must be a string; otherwise, a compilation error occurs.
    • If the resulting string is an absolute regular file path, an attempt is made to load it. Failure to load the file results in a compilation error.
    • If the path is not absolute, it is considered invalid and leads to a compilation error.
  5. At this point, the path found in the configuration script is a directory path. The configuration script of that directory is loaded and evaluated. If the process fails, a compilation error occurs.
  6. The structure obtained from the configuration script is reduced by an exports -> (libraries | applications) -> name sequence. The reduction product must be a string; otherwise, a compilation error occurs.
    • If the resulting string defines an absolute regular file path, the path is used to load the file sought, otherwise a compilation error occurs.

MUFL parse-stage constructs such as #string_literal, ##glob_literal, and #$long_name might come in handy when writing configuration files.

  • #string_literal resolves to a string literal equal to an absolute path whose stem equals string_literal. For example:

    • #hello.mm -> "/usr/me/mufl-poc-cpp/project/hello.mm"
    • #____ -> "/usr/me/mufl-poc-cpp/project/____"
  • ##glob_literal resolves to an array of filesystem objects matching the glob_literal. For example:
    • ##"." -> ["/usr/me/mufl-poc-cpp/"]
    • ##"./tests/Functional/" ->
        [
            "/usr/me/mufl-poc-cpp/tests/Functional/01.Types/",
            "/usr/me/mufl-poc-cpp/tests/Functional/02.Arithmetic/",
            "/usr/me/mufl-poc-cpp/tests/Functional/03.Flow_Control/",
            "/usr/me/mufl-poc-cpp/tests/Functional/04.Iterations/"
        ]
      
  • #$long_name resolves to an environment variable of the current process scope. If the variable is not defined, then it resolves to an empty string. For example:

    • Somewhere in shell: export Var='Hello'
    • #$Var -> "Hello"

Another useful MUFL construct is config_load(<directory_absolute_path> | (<directory_absolute_path | glob_literal> ...)). The possible arguments are:

  • A single string literal defining an absolute directory path. In this case, the directory’s configuration script is loaded, evaluated, and the construct resolves to an immutable dictionary.
  • An arbitrary number of glob literals, resolving to directory paths, and/or string literals defining absolute directory paths. In this case, the resulting structure consists of all the exports of the configuration scripts of the directories mentioned.

    If all of the paths fail (which means the configuration files do not exist or are invalid), the resulting structure is empty, potentially causing a reduction error later on at the evaluation stage.

Usage Example

This section describes a tricky configuration script usage example. It is not a real life scenario, but rather showcases MUFL’s powerful project-organization capability.

Directory structure:

(dir) Project
    |-(dir) Package_A
                  |-(file) MyApp.mu
                  |-(file) config.mufl
    |-(dir) Package_B
                  |-(file) config.mufl
    |-(dir) Package_C
                  |-(file) SomeLib.mm
                  |-(file) unrelatedFile.mm

Application MyApp declared in the Package_A/MyApp.mu file:

application MyApp loads library SomeLib
{
    SomeLib::function().
    _print SomeLib::variable.
}

Library SomeLib defined in the Package_C/SomeLib.mm file:

library SomeLib
{
    fn function (_)
    {
        _print "Hello from the library!\n".
    }

    variable = "Library constant\n".
}

Configuration script Package_A/config.mufl that says, “search for SomeLib in the Package_B directory”:

config script
{
    (
        $imports->(
            $libraries->(
                $SomeLib->#"../Package_B"
            ),
            $applications->(,)
        ),
        $exports->(
            $libraries->(,),
            $applications->(,)
        )
    ).
}

Configuration script Package_B/config.mufl that says, “load the Package_C configuration and search it”:

config script
{
    (
        $imports->(
            $libraries->(,),
            $applications->(,)
        ),
        $exports->(
            $libraries->(
                $SomeLib->( (config_load #"../Package_C") $exports $libraries $SomeLib )
            ),
            $applications->(,)
        )
    ).
}

And lastly, the configuration script Package_C/config.mufl is missing because the author forgot to create it. But, when the compiler cannot find the script, it generates this default configuration structure:

(
    $imports->(
        $libraries->(
            $SomeLib->#"SomeLib.mm"
        ),
        $applications->(,)
    ),
    $exports->(
        $libraries->(
            $SomeLib->#"SomeLib.mm"
        ),
        $applications->(,)
    )
)

The compiler imports and exports every source file in the Package_C directory, even though it was not explicitly instructed to do so. Thus, when the code compiles and runs, it prints:

Hello from the library! 
Library constant

When organizing your project, be sure to follow the proper syntax and utilize the available constructs to ensure the correct file paths and dependencies are resolved during the compilation process.

Useful Notes

There are no strict constraints on the format of configuration script files. The only requirement is that, in the end, the file compiles and evaluates down to a valid configuration structure.

You can additional logic to your configuration scripts, such as logic helping to resolve dependencies or generate the configuration structure.

For example, consider this completely valid configuration script:

config script {

local_libraries=##"./*.mm".
local_applications=##"./*.mu".

local_libraries_dict is str->>str = (,).
local_applications_dict is str->>str = (,).

fn find_name (path: str) -> str
{
    length = _strlen path.
    last_idx = 0. 
    sc path -- (idx->ch) {
       if ch == "/" then
            last_idx  -> idx.
       end
    }
     
    name = _substr path (last_idx + 1) (length - last_idx - 4). 
    return name.
}

sc local_libraries -- (->local_library_path) {
    local_libraries_dict (find_name local_library_path) -> local_library_path.
}


sc local_applications -- (->local_application_path) {
    local_applications_dict (find_name local_application_path) -> local_application_path.
}


std_lib_config = (config_load #$MUFL_STDLIB_PATH).

(
    $exports -> ($libraries -> (,), $applications -> (,)),
    $imports -> (
        $libraries -> (std_lib_config $exports $libraries)'local_libraries_dict,
        $applications -> (std_lib_config $exports $applications)'local_applications_dict
    )
).
}

This script is actually an example of a very typical configuration script from the ADAPT production code. It demonstrates a full range of useful techniques and tools used for generating the configuration file, such as:

  • Using glob literals to scan the folders in search of potential source files
  • Filtering those results with MUFL code
  • Using dictionary combinators to combine the resulting structures:
    • From previously loaded config files
    • From glob search expressions