| 1 | {-# OPTIONS_GHC -fglasgow-exts -fallow-overlapping-instances #-} |
|---|
| 2 | |
|---|
| 3 | {-| |
|---|
| 4 | Parameter binding. |
|---|
| 5 | |
|---|
| 6 | > A star was bound upon her brows, |
|---|
| 7 | > A light was on her hair |
|---|
| 8 | > As sun upon the golden boughs |
|---|
| 9 | > In Lorien the fair... |
|---|
| 10 | -} |
|---|
| 11 | |
|---|
| 12 | module Pugs.Bind ( |
|---|
| 13 | bindParams, bindSomeParams, |
|---|
| 14 | ) where |
|---|
| 15 | import Pugs.Internals |
|---|
| 16 | import Pugs.AST |
|---|
| 17 | import Pugs.Types |
|---|
| 18 | |
|---|
| 19 | {-| |
|---|
| 20 | Contains either a valid value of @a@ (@Right@), or a @String@ error |
|---|
| 21 | message (@Left@). |
|---|
| 22 | -} |
|---|
| 23 | type MaybeError a = Either String a |
|---|
| 24 | |
|---|
| 25 | {-| |
|---|
| 26 | Match up named arguments with named parameters, producing a list of new |
|---|
| 27 | bindings, and lists of remaining unbound args and params. |
|---|
| 28 | -} |
|---|
| 29 | bindNames :: [Exp] -- ^ List of argument expressions to be bound |
|---|
| 30 | -> [Param] -- ^ List of parameters to try binding; includes both |
|---|
| 31 | -- named params and positional params |
|---|
| 32 | -> (Bindings, [Exp], [Param]) -- ^ Bindings made; |
|---|
| 33 | -- remaining (unbound) named args; |
|---|
| 34 | -- remaining (positional) params |
|---|
| 35 | bindNames exps prms = (bound, exps', prms') |
|---|
| 36 | where |
|---|
| 37 | prms' = prms \\ (map fst bound) |
|---|
| 38 | (bound, exps') = foldr doBindNamed ([], []) (map unwrapNamedArg exps) |
|---|
| 39 | doBindNamed (name, exp) (bound, exps) = case foundParam of |
|---|
| 40 | Just prm -> ( ((prm, exp) : bound), exps ) |
|---|
| 41 | _ -> ( bound, (Syn "named" [Val (VStr $ cast name), exp]:exps) ) |
|---|
| 42 | where |
|---|
| 43 | foundParam = find ((== name) . v_name . paramName) prms |
|---|
| 44 | |
|---|
| 45 | emptyHashExp :: Exp |
|---|
| 46 | emptyHashExp = Val $ VList [] -- VHash $ vCast $ VList [] |
|---|
| 47 | |
|---|
| 48 | emptyArrayExp :: Exp |
|---|
| 49 | emptyArrayExp = Val $ VList [] -- VArray $ vCast $ VList [] |
|---|
| 50 | |
|---|
| 51 | {-| |
|---|
| 52 | Create a binding from the slurpy hash parameter (e.g. @\*%_@) to a hash |
|---|
| 53 | containing all the remaining named arguments. If multiple slurpy hashes |
|---|
| 54 | are given, only the first gets the arguments--the rest get an empty hash. |
|---|
| 55 | Used by 'bindSomeParams'. |
|---|
| 56 | -} |
|---|
| 57 | bindHash :: [Exp] -- ^ Named arguments (pair expressions) that were not |
|---|
| 58 | -- consumed by explicit named parameters |
|---|
| 59 | -> [Param] -- ^ List of slurpy hash parameters |
|---|
| 60 | -> MaybeError Bindings |
|---|
| 61 | bindHash [] [] = return [] |
|---|
| 62 | bindHash [] [p] = return [ (p, emptyHashExp) ] |
|---|
| 63 | bindHash (v:_) [] = fail $ "Named argument found where no matched parameter expected: " ++ show (unwrapNamedArg v) |
|---|
| 64 | bindHash vs (p:ps@(_:_))= do |
|---|
| 65 | first <- (bindHash vs [p]) |
|---|
| 66 | return $ first ++ (ps `zip` repeat emptyHashExp) |
|---|
| 67 | bindHash vs [p] = return [ (p, Syn "\\{}" [Syn "," vs]) ] -- XXX cast to Hash |
|---|
| 68 | |
|---|
| 69 | {-| |
|---|
| 70 | Create bindings from the slurpy scalar and array parameters to the remaining |
|---|
| 71 | positional arguments. The first slurpy array param gets all of the remaining |
|---|
| 72 | args; subsequent slurpy array params get an empty array. Slurpy scalars may |
|---|
| 73 | not appear after slurpy array params. |
|---|
| 74 | |
|---|
| 75 | Returns the bindings performed, and the sub's new 'SlurpLimit'. |
|---|
| 76 | |
|---|
| 77 | Mostly uses 'doBindArray' to do its dirty work. Used by 'bindSomeParams'. |
|---|
| 78 | |
|---|
| 79 | >[12:16] <scook0> autrijus: At the moment, if you call a sub that has multiple slurpy arrays, |
|---|
| 80 | > Pugs deliberately binds the first one normally, and makes all the rest empty |
|---|
| 81 | >[12:17] <scook0> Is this proper behaviour, or is it just a quirk of the current implementation? |
|---|
| 82 | >[12:17] <autrijus> no, that's specced. |
|---|
| 83 | >[12:17] <autrijus> i.e. correct |
|---|
| 84 | -} |
|---|
| 85 | bindArray :: [Exp] -- ^ List of slurpable argument expressions |
|---|
| 86 | -> [Param] -- ^ List of all slurpy positional params (scalar and array) |
|---|
| 87 | -> SlurpLimit -- ^ The sub's current 'SlurpLimit' |
|---|
| 88 | -> MaybeError (Bindings, SlurpLimit) |
|---|
| 89 | bindArray vs ps oldLimit = do |
|---|
| 90 | let exp = Ann (Cxt cxtSlurpyAny) (Syn "," vs) |
|---|
| 91 | case foldM (doBindArray exp) ([], 0) prms of |
|---|
| 92 | Left errMsg -> fail errMsg |
|---|
| 93 | Right (bound, n) -> do |
|---|
| 94 | let newLimit = case prms of |
|---|
| 95 | ((_, SArray):_) -> oldLimit |
|---|
| 96 | ((_, SArrayMulti):_) -> oldLimit |
|---|
| 97 | _ | n > 0 -> (n, exp) : oldLimit |
|---|
| 98 | _ -> oldLimit |
|---|
| 99 | return (reverse bound, newLimit) |
|---|
| 100 | where |
|---|
| 101 | prms = map (\p -> (p, v_sigil $ paramName p)) ps |
|---|
| 102 | |
|---|
| 103 | {-| |
|---|
| 104 | Construct an expression representing an infinite slice of the given |
|---|
| 105 | array expression, beginning at element /n/ (i.e. @\@array\[\$n...\]@). |
|---|
| 106 | |
|---|
| 107 | Used by 'doBindArray' to bind a slurpy array parameter to the rest of |
|---|
| 108 | the slurpable arguments. |
|---|
| 109 | -} |
|---|
| 110 | doSlice :: Exp -- ^ The array expression to slice |
|---|
| 111 | -> VInt -- ^ Index of the first element in the resulting slice (/n/) |
|---|
| 112 | -> Exp |
|---|
| 113 | doSlice v n = Syn "[...]" [v, Val $ VInt n] |
|---|
| 114 | |
|---|
| 115 | -- XXX - somehow force failure |
|---|
| 116 | {-| |
|---|
| 117 | Construct an expression representing element /n/ in the given array |
|---|
| 118 | expression (i.e. @\@array\[\$n\]@). |
|---|
| 119 | |
|---|
| 120 | Used by 'doBindArray' to bind a particular slurpy scalar parameter to one of |
|---|
| 121 | the slurpable arguments. |
|---|
| 122 | -} |
|---|
| 123 | doIndex :: Exp -> VInt -> Exp |
|---|
| 124 | doIndex v n = Syn "[]" [Syn "val" [v], Val $ VInt n] |
|---|
| 125 | |
|---|
| 126 | doBindArray :: Exp -> (Bindings, VInt) -> (Param, VarSigil) -> MaybeError (Bindings, VInt) |
|---|
| 127 | doBindArray _ (xs, -1) (p, SArray) = return (((p, emptyArrayExp):xs), -1) |
|---|
| 128 | doBindArray _ (_, -1) (p, _) = fail $ "Slurpy array followed by slurpy scalar: " ++ show p |
|---|
| 129 | doBindArray v (xs, n) (p, SArray) = return (((p, doSlice v n):xs), -1) |
|---|
| 130 | doBindArray v (xs, n) (p, _) = case v of |
|---|
| 131 | (Syn "," []) -> fail $ "Insufficient arguments for slurpy scalar" |
|---|
| 132 | _ -> return (((p, doIndex v n):xs), n+1) |
|---|
| 133 | -- doBindArray _ (_, _) (_, x) = internalError $ "doBindArray: unexpected char: " ++ (show x) |
|---|
| 134 | |
|---|
| 135 | |
|---|
| 136 | isNamedArg :: Exp -> Bool |
|---|
| 137 | isNamedArg (Syn "named" [(Val (VStr _)), _]) = True |
|---|
| 138 | isNamedArg (Syn "named" [Ann _ (Val (VStr _)), _]) = True -- should the Ann reach here? |
|---|
| 139 | isNamedArg arg@(Syn "named" _) = error $ "malformed named arg: " ++ show arg |
|---|
| 140 | isNamedArg _ = False |
|---|
| 141 | |
|---|
| 142 | unwrapNamedArg :: Exp -> (ID, Exp) |
|---|
| 143 | unwrapNamedArg (Syn "named" [(Val (VStr key)), val]) = (cast key, val) |
|---|
| 144 | unwrapNamedArg (Syn "named" [Ann _ (Val (VStr key)), val]) = (cast key, val) -- (see comment in isNamedArg) |
|---|
| 145 | unwrapNamedArg x = error $ "not a well-formed named arg: " ++ show x |
|---|
| 146 | |
|---|
| 147 | {-| |
|---|
| 148 | Bind parameters to a callable, then verify that the binding is complete |
|---|
| 149 | (i.e. all mandatory params are bound; all unspecified params have default |
|---|
| 150 | bindings). |
|---|
| 151 | |
|---|
| 152 | Uses 'bindSomeParams' to perform the initial binding, then uses |
|---|
| 153 | 'finalizeBindings' to check all required params and give default values to |
|---|
| 154 | any unbound optional ones. Once this is complete, /everything/ should be |
|---|
| 155 | bound. |
|---|
| 156 | |
|---|
| 157 | Note that while 'bindParams' produces values /representing/ the bindings from |
|---|
| 158 | params to args, it does not actually introduce any symbols--that occurs later |
|---|
| 159 | on in the call process. |
|---|
| 160 | -} |
|---|
| 161 | bindParams :: VCode -- ^ A code object to perform bindings on |
|---|
| 162 | -> (Maybe Exp) -- ^ (Optional) explicit invocant |
|---|
| 163 | -> [Exp] -- ^ List of arguments (actual params) to bind |
|---|
| 164 | -> MaybeError VCode -- ^ Returns either a new 'VCode' with all the |
|---|
| 165 | -- bindings in place, or an error message |
|---|
| 166 | -- Special case: 'close $fh' should be bound as '$fh.close' |
|---|
| 167 | bindParams sub Nothing [inv] |
|---|
| 168 | | (p:_) <- subParams sub, isInvocant p |
|---|
| 169 | = bindParams sub (Just inv) [] |
|---|
| 170 | bindParams sub invExp argsExp = do |
|---|
| 171 | case bindSomeParams sub invExp argsExp of |
|---|
| 172 | Left errMsg -> Left errMsg |
|---|
| 173 | Right boundSub -> finalizeBindings boundSub |
|---|
| 174 | |
|---|
| 175 | {-| |
|---|
| 176 | Verify that all invocants and required parameters are bound, and give default |
|---|
| 177 | values to any unbound optional parameters. |
|---|
| 178 | -} |
|---|
| 179 | finalizeBindings :: VCode -> MaybeError VCode |
|---|
| 180 | finalizeBindings sub = do |
|---|
| 181 | let params = subParams sub |
|---|
| 182 | bindings = subBindings sub |
|---|
| 183 | boundInvs = filter (isInvocant . fst) bindings -- bound invocants |
|---|
| 184 | invocants = takeWhile isInvocant params -- expected invocants |
|---|
| 185 | |
|---|
| 186 | -- Check that we have enough invocants bound |
|---|
| 187 | when (not . null $ invocants) $ do |
|---|
| 188 | let missing = show (length invocants) |
|---|
| 189 | supplied = show (length boundInvs) |
|---|
| 190 | fail $ concat |
|---|
| 191 | [ "Missing invocant parameters in '" |
|---|
| 192 | , cast (subName sub) |
|---|
| 193 | , "': " |
|---|
| 194 | , supplied, " received, " |
|---|
| 195 | , missing, " missing" |
|---|
| 196 | ] |
|---|
| 197 | |
|---|
| 198 | let (boundOpt, boundReq) = partition (isOptional . fst) bindings -- bound params which are required |
|---|
| 199 | (optPrms, reqPrms) = partition isOptional params -- all params which are required, and all params which are opt |
|---|
| 200 | |
|---|
| 201 | -- Check length of required parameters |
|---|
| 202 | when (length boundReq < length reqPrms) $ do |
|---|
| 203 | fail $ "Missing required parameters: " |
|---|
| 204 | ++ unwords (map (cast . paramName) $ reqPrms \\ map fst boundReq) |
|---|
| 205 | |
|---|
| 206 | let unboundOptPrms = optPrms \\ (map fst boundOpt) -- unbound optParams are allPrms - boundPrms |
|---|
| 207 | optPrmsDefaults = [ |
|---|
| 208 | Syn "param-default" [paramDefault prm, Val (VCode sub)] |
|---|
| 209 | | prm <- unboundOptPrms |
|---|
| 210 | ] -- get a list of default values |
|---|
| 211 | boundDefOpts = unboundOptPrms `zip` optPrmsDefaults -- turn into exprs, so that +$y = $x will work |
|---|
| 212 | |
|---|
| 213 | return sub { |
|---|
| 214 | subBindings = ((subBindings sub) ++ boundDefOpts) |
|---|
| 215 | } |
|---|
| 216 | |
|---|
| 217 | {-| |
|---|
| 218 | Take a code object and lists of invocants and arguments, and produce (if |
|---|
| 219 | possible) a new 'VCode' value representing the same code object, with as many |
|---|
| 220 | parameters bound as possible (using the given invocants and args). |
|---|
| 221 | -} |
|---|
| 222 | bindSomeParams :: VCode -- ^ Code object to perform bindings on |
|---|
| 223 | -> (Maybe Exp) -- ^ Explicit invocant expression |
|---|
| 224 | -> [Exp] -- ^ List of argument expressions |
|---|
| 225 | -> MaybeError VCode -- ^ A new 'VCode' structure, augmented |
|---|
| 226 | -- with the new bindings |
|---|
| 227 | bindSomeParams sub invExp argsExp = do |
|---|
| 228 | let params = subParams sub |
|---|
| 229 | bindings = subBindings sub |
|---|
| 230 | slurpLimit = subSlurpLimit sub |
|---|
| 231 | (invPrms, argPrms) = span isInvocant params |
|---|
| 232 | (givenInvs, givenArgs) = if null invPrms |
|---|
| 233 | then ([], (maybeToList invExp++argsExp)) |
|---|
| 234 | else (maybeToList invExp, argsExp) |
|---|
| 235 | |
|---|
| 236 | let boundInv = invPrms `zip` givenInvs -- invocants are just bound, params to given |
|---|
| 237 | (namedArgs, posArgs) = partition isNamedArg givenArgs |
|---|
| 238 | (boundNamed, namedForSlurp, allPosPrms) = bindNames namedArgs argPrms -- bind pair args to params. namedForSlup = leftover pair args |
|---|
| 239 | (itemPrms, slurpyPrms) = break isSlurpy allPosPrms -- split any prms not yet bound, into regular and slurpy. allPosPrms = not bound by named |
|---|
| 240 | posPrms = filter (not . isNamed) itemPrms |
|---|
| 241 | boundPos = posPrms `zip` posArgs -- bind all the unbound params in positional order |
|---|
| 242 | posForSlurp = drop (length posPrms) posArgs -- and whatever's left will be slurped |
|---|
| 243 | |
|---|
| 244 | -- Bind slurpy arrays and hashes |
|---|
| 245 | let (slurpNamed, slurpPos) = partition ((SHash ==) . v_sigil . paramName) slurpyPrms |
|---|
| 246 | -- defaultPos = if hasDefaultArray then [] else [defaultArrayParam] |
|---|
| 247 | defaultScalar = if hasDefaultScalar then [] else [] -- XXX - fetch from *@_ |
|---|
| 248 | hasDefaultScalar= isJust (find ((varTopic ==) . paramName) params) |
|---|
| 249 | |
|---|
| 250 | boundHash <- bindHash namedForSlurp slurpNamed -- put leftover named args in %_ |
|---|
| 251 | (boundArray, newSlurpLimit) <- bindArray posForSlurp slurpPos slurpLimit |
|---|
| 252 | boundScalar <- return $ defaultScalar `zip` (givenInvs ++ givenArgs) -- put, uh, something in $_ |
|---|
| 253 | |
|---|
| 254 | let newParams = params \\ (map fst newBindings); |
|---|
| 255 | newBindings = concat |
|---|
| 256 | [ bindings -- Existing bindings |
|---|
| 257 | , boundInv -- Newly bound invocants |
|---|
| 258 | , boundNamed -- ...nameds |
|---|
| 259 | , boundPos -- ...positional |
|---|
| 260 | , boundHash -- ...*%hash |
|---|
| 261 | , boundArray -- ...*@array |
|---|
| 262 | , boundScalar -- ...*$scalar |
|---|
| 263 | ] |
|---|
| 264 | |
|---|
| 265 | return sub |
|---|
| 266 | { subBindings = newBindings |
|---|
| 267 | , subParams = newParams |
|---|
| 268 | , subSlurpLimit = newSlurpLimit |
|---|
| 269 | } |
|---|