16.5 簡易URL解析

練習問題16.5.3 への回答.
URLの(ものすごく)適当なパーサ?を作ります.
コンビネータとかかっこいいことは何もなくて, 単に手書きでゴリゴリ書く問題です.
直前に出てきたreaderを使う練習でしょう.

以下のような形式の文法を解析します.

url ::= http://domain<path><anchor> 
      | file://path
	  | relativePath
domain ::= id | id.domain
path ::= /relativepath
id ::= [a-zA-Z][a-zA-Z0-9]*
anchor ::= #id

ホント適当ですね(^^

で,本文中でhttp形式のパーサ(笑)は与えられているので,
残っているfileとrelativepath(相対パス)形式のパーサを書くのが本題です.

回答

以下のようになりました.

structure SS = Substring;
fun parseFile s = (* ファイルパス形式解析 *)
  let
    val s = if SS.isPrefix "://" s then SS.triml 3 s (* プレフィックス削除 *)
            else raise urlFormat
    val (relpath,anchor) = SS.splitl (curry op<> #"#") s (* 末尾のアンカーを分割 *)
	(* 文字列を '/' 区切りでトークンに分割 *)
    val xs = map SS.string $ SS.tokens (curry op= #"/") relpath
    val last_ = List.last xs
  in
    if SS.isEmpty anchor
    then {path=xs, anchor=NONE}
    else {path=xs, anchor=SOME $ SS.string $ SS.triml 1 anchor}
  end

fun parseRelative s = (* 相対アドレス解析 *)
  let
    val (path,anchor) = SS.splitl (curry op<> #"#") s
    val xs = map SS.string $ SS.tokens (curry op= #"/") path
  in
    {path=xs, anchor=if SS.isEmpty anchor
                     then NONE
                     else SOME $ SS.string anchor}
  end

使用する様子

(* http形式 *)
- Url.parseUrl "http://d.hatena.ne.jp/eldesh/edit#hoge";
val it =
  HTTP
    {anchor=SOME "hoge",host=["d","hatena","ne","jp"],
     path=SOME ["eldesh","edit"]} : Url.url
(* ファイルパス形式 *)
- Url.parseUrl "file://usr/local/bin/gcc";
val it = FILE {anchor=NONE,path=["usr","local","bin","gcc"]} : Url.url
(* 相対パス形式 *)
- Url.parseUrl "../../www/apache/httpd.conf";
val it = RELATIVE {anchor=NONE,path=["..","..","www","apache","httpd.conf"]}
  : Url.url

ファイルパスにアンカーとか要るのか分かりませんが, とりあえず動いてるようですね.
SubstringとString型を変換しながら処理するのはめんどくさいので,
出来るだけ文字列処理はSubstringで完結しておいて, 最後にStringに戻すべきかな.
というか違いがよく分からないけど,SubstringはStringPieceみたいなモンなのかな?

きれいなCurry化

ところでSMLについて喋ると必ず言ってますけど,
二項演算子が2タプルを引数に取るんですね. つまり…

- op+ 2 3
stdIn:6.1-6.8 Error: operator and operand don't agree [literal]
  operator domain: 'Z * 'Z
  operand:         int
  in expression:
    + 2

引数を二つ並べるとエラーになりますorz
正しくはこう

- op+ (2,3)
val it = 5 : int

つまりカリー化出来ないorz
コレを解決するために, この本の中のコードは

fun neq a b = not (a=b);
fun  eq a b =     (a=b);

みたいな目も当てられない感じのことになってます.
私の回答中では以下の関数でしのいでいますが,

fun curry f x y = f (x,y);

それでも見づらいので何かカリー化するprefix記号を考えたいんですが思いつかない….いいもの有ったら教えて下さい.
カリー化っぽい記号って何だろう?