インタラクティブシなREPLをWebページ上に実装する
自分で言語を実装したとき、できればWebページ上でさっと試せるとカッコいいなと思いますよね。たとえばこんな感じで:
- http://golang-jp.org/
- https://islisp.js.org/
- https://jscl-project.github.io/
- https://www.haskell.org/
今回ぼくは自作の言語に対して、同じようなことを実験したので、その成果を記します。
どんなものを作ったか
どうやって実装したか
ここではテキストエリアのonChangeなどの更新をさっと取得・描画できるVue.jsを用いたリアクティブなスタイルで実装していきます。
REPLのデータモデル
まず、REPLに必要なものを考えてみましょう。REPLは三つの要素から出きていると考えました。それすなわち
- 読み込み行
- 出力行
- 過去の読み込み行
の三つです。
読み込み行は、プロンプトを表示し、ユーザの入力を促す行です。
出力行は、ユーザの入力を評価した結果を表示する行です。
過去の読み込み行は、プロンプトと、過去にユーザが入力した文字列から成ります。これは出力行と考えてもよいのですが、なんとなく分けています。
各行をJavaScriptのオブジェクトで表現し、配列で保持しましょう。すなわち、こうです:
[ { type: 'old-prompt', msg: '$ ', input: 'aaaaa'}, { type: 'output', msg: 'aaaaa'}, { type: 'old-prompt', msg: '$ ', input: 'bbbbb'}, { type: 'output', msg: 'bbbbb'}, { type: 'prompt', msg: '$ ' }, ]
REPLのView
そして、あとはそれを描画するViewの部分です。Vue.jsの描画対象を#app
とすると、その要素の中に、データモデルの各type
に対応する要素を書き、v-for
で配列全体を描画する、という流れになります。
<div id="app"> <!-- ここで、各行を描画--> <div v-for="line in lines"> <!-- 出力行 --> <div v-if="line.type == 'output'">{{ line.msg }}</div> <!-- 読み込み行 --> <!-- 入力中の内容はv-modelで逐次取得する --> <div v-if="line.type == 'prompt'"> <div>{{ line.msg }}</div> <input type="text" v-model="readline" v-on:keydown.enter="rep(line.id)"> </div> <!-- 過去の読み込み行 --> <div v-if="line.type == 'old-prompt'"> <div >{{ line.msg }}</div> <span>{{ line.input }}</span> </div> </div> </div>
REPLのcontroller
表示の制御は、REPLのinput
でエンターキーが押されたときに駆動するようにします。REPLなので。
すなわち、JavaScriptのコード全体としてはこんな感じになります。
<script> const app = new Vue({ el: '#app', data: { readline: "", lineCount: 0, lines: [], }, methods: { // 現在の読み込み行の内容でreadとevalをし、 // 過去の読み込み行に更新し、結果と次の読み込み行を追加する rep: function (id) { this.lines[id].input = this.readline.toString() this.lines[id].type = 'old-prompt' // これらはもちろん、あなたの言語のreadとevalですよ! let result = eval(read(this.lines[id].input)) this.print(result) this.prompt() }, // 読み込み行を追加する prompt: function () { let prompt = getCurrentPackageName() + '> ' this.readline = '' this.lines.push( { id: ++this.lineCount, type: 'prompt', msg: prompt }) }, // 出力行を追加する print: function (str) { this.lines.push( { id: ++this.lineCount, type: 'output', msg: str }) } }, created: function () { this.print('welcome to my cool REPL.') this.prompt() } } </script>
あとはCSSでカッコいい見た目や色を設定してあげるだけである。
おわりに
これにより、自作言語ができたときあなたはすぐにWeb REPLを追加することができるようになりました。
まさかLisp以外の記事が増えるとは…。