Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8952 articles
Browse latest View live

WSL2でElectronを動かすの巻

$
0
0

こんにちは!

お鍋が美味しい季節になってまいりました。おさむです。
WSL2+Electronな環境を構築する機会がありましたので、やり方をシェアしたいと思います。

この記事のゴール

WSL2環境下にて、Electronで作成した画面が表示出来るところまで

前提

  1. WSL2のセットアップが完了している
  2. Ubuntu 18.04 LTSのOSイメージを使用している(たぶん他のディストリでもいける)

やりかた

①Node.jsとnpmのインストール

apt-getで入れてしまいましょう。

# Node.js$ sudo apt-get install-y nodejs

# npm$ sudo apt-get install-y npm

②プロジェクトの作成

Electron Documentationに従い、npmのプロジェクトを作成します。

# ディレクトリを作成し、その中に移動$ mkdir sampleProject
$ cd sampleProject

# npmプロジェクト作成。質問に答えながらいい感じに$ npm init

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json`for definitive documentation on these fields
and exactly what they do.

Use `npm install<pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (sampleProject) sample
version: (1.0.0) 1.0.0
description:
entry point: (index.js) index.js
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /home/osamu/sampleProject/package.json:

{"name": "sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {"test": "echo \"Error: no test specified\"&& exit 1"},
  "author": "",
  "license": "ISC"}


Is this ok? (yes)yes

続いて、サンプルのHTMLとJavaScriptの配置。
これらもElectron Documentationのサンプルソースをお借りしましょう。
npm initを実行したディレクトリに、それぞれindex.htmlindex.jsという名前で保存します。

index.html
<html><head><metacharset="UTF-8"><title>Hello World!</title><!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag --><metahttp-equiv="Content-Security-Policy"content="script-src 'self' 'unsafe-inline';"/></head><body><h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body></html>
index.js
const{app,BrowserWindow}=require('electron')functioncreateWindow(){// Create the browser window.letwin=newBrowserWindow({width:800,height:600,webPreferences:{nodeIntegration:true}})// and load the index.html of the app.win.loadFile('index.html')}app.on('ready',createWindow)

こんな感じで配置できればOKです。

$ ls-l
total 12
-rw-r--r-- 1 osamu osamu 546 Nov 16 11:31 index.html
-rw-r--r-- 1 osamu osamu 342 Nov 16 11:31 index.js
-rw-r--r-- 1 osamu osamu 200 Nov 16 11:30 package.json

③Electronのインストール

Electron Documentationに記載の通り、Electronのインストールを実行します。

# ローカルインストールの場合はこっち$ npm install--save-dev electron
# おまじない$ sudo node node_modules/electron/install.js

# グローバルインストールの場合はこっち$ sudo npm install-g electron
# おまじない。ディストリによっては/usr/lib/node_modules/electron/install.jsかも$ sudo node /usr/local/lib/node_modules/electron/install.js


おまじないって何?

npm installしただけでは、electronの起動時に以下のような例外が出て起動に失敗します。
Electron failed to install correctly, please delete node_modules/electron and try installing again

この例外をスローしている箇所のソースを見てみると、path.txtが足りないとのこと。

index.js
varpathFile=path.join(__dirname,'path.txt')functiongetElectronPath(){if(fs.existsSync(pathFile)){...}else{thrownewError('Electron failed to install correctly, please delete node_modules/electron and try installing again')}}

ここに書いてあるとおり、手でinstall.jsを実行することで欠落したファイルの追加インストールをすることが出来ました。


④ディスプレイサーバの導入

いざ、実行…といきたいところですが、もうひと手間必要です。
Electronにより生成された画面の情報をウィンドウとして表示するため、ディスプレイサーバを導入します。

バイナリの取得・インストール

VcXsrvのプロジェクトページからダウンロードし、インストール。
インストール時の設定は全部デフォルトでOKです。
image.png

image.png
image.png

サーバの起動

こんなかんじのアイコンのアプリケーションを起動します。
image.png

サーバの設定も基本的にデフォルトでOKなのですが…以下の一点だけ変更が必要です。
起動時のオプションに"-ac"を追加する必要があります。
image.png


-acって何?

アクセス制限を解除するオプションとのこと。
XSERVERのページを見ると、このオプションについて言及されています。

disables host-based access control mechanisms. Enables access by any host, and permits any host to modify the access control list.

WSL2はHyper-Vの上で動いているため、Windows側と別ホストとして扱われます(詳しくはこの記事を見てね)。
そのため、このオプションにより制限を緩めてやらないと画面の表示が出来ません。


環境変数DISPLAYの設定

WSL2からVcXsrvに接続するための設定を行います。
これはシェルを起動する度に都度実行する必要があるので、.bashrcに追記してしまいましょう。

# 再起動の度にこれを叩くのはちょっと面倒なので…$ export DISPLAY=`ip route | grep'default via' | grep-Eo'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'`:0
# .bashrcの中に…
vi ~/.bashrc
~/.bashrc
# ~/.bashrc: executed by bash(1) for non-login shells.# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)# for examples# ~~中略~~# 追記。これでシェルを起動する度に自動で設定される。export DISPLAY=`ip route | grep'default via' | grep-Eo'[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'`:0


なんでip routeを使ってるの?

別項でも触れていますが、WSL2はHyper-Vの上で動いているという前提のもと(詳しくはこの記事を見てね)。

WSL2の中から外のネットワークに出ていくのにあたり、

WSL2 → (Hyper-Vのネットワーク) → Windows(ホスト) → (ホストの所属するネットワーク)

という流れが構築されているようです。
WSL2のデフォルトルートとして設定されているのがWindowsのアドレスであり、
WSL2から送出されたパケットはWindowsめがけてHyper-Vのネットワークを駆け巡ることになります。
なので、デフォルトルート=ディスプレイサーバが起動しているWindowsである、という理屈です。

ちなみに、Windows側ホストにも以下のような見慣れないアダプタが出現しています。
これを介してWSL2とやり取りをしているようですね。

イーサネット アダプター vEthernet (WSL):

   接続固有の DNS サフィックス . . . . .:
   IPv4 アドレス . . . . . . . . . . . .: 172.17.64.1
   サブネット マスク . . . . . . . . . .: 255.255.240.0
   デフォルト ゲートウェイ . . . . . . .:


⑤サンプルアプリケーションの起動

先程npm initしたディレクトリにて、下記コマンドを実行するとアプリケーションが立ち上がります。

# ローカルインストールの場合はこっち$ node ./node_modules/electron/cli.js index.js 

# グローバルインストールの場合はこっち$ electron index.js

image.png

お疲れさまでした。

参考サイト


Node.js でつくる Node.js-WASM コンパイラー - 01:WASMで定数戻り値を返す

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 WebAssembly(WASM)の基本を理解するために、ミニNode.jsからWASMを生成する小さなコンパイラーに取り組みます。

※以前の記事「Node.js でつくる WASM トランスパイラー - 01:初めてのWASMで定数戻り値を返す」のアップデート版になります

これまでの取り組み

ミニコンパイラー編のおまけでLLVM IRからWASMに変換して見ましたが、ツールに任せていて中身は理解できていませんでした。

また、余計なコードが大量に出力されています。今回は、必要最低限のWASMコードを生成することを目指します。

目指すもの

今回もプログラミング言語というのにはおこがましいぐらい、小さな仕様を目指します。

  • 扱えるのは32ビット符号付整数のみ
  • 四則演算、余りの計算
  • ローカル変数
    • 配列やハッシュ(連想配列)は使えない
  • if - else
  • ループは while のみ
  • 組込関数として、整数の出力と、固定文字列の出力ができる
  • ユーザ定義関数が使える
  • FizzBuzzと、再帰によるフィボナッチ数列の計算がゴール

ミニコンパイラーと同様に、コンパイラーはミニNode.jsインタープリターで実行できる縛りを設けました。そのためモダンな書き方になっていないのはお見逃しください。

WASMの実行はNode.jsで.wasmファイルを読みんで呼び出す形にします。

前提環境

今回の記事は、macOS Mojave 10.14 を前提にしてます。Linux系OSでも同様に利用できると思います。Windowsの場合の環境構築方法については調査していません。あしからず。
また node v10.x で動作確認しています。

初めてのWASM:「1」だけを返すWAT

WATファイル

WASM(WebAssembly)のテキスト表記は、WAT(またはWAST)ファイルと呼ばれます。このシリーズでは、次にように区別して呼び分けます。

  • WAT ... WebAssemblyのテキスト表記。プリミティブな表現でスタックに値を積んで操作する
  • WAST ... S式を使ったWebAssembyのテキスト表記。カッコで括ることでスタックを意識させない    

それでは、最初の一歩として「1」を返す関数を作ってみます。

one.wat
(module
  ;; ---- export main function  ---
  (export "exported_main" (func $main)) ;; 関数$mainを、exported_main という名前で公開
  (func $main (result i32) ;; i32(符号あり32ビット整数)を返す
    i32.const 1 ;; スタックに「定数1」を積む
    return ;; スタックの値を返す
  )
)

WASM内の \$main() 関数を、exported_main()という名前で外部に公開しています。\$main() 関数は、i32(符号あり32ビット整数)を返します。
WASMはスタック型の処理系なので、「定数1」をスタックに積んでから、関数の戻り値として返しています。

WASMファイルへの変換

これを wat2wasm を使ってバイナリ表記のWASMファイルに変換します。先ほどのファイルを one.wat として用意して次のように変換すると、one.wasm が生成されます。

$ wat2wasm one.wat

補足: wat2wasm のセットアップ

wat2wasm は WebAssembly/wabtに含まれるツールです。

WebAssembly/wabt のインストール

GitHubからソースを取得し、ビルドします。ビルドには cmake が必要です。

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ mkdir build
$ cd build
$ cmake ..
$ cmake --build .

ビルド後、必要に応じてパスを通しておきます。

WASM の実行

Node.js v10では、WASMの実行がサポートされています。次のコードを使って、wasmファイルを読み込み実行します。

run_wasm_simple.js
'use strict'constfs=require('fs');constfilename=process.argv[2];// 対象とするwasmファイル名console.warn('Loading wasm file: '+filename);letsource=fs.readFileSync(filename);lettypedArray=newUint8Array(source);letret=null;WebAssembly.instantiate(typedArray,{}// 実行時の環境).then(result=>{ret=result.instance.exports.exported_main();console.warn('ret code='+ret);process.exit(ret);}).catch(e=>{console.log(e);});
wasmの実行
$ node run_wasm_simple.js
Loading wasm file: one.wasm
ret code=1

exported_main() の戻り値として、1 が返ってきていることが確認できました。one.wat を編集して、再度 wat2wasm で wasm に変換して実行すると、修正した値が返ってきます。

修正したone.wat
(module
  ;; ---- export main function  ---
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 111
    return
  )
)
実行結果
$ wat2wasm one.wat
$ node run_wasm_simple.js one.wasm
Loading wasm file: one.wasm
ret code=111

最初のコンパイラーの実装

それでは、ミニNode.jsのソースコード → WATファイル のコンパイラーを実装して行きます。最初は、定数を返すだけの処理から始めることにします。

基本の構造

基本的な構造は、ミニコンパイラーと同じにしました。(参考:Node.js でつくる Node.js ミニコンパイラ - 03 : 足し算(+演算子)を実現する

  • esprimaでjsのソースを読み込み、パース
  • simplify()で単純化
  • compile()から再帰的にgenerate()を呼び出して、WATのテキストを生成、ファイルに保存

対象とするソースコード

次のソースコードを対象にします。

eight.js
8;

これを次のように変換するのがゴールです。

(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 8
    return
  )
)

ソースのパース

ミニインタープリター、ミニコンパイラーで使ったモジュール(module_parser_13.js)をベースに拡張したモジュール(module_parser_15.js)を使って、ソースコードをパースし単純化します。このモジュールは、内部で esprima を使ってJavaScriptのソースコードをパースしています。
- 参考:Node.jsでつくるNode.js - Step 1:ソースのパース
- 参考;Node.jsでつくるNode.js - Step2:単純化
- 参考:Node.jsでつくるNode.js - Step 10の パースを行う loadAndParseSrc()

esmprima は npm等でインストールしておいてください。

$  npm install esprima
パーサーの呼び出し
consttree=loadAndParseSrc();

今回は、次のような単純化したAST(抽象構文木)が取得できます。

[ 'lit', 8 ]

WATの生成

定数を解釈して、WATの該当部分を生成するコードは次のようにしました。

functiongenerate(tree){if(tree===null){return'';}if(tree[0]==='lit'){constv=tree[1];constblock='i32.const '+v;returnblock;}println('-- ERROR: unknown node in generate() ---');printObj(tree);abort();}

さらにこ前後の箇所を付け加えて全体を生成します。

// ---- compile simplified tree into WAT ---functioncompile(tree){constmainBlock=generate(tree);letblock='(module'+LF();block=block+TAB()+'(export "exported_main" (func $main))'+LF();block=block+TAB()+'(func $main (result i32)'+LF();block=block+TAB()+TAB()+mainBlock+LF();block=block+TAB()+TAB()+'return'+LF();block=block+TAB()+')'+LF();block=block+')';returnblock;}

TAB()やLF()は、ミニインタープリターで実行できるようにグルーバル定数の代わりに関数で固定の文字列を返すために用意しています。本筋にはあまり関係ありません。

functionLF(){return'\n';}functionTAB(){return'';}

以上の処理をつないで、生成されたWAT文字列をファイルに出力すれば、WATファイルが完成です。(今回は固定のファイル名 generated.wat にしています)

// --- load and parse source ---consttree=loadAndParseSrc();// --- compile to WAT --constwat=compile(tree);writeFile('generated.wat',wat);

コンパイラーの実行

コンパイラーの全体のソースを mininode_wasm_01.js としました。

これを使って対象となる eight.js を変換、実行します。

  • mininode_wasm_01.js で、eight.js → generated.wat に変換
  • wat2wasm で、 generated.wat → generated.wasm に変換
  • run_wasm_simple.js で、generated.wasm を実行
実行結果
$ node mininode_wasm_01.js eight.js
$ more generated.wat
(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 8
    return
  )
)
$
$ wat2wasm generated.wat
$ node run_wasm_simple.js generated.wasm
Loading wasm file: generated.wasm
ret code=8

戻り値として「8」がちゃんと返ってきました。

テストの整備

ミニインタープリター編ではテストなし、ミニコンパイラー編では最後になってからテストを用意しましたが、今回は最初から準備しておきます。今回もEnd-to-Endのテストとして、次の3つを比較することにしました。

  • Node.jsで直接実行したときの終了コード
  • ミニNode.jsコンパイラーで生成したWASMを、Node.jsで実行したときの戻り値
  • ミニNode.jsインタープリターからコンパイラーを実行して生成されたWASMを、Node.jsで実行したときの戻り値

Node.jsで直接実行

前処理

実行対象のソースコードeight.jsは、次のような中身です。

eight.js
8;

これをNode.jsで実行することはできますが、何も起こらず終了コードも「0」(ゼロ)になります。そこでソースコードを次のように変換してから実行してやることで、終了コードとして「8」を取得できるようにします。

変換したソースコード
process.exit(8);

前処理はシェルスクリプトで行っています。

シェルスクリプト抜粋
PreprocessForDirect() {
  echo "-- preprocess for exit code:  src=$jsfile tmp=$direct_file --"
  echo "process.exit(" > $direct_file
  cat $jsfile | sed -e "s/;\$//" >>  $direct_file  #  remove ';' at line end
  echo ");" >> $direct_file
}

ここで、変数は次を保持しています。

  • $jsfile ... テストに使うjsのファイル名
  • $direct_file ... Node.jsでの直接実行用に一時的に変換したファイル名

直接実行

前処理が終わったら、Node.jsで直接実行して終了コードを覚えておきます。こちらもシェルスクリプト内で実行します。

シェルスクリプト抜粋
NodeDirect() {
  echo "-- node $src --"
  node $direct_file
  direct_exit=$?
  echo "direct exit code=$direct_exit"
}
  • $direct_exit ... Node.jsでの直接実行の終了コードを覚えておく変数

WASMの生成と実行

js → WATヘの変換

まず、今回作成しているコンパイラーで、対象とするjsをWATファイルに変換します。シェルスクリプトから実行しています。

シェルスクリプト抜粋
TranslateToWat() {
  echo "--- translate src=$jsfile wat=$wat_file translater=$translater ---"
  node $translater $jsfile
  if [ "$?" -eq "0" ]
  then
    echo "translate SUCCERSS"
    mv generated.wat $wat_file
  else
    echo "ERROR! ... translate FAILED !"
    exit 1
  fi
}

ここで、変数の内容は次の通りです。

  • $translater ... テスト対象になっている、(ミニNode.jsで書かれた)コンパイラーのファイル名
  • $jsfile ... テストに使うjsのファイル名
  • $wat_file ... 生成するWATファイル名

このシェルスクリプトを実行すると、例えば次のような処理が行われます。

$ node mininode_wasm_01.js sample/eight.js
$ mv generated.wat test/tmp/eight.js.wat

WAT → WASM への変換

シェルスクリプト抜粋
WatToWasm() {
  echo "--- wat $wat_file to wasm $wasm_file --"
  $wat_to_wasm $wat_file -o $wasm_file
  if [ "$?" -eq "0" ]
  then
    echo "wat2wasm SUCCERSS"
  else
    echo "ERROR! ... wat2wasm FAILED !"
    exit 1
  fi
}

変数の内容は次の通りです。

  • $wat_to_wasm ... wat2wasm コマンド。パスを通しておくか、事前に環境変数 WAT2WASM_FOR_TEST にフルパスを設定しておく
  • $wat_file ... 変換するWATファイル名
  • $wasm_file ... 変換後のWASMファイル名

WASMの実行結果

生成したWASMを実行するには、この記事の「 WASM の実行」で準備したrun_wasm_simple.js を利用します。

ExecWasm() {
  echo "--- exec $wasm_file from node"
  node $wasm_exec $wasm_file
  wasm_exit=$?
  echo "wasm exit code=$wasm_exit"
}

変数の内容は次の通りです。

  • $wasm_exec ... wasmの実行に使うNode.jsのコード。今回はrun_wasm_simple.jsを利用
  • $wasm_file ... 実行するWASMファイル名
  • $wasm_exit ... wasmの戻り値を保持する

このシェルスクリプトを実行すると、例えば次のような処理が行われます。

$ node run_wasm_simple.js test/tmp/eight.js.wasm

ミニインタープリターを使ったWASM生成

今回のコンパイラーは、以前つくったミニNode.jsインタープリターで動くことを縛りにしています。(なのでモダンな書き方はしていません、という言い訳)

詳細は省略しますが、例えば次のような処理を行って、もう一つWASMファイルを生成、実行しています。

$ node mininode_15.js mininode_wasm_01.js sample/eight.js
$ mv generated.wat test/tmp/inetep_eight.js.wat
$ watwasm test/tmp/inetep_eight.js.wat
$ node run_wasm_simple.js test/tmp/inetep_eight.js.wasm
  • mininode_15.js ... ミニNode.jsインタープリター
  • mininode_wasm_01.js ... テスト対象のコンパイラー

終了コードの比較

シェルスクリプトで、終了コードを比較します。

CompareExitCode() {
  if [ "$direct_exit" -eq "$wasm_exit" ]
  then
    echo "OK ... node <-> wasm exit code match: $direct_exit == $wasm_exit"
  else
    echo "ERROR! ... node <-> wasm exit code NOT MATCH : $direct_exit != $wasm_exit"
    echo "ERROR! ... node <-> wasm exit code NOT MATCH : $direct_exit != $wasm_exit" > $exitcode_file
    exit 1
  fi

  if [ "$direct_exit" -eq "$interp_wasm_exit" ]
  then
    echo "OK ... node <-> interp-wasm exit code match: $direct_exit == $interp_wasm_exit"
  else
    echo "ERROR! ... node <-> interp-wasm exit code NOT MATCH : $direct_exit != $interp_wasm_exit"
    echo "ERROR! ... node <-> interp-wasm exit code NOT MATCH : $direct_exit != $interp_wasm_exit" > $exitcode_file
    exit 1
  fi
}

変数の内容は次の通りです。

  • $direct_exit ... Node.jsでの直接実行の終了コード
  • $wasm_exit ... wasmの戻り値
  • $interp_wasm_exit ... ミニインタープリターからコンパイラーを実行して生成したwasmの戻り値
  • $exitcode_file ... 終了コードに違いがあったことを記録しておくテキストファイル

テストコードはこちらにあります。

次のように実行します。(引数は、コンパイラー, ミニインタープリター, 対象ソース)

$ cd test
$ sh test_exitcode.sh mininode_wasm_01.js mininode_15.js eight.js
--- translate src=../sample/eight.js wat=tmp/eight.js.wat translater=../mininode_wasm_01.js---
-- start WASM translator --
Loading src file:../sample/eight.js  _argIndex=2
 ... 省略 ...
direct exit code=8
OK ... node <-> wasm exit code match: 8 == 8
OK ... node <-> interp-wasm exit code match: 8 == 8

次回は

次は四則演算と、余りを計算できるようにする予定です。

これまでのソース

GitHubにソースを上げておきます。

Node.js でつくる Node.js-WASM コンパイラー - 02:四則演算を実装する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。今回は四則演算と余り(+, -, *, /, %)を実装してみましょう。

※以前の記事「Node.js でつくる WASM トランスパイラー - 02:四則演算を実装する」のアップデート版になります

これまでの取り組み

足し算の実現

対象ソース

Node.jsの対象となるコードはこちらです。単純な整数の足し算です。

add.js
1+2;

これをミニインタープリター、ミニコンパイラーで使ったモジュールの拡張版(module_parser_15.js)でパース/単純化したASTを作ります。結果はこちらのようになります。

[ '+', [ 'lit', 1 ], [ 'lit', 2 ] ]

足し算を表現するWASMは次のようになります。32ビット整数の足し算は i32.add を使います。

(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 1
    i32.const 2
    i32.add
    return
  )
)

WATの生成

前回の整数リテラルに加えて、足し算('+')も2項演算子として生成できるように拡張します。

  • generateLiteral() ... 整数リテラル生成
  • generateAddOperator() ... 足し算生成
    • 内部で、演算子の左の項、右の項を生成するために、generate()を再帰的に呼び出す
  • TABs() ... WATのインデントを整形するためのユーティリティ関数
generate()を拡張
// ---- genereate WAT block ---functiongenerate(tree,indent){if(tree===null){return'';}if(tree[0]==='lit'){returngenerateLiteral(tree,indent);}// --- add operator ---if(tree[0]==='+'){returngenerateAddOperator(tree,indent);}println('-- ERROR: unknown node in generate() ---');printObj(tree);abort();}// -- 整数リテラル --functiongenerateLiteral(tree,indent){constv=tree[1];constblock=TABs(indent)+'i32.const '+v;returnblock;}// --- 足し算 ---functiongenerateAddOperator(tree,indent){constleftBlock=generate(tree[1],indent);constrightBlock=generate(tree[2],indent);letblock='';block=block+leftBlock+LF();block=block+rightBlock+LF();block=block+TABs(indent)+'i32.add'+LF();;returnblock;}// --- インデント整形用 ---functionTABs(n){lettabs='';leti=0;while(i<n){tabs=tabs+TAB();i=i+1;}returntabs;}

これで、足し算も生成できるようになりました。

足し算の実行

足し算までサポートしたコンパイラーのコードを mininode_wasm_02_add.js とします。これでWASMを生成して実行してみましょう。

  • WAT→WASM変換 ... wat2wasm を利用
  • WASMの実行 ... 前回用意した run_wasm_simple.js を利用
$ node mininode_wasm_02_add.js add.js
$ cat generated.wat
(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 1
    i32.const 2
    i32.add

    return
  )
)
$ wat2wasm generated.wat
$ node run_wasm_simple.js generated.wasm
Loading wasm file: generated.wasm
ret code=3

終了コードが「3」となり、1+2が計算できました。

複数の足し算

今回の足し算の生成では、再帰的な処理を行っています。これにより、複数の足し算があっても生成できるようになっています。

対象となるソースと、それを表す単純化ASTはこちらです。

add_many.js
1+2+(3+(4+5));
単純化AST
[ '+',
  [ '+', [ 'lit', 1 ], [ 'lit', 2 ] ],
  [ '+', [ 'lit', 3 ], [ '+', [ 'lit', 4 ], [ 'lit', 5 ] ] ]
]

WAT/WASM生成、実行結果がこちら。

複数足し算の実行結果
$ node mininode_wasm_02_add.js sample/add_many.js 
$ cat generated.wat
(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 1
    i32.const 2
    i32.add

    i32.const 3
    i32.const 4
    i32.const 5
    i32.add

    i32.add

    i32.add

    return
  )
)
$ wat2wasm generated.wat
$ node run_wasm_simple.js generated.wasm
Loading wasm file: generated.wasm
ret code=15

入れ子の足し算が生成され、実行結果も正しく「15」が得られました。

残りの四則演算のサポート

WASMの演算命令

符号付32ビット整数の演算には、次の命令を使います。

  • i32.add ... 足し算(+)
  • i32.sub ... 引き算(-)
  • i32.mul ... 掛け算(*)
  • i32.div_s ... 割り算(/)
  • i32.rem_s ... 余り(%)

これをサポートするように、generate()関数を拡張します。

二項演算子の生成

先ほどは足し算専用でしたが、四則演算の二項演算子に対応するようにかくちょうします。

generate()の拡張
// ---- genereate WAT block ---functiongenerate(tree,indent){if(tree===null){return'';}if(tree[0]==='lit'){returngenerateLiteral(tree,indent);}// --- binary operator ---if(tree[0]==='+'){returngenerateBinaryOperator(tree,indent,'add');}if(tree[0]==='-'){returngenerateBinaryOperator(tree,indent,'sub');}if(tree[0]==='*'){returngenerateBinaryOperator(tree,indent,'mul');}if(tree[0]==='/'){returngenerateBinaryOperator(tree,indent,'div_s');}if(tree[0]==='%'){returngenerateBinaryOperator(tree,indent,'rem_s');}println('-- ERROR: unknown node in generate() ---');printObj(tree);abort();}// --- 二項演算子 ---functiongenerateBinaryOperator(tree,indent,operator){constleftBlock=generate(tree[1],indent);constrightBlock=generate(tree[2],indent);constop='i32.'+operator;letblock='';block=block+leftBlock+LF();block=block+rightBlock+LF();block=block+TABs(indent)+op+LF();returnblock;}

演算子の左の項、右の項を再帰的に生成するのは、足し算の時と同様です。

四則演算の実行

対象ソース

四則演算と余りを用いた、次のソースを用意しました。

sample/binoperator.js
// expect 2(1+3*5-4/2)%3;

WASM生成と実行

コンパイラーのソースコードを、mininode_wasm_02.js とします。

  • コンパイラーで、sample/binoperator.js → generated.wat
  • wat2wasm で、generated.wat → generated.wasm
  • run_wasm_simple.js で実行
$ node mininode_wasm_02.js sample/binoperator.js
$ cat generated.wat
(module
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 1
    i32.const 3
    i32.const 5
    i32.mul

    i32.add

    i32.const 4
    i32.const 2
    i32.div_s

    i32.sub

    i32.const 3
    i32.rem_s

    return
  )
)
$
$ wat2wasm generated.wat
$ node run_wasm_simple.js generated.wasm
Loading wasm file: generated.wasm
ret code=2

実行結果の終了コードは「2」です。(1 + 3*5 - 4/2) % 3 = (1 + 15 - 2) % 3 = 14 % 3 = 2 なので、想定通りになりました。

テストの改良

前回は、単一のソースを対象としたテストとして、test_exitcode.shというシェルスクリプトを用意しました。
今回はこれを複数回実行して、トータルの成功/失敗件数を出すシェルスクリプトを用意します。

メインの処理はこちらです。

# --- test ---
TestSingleExitCode() {
  # --- exec 1 test case --
  testfile=$1

  # usage:
  #  sh test_exitcode.sh compilername interpname filename 
  #
  sh test_exitcode.sh $compiler $interpreter $testfile
  last_case_exit=$?

  # --- check test result--
  case_count=$(($case_count+1))
  if [ "$last_case_exit" -eq 0 ]
  then
    # -- test OK --
    ok_count=$(($ok_count+1))
    echo "$testfile ... OK" >> $summary_file 
  else
    # -- test NG --
    err_count=$(($err_count+1))
    echo "$testfile ... NG" >> $summary_file 
  fi
}
  • 引数 ... 実行対象のソース
  • $compiler ... コンパイラー(テストの対象)
  • $interpreter ... 比較に使うミニNode.jsインタープリター
  • $case_count ... 実行したテストの数
  • $ok_count ... 成功したテストの数
  • $err_count ... 失敗したテストのkazu

複数回実行し、結果をレポートします。

# ---- exec test case -----
# step_01
TestSingleExitCode one.js
TestSingleExitCode two.js
TestSingleExitCode eight.js

# step_02
TestSingleExitCode add.js
TestSingleExitCode add_many.js
TestSingleExitCode binoperator.js

# --- report --
Report

# --- exit ----
exit $err_count

実行すると、次のように結果を出します。

$ sh test_exitcode_multi.sh
... 途中経過は省略 ... 
===== test finish ======
 total=6
 OK=6
 NG=0
======================

これで今後の回帰テストも楽になりました。

次回は

次回は、複数行のとローカル変数を実装する予定です。

これまでのソース

GitHubにソースを上げておきます。

Node.js でつくる Node.js-WASM コンパイラー - 03:ローカル変数を実装する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

※以前の記事「Node.js でつくる WASM トランスパイラー - 03:ローカル変数を実装する」のアップデート版になります。

これまでの取り組み

今回実現したいこと

今回はローカル変数のサポートが目標ですが、合わせて他の機能も実装します。

  • ローカル変数(宣言、参照、代入)
  • 複数行のサポート ... 変数を使って複数の計算を行うため
  • 簡易デバッグ出力 ... 途中の計算結果を確認するため

簡易デバッグ出力

WASMから直接コンソール出力することはできないため、呼び出し元のJavaScript(Node.js)から、コンソール出力用の関数を渡すようにします。

呼びだし元

WASMから使うための関数を用意し、WebAssembly.instantiate()の際に渡します。

呼び出し例(run_wasm_putn.js)
constimports={// WASM側には、imported_putn()という関数が見えているimported_putn:function(arg){console.log(arg);}};letsource=fs.readFileSync(filename);lettypedArray=newUint8Array(source);WebAssembly.instantiate(typedArray,{imports:imports}).then(result=>{// WASM側では、exported_main() という関数をエキスポートしているconstret=result.instance.exports.exported_main();process.exit(ret);}).catch(e=>{console.log(e);});

WAT/WASM内

  • 関数をimportして、それを内部の名前に割り付ける
  • 関数呼び出しは call を使う
WASM側での読み込み、関数呼び出し(use_putn.wat)
(module
  ;; --- imported_putn() という外部の関数を、$putn() として内部で呼べるようにする
  (func $putn (import "imports" "imported_putn") (param i32))

  ;; --- $main()関数を、外部にexported_main() という名前で公開する
  (export "exported_main" (func $main))

  ;; --- 実際の処理 ---
  (func $main (result i32)
    ;; --- 外部の関数を呼びだす ---
    i32.const 123
    call $putn

    i32.const 0
    return
  )
)

putn()の実行結果

wat2wasmで.watを.wasmに変換し、Node.jsを使って実行します。

$ wat2wasm use_putn.wat 
$ node run_wasm_putn.js use_putn.wasm
123

期待通り、123が標準出力に表示されまました。

コンパイラーの拡張

対象のミニNode.jsコードを用意します。

putn.js
putn(123);

これをパース、単純化すると次の単純化ASTが得られます。

[ 'func_call', 'putn', [ 'lit', 123 ] ]

func_call が関数呼び出しに相当します。今回は簡易版の実装として、putn()だけ呼び出せるような処理を、コンパイラーのgenerate()関数に追加します。(※lctxについては、あとで説明します)

functiongenerate(tree,indent,lctx){// ... 省略 ...if(tree[0]==='func_call'){// tree = ['func_call', 'name', arg1, ... ]constfuncName=tree[1];if(funcName==='putn'){returngenerateCallPutn(tree,indent,lctx);}println('-- ERROR: unknown func in generate() ---');printObj(tree);abort();}// ... 省略 ...}// --- debug func putn() ---functiongenerateCallPutn(tree,indent,lctx){// tree = ['func_call', 'name', arg1, arg2, ... ]constvalueBlock=generate(tree[2],indent,lctx);if(!valueBlock){println('---ERROR: empty args for putn() --');abort();}letblock=valueBlock+LF();block=block+TABs(indent)+'call $putn'+LF();returnblock;}

関数呼び出しの処理の生成は、新しく用意したgenerateCallPutn()関数で行っています。引数は1個だけを想定していますが、それが式だった場合も想定して再帰的にgenerate()を呼び出しています。

複数行のサポート

せっかくのローカル変数を活かすには、複数行の処理が書きたいところです。例えば次のようなミニNode.jsのコードを用意します。(最後の0(ゼロ)は終了コードになります)

multi_lines.js
putn(1);putn(123);0;

これをパース、単純化すると次の単純化ASTが得られます。stmts(statements)が複数行のかたまりに相当します。

[ 'stmts',
  [ 'func_call', 'putn', [ 'lit', 1 ] ],
  [ 'func_call', 'putn', [ 'lit', 123 ] ],
  [ 'lit', 0 ] ]

これに対応するよう、コンパイラーのgenerate()関数を拡張しておきましょう。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- multi lines ---if(tree[0]==='stmts'){leti=1;letblock='';while(tree[i]){block=block+generate(tree[i],indent,lctx)+LF()+LF();i=i+1;}returnblock;}// ... 省略 ...}

ローカル変数のサポート

ローカル変数を扱うために、3つの処理を考えます。

  • 変数の宣言(と初期値の代入): var_decl
  • 変数の再代入: var_assign
  • 変数の参照: var_ref

変数の宣言

ローカル変数の宣言は、初期値がない場合と、初期値がある場合があります。

ミニNode.jsでの表記
leta;letb=1;

これをパース、単純化した内部の単純化ASTは次のようになっています。

単純化AST
[ 'var_decl', 'a', null ],
[ 'var_decl', 'b', [ 'lit', 1 ] ],

WATでの表記

WebAssemblyのテキスト表現WATでは、ローカル変数に相当する「local $変数名」があるので、それを使います。初期値がある場合は、宣言後に「set_local」を用いて値をセットします。

WATでの表記
;; let a;
(local $a i32)

;; let b = 1;
(local $b i32)
i32.const 1
set_local $b

コンパイラーの拡張

まず、宣言されたローカル変数をコンパイラー内で覚えておくために、ローカルコンテキスト(lctx)というハッシュ(連想配列)を用意します。これが先ほどからgenerate()の引数に追加されていたのです。今回は型もi32の一種類だけなので、シンプルな持ち方にしています。

ローカル変数の保持
lctx['変数名'] = '$変数名' 

これを使って、generate()関数を拡張します。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- local variable --if(tree[0]==='var_decl'){constblock=declareVariable(tree,indent,lctx);returnblock;}// ... 省略 ...}// --- declare variable ---functiondeclareVariable(tree,indent,lctx){// -- check NOT exist --constname=tree[1];if(nameinlctx){println('---ERROR: varbable ALREADY exist (compiler) --');abort();}// -- add local variable --constvarName='$'+name;lctx[name]=varName;// --- assign initial value --letinit=generate(tree[2],indent,lctx);letblock='';if(init){block=block+init+LF();block=block+TABs(indent)+'set_local '+varName+LF();}returnblock;}

実際の変数宣言は、declareVariable()関数で生成しています。初期値が式で渡せるように、その評価のためにgenerate()を再帰的に呼び出します。

変数の代入

変数に値を代入する場合、単純化ASTでは var_assign で表現しています。

[ 'var_assign', 'a', 代入する値 ],

代入する値の部分はリテラルだったり、式だったりします。

WATでは、すでに変数の宣言で登場している「set_local」を使います。generate()関数を拡張し、assignVariable()関数を追加しました。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- local variable --if(tree[0]==='var_assign'){constblock=assignVariable(tree,indent,lctx);returnblock;}// ... 省略 ...}functionassignVariable(tree,indent,lctx){// -- check EXIST --constname=tree[1];if(nameinlctx){letblock='';constvarName=lctx[name];constvalueBlock=generate(tree[2],indent,lctx);if(!valueBlock){println('---ERROR: var assign value NOT exist --');abort();}block=block+valueBlock+LF();block=block+TABs(indent)+'set_local '+varName+LF();returnblock;}println('---ERROR: varibable NOT declarated (assign)--:'+name);abort();}

assignVariable()ではローカルコンテキスト lctx から変数表記を取り出して利用します。代入する内容は例によってgenerate()を呼び出して生成しています。

変数の参照

変数aの値を参照する場合、単純化ASTでは var_ref で表現しています。

[ 'var_ref', 'a' ],

WATでは、次のように「get_local」を使います。

 (get_local $a)

変数の参照のために generate()関数を拡張し、referVariable()関数を追加しました。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- local variable --if(tree[0]==='var_ref'){constblock=referVariable(tree,indent,lctx);returnblock;}// ... 省略 ...}// --- variable refer ---functionreferVariable(tree,indent,lctx){// -- check EXIST --constname=tree[1];if(nameinlctx){letblock='';constvarName=lctx[name];block=TABs(indent)+'get_local '+varName;returnblock;}println('---ERROR: varibable NOT declarated (ref)--:'+name);abort();}

referVariable()ではローカルコンテキスト lctx から変数表記を取り出して利用します。get_local を使うと、変数の値が読み出されてスタックに積まれます。そのまま式で利用したり、関数の引数として利用することができます。

全体の結合

  • ローカル変数のためのローカルコンテキストの準備
  • 簡易デバッグ出力のための関数 putn() のインポート
  • 出力するWATのインデント調整

を行うため、処理を追加し、今回のコンパイラーの完成となります。

functioninitialLocalContext(){constctx={};returnctx;}letl_ctx=initialLocalContext();// top level local context// --- load and parse source ---consttree=loadAndParseSrc();// --- compile to WAT --constwat=compile(tree,l_ctx);// ---- compile simplified tree into WAT ---functioncompile(tree,lctx){constmainBlock=generate(tree,2,lctx);constvarBlock=generateVariableBlock(tree,2,lctx);letblock='(module'+LF();block=block+TAB()+'(func $putn (import "imports" "imported_putn") (param i32))'+LF();block=block+TAB()+'(export "exported_main" (func $main))'+LF();block=block+TAB()+'(func $main (result i32)'+LF();block=block+varBlock+LF();block=block+mainBlock+LF();block=block+TAB()+TAB()+'return'+LF();block=block+TAB()+')'+LF();block=block+')';returnblock;}

全体のソースコードは、 mininode_wasm_03.jsとして GitHub に上げておきます。

変数を使った処理の実行

対象ソース

複数の処理を行う、次のコードを対象にします。

var.js
// --- putn() test ---putn(1);// 1// --- declare variable ---leta=1+2+3;putn(a);// 6// --- assigne variable, refer variable ---letb;b=a+1;b=b+2;putn(b);// 9putn(a+b*2);// 24b;// expect 9

WASM生成と実行

コンパイラーのソースコードを、mininode_wasm_03.js とします。

  • コンパイラーで、sample/var.js → generated.wat
  • wat2wasm で、generated.wat → generated.wasm
  • run_wasm_putn.js で実行
$ node mininode_wasm_03.js sample/var.js
$ cat generated.wat
(module
  (func $putn (import "imports" "imported_putn") (param i32))
  (export "exported_main" (func $main))
  (func $main (result i32)
    (local $a i32)
    (local $b i32)

    i32.const 1
    call $putn

    i32.const 1
    i32.const 2
    i32.add

    i32.const 3
    i32.add

    set_local $a

    get_local $a
    call $putn

    get_local $a
    i32.const 1
    i32.add

    set_local $b

    get_local $b
    i32.const 2
    i32.add

    set_local $b

    get_local $b
    call $putn

    get_local $a
    get_local $b
    i32.const 2
    i32.mul

    i32.add

    call $putn

    get_local $b

    return
  )
)
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
1
6
9
24
ret code=9

想定通りの数値が表示され、戻り値も9になりました。

標準出力のテスト

前回までは戻り値を比較するテストを行っていました。今回は標準出力を比較するテストを用意します。比較の対象は、次の3つです。

  • Node.jsで直接実行したときの標準出力
  • ミニNode.jsコンパイラーで生成したWASMを、Node.jsで実行したときの標準出力
  • ミニNode.jsインタープリターからコンパイラーを実行して生成されたWASMを、Node.jsで実行したときの標準出力

この3つが一致していたら、テストは成功とします。

Node.jsで直接実行

前処理

実行対象となるソースは次の内容です。

var.js
// --- putn() test ---putn(1);// 1// --- declare variable ---leta=1+2+3;putn(a);// 6// --- assigne variable, refer variable ---letb;b=a+1;b=b+2;putn(b);// 9putn(a+b*2);// 24b;// expect 9

ここで呼び出している putn() は、今回用意した簡易デバッグ出力関数です。Node.jsで実行するには、この関数を用意する必要があります。
そこで、次のコードを用意しておいて、連結してから実行することにしました。

builtin_helper.js
functionputn(n){console.log(n);}

※ GitHubにあるコードは、以後のステップで使う処理も含んでいるので、違う内容になっています。

これを連結してから実行するように、シェルスクリプトで関数を用意しました。

PreprocessBuiltinForDirect() {
  echo "-- preprocess for builtin func:  src=$jsfile tmp=$direct_file --"
  cat $helper_file > $direct_file # putn(), puts()
  cat $jsfile >>  $direct_file
}
  • $jsfile ... テストに使うjsのファイル名
  • $direct_file ... Node.jsでの直接実行用に一時的に変換したファイル名
  • $helper_file ... 組み込み関数を定義した、ヘルパーファイル名

直接実行

前処理が終わったら、Node.jsで実行します。実行時の標準出力の内容をファイルにリダイレクトして記録しておきます。

NodeDirect() {
  echo "-- node $src --"
  node $direct_file > $direct_stdout
  direct_exit=$?
  echo "direct exit code=$direct_exit"
}

ここで、変数は次を保持しています。

  • $direct_file ... Node.jsでの直接実行用に一時的に変換したファイル名
  • $direct_stdout ... Node.jsで直接実行した際の標準出力を記録しておくファイル名

WASMの生成と実行

js → WATヘの変換

WATの生成は 01:01:WASMで定数戻り値を返すの処理と同じです。

# -- translate to wat ---
TranslateToWat() {
  echo "--- translate src=$jsfile wat=$wat_file translater=$translater ---"
  node $translater $jsfile
  if [ "$?" -eq "0" ]
  then
    echo "translate SUCCERSS"
    mv generated.wat $wat_file
  else
    echo "ERROR! ... translate FAILED !"
    exit 1
  fi
}

ここで、変数の内容は次の通りです。

  • $translater ... テスト対象になっている、(ミニNode.jsで書かれた)トランスパイラーのファイル名
  • $jsfile ... テストに使うjsのファイル名
  • $wat_file ... 生成するWATファイル名

WAT → WASM への変換

WAT → WASMへの変換は wat2wasm を使います。こちらも 01 同じ処理です。

WatToWasm() {
  echo "--- wat $wat_file to wasm $wasm_file --"
  $wat_to_wasm $wat_file -o $wasm_file
  if [ "$?" -eq "0" ]
  then
    echo "wat2wasm SUCCERSS"
  else
    echo "ERROR! ... wat2wasm FAILED !"
    exit 1
  fi
}

変数の内容は次の通りです。

  • $wat_to_wasm ... wat2wasm のパス。パスを通しておくか、事前に環境変数WAT2WASM_FOR_TESTにフルパスを設定しておく
  • $wat_file ... 変換するWATファイル名
  • $wasm_file ... 変換後のWASMファイル名

WASMの実行

WASMの実行時には、標準出力をファイルにリダイレクトして記録しておきます。

ExecWasm() {
  echo "--- exec $wasm_file from node"
  node $wasm_exec $wasm_file > $wasm_stdout
  wasm_exit=$?
  echo "wasm exit code=$wasm_exit"
}

変数の内容は次の通りです。

  • $wasm_exec ... wasmの実行に使うNode.jsのコード。今回は run_wasm_builtin.jsを利用
  • $wasm_file ... 実行するWASMファイル名
  • $wasm_stdout ... 標準出力を記録するファイル名

実行に利用している run_wasm_builtin.jsは、この記事の最初に用意した run_wasm_putn.jsと同様の処理ですが、 putn() 以外にも今後のステップで利用する他のビルトイン関数も含めて用意しています。 run_wasm_builtin.js については、別の記事で説明する予定です。

ミニインタープリターを使ったWASM生成

引き続き今回のコンパイラーは、以前つくったミニNode.jsインタープリターで動くことを縛りにしています。
詳細は省略しますが、もう一つWASMファイルを生成、実行しています。実行時の標準出力は、こちらもファイルに記録しておきます。

標準出力のチェック

標準出力の比較では改行文字の違いは無視して diff を取っています。

DiffStdout() {
  diff --strip-trailing-cr $direct_stdout $wasm_stdout > $diff_direct_wasm
  diff --strip-trailing-cr $direct_stdout $interp_wasm_stdout > $diff_direct_interp_wasm
}

ここで変数は次の通りです。

  • $direct_stdout ... Node.jsで直接実行した場合の標準出力の内容
  • $wasm_stdout ... コンパイラーで生成した WASM を実行した場合の標準出力の内容
  • $interp_wasm_stdout ... ミニンタープリターから実行したコンパイラーで生成した WASM を実行した場合の標準出力の内容
  • $diff_direct_wasm ... 直接実行と、WASMの標準出力の差分ファイル
  • $diff_direct_interp_wasm ... ミニンタープリターを使った場合の標準出力の差分ファイル

その差分の中身が空であればテストは成功、中身が何かあったらテストは失敗とみなします。

CheckStdout() {
  if [ -s $diff_direct_wasm ]
  then
    echo "ERROR! ... node <-> wasm stdout are different"
    cat $diff_direct_wasm
    exit 1
  else
    echo "OK ... node <-> wasm stdout are same"
  fi

  if [ -s $diff_direct_interp_wasm ]
  then
    echo "ERROR! ... node <-> inerp-wasm stdout are different"
    cat $diff_bin
    exit 1
  else
    echo "OK ... node <-> inerp-wasm stdout are same"
  fi
}

ここまでのテスト実行するシェルスクリプトを、test_stdout.shとします。

複数テストの実行

前回の 02:四則演算を実装するで用意した複数のテストを拡張して、今回の標準出力の比較を使ったテストを組み込みます。

TestSingleStdout() {
  # --- exec 1 test case --
  testfile=$1

  # usage:
  #  sh test_stdout.sh compilername interpname filename 
  #
  sh test_stdout.sh $compiler $interpreter $testfile
  last_case_exit=$?

  # --- check test result--
  case_count=$(($case_count+1))
  if [ "$last_case_exit" -eq 0 ]
  then
    # -- test OK --
    ok_count=$(($ok_count+1))
    echo "$testfile ... OK" >> $summary_file 
  else
    # -- test NG --
    err_count=$(($err_count+1))
    echo "$testfile ... NG" >> $summary_file 
  fi
}
  • 引数 ... 実行対象のソース
  • $compiler ... コンパイラー(テストの対象)
  • $interpreter ... 比較に使うミニNode.jsインタープリター

これを使って、これまでのテストをまとめて実行することができます。

# ---- exec test case -----
# step_01
TestSingleExitCode one.js
TestSingleExitCode two.js
TestSingleExitCode eight.js

# step_02
TestSingleExitCode add.js
TestSingleExitCode add_many.js
TestSingleExitCode binoperator.js

# step_03
TestSingleStdout putn.js
TestSingleStdout multi_lines.js
TestSingleStdout var.js

次回は

次回は、比較演算子を実装する予定です。

ここまでのソース

GitHubにソースを上げておきます。

Node.js でつくる WASM コンパイラー - 04:比較演算子を実装する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

※以前の記事「Node.js でつくる WASM トランスパイラー - 04:比較演算子を実装する」のアップデート版になります。

今回実現したいこと

今回は次の比較演算子のサポートが目標です。それぞれ、WATでは次のように表記します。

  • === ... eq
  • !== ... ne
  • < ... lt_s (符号あり)
  • > ... gt_s (符号あり)
  • <= ... le_s (符号あり)
  • >= ... ge_s (符号あり)

比較の変換例

このミニNode.jsのソースコードを対象とします。

putn(1!==2);

putn()は、前回用意した簡易デバッグ出力用の組み込み関数です。

これを読み込み、単純化したASTはこちらになります。

[ 'func_call', 'putn', 
  [ '!==', [ 'lit', 1 ], [ 'lit', 2 ] ]
]

この部分を次のWATに変換することが、今回の目的です。

    i32.const 1
    i32.const 2
    i32.ne

    call $putn

比較演算子の生成

早速コンパイラーのgenerate()関数を拡張します。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- compare operator ---if(tree[0]==='==='){returngenerateCompareOperator(tree,indent,'eq',lctx);}if(tree[0]==='=='){returngenerateCompareOperator(tree,indent,'eq',lctx);}if(tree[0]==='!=='){returngenerateCompareOperator(tree,indent,'ne',lctx);}if(tree[0]==='!='){returngenerateCompareOperator(tree,indent,'ne',lctx);}if(tree[0]==='>'){returngenerateCompareOperator(tree,indent,'gt_s',lctx);}if(tree[0]==='>='){returngenerateCompareOperator(tree,indent,'ge_s',lctx);}if(tree[0]==='<'){returngenerateCompareOperator(tree,indent,'lt_s',lctx);}if(tree[0]==='<='){returngenerateCompareOperator(tree,indent,'le_s',lctx);}// ... 省略 ...}// --- compare operator ---functiongenerateCompareOperator(tree,indent,operator,lctx){constleftBlock=generate(tree[1],indent,lctx);constrightBlock=generate(tree[2],indent,lctx);constop='i32.'+operator;letblock='';block=block+leftBlock+LF();block=block+rightBlock+LF();block=block+TABs(indent)+op+LF();returnblock;}

実際にコードを生成するのは、generateCompareOperator()関数で行なっています。

比較演算子の実行

対象ソース

こちらのコードをWASM生成対象とします。

sample/neq.js
putn(1!==2);0;

WASMの生成

今回のステップまでのコンパイラーのソースコードを、mininode_wasm_04.js とします。次のようにgenerated.wat を生成します。

$ node mininode_wasm_04.js sample/neq.js

生成結果はこちら。

generated.wat
(module
  (func $putn (import "imports" "imported_putn") (param i32))
  (export "exported_main" (func $main))
  (func $main (result i32)
    i32.const 1
    i32.const 2
    i32.ne

    call $putn

    i32.const 0
    return
  )
)

これを WASM に変換、前回用意した run_wasm_putn.js を使って実行します。

$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
1
ret code=0

「1 !==2」は true なので、1 が標準出力に表示されました。

色々な比較の実行

もう一つ、色々な比較を使ったサンプルを変換、実行してみます。

comp.js
leta=1;putn(a);// expect 1letb=(a===2);putn(b);// expect 0putn(a==1);// expect 1letc=(a!==2);putn(c);// expect 1putn(a!=1);// expect 0putn(999);a=a*10;putn(a>10);// expect 0putn(a>=10);// expect 1putn(a<10);// expect 0putn(a<=10);// expect 122;

実行結果はこちら。色々な比較演算子も想定通りの出力になりました。

Loading wasm file: generated.wasm
1
0
1
1
0
999
0
1
0
1

次回は

目標の1つであるFizzBuzzに必要な、条件分岐とループを実装する予定です。

ここまでのソース

GitHubにソースを上げておきます。

Node.js でつくる WASM コンパイラー - 05:条件分岐とループを実装する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

※以前の記事「Node.js でつくる WASM トランスパイラー - 05:条件分岐とループを実装する」のアップデート版になります。

これまでの取り組み

今回実現したいこと(1) if

今回の目標の1つ目は条件分岐(if 〜 else)のサポートです。

ifの例

対象となるミニNode.jsのコードはこちら。

if1.js
if(1){putn(111);}

生成したいWATはこちらです。

    i32.const 1
    if
      i32.const 111
      call $putn
    end

if 〜 else の例

もう1つ、elseがある場合はこちら。

if0.js
if(0){putn(111);}else{putn(0);}

同様に、このようにWATを生成していきます。

    i32.const 0
    if
      i32.const 111
      call $putn
    else
      i32.const 0
      call $putn
    end

if 〜 else の生成

いつものようにコンパイラーのgenerate()関数を拡張します。実際にif〜elseに対応する部分は、genereateIf()関数で生成しています。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- if ---if(tree[0]==='if'){constblock=genereateIf(tree,indent,lctx);returnblock;}// ... 省略 ...}// --- if ---// tree = ['if', condition, then, else]functiongenereateIf(tree,indent,lctx){constconditionBlock=generate(tree[1],indent,lctx);constpositiveBlock=generate(tree[2],indent+1,lctx);// -- condition --letblock=conditionBlock+LF();// -- if-then --block=block+TABs(indent)+'if'+LF();block=block+positiveBlock;// -- else ---if(tree[3]){constnegativeBlock=generate(tree[3],indent+1,lctx);block=block+TABs(indent)+'else'+LF();block=block+negativeBlock;}// -- end --block=block+TABs(indent)+'end'+LF();returnblock;}

if 〜 else の実行

複数の条件分岐を使ったサンプルを用意します。

if.js
leta=1;if(a===1){putn(111);}if(a!==1){putn(222);}else{putn(333);}putn(999);33;

これをWASMに変換、実行した結果がこちらです。

$ node mininode_wasm_05.js sample/if.js
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
111
333
999
ret code=33

想定通りの出力になりました。

今回実現したいこと(2) while ループ

今回の目標の2つ目は while ループのサポートです。

whileの例

leta=0;while(a<10){putn(a);a=a+1;}

生成したいWATはこちら。

    (local $a i32)
    i32.const 0
    set_local $a

    loop ;; --begin of while loop--
      get_local $a
      i32.const 10
      i32.lt_s
      if
        get_local $a
        call $putn

        get_local $a
        i32.const 1
        i32.add
        set_local $a

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--

色々な実現の仕方があると思いますが、今回は while ループの中身を if で記述し、br 命令でループの先頭に戻るようにしました。

while の生成

再度generate()関数を拡張します。実際にwhileに対応する部分は、genereateWhile()関数で生成しています。

functiongenerate(tree,indent,lctx){// ... 省略 ...// --- while ---if(tree[0]==='while'){constblock=genereateWhile(tree,indent,lctx);returnblock;}// ... 省略 ...}// --- while ---// tree = ['while', condition, then, else]functiongenereateWhile(tree,indent,lctx){constconditionBlock=generate(tree[1],indent+1,lctx);constinnerBlock=generate(tree[2],indent+2,lctx);letblock=TABs(indent)+'loop ;; --begin of while loop--'+LF();block=block+conditionBlock;block=block+TABs(indent+1)+'if'+LF();block=block+innerBlock;block=block+TABs(indent+2)+'br 1 ;; --jump to head of while loop--'+LF();block=block+TABs(indent+1)+'end ;; end of if-then'+LF();block=block+TABs(indent)+'end ;; --end of while loop--';returnblock;}

while の実行

whileのサンプルも用意しました。

while.js
leta=0;while(a<10){putn(a);a=a+1;}0;

これをWASMに変換、実行した結果がこちらです。

$ node mininode_wasm_05.js sample/while.js
$ wat2wasm generated.wat
$ node run_wasm_putn.js generated.wasm
Loading wasm file: generated.wasm
0
1
2
3
4
5
6
7
8
9
ret code=0

無事ループが実行されました。

次回は

いよいよ目標の1つであるFizzBuzzにチャレンジする予定です。

ここまでのソース

GitHubにソースを上げておきます。

Node.js でつくる WASMコンパイラー - 06:文字列出力を実装しFizzBuzzを実現する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

これまでの取り組み

今回の実現したいこと

前回作った if による条件分岐と while ループを使って FizzBuzz を実現するのが今回の目的です。が、そのためには準備しなくてなならないものがあります。それは「Fizz」「Buzz」といった決まった文字列を画面出力する手段です。

  • 固定の文字列(文字列定数)を扱う
  • 文字列定数を標準出力に表示する、 puts() 組み込み関数

puts() の実体は、putn() の時と同様に、WASMの呼び出し元(Node.js)で用意して渡してあげます。WASMの中では、それを import して利用します。

文字列定数の表現

文字列定数を扱う方法として、memory があります。64Kib(64*1024バイト)単位で確保されるデータ領域で、名前をつけることができます。下記は3つの文字列定数を確保している例です。データ領域の先頭からのオフセットをi32定数で指定し、文字列の終端はNull文字(0x00)を付けています。

  (memory $string_area 1) ;; string_area 64KiB
  (data (i32.const 0) "abc\00")
  (data (i32.const 4) "ABCDEFG\00")
  (data (i32.const 12) "12345678901234567890123456789012345678901234567890\00")

対象となるJSコード内に文字列が出てきたら覚えておき、WATを生成する際に memory と data のブロックを書き出すことにします。

WASM内部から呼び出し元へ文字列を渡す

用意した文字列を出力するには、putn()の時と同様に呼び出し元のNode.jsで用意した関数をインポートして使う必要があります。文字列を渡すときには、次の情報を伝えます。

  • WASMで確保した memory 領域をエクスポート
  • その領域内での文字列の開始オフセット
  • 文字列の長さ ... ※今回は直接長さを渡すのではなく、Null文字で終端する方式

memory 領域は次の様に名前(この例では"exported_string")をつけてエクスポートします。

  (memory $string_area 1) ;; string_area 64KiB
  (data (i32.const 0) "abc\00")
  (data (i32.const 4) "ABCDEFG\00")
  ; ... 省略 ...
  (export "exported_string" (memory $string_area))

あとはインポートした関数に文字列の先頭オフセットを渡して呼び出せば、出力したい文字列を渡すことができます。関数を $puts() という名前でインポートしているとすると、次の様になります。

(func $puts (import "imports" "imported_puts") (param i32)) ;; インポート
    ;; ... 省略 ...
    i32.const 4  ;; "ABCDEFG\00" のオフセット
    call $puts   ;; puts()を呼び出す

呼び出し元での puts() の準備

呼び出しもとのNode.jsでは、次の様に puts()を準備し、文字列を出力しています。

関数の準備
constimports={imported_putn:function(arg){// built-in function putn(): 32ビット整数の出力console.log(arg);},imported_puts:function(offset){// built-in function puts(): 文字列の出力letstr='';letarr=newUint8Array(exported_string.buffer);for(leti=offset;arr[i];i++){str+=String.fromCharCode(arr[i]);}console.log(str);}};

imported_puts() では、引数で渡されたオフセットから1バイトずつ取ってきて、UNICODE文字列に変換しています。Null文字(0x00)が登場したら変換を終了し、コンソールに出力します。

WASMの呼び出しとmemory領域の取得
WebAssembly.instantiate(typedArray,{imports:imports}).then(result=>{exported_string=result.instance.exports.exported_string;// WASMからエクスポートされたメモリー領域ret=result.instance.exports.exported_main();console.warn('ret code='+ret);process.exit(ret);}).catch(e=>{console.log(e);});

WASMを呼び出す際には、インスタンス化した際にエクスポートされるメモリー領域を exported_string に記憶しておき、先の imported_puts() で利用できるようにしています。

コンパイラーでの文字列定数の扱い

グローバルコンテキストの導入

03:ローカル変数を実装するでは変数を扱うためにローカルコンテキストを導入しましたが、今回は文字列定数を格納するためにグローバルコンテキストを導入します。言い換えれば、文字列定数はグローバルで共通のリソースということになります。

letg_ctx={'strIdx':0,// string index'strOffset':0,// offset of next string'strList':{},// string hash:  strList['$s_1'] = ['xxxxx', offset, length]'funcList':{},// function hash: funcList['func1'] = [func_type, func_symbol, ret_type, args_count, func_body]//  ex) funcList['add'] = ['user_defined', '$add', 'i32', 2, '.....']};
  • strIdx ... 文字列定数の通し番号(0〜)。文字列の名前は $s_0, $s_1, ...
  • strOffset ... 次の文字列が格納される先頭オフセット
  • strList ... 文字列を保持するハッシュ(連想配列)
    • strList['$s_1'] = ['xxxxx', offset, length] という形で保持

また funcList は今回はまだ使いませんが、今後ユーザー定義関数の保持に使う予定です。

文字列定数の記録

文字列定数は、次の様にリテラルとして単純化ASTで表現されます。

[ 'lit', 'abc' ]

そのため、以前用意したリテラルを扱う処理を拡張します。

functiongenerateLiteral(tree,indent,gctx,lctx){constv=tree[1];constt=getTypeOf(v);if(t==='number'){constblock=TABs(indent)+'i32.const '+v;returnblock;}if(t==='string'){// --- string literal ---constoffset=addGlobalString(tree[1],gctx);constblock=TABs(indent)+'i32.const '+offset;returnblock;}println('---ERROR: unknwon type of literal--:'+t);abort();}

文字列定数を記憶しておく処理は addGlobalString() で行います。文字列の先頭オフセットを返すようにしています。

// -- add global string, return name of string --functionaddGlobalString(str,gctx){// -- strings --// '$s_1' : ['xxxxxxx', offset, len],// --- name of stringletidx=gctx['strIdx'];constname='$s_'+idx;idx=idx+1;gctx['strIdx']=idx;constlen=getLength(str);constcstr=str+'\\00';constclen=len+1;constcoffset=gctx['strOffset'];constnextOffset=coffset+clen;gctx['strOffset']=nextOffset;constglobalString=[cstr,coffset,clen];letstrList=gctx['strList'];strList[name]=globalString;returncoffset;}

文字列定数の利用

今回は文字列の変数への代入や連結などの操作は一切サポートしません。できるのは、puts()を使って標準出力に表示するだけです。そのため、puts()の引数に文字列の先頭オフセットを渡すことだけできるようにします。

文字列定数の利用例
puts("hello");

これに対応する単純化ASTはこちらです。

 [ 'func_call', 'puts', [ 'lit', 'hello' ] ]

対応するWATはこちら。

    i32.const 4  ;; memory領域における、文字列の先頭オフセット
    call $puts

先頭オフセットの値は先ほど用意した addGlobalString()を呼び出した際の戻り値を利用します。すでに示した様に、generateLiteral() では、addGlobalString()の戻り値を、i32.const の32ビット符号付整数としてスタックに積むコードを生成しています。

generateLiteral()の抜粋
// --- string literal ---constoffset=addGlobalString(tree[1],gctx);constblock=TABs(indent)+'i32.const '+offset;

puts()関数の呼び出し部分は、簡易デバッグ関数を拡張して対応します。

functiongenerate(tree,indent,gctx,lctx){// ... 省略 ...// === tentative func call for debug (putn, puts) ====if(tree[0]==='func_call'){// tree = ['func_call', 'name', arg1, arg2, ... ]constfuncName=tree[1];if(funcName==='putn'){returngenerateCallPutn(tree,indent,gctx,lctx);}if(funcName==='puts'){returngenerateCallPuts(tree,indent,gctx,lctx);}println('-- ERROR: unknown func in generate() ---');printObj(tree);abort();}// ... 省略 ...}// --- debug func puts() ---functiongenerateCallPuts(tree,indent,gctx,lctx){// tree = ['func_call', 'name', arg1, arg2, ... ]constvalueBlock=generate(tree[2],indent,gctx,lctx);letblock=valueBlock+LF();block=block+TABs(indent)+'call $puts'+LF();returnblock;}

文字列定数の生成

WATを生成する際には、グローバルコンテキストに保持していた文字列定数をまとめて書き出します。

functioncompile(tree,gctx,lctx){// ... 省略 ...// --- mempory segment (static string) --conststringBlock=generateGlobalString(gctx);block=block+LF();block=block+TAB()+';; ---- export static string  ---'+LF();block=block+TAB()+'(memory $string_area 1) ;; string_area 64KiB'+LF();block=block+stringBlock;block=block+TAB()+'(export "exported_string" (memory $string_area))'+LF();// ... 省略 ...}// --- 文字列定数を書き出す ---functiongenerateGlobalString(gctx){letblock='';conststrList=gctx['strList'];conststrings=getKeys(strList);constlen=getLength(strings);letkey;leti=0;letgstr;letoffset;letstr;while(i<len){key=strings[i];gstr=strList[key];// ['xxxxxxx', offset, length]str=gstr[0];offset=gstr[1];block=block+TAB()+'(data (i32.const '+offset+') "'+str+'")'+LF();i=i+1;}returnblock;}

実行例

次のコードを用意します。

putn_puts.js
putn(123);puts('abc');putn(456);puts('ABCDEFG');1;

ここまでのコンパイラーのソースコードを、mininode_wasm_06.js とします。またputs()も使える様に準備した呼び出し用ソースは run_wasm_builtin.js として用意しています。

  • コンパイラーで、sample/putn_puts.js → generated.wat
  • wat2wasm で、generated.wat → generated.wasm
  • run_wasm_builtin.js で実行
$ node mininode_wasm_06.js sample/putn_puts.js
$ cat generated.wat
cat generated.wat
(module
  ;; ---- builtin func imports ---
  (func $putn (import "imports" "imported_putn") (param i32))
  (func $puts (import "imports" "imported_puts") (param i32))

  ;; ---- export static string  ---
  (memory $string_area 1) ;; string_area 64KiB
  (data (i32.const 0) "abc\00")
  (data (i32.const 4) "ABCDEFG\00")
  (export "exported_string" (memory $string_area))

  ;; ---- export main function  ---
  (export "exported_main" (func $main))
  (func $main (result i32)

    i32.const 123
    call $putn

    i32.const 0
    call $puts

    i32.const 456
    call $putn

    i32.const 4
    call $puts

    i32.const 1

    return
  )
)
$ wat2wasm generated.wat
$ node run_wasm_builtin.js generated.wasm
Loading wasm file: generated.wasm
123
abc
456
ABCDEFG
ret code=1
$

無事、整数と文字列が出力されました。

FizzBuzzの実現

さて、いよいよ目標の1つであるFizzBuzzの実現です。ここまで用意したif, while, putn(), puts()を総動員します。

fizzbuzz_loop.js
leti=1;while(i<=100){if(i%(3*5)===0){puts('FizzBuzz');}elseif(i%3===0){puts('Fizz');}elseif(i%5===0){puts('Buzz');}else{putn(i);}i=i+1;}0;

これをコンパイル、実行すると、FizzBuzzの達成です!

$ node mininode_wasm_06.js sample/fizzbuzz_loop.js 
$ wat2wasm generated.wat
$ node run_wasm_builtin.js generated.wasm
Loading wasm file: generated.wasm
1
2
Fizz
4
Buzz
Fizz
7
...省略...
94
Buzz
Fizz
97
98
Fizz
Buzz
ret code=0

次回は

次回は、ユーザー定義関数を実装する予定です。

ここまでのソース

GitHubにソースを上げておきます。

Node.js でつくる WASMコンパイラー - 07:ユーザ定義関数を実装する

$
0
0

はじめに

Node.jsで小さなプログラミング言語を作ってみるシリーズを、「ミニインタープリター」「ミニコンパイラー」とやってきました。そして三部作(?)の最後として、 ミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

これまでの取り組み

今回の実現したいこと

これまで関数はあらかじめ用意している2つの組み込み関数だけが利用できました。今回は自由に関数を定義して、呼び出せる様にします。

WATでの関数の定義

JSで次の様な関数を考えます。

functionadd(x,y){returnx+y;}

対応するWATでは、次の様に関数を記述します。

(func $add (param $x i32) (param $y i32) (result i32)
  get_local $x
  get_local $y
  i32.add
  return
)

WATでの関数の呼び出し

すでに putn() / puts() で見た様に、関数呼び出しは次の様に引数をスタックに積んでから呼び出します。

  i32.const 1
  i32.const 2
  call $add

このようなWATを生成できるようにして行きます。

ユーザ定義関数の実装

関数の定義

ユーザ定義関数が登場した場合、パーサーは次の単純化ASTを生成します。

  • [ 'func_def', 関数名, [ 引数の配列 ], [ 関数の中身 ] ]

上記の add() 関数の場合は次の様になります。

[ 'func_def',
    'add',
    [ 'x', 'y' ],
    [ 'ret', 
        [ '+', [ 'var_ref', 'x' ], [ 'var_ref', 'y' ] ] 
    ] 
]

これを扱えるように、コンパイラーを拡張します。

functiongenerate(tree,indent,gctx,lctx){// ... 省略 ...// --- func_def user_defined function ---if(tree[0]==='func_def'){constblock=generateFuncDef(tree,1,gctx);returnblock;}// ... 省略 ...}functiongenerateFuncDef(tree,indent,gctx){// tree = ["func_def", name, args[], body[]]// -- append to global context --// function hash: funcList['func1'] = [func_type, func_symbol, ret_type, func_block]//  ex) funcList['add'] = ['user_defined', '$add', 'i32', '...']constfuncName=tree[1];constargs=tree[2];constargCount=getLength(tree[2]);constfuncSymbol='$'+funcName;constfuncType='i32';constfuncResult='(result '+funcType+')';letfuncBlock=TABs(indent)+'(func '+funcSymbol;// -- add temporary with empty body ---addGlobalFunc(gctx,funcName,funcSymbol,funcType,'');// --- agrs ---letargBlock='';leti=0;while(i<argCount){argBlock=argBlock+''+'(param $'+args[i]+' i32)';i=i+1;}funcBlock=funcBlock+argBlock+''+funcResult+LF();// --- func bocy ---// --- prepare new local context for inside of function --letnewLctx=initialLocalContext();// --- NEED to add args as localVari=0;letname;letvarName;while(i<argCount){name=args[i];varName='$'+name;newLctx[name]=varName;i=i+1;}letfuncBody=generate(tree[3],indent+1,gctx,newLctx);// --- build func block ---constvarOffset=argCount;constvarBlock=generateVariableBlock(indent+1,newLctx,varOffset);funcBlock=funcBlock+varBlock+funcBody;// --- add dummy return ---funcBlock=funcBlock+TABs(indent+1)+'i32.const 88'+LF();funcBlock=funcBlock+TABs(indent+1)+'return'+LF();// --- close func definition ---funcBlock=funcBlock+TABs(indent)+')'+LF();// ==== whole func definition ===addGlobalFunc(gctx,funcName,funcSymbol,funcType,funcBlock);println('---funcBlock--');println(funcBlock);// --- no code in this timing --constempty='';returnempty;}

関数定義を生成する generateFuncDef() では、複数の処理を行います。

  • 前回用意したグローバルコンテキストに関数の情報を仮登録する ... addGlobalFunc()
  • 関数のシンボル、引数、戻り値を宣言を生成
  • 関数内で使うローカル変数を宣言生成 ... generateVariableBlock()
  • 関数の処理の中身を生成
    • この時に、関数内のローカル変数を扱うために新しいローカルコンテキストを生成
    • 手抜きで関数の最後で return しないケース(if-then, while のブロック内で return)をきちんと判別できていないため、ダミーの return 文を最後に付与
  • 改めて、グローバルコンテキストに関数の情報を登録する ... addGlobalFunc()

最初に関数の情報を仮登録するのは、以前のLLVMを使ったコンパイラーを実装したときに、ユーザー定義関数の中で再帰呼び出しを行うケースに対処した経験に基づいています。

functiongenerateVariableBlock(indent,lctx,offset){constvariables=getKeys(lctx);constlen=getLength(variables);letkey;leti=offset;letblock='';letvarName;while(i<len){key=variables[i];varName=lctx[key];//  (local $a i32)block=block+TABs(indent)+'(local '+varName+' i32)'+LF();i=i+1;}returnblock;}//ex) funcList['add'] = ['user_defined', '$add', 'i32', '.....']functionaddGlobalFunc(gctx,name,symbol,type,funcBlock){letfuncList=gctx['funcList'];funcList[name]=['user_defined',symbol,type,funcBlock];}functiongetGlobalFunc(gctx,name){constfuncList=gctx['funcList'];returnfuncList[name];}

また今回扱うユーザ定義に関数には、いくつか制約があります。

  • 呼び出すよりも前に定義されている必要がある
  • 必ず 32ビット符号付整数を返す

関数からのreturn

ユーザ定義関数の生成のために、return文にも対応しておきます。return文は次の単純化ASTで表現されます。

[ 'ret', _戻り値の式_ ]

これに対応する様に、コンパイラーを拡張します。

functiongenerate(tree,indent,gctx,lctx){// ... 省略 ...// --- return from function ---if(tree[0]==='ret'){constblock=generateFuncRet(tree,indent,gctx,lctx);returnblock;}// ... 省略 ...}functiongenerateFuncRet(tree,indent,gctx,lctx){constvalueBlock=generate(tree[1],indent,gctx,lctx);letblock=valueBlock+LF();block=block+TABs(indent)+'return'+LF();returnblock;}

generateFuncRet()では、戻り値の式の結果をスタックに積み、return を行います。

ユーザ定義関数の呼び出し

関数呼び出しの部分は、もともと用意していた組み込み関数のputn()/puts()呼び出しの部分を拡張しました。

functiongenerate(tree,indent,gctx,lctx){// ... 省略 ...if(tree[0]==='func_call'){// tree = ['func_call', 'name', arg1, arg2, ... ]constfuncName=tree[1];// --- builtin function ---if(funcName==='putn'){returngenerateCallPutn(tree,indent,gctx,lctx);}if(funcName==='puts'){returngenerateCallPuts(tree,indent,gctx,lctx);}// --- user defined functions ---constblock=generateFunCall(tree,indent,gctx,lctx);returnblock;}// ... 省略 ...}// --- user defined functions ---functiongenerateFunCall(tree,indent,gctx,lctx){// tree ['func_call', 'name', arg1, arg2, ... ]constfuncName=tree[1];constgfunc=getGlobalFunc(gctx,funcName);// gfunc : ['user_defined', symbol, type, funcBlock];if(gfunc){letblock='';// --- args ---letargBlock='';leti=0;while(tree[2+i]){argBlock=argBlock+generate(tree[2+i],indent,gctx,lctx)+LF();i=i+1;}// --- call ---block=block+argBlock;block=block+TABs(indent)+'call '+gfunc[1]+LF();returnblock;}println('-- ERROR: unknown func in generateFunCall() name='+funcName);abort();}

実際の呼び出し処理は generateFunCall() で生成しています。

  • getGlobalFunc() で、登録済みのユーザ定義関数を探し、シンボル名を取得
  • 引数をスタックに積む
  • 関数を呼び出す

定義時の引数の数と、呼び出し時に渡される引数の数のチェックはしていません。

ユーザ定義関数の実体の生成

関数定義の処理では、グローバルコンテキストに登録するとこまでしかやっていないので、最後に全体を生成するさに、一緒に出力します。

// ---- compile simplified tree into WAT ---functioncompile(tree,gctx,lctx){// ... 省略 ...// --- export main function  --block=block+LF();block=block+TAB()+';; ---- export main function  ---'+LF();block=block+TAB()+'(export "exported_main" (func $main))'+LF();block=block+TAB()+'(func $main (result i32)'+LF();block=block+varBlock+LF();block=block+mainBlock+LF();block=block+TAB()+TAB()+'return'+LF();block=block+TAB()+')'+LF();// ---- global user_defined functions ---block=block+generateGlobalFunctions(gctx);block=block+')';returnblock;}functiongenerateGlobalFunctions(gctx){letblock=LF();block=block+TAB()+';; --- user_defined functions ---'+LF();constnames=getGlobalFunctionNames(gctx);constlen=getLength(names);println('--getGlobalFunctionNames() len='+len);letkey;leti=0;letgfunc;while(i<len){key=names[i];gfunc=getGlobalFunc(gctx,key);// gfunc : ['user_defined', symbol, type, funcBlock];if(gfunc[0]==='user_defined'){block=block+gfunc[3]+LF();}i=i+1;}returnblock;}

generateGlobalFunctions() では、登録されているユーザ定義関数の実体を取り出して、順番に出力しています。

ユーザ定義関数の利用例:再帰でフィボナッチ数列

今回の目標の一つだったフィボナッチ数列の計算を、再帰呼び出しを使ってやってみます。

元のソース

fib_func.js
functionfib(x){if(x<=1){returnx}else{returnfib(x-1)+fib(x-2);}}leti=0;while(i<10){putn(fib(i));i=i+1;}0;

コンパイラーで生成された WAT

generated.wat
(module
  ;; ---- builtin func imports ---
  (func $putn (import "imports" "imported_putn") (param i32))
  (func $puts (import "imports" "imported_puts") (param i32))

  ;; ---- export static string  ---
  (memory $string_area 1) ;; string_area 64KiB
  (export "exported_string" (memory $string_area))

  ;; ---- export main function  ---
  (export "exported_main" (func $main))
  (func $main (result i32)
    (local $i i32)

    i32.const 0
    set_local $i

    loop ;; --begin of while loop--
      get_local $i
      i32.const 10
      i32.lt_s
      if
        get_local $i
        call $fib

        call $putn

        get_local $i
        i32.const 1
        i32.add

        set_local $i

        br 1 ;; --jump to head of while loop--
      end ;; end of if-then
    end ;; --end of while loop--
    i32.const 0

    return
  )

  ;; --- user_defined functions ---
  (func $fib (param $x i32) (result i32)
    get_local $x
    i32.const 1
    i32.le_s

    if
      get_local $x
      return
    else
      get_local $x
      i32.const 1
      i32.sub

      call $fib

      get_local $x
      i32.const 2
      i32.sub

      call $fib

      i32.add

      return
    end
    i32.const 88
    return
  )

)

実行結果

$ wat2wasm generated.wat
$ node run_wasm_builtin.js generated.wasm
Loading wasm file: generated.wasm
0
1
1
2
3
5
8
13
21
34
ret code=0

無事ユーザ定義関数を使い、実行することができました。

次回は

WASMコンパイラーの実装は今回で一区切りなのですが、追加の取り組みとして WASI (WebAssembly System Interface) 対応を行う予定です。

ここまでのソース

GitHubにソースを上げておきます。


Node.jsでつくるNode.js-WASMコンパイラ - もくじ

$
0
0

はじめに

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」(ラムダノート, Amazon) という本に感銘を受けて、自分でもNode.jsで小さなプログラミング言語を作ってみるシリーズをやってみました。「ミニインタープリター」「ミニコンパイラー」ときて、三部作(?)の最後はミニNode.jsからWASMを生成する小さなコンパイラーに取り組んでいます。

前提環境

今回は私の環境である macOS 10.14 Mojave を前提にしています。また Node.js v10.13 で動作を確認しています。

Node.js-WASM コンパイラー目次

謝辞

「RubyでつくるRuby ゼロから学びなおすプログラミング言語入門」(ラムダノート, Amazon) に感銘をうけて自分でもミニNode.jsを作って見ましたが、本を読んでいた時には気がつかなかったことが多々ありました。書籍の構成、ミニRubyの設計がとても優れていて、とても参考になりました。
素晴らしい書籍を作ってくださった作者の遠藤さんとラムダノートさんに改めて感謝します。ありがとうございました。

ソースコード

今回作ったコードは GitHub で公開しています。

Cloud Functions for FirebaseとFirebase Hostingを試してみる

$
0
0

やること

Cloud Functions for FirebaseとFirebase Hostingを試してみようと思います。
html/cssの静的ファイルのデプロイ、jsファイルの動的ファイルのデプロイ、html/css/jsの動的サイトのデプロイ、の3パターンを試してみようと思います。

Firebaseを使うための準備

Firebase CLIをインストールとログインをします。

~$ npm i -g firebase-tools

インストールが完了したらプロジェクトディレクトリを作成&移動してFirebaseにloginします。

~$ mkdir firebase-sample
~$ cd firebase-sample
~$ firebase login

loginしたらFirebase ConsoleでFirebaseプロジェクトを作成しておきます。今回は「myproj」という名前で作っておきましょう。
Firebaseプロジェクトを作成したらinitしていきます。

~$ firebase init

設定内容は以下の通りでです。

  • features:FunctionsとHosting
  • Option:Use an existing project
  • Firebase project for this directory:myproj
  • language:JavaScript
  • use ESLint:n
  • install dependencies with npm now:y
  • use as your public directory:public
  • Configure as a single-page app:y

静的ファイルのデプロイと動的ファイルのデプロイ

ちょっと手抜きをして静的ファイルのデプロイと動的ファイルのデプロイを一緒にやっていきます。

initが完了したら諸々ファイルが作られていると思います。funtionsに動的ファイル、publicに静的ファイルを配置します。init後はそれぞれサンプル的なファイルが配置されていると思います。/functions/index.jsを以下のように書きまえます(コメントを外すだけです)。

constfunctions=require('firebase-functions');// // Create and Deploy Your First Cloud Functions// // https://firebase.google.com/docs/functions/write-firebase-functions//exports.helloWorld=functions.https.onRequest((request,response)=>{response.send("Hello from Firebase!");});

次にfirebase.jsonを以下のように修正します。rewritesにオブジェクトを追加しているだけです。

{"hosting":{"public":"public","ignore":["firebase.json","**/.*","**/node_modules/**"],"rewrites":[{"source":"/hello","function":"helloWorld"},{"source":"**","destination":"/index.html"}]}}

firebase.jsonを修正したらプロジェクトをローカルで動かしてみましょう。

~$ firebase serve

サーバが起動したらlocalhost:5000にアクセスして/public/index.htmlの内容が表示、localhost:5000/helloにアクセスして「Hello from Firebase!」を表示されていればローカルでの動作確認OKです。

Firebaseにデプロイする前に/public/style.cssを作成し、HTMLを書き換えましょう。

body{background-color:antiquewhite;}
<!DOCTYPE html><html><head><metacharset="utf-8"><title>Firebase hosting sample</title><linkrel="stylesheet"href="./style.css"></head><body><p>index.html</p></body></html>

先程と同じようにローカルで確認して問題が無ければ以下のコマンドでデプロイしましょう。

~$ firebase deploy

デプロイが完了したらTerminalにHosting URLが表示されていると思うのでアクセスして先程修正したHTMLが表示されていれば静的ファイルのデプロイはOKです!

[Hosting URL]/helloにアクセスして「Hello from Firebase!」と表示されていれば動的ファイルのデプロイもOKです!

動的サイトのデプロイ

動的サイトはExpressで作成していきます。必要なPackageをインストールします。インストールする時にfunctionsディレクトリに移動するのを忘れないでください。

~$ cd functions
~$ npm i express jade

それでは動的サイトをササッと作っていきましょう。ExpressやJadeの詳細は割愛します。

/functions/index.jsを以下のように修正します。

constfunctions=require('firebase-functions');constexpress=require('express');constpath=require('path');constapp=express();app.set("views",path.join(__dirname,"views"));app.set("view engine","jade");app.get('/jade',function(req,res){res.render('sample',{message:'Hello Jade!!'});});exports.site=functions.https.onRequest(app);

firebase.jsonを以下のように修正します。

{"hosting":{"public":"public","ignore":["firebase.json","**/.*","**/node_modules/**"],"rewrites":[{"source":"**/*","function":"site"}]}}

/functionsにviewsディレクトリを作成し、/functions/views/sample.jadeを追加して以下のコードを貼り付けます。

doctype
html(lang="ja")
  head
    title Web site!!
    link(rel='stylesheet', href='style.css')
  body
    p= message

ローカルで動かしてlocalhost:5000/jadeにアクセスして「Hello Jade!!」と表示されていれば動作確認OKです!firebaseにデプロイして同じ挙動になるか動作してみましょう!

おわり

Firebase Realtime DBも試してみたいし、GitHub Actions使って自動デプロイとかもやってみたい。

初学者が、LINE Botを作るまで

$
0
0

はじめに

製作者がLNE Botを作るまでの道のりを、日記代わりに付けているものです。
初学者の参考になれば、幸いです。
Qiita初投稿のため、見辛い部分はあると思われます。
下記のページを参照しながらの制作を行なっております。
 LINEのBot開発 超入門(前編) ゼロから応答ができるまで

<開発環境>
MacBook Air (13-inch, 2017)
OS:Mojave
バージョン:10.4.6

Node.jsをインストール

Node.jsを使用して、Bot本体を作成します。

フォルダーの作成&移動

$ cd ~
$ mkdir -p App/bot
$ cd App/bot

npm initコマンドでpackage.jsonファイルを作成

$ npm init

すると、下記の画面が発生。
npm initコマンド を実行する前段階が整っていないため、発生している。

-bash: npm: command not found

次に、nodebrewをインストールしてみる。

$ nodebrew ls-remote

すると、下記の画面が発生。
nodebrew ls-remoteコマンド を実行する前段階が整っていないため、発生している。

-bash: npm: command not found

色々探した結果、下記のページを参照することで、解決できた。
MacにNode.jsをインストール

新しい Mac に Git と Node.js の環境を作る

$
0
0

どうでも良い前置き

5 年ぶりぐらいに Mac を買い替えました。
今までの買い替えの際には移行アシスタントを使って環境をそのまんま移行していましたけれども、これまでも Mac 買い替えのたびに前の端末から移行を繰り返して来ており、もはや不要な設定やソフトも沢山ある状態でした。
と言うわけで今回は、昨日までの弱虫だった自分を脱ぎ捨てて新しいワタシに生まれ変わろうと言う趣旨で、移行アシスタントに頼らずゼロから環境を作ることにしました。
データの大部分を iCloud に置く運用にしていたので端末固有の情報に対する依存性が下がって来ているのもその決断を後押ししています。

とは言え、ローカルの開発環境はローカルに作らなければなりません。
と言うわけで、新しい Mac に開発環境を作る流れの記録です。

この記事でやること

  • Xcoce コマンドラインツールのインストール
  • Homebrewのインストール
  • Gitのインストール
  • Node.jsのインストール
  • Visual Studio Codeのインストール
  • その他、細々したもの

開発環境構築と言っときながら Xcodeとか eclipseとか Xamarinの話はしないのかよとおっしゃる向きもあるかも知れませんが、当方 JS 大好き人間ですので(←)、そこらへんはどうかご容赦いただければ。

OS 環境と前提

導入時の OS は macOS Catalinaです。
Mac 購入時点ではちょうど OS の入れ替わりのタイミングで、初期導入 OS は Mojaveでしたが直ぐに Catalinaにアップデートしました。
Catalinaからデフォルトシェルが zshに変わりましたけれども、前バージョンからのアップデートだと引き続き bashがデフォルトシェルとして使用され続けます。
この記事では bash前提なので、zshの方は適宜読み替えていただければと思います。

また、この記事は 2019 年 11 月時点の内容に基づいています。
将来的にはここで記されている通りには行かなくなるかも知れません。

環境構築

Xcode コマンドラインツール と Homebrew をインストールする

一連の流れで同時にできます。
Homebrew の公式サイトの手順に従います。

ターミナルを立ち上げて、公式サイトで記されている以下のスクリプトを実行します。

/usr/bin/ruby -e"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

こう言うのをインストールしますよとかこう言うディレクトリを自動的に作成しますよなど情報が出ます。
01-001-homebrew.png

ここで、最後の方に記述がある

==> The Xcode Command Line Tools will be installed.

に注目してください。
Xcode コマンドラインツールXcode導入後にメニューからインストールすることもできますが、重量級の開発環境である Xcodeをインストールしなくともコマンドラインツールだけ個別で入れることができます。便利ですね!

ともかく、Enter キーを押下。

パスワードの入力を求められますので入力。言わずもがなですが管理者権限が必要です。

バックグラウンドでXcode コマンドラインツールのダウンロードとインストールが実行されます。

そのまま待っていれば Homebrewもインストールされます。
01-002-homebrew-installed.png

バージョンを確認してみます。

$ brew --version
Homebrew 2.1.16
Homebrew/homebrew-core (git revision 268c; last commit 2019-11-17)

Git をインストールする

macOSには標準で Gitがインストール済みです。

$ git --version
git version 2.21.0 (Apple Git-122.2)

これで事足りると言えば事足りるわけですが、せっかくなので HomebrewGitをインストールします。

$ brew install git

依存モジュールとともにインストールされます。
02-001-git-installed.png

ただ、これだけだと依然として gitコマンドは Apple 謹製のものを参照し続けます。
今インストールしたものを利用するよう設定します。

echo'export PATH="/usr/local/Cellar/git/2.24.1_0/bin:$PATH"'>> ~/.bash_profile

バージョンのところは適宜インストールされたバージョンに読み替えてください。
zshの人は最後は >> ~/.zshrcにすれば良いかと思います。

これで、新しくターミナルウィンドウを立ち上げるか source ~/.bash_profileすれば Homebrewでインストールした Gitが有効になります。

$ git --version
git version 2.24.0

Node.js をインストールする

お次は Node.jsです。これも Homebrewでインストールします。
まずはバージョンマネージャーの Nodebrewから。
nvmでも別に良いと思いますので、何を選ぶかはお好みで。

$ brew install nodebrew

03-001-nodebrew.png

バージョンを確認しておきます。

$ nodebrew --version
nodebrew 1.0.1

Usage:
    nodebrew help                         Show this message
    nodebrew install<version>            Download and install<version> (from binary)
    nodebrew compile <version>            Download and install<version> (from source)
    nodebrew install-binary <version>     Alias of `install`(For backword compatibility)
    nodebrew uninstall <version>          Uninstall <version>
    nodebrew use <version>                Use <version>
    nodebrew list                         List installed versions
    nodebrew ls                           Alias for`list`
    nodebrew ls-remote                    List remote versions
    nodebrew ls-all                       List remote and installed versions
    nodebrew alias<key> <value>          Set alias
    nodebrew unalias<key>                Remove alias
    nodebrew clean <version> | all        Remove source file
    nodebrew selfupdate                   Update nodebrew
    nodebrew migrate-package <version>    Install global NPM packages contained in<version> to current version
    nodebrew exec<version> --<command>  Execute <command> using specified <version>

Example:
    # install
    nodebrew install v8.9.4

    # use a specific version number
    nodebrew use v8.9.4

インストール可能な Node.jsのバージョンを確認します。

$ nodebrew ls-remote
v0.0.1    v0.0.2    v0.0.3    v0.0.4    v0.0.5    v0.0.6    

(省略)

v13.0.0   v13.0.1   v13.1.0   

io@v1.0.0 io@v1.0.1 io@v1.0.2 io@v1.0.3 io@v1.0.4 io@v1.1.0 io@v1.2.0 io@v1.3.0
io@v1.4.1 io@v1.4.2 io@v1.4.3 io@v1.5.0 io@v1.5.1 io@v1.6.0 io@v1.6.1 io@v1.6.2
io@v1.6.3 io@v1.6.4 io@v1.7.1 io@v1.8.1 io@v1.8.2 io@v1.8.3 io@v1.8.4 

io@v2.0.0 io@v2.0.1 io@v2.0.2 io@v2.1.0 io@v2.2.0 io@v2.2.1 io@v2.3.0 io@v2.3.1
io@v2.3.2 io@v2.3.3 io@v2.3.4 io@v2.4.0 io@v2.5.0 

io@v3.0.0 io@v3.1.0 io@v3.2.0 io@v3.3.0 io@v3.3.1 

欲しいバージョンを指定して Node.jsをインストールします。
ここでは最新の v13.1.0をインストールしてみます。

$ nodebrew install v13.1.0

以前使われていたサブコマンド install-binaryinstallのエイリアスになっているようです。

コマンド実行時に以下のようなエラーが出た場合は、

Fetching: https://nodejs.org/dist/v13.1.0/node-v13.1.0-darwin-x64.tar.gz
Warning: Failed to create the file                                                                                     
Warning: /Users/********/.nodebrew/src/v13.1.0/node-v13.1.0-darwin-x64.tar.gz: 
Warning: No such file or directory
                                                                                                                    0.0%
curl: (23) Failed writing body (0 != 1028)
download failed: https://nodejs.org/dist/v13.1.0/node-v13.1.0-darwin-x64.tar.gz

ディレクトリを切っておけば解決します。

$ mkdir-p ~/.nodebrew/src

インストールした Node.jsを有効にします。

nodebrew use v13.1.0

パスを通します。
これも bash以外の人はよしなにお願いします。

echo'export PATH=$HOME/.nodebrew/current/bin:$PATH'>> ~/.bash_profile

これで Node.jsのインストールは終わりです。

$ node --version
v13.1.0

Visual Studio Code をインストールする

コードエディタは人によって色々お好みとか宗派があるでしょうが個人的には VS Codeがお好みです。

公式サイトより ZIP ファイルをダウンロードして、解凍し、程良いところに配置します。
Windows ではユーザーのディレクトリにインストールするのが常道になっていますので、その流儀に則れば自分のホームディレクトリにしかるべきディレクトリを切って実行ファイルを配置すれば良いでしょう。
けれども従来通りアプリケーションフォルダに放り込んでも別に良いと思います。
04-001-vscode.png

おすすめの機能拡張は色々ありますが、まずは最低限以下のものを。

その他諸々

フォント指定

コーディングフォントは Ricty Diminished Discordが見易くて好きです。
インストールしたら、 VS Codesettings.jsonで以下のようにすれば反映されます。
また、5K の画面に 12px のフォントは小さすぎて疲れ目が促進されるので少し大きくしておきます。

{"editor.fontFamily":"Ricty Diminished Discord","editor.fontSize":14}

マークダウンのプレビューの改行

VS Codeデフォルトのプレビュー機能を利用する場合、末尾に半角スペース2つを置かなければプレビュー上改行されませんが、以下の設定を入れればただの改行だけでプレビュー上でも改行して表示されます。

{"markdown.preview.breaks":true}

指定文字数の位置にルーラーを表示する

こう言うコーディング規約は割と一般的ですよね?

{"editor.rulers":[80,100]}

配列で渡すと複数のルーラーを置けます。

デフォルトシェルを zsh に切り替える

bashを使い続けているとターミナルを起動するたびに毎度毎度 Catalina 様にお小言を頂戴する羽目になります。

The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.

お小言を黙らせる手段もきっとあるのでしょうが、Catalina 様の思し召しのままにデフォルトシェルを zshに切り替えてみます。

$ chsh -s /bin/zsh

実行にはパスワードの入力が求められます。

これだけだとパスとかの設定をやり直しになりますので、

% ln-s ~/.bash_profile ~/.zshrc

として両刀遣いを気取ります。
.zshrcに対応するのは .bashrcだろうとか、そもそも lnではなく cpしなさいよとか色々ご意見はありましょうが、zsh特有の設定変更を行いたくなった時にちゃんとすれば良いかと思います。

他にも色々あると思いますけれども、まずはここまで。

参考

macOS用パッケージマネージャー — Homebrew
HomebrewでGitをインストールする
MacにNode.jsをインストール

node.jsからBigQueryを使う

$
0
0

久しぶりに触ったらハマった(ハマる要素はありませんが)のでメモ。

前提

  • BigQuery側でデータセットおよびテーブルが作成されている。
  • 適切なサービスアカウントが作成され、認証用のjsonが(カレントに)ダウンロードされている。

準備

mkdir bq-test
cd bq-test
npm init -f
npm install--save @google-cloud/bigquery
touch index.js

実装

index.jsを実装します。下記のような感じ。

index.js
const{BigQuery}=require('@google-cloud/bigquery');constbigquery=newBigQuery({projectId:'your-project-id',keyFilename:'path/to/key.json'});constquery="select * from test.members";//simplebigquery.query(query).then(data=>{constrows=data[0];rows.forEach(row=>{console.log(JSON.stringify(row));})}).catch(error=>{console.log(error);});//with optionsconstoptions={query:query,useLegacySql:false,}bigquery.createQueryJob(options).then(results=>{const[job]=results;returnjob.getQueryResults();}).then(results=>{const[rows]=results;rows.forEach(row=>{console.log(JSON.stringify(row));})}).catch(error=>{console.log(error);})

実行

あくまで参考ですが(同じ結果が2回出ます)。

node index.js

{"id":1,"name":"hoge","age":22}{"id":2,"name":"foo","age":33}{"id":3,"name":"boo","age":44}{"id":1,"name":"hoge","age":22}{"id":2,"name":"foo","age":33}{"id":3,"name":"boo","age":44}

参考

Node+TypeScript+ExpressでAPIサーバ構築

$
0
0

はじめに

Node+TypeScript+ExpressでAPIサーバを作っていきます。
本記事はTypeScript環境の構築からAPIサーバの作成までの流れを記した初心者向けな記事になります。

本記事で紹介する技術は以下になります。

  • Node.js
  • TypeScript
  • ts-node
  • express
  • swagger

作業環境

OS: Windows 10 Pro
Node: 12.1.0
npm: 6.9.0 

TypeScript+Node環境の構築

Node環境でTypeScriptを始めるには

※手元のOSにNode.jsがインストールされていることを前提に始めます。

まずは適当に作業ディレクトリを作成して、その中にtypescriptパッケージをインストールしましょう。

PS C:\Users\user\Desktop> mkdir project; cd project;
PS C:\Users\user\Desktop\project> npm init -y 
PS C:\Users\user\Desktop\project> npm install -D typescript @types/node

作業するプロジェクト配下でtypescript@types/nodeをインストールしましょう。

@types/nodeは、Node.jsの型定義ファイルになります。
型定義ファイルというのは、TypeScriptのコンパイラに型を教えてくれるようなものです。
JavaScriptのライブラリは本来、JavaScript上で使用される事を想定しているので型定義が無いままに用意されていることが多いです。
そのため、TypeScript上で型チェックの恩恵を受けながら使用するために@typesから始まる型定義ファイルが別途用意されております。
※Angular等の初めから型定義ファイルが用意されているライブラリも存在しています。

無事にインストールが出来ましたら、試しにTypescriptのバージョンを確認してみましょう。

PS C:\Users\user\Desktop\project> ./node_modules/.bin/tsc -v
Version 3.7.2

上記のようにPathを打ち込むのが辛い場合はnpm-scriptsのタスク実行を活用しましょう。
npm init -yで作成したpackage.jsonscriptsに下記を追加することで、タスク実行が可能になります。

package.json
"scripts":{"tsc:ver":"tsc -v"//これ},
# npm-scriptsによるタスク実行
PS C:\Users\user\Desktop\project> npm run tsc:ver
Version 3.7.2

TypeScriptがインストールされている事を確認出来たので、実際にTypeScriptのファイルを作って動作検証をしてみます。

その前に、tsconfig.jsonを作成しときましょう。
tsconfig.jsonとは、TypeScriptで記述されたファイル(.ts)をJavaScriptファイル(.js)にコンパイルする時の設定を記述するファイルです。
TypeScriptのプロジェクトには設置しておく必要があります。
./node_modules/.bin/tsc --initを実行することで簡単に作成することが出来ます。

PS C:\Users\user\Desktop\project> ./node_modules/.bin/tsc --init
message TS6071: Successfully created a tsconfig.json file.

tsconfig.jsonの中身については適宜調べておくと良いかもですね。

続いて、TypeScriptファイルを作成してみます。

PS C:\Users\user\Desktop\project> vi index.ts
index.ts
consthello=(name:string):void=>console.log('Hello',name)hello('Tarou')

作成できたら、tsファイルをjsファイルにコンパイルした後実行してみましょう。
ここでは、npm-scriptsにコンパイル用のコマンドを定義しておきます。

package.json
"scripts":{"tsc":"tsc"},
# 実行するとindex.jsが生成されます。
PS C:\Users\user\Desktop\project> npm run tsc
# 生成されてindex.jsを実行します。
PS C:\Users\user\Desktop\project> node index.js
Hello Tarou

以上が、Node環境でTypeScriptを利用する一例になります。

ts-nodeを使う

TypeScriptで記述されたプログラムを実行するためには、tscコマンドでコンパイルした後にnodeで実行という手順を取りました。
正直、開発中にいちいちコンパイルして実行するのは面倒くさいです。
これらを一気に実行してくれるのが、ts-nodeというパッケージになります。
さっそくインストールしてみます。

PS C:\Users\user\Desktop\project> npm install -D ts-node

パッケージを追加したら、npm-scriptsに定義しておきましょう。

package.json
"scripts":{"tsc":"tsc","ts-node":"ts-node"},

では、先ほど作成したtsファイルをts-nodeで実行してみます。

PS C:\Users\user\Desktop\project> npm run ts-node index.ts

> project@1.0.0 ts-node C:\Users\user\Desktop\project
> ts-node "index.ts"

Hello Tarou

コンパイルを行わなずにindex.tsを実行することが出来ました。
ちなみに、ts-nodeは実行の度にtsファイルをコンパイルするので実行完了は毎回遅いです。
また、index.jsのようなコンパイルされたファイルは生成されません。
JSファイルを生成したい時はtscでビルドしましょう。

以上が、TypeScript+Node環境の構築の流れでした。

expressを使ってみよう

ここでは、一度は耳にしたことがありそうなNode.jsフレームワークexpressに触れます。
expressの事については公式やネットの記事等をご参照頂ければと思います。
expressを使用してAPIサーバを実際に作ってみます。

まずは、expressパッケージをインストールします。
型定義ファイルも一緒にインストールします。

PS C:\Users\user\Desktop\project> npm install express
PS C:\Users\user\Desktop\project> npm install -D @types/express

インストールしたら、index.tsにgetpostのapiサーバを実装していきます。
公式ガイドを参考にしました。

index.ts
importexpressfrom'express'constapp:express.Express=express()// CORSの許可app.use((req,res,next)=>{res.header("Access-Control-Allow-Origin","*")res.header("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept")next()})// body-parserに基づいた着信リクエストの解析app.use(express.json())app.use(express.urlencoded({extended:true}))// GetとPostのルーティングconstrouter:express.Router=express.Router()router.get('/api/getTest',(req:express.Request,res:express.Response)=>{res.send(req.query)})router.post('/api/postTest',(req:express.Request,res:express.Response)=>{res.send(req.body)})app.use(router)// 3000番ポートでAPIサーバ起動app.listen(3000,()=>{console.log('Example app listening on port 3000!')})

無事にGetとPostのAPIサーバが出来上がりました。

CORSの許可する記述については、ローカル環境以外からAPIを実行したい時に必要な記述になります。
ローカル以外の環境から上記のAPIを実行すると、セキュリティの観点からアクセスが許されません。
ローカル内での実行のみならCORS許可の記述は不要です。
CORSの詳細についてはこちらが参考になります。

express.json()express.urlencoded()についてはクライアントのデータを取得する際に必要な記述になります。
従来はbody-parserというパッケージをインストールして実装しておりましたが、Express標準に組み込まれたようです。

import bodyParser from 'body-parser'
const app = express()
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())

では、ts-nodeindex.tsを実行してAPIサーバを起動します。

PS C:\Users\user\Desktop\project> npm run ts-node index.ts

別のターミナルからCURLでリクエストしてみましょう。

$ curl localhost:3000/api/getTest?name=hoge
{"name":"hoge"}$ curl -X POST localhost:3000/api/postTest -d"name=hoge"{"name":"hoge"}

無事にexpressを用いてAPIサーバを構築することが出来たようです。

Swaggerを利用してみる

突然ですが、Swaggerをご存じでしょうか?
Swaggerとは、RestAPIを構築するためのオープンソースなフレームワークです。
RestAPIの設計をする時に利用したりするツールになります。

APIの仕様書を作成したい時等に凄く便利なので知らない人はぜひ覚えとくとよいかもしれません。

Swagger Editorを使ってOASを作成してみる

試しに、先ほどExpressで作成したAPIの仕様書をOAS形式で作成してみましょう。
OASというのはRestAPIの仕様定義です。詳細はOASをご参照ください。
SwaggerでOASを作成するツールとしてSwagger Editorがあります。
Web版が用意されているのでSwagger-Editorにアクセスして作成してみます。

openapi.yaml
openapi:3.0.0info:title:Sample APIversion:0.0.1servers:-url:http://localhost:3000/apidescription:Sample Hosttags:-name:Samplepaths:/getTest:get:tags:["Sample"]summary:Get test.description:Get test.operationId:getTestparameters:-in:queryname:nameschema:type:stringresponses:'200':description:Sample/postTest:post:tags:["Sample"]summary:Post test.description:Post test.operationId:postTestrequestBody:content:application/x-www-form-urlencoded:schema:type:objectproperties:name:type:stringresponses:'200':description:Sample

今回はOASv3.0の形式で作成しました。※OASv2.0とは文法が異なるのでご注意ください。
一からの作成が面倒くさい場合は、上記を丸々コピーして貼り付ければ動くと思います。

Swagger Editorの右側のほうでリクエストテストが行えるので動作確認してみましょう。
まずは、先ほど作成したExpressのAPIサーバを起動します。

PS C:\Users\user\Desktop\project> npm run ts-node index.ts
> project@1.0.0 ts-node C:\Users\user\Desktop\project
> ts-node "index.ts"
Example app listening on port 3000!

起動できましたら、Swagger Editorの画面にアクセスします。
3.png
上画像の「Try it out」を押下後、リクエストするパラメータを入力して「Excute」で実行してみましょう。

先ほど、CURLでのリクエストテストを行った時と同じような結果が返ってきたかと思います。
API仕様書の作成と同時にテストが行えるのは素晴らしいですね。

以上が、TypeScript環境の構築からAPIサーバの作成までの基本的な流れでした。
なにかの参考になれば幸いです。

第1回 - Web API と Web UI でファイルダウンロード(Web API 編)

$
0
0

はじめに

今回から2回に分けてファイルをダウンロードする簡単な Web API と Web UI のサンプルを紹介します。
第1回目の今回は Node.js + Express.jsでの Web API です。
第2回目は Vue.js + Vuetifyで簡単な Web UI を作り、今回の Web API を叩いて実際にファイルをダウンロードします。

  1. Web API 編(←イマココ)
  2. Web UI 編

環境

OS: macOS Mojave 10.14.6
Node.js: 10.16.3
Express.js: 4.17.1

API 仕様(概略)

  • GET
  • URI
    /api/download
  • クエリパラメータ
    file=ファイル名
    ファイル名は sanple.png or sample.csv が指定可能

ディレクトリ構成

root/
 ┣ api/
 ┃ ┗ download.js
 ┣ static/
 ┃ ┃ sample.csv
 ┃ ┗ sample.png
 ┗ app.js

download.js

download.js
'use strict';constexpress=require('express');constrouter=express.Router();const{check,validationResult}=require('express-validator');constpath=require('path');constfs=require('fs').promises;constContentType=[{ext:'csv',value:'text/csv'},{ext:'png',value:'image/png'},]router.get('/',validateParam(),async(req,res)=>{consterrors=validationResult(req);if(!errors.isEmpty()){res.status(400).end();return;}constfilepath=path.join('static',req.query.file);constfile=awaitfs.readFile(filepath).catch((error)=>{console.log(error);res.status(404).end();return;});constfilenames=req.query.file.split('.');constcontentTypes=getContentType(filenames[filenames.length-1]);constheaders={'Access-Control-Allow-Origin':req.headers.origin,'Access-Control-Allow-Headers':'Content-Type','Access-Control-Expose-Headers':'Content-Disposition','Access-Control-Allow-Methods':'GET','Content-Type':(contentTypes.length>0)?contentTypes[0].value:null,'Content-Disposition':'attachment; filename='+req.query.file};res.set(headers).send(file);});functionvalidateParam(){return[check('file').exists({checkNull:true})];}functiongetContentType(ext){returnContentType.filter((contentType)=>{if(ext===contentType.ext)returncontentType})}module.exports=router;

ファイル読込

download.js(抜粋)
constpath=require('path');constfs=require('fs').promises;~中略~constfilepath=path.join('static',req.query.file);constfile=awaitfs.readFile(filepath).catch((error)=>{console.log(error);res.status(404).end();return;});

解説

fs.readFile()で static 配下からファイルを読み込んでいます。
ファイルが無い場合は 404 を返却して終了しています。

ヘッダー設定

download.js(抜粋)
constContentType=[{ext:'csv',value:'text/csv'},{ext:'png',value:'image/png'},]~中略~constfilenames=req.query.file.split('.');constcontentTypes=getContentType(filenames[filenames.length-1]);constheaders={'Access-Control-Allow-Origin':req.headers.origin,'Access-Control-Allow-Headers':'Content-Type','Access-Control-Expose-Headers':'Content-Disposition','Access-Control-Allow-Methods':'GET','Content-Type':(contentTypes.length>0)?contentTypes[0].value:null,'Content-Disposition':'attachment; filename='+req.query.file};~中略~functiongetContentType(ext){returnContentType.filter((contentType)=>{if(ext===contentType.ext)returncontentType})}

解説

ヘッダーにファイルダウンロードをさせるのに必要な設定をしていきます。
まず、 Content-Dispositionattachment; filename=xxxの形式で指定します。
今回はファイル形式がわかっているので、 Content-Type も指定します(決め打ちでも良かったんですがそれだとイマイチなので、適当に関数を作りましたが、結果イマイチでしたね)。
また、Web UI からのファイルダウンロードを想定するので、 Access-Control-*を指定して CORS 対策をします。
あと、Web UI 側でダウンロードする際、 Content-Disposition からファイル名を取得したいので、'Access-Control-Expose-Headers': 'Content-Disposition'を指定します。

動作確認

download.gif
画質が荒いので(スミマセン)ちょっと見づらいですが、ブラウザからURLを叩いてファイルがダウンロードされているのがわかると思います。

まとめ

今回はファイルをダウンロードする Web API のサンプルを紹介しました。
ファイルの取得方法や、 CORS 対策は実用途によって色々とアレンジが必要になりますが、ファイルダウンロードの基本的な部分は変わりません。
また、 API の引数チェックに express-validator を使用していますが今回は説明を省いています。気になる方はコチラもどうぞ。
次回は Web UI から Web API を実行し、ファイルをダウンロードします。

今回、使用したコードはGitHubで公開しています。
https://github.com/ponko2bunbun/express-file-download-api-sample


Node.js の C++ によるアドオンで、AsyncWorker からイベントを受け取る

$
0
0

はじめに

最近、必要があって Node.js の Native アドオンを作りました。
その中で、外部とのデータやり取りで AsyncWorker を使っていたのですが、
値を受け取るときに Callback を使うサンプルはたくさんあるのですが、
イベントで返すサンプルが見つからなくてハマったので、ここに作り方をメモしておきます。

準備

現在、Node.js の Native アドオンの作成には色々方法がありますが、
今回のサンプルプログラムは、node-addon-api (N-API の C++ラッパー) を使っています。
(というか、他のやり方は「おまじない」だらけで、何が必要なのかがよくわからなかった・・・)

アドオンは、node-gyp を使ってビルドするのですが、環境作成方法などについては、先人の方々の優良記事がたくさんありますので割愛します。
(node-gyp タグで検索すると、たくさん見つかります。)

今回のサンプルプログラム作成に当たり、
https://qiita.com/Satachito/items/fa681ba96dc8e52ca7c1
が、非常に参考になりました。

  • この記事のプログラム一式は、以下のところにあります。
    https://github.com/dejirutek/async-emitter_sample

  • Node.js v12.13.0 で確認。

  • node-addon-api のバージョンは、1.7.1 で確認しています。それ以下のバージョンだと、恐らくコンパイルが通りません。

  • このサンプルプログラムは、Windows(7 / 10)でのみ動作確認しています。

まずは、呼び出し側プログラム (JavaScript) について

ソースファイル

addon.js
'use strict'// 利用するAPIconst{EventEmitter}=require('events');const{inherits}=require('util');// アドオン初期化const{AsyncEmitter}=require('bindings')('async_emitter');// EventEmitter クラスを継承させるinherits(AsyncEmitter,EventEmitter);// 引き数は、イベント発生インターバル(秒)constemitter=newAsyncEmitter(1);// 'data' イベントリスナーemitter.on('data',(data,len)=>{console.log('event data =',data,' len =',len);});letiLength=8;letiCount=5;// Workerパラメータ初期化emitter.AsyncInit(iLength,iCount);// キューに投入emitter.AsyncQueue();

サンプルプログラムの動作

「指定した時間間隔(1秒)で、指定したバイト数(8バイト)の乱数を、指定した回数(5回)だけ返す」
というものです。

実行例

>node addon.js
event data = <Buffer a4 6c 40 6f 4f 13 8b 08>  len = 8
event data = <Buffer a7 68 08 6f 9a 6a b3 99>  len = 8
event data = <Buffer aa 65 d0 6e e4 c1 dc 2a>  len = 8
event data = <Buffer ad 61 98 6d 2f 18 04 ba>  len = 8
event data = <Buffer b1 5d 60 6c 79 6e 2d 4b>  len = 8

解説

つぎに、アドオン本体プログラム (C++) について

ファイルリスト

ファイル名適用
binding.gypnode-gyp 定義ファイル(説明は省略)
addon.cc初期化の時に呼ばれる
async-emitter.hクラス定義、他
async-emitter.cc主に Node.js とのインターフェースを記述
local-worker.cc主に Native 処理を記述
  • ファイル全体は、前述のリンクを参照のこと。
addon.cc
#include <napi.h>
#include "async-emitter.h"
Napi::ObjectInit(Napi::Envenv,Napi::Objectexports){AsyncEmitter::Init(env,exports);returnexports;}NODE_API_MODULE(NODE_GYP_MODULE_NAME,Init)
  • 「アドオン初期化」時に、呼ばれるプログラムの実体
  • "exports" は、Node.js モジュールの "module.exports" に相当
async-emitter.h
#include <napi.h>
#include <vector>
classAsyncEmitter:publicNapi::ObjectWrap<AsyncEmitter>{public:staticNapi::ObjectInit(Napi::Envenv,Napi::Objectexports);AsyncEmitter(constNapi::CallbackInfo&info);~AsyncEmitter();protected:classLocalWorker:publicNapi::AsyncWorker{public:staticLocalWorker*worker;staticNapi::ObjectReferencerefThis;staticNapi::FunctionReferencerefEmit;LocalWorker(Napi::Envenv,intinterval):Napi::AsyncWorker(env),interval(interval){}~LocalWorker(){}voidInitPerm(intlength,intcount){this->length=length;this->count=count;}protected:voidExecute();voidOnOK();private:intinterval;intlength;intcount;std::vector<uint8_t>data;};private:staticNapi::FunctionReferenceconstructor;Napi::ValueAsyncInit(constNapi::CallbackInfo&info);Napi::ValueAsyncQueue(constNapi::CallbackInfo&info);};
  • ここでのポイントは「ObjectWrap のサブクラスとして AsyncWorker」を、置いたところ。
  • ObjectWrap については、
    https://github.com/nodejs/node-addon-api/blob/master/doc/object_wrap.md
    を、参照のこと。これは簡単に言うと「C++ のクラスを、Node.js のオブジェクト(クラス)に見せる」仕組み。これにより、「他のNode.js モジュール(今回だと、EventEmitter)から、継承」が出来るようになる。
  • AsyncWorker の使い方は、Examplesよりも、Testsの方が、参考になった。
async-emitter.cc
#include "async-emitter.h"
Napi::FunctionReferenceAsyncEmitter::constructor;Napi::ObjectAsyncEmitter::Init(Napi::Envenv,Napi::Objectexports){Napi::HandleScopescope(env);Napi::Functionfunc=DefineClass(env,"AsyncEmitter",{InstanceMethod("AsyncInit",&AsyncEmitter::AsyncInit),InstanceMethod("AsyncQueue",&AsyncEmitter::AsyncQueue)});constructor=Napi::Persistent(func);constructor.SuppressDestruct();exports.Set("AsyncEmitter",func);returnexports;}AsyncEmitter::AsyncEmitter(constNapi::CallbackInfo&info):Napi::ObjectWrap<AsyncEmitter>(info){Napi::Envenv=info.Env();Napi::Object_this=info.This().As<Napi::Object>();Napi::Function_emit=_this.Get("emit").As<Napi::Function>();intinterval=info[0].As<Napi::Number>().Int32Value();LocalWorker*worker=newLocalWorker(env,interval);worker->SuppressDestruct();worker->refThis=Napi::Persistent(_this);worker->refThis.SuppressDestruct();worker->refEmit=Napi::Persistent(_emit);worker->refEmit.SuppressDestruct();LocalWorker::worker=worker;}AsyncEmitter::~AsyncEmitter(){LocalWorker::worker=nullptr;}Napi::ValueAsyncEmitter::AsyncInit(constNapi::CallbackInfo&info){Napi::Envenv=info.Env();intlength=info[0].As<Napi::Number>().Int32Value();intcount=info[1].As<Napi::Number>().Int32Value();LocalWorker::worker->InitPerm(length,count);returninfo.Env().Undefined();}Napi::ValueAsyncEmitter::AsyncQueue(constNapi::CallbackInfo&info){LocalWorker::worker->Queue();returninfo.Env().Undefined();}
  • ここでのポイントは「各オブジェクトの Reference を取って、それを SuppressDestruct() して、消えないようにした」ところ。こうしないと、AsyncWorker の Execute() 処理が終わって戻ってきた(OnOK()の時点)ときに、オブジェクトが消えてしまう。
  • "refThis" と "refEmit" には、各々、"Node.js 上の this"(この場合、このアドオン自身を指す) と "emitter.emit()" (https://nodejs.org/api/events.html#events_emitter_emit_eventname_argsを参照) の、Reference が入っている。
local-worker.cc
#include "async-emitter.h"
#include <thread>
#include <chrono>
#include <ctime>        // time
#include <cstdlib>      // srand, rand
AsyncEmitter::LocalWorker*AsyncEmitter::LocalWorker::worker=nullptr;Napi::ObjectReferenceAsyncEmitter::LocalWorker::refThis;Napi::FunctionReferenceAsyncEmitter::LocalWorker::refEmit;voidAsyncEmitter::LocalWorker::Execute(){std::this_thread::sleep_for(std::chrono::seconds(interval));std::srand(time(NULL));data.clear();for(inti=0;i<length;i++){uint8_trdata=rand()%0x100;data.push_back(rdata);}}voidAsyncEmitter::LocalWorker::OnOK(){Napi::Functionemit=refEmit.Value();Napi::Object_this=refThis.Value();Napi::Envenv=Env();Napi::HandleScopescope(env);if(data.size()){emit.Call(_this,{Napi::String::New(env,"data"),Napi::Buffer<uint8_t>::Copy(env,data.data(),data.size()),Napi::Number::New(env,length)});}if(--count){worker->Queue();}}
  • Execute() は、Native の世界で「何でもやり放題」なので Sleep でも EventWait でも何でも使える。
    (この場所以外では、例えば Sleep すると「Node.js のイベントループ自体」が止まってしまうのでマズい。)
  • OnOK() は、Execute() が終了した時点で呼ばれる。Reference.Value() で、元の実体が取れる。
  • emit.Call() は、Node.js 上での "emitter.emit()" と同じ。

補足

  • このサンプルプログラムは、機能検証を目的としたもなので、例外処理は入っていませんのでご注意ください。
  • このアドオンは、「クラスのような形」をしていますが、例えば「インターバルの違う2つのオブジェクト」の同時生成、
constemitter1=newAsyncEmitter(1);constemitter2=newAsyncEmitter(2);

のようなことはできません。(やるとプログラムが落ちます。多分、メモリリークしてる。)
おそらく Reference が、static になっているからだと思いますが、static にしないと、コンパイルが通りません。(正直、この辺の理屈はまだよく理解していない・・・)

  • 本当は、EventEmitter / inherits も、アドオン内に取り込みたかった(だぶん、出来るはず)のですが、そこまで至っていません。
  • その他、Native アドオンについて、まだよく理解していないところがあるので、何か間違ったことを言っているかもしれません。

終わりに

以上です。Node.js の Native アドオンについての情報は、この程度の情報すらネットを検索しても(英語でも)殆どないのが現状です。(あっても、古い情報が多い)
この情報が、どなたかのお役に立てれば幸いです。

音オペレーションで革命的なUXを!

$
0
0

まえふり

もう年末が近いですね。
1999年の大みそかから2000年になった時に将来の不安よりドキドキ感が強かったもんですがもうあれから20年近く時間が過ぎてしまったんですねー。
AIやらマネージドサービスやらで、がしがしプラットフォーマーが世の中をひっぱってってくれているんですが、

  • 「[XXX ] [検索] (カチッ)」とかいうCMに出てくるやつ
  • (ラジオで)「番組へのお便りはなんちゃらアットマークにメールください」

こういうのいい加減めんどくさいUXなんでやめたら良いと思うんです。20年前のURL一字一句手打ちとどう違うのかと。
どんなに短くなっても復活の呪文を打つのが変わらないならおんなじかと。

ITリテラシー問題とするのであれば音声認識、言語処理がリテラシーが低い人にも使い易く人気ですが
スマートスピーカーのウェイクワードって恥ずかしい。日本人が消極的なんじゃなくて普通にヘンに見える。
言霊とか人形供養とかセンシティブに魂を扱って来た遺伝子がそう思わせてるんじゃないか。(因果推論)

QRもスマホだしてアプリさがしてうごかしてQRコードにかざしてURLクリックしてとスマートなようなそうじゃないような。

デバイス同士で会話してくれ!人間巻き込むな!

音でデータを送る

ちょっと前にChripというのを紹介している記事があって、

音通信ライブラリChirpを利用してTHETAを操る

面白かったので使ってみたが各種SDKとか組み込めるネタがバッチシあるのに内部でAPIトークン持ってるので
タダだけど提供側に見られちゃってる感があるのでもっと、きさくに使えるの無いかなーって探したら、あったQuiet.js。

Quiet.js Transmit and receive data in the browser at 44.1kHz

ただこれブラウザでjavascript呼び込む仕組みにべったり依存しているので汎用的には動かない。

よって魔改造開始

Google Extension 要は音でブラウザに入力できる
quiet.gif

node.js 要は音でキーボード操作できる
nodejs.gif

※quiet.jsのライセンスがよく分からないので同梱していません。同じフォルダにquiet.js、quiet-emscripten.js、quiet-emscripten.js.memを置いてあげれば動きます。

複数台PCでエージェント立ち上げてスピーカーで流せばオーケストラ・オーケストレーション(???)だな、これ!

あとこの技術が楽しいのは不可聴領域を使えるので、そこにノイズにならないような音であれば同時に流してもOK!つまり、

  • CMにまぜてしまえばスマホワンポチで特設サイトまで誘導
  • 電車内で流せば車内放送の聞き逃し、満員電車でトレインチャンネルが見れないが解消
  • 社歌にデータ混ぜて打刻とかすればいい。普通社歌って絶滅危惧種だからこのアイディア無し

こういう使い方もできるわけで相当可能性があると思われます。
というわけでバンバン使うといいよ!

あ、録音&輻輳されたら死亡です。

愚痴

スマホからキックしたくてcordovaに組み込むのトライしましたが、
もともとのJSコードをそれ作者にどうなのよ?というレベル改造しまくらないと動かないみたいなのでやめました。
今からKotlinや、React Native学習してよしんば動いたとしても1Qくらいかかっちゃう気がするのに、
スマホ自体にhttpd入れてループバックでブラウザから開くと余裕で動くのにアプリだと動かないとかブラウザの利便性異常だと思った。
同じことするだけなのにイチから勉強するモチベーションが。。
なんとか突破したかったけど参考に録音系のアプリ探してみても、ただ、外部のサイトをブラウザで開くだけのアプリばっか出てきてうんざりしたところで打ち止め。
※ブラウザで開くとThe AudioContext was not allowed to start. とか諸問題も回避できる。

Node.jsでperformance.now()を使用した際に"ReferenceError: performance is not defined"というエラーが出る

$
0
0

TL; DR

以下のいずれかの方法でperformanceオブジェクトを宣言する。

constperformance=require('perf_hooks').performance;

もしくは

const{performance}=require('perf_hooks');

詳細

vart0=performance.now();console.log('Do something');vart1=performance.now();console.log("Call to doSomething took "+(t1-t0)+" milliseconds.");

上記のようなコードをNode.jsで実行するとReferenceError: performance is not definedというエラーが出た。
Node.jsでPerformance APIを使用するにはPerformance Timing APIのドキュメントにあるようにpref_hookモジュールをインポートしてあげる必要があるみたいでした。

発展

Chromeでは、わざわざインポートしなくてもperformance.now()が使えるのにどうしてNode.jsではインポートする必要があるのか疑問に思いとさらに調べてみました。

どうやらNode.jsではPerformance APIはまだ試験的に実装されている段階なのでChromeとは違いglobalオブジェクト(Chromeの場合はwindow)にエクスポーズしていないみたいでした1
globalにエクスポーズするプルリクも既に出ている2ので試験的な段階が終了すればNode.jsでもモジュールをインポートせずに使えるようになりそうです。

Raspberrypiで動体検知カメラを自作

$
0
0

はじめに

自宅のセキュリティを強化したいと思ってRaspberryPiで監視カメラを自作。セキュリティサービスや高価な監視カメラを買う前にちょっと試してみたいという方向け。
具体的には、玄関前やリビング窓の不審者を検知しLINE通知させる仕組みとしている。将来的にはLINEBotにメッセージを送ると撮影させる事も考えたい。

準備するもの

・RaspberryPi Zero WH
Screenshot 2019-11-18 at 17.22.42.png
・カメラモジュール
Screenshot 2019-11-18 at 17.32.03.png
・zero用カメラケーブル
Screenshot 2019-11-18 at 17.24.43.png
※これらは事前に接続しておいてください

・RaspberryPiへRaspbianOSのインストール
・SSH接続ユーザの作成
LINE Notifyアクセストークン

今回はなるべく小型に作ることをコンセプトにしているため、RaspberryPi Zeroを選択した。このスターターキットは安定稼働に必要な5V3A対応電源と、フリスクサイズのケース、さらにケースにはカメラが取り付けられる穴が空いているので、初心者にも始めやすいと思う。
RaspberryPi3を利用する場合、カメラモジュールをそのまま使えるのでzero用ケーブルは不要。

ただひとつ失敗したことはzero WHはGPIOピンがデフォルトで付いていて、カメラ穴が空いている上蓋ケースを取り付けられなかった。GPIOピンは使わずカメラモジュールだけを使いたい場合はzero Wを購入することをおすすめする。
Screenshot 2019-11-18 at 17.28.08.png

ゴール

RaspberryPiで動体検知するとLINEに画像を送付する

設定

RaspberryPiがカメラモジュールを認識するように有効化

sudo raspi-config

動体検知にはmotionというモジュールを利用

sudo apt-get install motion

Daemonが起動できるように所有者を変更

chown motion:motion /var/lib/motion  

motion.confの設定。YOUR_PERSONAL_ACCESS_TOKENはLINE Notifyに合わせて設定

/var/etc/motion.conf
#解像度をLine通知に合わせてなるべく上げる
width 1024
height 768

# N秒以内のイベントを同一イベントとする設定
event_gap 5

# 動体検知したイベントの中で、一番変化が大きかった画像を保存する
output_pictures center

# 動画は保存しない
ffmpeg_output_movies off

# 動体検知箇所を赤枠表示
locate_motion_mode on

# 変化があったピクセル数を右上に表示
text_changes on

# 右下の時刻表示サイズを倍にする(デフォルトは小さくて見づらい)
text_double on

# 画像保存時に、LINE Notifyに送信する
on_picture_save curl https://notify-api.line.me/api/notify -X POST -H'Authorization: 
Bearer YOUR_PERSONAL_ACCESS_TOKEN'-F'imageFile=@%f'

デーモンによる自動起動設定

/etc/defaults/motion
start_motion_daemon=yes

サービス起動+自動起動設定

sudo service motion start
sudo update-rc.d motion defaults   # 自動起動設定

結果

玄関とリビングの前の足元に設置してみたところ。変化があった部分に白枠がついてLINEに通知してくれるようになった。
Screenshot 2019-11-18 at 18.19.15.png

とりあえずのお試しとしてはGood!だけど運用上の課題はたくさんありそう。ストレージ寿命、電源供給、自然環境(温度、風雨)対策など。それらの検討はまた別記事で。

参考

LINE Notifyを使って動体検知
https://engineering.linecorp.com/ja/blog/using-line-notify-to-send-stickers-and-upload-images/

motionのパラメータ設定
https://www7390uo.sakura.ne.jp/wordpress/archives/250

本当の初心者のためのNode.js超入門 ~環境構築編~

$
0
0

はじめに

 今回このシリーズを書くに至った経緯として、この頃未経験から転職してプログラマーになる方とか増えている印象が強いのですが、プログラミングを教える際に色々と余計なことを教えてしまったり、逆に併せて教えるべきことが抜けていたりといったことが多いなと感じる部分があったので、「完全素人向けに教えるにはどこを教えたらいいだろう?」という自身の疑問を解決するためのまとめとして書くことにしました。
 私自身も分かっていない部分多くあるので、一つ一つ再度勉強しながら書いていこうと思います。また、私同様に独学でプログラミングを始めた方の手助けになれればという気持ちですので、この気持ちに賛同していただける方はご指摘等頂けると幸いです。(もちろん初学者の方はご質問いただいても構いませんのでお気兼ねなく!)

 少々前置きが長くなってしまいましたが、今回はNode.jsについて基礎的な内容と環境構築方法について見ていこうと思います。
 今後Pythonなども同様の内容を書いていこうと思いますので、ご興味ある方はそちらもご覧頂ければと思います。

Webアプリケーションについて

 Node.jsを触ってみようと思った皆さんは、おそらくWebアプリケーションを作成しようと思ったから(あるいはしなければならなくなったから)というモチベーションからだと思います。
 そこで、ここではまずWebアプリケーションについてサラッとおさらいしておこうと思います(このあたりはよく分かっているという方は読み飛ばしてください)。

WebサイトとWebアプリケーションの違い

 もしかしたら読者の方の中には、Webサイトは作成したことがあるが、Webアプリケーションの作成は初めてという方がいらっしゃるかもしれません。ここではWebサイトとWebアプリケーションの違いについてザックリと説明していこうと思います。

Webサイトの主体は情報提供

 Webサイトは多くの場合、URLを入力したら作成したHTMLファイルが表示されるといった、静的ファイルのホスティングが行われるだけです。
 サイトを見に行ったユーザーは、そのページに表示されている情報を閲覧することを目的としています。

Webアプリケーションの主体は情報のやり取り

 一方でWebアプリケーションの場合は、ユーザーによるフォーム入力や、送信ボタンのクリックなど様々な行動を行います。そして、ユーザーが送信した情報に応じて処理された結果が返ってくるといった、対話型の流れがあるのが特徴といえるでしょう。

サーバーサイドの処理

 先に述べた通り、Webアプリケーションはユーザーとシステムの間での情報のやり取りがあるため、ユーザーから受け取った情報を処理するプログラムが必要となります。
 細かい説明は省きますが、ここで必要となるのがサーバーサイドのプログラムです。クライアントから情報を受け取ったサーバーは、必要な処理を行い、その結果だけをクライアントに返します。
 そしてここで使われるのが、Node.jsやPHP、Java、Pythonといったプログラミング言語というわけです(JavaScriptから入ったという方は、上記の言語とJavaScriptがどこで動作しているのか、その違いについても抑えておく必要があります)。

Node.jsとは?

 さて、いよいよ本題のNode.jsについて見ていこうと思います。

Node.jsは何ができるの?

 Node.jsは他の言語と同様にWebアプリケーション以外でも利用することができますが、Webアプリケーションでの利用が主な用途です。
 というのもこのNode.js、単体でWebアプリケーションを動作させることができます。PHPなどを利用したことがある方はご存知かと思いますが、通常はApacheやnginxなどのWebサーバーソフトウェアを利用します。しかしNode.jsでは、JavaScriptで書いたファイル一つでサーバーとしての機能を実装することができるのです。
 簡単にスピーディーにWebサーバーを構築できる点は大きなメリットですね。

JavaScriptのプログラムをそのまま動かすことが可能

 少し細かい話になりますが、Node.jsは「V8」というGoogle製のJavaScript実行エンジンを使って作られています。これは、JavaScriptのランタイムエンジンというもので、JavaScriptで書いたスクリプトを読み込んで実行することができます。

何が便利なの?

 先にも述べた通り、Node.jsは主にWebアプリケーション開発に利用されることの多い言語です。そしてそのWebアプリケーションでは、フロント開発としてJavaScriptによる処理を作成することが多いです。
 Node.js台頭以前は、「フロント側はJavaScript、サーバー側は別の言語を使う」というのが普通でしたが、Node.jsの登場によって、フロントもサーバーもJavaScriptで記述することができるようになりました。
 何となくイメージできると思いますが、これが非常に便利なわけです。

 つまりJavaScript一つ覚えれば、Webアプリケーションを構築できてしまうわけです(実際はもっと色々覚えた方がいいですが……)。

Node.jsのバージョン

 Node.jsのことがざっくり分かったところで、実際に動かすためにインストールをしようと思いますが、その前に少しNode.jsのバージョンによる違いを少し説明しておこうと思います。

偶数バージョンと奇数バージョン

 え?急に何の話? って感じですよね。ですがこれ、割と大事な話なので説明しておきます。

 まずは偶数バージョンですが、これはLTS(Long Term Support)と呼ばれるもので、長期サポートが保証されているバージョンです。つまり基本的には安定した動作が保証されるものということですね。

 一方で奇数バージョンですが、お察しの通りこちらは長期サポートは保証されていません。ver11を例にとると、リリースされた2018年4月から2019年6月までの1年程度しかサポートされません(ver10は2021年春まで)。
 ではこの奇数バージョンは何が違うのでしょうか?簡単に言うと、こちらは新しい技術に積極的に取り組んだり、性能向上が短い間で行われます。

 初学者の方は特に、安定的な運用が可能な偶数バージョンをインストールするようにしましょう。

Node.jsのインストール

さて、ここまで長くなってしまいましたが、いよいよNode.jsをインストールします。
Windows版、Mac版ありますが、基本的にはインストール方法は同じです。
今回は私の端末のOSはWindowsですので、Windowsでのセットアップを行います。

Node.jsのWebサイトからインストール用のファイルをダウンロードしましょう(2019.11.15アクセス)。

image.png

現時点での奇数バージョンは12.13.0ですね。
こちらをクリックしてインストーラを落としてください。

インストーラを実行したら順に進んでいき、ライセンス規約に同意のチェックボックスにチェックを入れて次へ進んでください。
image.png

インストール先は基本的にはデフォルトのままでOKですが、何か指定がある方はそのフォルダパスを指定してください。

image.png

あとはそのままインストールを実行して下さい。
※追加のツールをインストールするかみたいなことを聞かれますが、これをやるとPythonが2.7とかになるのでPythonの環境構築を行っている方は気を付けて下さい。
環境を崩したくない場合は追加のインストールは不要です。

動作確認

基本的にサーバーサイド言語をインストールしたら、動作確認としてバージョンの表示をやってみることが多いです。
正常にインストールされているか、何がインストールされているかを同時に確認出来て便利ですしね。

コマンドプロンプトを開いて、以下のコマンドを入力してみましょう。

Node.jsのバージョン確認
node-v

コマンド実行後、以下のようなメッセージが出たら無事にインストールできているということになります。

Node.jsのバージョン出力結果
v12.13.0

ちなみに、どの言語もほぼ共通ですが、

node

のように実行コマンドのみを入力すると、続けてNode.jsの挙動を確認することができますが、実際にそういったことをするのはほとんどないので、ここでは行いません。

それでは今回はこの辺にしておきましょう。
次回はJavaScriptのスクリプトを作成し、Node.jsで実行する流れを説明していこうと思います。

Viewing all 8952 articles
Browse latest View live