tag:crieit.net,2005:https://crieit.net/users/suzusime/feed すずしめの投稿 - Crieit Crieitでユーザーすずしめによる最近の投稿 2024-03-20T17:33:33+09:00 https://crieit.net/users/suzusime/feed tag:crieit.net,2005:PublicArticle/18797 2024-03-20T17:02:46+09:00 2024-03-20T17:33:33+09:00 https://crieit.net/posts/genryu-tt 源流明朝ほかを等幅で使う方法 <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan">ButTaiwan</a>氏が配布している源ノ角ゴシック・源ノ明朝派生フォントに、以下のようなものがあります。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genyo-font">源様明朝</a>: 源ノ明朝をそのままTrueType化したもの</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genryu-font">源流明朝</a>: 源ノ明朝に漢字の角立て処理などを施した上でTrueType化したもの</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genwan-font">源雲明朝</a>: 源流明朝に更に滲み処理を施したうえでTrueType化したもの</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genyog-font">源様ゴシック</a>: 源ノ角ゴシックをそのままTrueType化したもの</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genseki-font">源石ゴシック</a>: 源ノ角ゴシックに漢字の角立て処理などを施した上でTrueType化したもの</li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/gensen-font">源泉丸ゴシック</a>: 源ノ角ゴシックに丸ゴシック化の処理を施した上でTrueType化したもの</li> </ul> <p>これらは機械生成ながらかなり「いいかんじ」で、個人的に好きなフォント群です。</p> <p>ただ、これらのフォントは ver. 1.5 以降では日本語のかなや括弧の類がデフォルトでプロポーショナル(等幅ではなく、文字が詰められる)になるようになっています。用途によってはプロポーショナルでよいのですが、等幅で使いたい場合もあると思います(私はエディタの本文フォントとして等幅で使いたくなりました)。</p> <p>本記事では、等幅で使う方法を簡単に紹介します。</p> <h1 id="方法1:旧バージョンを使う"><a href="#%E6%96%B9%E6%B3%951%EF%BC%9A%E6%97%A7%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E3%81%86">方法1:旧バージョンを使う</a></h1> <p>上に「ver. 1.5 以降では」と書いたように、その前はデフォルトで等幅なフォントファイルとして配布されていました。従って、古いバージョンをダウンロードするのが最も簡単な解決策です。</p> <p>GitHub の Releases には ver 1.5 以降しかないので、過去のコミットのうち ver 1.5 より前のものを探して、そこから Download zip します。例えば、源流明朝であれば<a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genryu-font/tree/f35ab0de3a84210b2d6aff8127b0cee0203e6dbe">https://github.com/ButTaiwan/genryu-font/tree/f35ab0de3a84210b2d6aff8127b0cee0203e6dbe</a>です。</p> <h1 id="方法2:OpenTypeのフィーチャータグで指定する"><a href="#%E6%96%B9%E6%B3%952%EF%BC%9AOpenType%E3%81%AE%E3%83%95%E3%82%A3%E3%83%BC%E3%83%81%E3%83%A3%E3%83%BC%E3%82%BF%E3%82%B0%E3%81%A7%E6%8C%87%E5%AE%9A%E3%81%99%E3%82%8B">方法2:OpenTypeのフィーチャータグで指定する</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/ButTaiwan/genyo-font/issues/26">仮名、かぎ括弧は等幅がデフォルトの方が使いやすいです #26</a> というIssueで作者さんから案内されているのですが、OpenTypeの fwid, salt, ss01 のフィーチャータグを指定すると全角になるようです。なので、使いたいソフトごとのフォント設定で、このようなタグの指定を行えばよいです。</p> <p>例えば、CSSの場合は、以下で等幅になりました。</p> <pre><code class="css">body { font-family: "源流明朝"; font-feature-settings: "ss01" 1; } </code></pre> すずしめ tag:crieit.net,2005:PublicArticle/18696 2024-01-06T02:33:27+09:00 2024-01-06T02:34:32+09:00 https://crieit.net/posts/raku-cp932-stdout RakuでWindowsのターミナルに文字列を出力する方法 <h1 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h1> <p>Raku(旧Perl6)はWindowsでも使えるが、日本語Windowsのターミナルは普通文字コードがUTF-8ではなくCP932(Shift_JISの拡張)である。ソースコードをUTF-8で書いた場合、普通に<code>say</code>などをすると日本語などの非ASCII文字の出力は文字化けしてしまう。</p> <p>本稿はその回避策のメモである。</p> <h1 id="前提"><a href="#%E5%89%8D%E6%8F%90">前提</a></h1> <ul> <li>バージョンは Rakudo v2023.12</li> <li>ソースコードはUTF-8で書く。</li> </ul> <h1 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h1> <pre><code class="perl">$*OUT.encoding: 'windows-932'; my $name = "灯花"; say "こんにちは $name さん"; </code></pre> <p><code>$*OUT</code> が標準出力なので、<code>$*OUT.encoding: 'windows-932';</code>として標準出力の文字コードをCP932に変更すればよい。</p> <h1 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/routine/encoding">encoding | Raku Documentation</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/language/variables">Variables | Raku Documentation</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/B73W56H84/items/18053bf37de8bb2bb808">Perl6 特殊変数チートシート - Dynamic variables #Perl6 - Qiita</a></li> </ul> すずしめ tag:crieit.net,2005:PublicArticle/18317 2022-11-13T10:49:01+09:00 2022-11-13T10:56:11+09:00 https://crieit.net/posts/WSL2-Emacs-org-mode-Windows-Edge WSL2のEmacsでorg-modeのリンクをWindows側のEdgeで開く <p>WSL2に入れたEmacsでorg-modeを使う際、そのままだとorg-mode内のWebサイトへのリンクはWSL2内のlynx(テキストブラウザ)で開こうとする。これを、代わりにWindows側のブラウザで開くように設定したい。その方法を記録しておく。</p> <h1 id="方法"><a href="#%E6%96%B9%E6%B3%95">方法</a></h1> <h2 id="Windows側のEdgeにWSL側でパスを通す"><a href="#Windows%E5%81%B4%E3%81%AEEdge%E3%81%ABWSL%E5%81%B4%E3%81%A7%E3%83%91%E3%82%B9%E3%82%92%E9%80%9A%E3%81%99">Windows側のEdgeにWSL側でパスを通す</a></h2> <p>そもそもWSL側からWindows側のEdgeを起動できないと話にならない。もちろんフルパスを入れれば起動できるのだが、簡単にするためにまずはWSL側のパスが通っている場所にEdgeの実行ファイルへのシンボリックリンクをつくる。</p> <p><code>msedge.exe</code> は <code>C:\Program Files (x86)\Microsoft\Edge\Application</code> にあるので、以下のようにする。シンボリックリンクを作る場所は <code>/usr/local/bin</code> とする。</p> <pre><code class="sh">sudo ln -s /mnt/c/Program\ Files\ \(x86\)/Microsoft/Edge/Application/msedge.exe /usr/local/bin/msedge </code></pre> <p>これで、WSL側からWindows側のEdgeを簡単に呼べるようになる。</p> <pre><code class="sh">msedge "yahoo.co.jp" </code></pre> <p>なお、WSLには <code>wslview</code> というコマンドがあり、これにURLを渡すことでもWindows側のEdgeで開いてくれるのだが、これをEmacsのブラウザに設定した場合は何故か画面が崩れるという問題が発生した。</p> <h2 id="Emacsの設定"><a href="#Emacs%E3%81%AE%E8%A8%AD%E5%AE%9A">Emacsの設定</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://www.emacswiki.org/emacs/BrowseUrl">https://www.emacswiki.org/emacs/BrowseUrl</a> を参考に、init.elに設定を書く。org-modeのリンクについては普通のリンク向けのブラウザ設定だけではだめなようなので注意。</p> <pre><code>(setq browse-url-browser-function 'browse-url-generic browse-url-generic-program "msedge") (defadvice org-open-at-point (around org-open-at-point-choose-browser activate) (let ((browse-url-browser-function (cond ((equal (ad-get-arg 0) '(4)) 'browse-url-generic) ((equal (ad-get-arg 0) '(16)) 'choose-browser) (t (lambda (url &optional new) (browse-url-generic url t))) ))) </code></pre> <h1 id="おまけ:leaf.elの場合"><a href="#%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%9Aleaf.el%E3%81%AE%E5%A0%B4%E5%90%88">おまけ:leaf.elの場合</a></h1> <p>私は leaf.el を使っているので以下のようにした。<code>C-w</code>と<code>M-w</code>のときにWindows側のクリップボードにも渡す設定も書いている。</p> <pre><code>(leaf wsl-settings :custom ((browse-url-browser-function . 'browse-url-generic) (browse-url-generic-program . "msedge")) :config ;; org-modeで外部ブラウザでリンクを開くようにする設定 (defadvice org-open-at-point (around org-open-at-point-choose-browser activate) (let ((browse-url-browser-function (cond ((equal (ad-get-arg 0) '(4)) 'browse-url-generic) ((equal (ad-get-arg 0) '(16)) 'choose-browser) (t (lambda (url &optional new) (browse-url-generic url t))) ))) ad-do-it)) (leaf wsl-copy-setting :doc "コピーしたものをクリップボードに渡す設定" :init (defun wsl-copy (start end) (interactive "r") (shell-command-on-region start end "clip.exe") (kill-ring-save start end)) :bind (("M-w" . wsl-copy))) (leaf wsl-cut-setting :doc "切り取りしたものをクリップボードに渡す設定" :init (defun wsl-cut (start end) (interactive "r") (shell-command-on-region start end "clip.exe") (kill-region start end)) :bind (("C-w" . wsl-cut))) ) </code></pre> すずしめ tag:crieit.net,2005:PublicArticle/18284 2022-08-20T12:35:05+09:00 2023-03-23T13:45:21+09:00 https://crieit.net/posts/connect-go-with-cors connect-webを試そうとしたらCORSまわりでちょっとはまった話 <h1 id="背景"><a href="#%E8%83%8C%E6%99%AF">背景</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://future-architect.github.io/articles/20220819a/">gRPCがフロントエンド通信の第一の選択肢になる時代がやってきたかも? | フューチャー技術ブログ</a> を読んで、「HTTP/1.1で動いてcurlで投げたJSONも処理できるgRPC(もどき)って最強じゃん!」と思ったので <a target="_blank" rel="nofollow noopener" href="https://connect.build/docs/introduction">チュートリアル</a> を試してみた。</p> <p>connect-go のほうのチュートリアルは自分で書いたprotocol bufferからサーバーとクライアントを両方作る方法の解説だったのだけれど、connect-web のほうのチュートリアルは既存のWebサービスにつなぐためのクライアントの作り方の解説であったため、チュートリアルを参考にしつつ connect-go で作ったサーバーに接続する connect-web のクライアントを作ろうとした。</p> <h1 id="本文"><a href="#%E6%9C%AC%E6%96%87">本文</a></h1> <h2 id="やろうとしたこと"><a href="#%E3%82%84%E3%82%8D%E3%81%86%E3%81%A8%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">やろうとしたこと</a></h2> <p>まずは <a target="_blank" rel="nofollow noopener" href="https://connect.build/docs/web/getting-started">Getting started | Connect</a> のPrepareの節を行い、プロジェクトをつくる。</p> <p>次に、protocol bufferからTypeScriptのコードを生成。<a target="_blank" rel="nofollow noopener" href="https://connect.build/docs/web/generating-code">Generating code | Connect</a> にある通りの <code>buf.gen.yaml</code> を作った後、</p> <pre><code>ln -s ../connect-go-example/greet greet buf generate </code></pre> <p>で <code>gen</code> 以下にTypeScriptのコードを生成する。</p> <p>そして、App.tsx を以下のようにした。</p> <pre><code class="javascript">import { createConnectTransport, createPromiseClient, } from "@bufbuild/connect-web"; import React, { useState } from "react"; import { GreetService } from "../gen/greet/v1/greet_connectweb"; import './App.css' const transport = createConnectTransport({ baseUrl: "http://localhost:8080", }) const client = createPromiseClient(GreetService, transport) function App() { const [userName, setUserName] = useState(""); const [message, setMessage] = useState("ここにメッセージが入ります"); const sendName = async (e: React.FormEvent<HTMLElement>) => { e.preventDefault(); const res = await client.greet({ name: userName, }) setMessage(res.greeting) } return ( <div className="App"> <p>{message}</p> <form onSubmit={sendName}> <input value={userName} onChange={e => setUserName(e.target.value)} /> <button type="submit">送信</button> </form> </div> ) } export default App </code></pre> <p>これで <code>npm run dev</code> して表示されたブラウザで「送信」ボタンを押すといいかんじに名前が表示されるはず……だが、何も出ない。ブラウザの開発者コンソールを見ると、<strong>405 Method Not Allowed</strong> のエラーが返ってきている。</p> <p><a href="https://crieit.now.sh/upload_images/cd71f0d0d2c7cca896698c0a4bd3a31d630051c1b137f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/cd71f0d0d2c7cca896698c0a4bd3a31d630051c1b137f.png?mw=700" alt="image.png" /></a></p> <h2 id="原因"><a href="#%E5%8E%9F%E5%9B%A0">原因</a></h2> <p>このエラーの意味は「POSTしか受け付けないサーバーにOPTIONSのリクエストを送っている」というものである。が、OPTIONSなんて聞いたこともないようなHTTPリクエストを送っているつもりはない。それでしばらく調べたのだが、これはCORSプリフライトというやつらしい。「単純リクエスト」以外のリクエストを送りたい時は予めOPTIONSで宛先サーバーの様子を調べるのである。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS">オリジン間リソース共有 (CORS) - HTTP | MDN</a></li> </ul> <h2 id="対処"><a href="#%E5%AF%BE%E5%87%A6">対処</a></h2> <p>というわけで、このOPTIONSをちゃんと処理できるような処理をサーバー側に足せば良いということになる。Goの <code>net/http</code> の場合は <a target="_blank" rel="nofollow noopener" href="https://github.com/rs/cors">rs/cors: Go net/http configurable handler to handle CORS requests</a> という定番ライブラリがあるようなので、これを噛ませてみることにする。</p> <pre><code class="go">package main import ( "context" greetv1 "example/gen/greet/v1" "example/gen/greet/v1/greetv1connect" "fmt" "log" "net/http" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" "github.com/bufbuild/connect-go" "github.com/rs/cors" ) type GreetServer struct{} func (s *GreetServer) Greet( ctx context.Context, req *connect.Request[greetv1.GreetRequest], ) (*connect.Response[greetv1.GreetResponse], error) { log.Println("Request headers: ", req.Header()) res := connect.NewResponse(&greetv1.GreetResponse{ Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name), }) res.Header().Set("Greet-Version", "v1") return res, nil } func main() { greeter := &GreetServer{} mux := http.NewServeMux() path, handler := greetv1connect.NewGreetServiceHandler(greeter) mux.Handle(path, handler) corsHandler := cors.Default().Handler(h2c.NewHandler(mux, &http2.Server{})) // corsのハンドラを追加した // corsHandler := h2c.NewHandler(mux, &http2.Server{}) // もとの実装はこれ http.ListenAndServe( "localhost:8080", corsHandler, ) } </code></pre> <p>これでサーバーを立て直すと、無事リクエストが処理されるようになった。</p> <h1 id="感想"><a href="#%E6%84%9F%E6%83%B3">感想</a></h1> <p>Connect自体とは関係ない現代の基本的なWeb技術のところではまってしまった。そろそろちゃんとCORSを理解しないといけないと思った。</p> すずしめ tag:crieit.net,2005:PublicArticle/17789 2021-11-27T13:51:05+09:00 2021-12-01T17:30:57+09:00 https://crieit.net/posts/etude-grpc-tonic tonicを使ってgRPCサーバーを立ててみる <p>gRPCサーバー+grpc-webをバックエンドにしてWiki的なのをつくろうという気持ちがあるので、そのための実験をしています。今回はgRPCサーバーをRustでつくれるかの調査です。</p> <h1 id="ライブラリ選定"><a href="#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E9%81%B8%E5%AE%9A">ライブラリ選定</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://medium.com/nttlabs/rust-and-grpc-cb10a783c9da">Rustで、Hello Worldを卒業した?次は、gRPCだよね! | by FUJITA Tomonori | nttlabs | Medium</a> というRust教な記事があり、ここで <strong><a target="_blank" rel="nofollow noopener" href="https://github.com/hyperium/tonic">tonic</a></strong> が推されています。</p> <p>私は別にRust教信者ではないですが、Rustだけで完結していると環境構築が楽そうなので、とりあえずtonicを試してみることにしました。<a target="_blank" rel="nofollow noopener" href="https://lib.rs/search?q=grpc">‘grpc’ search // Lib.rs</a> でもtonicが一番人気っぽいですし、まあたぶんよくできているのでしょう。</p> <h1 id="Hello world"><a href="#Hello+world">Hello world</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md">tonic/helloworld-tutorial.md at master · hyperium/tonic</a>に従ってHelloWorldします。</p> <h2 id="環境準備"><a href="#%E7%92%B0%E5%A2%83%E6%BA%96%E5%82%99">環境準備</a></h2> <p>Rust 2021 Editionに依存しているのでRust 1.56以降が必要というのはなかなか面白いですね。</p> <p>エディタにはVSCode、補完にはrust-analyzerを使います。ここで注意点なのですが、READMEには</p> <blockquote> <p>If you're using rust-analyzer we recommend you set "rust-analyzer.cargo.loadOutDirsFromCheck": true to correctly load the generated code.</p> </blockquote> <p>とあるものの、この通りにやると "unknown configuration setting" とVSCodeに言われてしまいます。<a target="_blank" rel="nofollow noopener" href="https://github.com/rust-analyzer/rust-analyzer/issues/6448#issuecomment-961441435">rust-analyzerのIssue</a>によると、この <code>rust-analyzer.cargo.loadOutDirsFromCheck</code> 設定は <code>rust-analyzer.cargo.runBuildScripts</code> に改名された上に既定でオンになるようになったらしいです。つまり、特に何の設定もしなくてもよいということですね。</p> <h2 id="proto/sinanoki.proto"><a href="#proto%2Fsinanoki.proto">proto/sinanoki.proto</a></h2> <p>protocol bufferでAPI定義を書きます。なお、このsinanokiというのは単なる(私が作ろうとしているものの)プロジェクト名なので意味は無いです。</p> <pre><code>syntax = "proto3"; package sinanoki; service PageManager { rpc GetPage(GetPageRequest) returns (GetPageResponce); } message GetPageRequest { string path = 1; } message GetPageResponce { string markdown = 1; } </code></pre> <h2 id="cargo.toml"><a href="#cargo.toml">cargo.toml</a></h2> <pre><code class="toml">[package] name = "sinanoki-backend" version = "0.1.0" authors = ["suzusime <***@gmail.com>"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tonic = "0.6" prost = "0.9" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [build-dependencies] tonic-build = "0.6" </code></pre> <p>言われる通りにdependenciesの設定を追加します。今回はサーバーだけ(複数の実行ファイルはいらない)なので、binまわりの設定は飛ばしてserver.rsの代わりにmain.rsに書くことにします。</p> <h2 id="build.rs"><a href="#build.rs">build.rs</a></h2> <p>ビルドに対して特殊な処理が必要になるので、ルートフォルダにbuild.rsを置きます。</p> <pre><code class="rust">fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::compile_protos("./proto/sinanoki.proto")?; Ok(()) } </code></pre> <h2 id="src/main.rs"><a href="#src%2Fmain.rs">src/main.rs</a></h2> <p>今までの設定をして<code>cargo build</code>すると、protocol bufferに書いたものに対応したサーバー/クライアントの雛型となるRustのコードがtarget以下に生成されるようになります。これを利用してサーバーを実装します。</p> <p>例えば私の場合は <code>target/debug/build/sinanoki-backend-1664b6d9540c1c9f/out/sinanoki.rs</code> に雛型コードが吐かれました。この雛型コードの中に</p> <pre><code class="rust"> #[async_trait] pub trait PageManager: Send + Sync + 'static { async fn get_page( &self, request: tonic::Request<super::GetPageRequest>, ) -> Result<tonic::Response<super::GetPageResponce>, tonic::Status>; } </code></pre> <p>というのがあるので、これをコピペしたものを使い、以下のようなコードを<code>src/main.rs</code>に書きます。</p> <pre><code class="rust">use sinanoki::page_manager_server::{PageManager, PageManagerServer}; use sinanoki::{GetPageRequest, GetPageResponce}; use tonic::{transport::Server, Request, Response, Status}; pub mod sinanoki { tonic::include_proto!("sinanoki"); // The string specified here must match the proto package name } #[derive(Debug, Default)] pub struct MyPageManager {} #[tonic::async_trait] impl PageManager for MyPageManager { async fn get_page( &self, request: Request<GetPageRequest>, ) -> Result<Response<GetPageResponce>, Status> { println!("Got a request: {:?}", request); let reply = GetPageResponce { markdown: format!("you requested a file of {}", request.into_inner().path).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; let manager = MyPageManager::default(); Server::builder() .add_service(PageManagerServer::new(manager)) .serve(addr) .await?; Ok(()) } </code></pre> <h2 id="実行"><a href="#%E5%AE%9F%E8%A1%8C">実行</a></h2> <p>あとは</p> <pre><code>$ cargo run </code></pre> <p>して、</p> <pre><code>$ grpcurl -plaintext -import-path ./proto -proto ./proto/sinanoki.proto -d '{"path": "/index.md"}' localhost:50051 sinanoki.PageManager/GetPage { "markdown": "you requested a file of /index.md" } </code></pre> <p>で動作が確認できました。</p> <h1 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h1> <p>どうせ何かハマるだろうと思っていましたが、すんなり動きました。</p> <p>困ったところを挙げるならば、(tonicに限らず)Rustの補完があまり気持ちよく動いてくれないことでしょうか。単にバグっぽいところ(implしようとしたときに勝手に出してくれるコードが明らかにおかしい)を除いても適切な補完候補がなかなか出てくれなくて、少し厳しいです。おそらく私の設定が悪いんだろうなと思っているのですが……。</p> すずしめ tag:crieit.net,2005:PublicArticle/17761 2021-11-12T01:52:27+09:00 2021-11-12T02:06:23+09:00 https://crieit.net/posts/grpc-cs-dotnet6-on-mac C#(.Net 6)でgRPCサーバー on Mac <p>.Net 6 も出たし……と思い、C# で gRPC のサンプルをMacで動かそうとしたらエラーで落ちてちょっと戸惑ったので、手順を書いておきます。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio-code">ASP.NET Core で .NET Core gRPC のクライアントとサーバーを作成する | Microsoft Docs</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/grpc/test-tools?view=aspnetcore-6.0">ASP.NET Core で gRPCurl を使用して gRPC サービスをテストする | Microsoft Docs</a></li> </ul> <h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1> <h2 id="テンプレートからプロジェクトを作る"><a href="#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%8B%E3%82%89%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E4%BD%9C%E3%82%8B">テンプレートからプロジェクトを作る</a></h2> <p>gRPCのテンプレからプロジェクトを作ります。</p> <pre><code class="bash">$ dotnet new grpc -o GrpcGreeter </code></pre> <h2 id="実行(落ちる)"><a href="#%E5%AE%9F%E8%A1%8C%EF%BC%88%E8%90%BD%E3%81%A1%E3%82%8B%EF%BC%89">実行(落ちる)</a></h2> <p>普通に実行します。するとMacでは落ちます。</p> <pre><code class="bash">$ cd GrpcGreeter $ dotnet run ビルドしています... Unhandled exception. System.IO.IOException: Failed to bind to address https://localhost:7229. ---> System.AggregateException: One or more errors occurred. (HTTP/2 over TLS is not supported on macOS due to missing ALPN support.) (HTTP/2 over TLS is not supported on macOS due to missing ALPN support.) </code></pre> <h2 id="対処:TLSを使わない"><a href="#%E5%AF%BE%E5%87%A6%EF%BC%9ATLS%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%84">対処:TLSを使わない</a></h2> <p>落ちる理由はエラーメッセージにも <a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-6.0&tabs=visual-studio-code">チュートリアル</a> にも書いてあって、Macや古いWindowsではHTTP2 on TLSがサポートされていないからです。よってTLSを使わないようにすれば解決です。</p> <p>で、<a target="_blank" rel="nofollow noopener" href="https://docs.microsoft.com/ja-jp/aspnet/core/grpc/troubleshoot?view=aspnetcore-6.0#unable-to-start-aspnet-core-grpc-app-on-macos">.NET Core での gRPC のトラブルシューティング | Microsoft Docs</a>にはProgram.csに書き足せという旨が書かれているわけですが、これが正直よくわからない。たぶんテンプレートの形式が変わってしまったのだろうなと思われます。</p> <p>というわけで、次のようにして雑に解決しました。</p> <p>Properties/launchSettings.json には</p> <pre><code class="json">{ "profiles": { "GrpcGreeter": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "applicationUrl": "http://localhost:5142;https://localhost:7229", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } </code></pre> <p>とありますが、これの <code>applicationUrl</code> からhttpsのほうのURLを削り、</p> <pre><code class="json">{ "profiles": { "GrpcGreeter": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, "applicationUrl": "http://localhost:5142", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } </code></pre> <p>として保存します。</p> <h2 id="実行"><a href="#%E5%AE%9F%E8%A1%8C">実行</a></h2> <p>これで実行できます。</p> <pre><code class="bash">$ dotnet run ビルドしています... info: Microsoft.Hosting.Lifetime[14] Now listening on: http://localhost:5142 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down. info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development info: Microsoft.Hosting.Lifetime[0] Content root path: /Users/suzusime/sandbox/GrpcGreeter/ </code></pre> <h2 id="grpcurlでテスト"><a href="#grpcurl%E3%81%A7%E3%83%86%E3%82%B9%E3%83%88">grpcurlでテスト</a></h2> <p>これでサーバーが立つことがわかったので、grpcurlでAPIを叩いてみます。</p> <h3 id="reflectionを追加"><a href="#reflection%E3%82%92%E8%BF%BD%E5%8A%A0">reflectionを追加</a></h3> <p>grpcurlで叩くにはサーバーにreflection機能を追加する必要があるので、追加します(Goでやったときは不要だった気がするので本当は不要なのかも)。</p> <p>まずは</p> <pre><code class="bash">$ dotnet add package Grpc.AspNetCore.Server.Reflection --version 2.40.0 </code></pre> <p>でプロジェクトを追加します。</p> <p>次に、<code>Program.cs</code>を</p> <pre><code class="cs">using GrpcGreeter.Services; var builder = WebApplication.CreateBuilder(args); // Additional configuration is required to successfully run gRPC on macOS. // For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682 // Add services to the container. builder.Services.AddGrpc(); builder.Services.AddGrpcReflection(); // 追加 var app = builder.Build(); // Configure the HTTP request pipeline. app.MapGrpcService<GreeterService>(); app.MapGrpcReflectionService(); // 追加 app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"); app.Run(); </code></pre> <p>のように編集します。</p> <h3 id="grpcurlのインストール"><a href="#grpcurl%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">grpcurlのインストール</a></h3> <p>homebrewで入ります。</p> <pre><code class="bash">$ brew install grpcurl </code></pre> <h3 id="grpcurlで叩く"><a href="#grpcurl%E3%81%A7%E5%8F%A9%E3%81%8F">grpcurlで叩く</a></h3> <p>サーバーを立てます。</p> <pre><code class="bash">$ dotnet run </code></pre> <p>別のコンソールを開いてgrpcurlでサーバーを叩きましょう。まずは describe でAPIの使い方を調べてみます。</p> <pre><code class="bash"># 機能の一覧を出す $ grpcurl -plaintext localhost:5142 describe greet.Greeter is a service: service Greeter { rpc SayHello ( .greet.HelloRequest ) returns ( .greet.HelloReply ); } grpc.reflection.v1alpha.ServerReflection is a service: service ServerReflection { rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse ); } # 型の詳細を見る $ grpcurl -plaintext localhost:5142 describe greet.HelloRequest greet.HelloRequest is a message: message HelloRequest { string name = 1; } </code></pre> <p>これでAPIの叩き方がわかったので、リクエストを送ってみます。</p> <pre><code class="bash">$ grpcurl -plaintext -d '{ "name": "世界" }' localhost:5142 greet.Greeter/SayHello { "message": "Hello 世界" } </code></pre> <p>gRPCのサーバーが動いていることが確かめられました。</p> <h1 id="その他"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96">その他</a></h1> <ul> <li>protobufのパスは GrpcGreeter.csproj で指定できるようになっています。protobufを変更しても、dotnet runしたときにいいかんじにC#のファイルも生成してくれるようです。</li> <li>サーバーの実装は Services/GreeterService.cs にあります(見れば分かりますが)。Greeter.GreeterBaseという自動生成される基底クラスを継承してオーバーライドしていく方式みたいです。</li> </ul> <h1 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h1> <p>テンプレに従うだけで動かせるので、とりあえずgRPCを試してみたいという需要ならGoでやる以上に楽ですね。</p> <p>あと、Visual Studio for Mac 2022 Previewも入れてみたのですが、重くて正直微妙でした。VS Codeの出来が良すぎる……。</p> すずしめ tag:crieit.net,2005:PublicArticle/17362 2021-06-04T22:22:22+09:00 2021-06-04T22:30:00+09:00 https://crieit.net/posts/raku-grammar RakuのGrammarで四則演算を構文解析してみる <p>(自ブログの記事の転載です)</p> <h2 id="概要"><a href="#%E6%A6%82%E8%A6%81">概要</a></h2> <p>PerlやRubyのような言語には正規表現が言語機能として組み込まれています。正規表現はいろいろなところで活躍していますが、読みにくくなってしまいがちであったり、再起のある構文は解析できなかったりなどの弱点がありました。</p> <p>Raku(旧称Perl6)には、その点への対処として、Perl5とは異なる正規表現記法が導入されたほか、Grammarというさらに強力な構文解析機能が組み込まれています。これは、Parsing Expression Grammar (PEG)と呼ばれる文法に従う言語を解析できるものらしい<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>です。<a target="_blank" rel="nofollow noopener" href="https://raku.org/">Raku公式サイトのトップページ</a>のプログラム例で真っ先に挙げられているなど、Rakuの目玉機能の一つと思われるこのGrammarですが、日本語の解説を見ないので、公式チュートリアルを元に試しに使ってみた記事を書きます。</p> <h3 id="Rakuのインストール"><a href="#Raku%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">Rakuのインストール</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://raku.guide/ja/#_raku%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">1.3 Rakuのインストール - Raku 入門</a>を参考にインストールしてください。</p> <h3 id="参考文献"><a href="#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE">参考文献</a></h3> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/language/grammar_tutorial">Grammar tutorial</a>: 今回の元ネタたるGrammarのチュートリアル。</li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/language/grammars#sym">Grammars</a>: チュートリアルではない包括的なGrammarの解説。</li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/language/regexes">Regexes</a>: Rakuの正規表現の解説。Grammarのルールの記述には正規表現を用いるので、チュートリアルの例文によくわからないものが出てきたら適宜参照するとよさそうです。</li> <li><a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/Parsing_Expression_Grammar">Parsing Expression Grammar - Wikipedia</a></li> </ul> <h2 id="構文解析"><a href="#%E6%A7%8B%E6%96%87%E8%A7%A3%E6%9E%90">構文解析</a></h2> <h3 id="コード例"><a href="#%E3%82%B3%E3%83%BC%E3%83%89%E4%BE%8B">コード例</a></h3> <p>短いので、いきなり完成形を示してしまいましょう。<code>shisoku.raku</code>を次のように書きます。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m; </code></pre> <p>これを実行すると次のように表示されます。</p> <pre><code class="sh">$ raku shisoku.raku 「6*82 /4 + 43 -2*4」 多項式 => 「6*82 /4 + 43 -2*4」 単項式 => 「6*82 /4 」 数 => 「6」 乗算 => 「*」 単項式 => 「82 /4 」 数 => 「82 」 乗算 => 「/」 単項式 => 「4 」 数 => 「4 」 加算 => 「+」 多項式 => 「 43 -2*4」 単項式 => 「 43 」 数 => 「 43 」 加算 => 「-」 多項式 => 「2*4」 単項式 => 「2*4」 数 => 「2」 乗算 => 「*」 単項式 => 「4」 数 => 「4」 </code></pre> <p>いい感じに構文木ができていますね。日本語の鉤括弧が出てきていて、初めてみる方はなんだこれと思うかもしれませんが、Rakuは非ASCIIな記号をどんどん使っていくノリなので慣れてください。</p> <p>なお、この構文木の各要素は</p> <pre><code class="perl"># オブジェクトのメンバとして読める say $m.<多項式>.<単項式>.<単項式>.<数> # 「82 」 # .はなくてもいい say $m<多項式><単項式><単項式><数> # 「82 」 </code></pre> <p>のように、根から辿ることで参照することができます。</p> <h3 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h3> <h4 id="文法の定義"><a href="#%E6%96%87%E6%B3%95%E3%81%AE%E5%AE%9A%E7%BE%A9">文法の定義</a></h4> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m; </code></pre> <p><code>class</code>を使ってクラスを定義するように、<code>grammar</code>を使って文法を定義します。ここで<code>四則</code>という名前で定義しています。今回は日本語にしましたが、これは四則演算を英語で何と言うかが出てこなかったからです。別に英語(や他の言語)でも構いません。</p> <p>定義した文法に対して<code>parse</code>を呼ぶと、構文解析結果を返してくれます(この時の返り値<code>$m</code>をマッチオブジェクトといいます)。このマッチオブジェクトを<code>say</code>すると、先ほど示したようにいいかんじに表示してくれます。</p> <p>なお、もう少し厳密にオブジェクトの構造を見たい場合は</p> <pre><code class="perl">say $m.raku; </code></pre> <p>とします。<code>raku</code>メソッドは、全てのオブジェクトに生えている<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">2</a></sup>、そのオブジェクトのRaku的な表現を返す函数です。</p> <h4 id="トークンの定義(前半)"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%AE%9A%E7%BE%A9%EF%BC%88%E5%89%8D%E5%8D%8A%EF%BC%89">トークンの定義(前半)</a></h4> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m; </code></pre> <p>grammarの中には、文法の内容を<code>token</code>を用いて書いていきます。</p> <p>まず、「全体は〜である」という主張を<code>TOP</code>に書きます。今回の場合、全体は多項式です。多項式は別に定義することにして、とりあえず<code><多項式></code>とだけ書いています。このように<code><></code>で囲むことで、別に定義したトークンを参照することができます。</p> <p>次は、多項式。「多項式は単項式である」または「多項式は単項式と加算記号と多項式を順に並べたものである」という定義を素直に書いています。ここで若干注意すべきは、複雑なものを後に書いていることです。PEGはルールを前から順にあてはめていくので、両方当てはまりうる場合は注意する必要があります。まあ、なんとなくマッチしにくそうなものを前に持っていけばいいのではないでしょうか。<strong>なお、Rakuで正規表現を書くときの式では、空白が無視されます。</strong><code>{ <単項式> <加算> <多項式> | <単項式> }</code>と書いても、<code>{<単項式><加算><多項式>|<単項式>}</code>と書いても同じです。</p> <p>3つめは「加算」ですが、これは見ての通りです。こう書かずに</p> <pre><code class="perl"> token 多項式 { <単項式> [<和> | <差>] <多項式> | <単項式> } token 和 { '+' } token 差 { '-' } </code></pre> <p>のようにしてもよかったのですが、そうはしませんでした。これは、この後で行う演算がこの形式では不便そうだったからです。</p> <h4 id="トークンの定義(後半)"><a href="#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%81%AE%E5%AE%9A%E7%BE%A9%EF%BC%88%E5%BE%8C%E5%8D%8A%EF%BC%89">トークンの定義(後半)</a></h4> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m; </code></pre> <p>単項式と乗算に関しては多項式のところと同じです。</p> <p>少し説明の必要があるのが数の定義(整数の定義)のところです。<code>\d+</code>は正規表現で「1文字以上の連続した数字」なのですが、これだけだと数式にスペースを入れたときに認識されなくなってしまいます。そこで、スペースを入れても良いようにするために、「0文字以上の空白」を表す組み込みルールである<code><.ws></code>を前後に入れました。「0文字以上の空白」は<code><ws></code>でもいいのですが、このように前に<code>.</code>を入れ<code><.ws></code>とすることで、キャプチャされない(構文木に現れない)ようにすることができます(この挙動は<code>ws</code>以外でも同じです)。</p> <p>なお、<code><.ws></code>は「0文字以上の空白」であると書きましたが、実は置かれた場所が普通の文字の間である場合(スペースがないと単語区切りがわからない場合)は、「1文字以上の空白」を表します。例えば、<code>'a'<.ws>'*'</code>は<code>a*</code>にマッチしますが、<code>'a'<.ws>'b'</code>は<code>ab</code>にはマッチしません(<code>a b</code>にはマッチする)。詳しくは、<a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/language/grammars#ws">wsの解説</a>を読んでください。また、今回の記事では使いませんが、<code>token</code>の代わりに<code>rule</code>を使うと、正規表現の表記中の空白を自動で<code><.ws></code>に置き換えてくれます。</p> <h2 id="計算の実行"><a href="#%E8%A8%88%E7%AE%97%E3%81%AE%E5%AE%9F%E8%A1%8C">計算の実行</a></h2> <h3 id="action object"><a href="#action+object">action object</a></h3> <p>さて、以上で構文木をつくることができました。この木を何らかの函数で読み込んで処理してやれば計算ができるはずですが、それはたくさんの場合分けが必要になって少し面倒かもしれません。そこで、構文解析時に処理を行って値を返す機能(action object)を使ってみましょう。</p> <p>まずは、何もせずに適当な値を返す例からです。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } class 四則計算処理 { method TOP($/) { make 6 } method 多項式($/) { make 7 } } my $m = 四則.parse('4', actions => 四則計算処理.new); say $m.made[0]; # 6 say $m<多項式>.made[0]; # 7 </code></pre> <p>まず、<code>四則計算処理</code>というクラスを作っています。ここに、値を計算して返す処理を入れていきます。この例では<code>TOP</code>と<code>多項式</code>という名前でメソッドを定義していますが、これはそれぞれ同名のトークンが見つかった時に為される処理になります。返す値は、<code>return</code>ではなく<code>make</code>という特別な函数を使って返します。</p> <p>このクラスのインスタンスを作って<code>parse</code>函数の<code>actions</code>引数に渡してやると、この処理を実行してくれます。得た返り値は、<code>made</code>という配列に入ります。今回はただ整数の6と7を返しているだけなので、それがただ入っています。</p> <h3 id="数を返す"><a href="#%E6%95%B0%E3%82%92%E8%BF%94%E3%81%99">数を返す</a></h3> <p>今度は、マッチした値を使ってみます。まずは簡単な<code>数</code>からいきましょう。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } class 四則計算処理 { method 数($/) { make $/.Int } } my $m = 四則.parse('4', actions => 四則計算処理.new); say $m<多項式><単項式><数>.made[0]; # 4 </code></pre> <p>こうすると、マッチしたのと同じ<code>4</code>が返ってきます。<code>$/</code>でマッチオブジェクトが受け取れるので、それを<code>Int</code>で整数にして返しているわけです。整数以外で返したい場合は、<a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/type/Match">class Match</a>で適当な函数を探してみてください。</p> <h3 id="単項式を計算する"><a href="#%E5%8D%98%E9%A0%85%E5%BC%8F%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B">単項式を計算する</a></h3> <p>次は、<code>単項式</code>です。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } class 四則計算処理 { method 単項式($/) { given $/<乗算> { when Nil { # ただの数の場合 make $/<数>.made[0] } when '*' { # 掛け算の場合 make $/<数>.made[0] * $/<単項式>.made[0] } when '/' { # 割り算の場合 make $/<数>.made[0] / $/<単項式>.made[0] } } } method 数($/) { make $/.Int } } my $m = 四則.parse('5*6/7', actions => 四則計算処理.new); say $m<多項式><単項式>.made[0]; # 4.285714 say $m<多項式><単項式>.made[0].raku; # <30/7> </code></pre> <p>今度は場合分けが生じるので、少し複雑になっています。<code>given</code>は他の言語でいうswitchですから、<code>$/<乗算></code>の値を見て「ただの数の場合」「掛け算の場合」「割り算の場合」に分けています。あとは、子要素の返した値を<code>made</code>で受け取って、計算して返しているだけです。</p> <p>なお、ここで整数の割り算<code>/</code>をしていますが、Rakuの割り算<code>/</code>は、切り捨てるでも浮動小数点数を返すでもなく有理数を返すという変態仕様です。例でも、<code>4.285714</code>と表示されるものが内部では<code>30/7</code>で持たれていることがわかります。</p> <h3 id="多項式を計算する(完成)"><a href="#%E5%A4%9A%E9%A0%85%E5%BC%8F%E3%82%92%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%EF%BC%88%E5%AE%8C%E6%88%90%EF%BC%89">多項式を計算する(完成)</a></h3> <p>同様にして多項式も実装すれば、完成です。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> } token 多項式 { <単項式> <加算> <多項式> | <単項式> } token 加算 { '+' | '-' } token 単項式 { <数> <乗算> <単項式> | <数> } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws>} } class 四則計算処理 { method TOP($/) { make $/<多項式>.made[0] } method 多項式($/) { given $/<加算> { when Nil { make $/<単項式>.made[0] } when '+' { make $/<単項式>.made[0] + $/<多項式>.made[0] } when '-' { make $/<単項式>.made[0] - $/<多項式>.made[0] } } } method 単項式($/) { given $/<乗算> { when Nil { make $/<数>.made[0] } when '*' { make $/<数>.made[0] * $/<単項式>.made[0] } when '/' { make $/<数>.made[0] / $/<単項式>.made[0] } } } method 数($/) { make $/.Int } } my $m = 四則.parse('6*82 /4 + 43 -2*4', actions => 四則計算処理.new); say $m.made[0]; # 158 </code></pre> <p>確かに、<code>6*82 /4 + 43 -2*4</code>の計算結果である158が返ってきています。このように、<code>make</code>と<code>made</code>を使うことで構文木の親に値を渡していくことができ、自然に再起的な処理を書くことができます。</p> <p>以上の例では計算処理を別のクラスに書いて<code>actions</code>で渡していましたが、実は<code>grammar</code>の中に直接書いてしまうこともできます。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> { make $/<多項式>.made[0] } } token 多項式 { [ <単項式> <加算> <多項式> | <単項式> ] { given $/<加算> { when Nil { make $/<単項式>.made[0] } when '+' { make $/<単項式>.made[0] + $/<多項式>.made[0] } when '-' { make $/<単項式>.made[0] - $/<多項式>.made[0] } } } } token 加算 { '+' | '-' } token 単項式 { [ <数> <乗算> <単項式> | <数> ] { given $/<乗算> { when Nil { make $/<数>.made[0] } when '*' { make $/<数>.made[0] * $/<単項式>.made[0] } when '/' { make $/<数>.made[0] / $/<単項式>.made[0] } } } } token 乗算 { '*' | '/' } token 数 {<.ws> \d+ <.ws> { make $/.Int } } } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m.made[0]; # 158 </code></pre> <p>パターンの後に中括弧で囲った処理を書くとそれが実行されるというわけです。orの処理があるときはそれぞれに対して処理を書くようになっているらしく、もとの例と同様に書くならば、<code>[ <単項式> <加算> <多項式> | <単項式> ]</code>のようにパターンを括弧で括ってorが外に出ないようにする必要がありました。</p> <p>もちろん、orごとに処理が書けることを利用して次のように書くこともできます。</p> <pre><code class="perl">#!/usr/bin/env raku grammar 四則 { token TOP { <多項式> { make $/<多項式>.made[0] } } token 多項式 { <単項式> '+' <多項式> { make $/<単項式>.made[0] + $/<多項式>.made[0] } | <単項式> '-' <多項式> { make $/<単項式>.made[0] - $/<多項式>.made[0] } | <単項式> { make $/<単項式>.made[0] } } token 単項式 { <数> '*' <単項式> { make $/<数>.made[0] * $/<単項式>.made[0] } | <数> '/' <単項式> { make $/<数>.made[0] / $/<単項式>.made[0] } | <数> { make $/<数>.made[0] } } token 数 {<.ws> \d+ <.ws> { make $/.Int } } } my $m = 四則.parse('6*82 /4 + 43 -2*4'); say $m.made[0]; # 158 </code></pre> <p><code>加算</code>と<code>乗算</code>のトークンが不要になってすっきりしましたね。使い方などで、アクションオブジェクトを使うか否か適宜決めるとよさそうです。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>以上、RakuのGrammar機能で四則演算を構文解析し、計算を行う方法についてみてきました。「再帰的な正規表現」くらいの気持ちで気軽に使えるものだ、というのが印象です。PEGパーサーライブラリは多くの言語にありますが、標準機能に組み込んでいるのは、文字列処理に強い言語Perlを継ごうという意志を感じられて面白いなと思います。</p> <p>問題は、今あまり構文解析したい対象がないことですね……。一時期は構文解析のことばかり調べていたのですが、その熱意が失われてしまっています。XMLやJSON、YAML、TOMLといった有名どころのパーサは誰かが公開しているので、あまり自分で書く必要が出てこないというのが大きい理由です。Visual C++を触っていた高校生の頃に比べると外部ライブラリを使う手間は100分の1くらいになっているのですが、そのぶん自分で難しいことをこなす動機がなくなって、少し寂しいかなとも思うのです。</p> <h2 id="次に読むとよさそうな資料"><a href="#%E6%AC%A1%E3%81%AB%E8%AA%AD%E3%82%80%E3%81%A8%E3%82%88%E3%81%95%E3%81%9D%E3%81%86%E3%81%AA%E8%B3%87%E6%96%99">次に読むとよさそうな資料</a></h2> <p><a href="#参考文献">参考文献</a>に挙げたもののほかに、次のようなものが参考になりそうです。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://examples.perl6.org/categories/interpreters/calc.html">Simple Infix Arithmetic Calculator</a>: もう少ししっかり書かれた四則演算計算機のコードです。例外処理がされています。 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://examples.perl6.org/categories/interpreters.html">Language or DSL interpreters</a>には他にも逆ポーランド記法計算機、brainfuckインタプリタ、Lispインタプリタの例があります。</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/moritz/json">moritz/json: A tiny JSON parser and emitter for Perl 6 on Rakudo</a>: 簡潔に書かれたJSONパーサです。</li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>「らしい」と書いたのは、現在のRakuのドキュメントにはこのGrammarがPEGを解析できるといったことが記されている部分が見当たらないからです。Perl6の開発段階のドキュメントにはPEGを実装する旨が書かれているようです(<a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Raku_rules">Raku rules - Wikipedia</a>の出典を参照)。まあ、機能を見たらPEGが解析できるか否かわかる人にはわかるのでしょうが……。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:3" role="doc-endnote"> <p>Rakuでは全てのクラスが<a target="_blank" rel="nofollow noopener" href="https://docs.raku.org/type/Mu"><code>Mu</code>(無)クラス</a>を継承しています。<code>raku</code>メソッドは<code>Mu</code>クラスで定義されています。 <a href="#fnref:3" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> すずしめ tag:crieit.net,2005:PublicArticle/17135 2021-05-16T21:45:14+09:00 2021-05-16T22:32:39+09:00 https://crieit.net/posts/M1-Mac-Rust M1 Mac上のRustでクロスコンパイルする <p>(自ブログの転載です)</p> <p>最近RustでCGIを書くなんてことをしていたのですが、諸事情でサーバー側にRustのコンパイラを入れられず、別のマシンでビルドしたバイナリをコピーして動かしました。その時はLinuxの別マシンでビルドしたのですが、開発機であるM1 Macでビルドできた方が楽なので、その方法を調べました。</p> <h2 id="クロスコンパイラをインストールする"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">クロスコンパイラをインストールする</a></h2> <p>まずは別のCPUを対象としてコンパイルするためのコンパイラ(クロスコンパイラ)をインストールする必要があります。今回は <a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a> を使わせてもらいます。Homebrewでもインストールできるようですが、<a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains/releases/">Release</a> にあるビルド済みバイナリを用いるとお手軽です<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p> <p>Releaseのページにはたくさんのtar.gzが並んでいるので、目的にあったバイナリをダウンロードします。私の場合は「M1 Mac (aarch64-darwin)上で」「x86_64のLinuxで動く、CライブラリとしてMUSLを使ったバイナリ(x86_64-unknown-linux-musl)を作る」ため、<code>x86_64-unknown-linux-musl-aarch64-darwin.tar.gz</code>をダウンロードしました<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p> <p>そして、これを解凍し、中の<code>bin</code>ディレクトリにパスを通します。</p> <p>現時点(2021/05/16)での最新版(v10.3.0)を使う場合、以上のことをするコマンドは次の通りです。</p> <pre><code class="sh">$ mkdir -p $HOME/tools && cd $HOME/tools # 適当なディレクトリ $ wget https://github.com/messense/homebrew-macos-cross-toolchains/releases/download/v10.3.0/x86_64-unknown-linux-musl-aarch64-darwin.tar.gz $ tar xf x86_64-unknown-linux-musl-aarch64-darwin.tar.gz # 解凍 $ export PATH="$HOME/tools/x86_64-unknown-linux-musl/bin:$PATH" # パスを通す $ x86_64-unknown-linux-musl-gcc # パスが通っているか確かめる x86_64-unknown-linux-musl-gcc: fatal error: no input files compilation terminated. </code></pre> <p>なお、永続的に設定したい場合は<code>export</code>は<code>.zshrc</code>なりのシェル設定ファイルに書いて下さい(以下同じ)。</p> <h2 id="Rustでクロスコンパイルできるようにする"><a href="#Rust%E3%81%A7%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">Rustでクロスコンパイルできるようにする</a></h2> <p>まず、Rust側でターゲットを追加します。</p> <pre><code class="sh">$ rustup target add x86_64-unknown-linux-musl </code></pre> <p>これだけではRustがクロスコンパイラを見つけてくれないため、<a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a>のREADMEに従って次のように環境変数を設定します。</p> <pre><code class="sh">$ export CC_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-gcc $ export CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ $ export AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar $ export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc </code></pre> <h2 id="クロスコンパイルしてみる"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">クロスコンパイルしてみる</a></h2> <p>以上で設定ができたはずなので、Hello worldプロジェクトで確かめてみます。</p> <pre><code class="sh">$ cargo new etude-cross $ cd etude-cross $ cargo build --target x86_64-unknown-linux-musl --release # クロスコンパイル $ file target/x86_64-unknown-linux-musl/release/etude-cross # ファイル形式を確かめる target/x86_64-unknown-linux-musl/release/etude-cross: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped </code></pre> <p>確かにELF 64-bitのバイナリができあがりました。</p> <h2 id="参考文献"><a href="#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE">参考文献</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://omarkhawaja.com/cross-compiling-rust-from-macos-to-linux/">Cross-Compiling Rust from macOS To Linux</a> <ul> <li>この記事では <a target="_blank" rel="nofollow noopener" href="https://github.com/FiloSottile/homebrew-musl-cross">FiloSottile/homebrew-musl-cross</a> を使っていますが、これはM1 Macでは動かなかったので(<a target="_blank" rel="nofollow noopener" href="https://github.com/FiloSottile/homebrew-musl-cross/issues/23">Apple silicon support? · Issue #23</a>)、代わりに <a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a> を使いました。</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://rust-lang.github.io/rustup/cross-compilation.html">Cross-compilation - The rustup book</a></li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>変なバイナリである可能性を排除できないのでセキュリティ的にはよろしくないかもしれませんが。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:2" role="doc-endnote"> <p>例えばラズパイで動かすバイナリが欲しいなど、別のアーキテクチャ用のバイナリが欲しい場合は以下の文章の<code>x86_64-unknown-linux-musl</code>の部分を適宜読み替えてください。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> すずしめ