Mon Feb 17 21:40:01 JST 2014 (modified: Sat Sep 30 16:15:34 JST 2017)
views: 2980, keywords:Haskell,名前はまだない,グルー言語,グルー言語を作る,おじさん頑張ったよ この記事は最終更新日が7年以上前のものです。
グルー言語作成の最初の一歩
こういう経緯で、前々から作りたいと思っていたシェルスクリプトに代わりうるグルー言語を作ることになったわけですが、ブログの記事を見た方やその場にいた方から「有言実行すばらしい!」などとコメントをいただきました。しかし、冷静に考えれば記事書いただけじゃ有限実行じゃないので、本日午前中、筑波エクスプレスの社内とつくば駅前のミスタードーナツで最初のプロトタイプを書きました。
こういうのはいろんな人が参加できるべきですが、Haskellで書き始めてしまい、最初から人を拒むという、コミュニケーションに問題のある人格っぷりを演出しております。でもパーサ書くの、これが一番簡単なので・・・。とりあえずやりたいことを表現するためのプロトタイプ作りは、当面Haskellで行います。
作ったもの
一つのパイプラインに相当するものを新言語で書き、それをbashのコードに変換するコンバータです。やりたいことがbashの範疇を超えない限りはこれをどんどん成長させていこうかと。もしかしたらコンバータだけで自分の欲しいものができてしまうかもしれない・・・。
- 新言語のコード(関数みたいなもの一個だけ書いたもの)
/SAMPLE_SCRIPTS/findfilename.glue
uedambp:GlueLang ueda$ cat PROTOTYPE/findfilename.glue
uedambp:PROTOTYPE ueda$ cat SAMPLE_SCRIPTSfilter main word dir:
dir
find grep word
dirで指定したディレクトリをfindして、その出力をwordで指定した文字列で検索するフィルタという意味です。
これを次のように変換するコマンド(langToBash)を作りました。
uedambp:PROTOTYPE ueda$ ./langToBash ./SAMPLE_SCRIPTS/findfilename.glue
#!/bin/bash -e
function main(){
find $2 | grep $1
}
main "$1" "$2"
ちゃんと動きます。
uedambp:PROTOTYPE ueda$ ./langToBash ./SAMPLE_SCRIPTS/findfilename.glue > hoge.bash
uedambp:PROTOTYPE ueda$ chmod +x hoge.bash
uedambp:PROTOTYPE ueda$ ./hoge.bash "lang" "."
./langToBash
./langToBash.hi
./langToBash.hs
./langToBash.o
書いたコード
とにかく動くものを。拙速に拙速に・・・ということで、Haskell分かる人にとっては拙速なコードでございます。電車とドーナツ屋で生まれました。あ、日付が違う・・・。
import System.Environment
import System.IO
import Text.Parsec
import Text.Parsec.String
import qualified Data.Text as D
--import Text.ParserCombinators.Parsec
showUsage :: IO ()
= do System.IO.hPutStr stderr
showUsage "Usage : langToBash <file>\\n" ++
("Sun Feb 16 15:55:08 JST 2014\\n" )
type FilterName = String
type FilterArgs = (Int,String)
type FilterCode = String
data Filter = Filter FilterName [FilterArgs] [FilterCode] deriving Show
data Script = Script [Filter] | Err String deriving Show
main :: IO()
= do args <- getArgs
main case args of
-> showUsage
[] -> readF f >>= putStr . toBash . parseGlueLang
[f] -> showUsage
_
toBash :: Script -> String
Script fs) = unlines (header:(map toOneLiner fs) ++ [footer])
toBash (where header = "#!/bin/bash -e\\n"
= "main " ++ mainArgs fs
footer
mainArgs :: [Filter] -> String
Filter "main" args _):fs) = unwords $ [ "\\"" ++ ('$':(show n)) ++ "\\"" | n <- [1..len]]
mainArgs ((where len = length args
= ""
mainArgs _
toOneLiner :: Filter -> String
Filter fname opts codes) = func fname ++ "\\n"
toOneLiner (++ (pipeCon $ map (convArgs opts) codes) ++ "}"
where func fname = "function " ++ fname ++ "(){"
pipeCon :: [String] -> String
= s ++ "\\n"
pipeCon [s] :ss) = s ++ " | " ++ pipeCon ss
pipeCon (s
convArgs :: [FilterArgs] -> FilterCode -> String
= str
convArgs [] str :ops) str = convArgs ops $ D.unpack (D.replace (D.pack op) (D.pack $ ('$':show n)) (D.pack str))
convArgs ((n,op)
readF :: String -> IO String
"-" = getContents
readF = readFile f
readF f
parseGlueLang :: String -> Script
= case parse code "" str of
parseGlueLang str Right scr -> scr
Left err -> Err ( show err )
= many1 langFilter >>= return . Script
code
= do string "filter "
langFilter <- langWord
nm <- many langWord
args
many langSpace':'
char '\\n' )
many1 ( char <- many1 langFilterCode
lns return $ Filter nm (zip [1..] args) lns
= do w <- many1 (noneOf " :\\n\\t")
langWord
many langSpacereturn w
= oneOf " \\t"
langSpace
= do ln <- many (noneOf "\\n")
langFilterCode '\\n'
char return ln
以後はこのコードをピカピカにする一方、新言語の文法についてくどくど考察するつもりです。しかし、勤め人としてはちょっと休止せざるをえない事情がありまして、しばらく冬眠します・・・。
とりあえず動くものを作ったので許してちょえ。