jparsec入門
の編集
Top
/ jparsec入門
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
10の質問
ABC予想
AI
AI API
AI プロンプト
AIでつかわれているtransformerのまとめ
AIの話題
ANTLR
ANTLR v3 FAQ よくある質問
ANTLR 独学
ANTLR4 独学
ANTLRでOracleのDDLを解析してみる
ANTLRチュートリアル
AOP
API
ARMマイコン基盤
ATOM SHELL理論
Access VBAメモ
Access-Control-Allow-Origin
AndroidとTensorflow
Android開発
Android開発 入門
AngularJS
Anko
Apache Bench
ArchUnitを学ぶ
Axiosとは
Axis2
BI Publisherで始めるデータ駆動型レポート作成
BPMNの勉強
BackTrack4
Blog from iPhone
Bootstrapとは
BracketName
C3 AI Applications
C3 AI エクスマキナ
CSS備忘録
CentOS
ChatGPTの話題
Chevrotainのパーサメソッド
Chevrotain一覧
Chromeエクステンション
Cocoa Touch Static Library
CoffeeScript
Confluent Control Centerやってみる
C言語でオブジェクト志向な記述方法
DDD ドメイン駆動設計
DDL生成ツール
DJUnit
DMM.comのAPIとか
DOSコマンドメモ
Dashcode
DeepFloyd IF
Dockerが動かない場合の対処
ES2015
Eclipse Monkey
Eclipse Plugin
Eclipseの色設定
Eclipse使いがXCode使い初めて知りたいこと
ElasticMQメモ
Elixir
Emmet
Erlangメモ
ExcelファイルをAIに読ませる
Exceptionを見やすく
Expression Tree
FLEX
FLEX リフレクション
Fisheye
FlashやJavascriptを使った演出
FormattingRules
FrontPage
GAE
GAE Data Store API
GENERAL SQL PARSER JAVA を試してみる
GLOBAL
GPT4ALL
GQL
GUIからMacPortsを管理するアプリケーション - Porticus
Generative Adversarial Networks
Gin JavaScriptで構文解析
Git Blame
GitHubアクションを使ったトロイの木馬のまとめ
GitLab
GitLabRunnerを増やす
GitLabでPlantUML使ってみる
GitLabでプロジェクト管理する
GitLabの機能をそのまま使って認証システム作ったらどこまでできる?
GitLabサーバインストールとメンテ注意事項
GitとAntとSpringとJUnit
Google Cloud Platform
Googleの裏技
Google認定プロジェクトマネージャの勉強メモ
Grails
GraphQL
HTM 階層型時間メモリ
HTML スクレイピング
HTML パース
HTML5
HTML5 Canvas
Hadoop
Help
If Then Maybe プログラミング
Inkscape script
InterWiki
InterWikiName
InterWikiSandBox
JAVAの記事一覧
JBoss
JDBC テーブル一覧を得る
JDBC カラム一覧を得る
JDT eclipse
JGRIB
JHIPSTER JDL
JHIPSTER OpenAPI
JHIPSTER エンティティをフィルタリングする
JHIPSTER6.1.2
JHIPSTERでスマホサイト
JHIPSTERのBLUEPRINTを作る
JHIPSTER一覧
JHipster
JHipster API FirstDepelop
JHipster エンティティを更新する
JHipster7をつかってみる
JHipsterでBuleprintを使いこなす
JHipsterのコード生成を改造
JHipsterのプロジェクトをGitLabでCI/CDする
JHipsterのプロジェクトをデプロイする
JMeter
JOOQとは
JSFとStruts
JSqlParser
Java Closure
Java Compiler API
Java Function
Java SQL Parserを調査する
Java Spring AOP
Java Spriteを設計してみる
Java オブジェクトのダンプ
Java ドラックできる曲線
Java 備忘録
Java 文字化け
Java11以降のJRE
Java7サンプルコード
JavaFx
JavaScriptでパーサを作る Chevrotain
Javaasist 動的にクラスを編集
Javascript グラフィックライブラリ
Javascript コーディングパターン
Javascript界隈
Javassist
JavaでSVG
Javaで関数型で引数をとる
JavaのジェネリクスTip
Javaのラムダ式
Javaの有名なライブラリ紹介
Javaは、IDEのテンプレートを使いこなせばいいよ
Javaプログラマ向けモナド
Javaメモリリーク
Jenkins
Jenkins(Hudson)メモ
Jestとは
Jhipsterマイグレーション
Json Yaml Xml Hash Scala
Kafka REST Proxy さわってみる
Kotolin
LDAPサーバをdockerで立ち上げる
Linux メモ
LiquiBaseとは
Lombok
MDBをコンパクトにするVBA
MQL5 小作品
MT4
MT5 EA
MYSQL
MacTool
Macにしゃべらせる
Mac用のメモ
Mattermostを使ってオンプレミスでチャット環境を作る
Maven
Mementoパターン
MenuBar
NILScript
NetBeanでプロファイル
Network Service Desk Engineer
Node-RED
Node-Red
NumPy
OQL オブジェクト問い合わせ言語
OSコマンドインジェクション
ObjctiveC サウンド
ObjectMapperの備忘録
ObjectiveC NSString
ObjectiveC サーバ
ObjectiveC ターミナル用コマンドを作る
ObjectiveC バックグラウンド
ObjectiveC ワーニング
Oculusアプリの開発
OpenFeint
OpenOffice
OpenResty
Outlook VBA
PHP
POSTGRESQL
Pandas Python Data Analysis Library
PdfBox Java用PDFライブラリ
Plagger
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
PyHipster
QuartzCore
RAD
REST
RWKV
Rails3
Railsと差分開発についての考察
React.js
React.js モーダル画面
RecentDeleted
RedmineLE
Redshift
Relumeでサイトの骨格を作る
Require.js
Rubycocoa
RubyでScalaをコンパイルするツールをつくる
Rubyアソシエーション認定証
Ruby入門
SCALA REPL
SCALA support tool
SCALAの記事一覧
SELinux
SEO
SEO Yahoo対策
SEO対策一覧
SPAM対策
SQLite
SSH
SVNをJavaで操作
SakuraZencoding
SandBox
Scala / Hadoop
Scala Process exec
Scala 遅延評価
Scala/LiftでSlim3
ScalaSigParser
ScalaWithExcel
Scala チュートリアル
Scalaで3D
ScalaでLisp
ScalaとGroovyのPOJO比較
ScalaのIDEについて
Scala言語を学ぶやさしいツール「Kojo」
Slack API やってみる
SocketAppender
Spring
Spring bootでのテストのTIPS
SpringBootのSTSの新規プロジェクトでるエラーの対応
SpringSecurity SAML
Sqlite
Squirrel
StringTemplate
Stringクラス拡張
TALEND
ThreadLocal
Todo一覧
Trac Lightning
Twitter
UltraEdit
Unityでシューティングゲーム作る際のメモ
VBAでREST通信
VBAのコード
VBAをOpenOffice.org Basicにする
VBAをOpenOffice.org+Basicにする
VPN構築の勉強メモ
VPSやIaaSメモ
VSCodeでRuby開発
VSCodeメモ
VSCode用ChatGptのPlugin
VSCode設定
VirtualBox On Mac
Visual Studio Code プラグイン開発
Vuexとは
WBS管理の弊害
WIN32API
WSDL
Watson
WebDesign探訪
WebLogic フィルタ
WikiEngines
WikiName
WikiWikiWeb
Windows10のPowerShell でキーボードの言語切り替え
WindowsTool
Worker Thread パターン
XBee
XDOCLET
XForms
XPath
XSL
YahooPIPES
Yahooインフォセンター
Yet Another Pragger
YouTuber
YukiWiki
anacondaをcygwinで使う
ansible
antlr snippet
antlr 再入門
antlrと日本語
autoit
automator
bluemix
bootstrap2
bower
ccze Colorize log files on CentOS and Ubuntu using ccze tool
centos7
cglibを使って動的コード生成
cocos2d
cygwin
diff
dockerのローカルイメージをDocker-in-Dockerで参照する
eclipse設定
emacs 備忘録
emacs 文字列置換
emacsをviライクにする
excel tips
excelのdiff
expectで自動化
figmaにプラグインをインストールする
ftp自動化
gemini
generator-jhipster-gql
git diffを使った構成管理の省力化
goをやってみる
grizzly
gulp
homebrew
iPhone Bluetoothプログラミング
iPhone iAd
iPhone 実機テスト手続き
iPhoneでグラフィックのHellowWorld
iPhoneとGmailメール
iPhoneに実機転送
iPhoneプログラミング
iPhoneプログラミング/ビューを理解すればiPhoneアプリの基礎を押さえられる
iPhoneプログラミング一覧
iPhoneプログラミング入門
iPhone開発/Interface Builder Plug-in
iPhone開発/キャプチャの取り方
intra-mart
jQuery.Flickableのメモ
java spring boot 認証 memo
jersey
jhipster-codeにアノテーション追加してみる
jhipsterのテンプレート改造準備
jparsecドキュメント日本語訳
jparsec入門
kafkaの勉強
log4j2の脆弱性
mac diff
mailcowのインストール
memcached
minecraft マイクラ あるきながら、高速ダンジョン作成
mqttの勉強
nginx_lua
nginxのメモ
node_moduleをnpm linkを使って自分用にする
npm
openapi generator
openapi-generatorをコンパイル
openstack
oraclerac
play framework 1.2.5 sample
play! framework
play!framework selenium
playframework テンプレート
postmanとopenapi
prezi プレゼン
pukiwikiで行動管理
pukiwikiに類似したツール
pukiwiki勉強
pukiwiki記事一覧
python
python3のwindowsでの日本語文字化け対応
pythonでseleniumを使う
pythonのテストに使うライブラリ
rails5
reactでポップアップ表示
redmine
ruby on rails 6.0.0
scala
scala 99problem 32~
scala prototype.zip
scala repl
scala sbaz
scala spring
scala/インストール
scalaでまだ不勉強なところ
scalaのインストール
selenium
slack api
spark
spring boot
spring initializerをつかってプロジェクトのひな型をゲットする
spring-test
springboot
springboot env
storybook
sublimetext2
swagger
tracについて
ubuntu
vaadin
vue を typescriptで開発
vue 共通部品作成
vue.js memo
vue.jsとは
vue.jsのデバッグ
vue一覧
webの編集画面のよくあるパターン
windows環境構築
wordpress
xamppについて
•Axis2の本家のスタートガイドによるWebサービスの作り方
【Javascript】【CLIライブラリ】commanderの勉強
【MQL5】KuniRangeBreakoutEA
いまさらながらC++
びっくりする短いコード
アクター
アニメーション
アノテーション
アプリコット
アプリコット PukiWiki
アプリコード
アプリコード林邦行
イラストのエフェクト
インテンショナルプログラミング
カスタマイズjhipster7.9.3イメージ
カブロボ
ガイガーカウンター
クラスとハッシュマップの関係
クラック対策
クロス集計
コマンドラインという概念への考察
コミニュケーション
コード生成
サロゲートキーを使ったテーブル設計
シェルのサンプル
シェルサンプル
スクレイピング
スレッドプログラミングメモ
ソースtoソース変形
ターミナルをAppleScriptで制御
テキストエディタ作成javascriptフレームワーク
テスト用まっさらDBをdockerでたてる
テスト駆動
テレワーク環境の比較
ドット絵
バイオビルダー合成生物学メモ
バグの少ない設計のためのValueObject
パフォーマンスチューニング
フロントエンドのテストの結合テストを減らすには?
プッシュ技術
プログラマーじゃない人に覚えてほしいプログラムのコメントの書き方
プロジェクト管理スプレッドシート
マイクラ 有名ディメンション モッド
マクスウェル方程式
メタ
ラムダ計算について考える
リベリカJava13いいみたい
リモートワークでのプロジェクト注意点
レイアウトツール
ログ解析
世界の構文解析グラマーたち
予定表
予定表/2009-12-14
予定表/2009-12-18
予定表/2009-12-19
予定表/2009-12-22
予定表/2009-12-23
予定表/2009-12-24
事業の心構え
事業計画方針
人工知能とCUDA
人工知能コンペKaggle
仕様書のフォーマットについての考察
他言語サイトサンプル作成
仮説Oracleの罠
作曲と効果音作り
共和分
口コミ
古いRails5を入れる
哲学
大文字小文字変換
学習をHackする
扶養とシステム
投薬のみのガンの治療薬
擬似コーディングのすすめ
放射能対策
数式を扱う
文章を書く
新エネルギー
新年の抱負2010
新技術 プログラム編
日本のゼネコン式IT開発が失敗する理由
最近更新したページ
未来技術/新技術
枯れた技術の水平思考
株価データ
業界の動向
構文解析の記事一覧
正規表現
気象データ
流れるようなインタフェース
管理画面の生成におけるopenapiとJDLなどの考察
細胞の若返り
経済のことをまとめてみる
脆弱性
自分でPlaggerみたいなのを作るためのメモ
論語/学而第一
負荷テスト
販売/デスクトップPC
販売/ノートパソコン
販売/外部ストレージ
起業
酸化グラフェン
開発哲学
電子出版
電子出版の記事一覧
非可換幾何学
顧客分析のデシル分析とRFM分析
DIコンテナについて考える
MP3から携帯着うたを作る方法
[[構文解析の記事一覧]] *目次 [#yca86807] #contents *jparsec [#e9851712] http://jparsec.codehaus.org/ パーサ生成フレームワーク YACCとの違いは外部ファイルを必要としない点が違う。 Ruby版も存在しており、rparsecという。言語の先頭1文字をとって区別をつけている。 haskell版もあるがこちらが、元になっているので、こちらの名前はparsecという。 *チュートリアル [#sf63f7e4] http://jparsec.codehaus.org/jparsec2+Tutorial **日本語のjparsec使用ブログ [#x951ebb7] だいたいチュートリアルの和訳相当だと思っていいです。 ちょっとTermクラスの名前が変わっているので、少々古いのかもしれません。 http://d.hatena.ne.jp/taichitaichi/20071008/1191808121 *例 [#qc0d3069] 今朝みれてたのに、例に出していた記事が404になって見れなくなっていた、あわててgoogleのキャッシュから保存する。 I Hate Anonymous Classes **URL [#c041eb23] http://docs.codehaus.org/display/JPARSEC/I+Hate+Anonymous+Classes 404になって見れなくなっているかもしれん **本文 [#sa51b147] *Jparsecの核心のアイデアについて(原文)I Hate Anonyous Classes! [#v1db40ab] お察しのとおり, jparsec全般的に関数脳的な考え方に基づいております。 jparsecを使い始めたら,MapやMap2,Map3などなどの実装にでくわします。で、型を入れていくとかったるくなるんです。 例えば, 次のクラスがあったとします。 -Parser<A>, -Parser<B> -Parser<C> さらに、それらを順次、実行させ結果を使って、クラスDを作りたいとします。つまりこんな感じの時です、 Parser<D> d = Parsers.sequence(a, b, c, new Map3<A, B, C, D>() { public D map(A a, B b, C c) { return new D(a, b, c); } }); まあ、そんなに悪くはないコードですよね?で、このAに具体的なクラス名を当てはめてかんがえてみますよ。 |A|UnbelievableGadget<Ipod>| |B|IncredibleCartoon<Panda<KungFu>> | |C|ViciouslyBeautiful<KingKong>| 代入すると次のコードになるよね。 Parser<D> d = Parsers.sequence(a, b, c, new Map3<UnbelievableGadget<Ipod>, IncredibleCartoon<Panda<KungFu>>, ViciouslyBeautiful<KingKong>, D>() { public D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) { return new D(ipod, panda, kingkong); } }); 使いやすくなったと思うかな? **JavaでHaskell的なコードが書けるMapperクラス [#r2d724a4] というわけで、こんな時にはRubyのような動的言語を使っている場合だと、そんなに非常識な記述じゃないんだよね。こんな感じ d = sequence(a, b, c) do |ipod, panda, kingkong| new D(ipod, panda, kingkong); end さらに関数言語オタク御用達のHaskellだと次のようにかけちゃうんです。 d = sequence a b c D ギャー、簡潔すぎる。えっ何?あなたはJavaプログラマーだって? あなたが自暴自棄になってやけを起こすまえにるまえに、まってください。まだ望みはあります。コーディング野郎どもは、Javaでも等価なコードが書けるものを作っていたんですよ! そんなあなたのためにこのMapperクラスをご用意致しました!!。ルビー風に記述するとこんな具合です。 Parser<D> d = new Mapper<D>() { D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) { return new D(ipod, panda, kingkong); } }.sequence(a, b, c); あれ?、「言っていること違うんじゃね?そこらじゅうにブラケットだらけじねぇかよ。!」だって? JAVA プログラマーよ、そんなに嘆くな。もう一個おもしろいものがあるんだぜ。 そいつは、”curry"っていうんだ。 Parser<D> d = Mapper.curry(D.class).sequence(a, b, c); このcurry()メソッドっていうのは、カリー化のための引数をとる。それはなにかっていうと、たとえば、 構文解析する前に、クラスDのコンストラクターがわかっていたとするよね?さらに、Dクラスかどうかで、判定したい場合は、次のように記述します。 Parser<D> d = Mapper.curry(D.class, true).sequence(a, b, c); **カリー化 演算子の例 [#z5b6af9d] A real example is to parse the Java ternary "?:" operator. let's first assume that the conditional expression is modeled as: public class ConditionaExpression implements Expression { // ... public ConditionalExpression(Expression cond, Expression consequence, Expression alternative) { // ... } } 注意深く見てくれよ。"? consequence表現 :" の箇所は右側に2つの演算の指示(右結合のバイナリー演算子ともいう)を持っている. どんな表現も consequence表現になるが, "?:"は、cond表現よりも緊密にalternative表現に絡んでいる。 "?:"を右結合のバイナリー演算子として宣言するためには、僕らは、次のようなパーサを作んなきゃならない。 それは、 ”?”と”:”の間のconsequence表現の構文解析器だよね。 んでもって、この構文解析器の戻り値は Map2 になるわけでさらに左側の演算指示箇所と右側の演算指示箇所を conditional表現に変換しなくてはなりません。ちょっとみてもらうとこんな具合です。: static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Parsers.between(terminals.token("?"), consequence, terminals.token(":")).map(new Map<Expression, Binary<Expression>>() { public Binary<Expression> map(final Expression consequenceExpr) { return new Binary<Expression>() { public Expression map(Expression condExpr, Expression alternativeExpr) { return new ConditionalExpression(condExpr, consequenceExpr, alternativeExpr); } }; } }; } 長いですね。複雑ですね。じっくり見ていただくためここで5分待ちましょうか。 (5分経過) よーし、見ていただけたでしょうか 戻り値の Parser<Binary<Expression>> ってOperatorTable内で以下のようにつかわれてます。 Parser.Reference<Expression> ref = Parser.newReference(); Parser<Expression> expression = new OperatorTable<Expression>() .prefix(...) .postfix(...) .infixr(conditionalOperator(ref.lazy()), 50) ....; ref.set(expression); 私が本当に驚きをもって、いま、あなたにお見せしたいのは、じゃまな、匿名クラスの記述なしに、同等のことをMapperクラスをつかうことでできるということなんです。 次のようになるんですよ。 static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Mapper.<Expression>curry(ConditionalExpression.class).infix(consequence.between(terminals.token("?"), terminals.token(":"))); } このコードでさっきのめんどくさくてご立派なコードと同等のコードとなります。 そしてさらに _メソッドを用意しておりまして、これを使うと、 "?" や":" の演算子の戻り値を気にせずに、直感的な記述になります。 import static org.codehaus.jparsec.misc.Mapper._; static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Mapper.<Expression>curry(ConditionalExpression.class).infix(_(terminals.token("?")), consequence, _(terminals.token(":"))); } **おわり [#q129725e] Mapperクラスの狡猾な使い方としては、あなたがまともであれば、cglibを使うともっと便利になるかもしれません。 以上、日本語に適当に訳してみた。 つづいて、別の資料について解説を試みる *尖がった括弧の解析 原文 That Pointy Brackets [#j2318e29] **>>が書けない問題 [#k409af8b] C++では, 入れ子状のテンプレートには(まだ、やってんのかわかんないけど) 特殊な文法 があって、そいつのせいで次の記述が書けなくなっている。 "Foo<Bar<int>>" 何でかっていうと ">>" って右シフト演算子なのであって、">"が2つ並んだ演算子ではないってことなのさ。すでに複雑になっている文法がよりいっそう複雑になっちゃいますねぇ。 でも、Javaにはこんな制限事項ってないんです。 It is still the job of the parser to properly disambiguate ">>" in expression from ">>" in nested generic types. One possible solution is to muck around with the grammar so that ">>" is used in both generics and expression. The cost is that multiple not-so-intuitive production rules need to be introduced to work around the ambiguity. **jparsecでの解決方法 [#mfde038b] Jparsec provides a simpler solution (check out the Java parser sample in the source code). In lexical analysis phase, "<" and ">" characters are uniformly tokenized individually. There will be no token for "<<", "<<<", ">>", or ">>>". Thus in parsing generic types, the parser will not be confused by any ">>" tokens. ***For example: [#o076df9c] private static final String[] OPERATORS = { "+", "-", "*", "/", ">", "<", ">=", "<=", // and all other operators. }; private static final String[] KEYWORDS = { "interface", "class", "enum", "private", "public", "protected", // all other operators }; private static final Terminals TERMINALS = Terminals.caseSensitive(OPERATORS, KEYWORDS); //... Now that makes it real simple for parsing nested generic types with the pointy brackets. But what about the "<<" and ">>" operators used in expression? What happens is that lexical analysis phase has no idea of what context the current token is in, but in syntactical analysis we know whether we are parsing a generic type or an expression. In the latter case, we will treat three adjacent ">" tokens as one single ">>>" operator, and two adjacent ">" tokens as one single ">>" operator. By adjacent, I mean that they have to be next to each other in the original source, if the first ">" character appears at line 7 column 6, the next one has to be at line 7, column 7. In other words, their physical indexes in the original source are adjacent. Luckily, the Token class carries the physical index in the original source. By using the next() combinator, we can specially handle the "adjacency": public static Parser<Token> adjacent(Parser<List<Token>> parser, final String name) { return parser.next(new Map<List<Token>, Parser<Object>>() { public Parser<Object> map(List<Token> tokens) { if (tokens.isEmpty()) return Parsers.always(); int offset = tokens.get(0).index(); for (Token token : tokens) { if (token.index() != offset) { return Parsers.expect(name); } offset += token.length(); } return Parsers.always(); } }).atomic().source().token(); } The above code checks that the list of tokens returned by a parser are adjacent. And then we can use the list() combinator to turn a the special operator string to the parser that returns token list: public static Parser<Token> adjacent(String operator) { List<Parser<Token>> parsers = new ArrayList<Parser<Token>>(operator.length()); for (int i = 0; i < operator.length(); i++) { parsers.add(TERMINALS.token(Character.toString(operator.charAt(i)))); } return adjacent(Parsers.list(parsers), operator); } And by calling adjacent(">>>"), we get a parser that parses three adjacent ">" tokens. Everything else in the grammar can stay as simple as they should be. One catch is that we need to make sure ">>" is not a prefix of ">>>" so to get the parser for ">>" operator, we will need to do a little bit of tweaking as: static final Parser<?> UNSIGNED_RIGHT_SHIFT = adjacent(">>>"); static final Parser<?> RIGHT_SHIFT = UNSIGNED_RIGHT_SHIFT.not().next(adjacent(">>")); And that's about it. The same code can be applied to "<<" and "<<<". Labels parameters Labels Enter labels to add to this page: Please wait Looking for a label? Just start typing. つづいて、別の資料について解説を試みる 対象はソースをダウンロードしてくると付いてくるサンプルコードについてだ。 *SQLの解析サンプルについて [#of950202] jparsecをダウンロードし、解凍すると [jparsec-2.0_src]-[examples]-[src]-[org]-[codehaus]-[jparsec]-[examples]-[sql] がある。 **Eclipseに取り込む手順 [#o49a3671] jparsecからダウンロードしてきたファイルを解凍しておきます。 junitのjarファイルも手元になければ、ダウンロードしてきます。 ダウンロードしてきたjunitはjunit-4.18.jarとかバージョン名がついているので、 junit.jarという名前にかえておきます。 junitはjparsecのlibフォルダに格納しておきます。 では、eclipseがわの準備を行ってみましょう。 Eclipseに新規にJavaプロジェクトを作成します。 ファイルメニューのインポートで先ほど解凍してできたフォルダを選択し、それをプロジェクトのsrcディレクトリを指定してとりこみます。 srcフォルダは、4つあります、本体用、本体test用、example用、exampleテスト用 インポート直後は まだ、プロジェクトのビルドパスにjarが登録されていませんので、コンパイルエラーになっています。 そこで、ビルドパスの設定でparsecのlibフォルダ内のjarをすべて登録します。 コンパイルエラー表示はほぼ消えます。 が、1カ所だけAllTestクラスでエラーになっています。 それは、作者がライブラリをあげたくないからだと build.xmlの80行目に明記してありました。 こんな感じ、 AllTests uses jtc, which is an extra dependency that I don't want to upload. おそらく、Androidのソースを流用したコードだから、著作権の問題であげれないとでも思ったのでしょうか。 それはさておき、 build.xmlには、このクラスのみ除外してコンパイルする記述がありました。 要するにいらないんです。この だからbuild.xmlをいじくりたくなかったので、つぎのようにクラスを書き換えておきました。 package org.codehaus.jparsec; //import org.openqa.jtc.junit.TestSuiteBuilder; import junit.framework.TestSuite; /** * * @author benyu */ public class AllTests extends TestSuite { public static TestSuite suite() { //return TestSuiteBuilder.suite(AllTests.class); return null; } } **コンパイル方法 [#v14b1801] build.xmlがあるので、toolは、ソースが公開されていないみたいなので、開発元の方用のantタスクかもしれません。それ以外はコンパイルできました。 **exampleにあるSQLパーサの使い方 [#ded7b6e6] exampleのテストケースをみると使い方が書いてありました。 ***どこに書いてあるかというと [#fae6fde4] ***パッケージ名: [#n1f6c4af] package org.codehaus.jparsec.examples.sql.parser; ***クラス名: [#i643a3ac] RelationParserTest ***メソッド名: [#s819af7a] public void testSelect() { ***内容の抜粋: [#p5e84e0f] SQLの問い合わせ文 select distinct 1, 2 as id from t1, t2 が下記のようにクラスの構造に解析されているのを確認しているテストコードが書かれていました。 Parser<Relation> parser = RelationParser.select(NUMBER, NUMBER, TABLE); assertParser(parser, "select distinct 1, 2 as id from t1, t2", new Select(true, Arrays.asList(new Projection(number(1), null), new Projection(number(2), "id")), Arrays.asList(table("t1"), table("t2")), null, null, null)); *persecのよみもの [#g2d334d4] **Parsec, 高速なコンビネータパーサ [#rf72fbda] 文字コードをEUCにしないと文字化けします。 http://www.lab2.kuis.kyoto-u.ac.jp/~hanatani/tmp/Parsec.html *俺的チュートリアルを書いてみる [#la10fd7b] 実は、jparsecのソースコードをダウンロードすると、計算機のサンプルコードが入っていて、 これがチュートリアルで解説してあるようなコードよりもすっきりさわやかなコードなのだ。 だから、このサンプルから逆に構築する手順を、観察力+妄想力で、作り、俺的チュートリアルをつくるのだ!。それが、漢ってもんだろ。 **ゴール [#k0421c8f] はっきりとしたゴールがあるってことは、それだけでも、しあわせなことなのさ。 まずは、四則演算の構文解析の作成方法を理解できるようにする。 四則演算って、いろいろな言語の基本的機能だから、構文解析のHelloWold的存在かとは思うけど、 言語を解析するには、数学でいうところの必要条件にすぎない。 ちなみに、下記のコードを作れるような手順をコードから逆に作ってみることを目標にします。 その後、BNFの解析なども行います。 /** * The main calculator parser. * * @author Ben Yu */ public final class Calculator { /** Parsers {@code source} and evaluates to an {@link Integer}. */ public static int evaluate(String source) { return parser().parse(source); } static final Parser<Integer> NUMBER = Scanners.INTEGER.map(new Map<String, Integer>() { public Integer map(String text) { return Integer.valueOf(text); } }); static final Binary<Integer> PLUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a + b; } }; static final Binary<Integer> MINUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a - b; } }; static final Binary<Integer> MUL = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a * b; } }; static final Binary<Integer> DIV = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a / b; } }; static final Binary<Integer> MOD = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a % b; } }; static final Unary<Integer> NEG = new Unary<Integer>() { public Integer map(Integer i) { return -i; } }; private static <T> Parser<T> op(char ch, T value) { return isChar(ch).retn(value); } static Parser<Integer> parser() { Parser.Reference<Integer> ref = Parser.newReference(); Parser<Integer> term = ref.lazy().between(isChar('('), isChar(')')).or(NUMBER); Parser<Integer> parser = new OperatorTable<Integer>() .prefix(op('-', NEG), 100) .infixl(op('+', PLUS), 10) .infixl(op('-', MINUS), 10) .infixl(op('*', MUL), 20) .infixl(op('/', DIV), 20) .infixl(op('%', MOD), 20) .build(term); ref.set(parser); return parser; } } *大まかな概念 [#qe9b8e5b] 解析するものは、括弧などで括られている箇所はparenと命名する。 中身をExpressionと命名する。 *Jparsecのサンプルから説明書を作ってみる。 [#z9c4734a] **パッケージ構成 [#m99766da] まずパーサにフォルダとしてastフォルダとpaserフォルダを作ります。 astフォルダは構文を解析した結果を保存しておくクラスです。 ***astのクラスについて [#g15aed37] astに格納するクラスの大部分は ValueObjectクラスを継承しており、それ以外の残りのクラスはenum型やInterfaceです。 ***Interface [#d89592c1] Interfaceには、これから解析しようとする対象を意味する名前だけでもいいので、シンプルな インタフェースを用意します。 このInterfaceは、構文木の末端のクラス名を意味するようにします。 **paserパッケージのクラスの作成 [#y885539b] 入れ物であるクラスの定義が終われば、今度はpaserパッケージのクラスの作成をします。 大雑把に言って入れ物を作って次の順番でクラスを構築していく感じです。 全体的な入れ物は Parser.Reference<Integer> ref = Parser.newReference(); で定義します。 この文をサンプルで探すとメインの処理が見つかりますが、複雑な構文になってくると、ルールごとに部分部分で解析するため、 いくつか要所要所ででてきます。 たとえば、一番簡単な計算機のサンプルは、SQLのサンプルではExpressionの構文解析の一機能にすぎません。 (サンプルのarithmeticメソッド参照) **末端表現をパターンで記述 [#a19619d8] 対象を表す正規表現と入れ物として定義したクラスを頭の中でイメージします。(実装ではPatternクラスのメソッドで正規表現を表現します。) よく使うものであれば、 Scannersにはよく使うであろう正規表現のPatternクラスのインスタンスが登録されていますし、 Terminalsにもよく使うであろう物が登録されています。 Terminalsはcurryのsequenceメソッドの引数に使うような設計です。 Scannersクラスで定義されていないかどうか確認します。 Scannersクラスで定義されていない場合、演算子の場合はString[]型で private static final String[] OPERATORS = {"*", "+", "?", "|", "::=", "(", ")"}; と定義したとすると、 private static final Terminals TERMS = Terminals.operators(OPERATORS); に格納します。 これらは、ルール定義の際に、 Mapper._(TERMS.token(name)) 入れ物のクラスが決まれば、次の定型文で単位を定義できます。 static final Parser<Rule> LITERAL = Mapper.curry(LiteralRule.class).sequence(いまからせつめいする引数) **Patternの定義 正規表現に使う名前を決める [#xa25f797] Scanners.isPatternで正規表現をメソッドで置き換えたPatternクラスのインスタンスを登録します。 たとえば、正規表現の「+」はmany1となります。 -分割の場合は sepBy1 -複数条件のOR結合はor -間に挟む場合はbetween ただしどれも、引数には、Mapper._(TERMS.token(name))のnameに登録済みの文字列を入れたものを使います。 ***頻繁に使うであろうPatternクラス [#c7c08a6f] Scannersにはよく使うであろう正規表現のPatternクラスのインスタンスが登録されています。 **Pattern Patternsクラスで定義 [#h75daaee] Parser<_> Scanners.isPatternで定義 例:Parser<Integer> NUMBER Parser<Tok> Lexers.lexerで定義 **演算子の組み立て方 [#hb0a41b0] 基本的にmapというメソッドをオーバロードさせていきます。 必要なパラメータは、どんな記号なのか? その次に、何項演算子なのかでクラスが異なります。 2項演算子の場合はBinary<Integer>で、演算子は数値の真ん中に来るので、 OperatorTableの.infixlで登録 1項演算子の場合はUnary<Integer>で、演算子は数値の先頭にくるので、.prefixで登録します。 static final Unary<Integer> NEG = new Unary<Integer>() { public Integer map(Integer i) { return -i; } }; static final Binary<Integer> PLUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a + b; } }; Parser<Integer> parser = new OperatorTable<Integer>() .prefix(op('-', NEG), 100) .infixl(op('+', PLUS), 10) .build(term); **括弧を定義する場合 [#sff090ac] 昔はこんな感じでTermsクラスがあったみたいですが、 final Terms ops = Terms.getOperatorsInstance("+", "-", "*", "/", "(", ")"); いまは、 括弧の中身をすぐには評価しないので、lazyをつけ、対になっている「(」と「)」をbetweenで括ります。もし、その対でなければ、 下記のようにorで連結していきます。 Parser<Integer> term = ref.lazy().between(isChar('('), isChar(')')).or(NUMBER); で、OperatorTableの最後にbuildの引数として格納します。 **構文を定義する。 [#u8e80826] ***SELECT文の定義例 [#p48d3599] たとえばSELECT文を定義してみます。 構文には、単体で意味のある箇所と 条件文のように塊で意味のある箇所があります。 さらに括弧のようなもっと上の優先順位で評価される塊を表現する箇所があり、 構文解析ではこのような、構成要素の数でパーサを分類していきます。 引数の数でこの分類を観察すると、1,2,3とひとつづつ増えていっています。 **インデントとスペース、コメントなどの除去 [#c5147af5] 最後は、インデントとスペースを除去して解析するように記述する。 たとえば、このような感じである。 static <T> T parse(Parser<T> parser, String source) { return parser.from(INDENTATION.lexer(TOKENIZER, Indentation.WHITESPACES.or(COMMENT).many())) .parse(source); } ***素朴な疑問 [#h2637aaf] 作者はunionとか、なぜ注意深く定義できたのか? bnf SQL selectで検索してみた。 http://savage.net.au/SQL/sql-92.bnf.html あまり関係がないようだ。 自分で解析しているのかもしれない。 *delete文の解析機を作ってみる。 [#f89a5667] 未記入
spamではない場合はチェックをいれてください。
タイムスタンプを変更しない
[[構文解析の記事一覧]] *目次 [#yca86807] #contents *jparsec [#e9851712] http://jparsec.codehaus.org/ パーサ生成フレームワーク YACCとの違いは外部ファイルを必要としない点が違う。 Ruby版も存在しており、rparsecという。言語の先頭1文字をとって区別をつけている。 haskell版もあるがこちらが、元になっているので、こちらの名前はparsecという。 *チュートリアル [#sf63f7e4] http://jparsec.codehaus.org/jparsec2+Tutorial **日本語のjparsec使用ブログ [#x951ebb7] だいたいチュートリアルの和訳相当だと思っていいです。 ちょっとTermクラスの名前が変わっているので、少々古いのかもしれません。 http://d.hatena.ne.jp/taichitaichi/20071008/1191808121 *例 [#qc0d3069] 今朝みれてたのに、例に出していた記事が404になって見れなくなっていた、あわててgoogleのキャッシュから保存する。 I Hate Anonymous Classes **URL [#c041eb23] http://docs.codehaus.org/display/JPARSEC/I+Hate+Anonymous+Classes 404になって見れなくなっているかもしれん **本文 [#sa51b147] *Jparsecの核心のアイデアについて(原文)I Hate Anonyous Classes! [#v1db40ab] お察しのとおり, jparsec全般的に関数脳的な考え方に基づいております。 jparsecを使い始めたら,MapやMap2,Map3などなどの実装にでくわします。で、型を入れていくとかったるくなるんです。 例えば, 次のクラスがあったとします。 -Parser<A>, -Parser<B> -Parser<C> さらに、それらを順次、実行させ結果を使って、クラスDを作りたいとします。つまりこんな感じの時です、 Parser<D> d = Parsers.sequence(a, b, c, new Map3<A, B, C, D>() { public D map(A a, B b, C c) { return new D(a, b, c); } }); まあ、そんなに悪くはないコードですよね?で、このAに具体的なクラス名を当てはめてかんがえてみますよ。 |A|UnbelievableGadget<Ipod>| |B|IncredibleCartoon<Panda<KungFu>> | |C|ViciouslyBeautiful<KingKong>| 代入すると次のコードになるよね。 Parser<D> d = Parsers.sequence(a, b, c, new Map3<UnbelievableGadget<Ipod>, IncredibleCartoon<Panda<KungFu>>, ViciouslyBeautiful<KingKong>, D>() { public D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) { return new D(ipod, panda, kingkong); } }); 使いやすくなったと思うかな? **JavaでHaskell的なコードが書けるMapperクラス [#r2d724a4] というわけで、こんな時にはRubyのような動的言語を使っている場合だと、そんなに非常識な記述じゃないんだよね。こんな感じ d = sequence(a, b, c) do |ipod, panda, kingkong| new D(ipod, panda, kingkong); end さらに関数言語オタク御用達のHaskellだと次のようにかけちゃうんです。 d = sequence a b c D ギャー、簡潔すぎる。えっ何?あなたはJavaプログラマーだって? あなたが自暴自棄になってやけを起こすまえにるまえに、まってください。まだ望みはあります。コーディング野郎どもは、Javaでも等価なコードが書けるものを作っていたんですよ! そんなあなたのためにこのMapperクラスをご用意致しました!!。ルビー風に記述するとこんな具合です。 Parser<D> d = new Mapper<D>() { D map(UnbelievableGadget<Ipod> ipod, IncredibleCartoon<Panda<KungFu>> panda, ViciouslyBeautiful<KingKong> kingkong) { return new D(ipod, panda, kingkong); } }.sequence(a, b, c); あれ?、「言っていること違うんじゃね?そこらじゅうにブラケットだらけじねぇかよ。!」だって? JAVA プログラマーよ、そんなに嘆くな。もう一個おもしろいものがあるんだぜ。 そいつは、”curry"っていうんだ。 Parser<D> d = Mapper.curry(D.class).sequence(a, b, c); このcurry()メソッドっていうのは、カリー化のための引数をとる。それはなにかっていうと、たとえば、 構文解析する前に、クラスDのコンストラクターがわかっていたとするよね?さらに、Dクラスかどうかで、判定したい場合は、次のように記述します。 Parser<D> d = Mapper.curry(D.class, true).sequence(a, b, c); **カリー化 演算子の例 [#z5b6af9d] A real example is to parse the Java ternary "?:" operator. let's first assume that the conditional expression is modeled as: public class ConditionaExpression implements Expression { // ... public ConditionalExpression(Expression cond, Expression consequence, Expression alternative) { // ... } } 注意深く見てくれよ。"? consequence表現 :" の箇所は右側に2つの演算の指示(右結合のバイナリー演算子ともいう)を持っている. どんな表現も consequence表現になるが, "?:"は、cond表現よりも緊密にalternative表現に絡んでいる。 "?:"を右結合のバイナリー演算子として宣言するためには、僕らは、次のようなパーサを作んなきゃならない。 それは、 ”?”と”:”の間のconsequence表現の構文解析器だよね。 んでもって、この構文解析器の戻り値は Map2 になるわけでさらに左側の演算指示箇所と右側の演算指示箇所を conditional表現に変換しなくてはなりません。ちょっとみてもらうとこんな具合です。: static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Parsers.between(terminals.token("?"), consequence, terminals.token(":")).map(new Map<Expression, Binary<Expression>>() { public Binary<Expression> map(final Expression consequenceExpr) { return new Binary<Expression>() { public Expression map(Expression condExpr, Expression alternativeExpr) { return new ConditionalExpression(condExpr, consequenceExpr, alternativeExpr); } }; } }; } 長いですね。複雑ですね。じっくり見ていただくためここで5分待ちましょうか。 (5分経過) よーし、見ていただけたでしょうか 戻り値の Parser<Binary<Expression>> ってOperatorTable内で以下のようにつかわれてます。 Parser.Reference<Expression> ref = Parser.newReference(); Parser<Expression> expression = new OperatorTable<Expression>() .prefix(...) .postfix(...) .infixr(conditionalOperator(ref.lazy()), 50) ....; ref.set(expression); 私が本当に驚きをもって、いま、あなたにお見せしたいのは、じゃまな、匿名クラスの記述なしに、同等のことをMapperクラスをつかうことでできるということなんです。 次のようになるんですよ。 static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Mapper.<Expression>curry(ConditionalExpression.class).infix(consequence.between(terminals.token("?"), terminals.token(":"))); } このコードでさっきのめんどくさくてご立派なコードと同等のコードとなります。 そしてさらに _メソッドを用意しておりまして、これを使うと、 "?" や":" の演算子の戻り値を気にせずに、直感的な記述になります。 import static org.codehaus.jparsec.misc.Mapper._; static Parser<Binary<Expression>> conditionalOperator(Parser<Expression> consequence) { return Mapper.<Expression>curry(ConditionalExpression.class).infix(_(terminals.token("?")), consequence, _(terminals.token(":"))); } **おわり [#q129725e] Mapperクラスの狡猾な使い方としては、あなたがまともであれば、cglibを使うともっと便利になるかもしれません。 以上、日本語に適当に訳してみた。 つづいて、別の資料について解説を試みる *尖がった括弧の解析 原文 That Pointy Brackets [#j2318e29] **>>が書けない問題 [#k409af8b] C++では, 入れ子状のテンプレートには(まだ、やってんのかわかんないけど) 特殊な文法 があって、そいつのせいで次の記述が書けなくなっている。 "Foo<Bar<int>>" 何でかっていうと ">>" って右シフト演算子なのであって、">"が2つ並んだ演算子ではないってことなのさ。すでに複雑になっている文法がよりいっそう複雑になっちゃいますねぇ。 でも、Javaにはこんな制限事項ってないんです。 It is still the job of the parser to properly disambiguate ">>" in expression from ">>" in nested generic types. One possible solution is to muck around with the grammar so that ">>" is used in both generics and expression. The cost is that multiple not-so-intuitive production rules need to be introduced to work around the ambiguity. **jparsecでの解決方法 [#mfde038b] Jparsec provides a simpler solution (check out the Java parser sample in the source code). In lexical analysis phase, "<" and ">" characters are uniformly tokenized individually. There will be no token for "<<", "<<<", ">>", or ">>>". Thus in parsing generic types, the parser will not be confused by any ">>" tokens. ***For example: [#o076df9c] private static final String[] OPERATORS = { "+", "-", "*", "/", ">", "<", ">=", "<=", // and all other operators. }; private static final String[] KEYWORDS = { "interface", "class", "enum", "private", "public", "protected", // all other operators }; private static final Terminals TERMINALS = Terminals.caseSensitive(OPERATORS, KEYWORDS); //... Now that makes it real simple for parsing nested generic types with the pointy brackets. But what about the "<<" and ">>" operators used in expression? What happens is that lexical analysis phase has no idea of what context the current token is in, but in syntactical analysis we know whether we are parsing a generic type or an expression. In the latter case, we will treat three adjacent ">" tokens as one single ">>>" operator, and two adjacent ">" tokens as one single ">>" operator. By adjacent, I mean that they have to be next to each other in the original source, if the first ">" character appears at line 7 column 6, the next one has to be at line 7, column 7. In other words, their physical indexes in the original source are adjacent. Luckily, the Token class carries the physical index in the original source. By using the next() combinator, we can specially handle the "adjacency": public static Parser<Token> adjacent(Parser<List<Token>> parser, final String name) { return parser.next(new Map<List<Token>, Parser<Object>>() { public Parser<Object> map(List<Token> tokens) { if (tokens.isEmpty()) return Parsers.always(); int offset = tokens.get(0).index(); for (Token token : tokens) { if (token.index() != offset) { return Parsers.expect(name); } offset += token.length(); } return Parsers.always(); } }).atomic().source().token(); } The above code checks that the list of tokens returned by a parser are adjacent. And then we can use the list() combinator to turn a the special operator string to the parser that returns token list: public static Parser<Token> adjacent(String operator) { List<Parser<Token>> parsers = new ArrayList<Parser<Token>>(operator.length()); for (int i = 0; i < operator.length(); i++) { parsers.add(TERMINALS.token(Character.toString(operator.charAt(i)))); } return adjacent(Parsers.list(parsers), operator); } And by calling adjacent(">>>"), we get a parser that parses three adjacent ">" tokens. Everything else in the grammar can stay as simple as they should be. One catch is that we need to make sure ">>" is not a prefix of ">>>" so to get the parser for ">>" operator, we will need to do a little bit of tweaking as: static final Parser<?> UNSIGNED_RIGHT_SHIFT = adjacent(">>>"); static final Parser<?> RIGHT_SHIFT = UNSIGNED_RIGHT_SHIFT.not().next(adjacent(">>")); And that's about it. The same code can be applied to "<<" and "<<<". Labels parameters Labels Enter labels to add to this page: Please wait Looking for a label? Just start typing. つづいて、別の資料について解説を試みる 対象はソースをダウンロードしてくると付いてくるサンプルコードについてだ。 *SQLの解析サンプルについて [#of950202] jparsecをダウンロードし、解凍すると [jparsec-2.0_src]-[examples]-[src]-[org]-[codehaus]-[jparsec]-[examples]-[sql] がある。 **Eclipseに取り込む手順 [#o49a3671] jparsecからダウンロードしてきたファイルを解凍しておきます。 junitのjarファイルも手元になければ、ダウンロードしてきます。 ダウンロードしてきたjunitはjunit-4.18.jarとかバージョン名がついているので、 junit.jarという名前にかえておきます。 junitはjparsecのlibフォルダに格納しておきます。 では、eclipseがわの準備を行ってみましょう。 Eclipseに新規にJavaプロジェクトを作成します。 ファイルメニューのインポートで先ほど解凍してできたフォルダを選択し、それをプロジェクトのsrcディレクトリを指定してとりこみます。 srcフォルダは、4つあります、本体用、本体test用、example用、exampleテスト用 インポート直後は まだ、プロジェクトのビルドパスにjarが登録されていませんので、コンパイルエラーになっています。 そこで、ビルドパスの設定でparsecのlibフォルダ内のjarをすべて登録します。 コンパイルエラー表示はほぼ消えます。 が、1カ所だけAllTestクラスでエラーになっています。 それは、作者がライブラリをあげたくないからだと build.xmlの80行目に明記してありました。 こんな感じ、 AllTests uses jtc, which is an extra dependency that I don't want to upload. おそらく、Androidのソースを流用したコードだから、著作権の問題であげれないとでも思ったのでしょうか。 それはさておき、 build.xmlには、このクラスのみ除外してコンパイルする記述がありました。 要するにいらないんです。この だからbuild.xmlをいじくりたくなかったので、つぎのようにクラスを書き換えておきました。 package org.codehaus.jparsec; //import org.openqa.jtc.junit.TestSuiteBuilder; import junit.framework.TestSuite; /** * * @author benyu */ public class AllTests extends TestSuite { public static TestSuite suite() { //return TestSuiteBuilder.suite(AllTests.class); return null; } } **コンパイル方法 [#v14b1801] build.xmlがあるので、toolは、ソースが公開されていないみたいなので、開発元の方用のantタスクかもしれません。それ以外はコンパイルできました。 **exampleにあるSQLパーサの使い方 [#ded7b6e6] exampleのテストケースをみると使い方が書いてありました。 ***どこに書いてあるかというと [#fae6fde4] ***パッケージ名: [#n1f6c4af] package org.codehaus.jparsec.examples.sql.parser; ***クラス名: [#i643a3ac] RelationParserTest ***メソッド名: [#s819af7a] public void testSelect() { ***内容の抜粋: [#p5e84e0f] SQLの問い合わせ文 select distinct 1, 2 as id from t1, t2 が下記のようにクラスの構造に解析されているのを確認しているテストコードが書かれていました。 Parser<Relation> parser = RelationParser.select(NUMBER, NUMBER, TABLE); assertParser(parser, "select distinct 1, 2 as id from t1, t2", new Select(true, Arrays.asList(new Projection(number(1), null), new Projection(number(2), "id")), Arrays.asList(table("t1"), table("t2")), null, null, null)); *persecのよみもの [#g2d334d4] **Parsec, 高速なコンビネータパーサ [#rf72fbda] 文字コードをEUCにしないと文字化けします。 http://www.lab2.kuis.kyoto-u.ac.jp/~hanatani/tmp/Parsec.html *俺的チュートリアルを書いてみる [#la10fd7b] 実は、jparsecのソースコードをダウンロードすると、計算機のサンプルコードが入っていて、 これがチュートリアルで解説してあるようなコードよりもすっきりさわやかなコードなのだ。 だから、このサンプルから逆に構築する手順を、観察力+妄想力で、作り、俺的チュートリアルをつくるのだ!。それが、漢ってもんだろ。 **ゴール [#k0421c8f] はっきりとしたゴールがあるってことは、それだけでも、しあわせなことなのさ。 まずは、四則演算の構文解析の作成方法を理解できるようにする。 四則演算って、いろいろな言語の基本的機能だから、構文解析のHelloWold的存在かとは思うけど、 言語を解析するには、数学でいうところの必要条件にすぎない。 ちなみに、下記のコードを作れるような手順をコードから逆に作ってみることを目標にします。 その後、BNFの解析なども行います。 /** * The main calculator parser. * * @author Ben Yu */ public final class Calculator { /** Parsers {@code source} and evaluates to an {@link Integer}. */ public static int evaluate(String source) { return parser().parse(source); } static final Parser<Integer> NUMBER = Scanners.INTEGER.map(new Map<String, Integer>() { public Integer map(String text) { return Integer.valueOf(text); } }); static final Binary<Integer> PLUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a + b; } }; static final Binary<Integer> MINUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a - b; } }; static final Binary<Integer> MUL = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a * b; } }; static final Binary<Integer> DIV = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a / b; } }; static final Binary<Integer> MOD = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a % b; } }; static final Unary<Integer> NEG = new Unary<Integer>() { public Integer map(Integer i) { return -i; } }; private static <T> Parser<T> op(char ch, T value) { return isChar(ch).retn(value); } static Parser<Integer> parser() { Parser.Reference<Integer> ref = Parser.newReference(); Parser<Integer> term = ref.lazy().between(isChar('('), isChar(')')).or(NUMBER); Parser<Integer> parser = new OperatorTable<Integer>() .prefix(op('-', NEG), 100) .infixl(op('+', PLUS), 10) .infixl(op('-', MINUS), 10) .infixl(op('*', MUL), 20) .infixl(op('/', DIV), 20) .infixl(op('%', MOD), 20) .build(term); ref.set(parser); return parser; } } *大まかな概念 [#qe9b8e5b] 解析するものは、括弧などで括られている箇所はparenと命名する。 中身をExpressionと命名する。 *Jparsecのサンプルから説明書を作ってみる。 [#z9c4734a] **パッケージ構成 [#m99766da] まずパーサにフォルダとしてastフォルダとpaserフォルダを作ります。 astフォルダは構文を解析した結果を保存しておくクラスです。 ***astのクラスについて [#g15aed37] astに格納するクラスの大部分は ValueObjectクラスを継承しており、それ以外の残りのクラスはenum型やInterfaceです。 ***Interface [#d89592c1] Interfaceには、これから解析しようとする対象を意味する名前だけでもいいので、シンプルな インタフェースを用意します。 このInterfaceは、構文木の末端のクラス名を意味するようにします。 **paserパッケージのクラスの作成 [#y885539b] 入れ物であるクラスの定義が終われば、今度はpaserパッケージのクラスの作成をします。 大雑把に言って入れ物を作って次の順番でクラスを構築していく感じです。 全体的な入れ物は Parser.Reference<Integer> ref = Parser.newReference(); で定義します。 この文をサンプルで探すとメインの処理が見つかりますが、複雑な構文になってくると、ルールごとに部分部分で解析するため、 いくつか要所要所ででてきます。 たとえば、一番簡単な計算機のサンプルは、SQLのサンプルではExpressionの構文解析の一機能にすぎません。 (サンプルのarithmeticメソッド参照) **末端表現をパターンで記述 [#a19619d8] 対象を表す正規表現と入れ物として定義したクラスを頭の中でイメージします。(実装ではPatternクラスのメソッドで正規表現を表現します。) よく使うものであれば、 Scannersにはよく使うであろう正規表現のPatternクラスのインスタンスが登録されていますし、 Terminalsにもよく使うであろう物が登録されています。 Terminalsはcurryのsequenceメソッドの引数に使うような設計です。 Scannersクラスで定義されていないかどうか確認します。 Scannersクラスで定義されていない場合、演算子の場合はString[]型で private static final String[] OPERATORS = {"*", "+", "?", "|", "::=", "(", ")"}; と定義したとすると、 private static final Terminals TERMS = Terminals.operators(OPERATORS); に格納します。 これらは、ルール定義の際に、 Mapper._(TERMS.token(name)) 入れ物のクラスが決まれば、次の定型文で単位を定義できます。 static final Parser<Rule> LITERAL = Mapper.curry(LiteralRule.class).sequence(いまからせつめいする引数) **Patternの定義 正規表現に使う名前を決める [#xa25f797] Scanners.isPatternで正規表現をメソッドで置き換えたPatternクラスのインスタンスを登録します。 たとえば、正規表現の「+」はmany1となります。 -分割の場合は sepBy1 -複数条件のOR結合はor -間に挟む場合はbetween ただしどれも、引数には、Mapper._(TERMS.token(name))のnameに登録済みの文字列を入れたものを使います。 ***頻繁に使うであろうPatternクラス [#c7c08a6f] Scannersにはよく使うであろう正規表現のPatternクラスのインスタンスが登録されています。 **Pattern Patternsクラスで定義 [#h75daaee] Parser<_> Scanners.isPatternで定義 例:Parser<Integer> NUMBER Parser<Tok> Lexers.lexerで定義 **演算子の組み立て方 [#hb0a41b0] 基本的にmapというメソッドをオーバロードさせていきます。 必要なパラメータは、どんな記号なのか? その次に、何項演算子なのかでクラスが異なります。 2項演算子の場合はBinary<Integer>で、演算子は数値の真ん中に来るので、 OperatorTableの.infixlで登録 1項演算子の場合はUnary<Integer>で、演算子は数値の先頭にくるので、.prefixで登録します。 static final Unary<Integer> NEG = new Unary<Integer>() { public Integer map(Integer i) { return -i; } }; static final Binary<Integer> PLUS = new Binary<Integer>() { public Integer map(Integer a, Integer b) { return a + b; } }; Parser<Integer> parser = new OperatorTable<Integer>() .prefix(op('-', NEG), 100) .infixl(op('+', PLUS), 10) .build(term); **括弧を定義する場合 [#sff090ac] 昔はこんな感じでTermsクラスがあったみたいですが、 final Terms ops = Terms.getOperatorsInstance("+", "-", "*", "/", "(", ")"); いまは、 括弧の中身をすぐには評価しないので、lazyをつけ、対になっている「(」と「)」をbetweenで括ります。もし、その対でなければ、 下記のようにorで連結していきます。 Parser<Integer> term = ref.lazy().between(isChar('('), isChar(')')).or(NUMBER); で、OperatorTableの最後にbuildの引数として格納します。 **構文を定義する。 [#u8e80826] ***SELECT文の定義例 [#p48d3599] たとえばSELECT文を定義してみます。 構文には、単体で意味のある箇所と 条件文のように塊で意味のある箇所があります。 さらに括弧のようなもっと上の優先順位で評価される塊を表現する箇所があり、 構文解析ではこのような、構成要素の数でパーサを分類していきます。 引数の数でこの分類を観察すると、1,2,3とひとつづつ増えていっています。 **インデントとスペース、コメントなどの除去 [#c5147af5] 最後は、インデントとスペースを除去して解析するように記述する。 たとえば、このような感じである。 static <T> T parse(Parser<T> parser, String source) { return parser.from(INDENTATION.lexer(TOKENIZER, Indentation.WHITESPACES.or(COMMENT).many())) .parse(source); } ***素朴な疑問 [#h2637aaf] 作者はunionとか、なぜ注意深く定義できたのか? bnf SQL selectで検索してみた。 http://savage.net.au/SQL/sql-92.bnf.html あまり関係がないようだ。 自分で解析しているのかもしれない。 *delete文の解析機を作ってみる。 [#f89a5667] 未記入
テキスト整形のルールを表示する