参加しました。今回は久しぶりの午前の部がありました。配信アーカイブは以下から
午前
午前は「自作シェルとRustの勉強会」と題して、SD誌に連載中の自作シェルRusty Bash(寿司シェル)の話と実装に利用しているRustについてでした。個人的にはRusty Bashって名前好きです。
Rustはsurgeを作ったとき以来書いてないのですべて無知、初見のつもりで聞いてました。
str
とString
の違い、メモリの配置などぼんやりしか理解してなかった部分がはっきりしたので良かったです。
大阪サテライトでは、解説内容の所々で出た内容に対する疑問を話したりしてました。これも楽しかったです。出た疑問のいくつかはそのまま配信で触れられて「エスパーなのかな?」って思ったりもしました。
またRust書いてみようかな~
午後
午後はいつもの問題を解くやつでした。全6問。今回もBash/Zsh解とは別にPowerShell解も出してみます。ただし今回は「このツールを使って解いた」みたいなのが多いので、差異があんまりない場合はやらないです
準備
今回はいつものShellGeiDataリポジトリではなく、午前にも登場したshellgei/rusty_bashを利用するとのことでした。
Q1
src/
以下にあるRustのソースから、メソッドが4つ以上つながっている部分を探すという問題。
$ rg '[^.]+(\.[^.]+\(.*\)){4}'
feeder/terminal/completion.rs
116: completion::compgen_h(core, &mut args).to_vec().into_iter().filter(|h| h.len() > 0).collect()
elements/subword.rs
71: split_str(self.get_text()).iter().map(|s| f(s.to_string())).collect()
utils/directory.rs
35: fs.iter().filter(|f| compare(f) ).map(|f| make_path(f) ).collect()
elements/command.rs
72: self.get_redirects().iter_mut().rev().for_each(|r| r.restore());
想定解としてはこの4つだったみたいですね。コマンドはまぁ正規表現頑張って書きました。という感じです。メソッドの引数内に()
が登場するので、その入れ子をいい感じにエスケープする必要がありますね。
もうちょっとそれ専用のツールを使った解も出してみます。
$ sg -p '$A.($$$).($$$).($$$).($$$)' -l rust
./elements/word.rs
74│ words.iter_mut()
75│ .map(|w| w.make_unquoted_word())
76│ .filter(|w| *w != None)
77│ .map(|w| w.unwrap())
78│ .collect()
82│ let sw: Vec<Option<String>> = self.subwords.iter_mut()
83│ .map(|s| s.make_unquoted_string())
84│ .filter(|s| *s != None)
85│ .collect();
<意外と長いので略>
ast-grepは抽象構文木を検索できるツールです。今回は4つ以上連鎖するメソッド呼び出しということなので、それにマッチするクエリを書いて検索したって感じですね。クエリを書くのがむずいんですが、正規表現を学ぶときと同じで慣れたら楽なんだろうなって思いました。改行を跨いだものも検索できるのでいいですね。
面白いのは --json
オプションでマッチ箇所の情報をJSONで出力できるところで、あとの問題でも使ってます。
PowerShell解は同じもの使うだけなので省略です
Q2
ShellCore
を Core
に置き換えてコンパイルが通るようにする問題
$ sed -i 's/ShellCore/Core/g' ./**/*.rs
$ cargo build
sed
で置換するだけでした。解答で見かけたやつですが \<ShellCore\>
とすることで AShellCore
みたいなものを除外できるとのことでした。勉強になる。
Q3
この問題は小問がありました
小問1
nix::sys::utsname::uname
のように、パスが3段以上になっている個所をファイル名、行番号付きで出力してください。ただし use
行は除いてください。という問題
$ sg -p '$$$::$$$::$$$' | grep -v ':use'
./utils.rs:64: if let Ok(info) = nix::sys::utsname::uname() {
./main.rs:33: nix::unistd::dup2(2, fd).expect("sush(fatal): init error");
./main.rs:43: nix::unistd::close(fd).expect("sush(fatal): init error");
./main.rs:47: thread::sleep(time::Duration::from_millis(100)); //0.1秒周期に変更
./main.rs:78: builtins::set::set_parameters(&mut core, &mut args);
./elements/pipeline.rs:62: let self_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_SELF).unwrap();
./elements/pipeline.rs:63: let children_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_CHILDREN).unwrap();
./elements/subword/command.rs:53: thread::sleep(time::Duration::from_millis(1));
./core.rs:171: let self_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_SELF).unwrap();
./core.rs:172: let children_usage = nix::sys::resource::getrusage(nix::sys::resource::UsageWho::RUSAGE_CHILDREN).unwrap();
./core.rs:245: let pid = nix::unistd::getpid();
<省略>
ast-grep
を利用しました。このままだとuse
行なものが含まれますが、それは grep
で除外する感じですね。
小問2
小問1で見つけたパスの内、elements/pipeline.rs
のものについて、パスを2段にして、残りをuse
にしてくださいという問題。大変そう
$ ADD=0 sg -p '$$$::$$$::$$$' -l rust ./elements/pipeline.rs --json | \
jq -r '.[]|[.text, .range.start.line]|@csv' | \
tr -d \" | \
awk -F, '{if(!($2 in a) || length($1)<length(a[$2])) a[$2]=$1; cnt[$2]++} END{for(x in a) if(cnt[x] > 1) print x, a[x]}' | \
while read L R; do sed -i "$((L+ADD))s/${R%::*}:://g;" ./elements/pipeline.rs; if ! grep "use $R;" ./elements/pipeline.rs --quiet; then sed -i "4iuse $R;"
./elements/pipeline.rs; ADD=$((ADD+1)); fi; done
$ cargo build
めちゃくちゃになってしまった。ast-grep
で対象のパスを切り出した後、そのJSON情報から置き換え行と置き換え文字列を計算してsed
を発行する感じですね。ADD
はuse
行を挿入したときにできる検索時との行数のズレをカウントするやつです。こうすることで、対象行だけsed
ができますが、今思えば use
行を除いて全部 sed
していいのでなくていいですね。
Q4
src/main.rs
の中から1つ関数を選んで tmp.rs
に書き出し、src/main.rs
からはコメントアウトする問題
$ sg -p 'fn show_version() { $$$BODY }' -l rust main.rs --json > a.json; cat a.json | jq -r '.[0].text' > tmp.rs; cat a.json | jq -r '"sed -i \"\(.[0].range.start.line+1),\(.[0].range.end.line+1)s@^@//@ \" main.rs"' | bash
はい。ast-grep
で取り出した情報を使って書き出しとコメントの挿入をしてます。便利~
Q5
main.rs
にある以下のような行を
use std::{env, process, thread, time};
を
use std::env;
use std::process;
use std::thread;
use std::time;
にする問題。
$ grep -Pe '^use ' main.rs | tr '{}' ' ' | sed 's/:: /::,/;s/, /,/g' | comb ifs=, ofs="" 2 | grep -Pe '^
use' | tr -d \; | sed -r 's/\s*$/;/'
use std::env;
use std::process;
use std::thread;
use std::time;
use std::path::Path;
use std::sync::Arc;
use std::sync::atomic::Ordering::Relaxed;
use crate::core::builtins;
use crate::core::ShellCore;
use crate::elements::script::Script;
use crate::feeder::Feeder;
use crate::feeder::InputError;
use signal_hook::consts;
use signal_hook::iterator::Signals;
egzactのcomb
を使ってuse
文を展開する感じですね。
Q6
Q5の逆をやる問題。大変そう。これは時間内に解けませんでした。
方針としては tree
のような構造を作ってから畳み込めばいいのかなと思い…
$ grep -Pe '^use ' ./main.rs | cut -d ' ' -f 2- | tr -d \; | stairl ifs=:: | sort -u
crate
crate core
crate core {builtins, ShellCore}
crate elements
crate elements script
crate elements script Script
crate feeder
crate feeder {Feeder, InputError}
signal_hook
signal_hook consts
signal_hook iterator
signal_hook iterator Signals
std
std path
std path Path
std sync
std sync Arc
std sync atomic
std sync atomic Ordering
std sync atomic Ordering Relaxed
std {env, process, thread, time}
という感じの出力を作ってみましたがここからどうにもできず…
おわり
今回はひさびさの午前がありました。誰かに何かを教えてもらうみたいなのが久しぶりでなんか懐かしかったです。講師の上田先生、TLで追加解説してくださった皆様ありがとうございました!