Rustで自作言語のインタプリタ作って Webで動くようにした話

Rustで自作言語のインタプリタ作って Webで動くようにした話

NEWDEBUGで話した内容です

8e8d56e1b27437990e5e1feff1b33777?s=128

garebare

August 01, 2020
Tweet

Transcript

  1. Rustで自作言語のインタプリタ作っ て Webで動くようにした話 NEW! DEBUG 2.0 ~ ちょっとしたトランスパイラを添えて ~ @garebare521

  2. やっぱり知名度が無いので自己紹介 ガレバレ Twitter@garebare521 作ったもの 自作言語 自作ブラウザ NES エミュレータの背景描画部分 PixelVtuber

  3. 近況 インターンニモ行ケズ エアコンガ壊レ 夏モ暑サニモ耐エ 時間ヲ持テ余シ

  4. そうだったのでUnityでゲーム作ってます

  5. きっかけ 自作ブラウザとかパーサーを作ったあたりからパースにハマり始めた そこから自作言語へ あとはノリ このスライドも間違ってる所あるかも

  6. 本題 説明すること 字句解析 構文解析 インタプリタ wasm

  7. 字句解析とは ソースコードをプログラム上の最小単位に分割すること let a = 1;

  8. 字句解析とは ソースコードをプログラム上の最小単位に分割すること “let” “a” “=” “1” ”;”

  9. 字句解析とは ソースコードをプログラム上の最小単位に分割すること “let” “a” “=” “1” ”;” -1 -2 -3

    -4 -5
  10. 実際の動作 let a = 1; l le let この時点でトークンを発行する 文字列や英数字の判断は正規表現

    その他の設定していないものは文字 コードを返す
  11. 構文解析 ほぼ自己流 ほぼ何も見てない 以上の理由でまぁまぁめちゃくちゃな実装

  12. 自作言語を作る本を買うべきだった

  13. 構文解析とは ざっくりいうと 字句解析で発行したものもとに抽象構文木を生成する こと

  14. 抽象構文木 まず木構造の根幹を定義し、要 素になる構造体の定義 配列には一つの型しか入らない ので列挙型も定義 #[derive(Debug, Clone)] pub enum Types

    { Number(NumberAST), Variable(VariableAST), } #[derive(Debug)] pub struct RootAST { pub node: Vec<Types>, } #[derive(Debug, Clone)] pub struct NumberAST { pub val: i64, pub node: Vec<Types>, } pub struct VariableAST{ pub name: String, pub node: Vec<Types>, }
  15. 抽象構文木 let a = 1; を字句解析したものから このような木を作ります 解析した要素をどんどん RootAST に放り込む

    これをいろんな構文や文字列な どに対応させる RootAST { node:[VariableAST (name:”a”, node:[NumberAST {val:1, node[] })] }
  16. インタプリター 構文解析以上に何も見ていない 完成してもこれがインタプリタかどうか悩んだ めちゃくちゃ中身を説明しにくい

  17. インタプリターとは 随時解釈しながら実行するプログラムのこと 種類も結構ある 今回作ったものは Rust で構文木をそのまま実行させるタイプ なので何にも変換せず、ターミナルに出力する際にも Rust の println!

    を使っている
  18. インタプリタで実装したこと ミュータブル、イミュータブルな変数 関数 四則演算 for 、 if 、構文 配列 標準入出力

    変数 他のファイルからのインポート etc…
  19. 変数の保存 変数だった場合は構文木をそのま ま配列に保存しちゃう その配列から使うもの取得するよう にする match { asts::Types::Variable(var) => {

    let var_contents = vec_variable.variable(var, vec_function); let mut var_ast = asts::VariableAST::new(&var.name); if !var.muttable { var_ast.muttable = false; } match var_contents { Some(content) => { var_ast.node.push(content); vec_variable.push(asts::Types::Variable(var_ast)); } None => {} } }
  20. 変数の中身を取り出す 総当りして中に入ってる構文木を返す 変数のスコープを考えて変数が入っている 配列をリバース 関数も同じ感じの処理をしている 総当りなので改善したい for vars in var_vec

    { for var in vars { let mut in_var = asts::VariableAST::new(""); match var { asts::Types::Variable(in_vars) => { in_var = in_vars; is_mutable = in_var.muttable; } _ => { let err = error::Error::new(&var); err.exit("This is not a variable"); } } . . . } }
  21. トランスパイラ C 言語を全く書かないくせに C 言語へのトランスパイラを作ろうとしたのでずさんもずさん 四則演算と if や for は実装

    今は C 言語をちょっと理解したのでまた気が向いたら作る なので割愛
  22. 全体的な改善点 構文解析が非効率な部分が多い インタプリタの四則演算が非効率 変数が総当り 既知のバグが多数 対応しきれていない書き方がある

  23. wasmに変換する

  24. wasmとは wasm(web assembly) クライアントサイドで動作する低水準なプログラミング言語 javascript より高速で他の言語から変換できる

  25. wasm-pack ビルドツールで色々うまくやってくれる curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh  でインストール Rustのプロジェクト内で wasm-pack build で色々生成してくれる

    これを使ってwasmを生成する
  26. package.json { "author": "You <you@example.com>", "name": "rust-webpack-template", "version": "0.1.0", "scripts":

    { "devbuild": "webpack", "devstart": "webpack-dev-server --open -d", "start": "node ./js/server.js", "test": "cargo test && wasm-pack test --headless" }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.1.0", "copy-webpack-plugin": "^5.0.3", "webpack": "^4.42.0", "webpack-cli": "^3.3.3", "webpack-dev-server": "^3.7.1", "rimraf": "^3.0.0" } }
  27. webpack.config.js const path = require("path"); const CopyPlugin = require("copy-webpack-plugin"); const

    WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); module.exports = { mode: "production", entry: { index: "./js/index.js" }, output: { path: path.resolve(__dirname, "./dist"), filename: "[name].js" }, devServer: { contentBase: path.resolve(__dirname, "./dist"), }, plugins: [ new CopyPlugin([ path.resolve(__dirname, "static") ]), new WasmPackPlugin({ crateDirectory: __dirname, }), ]
  28. lib.rs #[wasm_bindgen] を付けるとエ クスポートできる #[wasm_bindgen] pub fn run(string:&str) { output_result("Run......\n");

    let mut lexer = lexer::lexers::Lexer::new(string); let tokens = lexer.start(); let mut pars = ast::parsing::Parsing::new(&tokens); let result = parse.parsing(); interpreter::interpreters::run(result); output_result("\n......Done"); }
  29. lib.rs インポートは  #[wasm_bindgen(module=” ファイル名 ”)] #[wasm_bindgen(module = "/js/import.js")] extern "C"

    { pub fn output_result(input: &str); }
  30. index.js wasm は pkg に出力される コールバックから使える const wasm = import("../pkg/index.js");

    document.getElementById("run").onclick = function () { document.getElementById("result").textContent = ""; wasm.then(mod => { try { let code = document.getElementById("code").value; mod.run(code); } catch (error) { document.getElementById("result").textContent = "Error!" console.log(error); } }); }
  31. これでビルドしてhtmlから読み込めば ブラウザでインタプリタが実行できる

  32. server.js MINE Type に wasm を追加しない と実行することができない const express =

    require('express'); const path = require('path'); express.static.mime.define({'application/wasm': ['wasm']}); var app = express(); app.use('/', express.static(path.resolve(__dirname, "../dist"))); app.listen(process.env.PORT, function () { console.log('Example app listening on port' + process.env.PORT); })
  33. けれど今回はFire Baseでホスティングしたのでその あたりは上手いことやってくれました

  34. で実際できたのが

  35. https://koto-e2f64.web.app/

  36. やったー

  37. まぁしかし元が不安定なのでこれも不安定

  38. 詳細はリポジトリに https://github.com/garebareDA/koto

  39. マスコットキャラクターもいる

  40. 愛でやがれ!

  41. ご清聴ありがとうございました