今回の目標
今回は概ね以下のような動作をする、次の演算子を作ります。
トークンの種類 |
get 文脈ドメインでの挙動 |
---|---|
空括弧[]
|
空配列初期化子[]
|
括弧[ ]
|
配列初期化子[values]
|
前置$#
|
長さ演算子$#array
|
後置括弧[ ]
|
配列要素アクセサarray[index]
|
empty_array : [] ;
array : [1; 2; 3] ;
length : $#[1; 2; 3] ;
item : [1; 2; 3][1];
↓
let empty_attay = [] ;
let attay = [1, 2, 3] ;
let length = [1, 2, 3].length;
let item = [1, 2, 3][1] ;
長さの取得はPerlやbashのように前置$#
で行います。最大の添え字ではなく要素数が得られ、空配列のときに0になります。これは、通常「最大の添え字+1」と考えて問題ありません。
配列要素アクセサarray[index]
は、index
が0のときに先頭の要素を返します。すなわち添え字は0から始まります。
式の区切りはセミコロン;
fluorite-8では、引数やオブジェクト初期化子のような式を区切る表現は原則としてセミコロン;
で行います。
トップレベルや(
)
の中で;
を使うと命令の区切りになり、[
]
の中で;
を使うと項の区切りになり、{
}
の中で;
を使うと宣言項目の区切りになるという、文脈によって意味の変わるセミコロン;
がこの言語の大きな特徴です。
配列初期化子[values]
のパターン
配列初期化子[values]
には次の2種類の現れ方があります。
-
[1]
:values
が1個の要素であるパターン -
[1; 2]
:values
がセミコロン;
で区切られた複数の要素であるパターン
いずれもうまく動くように定義しなければなりません。
空配列初期化子[]
の追加
とりあえず一番簡単そうな空配列初期化子[]
から追加します。
実装
空括弧[]
の文法を追加します。括弧類のルールは新たな種類の括弧を1行で追加できる形にはなっていないので、追加できるように変形します。
Brackets = "(" _ main:Formula _ ")" {
return token("round", [main], location());
}
↓
Brackets = main:(
"(" _ main:Formula _ ")" { return ["round", [main], location()]; }
/ "[" _ "]" { return ["empty_square", [], location()]; }
) {
return token(main[0], main[1], main[2]);
}
なんか冗長に見えますが、他のルールと並べて見たときに規則的になるように書かれています。
空括弧[]
のget
時の挙動を定義します。
env.registerOperatorHandler("get", "empty_square", (env, token) => {
const uid = env.getNextUid();
return toOperation(
"const v_" + uid + " = [];\n",
"(v_" + uid + ")" // [] !== [] であるため一旦変数に入れて渡す
);
});
テスト
空配列が作れるようになりました。
長さ演算子$#array
の追加
空でない配列初期化子の実装の話は複雑なので、その前に一旦長さ演算子と配列要素アクセサの実装をしておきます。
実装
前置$#
トークンのルールを作り、get
時の挙動を定義します。
Left = head:((
"+" { return ["left_plus", location()]; }
/ "-" { return ["left_minus", location()]; }
/ "$#" { return ["left_dollar_hash", location()]; } // ここが増えた
) _)* tail:Pow {
let result = tail;
for (let i = head.length - 1; i >= 0; i--) {
result = token(head[i][0][0], [result], head[i][0][1]);
}
return result;
}
// 大まかな流れは符号演算子と同じ
env.registerOperatorHandler("get", "left_dollar_hash", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const uid = env.getNextUid();
return toOperation(
// 符号演算子とはここが違う ↓ ↓
o1.head + "const v_" + uid + " = " + o1.body + ".length;\n",
"(v_" + uid + ")"
);
});
テスト
array : [];
$#array
let v_0;
const v_1 = [];
v_0 = (v_1);
const v_2 = (v_0).length;
(v_2)
ちゃんと配列に対して.length
の動作を行っています。配列に要素があれば、その要素数が出力されることでしょう。
配列要素アクセサarray[index]
の追加
実装
後置括弧[
]
トークンのルールを作り、get
時の挙動を定義します。
Right = head:Factor tail:(_ (
"(" _ main:Formula _ ")" { return ["right_round", [main], location()] }
// ↓ここを追加
/ "[" _ main:Formula _ "]" { return ["right_square", [main], location()] }
))* {
let result = head;
for (let i = 0; i < tail.length; i++) {
result = token(tail[i][1][0], [result, ...tail[i][1][1]], tail[i][1][2]);
}
return result;
}
env.registerOperatorHandler("get", "right_square", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
env.pushAliasFrame();
const o2 = env.compile("get", token.argument[1]);
env.popAliasFrame();
const uid = env.getNextUid();
return toOperation(
// right_roundとはここが違うだけ ↓ ↓
o1.head + o2.head + "const v_" + uid + " = " + o1.body + "[" + o2.body + "];\n",
"(v_" + uid + ")"
);
});
テスト
array : [];
array[1]
let v_0;
const v_1 = [];
v_0 = (v_1);
const v_2 = (v_0)[(1)];
(v_2)
まだ空配列しか作れないのでundefinedになってしまいましたが、ちゃんと配列に対して[index]
を行っています。
細かい動作はJavaScriptのarray[index]
に従います。
多要素の配列初期化子[values]
の追加
ここでは次のようにコンパイルされる演算子の実装を目指します。
[1; 2; 3]
↓
const v_0 = [];
v_0[v_0.length] = (1);
v_0[v_0.length] = (2);
v_0[v_0.length] = (3);
(v_0)
array
文脈ドメインの追加
配列初期化子[values]
のvalues
に現れたセミコロン;
トークンは、通常の変数宣言文などの動作ではなく、区切られた各式の値を配列に追加する動作を行わなければなりません。
セミコロン;
トークンの挙動を切り替えるために、array
文脈ドメインを新しく導入します。
array
文脈ドメインでのコンパイルに必要な要素
array
文脈ドメインに登場するトークンには、セミコロン;
以外にもいくつかのものが考えられます。
トークンの種類 |
array 文脈ドメインでの挙動 |
---|---|
セミコロン;
|
; で区切られた各式の値を要素に持つ配列を生成する。 |
中置..
|
1 .. 10 と書くと1から10までの整数を要素に持つ配列を生成する。 |
後置空括弧[]
|
array[] と書くとarray の各要素を要素に持つ配列を生成する。 |
セミコロン;
は各式の値を単に順番に追加していくだけでしたが、トークンによってはもう少し複雑な過程によって要素を追加するかもしれません。
例えば、[1 .. 10]
は10個の代入文が並ぶのではなく、ループ構文を用いて次のようにコンパイルされるかもしれません。
const array = [];
for (let i = 1; i <= 10; i++) {
const v_0 = i;
array[array.length] = v_0;
}
array
ここで、上記の中間コードの構成要素が、**配列初期化子[
]
と配列要素を与えるコード1 .. 10
**のどちらによってもたらされたかを分類すると、次のようになります。
番号 | 出身fl8コード | 中間コード |
---|---|---|
1 |
[ ]
|
const array = []; |
2 | 1 .. 10 |
for (let i = 1; i <= 10; i++) { |
3 | 1 .. 10 |
const v_0 = i;
|
4 |
[ ]
|
array[array.length] =
|
5 | 1 .. 10 |
v_0
|
6 |
[ ]
|
;
|
7 | 1 .. 10 |
} |
8 |
[ ]
|
array |
見ての通り、中間コードの出身トークンが複雑に入り乱れています。
1と8は配列オブジェクトの生成処理の前後を囲うものです。
2と7は配列要素の列挙処理の前後を囲うものです。
3と5は配列要素の生成処理の前文と本文です。
4と6は配列要素の受領処理を行うものです。
また、3~6は複数回現れる可能性があります。
これをまとめると、array
挙動オブジェクトには次の性質が必要です。
- 「『配列要素の生成処理を**
get
挙動オブジェクトとして渡すと配列要素の受領処理を付加してrun
挙動オブジェクトとして返却する配列要素受領用set
挙動オブジェクト**』を受け取ると、それを使用して配列要素の列挙処理を行う**run
挙動オブジェクトを返すgenerate
メソッド**」を持っている。
generate
メソッドを使用する側は、「任意の変数に組み立て用の空配列をセットする」中間コードをまず書き、「その配列への追加処理を配列要素の受領処理とする配列要素受領用set
挙動オブジェクト」を作り、generate
メソッドに与え、JavaScript文を得ます。そして、ここまでの一連の処理を前文、組み立て用の変数を本文として、配列オブジェクトの生成処理を行う**get
挙動オブジェクト**を作ります。
壮絶に分かりづらいですが、要するに配列の生成処理は[
]
と1 .. 10
という2個のトークンがメッセージをやり取りしながら4階層の構造を成すことで成立しているということです。
array
文脈ドメインの実装
挙動オブジェクトを生成する関数を作ります。
// 型を表すコメントを追加
function toOperation(head/* string */, body/* string */) {
return {head, body};
}
function toOperationSet(accept/* operationGet => operationRun */, suggestedName = undefined/* string */) {
return {accept, suggestedName};
}
// これ↓を追加した
function toOperationArray(generate/* operationSet => operationRun */) {
return {generate};
}
function toOperationRun(head/* string */) {
return {head};
}
これでarray
文脈ドメインの定義ができました。
多要素の配列初期化子[values]
の実装
上の方で空括弧[]
を追加されたBrackets
ルールに、今度は括弧[
]
を追加します。
Brackets = main:(
"(" _ main:Formula _ ")" { return ["round", [main], location()]; }
// ↓ここが増えた
/ "[" _ main:Formula _ "]" { return ["square", [main], location()]; }
/ "[" _ "]" { return ["empty_square", [], location()]; }
) {
return token(main[0], main[1], main[2]);
}
空括弧[]
のget
挙動、セミコロン;
のarray
挙動を定義します。
env.registerOperatorHandler("get", "square", (env, token) => {
const o1 = env.compile("array", token.argument[0]); // 中身をarray文脈でコンパイル
const uid = env.getNextUid(); // 配列を格納する
// array挙動オブジェクトのgenerateに対して、配列追加セット挙動オブジェクトを渡す
const o2 = o1.generate(toOperationSet(o => toOperationRun(
o.head +
"v_" + uid + "[v_" + uid + ".length] = " + o.body + ";\n"
)));
return toOperation(
"const v_" + uid + " = [];\n" + // 空配列の生成
o2.head, // 配列の組み立て
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("array", "semicolons", (env, token) => {
// 各要素をgetした結果を結合してarray挙動として返す
return toOperationArray(oSet => toOperationRun(
token.argument.map(token2 => oSet.accept(env.compile("get", token2)).head).join("")
));
});
テスト
これで複数の要素を持つ配列が作れるようになりました。
[1; 2; (a : 3; a)]
要素数1の配列問題
さて、ここで問題が生じました。
配列初期化子[values]
は、values
に対してarray
文脈ドメインを要求します。要素数が複数であればvalue
はセミコロン;
トークンだったので、上で定義したセミコロン;
のarray
文脈ドメインの挙動が使われます。
しかし、要素数が1の配列は、values
にセミコロン;
以外のトークンが現れます。
[1 + 2]
そのような状況に対応するには、array
文脈ドメインでの挙動が定義されていないトークンに対してはget
文脈ドメインでのコンパイルを試みるというリダイレクション機能を実装する必要があります。
対策案A
この問題は、Environment
が持つcompile
メソッドに「文脈ドメインがarray
だった場合は、コンパイル失敗時にget
文脈ドメインでのコンパイルを試み、結果を整形して返す」という仕様を加えると解決できます。
compile(domain, token, options = {}) {
const handlerTable = this._operatorRegistry[domain];
if (handlerTable === undefined) throw new Fluorite8CompileError("Unknown domain: " + domain, this, token);
const handler = handlerTable[token.type];
if (handler === undefined) throw new Fluorite8CompileError("Unknown operator: " + domain + "/" + token.type, this, token);
const suggestedName = this._suggestedName;
this._suggestedName = options.suggestedName;
const operation = handler(this, token);
this._suggestedName = suggestedName;
return operation;
}
env.registerOperatorHandler("array", "semicolons", (env, token) => {
return toOperationArray(oSet => toOperationRun(
token.argument.map(token2 => oSet.accept(env.compile("get", token2)).head).join("")
));
});
↓
// コンパイルを試みるメソッド
tryCompile(domain, token, options) {
const handlerTable = this._operatorRegistry[domain];
if (handlerTable === undefined) return null; // 失敗時にnullを返す
const handler = handlerTable[token.type];
if (handler === undefined) return null;
const suggestedName = this._suggestedName;
this._suggestedName = options.suggestedName;
const operation = handler(this, token);
this._suggestedName = suggestedName;
return operation;
}
compile(domain, token, options = {}) {
let operation;
operation = this.tryCompile(domain, token, options); // コンパイル試行
if (operation !== null) return operation; // 成功時はそれを返す
if (domain === "array") { // array限定処理
// スコープの関係上ここにも設置
function toOperationArray(generate/* operationSet => operationRun */) {
return {generate};
}
operation = this.tryCompile("get", token, options); // コンパイル試行
// 成功時は整形して返す
if (operation !== null) return toOperationArray(oSet => oSet.accept(operation));
}
throw new Fluorite8CompileError("Unknown operator: " + domain + "/" + token.type, this, token);
}
env.registerOperatorHandler("array", "semicolons", (env, token) => {
// 「;」で区切られた各要素に対してもarrayでのコンパイルを行う
return toOperationArray(oSet => toOperationRun(
token.argument.map(token2 => env.compile("array", token2).generate(oSet).head).join("")
));
});
先ほどの図は次のように変わります。
これで今回の目標の演算子はすべて意図通り動くように実装されました。ひとまずゴールです。
Environment
とcustomizeEnvironment
の分離問題
対策案Aでは、ソースコードに論理構造上の問題が発生します。対策案A適用後のソースコード全体の大雑把な構成図を示します。
{
~loc関数~
~エラークラス~
class Environment {
~諸機能~
tryCompile(domain, token, options) {
~~
}
compile(domain, token, options = {}) {
let operation;
operation = this.tryCompile(domain, token, options);
if (operation !== null) return operation;
if (domain === "array") {
~toOperationArray関数~ // ■■■■■■■■■■■■■■■■■■■■■■■■
operation = this.tryCompile("get", token, options);
if (operation !== null) return toOperationArray(oSet => oSet.accept(operation));
}
throw new Fluorite8CompileError("Unknown operator: " + domain + "/" + token.type, this, token);
}
~諸機能~
}
function customizeEnvironment(env) {
~indent関数~
~toOperation系関数~ // ■■■■■■■■■■■■■■■■■■■■■■■■
~トークンの挙動定義部~
~組み込み定数定義部~
}
~token関数~
}
~ルール定義部~
Environment
のメソッドcompile
の中に、get
やarray
といった具体的な実装上の表現が現れています。
今までEnvironment
は演算子が挙動をもつことや文脈ドメインという概念があることは知っていましたが、具体的にどのようなものが登録されるのかについては無関心で、それらの設計はすべてcustomizeEnvironment
関数内で完結していました。
対策案Aはこの関心の切り分けに反しています。
また、JavaScript中間コードの生成に関わる部分もこれまではすべてcustomizeEnvironment
内に記述されていましたが、具体的な中間コード生成の処理がcustomizeEnvironment
から漏れ出たために、toOperationArray
が2か所に現れるという奇妙な構造が生じました。
実は中間コードがJavaScriptであるというのは、customizeEnvironment
関数内の特有の取り決めです。別のcustomizeEnvironment
関数を作ればPerlコードを生成することもできます。しかし中間コード生成機能がcustomizeEnvironment
から漏れ出すと、そこら辺の抽象化が崩れてしまいます。
このように対策案Aは処理内容の切り分けからも逸脱しています。
Environment
にコンパイラを登録できるようにする
コンパイル時にどのような処理を行えばよいかは、文脈ドメインによって異なります。そこで、対策案Aに対してEnvironment
に文脈ドメインごとのコンパイル動作を登録する機能を付け加え、それをcustomizeEnvironment
内から登録することで、「具体的な実装」をcustomizeEnvironment
内に収容します。
class Environment {
constructor() {
~~
this._compilerRegistry = Object.create(null); // コンパイラ管理オブジェクト
~~
}
~~
registerCompilerHandler(domain, handler) { // コンパイラの登録メソッド
this._compilerRegistry[domain] = handler;
}
tryCompile(domain, token, options) { // もはやcompileから直接呼ばれない
const handlerTable = this._operatorRegistry[domain];
if (handlerTable === undefined) return null;
const handler = handlerTable[token.type];
if (handler === undefined) return null;
const suggestedName = this._suggestedName;
this._suggestedName = options.suggestedName;
const operation = handler(this, token);
this._suggestedName = suggestedName;
return operation;
}
compile(domain, token, options = {}) {
const handler = this._compilerRegistry[domain]; // コンパイラを検索
if (handler === undefined) throw new Fluorite8CompileError("Unknown compiler: " + domain, this, token);
return handler(this, token, options); // コンパイル処理は委譲する
}
~~
}
これで、env.registerCompilerHandler
によって文脈ドメインごとにコンパイラを登録できるようになりました。
customizeEnvironment
内でコンパイラを登録します。ここからであればtoOperationArray
関数はそのまま見えます。
function customizeEnvironment(env) {
~~
// リダイレクションを行わない普通のコンパイラ
function registerDefaultCompilerHandler(domain) {
env.registerCompilerHandler(domain, (env, token, options) => {
let operation;
operation = env.tryCompile(domain, token, options);
if (operation !== null) return operation;
throw new Fluorite8CompileError("Unknown operator: " + domain + "/" + token.type, env, token);
});
}
// コンパイラの登録
registerDefaultCompilerHandler("get");
registerDefaultCompilerHandler("set");
registerDefaultCompilerHandler("run");
// arrayのコンパイルには特殊な動作を行う
env.registerCompilerHandler("array", (env, token, options) => {
let operation;
operation = env.tryCompile("array", token, options);
if (operation !== null) return operation;
operation = env.tryCompile("get", token, options);
if (operation !== null) return toOperationArray(oSet => oSet.accept(operation));
throw new Fluorite8CompileError("Unknown operator: array/" + token.type, env, token);
});
~~
}
これで、具体的な実装をcustomizeEnvironment
内に保ったままenv.compile
の挙動をカスタマイズすることが出来ました。
個々のコンパイラの実装がcustomizeEnvironment
内に移動しただけで、大まかな戦略は実装案Aと同じです。
ちゃんと要素数が1の配列の生成が出来ています。
テスト
ここまでに追加した文法をすべて使ったテストです。
[
[];
[1];
[1; 2; 3];
$#[1; 2; 3];
[1; 2; 3][1]
]
const v_0 = [];
const v_1 = [];
v_0[v_0.length] = (v_1);
const v_2 = [];
v_2[v_2.length] = (1);
v_0[v_0.length] = (v_2);
const v_3 = [];
v_3[v_3.length] = (1);
v_3[v_3.length] = (2);
v_3[v_3.length] = (3);
v_0[v_0.length] = (v_3);
const v_4 = [];
v_4[v_4.length] = (1);
v_4[v_4.length] = (2);
v_4[v_4.length] = (3);
const v_5 = (v_4).length;
v_0[v_0.length] = (v_5);
const v_6 = [];
v_6[v_6.length] = (1);
v_6[v_6.length] = (2);
v_6[v_6.length] = (3);
const v_7 = (v_6)[(1)];
v_0[v_0.length] = (v_7);
(v_0)
[
[],
[
1
],
[
1,
2,
3
],
3,
2
]
配列の実装が完了しました。
まとめ
ここまでに出来上がったPEG.jsコードです。
**[開閉]**
{
function loc(env, token) {
return `(${env.getFile()},L:${token.location.start.line},C:${token.location.start.column})`;
}
class Fluorite8CompileError extends Error {
constructor(message, env, token) {
super(message + " " + loc(env, token));
this.name = "Fluorite8CompileError";
this.file = env.getFile();
this.token = token;
}
}
class Environment {
constructor() {
this._nextUid = 0;
this._aliasFrame = Object.create(null);
this._operatorRegistry = Object.create(null);
this._compilerRegistry = Object.create(null);
this._file = "anonymous";
this._suggestedName = undefined;
}
getNextUid() {
return this._nextUid++;
}
registerAlias(alias, handlerTable) {
this._aliasFrame[alias] = handlerTable;
}
resolveAlias(alias) {
return this._aliasFrame[alias];
}
pushAliasFrame() {
this._aliasFrame = Object.create(this._aliasFrame);
}
popAliasFrame() {
this._aliasFrame = Object.getPrototypeOf(this._aliasFrame);
}
registerOperatorHandler(domain, type, handler) {
if (this._operatorRegistry[domain] === undefined) this._operatorRegistry[domain] = Object.create(null);
this._operatorRegistry[domain][type] = handler;
}
registerCompilerHandler(domain, handler) {
this._compilerRegistry[domain] = handler;
}
tryCompile(domain, token, options) {
const handlerTable = this._operatorRegistry[domain];
if (handlerTable === undefined) return null;
const handler = handlerTable[token.type];
if (handler === undefined) return null;
const suggestedName = this._suggestedName;
this._suggestedName = options.suggestedName;
const operation = handler(this, token);
this._suggestedName = suggestedName;
return operation;
}
compile(domain, token, options = {}) {
const handler = this._compilerRegistry[domain];
if (handler === undefined) throw new Fluorite8CompileError("Unknown compiler: " + domain, this, token);
return handler(this, token, options);
}
setFile(file) {
this._file = file;
}
getFile() {
return this._file;
}
getSuggestedName() {
if (this._suggestedName === undefined) return "anonymous";
return this._suggestedName;
}
}
function customizeEnvironment(env) {
function indent(code) {
return " " + code.replace(/\n(?!$)/g, "\n ");
}
function toOperation(head/* string */, body/* string */) {
return {head, body};
}
function toOperationSet(accept/* operationGet => operationRun */, suggestedName = undefined/* string */) {
return {accept, suggestedName};
}
function toOperationArray(generate/* operationSet => operationRun */) {
return {generate};
}
function toOperationRun(head/* string */) {
return {head};
}
function registerDefaultCompilerHandler(domain) {
env.registerCompilerHandler(domain, (env, token, options) => {
let operation;
operation = env.tryCompile(domain, token, options);
if (operation !== null) return operation;
throw new Fluorite8CompileError("Unknown operator: " + domain + "/" + token.type, env, token);
});
}
registerDefaultCompilerHandler("get");
registerDefaultCompilerHandler("set");
registerDefaultCompilerHandler("run");
env.registerCompilerHandler("array", (env, token, options) => {
let operation;
operation = env.tryCompile("array", token, options);
if (operation !== null) return operation;
operation = env.tryCompile("get", token, options);
if (operation !== null) return toOperationArray(oSet => oSet.accept(operation));
throw new Fluorite8CompileError("Unknown operator: array/" + token.type, env, token);
});
env.registerOperatorHandler("get", "integer", (env, token) => toOperation("", "(" + parseInt(token.argument, 10) + ")"));
env.registerOperatorHandler("get", "identifier", (env, token) => {
const handlerTable = env.resolveAlias(token.argument);
if (handlerTable === undefined) throw new Fluorite8CompileError("Unknown identifier: " + token.argument, env, token);
const handler = handlerTable["get"];
if (handler === undefined) throw new Fluorite8CompileError("Unreadable identifier: " + token.argument, env, token);
return handler(env);
});
env.registerOperatorHandler("get", "round", (env, token) => {
env.pushAliasFrame();
const o1 = env.compile("get", token.argument[0], {
suggestedName: env.getSuggestedName(),
});
env.popAliasFrame();
return o1;
});
env.registerOperatorHandler("get", "square", (env, token) => {
const o1 = env.compile("array", token.argument[0]);
const uid = env.getNextUid();
const o2 = o1.generate(toOperationSet(o => toOperationRun(
o.head +
"v_" + uid + "[v_" + uid + ".length] = " + o.body + ";\n"
)));
return toOperation(
"const v_" + uid + " = [];\n" +
o2.head,
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "empty_square", (env, token) => {
const uid = env.getNextUid();
return toOperation(
"const v_" + uid + " = [];\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "left_plus", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const uid = env.getNextUid();
return toOperation(
o1.head + "const v_" + uid + " = +" + o1.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "left_minus", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const uid = env.getNextUid();
return toOperation(
o1.head + "const v_" + uid + " = -" + o1.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "left_dollar_hash", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const uid = env.getNextUid();
return toOperation(
o1.head + "const v_" + uid + " = " + o1.body + ".length;\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "right_round", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
env.pushAliasFrame();
const o2 = env.compile("get", token.argument[1]);
env.popAliasFrame();
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + "(" + o2.body + ");\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "right_square", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
env.pushAliasFrame();
const o2 = env.compile("get", token.argument[1]);
env.popAliasFrame();
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + "[" + o2.body + "];\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "plus", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + " + " + o2.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "minus", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + " - " + o2.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "asterisk", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + " * " + o2.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "slash", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = " + o1.body + " / " + o2.body + ";\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "circumflex", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const uid = env.getNextUid();
return toOperation(
o1.head + o2.head + "const v_" + uid + " = Math.pow(" + o1.body + ", " + o2.body + ");\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "ternary_question_colon", (env, token) => {
const o1 = env.compile("get", token.argument[0]);
const o2 = env.compile("get", token.argument[1]);
const o3 = env.compile("get", token.argument[2]);
const uid = env.getNextUid();
return toOperation(
o1.head +
"let v_" + uid + ";\n" +
"if (" + o1.body + ") {\n" +
indent(
o2.head +
"v_" + uid + " = " + o2.body + ";\n"
) +
"} else {\n" +
indent(
o3.head +
"v_" + uid + " = " + o3.body + ";\n"
) +
"}\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "minus_greater", (env, token) => {
const name = token.argument[0].argument;
const uidBody = env.getNextUid();
env.pushAliasFrame();
env.registerAlias(token.argument[0].argument, {
get: (env, token) => toOperation("", "(v_" + uidBody + ")"),
});
const operationBody = env.compile("get", token.argument[1]);
env.popAliasFrame();
const label = `${env.getSuggestedName()}${loc(env, token)}`;
const uidSymbol = env.getNextUid();
const uid = env.getNextUid();
return toOperation(
"const v_" + uidSymbol + " = Symbol(" + JSON.stringify(label) + ");\n" +
"const v_" + uid + " = " + "{[v_" + uidSymbol + "]: function(v_" + uidBody + ") {\n" +
indent(
operationBody.head +
"return " + operationBody.body + ";\n"
) +
"}}[v_" + uidSymbol + "];\n",
"(v_" + uid + ")"
);
});
env.registerOperatorHandler("get", "semicolons", (env, token) => {
const heads = [];
for (let i = 0; i < token.argument.length - 1; i++) {
const operation = env.compile("run", token.argument[i]);
heads.push(operation.head);
}
const operation = env.compile("get", token.argument[token.argument.length - 1]);
return toOperation(
heads.join("") +
operation.head,
operation.body
);
});
env.registerOperatorHandler("set", "identifier", (env, token) => {
const handlerTable = env.resolveAlias(token.argument);
if (handlerTable === undefined) throw new Fluorite8CompileError("Unknown identifier: " + token.argument, env, token);
const handler = handlerTable["set"];
if (handler === undefined) throw new Fluorite8CompileError("Readonly identifier: " + token.argument, env, token);
return handler(env);
});
env.registerOperatorHandler("array", "semicolons", (env, token) => {
return toOperationArray(oSet => toOperationRun(
token.argument.map(token2 => env.compile("array", token2).generate(oSet).head).join("")
));
});
env.registerOperatorHandler("run", "colon", (env, token) => {
const name = token.argument[0].argument;
const uid = env.getNextUid();
env.registerAlias(name, {
get: (env, token) => toOperation("", "(v_" + uid + ")"),
set: (env, token) => toOperationSet(o => toOperationRun(o.head + "v_" + uid + " = " + o.body + ";\n"), name),
});
const operation = env.compile("get", token.argument[1], {
suggestedName: name,
});
return toOperationRun(
"let v_" + uid + ";\n" +
operation.head +
"v_" + uid + " = " + operation.body + ";\n"
);
});
env.registerOperatorHandler("run", "equal", (env, token) => {
const operationSetLeft = env.compile("set", token.argument[0]);
const operationGetRight = env.compile("get", token.argument[1], {
suggestedName: operationSetLeft.suggestedName,
});
return toOperationRun(
operationSetLeft.accept(operationGetRight).head
);
});
env.registerAlias("PI", {
get: (env, token) => toOperation("", "(" + Math.PI + ")"),
});
}
function token(type, argument, location) {
return {type, argument, location};
}
}
Root = _ main:Formula _ {
const token = main;
let operation;
try {
const env = new Environment();
env.setFile("OnlineDemo");
customizeEnvironment(env);
operation = env.compile("get", main);
} catch (e) {
console.log(e);
return ["CompileError: " + e, token];
}
const code = operation.head + operation.body;
let result;
try {
result = eval(code);
} catch (e) {
console.log(e);
return ["RuntimeError: " + e, code, token];
}
return [result, code, token];
}
Formula = Semicolons
Semicolons = head:Lambda tail:(_ (";" { return location(); }) _ Lambda)* {
if (tail.length == 0) return head;
return token("semicolons", [head, ...tail.map(s => s[3])], tail[0][1]);
}
Lambda = head:(If _ (
"->" { return ["minus_greater", location()]; }
/ ":" { return ["colon", location()]; }
/ "=" { return ["equal", location()]; }
) _)* tail:If {
let result = tail;
for (let i = head.length - 1; i >= 0; i--) {
result = token(head[i][2][0], [head[i][0], result], head[i][2][1]);
}
return result;
}
If = head:Add _ operator:("?" { return location(); }) _ body:If _ ":" _ tail:If {
return token("ternary_question_colon", [head, body, tail], operator);
}
/ Add
Add = head:Term tail:(_ (
"+" { return ["plus", location()]; }
/ "-" { return ["minus", location()]; }
) _ Term)* {
let result = head;
for (let i = 0; i < tail.length; i++) {
result = token(tail[i][1][0], [result, tail[i][3]], tail[i][1][1]);
}
return result;
}
Term = head:Left tail:(_ (
"*" { return ["asterisk", location()]; }
/ "/" { return ["slash", location()]; }
) _ Left)* {
let result = head;
for (let i = 0; i < tail.length; i++) {
result = token(tail[i][1][0], [result, tail[i][3]], tail[i][1][1]);
}
return result;
}
Left = head:((
"+" { return ["left_plus", location()]; }
/ "-" { return ["left_minus", location()]; }
/ "$#" { return ["left_dollar_hash", location()]; }
) _)* tail:Pow {
let result = tail;
for (let i = head.length - 1; i >= 0; i--) {
result = token(head[i][0][0], [result], head[i][0][1]);
}
return result;
}
Pow = head:Right _ operator:(
"^" { return ["circumflex", location()]; }
) _ tail:Left {
return token(operator[0], [head, tail], operator[1]);
}
/ Right
Right = head:Factor tail:(_ (
"(" _ main:Formula _ ")" { return ["right_round", [main], location()] }
/ "[" _ main:Formula _ "]" { return ["right_square", [main], location()] }
))* {
let result = head;
for (let i = 0; i < tail.length; i++) {
result = token(tail[i][1][0], [result, ...tail[i][1][1]], tail[i][1][2]);
}
return result;
}
Factor = Integer
/ Identifier
/ Brackets
Integer = main:$[0-9]+ {
return token("integer", main, location());
}
Identifier = main:$([a-zA-Z_] [a-zA-Z0-9_]*) {
return token("identifier", main, location());
}
Brackets = main:(
"(" _ main:Formula _ ")" { return ["round", [main], location()]; }
/ "[" _ main:Formula _ "]" { return ["square", [main], location()]; }
/ "[" _ "]" { return ["empty_square", [], location()]; }
) {
return token(main[0], main[1], main[2]);
}
_ = [ \t\r\n]*
この時点で次の特徴があります。
- ソースコードから構文木の生成
- 構文木から中間コードの生成
- 中間コードの評価
- 副作用を持つ演算の適切な順序での実行
- エラー出力の改善
- トークンの文脈の管理
- 識別子の文脈の管理
- トークンの出現場所の管理
- ソースファイル名の管理
- 関数名の推測
- 文脈ドメインごとのコンパイラの管理 (新規)
- スペース
- 識別子
- 組み込み定数 (ドメイン:
get
)PI
- 引数 (ドメイン:
get
) - 変数 (ドメイン:
get
set
)
- 組み込み定数 (ドメイン:
-
get
ドメイントークン- 整数リテラル
123
- 識別子
identifier
- 丸括弧
(formula)
- 空配列初期化子
[]
(新規) - 配列初期化子
[values]
(新規) - 関数呼び出し
function(argument)
- 配列要素アクセサ
array[index]
(新規) - べき乗
a ^ b
- 符号
+a
-a
- 長さ演算子
$#array
(新規) - 加減乗除
a + b
a - b
a * b
a / b
- 三項演算子
cond ? then ? else
- ラムダ式
arg -> formula
- 文
;
- 整数リテラル
-
set
ドメイントークン- 識別子
identifier
- 識別子
-
array
ドメイントークン (新規)- 配列要素区切り
;
(新規)
- 配列要素区切り
-
run
ドメイントークン- 変数宣言
variable : value
- 代入
acceptor = value
- 変数宣言
文脈リダイレクションは、あらゆる文脈におけるパースを統一された1個のシンタックスとして表現するfluorite-8を象徴する機構です。