はじめに
2023年の1月下旬よりQiita様へ参加させていただき、自作言語 Re:Mind(リマインド)の企画趣旨をQiita内の記事でご説明させていただいております。まだ実装がなにもないので、そろそろ試作に着手しようかと考えております。本記事はその試作品の内部設計情報で、ターゲット言語をJavaとしています。
この記事内容の作業目的
試作にあたって内部構想を、日本語ロジック仕様記述言語 Re:Mindで記述しておきます。本試作範囲は策定仕様のごくごく一部です。ターゲット言語は一部のみ対応、言語の対応ソース構文も超限定的です。
この記事内容の保証
※この記事には仕様的な情報が含まれます。自作言語はまだ設計開発中のため、発案者による実装は存在しません。本記事に開示された仕様は次バージョンの仕様においては予告なく変更される場合があります。また、このバージョンの仕様内で補足や追加がなされる場合があります。
補足
日本語ロジック仕様記述言語 Re:Mind(リマインド)はオープンな設計言語仕様で、どなたでもこの記法を使いロジックを記述することができます。
日本語トランスコンパイラ言語 Re:Mind(リマインド)はオープンな実装言語仕様で、どなたでもコンパイラ・トランスコンパイラを実装することができます。
試作概要・試作目的
本試作では日本語トランスコンパイラ言語 Re:Mind(リマインド)の原型を検証し、いろいろな問題点を洗い出します。ターゲット言語のソースコード入力は実装言語のソースコードのインライン記述にとどまります。試作範囲は段階的に拡張してまいります。
本試作の設計言語
・ロジック仕様記述言語 Re:Mind
本試作の実装言語
・C#
コンソールアプリケーションとして実装します。
本試作のターゲット言語
本試作のターゲット言語としては下記の3言語を想定しております、本記事はそのうちのJavaに該当する内容です。
・C#
・Java ←本記事(仕様)の対象言語
・Mind
・想定稼働OS Windows
本試作のターゲット言語の対応仕様範囲
"Hello World!"がコンソール出力されるソースコードを入力して(インライン・リテラルコーディング)、ターゲット言語のソースコードを(双方だらだらと)コンソール出力する。(どちらを出力するかは起動パラメータにする。)
お題のターゲット言語出力ソースコード
Java
package HelloWorld;
import java.lang.System;
/**
* プログラム型
*/
public class Program
{
/**
* メイン
* @param args 引数
*/
public static void main(String[] args)
{
System.out.println("Hello World!");
}
}
お題のトランスコンパイラ言語 Re:Mind入力ソースコード(ターゲット言語依存)
Java
Javaの場合、コンソールライブラリがSystemクラスのメンバクラスのPrintStreamのインスタンスを介して呼び出すというところが、ちょっとトランスコンパイラ的にめんどうなところです。
□パッケージ HelloWorld
/**
* Program
*/
▽public クラス プログラム型
/**
* main
* @param 引数 args
*/
▽static メイン(string[] 引数)
□コンソール.一行表示する("Hello World!")
△
△
■インポートする java.lang.System
▼public class コンソール PrintStream System.out
▼public static 一行表示する(string value)
println (String value)
▲
▲
2023/07/29 訂正
メインの戻り値はvoidを明示に訂正しています。
■importは■インポートするに訂正しています。
PrintStreamのインスタンス名をoutではなくSystem.outに訂正しています。
本試作の内部設計の妥当性
試作のため本実装には採用されない可能性があります。また、試作品の実装段階でフィードバック訂正される場合があります。
クラス構成
お手数ですが内部設計(基本構成)をご参照ください。
クラス・メンバの構成
お手数ですが内部設計(基本構成)をご参照ください。
クラスTargetJava
ターゲット言語Javaのトークナイザとコードジェネレータを保持するクラスの実装です。
列挙体、クラス(変数のみ)の宣言
お手数ですが内部設計(基本構成)をご参照ください。
SubMain
クラスProgramのメイン関数から、コンソールアプリケーションの起動パラメータで"java"が指定されたときに実行されるサブメイン関数です。(クラスTargetJavaの実質メイン関数)
このレベルでのC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
SubMainから直接呼ばれる主要関数
名称辞書を初期化する
Dictionary型の名称辞書を今回のターゲット言語の使用する範囲で予約されている予約語の日本語名とオリジナル英語名を対応づけしておきます。
/**
* initNameDictionary
* @param 名称辞書 nameDictionary
*/
▽static 名称辞書を初期化する(辞書型<string, string> 名称辞書)
□名称辞書.追加("パッケージ","package")
□名称辞書.追加("クラス","class")
□名称辞書.追加("インポートする","import")
□名称辞書.追加("string","String")
△
2023/07/29 訂正
□名称辞書.追加("インポートする","import")
□名称辞書.追加("string","String")
を追記しています。
ノードカテゴライズする
第1引数の「ソース」はRe:Mind(リマインド)のソースコード文字列全体です。「ソース」を改行で分割して、各ソース行の先頭文字「ノード」でソース行をカテゴライズした第2引数「ソース行リスト」に格納し、「ソース」の中で□パッケージ文と■import文のソースコード行文字列を第3引数「ユージングリスト」に格納します。
/**
* nodeCategorized
* @param ソース source
* @param ソース行リスト sourcesLineList
* @param インポートリスト usingList
*/
▽static int ノードカテゴライズする(string ソース,リスト型<ソース行型> ソース行リスト,リスト型<string> インポートリスト,辞書型<string, string> 名称辞書)
・int 戻り値 = 1
□ライン文字列配列 = ソース.Split("\r\n")
〇i=0,i<ライン文字列配列.Length,i++ 繰り返す
・ソース行 =new ソース行型
・ソース行文字列=ライン文字列配列[i]
□ソース行.ソース行番号=i+1
□ソース行.オリジナルソース文字列=ソース行文字列
□ソース行文字列=ソース行文字列.Trim()
□ソース行.オリジナルインデント=ソース行.オリジナルソース文字列.Substring(0,ソース行.オリジナルソース文字列.Length-ソース行文字列.Length)
□ソース行.ノード種類=ソース行ノードを判定する(ref ソース行文字列)//ノード種類を判定する
◇ソース行.ノード種類==ノード種類.例外 の場合
コンソール.一行表示する(ソース行.オリジナルソース文字列)
□戻り値を 返す
◇ここまで
□ソース行.行文字列=ソース行文字列
□ソース行リスト.追加(ソース行)
◇ソース行.ノード種類==ノード種類.パッケージ文 || ソース行.ノード種類==ノード種類.引用開始 の場合
□ソース行文字列=名称辞書でターゲットソース行を置換する(名称辞書,ソース行文字列)
□インポートリスト.追加(ソース行文字列+";")//Usingリストに追加
◇ここまで
〇ここまで
□ret=0//ここまで進行したら成功
□戻り値を 返す
△
2023/07/29 訂正
ユージングリストをインポートリストに訂正しています。
名称辞書を収集する
第1引数の「ソース行リスト」はノードカテゴライズするでカテゴライズされたソース行型のリストで、これをもう一度分析して、第2引数の名称辞書に追記して返します。
ノード種類がJavaDoc備考開始の場合はソース行を先読みして、後続するソース行のノード種類がavaDoc備考途中の場合は英字の関数名をValueとして、定義開始の場合は日本語関数名をKeyとして名称辞書に格納します。また、JavaDoc備考変数の場合は日本語引数名、英字引数名を名称辞書に格納します。
JavaのコンソールライブラリがSystemクラスのメンバクラスのPrintStreamのインスタンスを介して呼び出すというところはここで対応します。
/**
* collectNameDictionary
* @param ソース行リスト sourcesLineList
* @param 名称辞書 nameDictionary
*/
▽static int 名称辞書を収集する(リスト型<ソース行型> ソース行リスト,辞書型<string, string> 名称辞書)
・int 戻り値 = 1
〇i=0,i<ソース行リスト.Count,i++ 繰り返す
・ソース行=ソース行リスト.ElementAt(i)
・ノード種類 =ソース行.ノード種類
・ソース行文字列=ソース行.行文字列 ?? ""
◇ノード種類 で分岐する
◇ノード種類列挙体.JavaDoc備考開始//JavaDoc備考開始
//ソース行を先読みする
・string key=""
・string value=""
・string valueParam=""
・bool appearValue=false
〇j=i+1,j<ソース行リスト.Count,j++ 繰り返す
・ソース行forward=ソース行リスト.ElementAt(j)
・ノード種類forward =ソース行forward.ノード種類
・ソース行文字列forward=ソース行forward.行文字列 ?? ""
◇ノード種類forward=ノード種類.JavaDoc備考途中 && appearValue==false の場合
□appearValue=true
□value=ソース行forward.Replace(ソース行のノード種類.JavaDoc備考途中記号,"").Trim()
◇ここまで
◇ノード種類forward==ノード種類.定義開始 && appearValue の場合
□key=ソース行文字列forward.置換("static","").置換("public","")置換("private","").Trim()
・pos=key.IndexOf(ソース行のノード種類.引数開始記号)
◇pos>0 の場合//かっこが出現した場合
key=key.置換(key.Substring(pos),"")//かっこ開始から右側は除去
◇ここまで
□pos=key.LastIndexOf(" ")
◇pos>0 の場合//空白が出現した場合
key=key.Substring(pos).Trim()//空白から右側を対象とする
◇ここまで
□名称辞書.追加(key,value)
□脱出する//ND_DEF_BEGINで必ずbreak
◇ここまで
◇ノード種類forward==ノード種類.JavaDoc備考変数 の場合
□valueParam=ソース行文字列forward.置換(ソース行のノード種類.JavaDoc備考途中記号,"").Trim();
□valueParam=valueParam.置換(ソース行のノード種類.JavaDoc備考変数記号,"").Trim();
・keyValue=valueParam.Split(" ")
◇keyValue.Length==2 の場合
□名称辞書.追加(keyValue[0],keyValue[1])
◇ここまで
◇ここまで
〇ここまで
□脱出する
◇ノード種類列挙体.JavaDoc備考途中//JavaDoc備考途中
◇ノード種類列挙体.JavaDoc備考変数//JavaDoc備考変数
◇ノード種類列挙体.JavaDoc備考終了//JavaDoc備考終了
□脱出する
◇ノード種類列挙体.引用定義開始//引用定義開始
・string key2=""
・string value2=""
・string valueParam2=""
//classが存在したら当該行でkeyValue取得
◇ソース行文字列.IndexOf("class")>=0 の場合
□valueParam2=ソース行文字列.置換("static","").置換("public","").置換("private","").置換("class","").Trim()
・keyValue=valueParam2.Split(" ")
◇keyValue.Length==2 の場合
□名称辞書.追加(keyValue[0],keyValue[1])
◇他に keyValue.Length==3 の場合
□名称辞書.追加(keyValue[0],keyValue[2])//3つの場合はインスタンス名が3つ目想定
◇ここまで
◇他に
//classが存在しなければソース行を先読みする
□key2=ソース行文字列.置換("static","").置換("public","").置換("private","").Trim()
・pos=key2.IndexOf(ソース行のノード種類.引数開始記号)
◇pos>0 の場合//かっこが出現した場合
□key2=key2.置換(key2.Substring(pos),"").Trim()//かっこ開始から右側は除去
◇ここまで
・ソース行forward=ソース行リスト.ElementAt(i+1)
・ノード種類forward =ソース行forward.ノード種類
・ソース行文字列forward=ソース行forward.行文字列 ?? ""
□value2=ソース行文字列forward.置換("static","").置換("public","").置換("private","").Trim()
□pos=value2.IndexOf(ソース行のノード種類.引数開始記号)
◇pos>0 の場合//かっこが出現した場合
□value2=value2.置換(value2.Substring(pos),"").Trim()//かっこ開始から右側は除去
◇ここまで
□名称辞書.追加(key2,value2)
◇ここまで
□脱出する
//行レベルカテゴライズ済のため例外は生じない
◇ここまで
〇ここまで
□ret=0//ここまで進行したら成功
□戻り値を 返す
△
トランスコンパイルする
第1引数の「ソース行リスト」はノードカテゴライズするでカテゴライズされたソース行型のリストで、これをもう一度分析して、第3引数の名称辞書を使ってターゲット言語の英字に置換してから、第2引数のターゲットソース行型のリストを返します。
この関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
コード出力する
ユージングリストの内容を最初に出力して、続いてターゲットソース行リストを出力しますが、ソース行リストから元のインデントを復元しています。
この関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
支援関数
ノードカテゴライズする から呼ばれる支援関数
ソース行ノードを判定する
ソース行のノードを判定して、ノード種類列挙体の特定値を返します。
この関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
トランスコンパイルする から呼ばれる支援関数
ターゲットソース行を生成する
/**
* generateTargetSouceLine
* @param ソース行 sourceLines
* @param ターゲットソース行リスト targetSourcesLineList
* @param ノード種類 nodeKind
* @param 名称辞書 nameDictionary
* @param ターゲットソース行番号 targetSouceLineNumber
*/
▽static int ターゲットソース行を生成する(ソース行型 ソース行,リスト型<ターゲットソース行型> ターゲットソース行リスト,ノード種類列挙体 ノード種類,辞書型<string, string> 名称辞書,ref int ターゲットソース行番号)
・int 戻り値 = 1
・ターゲットソース行 =new ターゲットソース行型
・ソース行文字列=ソース行.行文字列 ?? ""
◇ノード種類 で分岐する
◇ノード種類列挙体.定義開始//定義開始
□ターゲットソース行番号+=1
□ターゲットソース行.ソース行番号=ソース行.ソース行番号
□ターゲットソース行.ターゲット行文字列=名称辞書でターゲットソース行を置換する(名称辞書,ソース行文字列)//名称辞書で置換する
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□ターゲットソース行番号+=1
□ターゲットソース行 =new ターゲットソース行型
□ターゲットソース行.ソース行番号=ソース行.ソース行番号
□ターゲットソース行.ターゲット行文字列=ターゲットソース行のノード種類.定義開始+改行コード//定義開始 中かっこ{を追加
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.定義終了//定義終了
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列=ターゲットソース行のノード種類.定義終了+改行コ//定義終了 中かっこ}を追加
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.引用定義終了//引用定義終了
□脱出する
◇ノード種類列挙体.宣言開始//宣言開始
◇ノード種類列挙体.実行開始//実行開始
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列=名称辞書でターゲットソース行を置換する(名称辞書,ソース行文字列)+";"//名称辞書で置換し実行文終端;を追加
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.引用開始//引用開始
◇ノード種類列挙体.引用定義開始//引用定義開始
□脱出する
◇ノード種類列挙体.単行備考開始//単行備考開始
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列=ソース行.行文字列//単行備考はそのまま
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.JavaDoc備考開始//JavaDoc備考開始
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列=ソース行.行文字列//単行備考はそのまま
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.JavaDoc備考途中//JavaDoc備考途中
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列= 名称辞書でJavaDoc備考途中を置換する(名称辞書,ソース行文字列,ターゲットソース行のノード種類.JavaDoc備考途中)
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.JavaDoc備考変数//JavaDoc備考変数
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列= 名称辞書でJavaDoc備考変数を置換する(名称辞書,ソース行文字列,ターゲットソース行のノード種類.JavaDoc備考変数)
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
◇ノード種類列挙体.JavaDoc備考終了//JavaDoc備考終了
□ターゲットソース行番号+=1
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行.ターゲット行文字列=ソース行.行文字列//単行備考はそのまま
□ターゲットソース行.ターゲットソース行番号=ターゲットソース行番号
□ターゲットソース行リスト.追加(ターゲットソース行)
□脱出する
//行レベルカテゴライズ済のため例外は生じない
◇ここまで
□ret=0//ここまで進行したら成功
□戻り値を 返す
△
名称辞書でターゲットソース行を置換する
支援関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
名称辞書でJavaDoc備考途中を置換する
支援関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
名称辞書でJavaDoc備考変数を置換する
支援関数のC#をターゲットとしたクラスTargetCsとの差異はありません。詳細はお手数ですが内部設計(ターゲット言語C#)をご参照ください。
備考
2023/07/29 試作品の再実行に伴い一部設計・仕様を訂正しています。
参考リンク