tag:crieit.net,2005:https://crieit.net/tags/Racc/feed 「Racc」の記事 - Crieit Crieitでタグ「Racc」に投稿された最近の記事 2021-11-03T08:04:56+09:00 https://crieit.net/tags/Racc/feed tag:crieit.net,2005:PublicArticle/17735 2021-11-03T08:04:56+09:00 2021-11-03T08:04:56+09:00 https://crieit.net/posts/simple-compiler-parser-ruby-racc Raccでかんたんな自作言語のパーサを書いた <p><a href="https://crieit.now.sh/upload_images/856c469c026e97152123f812f323d67e6181c1b9a3869.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/856c469c026e97152123f812f323d67e6181c1b9a3869.png?mw=700" alt="image" /></a></p> <p><自作言語処理系の説明用テンプレ></p> <p>自分がコンパイラ実装に入門するために作った素朴なトイ言語とその処理系です。簡単に概要を書くと下記のような感じ。</p> <ul> <li>小規模: コンパイラ部分は 1,000 行程度</li> <li>pure Ruby / 標準ライブラリ以外への依存なし</li> <li>独自VM向けにコンパイルする</li> <li>ライフゲームのために必要な機能だけ <ul> <li>変数宣言、代入、反復、条件分岐、関数呼び出し</li> <li>演算子: <code>+</code>, <code>*</code>, <code>==</code>, <code>!=</code> のみ(優先順位なし)</li> <li>型なし(値は整数のみ)</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">Ruby 以外の言語への移植</a>(コンパイラ部分のみ)</li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/1e683276541cf1b87b76">セルフホスト版</a>(別リポジトリ)</li> </ul> <p>下記も参照してください。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f9cb3fc4a496b354b729">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/2b95378b43a22109513c">Rubyでかんたんな自作言語のコンパイラを作った</a></li> </ul> <p><説明用テンプレおわり></p> <hr /> <p>もともとパーサ部分は手書きの再帰下降パーサでしたが、Racc 版を作ってみました。</p> <p>Racc で四則演算のパーサを作る方法は分かったがもう少しプログラム言語らしきものを扱っているサンプルを見たいとか、パースしたそばから実行する方式(インタプリタ方式)ではなく構文木が欲しいんだけど、という感じの人には参考になるかもしれません(昔の自分に送ってあげたい)。</p> <h1 id="できたもの"><a href="#%E3%81%A7%E3%81%8D%E3%81%9F%E3%82%82%E3%81%AE">できたもの</a></h1> <p><code>vgparser_racc.y</code> を追加したブランチです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/alt_parser_racc">github.com/sonota88/vm2gol-v2/tree/alt_parser_racc</a></p> <p>300行弱なので全部貼ってもいいのですが、雰囲気程度ということで途中を省略したものを貼ります。全体は GitHub の方で見てください。</p> <pre><code class="ruby">class Parser prechigh left "+" "*" preclow rule program: top_stmts { top_stmts = val[0] result = ["top_stmts", *top_stmts] } top_stmts: top_stmt { top_stmt = val[0] result = [top_stmt] } | top_stmts top_stmt { top_stmts, top_stmt = val result = [*top_stmts, top_stmt] } top_stmt: func_def func_def: "func" IDENT "(" args ")" "{" stmts "}" { _, fn_name, _, args, _, _, stmts, _, = val result = ["func", fn_name, args, stmts] } args: # nothing { result = [] } | arg { arg = val[0] result = [arg] } | args "," arg { args, _, arg = val result = [*args, arg] } arg: IDENT | INT stmts: # nothing { result = [] } | stmt { stmt = val[0] result = [stmt] } | stmts stmt { stmts, stmt = val result = [*stmts, stmt] } stmt: stmt_var | stmt_set | stmt_return | stmt_call | stmt_call_set | stmt_while | stmt_case | stmt_vm_comment | stmt_debug stmt_var: "var" IDENT ";" { _, ident, _ = val result = ["var", ident] } | "var" IDENT "=" expr ";" { _, ident, _, expr = val result = ["var", ident, expr] } # ... 途中省略 ... ---- header require "json" require_relative "common" ---- inner def next_token @tokens.shift end def to_token(line) token = Token.from_line(line) return nil if token.nil? if token.kind == :int Token.new(token.kind, token.value.to_i) else token end end def read_tokens(src) tokens = [] src.each_line do |line| token = to_token(line) next if token.nil? tokens << token end tokens end def to_racc_token(token) kind = case token.kind when :ident then :IDENT when :int then :INT when :str then :STR else token.value end [kind, token.value] end def parse(src) tokens = read_tokens(src) @tokens = tokens.map { |token| to_racc_token(token) } @tokens << [false, false] do_parse() end ---- footer if $0 == __FILE__ ast = Parser.new.parse(ARGF.read) puts JSON.pretty_generate(ast) end </code></pre> <h1 id="実行の例"><a href="#%E5%AE%9F%E8%A1%8C%E3%81%AE%E4%BE%8B">実行の例</a></h1> <p>入力とするプログラムの例です。</p> <pre><code class="javascript">// sample.vg.txt func main() { return 1 + (2 * 3); } </code></pre> <p>次のように実行するとASTに変換できます。</p> <pre><code class="sh">## vgparser_racc.rb を生成 $ bundle exec racc -t -o vgparser_racc.rb vgparser_racc.y $ ruby vglexer.rb sample.vg.txt > tmp/tokens.txt $ ruby vgparser_racc.rb tmp/tokens.txt [ "top_stmts", [ "func", "main", [ ], [ [ "return", [ "+", 1, [ "*", 2, 3 ] ] ] ] ] ] </code></pre> <h1 id="スタックの動きを見てみる"><a href="#%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E5%8B%95%E3%81%8D%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B">スタックの動きを見てみる</a></h1> <p>せっかく Racc を使っているので、適当なサンプルコード(下記)をパースさせてスタックの動きを図にしてみました。</p> <pre><code class="javascript">func add(a, b) { var c = a + b; return c; } func main() { var x = -1; var i = 0; while (i != 10) { case when (i == 1) { call_set x = add(i, x); } when (i == 2) { set x = 1 + 2; } when (1) { set x = 1 + (2 * 3); } set i = i + 1; } } </code></pre> <p>図の描き方については <a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a> を参照。</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/66f17460-1b71-e4bc-f82a-7a48d3fa269c.png" alt="image.png" /></p> <p>左端の、全体の 1/5 くらいの小さめの山が <code>add</code> 関数で、残りが <code>main</code> 関数ですね。</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>レキサはすでにあるのでそれを流用した <ul> <li><code>to_racc_token</code> メソッドで Token オブジェクトを Racc が期待する形式に変換</li> </ul></li> <li>1時間くらいで書けた。これより大きめの SQL パーサを少し前にすでに書いていてある程度慣れていたのと、パーサのテストがそのまま使えたのが良かった。</li> </ul> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <p>他に Racc 関連で書いたもの。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/6a96d2bcea9d134e38b7">Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/f55c1654fe101fa0e557">Ruby/Racc: パースに失敗した位置(行、桁)を得る</a></li> </ul> <h1 id="この記事を読んだ人はこちらの記事も読んでいます(たぶん)"><a href="#%E3%81%93%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A0%E4%BA%BA%E3%81%AF%E3%81%93%E3%81%A1%E3%82%89%E3%81%AE%E8%A8%98%E4%BA%8B%E3%82%82%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%84%E3%81%BE%E3%81%99%EF%BC%88%E3%81%9F%E3%81%B6%E3%82%93%EF%BC%89">この記事を読んだ人はこちらの記事も読んでいます(たぶん)</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sonota88/items/edce05e86d3248f49401">Parsletでかんたんな自作言語のパーサを書いた</a></li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16575 2021-01-10T08:38:01+09:00 2021-01-10T09:43:15+09:00 https://crieit.net/posts/Ruby-Racc-FlameGraph-5ffa3e59ccb77 Ruby/Racc: パース時のスタックの動きをFlameGraphっぽくビジュアライズする <p>(少し前に同じ記事を投稿していましたが、すぐに次の記事を投稿するとモバイル版では一覧で(折りたたまれて)見えなくなるようだったので一度引っ込めて投稿し直しました。まだ Crieit に慣れてないのでご容赦くださいませ……)</p> <p><a href="https://crieit.now.sh/upload_images/0e1305922bed9360c37ac2ff5dc93c965ff91788b6896.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0e1305922bed9360c37ac2ff5dc93c965ff91788b6896.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/8872ba6bd00c951d584f5e0daae90a325ff917c1c163e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8872ba6bd00c951d584f5e0daae90a325ff917c1c163e.png?mw=700" alt="image" /></a></p> <p>(↓これは FlameGraph に描かせたもの)<br /> <a href="https://crieit.now.sh/upload_images/e6294cd60200a969e3a5fac9581380e25ff917fa962f1.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e6294cd60200a969e3a5fac9581380e25ff917fa962f1.png?mw=700" alt="image" /></a></p> <p>こういう図を描いてみました。</p> <h1 id="手順"><a href="#%E6%89%8B%E9%A0%86">手順</a></h1> <h2 id="準備"><a href="#%E6%BA%96%E5%82%99">準備</a></h2> <p>説明用の簡単なサンプルです。<code>1 + 2</code> のような入力を受理するパーサ。</p> <p>ログ出力のために <code>@yydebug</code>, <code>@racc_debug_out</code> を設定しておきます。</p> <pre><code class="ruby"># sample_add.y class Parser rule expr: INT "+" INT { puts "expression found" result = val } end ---- inner def initialize # parser.rb を使ったときにデバッグ情報を出力する @yydebug = true # デバッグ情報の出力先をファイルに変更(デフォルトでは標準エラー出力) …… これは必須ではない @racc_debug_out = File.open("debug.log", "wb") end def next_token @tokens.shift end def parse(src) @tokens = src.split(" ").map do |s| case s when /^\d+$/ then [:INT, s.to_i] else [s, s] end end @tokens << [false, false] do_parse end ---- footer src = ARGV[0] result = Parser.new().parse(src) puts "result: " + result.inspect </code></pre> <p><code>-t (--debug)</code> オプションを付けて racc コマンドを実行。</p> <pre><code class="bash"># パーサを生成 racc -t -o parser.rb sample_add.y </code></pre> <p>生成されたパーサを実行すると次のような内容が debug.log に出力されます。</p> <pre><code class="bash">$ ruby parser.rb "1 + 2" expression found result: [1, "+", 2] $ head -30 debug.log read :INT(INT) 1 shift INT [ (INT 1) ] goto 2 [ 0 2 ] read "+"("+") "+" shift "+" [ (INT 1) ("+" "+") ] goto 4 [ 0 2 4 ] read :INT(INT) 2 shift INT [ (INT 1) ("+" "+") (INT 2) ] goto 6 [ 0 2 4 6 ] reduce INT "+" INT --> expr [ (expr [1, "+", 2]) ] goto 1 [ 0 1 ] </code></pre> <p>スタックの部分だけ欲しいので、 grep してみましょうか。</p> <pre><code class="bash">$ grep '\[ (' debug.log [ (INT 1) ] [ (INT 1) ("+" "+") ] [ (INT 1) ("+" "+") (INT 2) ] [ (expr [1, "+", 2]) ] [ (expr [1, "+", 2]) ($end false) ] [ (expr [1, "+", 2]) ($end false) ($end false) ] </code></pre> <p>軽く眺める程度ならこれだけでいいかもしれませんね。で、もっと複雑になったときにきれいに表示して見たい……ということで次へ。</p> <h2 id="パースしやすいログを出力する"><a href="#%E3%83%91%E3%83%BC%E3%82%B9%E3%81%97%E3%82%84%E3%81%99%E3%81%84%E3%83%AD%E3%82%B0%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B">パースしやすいログを出力する</a></h2> <p>ログに出力されたスタックの情報を使いたいのですが、そのままではパースしづらそうです。まじめにやろうとすると、これ用のパーサが必要になってしまいます(上の例のような簡単なものであればそんなに難しくなさそうですが)。</p> <p>そこで、横着して最初からパースしやすいフォーマットで出力することにしました。</p> <p>スタックの情報をどこで出力しているか調べると、ここ(<code>Racc::Parser#racc_print_stacks</code>)です。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/ruby/racc/blob/v1.5.2/lib/racc/parser.rb#L604-L611">https://github.com/ruby/racc/blob/v1.5.2/lib/racc/parser.rb#L604-L611</a><br /> 嬉しいことに単独のメソッドになっています。</p> <p>※ ちなみに呼び出し元を追っていくと分かりますが、スタックの実体は<br /> <code>Racc::Parser#racc_tstack</code><br /> <code>Racc::Parser#racc_vstack</code><br /> です(それぞれ記号と値のスタック)。</p> <p>racc コマンドの出力は <code>Racc::Parser</code> を継承したクラスになるので、<code>parser.y</code> の inner セクションに <code>racc_print_stacks</code> メソッドを書いておくと動作をオーバーライドできます。</p> <p>……というわけで、下記のようにしました。スタックの情報だけ JSON で別ファイルに出力します。</p> <pre><code class="ruby">---- header require "json" ---- inner def initialize # ... @racc_stack_out = File.open("stack.log", "wb") end # Override Racc::Parser#racc_print_stacks def racc_print_stacks(tstack, vstack) super(tstack, vstack) stack = tstack.zip(vstack).map { |t, v| [racc_token2str(t), v] } @racc_stack_out.puts JSON.generate(stack) end </code></pre> <pre><code class="bash">$ ruby parser.rb "1 + 2" expression found result: [1, "+", 2] $ cat stack.log [["INT",1]] [["INT",1],["\"+\"","+"]] [["INT",1],["\"+\"","+"],["INT",2]] [["expr",[1,"+",2]]] [["expr",[1,"+",2]],["$end",false]] [["expr",[1,"+",2]],["$end",false],["$end",false]] </code></pre> <p>これでパースしやすくなりました 👌</p> <h2 id="仕込み部分のまとめ"><a href="#%E4%BB%95%E8%BE%BC%E3%81%BF%E9%83%A8%E5%88%86%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81">仕込み部分のまとめ</a></h2> <p>ここまでのポイントをまとめておきます。</p> <ul> <li><code>@yydebug = true</code></li> <li><code>@racc_debug_out</code> を設定 <ul> <li>これは必須ではないが、標準エラー出力に出てほしくなければファイルなど別の出力先を設定しておく</li> </ul></li> <li><code>Racc::Parser#racc_print_stacks</code> をオーバーライド</li> <li>racc コマンドに <code>-t (--debug)</code> オプションを付けてパーサを生成</li> </ul> <h2 id="図に変換する"><a href="#%E5%9B%B3%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B">図に変換する</a></h2> <p>ここまでできたら、あとは stack.log を図に変換するだけ。</p> <pre><code class="bash">ruby stack_graph.rb stack.log > stack_graph.html </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/racc-stack-graph/blob/main/stack_graph.rb">stack_graph.rb</a> は 160行くらい(2021-01-04 時点)の簡単なスクリプトです。</p> <p>出力された HTML をブラウザで開くとこういう図が表示されます。x軸が処理の経過の方向、y軸がスタックが伸びる方向です。</p> <p><a href="https://crieit.now.sh/upload_images/0e1305922bed9360c37ac2ff5dc93c965ff918285b7f4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0e1305922bed9360c37ac2ff5dc93c965ff918285b7f4.png?mw=700" alt="image" /></a></p> <p>※ この例では、 <code>INT</code> や <code>expr</code> など定型的なものはパレット指定、それ以外はランダムに色を決めています。</p> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/racc-stack-graph">https://github.com/sonota88/racc-stack-graph</a></p> <p>このリポジトリを git clone して <code>./run.sh sample_add.y "1 + 2"</code> で試せます(パーサの生成 → パーサの実行 → 図の生成をまとめて実行)。</p> <h1 id="例1: 左結合・右結合の違いを見てみる"><a href="#%E4%BE%8B1%3A+%E5%B7%A6%E7%B5%90%E5%90%88%E3%83%BB%E5%8F%B3%E7%B5%90%E5%90%88%E3%81%AE%E9%81%95%E3%81%84%E3%82%92%E8%A6%8B%E3%81%A6%E3%81%BF%E3%82%8B">例1: 左結合・右結合の違いを見てみる</a></h1> <p>せっかくなので他の例も見てみましょう。</p> <p>まずは左結合と右結合の違い。他の部分は同じなので <code>class Parser ... end</code> の部分だけ示します。</p> <pre><code class="ruby"># sample_lr.y class Parser prechigh left "+" # right "+" preclow rule program: expr { puts "program found" result = val[0] } expr: INT | expr "+" expr { result = val } end </code></pre> <pre><code class="bash">./run.sh sample_lr.y "1 + 2 + 3" </code></pre> <p><a href="https://crieit.now.sh/upload_images/9e4478b04a779832b64ec14dbca107fa5ff91857c4753.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9e4478b04a779832b64ec14dbca107fa5ff91857c4753.png?mw=700" alt="image" /></a></p> <p><code>1 + 2</code> が来た時点ですぐ還元(reduce)され、次に <code>expr + 3</code> となったときにまた還元されています。</p> <hr /> <p>右結合に変えてみるとこう。パースの結果が <code>[1, "+", [2, "+", 3]]</code> になります。</p> <p><a href="https://crieit.now.sh/upload_images/709009c6f1e1ae5c0851581a79578a7d5ff91875cf671.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/709009c6f1e1ae5c0851581a79578a7d5ff91875cf671.png?mw=700" alt="image" /></a></p> <p>最初の <code>1 + 2</code> までシフト(shift)した時点では <code>1 + 2</code> の還元は発生せず、さらにスタックが積み上がったところで <code>2 + 3</code> が還元され、その後で <code>1 + expr</code> が還元されています。</p> <h1 id="例2: SQL"><a href="#%E4%BE%8B2%3A+SQL">例2: SQL</a></h1> <p><a href="https://crieit.now.sh/upload_images/861923e32cdc780d5e38bd128b69907a5ff918970228e.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/861923e32cdc780d5e38bd128b69907a5ff918970228e.png?mw=700" alt="image" /></a></p> <p>もう少し大きめの実際的なものということで、別件で作っているSQLパーサの例。select句、from句、…… と順番に還元され、最後に全体が1個の select文に還元されている様子です。</p> <p>次のような入力を与えました。動作確認用なので内容は適当。</p> <pre><code class="sql">select 123, 'str', null ,t1.a ,max(b) ,( case when a = 1 then 2 else 3 end ) as foo from db1 . table1 as t1 left outer join db2 . table2 as t2 on ( t2 . a = t1 . a and t2 . b = t1 . b ) and t2.a = t1.a where a = 123 and b <> 456 and c in (1, 2) group by a, b order by a, b limit 10 ; </code></pre> <h1 id="FlameGraph に渡してみる"><a href="#FlameGraph+%E3%81%AB%E6%B8%A1%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">FlameGraph に渡してみる</a></h1> <p>stack.log をちょっと加工すると <a target="_blank" rel="nofollow noopener" href="https://github.com/brendangregg/FlameGraph">FlameGraph</a> に渡せるのでは? と後から気づいてやってみました(この節は後から追記しました)。</p> <pre><code class="ruby"># to_flamegraph.rb require "json" File.open(ARGV[0]).each_line do |line| labels = JSON.parse(line).map { |t, _| t.to_s.gsub(";", "(semicolon)") } puts labels.join(";") + " 1" end </code></pre> <pre><code class="bash">ruby to_flamegraph.rb stack.log | path/to/flamegraph.pl > flamegraph.svg </code></pre> <p><a href="https://crieit.now.sh/upload_images/5cd6c4261e93c57cbbd204b6ef818ded5ff918c088109.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5cd6c4261e93c57cbbd204b6ef818ded5ff918c088109.png?mw=700" alt="image" /></a></p> <p><a href="https://crieit.now.sh/upload_images/e6294cd60200a969e3a5fac9581380e25ff918cb98474.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e6294cd60200a969e3a5fac9581380e25ff918cb98474.png?mw=700" alt="image" /></a></p> <p>できますね 😃</p> <p>FlameGraph で生成した SVG だと、キーワードにマッチする部分のハイライトや一部分のズーム表示といったインタラクティブな機能が利用できて便利。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://yuroyoro.hatenablog.com/entry/2017/11/09/124805">ディスク使用量をFlameGraphで可視化する - ( ꒪⌓꒪) ゆるよろ日記</a></p> <h1 id="バージョン"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3">バージョン</a></h1> <pre><code>racc 1.5.2 </code></pre> <h1 id="関連"><a href="#%E9%96%A2%E9%80%A3">関連</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/10/31/113917">Ruby/Racc: パースに失敗した位置(行、桁)を得る</a></li> </ul> <p>以下は Racc 関連ではありませんが、Ruby + パーサ関連で書いたものということで。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/05/111103">四則演算と剰余のみのexprコマンドをRubyで作ってみた</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/23/090115">正規表現エンジン(ロブ・パイクのバックトラック実装)をRubyで写経した</a></li> </ul> sonota486