それで、LuvvieScript の進歩を開始し、Twitter ですべてが少し始まりました... https://twitter.com/gordonguthrie/status/389659700741943296
Anthony Ramine https://twitter.com/nokusuは、私のやり方が間違っていて、Erlang AST ではなく Core Erlang を介して Erlang から JavaScript にコンパイルするべきだと指摘しました。これは私にとって説得力のある選択肢ですが、魅力的ではありません... Twitterはその議論の適切な媒体ではないので、ここに書いてアドバイスを得ようと思いました.
戦略概要
LuvvieScript には 3 つのコア要件があります。
- 同じパフォーマンスの Javascript にコンパイルされる Erlang の有効なサブセット
- Javascript ではなく LuvvieScript でブラウザーでデバッグできるように、完全なソース マップ
- LuvvieScript モジュールを実行するための「ランタイム」クライアント側 JavaScript 環境 (サーバー側通信を使用) (一種のページ内スーパーバイザ...)
これらのオプションの 3 番目は、この議論の範囲外ですが、最初の 2 つはコアです。
lazy-gits の当然の帰結があります - 私はできるだけ多くの Erlang および Javascript 構文ツール (レクサー、パーサー、トークナイザー、AST 変換など) を使用し、最小限のコードを記述したいと考えています。
現在の考え方
コードが現在次の構造として書かれている方法:
- コードを Erlang AST (行番号付き) にコンパイルします。
- コードをトークン化し (コメントと空白を保持)、それらのトークンを使用して、行/列情報をトークンにマップする辞書を作成します。
- 辞書と AST をマージして、行/列の AST を作成します (さまざまなアリティの fn をグループ化するためのちょっとした工夫が必要です)
- SpiderMonkey パーサー API https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_APIに実装されているように、この新しい Erlang AST を Javascript AST に変換します。
- JavaScript AST https://github.com/puffnfresh/brushtailでテール呼び出しを変異させるには、brushtail などの Javascript utils を使用します。
- ESCodeGen などの Javascript ユーティリティを使用して、javascript を発行します https://github.com/Constellation/escodegen
基本的に、次のような Erlang AST を取得します。
[{function,
{19,{1,9}},
atom1_fn,0,
[{clause,
{19,none},
[],
[[]],
[{match,
{20,none},
[{var,{20,{5,6}},'D'}],
[{atom,{20,{11,15}},blue}]},
{var,{21,{5,6}},'D'}]}]}]},
次に、次のような Javascript JSON AST に変換します。
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "answer",
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 10
}
}
},
"init": {
"type": "BinaryExpression",
"operator": "*",
"left": {
"type": "Literal",
"value": 6,
"raw": "6",
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 14
}
}
},
"right": {
"type": "Literal",
"value": 7,
"raw": "7",
"loc": {
"start": {
"line": 2,
"column": 17
},
"end": {
"line": 2,
"column": 18
}
}
},
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 18
}
}
},
"loc": {
"start": {
"line": 2,
"column": 4
},
"end": {
"line": 2,
"column": 18
}
}
}
],
"kind": "var",
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 19
}
}
}
],
"loc": {
"start": {
"line": 2,
"column": 0
},
"end": {
"line": 2,
"column": 19
}
}
}
エル プロブレモ
Anthony の主張はよくできています - Core Erlang は Erlang よりも単純化されたより正規の言語であり、単純な Erlang よりも Javascript へのトランスパイルがより容易になるはずですが、十分に文書化されていません。
Core Erlang の AST のような表現を簡単に取得できます。
{c_module,[],
{c_literal,[],basic_types},
[{c_var,[],{atom1_fn,0}},
{c_var,[],{atom2_fn,0}},
{c_var,[],{bish_fn,1}},
{c_var,[],{boolean_fn,0}},
{c_var,[],{float_fn,0}},
{c_var,[],{int_fn,0}},
{c_var,[],{module_info,0}},
{c_var,[],{module_info,1}},
{c_var,[],{string_fn,0}}],
[],
[{{c_var,[],{int_fn,0}},{c_fun,[],[],{c_literal,[],1}}},
{{c_var,[],{float_fn,0}},{c_fun,[],[],{c_literal,[],2.3}}},
{{c_var,[],{boolean_fn,0}},{c_fun,[],[],{c_literal,[],true}}},
{{c_var,[],{atom1_fn,0}},{c_fun,[],[],{c_literal,[],blue}}},
{{c_var,[],{atom2_fn,0}},{c_fun,[],[],{c_literal,[],'Blue 4 U'}}},
{{c_var,[],{string_fn,0}},{c_fun,[],[],{c_literal,[],"string theory"}}},
{{c_var,[],{bish_fn,1}},
{c_fun,[],
[{c_var,[],'_cor0'}],
{c_case,[],
{c_var,[],'_cor0'},
[{c_clause,[],
[{c_literal,[],bash}],
{c_literal,[],true},
{c_literal,[],berk}},
{c_clause,[],
[{c_literal,[],bosh}],
{c_literal,[],true},
{c_literal,[],bork}},
{c_clause,
[compiler_generated],
[{c_var,[],'_cor1'}],
{c_literal,[],true},
{c_primop,[],
{c_literal,[],match_fail},
[{c_tuple,[],
[{c_literal,[],case_clause},
{c_var,[],'_cor1'}]}]}}]}}},
{{c_var,[],{module_info,0}},
{c_fun,[],[],
{c_call,[],
{c_literal,[],erlang},
{c_literal,[],get_module_info},
[{c_literal,[],basic_types}]}}},
{{c_var,[],{module_info,1}},
{c_fun,[],
[{c_var,[],'_cor0'}],
{c_call,[],
{c_literal,[],erlang},
{c_literal,[],get_module_info},
[{c_literal,[],basic_types},{c_var,[],'_cor0'}]}}}]}
ただし、行の列/番号はありません。したがって、JS を生成する AST を取得できますが、決定的に SourceMap は取得できません。
質問 1必要な行情報を取得するにはどうすればよいですか - (「通常の」Erlang トークンから列情報を取得できます...)
Erlang Core は、生成プロセスにおいて通常の Erlang とは少し異なります。これは、関数呼び出しで変数名を独自の内部変数に置き換え始めるためです。これにより、ソース マップの問題も発生します。例として、次の Erlang 節があります。
bish_fn(A) ->
case A of
bash -> berk;
bosh -> bork
end.
Erlang AST は名前を適切に保持します。
[{function,
{31,{1,8}},
bish_fn,1,
[{clause,
{31,none},
[{var,{31,{11,12}},'A'}],
[[]],
[{'case',
{32,none},
[{var,{32,{11,12}},'A'}],
[{clause,
{33,none},
[{atom,{33,{9,13}},bash}],
[[]],
[{atom,{34,{13,17}},berk}]},
{clause,
{35,none},
[{atom,{35,{9,13}},bosh}],
[[]],
[{atom,{36,{13,17}},bork}]}]}]}]}]},
コア Erlang は、関数で呼び出されるパラメーターの名前を既に変更しています。
'bish_fn'/1 =
%% Line 30
fun (_cor0) ->
%% Line 31
case _cor0 of
%% Line 32
<'bash'> when 'true' ->
'berk'
%% Line 33
<'bosh'> when 'true' ->
'bork'
( <_cor1> when 'true' ->
primop 'match_fail'
({'case_clause',_cor1})
-| ['compiler_generated'] )
end
質問 2 Core Erlang で変数名を保存またはマップするためにできることはありますか?
質問 3コア Erlang が、Erlang にコンパイルしやすく、Erlang コードを変更するツールを作成しやすいように明示的に設計されていることを高く評価しています。
オプション
コアの erlang コードをフォークしてソース マッピング オプションを追加することもできますが、ここでは怠け者のカードをプレイします...
アップデート
Eric の回答に応えて、Core Erlang のセルレコードを生成する方法を明確にする必要があります。最初に、以下を使用して、プレーンな Erlang をコア erlang にコンパイルします。
c(some_module, to_core)
次に、この関数でcore_scan
とからニックネームを使用します。core_parse
compiler.erl
compile(File) ->
case file:read_file(File) of
{ok,Bin} ->
case core_scan:string(binary_to_list(Bin)) of
{ok,Toks,_} ->
case core_parse:parse(Toks) of
{ok, Mod} ->
{ok, Mod};
{error,E} ->
{error, {parse, E}}
end;
{error,E,_} ->
{error, {scan, E}}
end;
{error,E} ->
{error,{read, E}}
end.
問題は、そのツールチェーンに注釈付きの AST を発行させる方法です。これらのオプションを自分で追加する必要があると思います:(