前回 の続き。 AST からコード生成を行う。
目標
以下の定義を元に、
person mikoto {
age: 18;
rank: 1;
}
person makoto {
age: 19;
rank: 2;
}
person mokoto {
age: 20;
rank: 3;
}
以下のコードを生成する。
export type Person = {
age : number;
rank : number;
}
const mikoto : Person = {
age: 18,
rank: 1
}
const makoto : Person = {
age: 19,
rank: 2
}
const mokoto : Person = {
age: 20,
rank: 3
}
export const allPersons = [mikoto, makoto, mokoto];
前提
- 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
DLS 開発(コード生成まで)
コード生成機能実装
src/cli/generator.ts
を作成し、「person
定義ごとに定数を作り、全 person
が含まれた配列を作成」というコードを生成する実装を行う。
■ src/cli/generator.ts
import fs from 'fs';
import { CompositeGeneratorNode, NL, processGeneratorNode } from 'langium';
import path from 'path';
import { Model } from '../language-server/generated/ast';
import { extractDestinationAndName } from './cli-util';
// Person 型の定義
const TYPE_PERSON = `export type Person = {
age : number;
rank : number;
}`;
export function generateTypeScript(model: Model, filePath: string, destination: string | undefined): string {
const data = extractDestinationAndName(filePath, destination);
const generatedFilePath = `${path.join(data.destination, data.name)}.ts`;
// これから生成するファイルの内容を表すインスタンスを生成
const fileNode = new CompositeGeneratorNode();
// 型定義コードを追加
// TYPE_REASON 定数と、改行をふたつ
fileNode.append(TYPE_PERSON, NL, NL);
// DSL の person 定義ごとに Person 型の定数を追加
model.persons.forEach(person => {
fileNode.append(`const ${person.name} : Person = {
age: ${person.age},
rank: ${person.age}
}`, NL, NL)
});
// 全 person が含まれた Array を追加
const allPersons = model.persons.map(person => person.name).join(', ');
fileNode.append(`export const allPersons = [${allPersons}];`);
// 生成先ディレクトリがなければ作る
if (!fs.existsSync(data.destination)) {
fs.mkdirSync(data.destination, { recursive: true });
}
// 生成先にファイル書き出し
fs.writeFileSync(generatedFilePath, processGeneratorNode(fileNode));
// 生成したファイルのファイルパスを返却
return generatedFilePath;
}
cli からコード生成機能を呼び出す実装を追加
■ src/cli/index.ts
diff --git a/src/cli/index.ts b/src/cli/index.ts
index e0a4230..40d2e4c 100644--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -4,6 +4,14 @@ 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 { generateTypeScript } 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 = generateTypeScript(model, fileName, opts.destination);
+ console.log(colors.green(`TypeScript code generated successfully: ${generatedFilePath}`));
+};
export const testAction = async (fileName: string, opts: GenerateOptions): Promise<void> => {
const services = createFirststepServices().Firststep;@@ -24,6 +32,13 @@ export default function(): void {
.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 TypeScript code that all Person array.')
+ .action(generateAction);
+
program
.command('test')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)@@ -32,3 +47,4 @@ export default function(): void {
program.parse(process.argv); }
相対パスでファイルを指定するとデータ名が空になる問題の修正
src/cli/cli-util.ts
を修正。
diff --git a//src/cli/cli-util.ts b//src/cli/cli-uti
l.ts
index 53f6a94..b67cab6 100644--- a//src/cli/cli-util.ts
+++ b//src/cli/cli-util.ts
@@ -43,7 +43,7 @@ interface FilePathData {
}
export function extractDestinationAndName(filePath: string, destination: string | undefined): FilePathDat
a {- filePath = filePath.replace(/\..*$/, '').replace(/[.-]/g, '');
+ filePath = path.basename(filePath, path.extname(filePath)).replace(/[.-]/g, '');
return {
destination: destination ?? path.join(path.dirname(filePath), 'generated'), name: path.basename(filePath)
ビルド
npm run build
コード生成動作確認
「目標」で示した DSL 定義ファイルを作成し、コード生成を行う。
cat << EOF >> test.firststep
person mikoto {
age: 18;
rank: 1;
}
person makoto {
age: 19;
rank: 2;
}
person mokoto {
age: 20;
rank: 3;
}
EOF
node bin/cli generate -d ./generated_code ./test.firststep
これで、 generated_code/test.ts
が生成される。
# cat generated_code/test.ts
export type Person = {
age : number;
rank : number;
}
const mikoto : Person = {
age: 18,
rank: 18
}
const makoto : Person = {
age: 19,
rank: 19
}
const mokoto : Person = {
age: 20,
rank: 20
}
export const allPersons = [mikoto, makoto, mokoto];
今回はここまで。
参考資料
- langium/index.ts at main · langium/langium
- langium/generator.ts at main · langium/langium
- langium/cli-util.ts at main · langium/langium
memo.
— 大雪 命 (@mikoto2000) June 1, 2022./xxx.ts
に対して\..*?$
とかやると、全部消える。(この場合は、ファイルパス操作系 API 使えという話だが…)
[[正規表現] .*?は最短マッチではない - Qiita](https://t.co/aXwiZoS8pC)
0 件のコメント:
コメントを投稿