root/src/Pugs/Bind.hs

Revision 15297, 11.6 kB (checked in by audreyt, 20 months ago)

* Revert the previous patch; everything back to normal.

  • Property svn:mime-type set to text/plain; charset=UTF-8
  • Property svn:eol-style set to native
Line 
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
12module Pugs.Bind (
13    bindParams, bindSomeParams,
14) where
15import Pugs.Internals
16import Pugs.AST
17import Pugs.Types
18
19{-|
20Contains either a valid value of @a@ (@Right@), or a @String@ error
21message (@Left@).
22-}
23type MaybeError a = Either String a
24
25{-|
26Match up named arguments with named parameters, producing a list of new
27bindings, and lists of remaining unbound args and params.
28-}
29bindNames :: [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
35bindNames 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
45emptyHashExp :: Exp
46emptyHashExp  = Val $ VList [] -- VHash $ vCast $ VList []
47
48emptyArrayExp :: Exp
49emptyArrayExp = Val $ VList [] -- VArray $ vCast $ VList []
50
51{-|
52Create a binding from the slurpy hash parameter (e.g. @\*%_@) to a hash
53containing all the remaining named arguments. If multiple slurpy hashes
54are given, only the first gets the arguments--the rest get an empty hash.
55Used by 'bindSomeParams'.
56-}
57bindHash :: [Exp]   -- ^ Named arguments (pair expressions) that were not
58                    --     consumed by explicit named parameters
59         -> [Param] -- ^ List of slurpy hash parameters
60         -> MaybeError Bindings
61bindHash [] []          = return []
62bindHash [] [p]         = return [ (p, emptyHashExp) ]
63bindHash (v:_) []       = fail $ "Named argument found where no matched parameter expected: " ++ show (unwrapNamedArg v)
64bindHash vs (p:ps@(_:_))= do
65    first <- (bindHash vs [p])
66    return $ first ++ (ps `zip` repeat emptyHashExp)
67bindHash vs [p]         = return [ (p, Syn "\\{}" [Syn "," vs]) ] -- XXX cast to Hash
68
69{-|
70Create bindings from the slurpy scalar and array parameters to the remaining
71positional arguments. The first slurpy array param gets all of the remaining
72args; subsequent slurpy array params get an empty array. Slurpy scalars may
73not appear after slurpy array params.
74
75Returns the bindings performed, and the sub's new 'SlurpLimit'.
76
77Mostly 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-}
85bindArray :: [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)
89bindArray 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{-|
104Construct an expression representing an infinite slice of the given
105array expression, beginning at element /n/ (i.e. @\@array\[\$n...\]@).
106
107Used by 'doBindArray' to bind a slurpy array parameter to the rest of
108the slurpable arguments.
109-}
110doSlice :: Exp -- ^ The array expression to slice
111        -> VInt -- ^ Index of the first element in the resulting slice (/n/)
112        -> Exp
113doSlice v n = Syn "[...]" [v, Val $ VInt n]
114
115-- XXX - somehow force failure
116{-|
117Construct an expression representing element /n/ in the given array
118expression (i.e. @\@array\[\$n\]@).
119
120Used by 'doBindArray' to bind a particular slurpy scalar parameter to one of
121the slurpable arguments.
122-}
123doIndex :: Exp -> VInt -> Exp
124doIndex v n = Syn "[]" [Syn "val" [v], Val $ VInt n]
125
126doBindArray :: Exp -> (Bindings, VInt) -> (Param, VarSigil) -> MaybeError (Bindings, VInt)
127doBindArray _ (xs, -1) (p, SArray) = return (((p, emptyArrayExp):xs), -1)
128doBindArray _ (_, -1)  (p, _) = fail $ "Slurpy array followed by slurpy scalar: " ++ show p
129doBindArray v (xs, n)  (p, SArray) = return (((p, doSlice v n):xs), -1)
130doBindArray 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
136isNamedArg :: Exp -> Bool
137isNamedArg (Syn "named" [(Val (VStr _)), _]) = True
138isNamedArg (Syn "named" [Ann _ (Val (VStr _)), _]) = True -- should the Ann reach here?
139isNamedArg arg@(Syn "named" _)               = error $ "malformed named arg: " ++ show arg
140isNamedArg _                                 = False
141
142unwrapNamedArg :: Exp -> (ID, Exp)
143unwrapNamedArg (Syn "named" [(Val (VStr key)), val]) = (cast key, val)
144unwrapNamedArg (Syn "named" [Ann _ (Val (VStr key)), val]) = (cast key, val) -- (see comment in isNamedArg)
145unwrapNamedArg x = error $ "not a well-formed named arg: " ++ show x
146
147{-|
148Bind parameters to a callable, then verify that the binding is complete
149(i.e. all mandatory params are bound; all unspecified params have default
150bindings).
151
152Uses 'bindSomeParams' to perform the initial binding, then uses
153'finalizeBindings' to check all required params and give default values to
154any unbound optional ones. Once this is complete, /everything/ should be
155bound.
156
157Note that while 'bindParams' produces values /representing/ the bindings from
158params to args, it does not actually introduce any symbols--that occurs later
159on in the call process.
160-}
161bindParams :: 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'
167bindParams sub Nothing [inv]
168    | (p:_) <- subParams sub, isInvocant p
169    = bindParams sub (Just inv) []
170bindParams sub invExp argsExp = do
171    case bindSomeParams sub invExp argsExp of
172        Left errMsg -> Left errMsg
173        Right boundSub -> finalizeBindings boundSub
174
175{-|
176Verify that all invocants and required parameters are bound, and give default
177values to any unbound optional parameters.
178-}
179finalizeBindings :: VCode -> MaybeError VCode
180finalizeBindings 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{-|
218Take a code object and lists of invocants and arguments, and produce (if
219possible) a new 'VCode' value representing the same code object, with as many
220parameters bound as possible (using the given invocants and args).
221-}
222bindSomeParams :: 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
227bindSomeParams 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        }
Note: See TracBrowser for help on using the browser.