OCamlでSQL formatter を作ってみた

台風だったり暑さだったり、気象の振れ幅が大きくなってきてるなー、と思ったりしてます。でも古気候学とか噛じってると、さらに昔はもっと激烈だったんだろうなぁ、と思ったり思わなかったり。 さて、題名の通りなんですが、やっとこさ形になったのでこの記事を書こうと思います。 モチベーション https://github.com/derui/ocaml-sql-format Initial commitが5/31なので、大体この記事を書いている段階だと三ヶ月弱経過した形になります。まずなんで作ろうと思ったのかというと、とてもシンプルです。 そういえばFormatterって作ったことなかった 最近OCaml書いてないから書きたかった SQL formatterってあんまり見ない(ような気がしたけど探したら結構あった)のでやってみよう ついでに仕事でも使うし という感じで決定しました。大抵SQLのフォーマットって、ググって出てきたWeb toolに放り込んで…というのが多いですよね。最近のChatGPTだったりで、入力したものが学習されたり漏れうる、と考えると、あまりやりたくないところです。今の会社だとバリバリ個人情報を調べたりするんで、そのフォーマットごときでリスクを負いたくもないですし。モチベーションはそんな感じなので、どういう感じで作っていったかを思い出しながら書いていこうかと思います。 lexer/parserライブラリ 今回は当然OCamlを使ったのですが、SQLをパースするというのが一番の力仕事になることはわかりきっていました。また、最近はUnicodeがデフォルトですし(マレに絵文字を検索せんとあかんこともある)、スタンダードなocamllexよりは、よりモダンなものを使ってみたいものです。 Sedlex - https://github.com/ocaml-community/sedlex ということで、今回はSedlexを利用してみました。前々から使おうかなーと思ってたんですが、機会がなかったのでちょうどよかったです。Sedlexは、ppxを存分に利用したocamllexの代替として利用できるlexer generatorとなってます。特徴は↑を見てもらった方が早いので、こんな感じに使えるよ?というのを示そうかと let letter = [%sedlex.regexp? 'a' .. 'z' | 'A' .. 'Z' | 0x0153 .. 0xfffd] let rec token buf = match%sedlex buf with | letter -> Letter (Sedlexing.Utf8.lexeme buf) | eof -> Eof ocamllexで地味にペインだったのが、tuaregとかだと完全にはサポートされきってないので、フォーマットだったりなんだったりが微妙になったり補完がききづらかったり、という問題がありました。 sedlexはppxベースなので、すべて完全なOCamlソースとして扱える、というのが利点ですね。拡張の中だと補完は効かないんですが、まぁエラーはわかりやすいです。 parser generatorとしてはmenhir一択です。現状ocamlyaccを選択する理由はございませんので、もしocamlyaccしか使ったことがない方は使ってみることをオススメします。 テストと自動生成の仕組み formatterという決定論的なツールを作るのと、フォーマット前後の結果が重要なツールなので、テストも最初から考えとくことにしました。とはいえ、相性が最もよいのはexpectation testであることは想像がついていたので、ppx_expectを利用してます。 が!それだけだととてもめんどくさいです。現状100ファイルくらいあるのですが、全部に同じようなことを書いていくのは正直やってられないです。また、SQLはキーワードの数が正気か?ってくらい多いので、それに対応するlexer generatorの設定も死ぬほど多いです。これらを全部手書きしていたら時間がいくらあっても足りないので、いくつか自動生成ツールを作成しました。 キーワードからsedlexのフォーマットに変換する ast/printer/parserのデフォルトテンプレートを作成する SQLからのテストの生成 これらがあることで、大体一個のsyntaxを作成するのに小さければ5分〜10分、大きいものでも30分程度で量産できるようになりました。テストも、SQLを変更して再生成→promoteという流れを作ることができました。予想通りexpectation testはバチっと嵌ったので、こういう系統ではオススメです。 SQLのパースの苦しみ さて、一番苦しんだのはSQLをパースするためのgrammerを記述するところです。今回参考にしたのは三つあります。 https://ronsavage.github.io/SQL/sql-2003-2.bnf.html#data%20type http://teiid.github.io/teiid-documents/13.1.x/content/reference/r_bnf-for-sql-grammar.html#parseBasicDataType https://www.sqlite.org/lang.html 上から、 ISOにおける定義 、 teiidというツールにおけるBNF 、 SQLiteにおけるsyntax diagram となってます。最終的にはsqliteのを基本にしつつ、teiidを参考に、ISOのものを標準との比較資料として使いました。 パースは合計3回書き直しています。最初はISOのものを参考にしてたのですが、超絶な分量(約1000P)かつ、LRではそのまま記述できない再帰や、括弧が曖昧になってしまい解決できないケースが存在するっぽく、私の知識ではどうにかできませんでした…。 teiidのものはもうちょっとシンプルになっているのですが、同様に式における括弧が曖昧になってしまうようでした。括弧については、LRでは非常にやりづらいもので、if-then-elseと同様に解決しづらいものとして扱われています。 最終的にsqliteのものを利用したのは、必要十分な量、かつダイアグラム上曖昧になる部分が少ない、というところで参考にしてます。一部そのままだと書き下せない部分があったので、そこはteiidのものを利用してます。 SQLiteはシンプルかつ十分な性能…的な立ち位置なので、merge文などは実装されていません。個人的にもmerge文を使うケースはそんな無かったので、一旦ここはスキップしてます。 menhirのnew syntax 若干話は逸れますが、menhirにはold syntaxとnew syntaxってものがあります。 ...

August 12, 2023 · derui

最近知ったOCamlの小ネタ

すっかり暖かくなりました。ツツジも咲き出しているので、そろそろまた駒込とかに行こうかなぁ、とか思ってます。 今回は草埋めに近い、最近知った超スモールな小ネタになります。 ...

April 9, 2023 · derui

簡単なQRコードが出力できるようになった

観測史上最速の梅雨明けとかで、とてつもなく暑いですね。もう今から夏が怖いです。あれ、梅雨明けたからもう夏・・・? 思ったよりも時間がかかってしまいましたが、前からやってみたかったことについて書こうと思います ...

July 3, 2022 · derui

簡単なQRコードが出力できるようになった

観測史上最速の梅雨明けとかで、とてつもなく暑いですね。もう今から夏が怖いです。あれ、梅雨明けたからもう夏・・・? 思ったよりも時間がかかってしまいましたが、前からやってみたかったことについて書こうと思います ...

July 3, 2022 · derui

Rust + YewでTypeScript + Reactをリライトしてみた

あけましておめでとうございます。鏡開きギリギリなのでまだそう言っていいはず・・・。気付いたら転職して大体2年経過していたり、引越してから一年過ぎていたりして、時間の流れははえーなぁ、と思う日々です。 大体一ヶ月くらいセコセコとやって、年末年始も(珍しく)実家で作業していたりしたやつが、基本部分は動くようになったので、それについて書いてみます。 ...

January 10, 2022 · derui

Rust + YewでTypeScript + Reactをリライトしてみた

あけましておめでとうございます。鏡開きギリギリなのでまだそう言っていいはず・・・。気付いたら転職して大体2年経過していたり、引越してから一年過ぎていたりして、時間の流れははえーなぁ、と思う日々です。 大体一ヶ月くらいセコセコとやって、年末年始も(珍しく)実家で作業していたりしたやつが、基本部分は動くようになったので、それについて書いてみます。 <!–more–> きっかけ きっかけは単純で、色々見ていたときに、 yew というフレームワークを見つけたから、です。 https://yew.rs/ どういうものかというと、 Rust製 React.jsを強く指向したコンポーネントライブラリ 周辺にstate管理(だけじゃないけど後述)、ルーターなども整備していて、必要最小限は揃っている というものです。超荒く言うと、 Rustで全部やっちゃおうぜ というやつですね。js_of_ocamlとかBucklescriptとかで実際似たようなことをやっていた身としては馴染があります。あっていいのか。結論的には、いつもの やってみたかった駆動開発 です。 リポジトリ https://github.com/derui/simple-planning-poker/tree/yew すでにこのリポジトリが盛大な実験場となっているのは気にしないでください この記事の時点だと、必要最小限成立する程度の機能までしかできてません。一応道筋は見えているので、実装自体は簡単ではありますが。 構成 利用しているライブラリはCargo.tomlを見たらだいたいわかるようになっていますが、それ以外の構成も含めて、利用しているツールなど。 Rustは最新安定版 yarn v3 ずーっとv1使ってきましたが、アップグレードしてみました Webpack 5 wasm-pack + wasm-pack-plugin メインのprojectとサブprojectとして分離 ルートのCargo.tomlには、projectsの定義のみ入れています ライトなCleanArchitecture的思想 ただし、これはプロジェクト構成を失敗した感も・・・ yew-agent/yew-routerを利用してルーティングとか wasm-bindgen + gloo + wasm-bindgen-future Firebase(realtime database/auth)を利用 当然Rustから呼んでます TSな部分は、依存をglobalに展開 + Firebaseの初期化のみ という形になっています。ぶっちゃけ途中はテストも書かずにひたすら移植作業していたので、実際に動くのかどうか?は実際に動かしながら試してみた・・・というあんまりよくない形になっています。が、テスト書きながらだと多分この2倍かかった気がするので、とりあえずどういうものなのか?を確かめるという目的を達成するためにはこれでよかったかな、と。 yewでどうやって書くのか? Rustがどういうものか、とかは全部すっとばします。Rust公式に良質なドキュメントがあるのでそちらをどうぞ。また、初期セットアップも全部すっとばします。公式ドキュメントを見たほうが早いです。 超簡単なサンプルとしてはこんな感じになります。 #[derive(Properties, PartialEq)] pub struct Property { counter: u32, } #[function_component(Component)] pub fn component(props: &Property) -> Html { html! { <div><span> {"Counter: "} </span><span> {props.counter}</span></div> } } #[function_component(Main)] pub fn main() -> Html { let state = use_state(|| 0); let onclick = { let state = state.clone(); Callback::from(|_| state.set(*state + 1)) }; html! { <Component counter={*state} /> <button onclick={onclick}>{"Click"}</button> } } yewは、 struct component と functional component という二通りの実装が可能です。この辺もReactのClass ComponentとFunctional Componentとよく相似していますね。 ...

January 10, 2022 · derui

OCamlでSchemeを実装してみている

気づいたら1月が終わりそうです。そしてPS5が当たりました。まだ特にやるものは決めてないんですが、総計抽選回数2回目で当たったので、日頃の行いが良かったんだと思います。 最近引っ越しにかこつけて、環境をいろいろ変更しています。その話でもう一回くらい書けるネタはあるんですが、今回はなんとなく始めたOCamlでのScheme実装について書いてみたいと思います。 <!–more–> とりあえずリポジトリ https://github.com/derui/scheme-ocaml-impl この記事の時点だと、 primitiveな関数は極々一部のみ 数値は整数のみ define/if/set!/let/lambda を実装済み defineでのlambda定義はまだやってません という、必要最小限すぎる実装となっています。いろいろ余裕があったら、r7rsに準拠できるように実装していく想定ですが、途中で飽きる可能性もめっちゃ高いです。 動機 さて、なんでOCamlでSchemeを実装しようと思ったのか?ですが、これはもうかなり簡単です。 もともと言語実装をやってみたかった 実はまともに実装したことがない・・・ 仕事でKotlinばっかり書いてるので、OCamlを書きたかった Schemeは昔から実装してみたかった という、やってみたかった駆動開発です。SchemeはSICPとかでもサブセットの実装を行ったりしているらしく、またscheme/lispでの実装例が結構多いです。これは、scheme/lispをそのまま使う場合、めんどくさいread/parserの部分が全部ないし一部をそのまま流用できるため、実装の総量が減り、また見通しが良くなりやすい、という理由っぽいです。 実装の参考にしている資料 昔探したとき、なんかいろいろあったよなー・・・と思いながら探していたら、こんなのを見つけました。 https://www.cs.utexas.edu/ftp/garbage/cs345/schintro-v13/schintro_toc.html 本当に最小限の実装から、lambdaやmacroの実装、compilerの実装とかまで進んでいくようで、結構分量が多いです。ちなみにこの資料だと、schemeでschemeするタイプなので、OCamlで実装する場合は前提になっているいろいろなものを事前に実装する必要があったりします。 OCamlで実装してみてよかったこと・悪かったこと JSONやES5のlexer/parserくらいは実装したことがありますが、実際にOCamlで言語を実装したのは初めてでした。とりあえず動く、というところまでやってみて、OCamlで書いた感想を挙げてみます。 Pros/Cons 内容 Pros lexer/parserの実装からASTを組み上げるのが楽 Pros パターンマッチがeval時にやはり強力 Cons schemeのcons listをパターンマッチだけでやるとこれはこれでめんどくさい。OCamlのlistに変換とかできるけど、それを毎回やるとそれはそれで重くなりそう Pros monadicな実装が簡単(まだちゃんとやっていないけど) Cons 引数のリストを組み上げるときとかがめんどくさい 概ね、schemeで超多用されるlistをOCaml内で扱うのがめんどくさい・・・、という感想です。どこかで見ましたが、最低限のspecial formとmacro/syntax-ruleなどを実装したら、後はschemeだけで実装していくのがやはり楽なのでは・・・?と思ってしまいます。もちろん、scheme自体では実装できないもの(比較とか)はprimitiveで実装しないとなりませんが。 ただ、OCamlで組み込み関数を実装するのは、Cとかで実装するより楽です。型のチェックでパターンマッチを使うだけで済むので、他の関数を呼び出す必要もありませんし。 これから実装していく方向 現在、schemeの式はすべて (data, string) result という型になるようにしています。ただ、例外とかを考えると、もうちょっとちゃんとしたmonadになるようにしてあげた方がいいかな?とは思っています。エラーの種類とかもほしいので。 まだmacro/syntax-ruleとかの実装が全くできていないので、ここを早めに実装していこうかなー、と思っています。 言語実装でOCamlはオススメです もともと関数型言語(Haskell/Lispとか)は、言語実装に向いている言語です。OCamlはReasonML(リブランドされてReScriptになるようですが)などの実装自体でも利用されていますし、以外なところで使われていたりします。 また、OCamlはMenhirという強力なパーサージェネレータがあったり、ocamllexというlexerジェネレータが組込だったり(ocamllexにもより強力な代替があったりします)と、手軽にlexer/parserを実装する環境があります。 OCamlに興味があるけどなー・・・、という方は、簡単な言語実装などしてみちゃーいかがでしょうか。楽しい?よ?

January 24, 2021 · derui

GitHub Actionsでrelease processを作ってみた

私用でいろいろ忙しく、部屋の中がとっ散らかってしまっています。いらないものを整理していくのは大事・・・。 さて、今回はちょっと前につくった、GitHub Actionsを使ったリリースプロセスについてちょこっと書いてみます。 <!–more–> GitHub Actionsとは https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions 内容については、公式が詳しいのでそっちで。すげー簡単に言うと、GitHubに統合されたCodeBuildみたいな感じです。 GitHubがMicrosoftに買収されたことが影響しているのか、GitHub Actionsの中身はAzureの仮想マシンを使っているようです。公式ドキュメントに書いていたかと。 GitHub上にあることの利点としては、リポジトリとかの認証がいらず、かつソース上の設定とめっちゃ近いので、コンテキストの切り替えコストが低くなります。 GitHub Actionsの基本 自分の理解を試すために、ちょっとだけ基本的な概念だけ書いておきます。 Actionsは、 Event 、 Runner というランタイム的なものと、 Job > Step > Action という階層構造の定義があります。 このEvent/Runner/Jobを合わせて、 Workflow と呼ばれます。Github Actionsは、このWorkflowという単位で定義を作ります。 また、 Job はデフォルトで並列で動作するようになっています。(なので、これを忘れて上から動くような感じで書いてしまうと、全部一気に動いて大体エラーになる、ということになります) 作ったrelease flow https://github.com/derui/sxfiler/tree/master/.github/workflows こういうのを作りました。tagをpushするといろいろビルドして、最終的にGitHub Releaseを作成するようなフローになっています。 詳細は中身を見てほしいんですが、いくつかハマりポイントがありました。 Jobごとに異なる環境で動く ちゃんとドキュメントを読めば書いているんですが、jobは各々 完全に独立した環境 で動作します。CodeBuildとかとは設定のレベルが違うので、割と混乱しました。 完全に独立した環境、なので、各々の環境でcloneしてこないとなりません。これもしばらくなんでやろ・・・?と思ってしまった感じでした。あんまりjobを複数使ったり、というのは、シンプルなOSSとかだと無いと思います。 Dockerは普通に使える 各環境は単独の仮想マシンなので、普通にDockerが使えます。性能も意外と悪くない(2vCPU/7GiB)です。 ただ、私の作ったDockerfileみたいに、めっちゃ重いDockerfileを使う場合は、セオリー通りDockerHubとかにbuild済みのイメージをpushしておくのが良いです。 job間のファイルやり取りはartifactで job間は特に関連がないので、個々のJobで出来たものをまとめたりする場合、artifactを使う必要があります。 Releaseを作って、アセットをアップロードする場合は、大抵1つのJobにまとめられると思います。こういう場合はartifactを使う必要があります。 cacheはjob毎 cacheを利用する場合、基本的にはGitHub公式のActionを使うと思います。このcache、 Job毎 の定義になったりするみたいでしたので、複数のJobでキャッシュを使いたい場合はこの辺を注意する必要がありそうです。 使える場面は使っていきたい 企業で使う場合は色々な事情があったり、一極集中を避けようとCircleCIとかを使ったり、というのはあると思います。が、OSSとか個人開発とかで、そういったこだわりがない場合は、GitHub Releaseとかの連携がスムーズ(当たり前)だったり、並列で動かしても特にペナルティが無かったりと、結構優遇されています。 使ったことがない場合は一度試してみると、色々発見があると思います。ぜひ。

November 8, 2020 · derui

ctypesをduneで使っていく方法

気づけば10月、今年ももう3ヶ月を切っていることにびっくりです。 今回は、OMakeをビルドシステムとして使っていたツールをdune対応した時に困ったことがあったので、それについて書こうと思います。 <!–more–> 発端 https://github.com/derui/okeyfum このリポジトリですが、6年くらい前にノリだけで作ったツールです。もともとは OMake というビルドシステムを使っていました。しかし、OMakeが事実上の開発休止になり、このリポジトリ以外ではocamlbuildを使っていたりしました。そして現代は、事実上dune一択状態になりました。 そこで、暇を見つけてdune対応しようとしたとき、ctypesを使っていたために、色々とビルドが通らないようになってしまいました。 ctypesとは ちょっと脱線して、OCamlにおけるctypesというライブラリについて紹介しておきます。 https://github.com/ocamllabs/ocaml-ctypes どういうライブラリかは、最初の一文を見れば大体わかります。 ctypes is a library for binding to C libraries using pure OCaml. The primary aim is to make writing C extensions as straightforward as possible. The core of ctypes is a set of combinators for describing the structure of C types – numeric types, arrays, pointers, structs, unions and functions. You can use these combinators to describe the types of the functions that you want to call, then bind directly to those functions – all without writing or generating any C! ...

October 7, 2020 · derui

OCaml製プログラムでperformance profileをする

OCamlで作ったソフトウェアをチューニングしようとprofilingしようとしたら、4.09.0で gprof 対応が削除されていました。 https://github.com/ocaml/ocaml/pull/2314 これはこれで困ったので、Linuxでのprofiling方法を調べたのでメモります。 ...

September 22, 2020 · derui