DSL を定義すると、 LSP サーバーの実装を吐き出してくれるツール Langium を試す。
目標
以下の文法の DSL を作る。
person <NAME> {
age: <INTEGER>;
rank: <INTEGER>;
}
<NAME>
: 任意の名前, 半角小文字アルファベットのみ許容<INTEGER>
: 任意の整数
前提
- OS: Arch Linux
- Docker: Docker version 20.10.16, build aa7e414fdc
- 使用する Docker イメージ: node:18
作業用コンテナ起動
docker run -it --rm -v "$(pwd):/work" --workdir /work node:18 bash
Langium のジェネレーターをインストール
npm i -g yo generator-langium
Langium のプロジェクトを作成
# su - node
$ cd /work
$ yo langium
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== Yes
┌─────┐ ─┐
┌───┐ │ ╶─╮ ┌─╮ ╭─╮ ╷ ╷ ╷ ┌─┬─╮
│ ,´ │ ╭─┤ │ │ │ │ │ │ │ │ │ │
│╱ ╰─ ╰─┘ ╵ ╵ ╰─┤ ╵ ╰─╯ ╵ ╵ ╵
` ╶─╯
Welcome to Langium! This tool generates a VS Code extension with a "Hello World" language to get started
quickly. The extension name is an identifier used in the extension marketplace or package registry.
? Your extension name: firststep
The language name is used to identify your language in VS Code. Please provide a name to be shown in the
UI. CamelCase and kebab-case variants will be created and used in different parts of the extension and
language server.
? Your language name: firststep
Source files of your language are identified by their file name extension. You can specify multiple file
extensions separated by commas.
? File extensions: .firststep
create firststep/langium-config.json
create firststep/langium-quickstart.md
create firststep/language-configuration.json
create firststep/package.json
create firststep/tsconfig.json
create firststep/bin/cli
create firststep/src/extension.ts
create firststep/src/cli/cli-util.ts
create firststep/src/cli/generator.ts
create firststep/src/cli/index.ts
create firststep/src/language-server/firststep-module.ts
create firststep/src/language-server/firststep-validator.ts
create firststep/src/language-server/firststep.langium
create firststep/src/language-server/main.ts
create firststep/.vscode/extensions.json
create firststep/.vscode/launch.json
create firststep/.eslintrc.json
create firststep/.vscodeignore
added 173 packages, and audited 174 packages in 23s
27 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
npm notice
npm notice New minor version of npm available! 8.9.0 -> 8.11.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v8.11.0
npm notice Run npm install -g npm@8.11.0 to update!
npm notice
> firststep@0.0.1 langium:generate
> langium generate
Reading config from langium-config.json
src/language-server/firststep.langium:14:10 - This rule is declared but never referenced.
src/language-server/firststep.langium:15:10 - This rule is declared but never referenced.
Writing generated files to /work/firststep/src/language-server/generated
Writing textmate grammar to /work/firststep/syntaxes/firststep.tmLanguage.json
Langium generator finished successfully in 218ms
> firststep@0.0.1 build
> tsc -b tsconfig.json
No change to package.json was detected. No package manager install will be executed.
DSL 開発(文法チェックまで)
grammar コードの実装
以下コマンドで修正をウォッチしながら、src/language-server/firststep.langium
を修正する。
npm run langium:watch
■ src/language-server/firststep.langium
// DSL の文法名
grammar Firststep
// モデル定義。
// 「この DSL ではゼロから複数の Person を定義できる」という定義をしている
entry Model:
(persons+=Person)*;
/**
* 以下形式の文法を定義。
*
* person <NAME> {
* age: <INTEGER>;
* rank: <INTEGER>;
* }
*
* ※ このとき、 AST 上の person ノードは、 `age` と `rank` のパラメーターを持つ。
*/
Person:
'person' name=NAME '{'
'age' ':' age=INTEGER ';'
'rank' ':' rank=INTEGER ';'
'}'
;
// `<NAME>`: 任意の名前, 半角の小文字アルファベットのみ許容
terminal NAME: /[a-z]+/;
// `<INTEGER>`: 任意の整数
terminal INTEGER returns number: /[0-9]+/;
// 空白文字は AST に含めない
hidden terminal WS: /\s+/;
// マルチラインコメント(`/* XXX */`) は AST に含めない
hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//;
// シングルラインコメント(`// XXX`) は AST に含めない
hidden terminal SL_COMMENT: /\/\/[^\n\r]*/;
grammar コードからパーサーコード(TypeScript)の生成
npm run langium:watch
していればすでに生成済みのはずだが、ワンショットで再生成したい場合には以下コマンドを実行。
npm run langium:generate
パーサーコード(TypeScript)のビルド
以下コマンドでビルド。
npm run build
Langium プロジェクト生成時に実装済みの Person, Greeting 文法用のコードになっているため、ビルドに失敗する。
今回自分が実装した Person 文法用の実装に修正しなければならない。
Person, Greeting 文法用のコード削除
src/cli/generator.ts
,
src/language-server/firststep-validator.ts
の削除
generator は、「Greeting 情報を読み込んでコンソールに出力する TypeScript コードを生成する」という実装になっているため、削除。
firststep-validator は、「Person の ID の先頭が大文字でないとだめ」という実装になっているため、削除。
rm src/cli/generator.ts
rm src/language-server/firststep-validator.ts
src/cli/index.ts
の修正
src/cli/generator.ts
を削除したことで、ビルドが失敗するようになるので解消。
diff --git a/firststep/src/cli/index.ts b/firststep/src/cli/index.ts
index eeafcac..4322246 100644--- a/firststep/src/cli/index.ts
+++ b/firststep/src/cli/index.ts
@@ -1,17 +1,9 @@
-import colors from 'colors';
+//import colors from 'colors';
import { Command } from 'commander';-import { Model } from '../language-server/generated/ast';
-import { FirststepLanguageMetaData } from '../language-server/generated/module';
-import { createFirststepServices } from '../language-server/firststep-module';
-import { extractAstNode } from './cli-util';
-import { generateJavaScript } from './generator';
-
-export const generateAction = async (fileName: string, opts: GenerateOptions): Promise<void> => {
- const services = createFirststepServices().Firststep;
- const model = await extractAstNode<Model>(fileName, services);
- const generatedFilePath = generateJavaScript(model, fileName, opts.destination);
- console.log(colors.green(`JavaScript code generated successfully: ${generatedFilePath}`));
-};
+//import { Model } from '../language-server/generated/ast';
+//import { FirststepLanguageMetaData } from '../language-server/generated/module';
+//import { createFirststepServices } from '../language-server/firststep-module';
+//import { extractAstNode } from './cli-util';
export type GenerateOptions = {
destination?: string;@@ -24,13 +16,7 @@ export default function(): void {
// eslint-disable-next-line @typescript-eslint/no-var-requires
.version(require('../../package.json').version);
- const fileExtensions = FirststepLanguageMetaData.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 sourc
e file')- .action(generateAction);
+ // const fileExtensions = FirststepLanguageMetaData.fileExtensions.join(', ');
program.parse(process.argv); }
src/language-server/firststep-module.ts
の修正
src/language-server/firststep-validator.ts
を削除したことで、ビルドが失敗するようになるので解消。
--- a/firststep/src/language-server/firststep-module.ts
+++ b/firststep/src/language-server/firststep-module.ts
@@ -3,14 +3,12 @@ import {
LangiumServices, LangiumSharedServices, Module, PartialLangiumServices
} from 'langium';
import { FirststepGeneratedModule, FirststepGeneratedSharedModule } from './generated/module';-import { FirststepValidationRegistry, FirststepValidator } from './firststep-validator';
/**
* Declaration of custom services - add your own service classes here.
*/
export type FirststepAddedServices = {
validation: {- FirststepValidator: FirststepValidator
}
}
@@ -27,8 +25,6 @@ export type FirststepServices = LangiumServices & FirststepAddedServices
*/
export const FirststepModule: Module<FirststepServices, PartialLangiumServices & FirststepAddedServices>
= {
validation: {- ValidationRegistry: (services) => new FirststepValidationRegistry(services),
- FirststepValidator: () => new FirststepValidator()
} };
パースに成功したかどうかを確認するためのサブコマンドを追加
src/cli/index.ts
に、処理を実装する。
以下実装で、成功したときは「文法チェック OK」と表示し、失敗したときは、パーサーのエラーメッセージを表示するようになる。
diff --git a/firststep/src/cli/index.ts b/firststep/src/cli/index.ts
index 4322246..e0a4230 100644--- a/firststep/src/cli/index.ts
+++ b/firststep/src/cli/index.ts
@@ -1,9 +1,16 @@
-//import colors from 'colors';
+import colors from 'colors';
import { Command } from 'commander';-//import { Model } from '../language-server/generated/ast';
-//import { FirststepLanguageMetaData } from '../language-server/generated/module';
-//import { createFirststepServices } from '../language-server/firststep-module';
-//import { extractAstNode } from './cli-util';
+import { Model } from '../language-server/generated/ast';
+import { FirststepLanguageMetaData } from '../language-server/generated/module';
+import { createFirststepServices } from '../language-server/firststep-module';
+import { extractAstNode } from './cli-util';
+
+export const testAction = async (fileName: string, opts: GenerateOptions): Promise<void> => {
+ const services = createFirststepServices().Firststep;
+ await extractAstNode<Model>(fileName, services);
+ console.log(colors.green(`文法チェック OK`));
+ // ※ extractAstNode から呼ばれる `extractDocument` 内で、 `process.exit(1)` されるので、 try-catch
しない+};
export type GenerateOptions = {
destination?: string;@@ -16,7 +23,12 @@ export default function(): void {
// eslint-disable-next-line @typescript-eslint/no-var-requires
.version(require('../../package.json').version);
- // const fileExtensions = FirststepLanguageMetaData.fileExtensions.join(', ');
+ const fileExtensions = FirststepLanguageMetaData.fileExtensions.join(', ');
+ program
+ .command('test')
+ .argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
+ .description('文法チェック')
+ .action(testAction);
program.parse(process.argv); }
動作確認
パーサーの動作確認
test.firststep
ファイルを作って、先程実装した
test
サブコマンドで確認する。
$ node bin/cli test ./test-ok.firststep
文法チェック OK
$ node bin/cli test ./test-ng.firststep
There are validation errors:
line 1: Expecting keyword '{' but found `000`. [000]
OK.
今回はここまで。
コミット順序は前後するが、ここまでの修正を MiscellaneousStudy/Langium/firststep/firststep - GitHub に格納した。
あとは AST からコード生成などの所望の処理を行う実装をする感じ。
VSCode 拡張機能の動作確認
npm run build
でビルド- Langium プロジェクトを、
~/.vscode/extensions-oss
にコピーして VSCode を起動 - 拡張子が
.fiststep
の空ファイルを作成し、 VSCode で開く - grammar で実装した通りの補完やバリデーションの警告が出ることを確認
0 件のコメント:
コメントを投稿