# `MishkaCmsCore.Runtime.Compilers.ModuleCompiler`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/compilers/module_compiler.ex#L5)

Runtime module compiler for dynamically compiling and loading Elixir modules from AST.

The `ModuleCompiler` provides a safe and robust foundation for runtime compilation
within the MishkaCms ecosystem. It handles the entire lifecycle of module compilation,
from AST validation and module name extraction to compilation with diagnostic collection
and proper cleanup through module unloading.

## Features

* **Safe Runtime Compilation**: Compiles Elixir modules from quoted expressions with comprehensive error handling
* **Diagnostic Collection**: Gathers warnings and errors during compilation using `Code.with_diagnostics/1`
* **Module Lifecycle Management**: Provides utilities for module unloading and cleanup
* **AST Pattern Matching**: Supports various module definition patterns and aliases
* **Integration Ready**: Designed to work seamlessly with MishkaCms runtime compilers

## Basic Usage

    # Compile a simple module from quoted expression
    quoted = quote do
      defmodule MyApp.DynamicModule do
        def hello, do: "Hello, World!"
      end
    end

    case ModuleCompiler.compile_module(quoted, "dynamic.ex") do
      {:ok, module, diagnostics} ->
        IO.puts("Successfully compiled #{inspect(module)}")
        IO.puts("Diagnostics: #{length(diagnostics)} items")

      {:error, module, {error, diagnostics}} ->
        IO.puts("Failed to compile #{inspect(module)}: #{inspect(error)}")

      {:error, error} ->
        IO.puts("Invalid module definition: #{inspect(error)}")
    end

## Module Cleanup

    # Unload a module when no longer needed
    ModuleCompiler.unload(MyApp.DynamicModule)

## Safety and Best Practices

* Always handle both success and error cases when compiling modules
* Use meaningful file names for better error reporting and debugging
* Collect and review diagnostics even for successful compilations
* Unload modules when they're no longer needed to prevent memory leaks
* Use `Code.put_compiler_option(:ignore_module_conflict, true)` for dynamic recompilation

## Performance Considerations

* Module compilation is relatively expensive - cache compiled modules when possible
* Diagnostics collection adds minimal overhead but provides valuable debugging information
* Module unloading is necessary for long-running applications with dynamic modules
* Consider batching multiple module compilations when building complex systems

# `diagnostics`

```elixir
@type diagnostics() :: [Code.diagnostic(:warning | :error)]
```

A list of compilation diagnostics containing warnings and errors generated during the compilation process.

# `ast_to_format_string!`

Formats and prints AST as readable Elixir code for debugging and inspection.

This utility function converts an Abstract Syntax Tree (AST) back into formatted
Elixir source code and prints it to the console. This is particularly useful for
debugging complex AST transformations, inspecting generated code, or understanding
the structure of dynamically created modules.

## Process

1. **AST to String**: Converts the AST back to Elixir source code using `Macro.to_string/1`
2. **Format**: Applies Elixir's code formatter for consistent styling
3. **Output**: Prints the formatted code to the console with proper indentation

## Parameters

* `ast` - Any valid Elixir AST (quoted expression, module definition, etc.)

## Return Value

* `:ok` - Always returns `:ok` after printing (return value from `IO.puts/1`)

## Examples

    # Format a simple function definition
    ast = quote do
      def hello(name) do
        "Hello, #{name}!"
      end
    end

    ModuleCompiler.ast_to_format_string!(ast)
    # Outputs:
    # def hello(name) do
    #   "Hello, #{name}!"
    # end

    # Format a complete module
    module_ast = quote do
      defmodule MyApp.Example do
        @moduledoc "Example module"

        def greet(name), do: "Hi #{name}"

        def farewell(name) do
          "Goodbye, #{name}!"
        end
      end
    end

    ModuleCompiler.ast_to_format_string!(module_ast)
    # Outputs properly formatted module with consistent indentation

## Use Cases

* **Development Debugging**: Inspect AST generated by other compilers
* **Code Generation Verification**: Ensure generated code looks correct
* **Learning Tool**: Understand how Elixir represents code internally
* **Template Development**: Debug dynamic template compilation
* **AST Transformation**: Verify transformations produce expected results

## Notes

This function will raise an exception if the AST cannot be converted to valid
Elixir code or if the code cannot be formatted. Ensure the AST is well-formed
before calling this function.

The output goes directly to the console and is not captured or returned as a
string. For capturing formatted code as a string, use `Macro.to_string/1` and
`Code.format_string!/1` directly.

# `compile_module`

```elixir
@spec compile_module(Macro.t(), String.t(), keyword()) ::
  {:ok, module(), diagnostics()}
  | {:error, module(), {Exception.t(), diagnostics()}}
  | {:error, Exception.t() | :invalid_module}
```

Compiles a module from a quoted expression with comprehensive error handling and diagnostics collection.

This function serves as the main entry point for runtime module compilation. It first extracts
the module name from the provided quoted expression, then proceeds with compilation while
gathering diagnostic information throughout the process.

The compilation process includes:
* Module name extraction and validation
* Compiler option configuration for dynamic recompilation
* AST compilation using Elixir's compiler infrastructure
* Comprehensive diagnostic collection (warnings and errors)
* Proper error handling with detailed context

## Parameters

* `quoted` - A quoted expression containing a valid module definition (typically from `quote do`)
* `file` - Optional source file name for error reporting and debugging (defaults to "nofile")
* `opts` - Keyword list:
  * `:cache` - `true` to load the module from `MishkaCmsCore.Runtime.Compilers.CompiledModuleCache`
    when it holds this exact code, and to save it there when it is compiled. Only when the cache is
    configured; otherwise, and by default (`false`), the module is compiled. A module loaded from
    the cache is answered `{:ok, module, []}`, and the span's stop metadata says
    `%{cache: :hit, module: module}` — `%{cache: :miss, module: module}` when it was compiled
    through the cache, `%{}` when the cache was not used.

## Return Values

* `{:ok, module, diagnostics}` - Successful compilation with the loaded module and any diagnostics
* `{:error, module, {error, diagnostics}}` - Compilation failed for a valid module with error details
* `{:error, :invalid_module}` - The quoted expression doesn't contain a valid module definition

## Examples

    # Basic successful compilation
    quoted = quote do
      defmodule MyApp.TestModule do
        def greet(name), do: "Hello, #{name}!"
      end
    end

    case ModuleCompiler.compile_module(quoted, "test_module.ex") do
      {:ok, module, diagnostics} ->
        IO.puts("Compiled: #{inspect(module)}")
        # MyApp.TestModule.greet("World") => "Hello, World!"

      {:error, module, {error, diagnostics}} ->
        IO.puts("Failed to compile #{module}: #{inspect(error)}")

      {:error, :invalid_module} ->
        IO.puts("Invalid module structure provided")
    end

    # Compilation with warnings
    quoted_with_warning = quote do
      defmodule MyApp.WarningModule do
        def test do
          unused_var = "this will generate a warning"
          :ok
        end
      end
    end

    {:ok, module, diagnostics} = ModuleCompiler.compile_module(quoted_with_warning)
    # diagnostics will contain unused variable warnings

## Error Scenarios

    # Invalid module structure
    invalid = {:not_a_module, :definition}
    {:error, :invalid_module} = ModuleCompiler.compile_module(invalid)

    # Compilation error with context
    broken = quote do
      defmodule BrokenModule do
        def broken, do: undefined_function()
      end
    end

    {:error, module, {error, diagnostics}} = ModuleCompiler.compile_module(broken)

# `module_name`

Extracts the module name from a quoted module definition with support for multiple AST patterns.

This function analyzes the structure of a quoted expression to identify and extract
the module name from various forms of module definitions. It handles different
patterns that can occur in Elixir AST, including simple modules, aliased modules,
and modules with different structural representations.

## Supported AST Patterns

* `{:defmodule, _, [{:__aliases__, _, [ModuleName]}, _]}` - Simple module with alias
* `{:defmodule, _, [module_atom, _]}` - Direct module atom reference
* `{module_atom, {:defmodule, _, _}}` - Module with metadata tuple

## Parameters

* `quoted` - A quoted expression that should contain a module definition

## Return Values

* `{:ok, module}` - Successfully extracted module name as an atom
* `{:error, :invalid_module}` - The quoted expression doesn't match any supported module pattern

## Examples

    # Standard module definition
    quoted = quote do
      defmodule MyApp.UserModule do
        def hello, do: "world"
      end
    end

    {:ok, MyApp.UserModule} = ModuleCompiler.module_name(quoted)

    # Module with alias pattern
    alias_quoted = quote do
      defmodule SomeModule do
        def test, do: :ok
      end
    end

    {:ok, SomeModule} = ModuleCompiler.module_name(alias_quoted)

    # Invalid module structure
    invalid = {:not_a_module, :at_all}
    {:error, :invalid_module} = ModuleCompiler.module_name(invalid)

## Notes

This function performs pattern matching on the AST structure and does not
validate that the extracted module name is valid or that the module definition
is syntactically correct. It only extracts the name portion of the module
definition for use in the compilation process.

If no supported pattern is matched, the function logs an error with the
problematic quoted expression for debugging purposes.

# `unload`

Safely unloads a module from the Erlang code server with proper cleanup.

This function provides a clean way to remove dynamically compiled modules from
memory when they are no longer needed. It performs a two-step process to ensure
complete removal of the module from the runtime system.

The unloading process:
1. **Purge**: Removes all references to the old module code
2. **Delete**: Removes the module definition from the code server
3. **Forget**: Deletes the module's saved files when `CompiledModuleCache` is on — an unloaded
   module is one that was removed or replaced, and is compiled afresh the next time it is asked for

This is essential for long-running applications that frequently compile and
discard modules to prevent memory leaks and module conflicts.

## Parameters

* `module` - The module atom to unload (e.g., `MyApp.DynamicModule`)

## Return Value

* `:ok` - Always returns `:ok` regardless of whether the module existed

## Examples

    # Compile and then unload a module
    quoted = quote do
      defmodule MyApp.TemporaryModule do
        def temp_function, do: "temporary"
      end
    end

    {:ok, module, _diagnostics} = ModuleCompiler.compile_module(quoted)

    # Use the module
    result = module.temp_function()  # => "temporary"

    # Clean up when done
    :ok = ModuleCompiler.unload(module)

    # Module is now removed from memory
    # module.temp_function() would raise UndefinedFunctionError

    # Safe to call on non-existent modules
    :ok = ModuleCompiler.unload(NonExistentModule)

## Use Cases

* **Dynamic Content Management**: Remove old page modules when content is updated
* **Testing Environments**: Clean up test modules between test runs
* **Hot Code Reloading**: Remove old versions before loading new ones
* **Memory Management**: Prevent memory leaks in long-running CMS applications

## Notes

This function is safe to call multiple times on the same module or on
modules that don't exist. The Erlang code server handles these cases
gracefully without raising errors.

After unloading, any existing processes or references holding onto the
module will need to be updated, as calls to the unloaded module will
result in `UndefinedFunctionError`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
