\begin{code}
module Text.Printf.TH.Parser (parse) where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Maybe (isJust, fromJust)
import Text.Printf.TH.Printer (get_conversion_func)
import Char (isDigit)
import List (nub, delete)
import Text.Printf.TH.Types
parse :: String -> ([Format], ArgNum)
parse = parse' 1 0
parse' :: ArgNum
-> ArgNum
-> String
-> ([Format],
ArgNum)
parse' x_var max_x_var "" = ([], (x_var 1) `max` max_x_var)
parse' x_var max_x_var xs
= case parse_format x_var xs of
(f, x_var', used, xs') ->
case parse' x_var' (maximum (max_x_var:used)) xs' of
(fs, final_max_x_var) -> (f:fs, final_max_x_var)
parse_format :: ArgNum
-> String
-> (Format,
ArgNum,
[ArgNum],
String)
parse_format x_var ('%':xs)
= case conv_spec of
'n' -> (CharCount, x_var, [], xs5)
'%' -> (Literal "%", x_var, [], xs5)
_ -> (Conversion converted', x_var0, used, xs5)
where (arg, used0, x_var0, xs0) = get_arg x_var3 xs
(flags, xs1) = get_flags xs0
flags' = if isJust mprec then delete ZeroPadded flags else flags
(mfw, used2, x_var2, xs2) = get_min_field_width x_var xs1
(mprec, used3, x_var3, xs3) = get_precision x_var2 xs2
(_length_mod, xs4) = get_length_modifier xs3
(conv_spec, xs5) = get_conversion_specifier xs4
conv_func = get_conversion_func conv_spec
used = used0 ++ used2 ++ used3
converted = conv_func arg flags' mfw mprec
converted' = fix_width flags' mfw converted
parse_format x_var xs = case break ('%' ==) xs of
(ys, zs) -> (Literal ys, x_var, [], zs)
fix_width :: [Flag] -> Maybe Width -> ExpQ -> ExpQ
fix_width _ Nothing e = e
fix_width flags (Just w) e = letE [dec_e] exp_spaced
where
dec_e = valD (varP (mkName "e")) (normalB e) []
exp_num_spaces = [| abs $w length $e |]
exp_num_spaces' = [| 0 `max` $exp_num_spaces |]
exp_spaces = [| replicate $exp_num_spaces' ' ' |]
exp_left_padded = [| $(varE (mkName "e")) ++ $exp_spaces |]
exp_right_padded = [| $exp_spaces ++ $(varE (mkName "e")) |]
exp_spaced = if LeftAdjust `elem` flags
then exp_left_padded
else [| if $w < 0 then $exp_left_padded
else $exp_right_padded |]
get_flags :: String -> ([Flag], String)
get_flags s = (flags'', s')
where (cs, s') = span (`elem` "#0- +'I") s
unique_cs = nub cs
flags = map (fromJust . (`lookup` flag_mapping)) unique_cs
flags' = if LeftAdjust `elem` flags then filter (/= ZeroPadded) flags
else flags
flags'' = if Plus `elem` flags then filter (/= BlankPlus) flags
else flags'
flag_mapping = [('#', AlternateForm),
('0', ZeroPadded),
('-', LeftAdjust),
(' ', BlankPlus),
('+', Plus),
('\'', Thousands),
('I', AlternativeDigits)]
get_min_field_width :: ArgNum
-> String
-> (Maybe Width, [ArgNum], ArgNum, String)
get_min_field_width x_var s
= case get_num s of
Just (n, s') -> (Just [| n |], [], x_var, s')
Nothing -> case get_star_arg x_var s of
Just (a, used, x_var', s') -> (Just a, used, x_var', s')
Nothing -> (Nothing, [], x_var, s)
get_precision :: ArgNum
-> String
-> (Maybe Precision, [ArgNum], ArgNum, String)
get_precision x_var ('.':s)
= case get_num s of
Just (n, s') -> (Just [| n |], [], x_var, s')
Nothing -> case get_star_arg x_var s of
Just (a, used, x_var', s') -> (Just a, used, x_var', s')
Nothing -> (Just [| 0 |], [], x_var, s)
get_precision x_var s = (Nothing, [], x_var, s)
get_star_arg :: ArgNum -> String -> Maybe (Arg, [ArgNum], ArgNum, String)
get_star_arg x_var ('*':s) = Just (get_arg x_var s)
get_star_arg _ _ = Nothing
get_arg :: ArgNum -> String -> (Arg, [ArgNum], ArgNum, String)
get_arg x_var s = case get_num s of
Just (i, '$':s') -> (varE ( mkName (xvar i) ), [i], x_var, s')
_ -> (varE (mkName (xvar x_var)), [], x_var + 1, s)
get_num :: String -> Maybe (Integer, String)
get_num s = case span isDigit s of
("", _) -> Nothing
(xs, s') -> Just ((read xs), s')
get_length_modifier :: String -> (String, String)
get_length_modifier s
| take 2 s `elem` ["hh", "ll"] = splitAt 2 s
| take 1 s `elem` ["h", "l", "L", "q", "j", "z", "t"] = splitAt 1 s
| otherwise = ("", s)
get_conversion_specifier :: String -> (Char, String)
get_conversion_specifier (x:xs) = (x, xs)
get_conversion_specifier "" = error "Printf: get_conversion_specifier \"\""
\end{code}