手続き型マクロは、与えられたトークンストリームを編集することでコードをコンパイル時に書き換えるのだが、この作業を楽にするためにsynクレートというものがあり、synを使うと構文木の操作がかなり楽にできる(らしい)。
syn::parse_macro_input!で構文解析を行い、構文木(=AST=Abstract Syntax Tree)を作成する。
この時、トークンストリームを、ItemFnやExprなどに変換して渡す必要がある。マクロに渡された内容が関数であればItemFn、式であればExprに変換する。
extern crate proc_macro; use proc_macro::TokenStream; use syn::{ parse_macro_input, Expr, // Cargo.toml 内 syn = {"2.0", features = ["full"] } ItemFn, // "full" が必要 };
// 関数を処理する場合 as ItemFn を使用 #[proc_macro] pub fn tokenstream_analyze_for_function(input: TokenStream) -> TokenStream { let inputcopy = input.clone(); let myinput:ItemFn = parse_macro_input!(inputcopy as ItemFn);// トークンストリームを解析 return input; }
// 式を処理する場合 as Expr を使用 #[proc_macro] pub fn tokenstream_analyze_for_formula(input: TokenStream) -> TokenStream { let inputcopy = input.clone(); let myinput:Expr = parse_macro_input!(inputcopy as Expr);// トークンストリームを解析 return input; }
synを使用するためにはCargo.tomlに以下の記述が必要。ItemFnを使用するためには、features=["full"]を加える必要がある。
このマクロは、以下のようにして使用する。(注意 結果は何も変わらない)
extern crate mymacro;
// 関数に対してマクロを適用 mymacro::tokenstream_analyze_for_function!{ fn myfunction(i:i32)->i32{ i + 1 } }
fn main() { let value:i32; let seven:i32 = 7;
// 式に対してマクロを適用 mymacro::tokenstream_analyze_for_formula! { value = (seven + 5) * 2 }
print!("結果 {}",value); }
上記のマクロは何も起こらないので、syn::ItemFnやsyn::Expr型の変数myinputにちゃんと値が入っているのか確認したい。そのためにquoteクレートを使用して構文木を表示してみる。
extern crate proc_macro; use std::{string, str::FromStr}; use proc_macro::TokenStream; use syn::{ parse_macro_input, Expr, ItemFn // "full" が必要 }; use quote::ToTokens;
// 関数 #[proc_macro] pub fn tokenstream_analyze_for_function(input: TokenStream) -> TokenStream { // トークンストリームを解析 let inputcopy = input.clone(); let myinput:ItemFn = parse_macro_input!(inputcopy as ItemFn); println!("** 関数の構文木ができているか確認 *********************"); // quoteを使って文字列化 let astext = myinput.to_token_stream().to_string(); println!("{}", astext); input }
// 式 #[proc_macro] pub fn tokenstream_analyze_for_formula(input: TokenStream) -> TokenStream { // トークンストリームを解析 let inputcopy = input.clone(); let myinput:Expr = parse_macro_input!(inputcopy as Expr); println!("** 式の構文木ができているか確認 *********************"); // quoteを使って文字列化 let astext = myinput.to_token_stream().to_string(); println!("{}", astext); input }
これをビルドすると、ビルド中に以下が表示される。
RustのsynはRustで書かれたソースコードを解析するためのもので、手続き型マクロを実装するときに重宝するが、普通のプログラムを書く時にも使用できる。
文字列からコードを解析する場合、syn::parse_strを用いる。
fn main() { // 文字列でRustのコードを書く let my_rust_code = " #[derive(Debug, Clone)] #[inline] pub fn myfunc(&self,x:i32, y:i32) -> f32{ println!(\"Hello, world!\"); } " ; // ソースコードのパース let my_parse = syn::parse_str::<syn::ItemFn>(my_rust_code).unwrap(); let mut myfv:MyFnVisitor = MyFnVisitor{indent:0}; myfv.visit_item_fn(&my_parse); }
///////////////////////////// /// 以下、解析のための構造体並びに関数 struct MyFnVisitor{ indent:usize, } use quote::ToTokens; use syn::visit::Visit; impl<'a> syn::visit::Visit<'a> for MyFnVisitor{ // 関数をパースするには、visit_item_fnを実装する fn visit_item_fn(&mut self, node: &'a syn::ItemFn) { let mut indentstr:String = "".to_string(); for _ in 0..self.indent{ indentstr.push_str(" "); } println!("{}****************",indentstr); // 関数がパブリックかどうかをチェック if let syn::Visibility::Public(_pub) = node.vis { println!("{}pub? {}",indentstr,_pub.to_token_stream().to_string()); } // アトリビュートを取得 for attr in &node.attrs{ let id = attr.path().get_ident().unwrap(); println!("{}attribute: {}",indentstr,id); } // 関数名を取得 let function_name = &node.sig.ident; println!("{}func_name: {}",indentstr,function_name); // 引数を取得 for arg in node.sig.inputs.iter(){ match arg{ syn::FnArg::Typed(pat_type) => { if let syn::Pat::Ident(patident) = &*pat_type.pat{ let arg_name = &patident.ident; let arg_type = &pat_type.ty.to_token_stream().to_string(); println!("{}arg: {} {}",indentstr, &arg_name, &arg_type ); } } _ => {} } } } }
前回、トークンストリームを見てみた。もう少し詳しく見てみる。
Rustの手続き的マクロを考える(1)トークンストリームを確認してみる
以下のようなマクロを使用する側。この実行結果は「24」になる。
extern crate mymacro; fn main() { let value:i32; let seven:i32 = 7;
mymacro::print_tokens! { value = (seven + 5) * 2; }
print!("結果 {}",value); }
extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn print_tokens(input: TokenStream) -> TokenStream { // TokenStreamをStringに変換 let input_str = input.to_string(); println!("受け取ったトークン: {}", input_str);// ビルド時画面に表示 // トークンの種類を特定 for token in input.clone().into_iter() { if let proc_macro::TokenTree::Literal( theItem ) = token{ println!("これはリテラル {}", theItem); } else if let proc_macro::TokenTree::Punct( theItem ) = token{ println!("これは記号 {}",theItem); } else if let proc_macro::TokenTree::Ident( theItem ) = token{ println!("これは識別子 {}",theItem); } else if let proc_macro::TokenTree::Group( theItem ) = token{ println!("これはグループ {}", theItem); } else{ println!("不明 {:?}", token); } }; // 入力をそのまま返す input }
トークンの扱いがわかったので、トークンを書き換えてみる。以下のマクロはprint_tokensに渡された内容が「2」であった場合は値を二倍(すなわち4)にする。
マクロに渡されたTokenStreamのinputを直接書き換えることはできないので、TokenTreeのVecを用意し、そこにトークンをpushしていく。その過程で編集したい内容を見つけたら新たなトークンを作成して既存のトークンと置き換える。
最後に、TokenTreeのVecをTokenStreamに変換して返す。
このマクロを使用して最初のプログラムを実行した結果は「48」になる。
extern crate proc_macro; use std::{string, str::FromStr}; use proc_macro::TokenStream; #[proc_macro] pub fn print_tokens(input: TokenStream) -> TokenStream { // TokenStreamをStringに変換 let input_str = input.to_string(); println!("受け取ったトークン: {}", input_str);// ビルド時画面に表示 // inputを書き換えることができないので、出力用のTokenStreamを作成 let mut output = Vec::new(); // トークン毎に処理 for token in input.into_iter() { // 所有権が移動しないように ref theItemとする if let proc_macro::TokenTree::Literal( ref theItem ) = token{ println!("リテラルを発見 {}", theItem); let new_token; let ival = theItem.to_string().parse::<i32>().unwrap();// リテラルの値を取得 if ival == 2{ println!("リテラルの値が2の時だけtoken書き換え"); // 新しいトークンを作成 new_token = proc_macro::TokenTree::from( proc_macro::Literal::i32_unsuffixed( ival * 2 /*二倍にする*/) ); // 新しいトークンを出力用の配列に追加 output.push(new_token); } else{ // 2以外の時はそのまま出力用の配列に追加 output.push(token.clone()); } } else{ // リテラル以外はそのまま出力用の配列に追加 output.push(token.clone()); } }; // 新しく作成したトークンの配列をTokenStreamに変換して返却 return TokenStream::from_iter(output); }
Rustには、「宣言的マクロ」(macro_rules!で定義する)と「手続き的マクロ」があり、「手続き的マクロ」にはさらにderiveマクロ、関数風マクロ、属性風マクロという分類がある。
手続き型マクロは、マクロの呼び出し元から入ってきたコード片を「トークンストリーム」として受け取る。PGはこのトークンストリームを処理することでマクロに与えられたコードをコンパイル時に書き換えることができる。
マクロを作る前に、トークンストリームを表示してみる。
手続き型マクロは、専用のクレートを作らなければいけない。
今回は、main関数があるほうをmymain、マクロを定義する法をmymacroとしてクレートを作成する。
Cargo.tomlに以下を記述する。
extern crate proc_macro; use proc_macro::TokenStream; #[proc_macro] pub fn print_tokens(input: TokenStream) -> TokenStream { // TokenStreamをStringに変換 let input_str = input.to_string(); // 画面に表示 println!("Received TokenStream: {}", input_str); // inputを一つ一つのTokenに分割 for tk in input.clone().into_iter() { // 画面に表示 println!("*** {}", tk); }; // 入力をそのまま返す(または他のTokenStreamを返す) input }
以下のdependenciesを追加。
[dependencies] mymacro = { path = "../mymacro" }
extern crate mymacro; fn main() { let mut value=5; mymacro::print_tokens! { value = value + 5; } print!("hello world {} ",value); }
mymainをbuildする。
すると、ビルド時に以下のようにトークンストリームが表示される(実行時ではない)。
ChatGPT4で簡単なコードなら自動生成できるようになったので、結果的に日本語入力頻度が上がっている。日本語入力をするなら親指シフト、親指シフトを使うなら紅皿が必要になる。
OSDNからダウンロードできるが、正直OSDNは遅くて待っていられない。
公式のGithubからもダウンロードできるので個人的にはこちらを推奨したい。
https://github.com/k-ayaki/benizara/releases/tag/ver.0.1.6.03
注意点として、benizara_01603.zipがmsiのインストーラ版、exeを直接導入したい場合、Source code (zip) を導入。これは、紅皿が「AutoHotKeyのスクリプトを実行ファイル化したもの」なので、紅皿本体のプログラムというものが(ほぼ)ないからではないかと思う。
ローカルディレクトリにある.bnzファイルが各キーを押した場合の配列を定義している。
例えば、.\NICOLA配列.bnzのデフォルトの設定ではBackspaceを右小指で入力できないので、このファイルを(念のため)コピーして、:キーのローマ字シフトなしの位置に「後」を書き込む。するとただ:キーを入力した場合はBackspaceを押したことになる。:を入力したい場合は左右どちらかの親指シフトで行える。
まず、cargoコマンドでそれぞれのプロジェクトを作成する。
これによって、同じ階層に二つのプロジェクトができる。
root ├─mylib │ │ .gitignore │ │ Cargo.lock │ │ Cargo.toml │ │ │ ├─.vscode │ │ launch.json │ │ │ ├─src │ │ lib.rs │ │ │ └─target │ │ .rustc_info.json │ │ CACHEDIR.TAG │ │ │ └─debug │ └─mymain │ .gitignore │ Cargo.lock │ Cargo.toml │ ├─src │ main.rs │ └─target │ .rustc_info.json │ CACHEDIR.TAG │ └─debugu
// mylib/src/lib.rs pub fn mylib_hello() { println!("Mylib: Hello, world!"); }
extern crate mylib; fn main() { mylib::mylib_hello(); }
[dependencies] mylib = { path = "../mylib" }
mymainだけをcargo buildすれば、自動でmylibもビルドされてリンクされる。
WindowsでVTK9.3をビルドする。
Static Link Libraryとしてビルドするには、BUILD_SHARED_LIBS をOFFにする。
ただし、Windowsでこのチェックを外すと、以下のエラーが起こる可能性がある。
そこで次の設定が必要
上記vtkSMPToolsAPIのエラーを避けるため、VTK_SMP_ENABLE_STDTHREADをOFFに設定。
VTK failed to build due to error LNK2019 and error LNK1120 on Windows with msvc
情報自体は大したことはない。ただ確認するためのVTKのビルドが長すぎる。
CSplitterWndでCMainFrameを画面分割する。構造としては、MFCが作成したシングルドキュメントのウィンドウは、CMainFrameを親とし、その子にツールバー、CChildView、ステータスバーが乗っている。このCChildViewをCSplitterWndで置き換えることで実現する。
ドキュメント/ビューアーキテクチャは無効にしておく。
CChildViewのオブジェクトをCSplitterWndのオブジェクトで置き換えるため、コード上のm_wndViewという変数がかかわるコードをすべて無効化する。m_wndViewの定義をコメントアウトする、あるいは検索すれば簡単に見つかる。
// MainFrm.h : CMainFrame クラスのインターフェイス // #pragma once #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame() noexcept; protected: DECLARE_DYNAMIC(CMainFrame) /* 略 */ protected: // コントロール バー用メンバー CToolBar m_wndToolBar; CStatusBar m_wndStatusBar; // 使用しない CChildView m_wndView; /* 略 */ };
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; //// フレームのクライアント領域全体を占めるビューを作成します。 //if (!m_wndView.Create(nullptr, nullptr, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, nullptr)) //{ // TRACE0("ビュー ウィンドウを作成できませんでした。\n"); // return -1; //} /* 略 */ return 0; }
void CMainFrame::OnSetFocus(CWnd* /*pOldWnd*/) { // ビュー ウィンドウにフォーカスを与えます。 //m_wndView.SetFocus(); }
protected: // コントロール バー用メンバー CToolBar m_wndToolBar; CStatusBar m_wndStatusBar; // 使用しない CChildView m_wndView; CSplitterWnd m_wndSplitter;// スプリッターの定義
CMainFrameでOnCreateClientをオーバーライドする。
OnCreateClientにはスプリッタの初期設定を記述する。
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) { // TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。 // スプリッタウィンドウの作成 m_wndSplitter.CreateStatic(this, 1, 2); // ペインの作成(例:左側にCLeftView、右側にCRightViewを配置) m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(200, 100), pContext); m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CRightView), CSize(100, 100), pContext); return CFrameWnd::OnCreateClient(lpcs, pContext); }
CLeftView,CRightViewに着色すると結果がわかりやすい
void CLeftView::OnDraw(CDC* pDC) { CDocument* pDoc = GetDocument(); // TODO: 描画コードをここに追加してください。 CRect rect; this->GetClientRect(&rect); pDC->FillSolidRect(&rect,RGB(255,0,0)); }
CFileDialogのフィルタ指定の書式は、コンストラクタ指定の場合は "表示用文字列|*.拡張子|"、m_ofn.lpstrFilter指定の場合、"表示用文字列\0*.拡張子\0"となる。
ただし注意があり、m_ofn.lpstrFilterで指定する場合、CStringを使ってはいけない。たぶん、\0が見つかるとそこで文字が切られてしまうため、最初の一つしか選択肢に出てこない。
以下指定例。プロジェクトはマルチバイト文字セットに設定してある。
CFileDialog fileDialog(FALSE, "*.bmp", "output.bmp"); const char* filter = "BMPファイル\0*.bmp\0" "JPGファイル\0*.jpg\0" "PNGファイル\0*.png\0" "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = filter; fileDialog.DoModal();
CFileDialog fileDialog( FALSE, "*.bmp", "output.bmp", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, "BMPファイル|*.bmp|" "JPGファイル|*.jpg|" "PNGファイル|*.png|" "全てのファイル|*.*||" ); fileDialog.DoModal();
CStringは\0を含められない(?)ので、m_ofn.lpstrFilterに与える文字列をCStringにしてはいけない。
CFileDialog fileDialog(FALSE, "*.bmp", "output.bmp"); // これは失敗する。 CString filter = "BMPファイル\0*.bmp\0" "JPGファイル\0*.jpg\0" "PNGファイル\0*.png\0" "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = (LPCTSTR)filter; fileDialog.DoModal();
CFileDialog fileDialog( FALSE, "*.bmp", "output.bmp", OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, // これは成功する (LPCTSTR)CString( "BMPファイル|*.bmp|" "JPGファイル|*.jpg|" "PNGファイル|*.png|" "全てのファイル|*.*||" ) ); fileDialog.DoModal();
指定した拡張子は、上から1スタートで番号が振られている。
fileDialog(FALSE, "bmp", "output.bmp"); const char* filter = "BMPファイル\0*.bmp\0" // index == 1 "JPGファイル\0*.jpg\0" // index == 2 "PNGファイル\0*.png\0" // index == 3 "全てのファイル\0*.*\0\0"; fileDialog.m_ofn.lpstrFilter = (LPCTSTR)filter; if (fileDialog.DoModal() == IDOK) {
// 選択されたファイルのインデックスを取得 int filterIndex = fileDialog.m_ofn.nFilterIndex; switch (filterIndex) { case 1: MessageBoxA("BMP"); break; case 2: MessageBoxA("JPG"); break; case 3: MessageBoxA("PNG"); break; case 4: MessageBoxA("*.*"); break; }
// なお入力されたファイル名 CString filePath = fileDialog.GetPathName(); MessageBoxA(filePath); }
wxWidgetsのGUIを作るためのデザイナー。いろいろな言語に対応しているが私はC++用コードを出力するために使用してみる。
wxFormBuilderでデザインした後はGenerate CodeでC++のソースコードに変換できるので、.hと.cppを読み込めば簡単に組み込める。
https://github.com/wxFormBuilder/wxFormBuilder/releases
Githubにビルド済みライブラリページへのリンクが張ってあるのでダウンロードすれば起動できる。
1.FormsからFrameなどを追加
2.LayoutからwxBoxSizerなどを追加
3.CommonからwxButtonなどを追加
4.File → Save から.fbp形式で保存
5.File → Generate Code
Generate Codeをすると、fbp と同じ場所に .h/.cpp が作成される。
noname.h / noname.cpp をプロジェクトに追加し、noname.h をincludeする。この中にMyFrame1等の名前でフレーム類が定義してあるので、OnInitの中でそのインスタンスを作成する。
#ifndef WX_PRECOMP #include <wx/wx.h> #endif #include <wx/gdicmn.h> // wxPointに必要 #include <wx/frame.h> // wxFrameに必要 ///////////////////////////////////// ///////////////////////////////////// /////////////////////////////////////
// wxFormBuilder で出力したファイル。内部で MyFrame1 が定義されている #include "noname.h" ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // wxAppはここから開始。自分で書く class MyApp : public wxApp { public: virtual bool OnInit() { MyFrame1* frame = new MyFrame1(0,-1,"Hello World", wxPoint(50, 50), wxSize(450, 340)); frame->Show(true); return true; } }; ///////////////////////////////////// ///////////////////////////////////// ///////////////////////////////////// // エントリポイントも自分で定義。WinMainをマクロで定義 wxIMPLEMENT_APP(MyApp);