誰?
自作言語を作っていて静的型検査が欲しくなった
とりあえず単体で動くものを作った
知見共有という名の感想
はじめに
今回作ったもの
説明: JavaScriptのprimitive型の型検査のみ行うmodule
demo
方針
適当なParserでASTを作り、対象のASTを2回走査する
変数をScopeに登録
型検査したい項目を調べエラーがあれば記録
エラーを出力
1. 変数をScopeに登録
Global Scopeからスタート
関数やIf文などが存在する度、新しいScopeを作成
変数を宣言する箇所を見つける度、現在のScopeに登録
1. 変数をScopeに登録
Scopeと変数定義
/* ここまで処理 */
let a = 'str';
let b = 4;
{
let a = true;
a = false;
b = 'str2';
a = c;
}
const scopes = [
/* Global Scope */
{
id: 1,
/* Global Scopeはnull */
parentId: null,
/* 変数の定義情報 */
defs: [],
},
/* Global Scope */
}
ソースコードを上から処理していく
ソースコード
1. 変数をScopeに登録
Scopeと変数定義
let a = 'str';
let b = 4;
/* ここまで処理 */
{
let a = true;
a = false;
b = 'str2';
a = c;
}
const scopes = [
/* Global Scope */
{
id: 1,
/* Global Scopeはnull */
parentId: null,
/* 変数の定義情報 */
defs: [
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
/* Global Scope */
}
ソースコードを上から処理していく
ソースコード
1. 変数をScopeに登録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
/* ここまで処理 */
a = false;
b = 'str2';
a = c;
}
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
Blockがあったら新しいScopeを作る
ソースコード
1. 変数をScopeに登録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
a = false;
b = 'str2';
a = c;
}
/* ここまで処理 */
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
宣言でない場合は何もしない
ソースコード
2. 型検査したい項目を調べエラーがあれば記録
以下のような検査項目を1つずつ調べていく(※)
検査項目に違反したらエラーオブジェクトをstackに登録する
以下はJavaScript的に問題ないことも含んでいる
代入の左辺と右辺は同じ型
配列参照添え字は整数
If 条件部はBoolean etc...
※ 同時でも問題はないとは思う
今回はこれだけやる
2. 型検査したい項目を調べエラーがあれば記録
Scopeと変数定義
/* ここまで処理 */
let a = 'str';
let b = 4;
{
let a = true;
a = false;
b = 'str2';
a = c;
}
1と同じように上から処理していく
ソースコード
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
2. 型検査したい項目を調べエラーがあれば記録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
/* ここまで処理 */
a = false;
b = 'str';
a = c;
}
宣言は無視して飛ばす
ソースコード
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
2. 型検査したい項目を調べエラーがあれば記録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
/*
「a = false;」 の処理中
1. ScopeのIdは2
2. a(左)はdefs内に存在
3. a(左)はBoolean
4. false(右)はBoolean
5. 型は同じセーフ!
*/
a = false;
b = 'str2';
a = c;
}
a = false; の型は問題なし
ソースコード
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
2. 型検査したい項目を調べエラーがあれば記録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
a = false;
/*
「b = 'str2';」 の処理中
1. ScopeのIdは2
2. b(左)はdefs内に存在しない
3. ScopeのparentIdを使い、親Scopeを参照
4. b(左)はdefs内に存在
5. b(左)はNumber
6. 'str2'(右)はString
7. 型が違う。エラー!
*/
b = 'str2';
a = c;
}
b = 'str2'; は型エラー。エラー情報を記録する
ソースコード
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
2. 型検査したい項目を調べエラーがあれば記録
Scopeと変数定義
let a = 'str';
let b = 4;
{
let a = true;
a = false;
b = 'str2';
/*
「a = c;」 の処理中
1. ScopeのIdは2
2. a(左)はdefs内に存在
3. a(左)はBoolean
4. c(右)はdefs内に存在しない
5. ScopeのparentIdを使い、親Scopeを参照
6. c(右)は親のdefs内にも存在しない
7. 親のparentIdはnull。これ以上ない!
8. c(右)は未定義のエラー
*/
a = c;
}
a = c; は未定義のエラー。エラー情報を記録する
ソースコード
const scopes = [
{
id: 1,
parentId: null, /* Global Scopeはnull */
defs: [ /* 変数の定義情報 */
{
name: 'a',
type: 'String',
},
{
name: 'b',
type: 'Number',
}
],
},
{
id: 2,
parentId: 1,
defs: [
{
name: 'a',
type: 'Boolean'
}
]
}
}]
3. エラーを出力
2で登録したエラーオブジェクトを元にエラーを出力する
単なるオモチャでは?
それを言ってはいけない
でもこの方針でも以下のような事には対応できる
1と2の対象とロジックを改良してやれば良い
※ この辺やるとParserを自作する羽目になると思う
実際のところ難しい?
正直結構難しい。この程度でもすごい勢いで複雑になった
でもNodeによって処理は分けられる
だからある程度どうにかなりそう
以下のような事は今回は無視している
やりがいのある内容だと思う
感想
備考
let a = 3; みたいなものを宣言と書いているのは
@babel/parserが出力するASTのnodeのtypeがVariableDeclarationのため
正直今回の場合、全部定義と書いた方が良いと思うのだけど
何故VariableDeclarationなのだろう…?
参考資料