Frontends
Plugin Registration
Plugin Registration
This page documents the plugin registration mechanism for MPL_Compiler frontends and backends, and how to add a 10th language.
The PluginRegistry
// core/plugin/PluginRegistry.h:12-97
class PluginRegistry {
public:
static PluginRegistry& instance(); // singleton
void registerFrontend(std::unique_ptr<ILanguageFrontend> fe);
void registerBackend(std::unique_ptr<ILanguageBackend> be);
ILanguageFrontend* getFrontendByLanguage(const std::string& lang) const;
ILanguageFrontend* getFrontendByExtension(const std::string& ext) const;
ILanguageFrontend* getFrontend(const std::string& langOrExt) const;
ILanguageBackend* getBackend(const std::string& lang) const;
std::vector<std::string> availableFrontends() const;
std::vector<std::string> availableBackends() const;
// Legacy compatibility helpers
ILanguageFrontend* getFrontendByExt(const std::string& ext) const;
ILanguageFrontend* getFrontendLegacy(const std::string& lang) const;
std::vector<std::string> listFrontends() const;
std::vector<std::string> listBackends() const;
private:
PluginRegistry() = default;
mutable std::mutex mu_;
std::unordered_map<std::string, std::unique_ptr<ILanguageFrontend>> frontends_;
std::unordered_map<std::string, std::unique_ptr<ILanguageBackend>> backends_;
std::unordered_map<std::string, std::string> extToLang_;
};
registerFrontend behaviour
PluginRegistry::registerFrontend (core/plugin/PluginRegistry.h:19):
void registerFrontend(std::unique_ptr<ILanguageFrontend> fe) {
std::lock_guard<std::mutex> lock(mu_);
auto name = fe->languageName();
for (const auto& ext : fe->fileExtensions()) {
extToLang_[ext] = name;
}
frontends_[name] = std::move(fe);
}
The frontend is keyed by both language name and each file extension. Thread-safe (mutex-protected) because the singleton can be touched from worker threads during parallel builds.
Lookup logic
getFrontend(const std::string& langOrExt) (line 45):
- If
langOrExtstarts with., treat it as an extension. - Otherwise, look up by language name first.
- If no language-name match, fall back to extension lookup.
This means getFrontend(".js") and getFrontend("javascript") both
return the same JSFrontend*.
registerPlugins() — the single entry point
MPL_Compiler.cpp:32-47:
void registerPlugins() {
auto& reg = plugin::PluginRegistry::instance();
reg.registerFrontend(std::make_unique<frontends::JSFrontend>());
reg.registerFrontend(std::make_unique<frontends::PythonFrontend>());
reg.registerFrontend(std::make_unique<frontends::CSharpFrontend>());
reg.registerFrontend(std::make_unique<frontends::GoFrontend>());
reg.registerFrontend(std::make_unique<frontends::RustFrontend>());
reg.registerFrontend(std::make_unique<frontends::PHPFrontend>());
reg.registerFrontend(std::make_unique<frontends::RubyFrontend>());
reg.registerFrontend(std::make_unique<frontends::JavaFrontend>());
reg.registerFrontend(std::make_unique<frontends::EMPLSFrontend>());
reg.registerFrontend(std::make_unique<frontends::SwiftFrontend>());
reg.registerFrontend(std::make_unique<frontends::KotlinFrontend>());
reg.registerBackend(std::make_unique<backends::CppBackend>());
}
registerPlugins() is called once during startup (from main, line
3883):
int main(int argc, char** argv) {
registerPlugins();
// ... rest of CLI ...
}
File-by-file registration
| Frontend | Header included at | Registration line |
|---|---|---|
| JavaScript / TypeScript | MPL_Compiler.cpp:17 |
line 34 |
| Python | line 18 | line 35 |
| C# | line 19 | line 36 |
| Go | line 20 | line 37 |
| Rust | line 21 | line 38 |
| PHP | line 22 | line 39 |
| Ruby | line 23 | line 40 |
| Java | line 24 | line 41 |
| EMPLS | line 25 | line 42 |
| Swift | line 26 | line 43 |
| Kotlin | line 27 | line 44 |
// MPL_Compiler.cpp:17-27
#include "frontends/javascript/JSFrontend.h"
#include "frontends/python/PythonFrontend.h"
#include "frontends/csharp/CSharpFrontend.h"
#include "frontends/go/GoFrontend.h"
#include "frontends/rust/RustFrontend.h"
#include "frontends/php/PHPFrontend.h"
#include "frontends/ruby/RubyFrontend.h"
#include "frontends/java/JavaFrontend.h"
#include "frontends/empls/EMPLSFrontend.h"
#include "frontends/swift/SwiftFrontend.h"
#include "frontends/kotlin/KotlinFrontend.h"
#include "backends/cpp/CppBackend.h"
CMake target wiring
The CMakeLists.txt defines per-frontend translation units so the
compiler sees the JSFrontend.h, PythonFrontend.h, etc., declarations.
The pattern (illustrative, actual layout may vary) is:
add_executable(MPL_Compiler
MPL_Compiler.cpp
frontends/javascript/JSFrontend.h
frontends/javascript/Lexer.cpp
frontends/javascript/Parser.cpp
frontends/javascript/JsStdLib.cpp
frontends/python/PythonFrontend.h
frontends/python/PythonStdLib.h
frontends/csharp/CSharpFrontend.h
frontends/csharp/CSharpFrontend.cpp
frontends/csharp/CSharpStdLib.cpp
# ... etc.
)
The 9 production frontends + 2 experimental (Swift, Kotlin) compile
into the single MPL_Compiler executable. Unity Build batches them in
groups of 25 to reduce compile time.
CLI dispatch
The CLI reads argv[1], looks up the frontend via extension or language
name, parses the source, and dispatches to the backend.
// MPL_Compiler.cpp (illustrative)
int main(int argc, char** argv) {
registerPlugins();
const std::string inputPath = argv[1];
const std::string target = (argc > 2 && std::string(argv[2]) == "--target")
? argv[3] : "cpp";
auto& reg = plugin::PluginRegistry::instance();
// Look up frontend by file extension
const std::string ext = fs::path(inputPath).extension().string();
auto* frontend = reg.getFrontendByExtension(ext);
if (!frontend) {
std::cerr << "No frontend registered for extension: " << ext << "\n";
return 1;
}
// Look up backend
auto* backend = reg.getBackend(target);
if (!backend) {
std::cerr << "No backend registered for target: " << target << "\n";
return 1;
}
// Parse and generate
const std::string source = readFile(inputPath);
auto module = frontend->parse(source, inputPath);
const std::string output = backend->generate(*module);
std::cout << output;
return 0;
}
How to add a 10th language
Step 1 — create the directory
mkdir frontends/mylang/
Step 2 — create the header(s)
// frontends/mylang/MyLangFrontend.h
#pragma once
#include "../../core/plugin/BraceFrontendBase.h"
namespace mpl {
namespace frontends {
class MyLangFrontend : public plugin::BraceFrontendBase {
public:
std::string languageName() const override { return "mylang"; }
std::vector<std::string> fileExtensions() const override { return {".ml"}; }
protected:
std::vector<plugin::BracePrintPattern> printPatterns() const override {
return { {"println", true, true} };
}
bool isComment(const std::string& line) const override {
return startsWith(line, "//");
}
std::shared_ptr<ir::VariableDeclNode> tryParseVarDecl(const std::string& line) override {
std::string l = trim(line);
if (startsWith(l, "let ")) {
size_t eq = l.find('=');
if (eq == std::string::npos) return nullptr;
return makeVar(trim(l.substr(4, eq - 4)), trim(l.substr(eq + 1)), true);
}
return nullptr;
}
};
} // namespace frontends
} // namespace mpl
Step 3 — optionally add a StdLib mapping
// frontends/mylang/MyLangStdLib.h
#pragma once
#include <string>
#include <vector>
#include <optional>
namespace mpl {
namespace frontends {
namespace mylang {
struct StdLibMapping {
std::string functionName;
std::string mplModule;
std::string mplFunction;
};
inline std::optional<StdLibMapping> findStdLibMapping(const std::string& name) {
static const std::vector<StdLibMapping> kMappings = {
{"println", "Console", "WriteLine"},
{"abs", "Math", "Abs"},
{"sqrt", "Math", "Sqrt"},
};
for (const auto& m : kMappings) {
if (m.functionName == name) return m;
}
return std::nullopt;
}
} // namespace mylang
} // namespace frontends
} // namespace mpl
Step 4 — register in MPL_Compiler.cpp
// MPL_Compiler.cpp:17-27 (add the include)
#include "frontends/mylang/MyLangFrontend.h"
// MPL_Compiler.cpp:32-47 (add the registration)
void registerPlugins() {
auto& reg = plugin::PluginRegistry::instance();
reg.registerFrontend(std::make_unique<frontends::JSFrontend>());
// ... existing frontends ...
reg.registerFrontend(std::make_unique<frontends::MyLangFrontend>());
reg.registerBackend(std::make_unique<backends::CppBackend>());
}
Step 5 — add to CMakeLists.txt
add_executable(MPL_Compiler
MPL_Compiler.cpp
# ... existing sources ...
frontends/mylang/MyLangFrontend.h
)
Step 6 — add tests
Create tests/mylang_<feature>_test.cpp and add to CMakeLists.txt's
test definitions.
Step 7 — build & test
cd MPL_Compiler/build
cmake --build . --parallel
ctest -R mylang --output-on-failure
Threading model
PluginRegistry::registerFrontend and getFrontend* are thread-safe.
The CLI uses single-threaded compilation by default but enables
parallel source-file compilation via MPL_Compiler.cpp's
compileFilesParallel worker pool. Each worker calls
getFrontendByExtension once per file.
The mutex (std::mutex mu_) is held only during the map operations
themselves, not during the actual parse() call, so multiple workers
can parse different files in parallel.
Order of operations
The CLI's flow (at a high level):
argv parsing
│
▼
registerPlugins() ← populate PluginRegistry singleton
│
▼
readFile(argv[1]) ← load source text
│
▼
frontend->parse(source, fn) ← PluginRegistry::getFrontendByExtension
│ (or by explicit --lang argument)
▼
[module graph expansion] ← resolve imports, multi-file programs
│ (JS only — see MPL_Compiler.cpp module
│ resolution)
▼
backend->generate(module) ← PluginRegistry::getBackend("cpp")
│
▼
[emit to stdout / --out path]
For multi-file programs (JS modules), registerPlugins() is called
once and the same registry serves every file.
Backend registration
There is currently one registered backend: CppBackend.
// MPL_Compiler.cpp:46
reg.registerBackend(std::make_unique<backends::CppBackend>());
Backends follow the same pattern as frontends — subclass
ILanguageBackend and implement:
// core/plugin/ILanguageBackend.h
class ILanguageBackend {
public:
virtual ~ILanguageBackend() = default;
virtual std::string languageName() const = 0;
virtual std::string generate(const ir::ModuleNode& module) = 0;
};
A second backend (e.g. JavaScriptBackend or WebAssemblyBackend)
would follow the same four-step pattern.
Summary
| Step | File | Lines |
|---|---|---|
| Include the frontend header | MPL_Compiler.cpp |
17-27 |
| Register the frontend | MPL_Compiler.cpp |
32-47 |
Call registerPlugins() from main |
MPL_Compiler.cpp |
3883 |
| CMake target | CMakeLists.txt |
various |
| PluginRegistry | core/plugin/PluginRegistry.h |
12-97 |
| ILanguageFrontend | core/plugin/ILanguageFrontend.h |
15-39 |
| BraceFrontendBase (most frontends inherit) | core/plugin/BraceFrontendBase.h |
29-10539 |
That's the entire registration mechanism: include, register, ship. The registry handles dispatch transparently.