Initial commit

- basic grammar and references
This commit is contained in:
JanikNex 2023-05-09 18:03:11 +02:00
parent b335fdb03c
commit 40ea38f596
24 changed files with 5168 additions and 0 deletions

13
.eslintrc.json Normal file
View file

@ -0,0 +1,13 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}

9
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"langium.langium-vscode"
]
}

31
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,31 @@
// A launch configuration that launches the extension inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
},
{
"name": "Attach to Language Server",
"type": "node",
"port": 6009,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/out/**/*.js",
"${workspaceFolder}/node_modules/langium"
]
}
]
}

4
.vscodeignore Normal file
View file

@ -0,0 +1,4 @@
.vscode/**
.vscode-test/**
.gitignore
langium-quickstart.md

4
build.sh Normal file
View file

@ -0,0 +1,4 @@
npm run langium:generate
#npm run build
vsce package

12
langium-config.json Normal file
View file

@ -0,0 +1,12 @@
{
"projectName": "ModelModelingLanguage",
"languages": [{
"id": "model-modeling-language",
"grammar": "src/language-server/model-modeling-language.langium",
"fileExtensions": [".mml"],
"textMate": {
"out": "syntaxes/model-modeling-language.tmLanguage.json"
}
}],
"out": "src/language-server/generated"
}

40
langium-quickstart.md Normal file
View file

@ -0,0 +1,40 @@
# Welcome to your Langium VS Code Extension
## What's in the folder
This folder contains all necessary files for your language extension.
* `package.json` - the manifest file in which you declare your language support.
* `language-configuration.json` - the language configuration used in the VS Code editor, defining the tokens that are used for comments and brackets.
* `src/extension.ts` - the main code of the extension, which is responsible for launching a language server and client.
* `src/language-server/model-modeling-language.langium` - the grammar definition of your language.
* `src/language-server/main.ts` - the entry point of the language server process.
* `src/language-server/model-modeling-language-module.ts` - the dependency injection module of your language implementation. Use this to register overridden and added services.
* `src/language-server/model-modeling-language-validator.ts` - an example validator. You should change it to reflect the semantics of your language.
* `src/cli/index.ts` - the entry point of the command line interface (CLI) of your language.
* `src/cli/generator.ts` - the code generator used by the CLI to write output files from DSL documents.
* `src/cli/cli-util.ts` - utility code for the CLI.
## Get up and running straight away
* Run `npm run langium:generate` to generate TypeScript code from the grammar definition.
* Run `npm run build` to compile all TypeScript code.
* Press `F5` to open a new window with your extension loaded.
* Create a new file with a file name suffix matching your language.
* Verify that syntax highlighting, validation, completion etc. are working as expected.
* Run `./bin/cli` to see options for the CLI; `./bin/cli generate <file>` generates code for a given DSL file.
## Make changes
* Run `npm run watch` to have the TypeScript compiler run automatically after every change of the source files.
* Run `npm run langium:watch` to have the Langium generator run automatically afer every change of the grammar declaration.
* You can relaunch the extension from the debug toolbar after making changes to the files listed above.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Install your extension
* To start using your extension with VS Code, copy it into the `<user home>/.vscode/extensions` folder and restart Code.
* To share your extension with the world, read the [VS Code documentation](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) about publishing an extension.
## To Go Further
Documentation about the Langium framework is available at https://langium.org

View file

@ -0,0 +1,30 @@
{
"comments": {
// symbol used for single line comment. Remove this entry if your language does not support line comments
"lineComment": "//",
// symbols used for start and end a block comment. Remove this entry if your language does not support block comments
"blockComment": [ "/*", "*/" ]
},
// symbols used as brackets
"brackets": [
["{", "}"],
["[", "]"],
["(", ")"]
],
// symbols that are auto closed when typing
"autoClosingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
],
// symbols that can be used to surround a selection
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
["\"", "\""],
["'", "'"]
]
}

2230
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

73
package.json Normal file
View file

@ -0,0 +1,73 @@
{
"name": "model-modeling-language",
"displayName": "model-modeling-language",
"description": "Please enter a brief description here",
"version": "0.0.1",
"engines": {
"vscode": "^1.67.0"
},
"categories": [
"Programming Languages"
],
"contributes": {
"languages": [
{
"id": "model-modeling-language",
"aliases": [
"Model modeling language",
"model-modeling-language"
],
"extensions": [
".mml"
],
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "model-modeling-language",
"scopeName": "source.model-modeling-language",
"path": "./syntaxes/model-modeling-language.tmLanguage.json"
}
]
},
"activationEvents": [
"onLanguage:model-modeling-language"
],
"files": [
"bin",
"out",
"src"
],
"bin": {
"model-modeling-language-cli": "./bin/cli"
},
"main": "./out/extension.js",
"scripts": {
"vscode:prepublish": "npm run build && npm run lint",
"build": "tsc -b tsconfig.json",
"watch": "tsc -b tsconfig.json --watch",
"lint": "eslint src --ext ts",
"langium:generate": "langium generate",
"langium:watch": "langium generate --watch"
},
"dependencies": {
"chalk": "~4.1.2",
"chevrotain": "~10.4.2",
"commander": "~10.0.0",
"langium": "~1.1.0",
"vscode-languageclient": "~8.0.2",
"vscode-languageserver": "~8.0.2",
"vscode-uri": "~3.0.7"
},
"devDependencies": {
"@types/node": "~16.18.11",
"@types/vscode": "~1.67.0",
"@typescript-eslint/eslint-plugin": "~5.51.0",
"@typescript-eslint/parser": "~5.51.0",
"esbuild": "^0.17.18",
"eslint": "~8.33.0",
"langium-cli": "~1.1.0",
"typescript": "~4.9.5"
}
}

51
src/cli/cli-util.ts Normal file
View file

@ -0,0 +1,51 @@
import chalk from 'chalk';
import path from 'path';
import fs from 'fs';
import { AstNode, LangiumDocument, LangiumServices } from 'langium';
import { URI } from 'vscode-uri';
export async function extractDocument(fileName: string, services: LangiumServices): Promise<LangiumDocument> {
const extensions = services.LanguageMetaData.fileExtensions;
if (!extensions.includes(path.extname(fileName))) {
console.error(chalk.yellow(`Please choose a file with one of these extensions: ${extensions}.`));
process.exit(1);
}
if (!fs.existsSync(fileName)) {
console.error(chalk.red(`File ${fileName} does not exist.`));
process.exit(1);
}
const document = services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName)));
await services.shared.workspace.DocumentBuilder.build([document], { validationChecks: 'all' });
const validationErrors = (document.diagnostics ?? []).filter(e => e.severity === 1);
if (validationErrors.length > 0) {
console.error(chalk.red('There are validation errors:'));
for (const validationError of validationErrors) {
console.error(chalk.red(
`line ${validationError.range.start.line + 1}: ${validationError.message} [${document.textDocument.getText(validationError.range)}]`
));
}
process.exit(1);
}
return document;
}
export async function extractAstNode<T extends AstNode>(fileName: string, services: LangiumServices): Promise<T> {
return (await extractDocument(fileName, services)).parseResult?.value as T;
}
interface FilePathData {
destination: string,
name: string
}
export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathData {
filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, '');
return {
destination: destination ?? path.join(path.dirname(filePath), 'generated'),
name: path.basename(filePath)
};
}

19
src/cli/generator.ts Normal file
View file

@ -0,0 +1,19 @@
import fs from 'fs';
import { CompositeGeneratorNode, NL, toString } from 'langium';
import path from 'path';
import { Model } from '../language-server/generated/ast';
import { extractDestinationAndName } from './cli-util';
export function generateJavaScript(model: Model, filePath: string, destination: string | undefined): string {
const data = extractDestinationAndName(filePath, destination);
const generatedFilePath = `${path.join(data.destination, data.name)}.js`;
const fileNode = new CompositeGeneratorNode();
fileNode.append('"use strict";', NL, NL);
if (!fs.existsSync(data.destination)) {
fs.mkdirSync(data.destination, { recursive: true });
}
fs.writeFileSync(generatedFilePath, toString(fileNode));
return generatedFilePath;
}

37
src/cli/index.ts Normal file
View file

@ -0,0 +1,37 @@
import chalk from 'chalk';
import { Command } from 'commander';
import { Model } from '../language-server/generated/ast';
import { ModelModelingLanguageLanguageMetaData } from '../language-server/generated/module';
import { createModelModelingLanguageServices } from '../language-server/model-modeling-language-module';
import { extractAstNode } from './cli-util';
import { generateJavaScript } from './generator';
import { NodeFileSystem } from 'langium/node';
export const generateAction = async (fileName: string, opts: GenerateOptions): Promise<void> => {
const services = createModelModelingLanguageServices(NodeFileSystem).ModelModelingLanguage;
const model = await extractAstNode<Model>(fileName, services);
const generatedFilePath = generateJavaScript(model, fileName, opts.destination);
console.log(chalk.green(`JavaScript code generated successfully: ${generatedFilePath}`));
};
export type GenerateOptions = {
destination?: string;
}
export default function(): void {
const program = new Command();
program
// eslint-disable-next-line @typescript-eslint/no-var-requires
.version(require('../../package.json').version);
const fileExtensions = ModelModelingLanguageLanguageMetaData.fileExtensions.join(', ');
program
.command('generate')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-d, --destination <dir>', 'destination directory of generating')
.description('generates JavaScript code that prints "Hello, {name}!" for each greeting in a source file')
.action(generateAction);
program.parse(process.argv);
}

59
src/extension.ts Normal file
View file

@ -0,0 +1,59 @@
import * as vscode from 'vscode';
import * as path from 'path';
import {
LanguageClient, LanguageClientOptions, ServerOptions, TransportKind
} from 'vscode-languageclient/node';
let client: LanguageClient;
// This function is called when the extension is activated.
export function activate(context: vscode.ExtensionContext): void {
client = startLanguageClient(context);
}
// This function is called when the extension is deactivated.
export function deactivate(): Thenable<void> | undefined {
if (client) {
return client.stop();
}
return undefined;
}
function startLanguageClient(context: vscode.ExtensionContext): LanguageClient {
const serverModule = context.asAbsolutePath(path.join('out', 'language-server', 'main'));
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging.
// By setting `process.env.DEBUG_BREAK` to a truthy value, the language server will wait until a debugger is attached.
const debugOptions = { execArgv: ['--nolazy', `--inspect${process.env.DEBUG_BREAK ? '-brk' : ''}=${process.env.DEBUG_SOCKET || '6009'}`] };
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
};
const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.mml');
context.subscriptions.push(fileSystemWatcher);
// Options to control the language client
const clientOptions: LanguageClientOptions = {
documentSelector: [{ scheme: 'file', language: 'model-modeling-language' }],
synchronize: {
// Notify the server about file changes to files contained in the workspace
fileEvents: fileSystemWatcher
}
};
// Create the language client and start the client.
const client = new LanguageClient(
'model-modeling-language',
'Model modeling language',
serverOptions,
clientOptions
);
// Start the client. This will also launch the server
client.start();
return client;
}

View file

@ -0,0 +1,385 @@
/******************************************************************************
* This file was generated by langium-cli 1.1.0.
* DO NOT EDIT MANUALLY!
******************************************************************************/
/* eslint-disable */
import { AstNode, AbstractAstReflection, Reference, ReferenceInfo, TypeMetaData } from 'langium';
export type AbstractElement = Class | Enum | Interface;
export const AbstractElement = 'AbstractElement';
export function isAbstractElement(item: unknown): item is AbstractElement {
return reflection.isInstance(item, AbstractElement);
}
export type BoolVal = boolean;
export type DataType = 'bool' | 'double' | 'float' | 'int' | 'string';
export type Statement = Attribute | CReference;
export const Statement = 'Statement';
export function isStatement(item: unknown): item is Statement {
return reflection.isInstance(item, Statement);
}
export interface Attribute extends AstNode {
readonly $container: Class | Interface;
readonly $type: 'Attribute';
defaultValue?: BoolVal | number | string
modifiers?: AttributeModifiers
name: string
type: DataType
}
export const Attribute = 'Attribute';
export function isAttribute(item: unknown): item is Attribute {
return reflection.isInstance(item, Attribute);
}
export interface AttributeModifiers extends AstNode {
readonly $container: Attribute;
readonly $type: 'AttributeModifiers';
derived: boolean
id: boolean
ordered: boolean
readonly: boolean
transient: boolean
unique: boolean
unsettable: boolean
volatile: boolean
}
export const AttributeModifiers = 'AttributeModifiers';
export function isAttributeModifiers(item: unknown): item is AttributeModifiers {
return reflection.isInstance(item, AttributeModifiers);
}
export interface Class extends AstNode {
readonly $container: Package;
readonly $type: 'Class';
abstract: boolean
body: Array<Statement>
extendedClasses: Array<Reference<Class>>
implementedInterfaces: Array<Reference<Interface>>
name: string
}
export const Class = 'Class';
export function isClass(item: unknown): item is Class {
return reflection.isInstance(item, Class);
}
export interface CReference extends AstNode {
readonly $container: Class | Interface;
readonly $type: 'CReference';
modifiers?: ReferenceModifiers
multiplicity: Multiplicity
name: string
opposite?: OppositeAnnotation
type: Reference<Class>
}
export const CReference = 'CReference';
export function isCReference(item: unknown): item is CReference {
return reflection.isInstance(item, CReference);
}
export interface Enum extends AstNode {
readonly $container: Package;
readonly $type: 'Enum' | 'EnumEntry';
name: string
}
export const Enum = 'Enum';
export function isEnum(item: unknown): item is Enum {
return reflection.isInstance(item, Enum);
}
export interface Import extends AstNode {
readonly $container: Model;
readonly $type: 'Import';
aliases: Array<ImportAlias>
target: string
}
export const Import = 'Import';
export function isImport(item: unknown): item is Import {
return reflection.isInstance(item, Import);
}
export interface ImportAlias extends AstNode {
readonly $container: Import;
readonly $type: 'ImportAlias';
alias: string
name: string
}
export const ImportAlias = 'ImportAlias';
export function isImportAlias(item: unknown): item is ImportAlias {
return reflection.isInstance(item, ImportAlias);
}
export interface Interface extends AstNode {
readonly $container: Package;
readonly $type: 'Interface';
abstract: boolean
body: Array<Statement>
extendedInterfaces: Array<Reference<Interface>>
name: string
}
export const Interface = 'Interface';
export function isInterface(item: unknown): item is Interface {
return reflection.isInstance(item, Interface);
}
export interface Model extends AstNode {
readonly $type: 'Model';
imports: Array<Import>
packages: Array<Package>
}
export const Model = 'Model';
export function isModel(item: unknown): item is Model {
return reflection.isInstance(item, Model);
}
export interface Multiplicity extends AstNode {
readonly $container: CReference;
readonly $type: 'Multiplicity';
mult?: number | string
upperMult?: number | string
}
export const Multiplicity = 'Multiplicity';
export function isMultiplicity(item: unknown): item is Multiplicity {
return reflection.isInstance(item, Multiplicity);
}
export interface OppositeAnnotation extends AstNode {
readonly $container: CReference;
readonly $type: 'OppositeAnnotation';
reference: Reference<CReference>
}
export const OppositeAnnotation = 'OppositeAnnotation';
export function isOppositeAnnotation(item: unknown): item is OppositeAnnotation {
return reflection.isInstance(item, OppositeAnnotation);
}
export interface Package extends AstNode {
readonly $container: Model | Package;
readonly $type: 'Package';
body: Array<AbstractElement>
name: string
subPackages: Array<Package>
}
export const Package = 'Package';
export function isPackage(item: unknown): item is Package {
return reflection.isInstance(item, Package);
}
export interface ReferenceModifiers extends AstNode {
readonly $container: CReference;
readonly $type: 'ReferenceModifiers';
derived: boolean
ordered: boolean
readonly: boolean
resolve: boolean
transient: boolean
unique: boolean
unsettable: boolean
volatile: boolean
}
export const ReferenceModifiers = 'ReferenceModifiers';
export function isReferenceModifiers(item: unknown): item is ReferenceModifiers {
return reflection.isInstance(item, ReferenceModifiers);
}
export interface EnumEntry extends Enum {
readonly $container: Package;
readonly $type: 'EnumEntry';
name: string
value?: number | string
}
export const EnumEntry = 'EnumEntry';
export function isEnumEntry(item: unknown): item is EnumEntry {
return reflection.isInstance(item, EnumEntry);
}
export interface ModelModelingLanguageAstType {
AbstractElement: AbstractElement
Attribute: Attribute
AttributeModifiers: AttributeModifiers
CReference: CReference
Class: Class
Enum: Enum
EnumEntry: EnumEntry
Import: Import
ImportAlias: ImportAlias
Interface: Interface
Model: Model
Multiplicity: Multiplicity
OppositeAnnotation: OppositeAnnotation
Package: Package
ReferenceModifiers: ReferenceModifiers
Statement: Statement
}
export class ModelModelingLanguageAstReflection extends AbstractAstReflection {
getAllTypes(): string[] {
return ['AbstractElement', 'Attribute', 'AttributeModifiers', 'CReference', 'Class', 'Enum', 'EnumEntry', 'Import', 'ImportAlias', 'Interface', 'Model', 'Multiplicity', 'OppositeAnnotation', 'Package', 'ReferenceModifiers', 'Statement'];
}
protected override computeIsSubtype(subtype: string, supertype: string): boolean {
switch (subtype) {
case Attribute:
case CReference: {
return this.isSubtype(Statement, supertype);
}
case Class:
case Enum:
case Interface: {
return this.isSubtype(AbstractElement, supertype);
}
case EnumEntry: {
return this.isSubtype(Enum, supertype);
}
default: {
return false;
}
}
}
getReferenceType(refInfo: ReferenceInfo): string {
const referenceId = `${refInfo.container.$type}:${refInfo.property}`;
switch (referenceId) {
case 'Class:extendedClasses':
case 'CReference:type': {
return Class;
}
case 'Class:implementedInterfaces':
case 'Interface:extendedInterfaces': {
return Interface;
}
case 'OppositeAnnotation:reference': {
return CReference;
}
default: {
throw new Error(`${referenceId} is not a valid reference id.`);
}
}
}
getTypeMetaData(type: string): TypeMetaData {
switch (type) {
case 'AttributeModifiers': {
return {
name: 'AttributeModifiers',
mandatory: [
{ name: 'derived', type: 'boolean' },
{ name: 'id', type: 'boolean' },
{ name: 'ordered', type: 'boolean' },
{ name: 'readonly', type: 'boolean' },
{ name: 'transient', type: 'boolean' },
{ name: 'unique', type: 'boolean' },
{ name: 'unsettable', type: 'boolean' },
{ name: 'volatile', type: 'boolean' }
]
};
}
case 'Class': {
return {
name: 'Class',
mandatory: [
{ name: 'abstract', type: 'boolean' },
{ name: 'body', type: 'array' },
{ name: 'extendedClasses', type: 'array' },
{ name: 'implementedInterfaces', type: 'array' }
]
};
}
case 'Import': {
return {
name: 'Import',
mandatory: [
{ name: 'aliases', type: 'array' }
]
};
}
case 'Interface': {
return {
name: 'Interface',
mandatory: [
{ name: 'abstract', type: 'boolean' },
{ name: 'body', type: 'array' },
{ name: 'extendedInterfaces', type: 'array' }
]
};
}
case 'Model': {
return {
name: 'Model',
mandatory: [
{ name: 'imports', type: 'array' },
{ name: 'packages', type: 'array' }
]
};
}
case 'Package': {
return {
name: 'Package',
mandatory: [
{ name: 'body', type: 'array' },
{ name: 'subPackages', type: 'array' }
]
};
}
case 'ReferenceModifiers': {
return {
name: 'ReferenceModifiers',
mandatory: [
{ name: 'derived', type: 'boolean' },
{ name: 'ordered', type: 'boolean' },
{ name: 'readonly', type: 'boolean' },
{ name: 'resolve', type: 'boolean' },
{ name: 'transient', type: 'boolean' },
{ name: 'unique', type: 'boolean' },
{ name: 'unsettable', type: 'boolean' },
{ name: 'volatile', type: 'boolean' }
]
};
}
default: {
return {
name: type,
mandatory: []
};
}
}
}
}
export const reflection = new ModelModelingLanguageAstReflection();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
/******************************************************************************
* This file was generated by langium-cli 1.1.0.
* DO NOT EDIT MANUALLY!
******************************************************************************/
import { LangiumGeneratedServices, LangiumGeneratedSharedServices, LangiumSharedServices, LangiumServices, LanguageMetaData, Module } from 'langium';
import { ModelModelingLanguageAstReflection } from './ast';
import { ModelModelingLanguageGrammar } from './grammar';
export const ModelModelingLanguageLanguageMetaData: LanguageMetaData = {
languageId: 'model-modeling-language',
fileExtensions: ['.mml'],
caseInsensitive: false
};
export const ModelModelingLanguageGeneratedSharedModule: Module<LangiumSharedServices, LangiumGeneratedSharedServices> = {
AstReflection: () => new ModelModelingLanguageAstReflection()
};
export const ModelModelingLanguageGeneratedModule: Module<LangiumServices, LangiumGeneratedServices> = {
Grammar: () => ModelModelingLanguageGrammar(),
LanguageMetaData: () => ModelModelingLanguageLanguageMetaData,
parser: {}
};

View file

@ -0,0 +1,13 @@
import { startLanguageServer } from 'langium';
import { NodeFileSystem } from 'langium/node';
import { createConnection, ProposedFeatures } from 'vscode-languageserver/node';
import { createModelModelingLanguageServices } from './model-modeling-language-module';
// Create a connection to the client
const connection = createConnection(ProposedFeatures.all);
// Inject the shared services and language-specific services
const { shared } = createModelModelingLanguageServices({ connection, ...NodeFileSystem });
// Start the language server with the shared services
startLanguageServer(shared);

View file

@ -0,0 +1,75 @@
import {
createDefaultModule,
createDefaultSharedModule,
DefaultSharedModuleContext,
inject,
LangiumServices,
LangiumSharedServices,
Module,
PartialLangiumServices
} from 'langium';
import {ModelModelingLanguageGeneratedModule, ModelModelingLanguageGeneratedSharedModule} from './generated/module';
import {ModelModelingLanguageValidator, registerValidationChecks} from './model-modeling-language-validator';
import {ModelModelingLanguageScopeComputation} from "./model-modeling-language-scope-computation";
/**
* Declaration of custom services - add your own service classes here.
*/
export type ModelModelingLanguageAddedServices = {
validation: {
ModelModelingLanguageValidator: ModelModelingLanguageValidator
}
}
/**
* Union of Langium default services and your custom services - use this as constructor parameter
* of custom service classes.
*/
export type ModelModelingLanguageServices = LangiumServices & ModelModelingLanguageAddedServices
/**
* Dependency injection module that overrides Langium default services and contributes the
* declared custom services. The Langium defaults can be partially specified to override only
* selected services, while the custom services must be fully specified.
*/
export const ModelModelingLanguageModule: Module<ModelModelingLanguageServices, PartialLangiumServices & ModelModelingLanguageAddedServices> = {
validation: {
ModelModelingLanguageValidator: () => new ModelModelingLanguageValidator()
},
references: {
ScopeComputation: (services) => new ModelModelingLanguageScopeComputation(services)
}
};
/**
* Create the full set of services required by Langium.
*
* First inject the shared services by merging two modules:
* - Langium default shared services
* - Services generated by langium-cli
*
* Then inject the language-specific services by merging three modules:
* - Langium default language-specific services
* - Services generated by langium-cli
* - Services specified in this file
*
* @param context Optional module context with the LSP connection
* @returns An object wrapping the shared services and the language-specific services
*/
export function createModelModelingLanguageServices(context: DefaultSharedModuleContext): {
shared: LangiumSharedServices,
ModelModelingLanguage: ModelModelingLanguageServices
} {
const shared = inject(
createDefaultSharedModule(context),
ModelModelingLanguageGeneratedSharedModule
);
const ModelModelingLanguage = inject(
createDefaultModule({shared}),
ModelModelingLanguageGeneratedModule,
ModelModelingLanguageModule
);
shared.ServiceRegistry.register(ModelModelingLanguage);
registerValidationChecks(ModelModelingLanguage);
return {shared, ModelModelingLanguage};
}

View file

@ -0,0 +1,49 @@
import {AstNode, AstNodeDescription, DefaultScopeComputation, LangiumDocument, streamAllContents} from "langium";
import {isClass, isCReference, isInterface, isPackage} from "./generated/ast";
export class ModelModelingLanguageScopeComputation extends DefaultScopeComputation {
override async computeExports(document: LangiumDocument): Promise<AstNodeDescription[]> {
const exportedDescriptions: AstNodeDescription[] = [];
for (const childNode of streamAllContents(document.parseResult.value)) {
if (isCReference(childNode)) {
const fullyQualifiedName = this.getQualifiedRefName(childNode, childNode.name);
// `descriptions` is our `AstNodeDescriptionProvider` defined in `DefaultScopeComputation`
// It allows us to easily create descriptions that point to elements using a name.
exportedDescriptions.push(this.descriptions.createDescription(childNode, fullyQualifiedName, document));
} else if (isClass(childNode) || isInterface(childNode)) {
const fullyQualifiedName = this.getQualifiedClassName(childNode, childNode.name);
// `descriptions` is our `AstNodeDescriptionProvider` defined in `DefaultScopeComputation`
// It allows us to easily create descriptions that point to elements using a name.
exportedDescriptions.push(this.descriptions.createDescription(childNode, fullyQualifiedName, document));
}
}
return exportedDescriptions;
}
private getQualifiedRefName(node: AstNode, name: string): string {
let parent: AstNode | undefined = node.$container;
if (isClass(parent)) {
name = `${parent.name}::${name}`;
parent = parent.$container;
}
while (isPackage(parent)) {
// Iteratively prepend the name of the parent namespace
// This allows us to work with nested namespaces
name = `${parent.name}.${name}`;
parent = parent.$container;
}
return name;
}
private getQualifiedClassName(node: AstNode, name: string): string {
let parent: AstNode | undefined = node.$container;
while (isPackage(parent)) {
// Iteratively prepend the name of the parent namespace
// This allows us to work with nested namespaces
name = `${parent.name}.${name}`;
parent = parent.$container;
}
return name;
}
}

View file

@ -0,0 +1,31 @@
import { ValidationChecks } from 'langium';
import { ModelModelingLanguageAstType} from './generated/ast';
import type { ModelModelingLanguageServices } from './model-modeling-language-module';
/**
* Register custom validation checks.
*/
export function registerValidationChecks(services: ModelModelingLanguageServices) {
const registry = services.validation.ValidationRegistry;
const validator = services.validation.ModelModelingLanguageValidator;
const checks: ValidationChecks<ModelModelingLanguageAstType> = {
//Person: validator.checkPersonStartsWithCapital
};
registry.register(checks, validator);
}
/**
* Implementation of custom validations.
*/
export class ModelModelingLanguageValidator {
/* checkPersonStartsWithCapital(person: Person, accept: ValidationAcceptor): void {
if (person.name) {
const firstChar = person.name.substring(0, 1);
if (firstChar.toUpperCase() !== firstChar) {
accept('warning', 'Person name should start with a capital.', { node: person, property: 'name' });
}
}
}*/
}

View file

@ -0,0 +1,103 @@
grammar ModelModelingLanguage
entry Model:
(imports+=Import | packages+=Package)*;
DataType returns string:
'int' | 'string' | 'bool' | 'double' | 'float';
BoolVal returns boolean:
'true' | 'false';
Import:
'import' target=STRING ('using' aliases+=ImportAlias (',' aliases+=ImportAlias)*)? ';';
ImportAlias:
name=QNAME 'as' alias=ID;
Multiplicity:
('[' (mult=NUM | mult=MULTIPLICITYSYMBOL) ('..' (upperMult=NUM | upperMult=MULTIPLICITYSYMBOL))? ']')?;
Package:
'package' name=ID '{' (subPackages+=Package | body+=AbstractElement)* '}';
AbstractElement:
Class | Interface | Enum;
fragment ClassModifier:
(abstract?='abstract')?;
fragment ClassBody:
'{'
(body+=Statement)*
'}';
fragment ClassExtension:
('extends' (extendedClasses+=[Class:ID] | extendedClasses+=[Class:QNAME]) (',' (extendedClasses+=[Class:ID] | extendedClasses+=[Class:QNAME]))*)?;
fragment InterfaceExtension:
('extends' (extendedInterfaces+=[Interface:ID] | extendedInterfaces+=[Interface:QNAME]) (',' (extendedInterfaces+=[Interface:ID] | extendedInterfaces+=[Interface:QNAME]))*)?;
fragment ClassImplements:
('implements' (implementedInterfaces+=[Interface:ID] | implementedInterfaces+=[Interface:QNAME]) (',' (implementedInterfaces+=[Interface:ID] | implementedInterfaces+=[Interface:QNAME]))*)?;
Class:
ClassModifier 'class' name=ID ClassExtension ClassImplements ClassBody;
Interface:
ClassModifier 'interface' name=ID InterfaceExtension ClassBody;
Statement:
Attribute | CReference;
AttributeModifiers:
readonly?='readonly' &
volatile?='volatile' &
transient?='transient' &
unsettable?='unsettable' &
derived?='derived' &
unique?='unique' &
ordered?='ordered' &
id?='id';
Attribute:
'attribute' type=DataType name=ID ('=' (defaultValue=STRING | defaultValue=NUM | defaultValue=DOUBLE | defaultValue=BoolVal))? ('{'modifiers=AttributeModifiers'}')?';';
ReferenceModifiers:
readonly?='readonly' &
volatile?='volatile' &
transient?='transient' &
unsettable?='unsettable' &
derived?='derived' &
unique?='unique' &
ordered?='ordered' &
resolve?='resolve';
OppositeAnnotation:
'@opposite' reference=[CReference:FQNAME];
CReference:
(opposite=OppositeAnnotation)? 'reference' (type=[Class:QNAME] | type=[Class:ID]) multiplicity=Multiplicity name=ID ('{' modifiers=ReferenceModifiers '}')?';';
Enum:
'enum' name=ID '{'
(EnumEntry ',')*
EnumEntry
'}';
EnumEntry:
name=ID | name=ID '=' (value=STRING | value=NUM);
hidden terminal WS: /\s+/;
terminal MULTIPLICITYSYMBOL: /[\*?+]/;
terminal DOUBLE returns number: NUM '.' NUM;
terminal NUM returns number: /[0-9]+/;
terminal STRING returns string: /"[^"]*"/;
terminal FQNAME returns string: QNAME '::' ID;
terminal QNAME returns string: ID ('.' ID)+;
terminal ID returns string: /[a-zA-Z_][\w_]*/;
hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
hidden terminal SL_COMMENT: /[^:]\/\/[^\n\r]*/;

View file

@ -0,0 +1,61 @@
{
"name": "model-modeling-language",
"scopeName": "source.model-modeling-language",
"fileTypes": [
".mml"
],
"patterns": [
{
"include": "#comments"
},
{
"name": "keyword.control.model-modeling-language",
"match": "\\b(abstract|as|attribute|bool|class|derived|double|enum|extends|false|float|id|implements|import|int|interface|ordered|package|readonly|reference|resolve|string|transient|true|unique|unsettable|using|volatile)\\b|\\B(@opposite)\\b"
},
{
"name": "string.quoted.double.model-modeling-language",
"begin": "\"",
"end": "\"",
"patterns": [
{
"include": "#string-character-escape"
}
]
}
],
"repository": {
"comments": {
"patterns": [
{
"name": "comment.block.model-modeling-language",
"begin": "/\\*",
"beginCaptures": {
"0": {
"name": "punctuation.definition.comment.model-modeling-language"
}
},
"end": "\\*/",
"endCaptures": {
"0": {
"name": "punctuation.definition.comment.model-modeling-language"
}
}
},
{
"begin": "[^:]//",
"beginCaptures": {
"1": {
"name": "punctuation.whitespace.comment.leading.model-modeling-language"
}
},
"end": "(?=$)",
"name": "comment.line.model-modeling-language"
}
]
},
"string-character-escape": {
"name": "constant.character.escape.model-modeling-language",
"match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)"
}
}
}

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"lib": ["ESNext"],
"sourceMap": true,
"outDir": "out",
"strict": true,
"noUnusedLocals": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"out",
"node_modules"
]
}