Skip to content

Latest commit

 

History

History
127 lines (92 loc) · 5.73 KB

File metadata and controls

127 lines (92 loc) · 5.73 KB

Typir-Langium: Typir integration for Langium

Typir-Langium is a framework for type checking of languages developed with Langium, the language workbench for developing textual domain-specific languages (DSLs) in the web.

Typir-Langium depends on Typir, the stand-alone library for type systems and type checking for software languages in the web, independent from any language workbench. Typir-Langium is a dedicated binding of Typir for languages and DSLs which are developed with Langium.

Installation

npm install typir-langium

Overview

Typir-Langium provides all features of Typir and adds some specifics for Langium projects:

  • Integration of Typir into the lifecycle of Langium, including:
    • For validations, you don't need to traverse the AST on your own, since Typir-Langium hooks into Langium's traversal during the validation phase.
    • Types which depend on AstNodes (e.g. user-defined functions or classes) are created and deleted according to Langium's build process.
  • Customizations of implementations for some Typir services, e.g. caches use Langium's DocumentCache
  • Additional APIs to register inference rules and validation rules in the usual Langium style (see below)

For an overview about the core features of Typir with a simple application example, see the root README.md of the Typir project.

Important design decision for Typir-Langium: Typir-Langium does not depend on langium/lsp, i.e. Typir-Langium can be used even for Langium-based DSLs which don't use LSP.

Getting started

Integrate Typir as additional Langium service into your DSL (<MyDSLSpecifics> is explained later):

export type MyDSLAddedServices = {
    // ...
    typir: TypirLangiumServices<MyDSLSpecifics>,
    // ...
}

In case of a multi-language project, this approach enables you to manage multiple type systems in parallel by having typir1: TypirLangiumServices, typir2: TypirLangiumServices and so on.

The Typir services are created in your module in this way:

{
  // ...
  typir: () => createTypirLangiumServices(shared, reflection, new MyDSLTypeSystem(), { /* customize Typir services here */ }),
  // ...
}

After creating the Langium services (which contain the Typir serivces now) and storing them in a variable like langiumServices, the Typir services need to be initialized with initializeLangiumTypirServices(langiumServices, langiumServices.typir).

The actual type system for your Langium-based language is defined as an implementation of the interface LangiumTypeSystemDefinition:

export class MyDSLTypeSystem implements LangiumTypeSystemDefinition<MyDSLSpecifics> {
    onInitialize(typir: TypirLangiumServices<MyDSLSpecifics>): void {
      // define constant types and rules for conversion, inference and validation here
    }

    onNewAstNode(languageNode: AstNode, typir: TypirLangiumServices<MyDSLSpecifics>): void {
      // define types and their rules which depend on the current AST respectively the given AstNode (as parsed by Langium from programs written by users of your language) here
    }
}

<MyDSLSpecifics> is used to inform Typir-Langium about the generated DSL-specific TypeScript-types, which describe the current, DSL-specific AST:

export interface MyDSLSpecifics extends TypirLangiumSpecifics {
    AstTypes: MyDSLAstType; // all AST types from the generated `ast.ts`
    // ... more could be customized here ...
}

Additional APIs

Beyond the APIs inherited from Typir core, Typir-Langium provides some additional APIs to ease type checking with Typir in Langium projects.

This includes an API to register validation rules for AstNode.$types, which is similar to the API of Langium for registering validation checks. Here is an excerpt from the LOX example:

typir.validation.Collector.addValidationRulesForAstNodes({
    IfStatement: (node /* is of type IfStatement */, accept) => typir.validation.Constraints.ensureNodeIsAssignable(node.condition, typeBool, accept,
            () => ({ message: "Conditions need to be evaluated to 'boolean'.", languageProperty: 'condition' })),
    VariableDeclaration: ... ,
    // ...
});

All properties of DiagnosticInfo from Langium are supported in Typir-Langium when creating validation issues. For example, this includes data to register code actions for validation issues which are created by Typir-Langium (see an example in LOX with test cases in lox-type-checking-operators.test.ts). Note that the properties node, property, and index are named languageNode, languageProperty, and languageIndex in Typir and these names are used in Typir-Langium as well.

In similar way, it is possible to register inference rules for AstNode.$types, as demonstrated in the LOX example:

typir.Inference.addInferenceRulesForAstNodes({
    // ...
    VariableDeclaration: (languageNode /* is of type VariableDeclaration */) => {
        if (languageNode.type) {
            return languageNode.type; // the user declared this variable with a type
        } else if (languageNode.value) {
            return languageNode.value; // the user didn't declare a type for this variable => do type inference of the assigned value instead!
        } else {
            return InferenceRuleNotApplicable; // this case is impossible, there is a validation in the Langium LOX validator for this case
        }
    },
    Parameter: (languageNode /* is of type Parameter */) => languageNode.type,
    // ...
});

Examples

Look at the examples in the examples/ folder of the repo (here). There we have some demo projects for you to get started.

License

MIT License