[プログラミング言語の作り方] Javascriptでinterpretorを作る

hello worldを出力する言語を開発する。
言語仕様としては文字列とprint関数だ。
source.3というファイルを作成する。

print("hello world")

### インタプリタのファイル構成
– interpretor.js
– lexer.js
– parser.js
– utils.js

### interpretor.js

var {read, show} = require("./utils.js");
var lexer = require("./lexer.js");
var parser = require("./parser.js");

var source = read("source.3");

var tokens = lexer(source);

var token = lexer(source);
show("token =", tokens);

parser(tokens);

### lexer.js
split(/(“.*”|print)|\n/)で、文字列、print、改行で分割
filter(a=>a);で不要な物を捨てる

module.exports = function(source){
    var tokens = source.split(/(".*"|print)|\n/);

    tokens = tokens.filter(a=>a);

    return tokens;
}

### parser.js
callprintでは文法規則に沿って関数名(文字列)の順番通りになっているかを確認し、問題なければその場で文字列の部分を表示(実行)
substr(開始位置、文字数)なので、msg.substr(1, msg.legnth-2);として文字列を切り出す

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    return callprint();
}

function callprint(){
    if(tokens.length==0) return;

    expect(tokens, "print");

    expect(tokens, "(");

    var msg = tokens.shift();
    num = calc(msg.length);

    var msg2 = msg.substr(1, num);

    
    console.log(msg2);

    expect(tokens,")");
}

function calc(len) {
    var total = len - 2;
    return Number.isInteger(total) > 0 ? total : 0;
}

### utils.js
shift()は配列の最初の要素を取り除く

module.exports = {read, show, error, accept, expect}

function read(filename) {
    return require('fs').readFileSync(filename,"utf-8");
}

function show(msg, obj){
    obj = require('util').inspect(obj, {
        showHidden: false, depth: null, maxArrayLength: null, colors: true
    });
    console.log(msg + obj);
}

function error(...msgs){
    console.log(msgs.join(""));
    process.exit();
}

function accept(tokens, ...cs){
    if(tokens.length==0) return;

    if(cs.includes(tokens[0])) return tokens.shift();
    return;
}

function expect(tokens, ...cs){
    var t = accept(tokens, ...cs);

    if(t) return t;

    error("tokens[0]=",tokens[0],"が",cs,"に含まれていたため終了");
}

lexer.jsはsplit以外は殆ど何もしていないのね。parseの方で、msgの切り抜きをやっている。

jsのrequireの使い方

分割した機能ごとのjsファイルのことをモジュールと呼ぶ
importを使うのがESM方式(ECMAScript Module)で、requireを使うのがCJS(CommonJS Modules)

### import文の書き方
モジュール側

export const helloWorld = function() {
    console.log('Hello World!');
}

読み込み側

import { helloWorld } from './module'

helloWorld();

### require
モジュール側
L module.exportsと書く

module.exports = function() {
    console.log('hello world!');
}

読み込み側
 L require文を使って先ほどのモジュールを読み込む

const helloWorldModule = require('./module.js');

helloWorldModule();

$ sudo apt install nodejs
$ node main.js
hello world!

読み込まれる方でmodule.export()とするのが鍵ですね。

[プログラミング言語の作り方] 開発のアプローチ

1. どんなプログラミング言語を作りたいか が大事
2. 小さいプログラミング言語を作り育てていく
3. 開発OSはUbuntuを使用する
4. 開発のためのプログラミング言語はC, js, javaを使う
※コンパイラを作る場合は、アセンブラ言語を用いる必要があり、アセンブラ言語の知識が必要

どんな言語を作りたいかが骨格だが、他の言語の特徴を知っておくことが必要だろう
Python, C, C++, Java, C# などは昔から人気だ。
近年ではTypeScript, Go, Rustなども支持を集めている。

すぐに思いつくだけでもこれだけある
– インタプリタかコンパイル型か
– オブジェクト型かどうか
– 型指定があるかどうか

ブロックチェーン用のプログラミング言語の場合、
– 誰向けの言語か
– Dappsとしての機能を備えるか
– チューリング完全か

また、Solidityなど、他のブロックチェーンが採用している言語は最低限調べる必要がある
インタプリタ型かコンパイル型かについては、両方の開発方法を知っておく必要がある

奥を知ろうと思うと結構時間がかかりそうだ

[プログラミング言語の作り方] 字句解析、構文解析、インタープリタ、コンパイラ

プログラミング言語を作るには、ルールに合致した機械的な文字列だけを扱う割り切ったプログラムを作ること
インタプリタとコンパイラの共通処理として、字句解析と構文解析がある
※インタプリタは1行ずつ変換すると同時に、その都度命令を処理実行する
※コンパイラは全ての命令をまとめて一括で変換して、一気に処理実行する

### 字句解析(lexical analysis)とは
単語を意味のあるトークンに区切る

num=13-5*2;
print(num);

こちらをtoken配列に入れる文字列処理する

tokens = [
	"num", "=", "13", "-", "5", "*", "2", ";",
	"print", "(", "num", "num", ";"
];

文字列を分割するsplit機能や正規表現は最近の言語では備わっている
C言語の場合は、一文字ずつアルファベットか数字か記号かをチェックしていく
字句解析は、簡単な文字列分割機能だけで十分

### 構文解析(parsing)
処理を途中で止める手段が必要だが、例えば引き算を解析する関数を呼ぶ
tree構造のデータにすると分かりやすくなる。抽象構文木(AST: abstract syntax tree)
tree構造にすると、leftとrightに分けて、=, *, -, () などはopと呼ぶのが面白い。bitcoinのOPやハッシュ値のparseとプログラミング言語のparsingにかなり共通点があることがわかる。

{
	left: {
		left: 'num',
		op: '=',
		right: {
			left: '13',
			op:'-',
			right: {
				left: '5',
				op: '*',
				right: '2' 
			}
		}
	},
	op: ';'
	right: {
		left: 'print',
		op: '()',
		right: 'num'
	}
}

parsingとは、token配列をASTに変換する

### インタプリタ
字句解析、構文解析の後、ASTをそのまま実行する

### コンパイラ
字句解析、構文解析の後、ASTを元に、アセンブラを出力する
アセンブラのテキストファイルはasコマンドでバイナリに変換する
その後、リンカと呼ばれるldコマンドで、複数のオブジェクトファイルを連結して1つの実行ファイルにする
実行ファイルを実行すると、execシステムコールが呼ばれ、OSはバイナリ中で指定されたローダーと呼ばれる外部プログラムを起動する。
ローダーは実行ファイルや動的ライブラリなどをメモリ上に配置して、指定されたエントリポイントからCPUに制御を渡す

[LightningNetwork] pathfinding

支払い人から受取人へのパスを見つけることをpathfindingと呼ぶ
PathfindingはBOLTでは定義されておらず、さまざまなアルゴリズムを選択できる
平均チャネル数は8.8で上位のノードは2000のチャネル数もある
pathfindingはグラフトラバーサルに分類される
MCFPと呼ばれるコストを最小にしながら特定のフローを実現

パス候補のリストを作成し、何らかの関数でフィルタリングしてソートする。続いて、送信できるパスが見つかるまで、パスのプロービングを行う
チャネルグラフの構築で、node_announcementでIP情報が含まれているため、地図を作成できる
マルチパートペイメントでpathを柔軟化できる

[LightningNetwork] オニオンルーティング

オニオンルーティングはTorで使われている暗号化通信の手法
メッセージの送信者が入れ子になった暗号化の層を作成
origin nodeとfinal nodeがある

共通シークレット、セッションの秘密鍵、公開鍵からECDHアルゴリズムにより、rho, mu, um, padの鍵を生成する
オニオンをラッピングする

### ゴシッププロトコル(共有)
LNのイニシャルピアディスカバリはDNSに基づく
チャネルを、チェーン内の位置に基づいて参照する

[LightningNetwork] ルーティング

パス上の中間ノードをルーティングノードという
ライトニングネットワークはHash Time-Locked Contractを採用している
経路探索はチャネルグラフを調べる
ルーティングは経路探索によって事前に選択されたパスを使って、AからBにペイメントを転送しようとするネットワーク経由の一連のやり取り

エスクロー契約にして、ペイメントハッシュでやり取りを行う
契約には期限を設定する
ノードが預託を行うに十分な資金を持っている必要がある
PTLC(Point Time-Locked Contract)はSchnorr signature
インボイスをAliceからDinaにリクエストし、発行する

### HTLC(Hash Time-Lock Contract)
インボイスに暗号学的ハッシュ関数が入っている
H = Sha256(R)
OP_HASH160 OP_EQUALVERIFY
ペイメントプリイメージとの比較
受取人とマッチする公開鍵とその署名をスクリプトに入れる
OP_SHA256 OP_EQUALVERIFY OP_ChECKSIG

[LightningNetwork] ペイメントの送信

### ペイメントの送信
AliceとBobでコミットメントトランザクションを作成する
ブロードキャストするとファンディングアウトプットが消費される

### ライトニングプロトコルの失効とペナルティのメカニズム
– 非対称コミットメントトランザクション
L self, remotoとする
L 資金を取り戻す時間が与えられる
– 遅延支払い
– 失効鍵
L ペナルティトランザクションを作成することでチャネルの資金を独り占めできる

### チャネルの状態遷移
commitment_signedとrevoke_and_ackの2つのメッセージを交換する

commitment_signed: channel_id, signature, num_htlcs, htlc_signature
revoke_and_ack: channel_id, per_commitment_secret, next_per_commitment_point

[LightningNetwork] payment channel

2of2マルチシグのトランザクションを保管していて、そのトランザクションを独占的に使う手段となり得たら、そのビットコインを事実上所有していることになる。
ペイメントチャネルは2人のチャネルパートナーが2of2マルチシグアドレスにファンディングを行うことによって確立される
ライトニングノードはノードの公開鍵によって識別
ネットワークアドレスはTCP/IPもしくはTCP/Tor

識別子は ノードID@address:port
2つのノード間の通信をLightning Peer Protocol for Channel Managementと呼ぶ
チャネルはメッセージの交換で確立される
open_channel, accept_channel, funding_created, funding_signed, channel_ready, channel_readyの6つの時系列
トランザクションが十分な数の承認を得た時点で、channel_readyメッセージを交換し、チャネルは通常の実行モードを開始

### マルチシグアドレス
2 2 CHCKMULTISIG
=> p2wshのアドレスとしてエンコードされる
トランザクションを作成し、署名するがブロードキャストしない
払い戻しトランザクションを作成する…ファンディングトランザクションのインプットを計算する

### funding_created
funding_txid
funding_output_index

### funding_signed
channel idはfunding transaction idとoutput indexのビットごとのxor

### funding_transactionをブロードキャスト
ブロックチェーンでの承認数がminimum_depthになるのを待つ。その後channel_readyメッセージで相手に送信

[bitcoin] IBDが終わるとどうなるか

$ bitcoin-core.daemon -testnet -prune=1000
// 省略
2024-01-08T08:05:09Z UpdateTip: new best=000000000000000d549bd14334c241abfeb36cc5b6d04541af6061c0a21cdc92 height=2571591 version=0x20800000 log2_work=75.635907 tx=68073107 date=’2024-01-08T07:53:55Z’ progress=0.999999 cache=10.3MiB(35178txo)
2024-01-08T08:08:24Z Disconnecting outbound peer 15 for old chain, best known block =
2024-01-08T08:08:24Z New outbound peer connected: version: 70016, blocks=2571591, peer=17 (outbound-full-relay)

$ bitcoin-core.cli -getinfo -testnet
Chain: test
Blocks: 2571591
Headers: 2571591
Verification progress: 99.9999%
Difficulty: 38940120.97505151

Network: in 0, out 10, total 10
Version: 250100
Time offset (s): -2
Proxies: n/a
Min tx relay fee rate (BTC/kvB): 0.00001000

Warnings: Unknown new rules activated (versionbit 28)

progressが99.9999%で待受状態となる
outbound-full-relay隣nodeが待機しているのね。
なるほど〜