@no22/dison 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.d.ts ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Dison言語 トランスパイラ 公開API
3
+ * -----------------------------------------------------------
4
+ * 方針:
5
+ * 1. Lexer : ソース全体をトークン列に分解する(./lexer)。
6
+ * 文字列リテラルとコメントは中身ごと1トークンとして
7
+ * 丸呑みするため、その中の `{` や `injectable` などの
8
+ * 文字列がDSLキーワードとして誤検出されることはない。
9
+ * 2. Parser : トークン列を読み、`configuration` / `override` /
10
+ * `injectable` だけを構造的に解釈する(./parser)。
11
+ * それ以外はすべて Raw ノードとして完全にそのまま通す
12
+ * (改行・スペース・コメントも100%保持)。
13
+ * 3. CodeGen : ASTからTypeScriptソースを再構築する(./codegen)。
14
+ *
15
+ * このファイルは各モジュール(lexer/ast/analysis/parser/codegen/runtime/
16
+ * collisions)を束ね、公開API(transpileDisonToTS、findBindCollisions等)
17
+ * を提供する薄い入り口。実装の詳細はそれぞれのモジュールを参照。
18
+ * -----------------------------------------------------------
19
+ *
20
+ * 注意(override代入値のセマンティクス):
21
+ * `override` ブロック内の `プロパティ = 式;` の右辺は、
22
+ * TypeScriptの式としてそのまま評価されます。クラス名を
23
+ * インスタンス化したい場合は必ず `new ClassName()` と明記してください。
24
+ *
25
+ * database = MockUserRepository; // ✗ クラスの「参照」そのものが入る
26
+ * // (this.database.findById が
27
+ * // 存在せず実行時エラーになる)
28
+ * database = new MockUserRepository(); // ○ 意図通りインスタンスが入る
29
+ *
30
+ * `new` の書き忘れを自動補完する変換は行っていません
31
+ * (意図的な設計判断。関数呼び出し式やファクトリ式などを
32
+ * 自由に書けるようにするため、単純な識別子だけを特別扱いしない方針)。
33
+ *
34
+ * configurationの有効化:
35
+ * `activateXxx()` を直接呼ぶ代わりに、専用構文 `activate ConfigName;` を
36
+ * 使うことを推奨します(`activateConfigName();` に変換されます)。
37
+ * 存在しないconfiguration名を指定した場合はパース時にエラーになります。
38
+ *
39
+ * configuration TestConfig { ... }
40
+ * activate TestConfig; // OK
41
+ * activate TypoConfig; // ✗ パースエラー(定義されていない)
42
+ *
43
+ * 他ファイルのconfigurationをactivateする場合、`activate Name from
44
+ * "path";` という構文で、利用者が`activateName`という生成後の関数名を
45
+ * 意識せずに済む(複数ファイル対応フェーズ2、docs/activate-from-syntax.md)。
46
+ *
47
+ * activate TestConfig from "./configs";
48
+ *
49
+ * `from`句が使われている場合、対応する`import { activateTestConfig }
50
+ * from "./configs";`をファイル先頭にまとめて出力し(ES Modulesの
51
+ * importはファイル先頭にしか書けないため)、`activate`が書かれた位置
52
+ * 自体には呼び出し文だけを残す。異なるパスが同じconfiguration名を
53
+ * 持つ場合はエイリアス(`activateTestConfig_2`等)で衝突を自動的に
54
+ * 回避する。`from`がある場合、Dison側ではそのパス先の中身を読まない
55
+ * ため存在確認はできず、タイプミス検出は`tsc`自身のimport解決に委ねる
56
+ * (`from`が無い場合は従来通り同一ファイル内の定義/importで検証する)。
57
+ *
58
+ * `activate`は関数呼び出し文に脱糖されるため、injectable/configuration
59
+ * のような「宣言」と違い文が書ける場所ならどこでも良いが、クラス本体の
60
+ * 直下(メソッドの外)だけは書けない(単独override/bindと同じ制約。
61
+ * 以前はこの構造的位置制約作業(後述)で見落としていた既存バグだった)。
62
+ *
63
+ * クラス単位の横断的な差し替え(bind):
64
+ * `override` はクラス内の特定プロパティ1つだけを差し替えるのに対し、
65
+ * `bind` は「その型が要求される場所すべて」を横断的に差し替える。
66
+ * 右辺は override と異なり任意の式ではなく、単一の型参照(具象クラス、
67
+ * ジェネリクス可)のみを許可し、トランスパイラが自動的に `new` を補う
68
+ * (コンストラクタに引数を渡したいケースは override のプロパティ単位
69
+ * 指定を使うこと)。
70
+ *
71
+ * configuration TestConfig {
72
+ * bind SqlUserRepository = MockUserRepository;
73
+ * }
74
+ *
75
+ * 左辺(差し替え元)はクラスだけでなく interface / type エイリアスも許可される。
76
+ * 実行時には存在しない型でも、TypeScript の型引数としてのみ扱うことで
77
+ * 問題なく渡せる。右辺は `new` で実体化する必要があるため、実質的に
78
+ * 具象クラスに限定される(interfaceを右辺に書くとtscがコンパイルエラーにする)。
79
+ *
80
+ * interface IUserRepository { findById(id: string): void; }
81
+ * configuration TestConfig {
82
+ * bind IUserRepository = SqlUserRepository;
83
+ * }
84
+ *
85
+ * 型安全性は自前で検証せず、生成される `bindType<T>()` 呼び出しの
86
+ * ジェネリクス(明示的な型引数)をTypeScript自身の型チェッカーに委ねている。
87
+ * 差し替え先クラスが差し替え元と型互換でなければ tsc がコンパイルエラーにする。
88
+ *
89
+ * ジェネリクス対応: 左辺・右辺とも `Repository<User>` のような、具体的な
90
+ * 型引数を指定した閉じたジェネリクスを書ける(`Repository<T>` のように
91
+ * 任意の型引数に適用されるオープンジェネリクスのバインドは非対応。
92
+ * docs/bind-generics.md 参照)。
93
+ *
94
+ * configuration TestConfig {
95
+ * bind Repository<User> = SqlUserRepository;
96
+ * bind Repository<Admin> = SqlAdminRepository;
97
+ * }
98
+ *
99
+ * 照合キーの正規化: injectable の型注釈も bind の左辺・右辺も、
100
+ * TYPE_BINDINGS に登録・参照する際のキーは「空白・コメントを除いた
101
+ * トークン列を連結した正規化済みの文字列」を使う(`typeKey`。生成コードの
102
+ * 型注釈として出力する文字列 `typeName` とは別に保持している)。単一の
103
+ * 識別子には内部に空白が入り得ないため元々問題にならなかったが、
104
+ * ジェネリクスは複数トークンからなる型注釈なので、正規化しないと
105
+ * `Repository<User>` と `Repository <User>` のような書き方の違いだけで
106
+ * bind が一致しなくなる(サイレントに効かなくなる)バグを生む。
107
+ *
108
+ * 連鎖: bind は連鎖する。`bind A = B;` と `bind B = C;` を両方書くと、
109
+ * `A` の解決は実行時に `resolveType` を再帰的に辿って最終的に `C` まで到達する
110
+ * (どちらを先に書いても構わない。循環している場合は分かりやすいエラーになる)。
111
+ *
112
+ * 優先順位: プロパティ単位の override > 型単位の bind > 宣言済みの既定初期化式
113
+ * (後述)。
114
+ *
115
+ * activate同士の実行順序に依存する点は変更していない。複数の configuration が
116
+ * 同じ型を bind/override した場合、後から activate した方が勝つ。
117
+ *
118
+ * injectableの既定初期化式(危険な型には "= <式>" が必須):
119
+ * `injectable prop: Type;` は `new Type()` を自動生成するが、これは
120
+ * Typeが「単純な識別子(+ジェネリクス)の形」かつ「このファイル内で
121
+ * interface/type/abstract classとして宣言されていない」場合にのみ安全
122
+ * (それ以外は new できずコンパイルエラーになる、または bind/override が
123
+ * 無ければ実行時エラーになる、という問題があった)。
124
+ *
125
+ * そこで「危険な型」(配列型・ユニオン型・関数型、または interface/
126
+ * type エイリアス/abstract classと判定された識別子型)については、
127
+ * `= <式>` による既定初期化式を構文的に必須にした。無い場合は
128
+ * パース時に `DisonParseError` になる。安全な型(通常のクラス型)には
129
+ * 同じ `= <式>` 構文を任意で使える(コンストラクタ引数を渡したい場合など)。
130
+ * 従来通り省略した場合は `new Type()` が自動生成される。
131
+ *
132
+ * injectable repo: IUserRepository = new SqlUserRepositoryImpl(); // OK
133
+ * injectable repo: IUserRepository; // ✗ パースエラー
134
+ * injectable database: SqlUserRepository; // OK(省略可、new SqlUserRepository())
135
+ * injectable database: SqlUserRepository = new SqlUserRepository(cfg); // OK(明示も可)
136
+ *
137
+ * 既定初期化式の右辺は override と同じ規約(自動で `new` を補わない、
138
+ * 任意のTS式をそのまま評価する)に揃えている。
139
+ *
140
+ * abstract class も interface/type と同様に「危険な型」として扱う
141
+ * (`new AbstractClass()` は tsc が常にコンパイルエラーにするため)。
142
+ * ただし同名の非abstractな `class` 宣言が別途あれば(TSの宣言マージ)
143
+ * 実際には new 可能なので「危険な型」からは除外する。
144
+ *
145
+ * 構文的に置ける位置の制約:
146
+ * `injectable` / `configuration` はそれぞれ生成物の形(クラスメンバ宣言 /
147
+ * 関数宣言)が特定の構文的位置でしか有効にならないため、それ以外の位置に
148
+ * 書かれた場合はパース時に `DisonParseError` になる(以前は無制限に
149
+ * 受理してしまい、tscの分かりにくいエラーの壁として現れていた)。
150
+ *
151
+ * - `injectable`: クラス本体の直下(メソッドの中は不可)
152
+ * - `configuration`: トップレベルのみ(関数・クラスの中は不可)
153
+ * - `activate`: 制限なし(条件分岐や関数の中で呼び分けてよい)
154
+ *
155
+ * 判定は `collectBlockContext` によるヒューリスティックな事前スキャン
156
+ * (トークン列全体を1回舐め、各位置の波カッコの深さと、直近の囲みブロックが
157
+ * クラス本体かどうかを記録する)に基づく。
158
+ *
159
+ * configurationで包まない単独のoverride/bind:
160
+ * `override`/`bind` の中身は(`configuration`と違い)ただの代入文
161
+ * (`DI_REGISTRY[...] = ...;` / `bindType<...>(...)`)に脱糖されるため、
162
+ * 宣言と違って構文的な位置の制約を受けない。そこで `configuration { ... }`
163
+ * で包まずに単独で書くことも許可している。書かれた位置でそのまま
164
+ * 即座に実行される代入文になるため、関数・メソッドのレキシカルスコープに
165
+ * ある変数を自然に捕捉できる。
166
+ *
167
+ * function createHarness(label: string) {
168
+ * class Tagged extends Base { tag = label; }
169
+ * override S {
170
+ * dep = new Tagged(); // label をクロージャで捕捉できる
171
+ * }
172
+ * return new S();
173
+ * }
174
+ *
175
+ * これが無いと、同じことをするには `DI_REGISTRY["S"]["dep"] = ...` という
176
+ * 生の実装詳細をユーザーに直接書かせることになり、Disonの目的
177
+ * (ServiceLocator的な実装戦略を言語セマンティクスの下に隠す)に反してしまう。
178
+ * 単独のoverride/bindは、`configuration`同様クラス本体の直下だけは
179
+ * 書けない(代入文を置ける位置ではないため)。
180
+ *
181
+ * bindのinterface/型エイリアス問題への対応("as <トークン>" / token):
182
+ * `bind`/`injectable`の照合キーは通常、型名の文字列(typeKey)を使う。
183
+ * 単一ファイルでは問題にならないが、複数ファイルで共有ランタイム
184
+ * モジュールを使う場合(docs/multi-file-support.md)、互いに無関係な
185
+ * 2ファイルがたまたま同名のinterfaceを独自に宣言しているだけで、
186
+ * 一方の`bind`がもう一方を汚染してしまう(実際に再現・実証済み。
187
+ * docs/bind-interface-token.md 1節)。
188
+ *
189
+ * 対応として、`injectable`/`bind`の左辺に任意の `as <トークン>` 句を
190
+ * 追加できるようにした。トークンは`token Name;`という新しい構文
191
+ * (`export const Name = Symbol("Name");`に脱糖される。トップレベル
192
+ * にのみ書ける)で作った、複数箇所から安定して参照される共有の値。
193
+ * 指定すると、型名の文字列の代わりにそのトークンの識別子参照が
194
+ * 照合キーになる(`TYPE_BINDINGS`は`Map<string | symbol, ...>`)。
195
+ *
196
+ * token IRepositoryToken;
197
+ * injectable repo: IRepository as IRepositoryToken = new Impl();
198
+ * bind IRepository as IRepositoryToken = Mock;
199
+ *
200
+ * トークンは複数箇所(複数ファイルにまたがる場合も含む)から常に
201
+ * 同じ値を参照する必要があるため、`as Symbol("X")`のようにその場で
202
+ * 新しい値を作ってはいけない(呼ぶたびに異なる値になり一致しなくなる)。
203
+ * `as`句の文法もこれを踏まえ、識別子+任意の".プロパティ"という
204
+ * 制限された形のみ許可し、任意の式・呼び出し式は許可していない。
205
+ *
206
+ * さらに`findBindCollisions`(複数ファイルを横断する別関数。CLIが
207
+ * 複数ファイルを処理する際にのみ呼ばれる)が、`bind`/`injectable`で
208
+ * 実際に使われている型名について、宣言・importの由来(ローカル宣言か、
209
+ * importならそのspecifier)を比較し、由来が食い違う(=衝突しうる)
210
+ * 箇所でトークンが使われていなければビルドエラーにする。これにより
211
+ * 利用者が衝突の存在に気づかずサイレントに汚染される、という事態を
212
+ * 防いでいる。詳細は docs/bind-interface-token.md 参照。
213
+ * -----------------------------------------------------------
214
+ */
215
+ import { DISON_RUNTIME_MODULE_SOURCE } from "./runtime.js";
216
+ import { findBindCollisions } from "./collisions.js";
217
+ import type { DisonFileInput, BindCollisionDiagnostic } from "./collisions.js";
218
+ export interface TranspileOptions {
219
+ runtimeModulePath?: string;
220
+ }
221
+ /**
222
+ * Dison言語のソースコードを受け取り、完全に動作するTypeScriptコードに変換する。
223
+ * DSL構文(configuration / override / injectable / activate / bind)以外の部分は、
224
+ * トークナイザ+パーサを経由してもなお100%元の文字列のまま出力される。
225
+ */
226
+ export declare function transpileDisonToTS(sourceCode: string, options?: TranspileOptions): string;
227
+ export { DISON_RUNTIME_MODULE_SOURCE, findBindCollisions };
228
+ export type { DisonFileInput, BindCollisionDiagnostic };
229
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqNG;AAMH,OAAO,EAA+B,2BAA2B,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,MAAM,WAAW,gBAAgB;IAI/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAoB7F;AAID,OAAO,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,CAAC;AAC3D,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,CAAC"}
package/dist/core.js ADDED
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Dison言語 トランスパイラ 公開API
3
+ * -----------------------------------------------------------
4
+ * 方針:
5
+ * 1. Lexer : ソース全体をトークン列に分解する(./lexer)。
6
+ * 文字列リテラルとコメントは中身ごと1トークンとして
7
+ * 丸呑みするため、その中の `{` や `injectable` などの
8
+ * 文字列がDSLキーワードとして誤検出されることはない。
9
+ * 2. Parser : トークン列を読み、`configuration` / `override` /
10
+ * `injectable` だけを構造的に解釈する(./parser)。
11
+ * それ以外はすべて Raw ノードとして完全にそのまま通す
12
+ * (改行・スペース・コメントも100%保持)。
13
+ * 3. CodeGen : ASTからTypeScriptソースを再構築する(./codegen)。
14
+ *
15
+ * このファイルは各モジュール(lexer/ast/analysis/parser/codegen/runtime/
16
+ * collisions)を束ね、公開API(transpileDisonToTS、findBindCollisions等)
17
+ * を提供する薄い入り口。実装の詳細はそれぞれのモジュールを参照。
18
+ * -----------------------------------------------------------
19
+ *
20
+ * 注意(override代入値のセマンティクス):
21
+ * `override` ブロック内の `プロパティ = 式;` の右辺は、
22
+ * TypeScriptの式としてそのまま評価されます。クラス名を
23
+ * インスタンス化したい場合は必ず `new ClassName()` と明記してください。
24
+ *
25
+ * database = MockUserRepository; // ✗ クラスの「参照」そのものが入る
26
+ * // (this.database.findById が
27
+ * // 存在せず実行時エラーになる)
28
+ * database = new MockUserRepository(); // ○ 意図通りインスタンスが入る
29
+ *
30
+ * `new` の書き忘れを自動補完する変換は行っていません
31
+ * (意図的な設計判断。関数呼び出し式やファクトリ式などを
32
+ * 自由に書けるようにするため、単純な識別子だけを特別扱いしない方針)。
33
+ *
34
+ * configurationの有効化:
35
+ * `activateXxx()` を直接呼ぶ代わりに、専用構文 `activate ConfigName;` を
36
+ * 使うことを推奨します(`activateConfigName();` に変換されます)。
37
+ * 存在しないconfiguration名を指定した場合はパース時にエラーになります。
38
+ *
39
+ * configuration TestConfig { ... }
40
+ * activate TestConfig; // OK
41
+ * activate TypoConfig; // ✗ パースエラー(定義されていない)
42
+ *
43
+ * 他ファイルのconfigurationをactivateする場合、`activate Name from
44
+ * "path";` という構文で、利用者が`activateName`という生成後の関数名を
45
+ * 意識せずに済む(複数ファイル対応フェーズ2、docs/activate-from-syntax.md)。
46
+ *
47
+ * activate TestConfig from "./configs";
48
+ *
49
+ * `from`句が使われている場合、対応する`import { activateTestConfig }
50
+ * from "./configs";`をファイル先頭にまとめて出力し(ES Modulesの
51
+ * importはファイル先頭にしか書けないため)、`activate`が書かれた位置
52
+ * 自体には呼び出し文だけを残す。異なるパスが同じconfiguration名を
53
+ * 持つ場合はエイリアス(`activateTestConfig_2`等)で衝突を自動的に
54
+ * 回避する。`from`がある場合、Dison側ではそのパス先の中身を読まない
55
+ * ため存在確認はできず、タイプミス検出は`tsc`自身のimport解決に委ねる
56
+ * (`from`が無い場合は従来通り同一ファイル内の定義/importで検証する)。
57
+ *
58
+ * `activate`は関数呼び出し文に脱糖されるため、injectable/configuration
59
+ * のような「宣言」と違い文が書ける場所ならどこでも良いが、クラス本体の
60
+ * 直下(メソッドの外)だけは書けない(単独override/bindと同じ制約。
61
+ * 以前はこの構造的位置制約作業(後述)で見落としていた既存バグだった)。
62
+ *
63
+ * クラス単位の横断的な差し替え(bind):
64
+ * `override` はクラス内の特定プロパティ1つだけを差し替えるのに対し、
65
+ * `bind` は「その型が要求される場所すべて」を横断的に差し替える。
66
+ * 右辺は override と異なり任意の式ではなく、単一の型参照(具象クラス、
67
+ * ジェネリクス可)のみを許可し、トランスパイラが自動的に `new` を補う
68
+ * (コンストラクタに引数を渡したいケースは override のプロパティ単位
69
+ * 指定を使うこと)。
70
+ *
71
+ * configuration TestConfig {
72
+ * bind SqlUserRepository = MockUserRepository;
73
+ * }
74
+ *
75
+ * 左辺(差し替え元)はクラスだけでなく interface / type エイリアスも許可される。
76
+ * 実行時には存在しない型でも、TypeScript の型引数としてのみ扱うことで
77
+ * 問題なく渡せる。右辺は `new` で実体化する必要があるため、実質的に
78
+ * 具象クラスに限定される(interfaceを右辺に書くとtscがコンパイルエラーにする)。
79
+ *
80
+ * interface IUserRepository { findById(id: string): void; }
81
+ * configuration TestConfig {
82
+ * bind IUserRepository = SqlUserRepository;
83
+ * }
84
+ *
85
+ * 型安全性は自前で検証せず、生成される `bindType<T>()` 呼び出しの
86
+ * ジェネリクス(明示的な型引数)をTypeScript自身の型チェッカーに委ねている。
87
+ * 差し替え先クラスが差し替え元と型互換でなければ tsc がコンパイルエラーにする。
88
+ *
89
+ * ジェネリクス対応: 左辺・右辺とも `Repository<User>` のような、具体的な
90
+ * 型引数を指定した閉じたジェネリクスを書ける(`Repository<T>` のように
91
+ * 任意の型引数に適用されるオープンジェネリクスのバインドは非対応。
92
+ * docs/bind-generics.md 参照)。
93
+ *
94
+ * configuration TestConfig {
95
+ * bind Repository<User> = SqlUserRepository;
96
+ * bind Repository<Admin> = SqlAdminRepository;
97
+ * }
98
+ *
99
+ * 照合キーの正規化: injectable の型注釈も bind の左辺・右辺も、
100
+ * TYPE_BINDINGS に登録・参照する際のキーは「空白・コメントを除いた
101
+ * トークン列を連結した正規化済みの文字列」を使う(`typeKey`。生成コードの
102
+ * 型注釈として出力する文字列 `typeName` とは別に保持している)。単一の
103
+ * 識別子には内部に空白が入り得ないため元々問題にならなかったが、
104
+ * ジェネリクスは複数トークンからなる型注釈なので、正規化しないと
105
+ * `Repository<User>` と `Repository <User>` のような書き方の違いだけで
106
+ * bind が一致しなくなる(サイレントに効かなくなる)バグを生む。
107
+ *
108
+ * 連鎖: bind は連鎖する。`bind A = B;` と `bind B = C;` を両方書くと、
109
+ * `A` の解決は実行時に `resolveType` を再帰的に辿って最終的に `C` まで到達する
110
+ * (どちらを先に書いても構わない。循環している場合は分かりやすいエラーになる)。
111
+ *
112
+ * 優先順位: プロパティ単位の override > 型単位の bind > 宣言済みの既定初期化式
113
+ * (後述)。
114
+ *
115
+ * activate同士の実行順序に依存する点は変更していない。複数の configuration が
116
+ * 同じ型を bind/override した場合、後から activate した方が勝つ。
117
+ *
118
+ * injectableの既定初期化式(危険な型には "= <式>" が必須):
119
+ * `injectable prop: Type;` は `new Type()` を自動生成するが、これは
120
+ * Typeが「単純な識別子(+ジェネリクス)の形」かつ「このファイル内で
121
+ * interface/type/abstract classとして宣言されていない」場合にのみ安全
122
+ * (それ以外は new できずコンパイルエラーになる、または bind/override が
123
+ * 無ければ実行時エラーになる、という問題があった)。
124
+ *
125
+ * そこで「危険な型」(配列型・ユニオン型・関数型、または interface/
126
+ * type エイリアス/abstract classと判定された識別子型)については、
127
+ * `= <式>` による既定初期化式を構文的に必須にした。無い場合は
128
+ * パース時に `DisonParseError` になる。安全な型(通常のクラス型)には
129
+ * 同じ `= <式>` 構文を任意で使える(コンストラクタ引数を渡したい場合など)。
130
+ * 従来通り省略した場合は `new Type()` が自動生成される。
131
+ *
132
+ * injectable repo: IUserRepository = new SqlUserRepositoryImpl(); // OK
133
+ * injectable repo: IUserRepository; // ✗ パースエラー
134
+ * injectable database: SqlUserRepository; // OK(省略可、new SqlUserRepository())
135
+ * injectable database: SqlUserRepository = new SqlUserRepository(cfg); // OK(明示も可)
136
+ *
137
+ * 既定初期化式の右辺は override と同じ規約(自動で `new` を補わない、
138
+ * 任意のTS式をそのまま評価する)に揃えている。
139
+ *
140
+ * abstract class も interface/type と同様に「危険な型」として扱う
141
+ * (`new AbstractClass()` は tsc が常にコンパイルエラーにするため)。
142
+ * ただし同名の非abstractな `class` 宣言が別途あれば(TSの宣言マージ)
143
+ * 実際には new 可能なので「危険な型」からは除外する。
144
+ *
145
+ * 構文的に置ける位置の制約:
146
+ * `injectable` / `configuration` はそれぞれ生成物の形(クラスメンバ宣言 /
147
+ * 関数宣言)が特定の構文的位置でしか有効にならないため、それ以外の位置に
148
+ * 書かれた場合はパース時に `DisonParseError` になる(以前は無制限に
149
+ * 受理してしまい、tscの分かりにくいエラーの壁として現れていた)。
150
+ *
151
+ * - `injectable`: クラス本体の直下(メソッドの中は不可)
152
+ * - `configuration`: トップレベルのみ(関数・クラスの中は不可)
153
+ * - `activate`: 制限なし(条件分岐や関数の中で呼び分けてよい)
154
+ *
155
+ * 判定は `collectBlockContext` によるヒューリスティックな事前スキャン
156
+ * (トークン列全体を1回舐め、各位置の波カッコの深さと、直近の囲みブロックが
157
+ * クラス本体かどうかを記録する)に基づく。
158
+ *
159
+ * configurationで包まない単独のoverride/bind:
160
+ * `override`/`bind` の中身は(`configuration`と違い)ただの代入文
161
+ * (`DI_REGISTRY[...] = ...;` / `bindType<...>(...)`)に脱糖されるため、
162
+ * 宣言と違って構文的な位置の制約を受けない。そこで `configuration { ... }`
163
+ * で包まずに単独で書くことも許可している。書かれた位置でそのまま
164
+ * 即座に実行される代入文になるため、関数・メソッドのレキシカルスコープに
165
+ * ある変数を自然に捕捉できる。
166
+ *
167
+ * function createHarness(label: string) {
168
+ * class Tagged extends Base { tag = label; }
169
+ * override S {
170
+ * dep = new Tagged(); // label をクロージャで捕捉できる
171
+ * }
172
+ * return new S();
173
+ * }
174
+ *
175
+ * これが無いと、同じことをするには `DI_REGISTRY["S"]["dep"] = ...` という
176
+ * 生の実装詳細をユーザーに直接書かせることになり、Disonの目的
177
+ * (ServiceLocator的な実装戦略を言語セマンティクスの下に隠す)に反してしまう。
178
+ * 単独のoverride/bindは、`configuration`同様クラス本体の直下だけは
179
+ * 書けない(代入文を置ける位置ではないため)。
180
+ *
181
+ * bindのinterface/型エイリアス問題への対応("as <トークン>" / token):
182
+ * `bind`/`injectable`の照合キーは通常、型名の文字列(typeKey)を使う。
183
+ * 単一ファイルでは問題にならないが、複数ファイルで共有ランタイム
184
+ * モジュールを使う場合(docs/multi-file-support.md)、互いに無関係な
185
+ * 2ファイルがたまたま同名のinterfaceを独自に宣言しているだけで、
186
+ * 一方の`bind`がもう一方を汚染してしまう(実際に再現・実証済み。
187
+ * docs/bind-interface-token.md 1節)。
188
+ *
189
+ * 対応として、`injectable`/`bind`の左辺に任意の `as <トークン>` 句を
190
+ * 追加できるようにした。トークンは`token Name;`という新しい構文
191
+ * (`export const Name = Symbol("Name");`に脱糖される。トップレベル
192
+ * にのみ書ける)で作った、複数箇所から安定して参照される共有の値。
193
+ * 指定すると、型名の文字列の代わりにそのトークンの識別子参照が
194
+ * 照合キーになる(`TYPE_BINDINGS`は`Map<string | symbol, ...>`)。
195
+ *
196
+ * token IRepositoryToken;
197
+ * injectable repo: IRepository as IRepositoryToken = new Impl();
198
+ * bind IRepository as IRepositoryToken = Mock;
199
+ *
200
+ * トークンは複数箇所(複数ファイルにまたがる場合も含む)から常に
201
+ * 同じ値を参照する必要があるため、`as Symbol("X")`のようにその場で
202
+ * 新しい値を作ってはいけない(呼ぶたびに異なる値になり一致しなくなる)。
203
+ * `as`句の文法もこれを踏まえ、識別子+任意の".プロパティ"という
204
+ * 制限された形のみ許可し、任意の式・呼び出し式は許可していない。
205
+ *
206
+ * さらに`findBindCollisions`(複数ファイルを横断する別関数。CLIが
207
+ * 複数ファイルを処理する際にのみ呼ばれる)が、`bind`/`injectable`で
208
+ * 実際に使われている型名について、宣言・importの由来(ローカル宣言か、
209
+ * importならそのspecifier)を比較し、由来が食い違う(=衝突しうる)
210
+ * 箇所でトークンが使われていなければビルドエラーにする。これにより
211
+ * 利用者が衝突の存在に気づかずサイレントに汚染される、という事態を
212
+ * 防いでいる。詳細は docs/bind-interface-token.md 参照。
213
+ * -----------------------------------------------------------
214
+ */
215
+ import { Lexer } from "./lexer.js";
216
+ import { collectDeclaredTypeKinds } from "./analysis.js";
217
+ import { Parser } from "./parser.js";
218
+ import { generate, resolveFromActivateBindings, generateFromImportStatements } from "./codegen.js";
219
+ import { generateRuntimeDeclarations, DISON_RUNTIME_MODULE_SOURCE } from "./runtime.js";
220
+ import { findBindCollisions } from "./collisions.js";
221
+ /**
222
+ * Dison言語のソースコードを受け取り、完全に動作するTypeScriptコードに変換する。
223
+ * DSL構文(configuration / override / injectable / activate / bind)以外の部分は、
224
+ * トークナイザ+パーサを経由してもなお100%元の文字列のまま出力される。
225
+ */
226
+ export function transpileDisonToTS(sourceCode, options = {}) {
227
+ const tokens = new Lexer(sourceCode).tokenize();
228
+ const typeKinds = collectDeclaredTypeKinds(tokens);
229
+ const ast = new Parser(tokens, typeKinds).parseProgram();
230
+ const fromBindings = resolveFromActivateBindings(ast);
231
+ const body = generate(ast, fromBindings);
232
+ const header = `// --- Auto-generated TypeScript code ---\n\n`;
233
+ const prelude = options.runtimeModulePath
234
+ ? `${header}import {\n` +
235
+ ` DI_REGISTRY,\n TYPE_BINDINGS,\n bindType,\n resolveType,\n registerOverride,\n getOverride,\n` +
236
+ `} from ${JSON.stringify(options.runtimeModulePath)};\n\n`
237
+ : `${header}${generateRuntimeDeclarations("")}\n`;
238
+ // "activate Name from '...';" が使われている場合、対応するimport文を
239
+ // ランタイムの前置きの直後・生成本体の前にまとめて出力する
240
+ // (ES Modulesのimportはファイル先頭にしか書けないため)。
241
+ const fromImports = generateFromImportStatements(fromBindings);
242
+ return prelude + fromImports + body;
243
+ }
244
+ // 公開API: 複数ファイル対応(docs/multi-file-support.md、
245
+ // docs/bind-interface-token.md)。CLIが複数ファイルを一括処理する際に使う。
246
+ export { DISON_RUNTIME_MODULE_SOURCE, findBindCollisions };
247
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqNG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,2BAA2B,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,2BAA2B,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AACxF,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAUrD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,OAAO,GAAqB,EAAE;IACnF,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChD,MAAM,SAAS,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,YAAY,EAAE,CAAC;IACzD,MAAM,YAAY,GAAG,2BAA2B,CAAC,GAAG,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAEzC,MAAM,MAAM,GAAG,+CAA+C,CAAC;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB;QACvC,CAAC,CAAC,GAAG,MAAM,YAAY;YACrB,sGAAsG;YACtG,UAAU,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,iBAAiB,CAAC,OAAO;QAC5D,CAAC,CAAC,GAAG,MAAM,GAAG,2BAA2B,CAAC,EAAE,CAAC,IAAI,CAAC;IAEpD,qDAAqD;IACrD,+BAA+B;IAC/B,uCAAuC;IACvC,MAAM,WAAW,GAAG,4BAA4B,CAAC,YAAY,CAAC,CAAC;IAE/D,OAAO,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC;AACtC,CAAC;AAED,8CAA8C;AAC9C,uDAAuD;AACvD,OAAO,EAAE,2BAA2B,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare const DI_REGISTRY: WeakMap<Function, Record<string, () => any>>;
2
+ export declare function registerOverride(cls: Function, prop: string, factory: () => any): void;
3
+ export declare function getOverride(cls: Function, prop: string): (() => any) | undefined;
4
+ export declare const TYPE_BINDINGS: Map<string | symbol, () => any>;
5
+ export declare function bindType<T>(typeKey: string | symbol, factory: () => T): void;
6
+ export declare function resolveType<T>(typeKey: string | symbol, defaultFactory: () => T): T;
7
+ //# sourceMappingURL=generated-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-runtime.d.ts","sourceRoot":"","sources":["../src/generated-runtime.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,WAAW,yCAA8C,GAAG,EAAI,CAAC;AAE9E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,GAAG,IAAI,CAOtF;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,SAAS,CAEhF;AAMD,eAAO,MAAM,aAAa,6BAAkC,GAAG,CAAG,CAAC;AAOnE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAE5E;AAKD,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,GAAG,CAAC,CAYnF"}
@@ -0,0 +1,51 @@
1
+ // --- Dison shared runtime module ---
2
+ // Multiple generated files import this module to share runtime state
3
+ // such as DI_REGISTRY/TYPE_BINDINGS.
4
+ // This file is auto-generated when the Dison package is built. Do not edit it by hand.
5
+ // Global dependency registry (per-property override).
6
+ // Keying on the class itself (not its name) lets tsc catch a typo in the
7
+ // override target class name as a "Cannot find name" error.
8
+ export const DI_REGISTRY = new WeakMap();
9
+ export function registerOverride(cls, prop, factory) {
10
+ let entry = DI_REGISTRY.get(cls);
11
+ if (!entry) {
12
+ entry = {};
13
+ DI_REGISTRY.set(cls, entry);
14
+ }
15
+ entry[prop] = factory;
16
+ }
17
+ export function getOverride(cls, prop) {
18
+ return DI_REGISTRY.get(cls)?.[prop];
19
+ }
20
+ // Global type-replacement registry (bind).
21
+ // The key is either a type-name string (class, interface, or type alias name)
22
+ // or a Symbol explicitly given via an "as <token>" clause (used to avoid
23
+ // name collisions between same-named interfaces/type aliases across files).
24
+ export const TYPE_BINDINGS = new Map();
25
+ const _resolvingTypeBindings = new Set();
26
+ // bindType<T> takes T as an explicit type argument, so tsc raises a
27
+ // compile error if the replacement factory isn't compatible with the
28
+ // original type. T may be an interface or type alias (used only as a
29
+ // type argument).
30
+ export function bindType(typeKey, factory) {
31
+ TYPE_BINDINGS.set(typeKey, factory);
32
+ }
33
+ // Resolves a value by recursively walking TYPE_BINDINGS. bind chains
34
+ // (e.g. bind A = B; bind B = C; makes resolving A eventually reach C).
35
+ // A cycle (A -> B -> A) raises a clear error instead of a stack overflow.
36
+ export function resolveType(typeKey, defaultFactory) {
37
+ const bound = TYPE_BINDINGS.get(typeKey);
38
+ if (!bound)
39
+ return defaultFactory();
40
+ if (_resolvingTypeBindings.has(typeKey)) {
41
+ throw new Error('Detected a circular "bind" reference ("' + String(typeKey) + '"). The bind chain loops back on itself.');
42
+ }
43
+ _resolvingTypeBindings.add(typeKey);
44
+ try {
45
+ return bound();
46
+ }
47
+ finally {
48
+ _resolvingTypeBindings.delete(typeKey);
49
+ }
50
+ }
51
+ //# sourceMappingURL=generated-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-runtime.js","sourceRoot":"","sources":["../src/generated-runtime.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,qEAAqE;AACrE,qCAAqC;AACrC,uFAAuF;AAEvF,sDAAsD;AACtD,yEAAyE;AACzE,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,EAAuC,CAAC;AAE9E,MAAM,UAAU,gBAAgB,CAAC,GAAa,EAAE,IAAY,EAAE,OAAkB;IAC9E,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,EAAE,CAAC;QACX,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAa,EAAE,IAAY;IACrD,OAAO,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,2CAA2C;AAC3C,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,GAAG,EAA8B,CAAC;AACnE,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAmB,CAAC;AAE1D,oEAAoE;AACpE,qEAAqE;AACrE,qEAAqE;AACrE,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CAAI,OAAwB,EAAE,OAAgB;IACpE,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,qEAAqE;AACrE,uEAAuE;AACvE,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAI,OAAwB,EAAE,cAAuB;IAC9E,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,cAAc,EAAE,CAAC;IACpC,IAAI,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,0CAA0C,CAAC,CAAC;IAC5H,CAAC;IACD,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,OAAO,KAAK,EAAE,CAAC;IACjB,CAAC;YAAS,CAAC;QACT,sBAAsB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ export type TokenType = "whitespace" | "comment" | "string" | "ident" | "keyword" | "punct" | "eof";
2
+ export interface Token {
3
+ type: TokenType;
4
+ text: string;
5
+ pos: number;
6
+ }
7
+ export declare const KEYWORDS: Set<string>;
8
+ export declare class Lexer {
9
+ private readonly src;
10
+ private i;
11
+ private readonly tokens;
12
+ constructor(src: string);
13
+ tokenize(): Token[];
14
+ private emit;
15
+ }
16
+ //# sourceMappingURL=lexer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexer.d.ts","sourceRoot":"","sources":["../src/lexer.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,KAAK,CAAC;AAEpG,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAID,eAAO,MAAM,QAAQ,aAAoF,CAAC;AAE1G,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,CAAC,CAAK;IACd,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,YAAY,GAAG,EAAE,MAAM,EAEtB;IAED,QAAQ,IAAI,KAAK,EAAE,CAuElB;IAED,OAAO,CAAC,IAAI;CAGb"}