Configuration Services¶
Pegium configures language behavior through explicit service objects. Instead of hiding language wiring behind a generated container, it uses one shared service container for runtime-wide concerns and one language service container per registered language.
For most projects, the entry point is:
auto services = pegium::services::makeDefaultServices(
sharedServices, "my-language");
services->parser = std::make_unique<const my::parser::MyParser>(*services);
makeDefaultServices(...) gives you a complete baseline: default core
services, default LSP services, and the languageId already assigned. From
there, you usually add your parser and replace only the pieces that are truly
language-specific.
Shared services¶
SharedServices owns the runtime pieces reused by every language registered in
the same process.
That includes:
- the service registry and AST reflection
- shared workspace services such as documents, index manager, document builder, workspace lock, and workspace manager
- shared LSP runtime services such as text documents, the language server, the document update handler, and the fuzzy matcher
These services are the right place for concerns that must stay consistent across the whole workspace or the whole language-server process.
Language-specific services¶
Each language gets its own Services object. It extends the core language
services with services.lsp, so one container owns both the semantic layer and
the editor-facing layer for that language.
This is where you configure:
- the parser
- name, scope, and linking services
- validation services
- language-level workspace helpers
- LSP providers such as formatter, hover, rename, or completion
Overriding services¶
The most common customization style is to keep the default graph and replace individual services in place:
auto services = pegium::services::makeDefaultServices(
sharedServices, "my-language");
services->parser = std::make_unique<const my::parser::MyParser>(*services);
services->references.scopeProvider =
std::make_unique<references::MyScopeProvider>(*services);
services->validation.validationRegistry =
std::make_unique<validation::MyValidationRegistry>(*services);
services->lsp.formatter = std::make_unique<lsp::MyFormatter>(*services);
This explicit style is one of Pegium's main architectural choices: the wiring is visible in ordinary C++ code, so it is easy to understand what the language actually depends on.
Adding your own services¶
Not every piece of custom logic needs to become a service. If some logic has no dependency on the rest of the framework, a plain function or helper class is often enough.
When the code really depends on other Pegium services, derive your own service
container from pegium::Services and let makeDefaultServices<...>(...)
return that derived type:
struct MyServices : pegium::Services {
explicit MyServices(const pegium::SharedServices &shared)
: pegium::Services(shared) {}
struct {
std::unique_ptr<MySummaryService> summaryService;
} app;
};
auto services = pegium::services::makeDefaultServices<MyServices>(
sharedServices, "my-language");
services->app.summaryService =
std::make_unique<MySummaryService>(*services);
That pattern keeps application-specific services close to the language service container without forcing you to replace the framework defaults.