root/src/Pugs/Parser.hs

Revision 21763, 82.6 kB (checked in by lwall, 2 months ago)

s/q:code/quasi/ plus various t/ buglets

  • Property svn:mime-type set to text/plain; charset=UTF-8
  • Property svn:eol-style set to native
</
Line 
1{-# OPTIONS_GHC -cpp -fglasgow-exts -funbox-strict-fields -fno-full-laziness -fno-cse -fallow-overlapping-instances #-}
2
3{-|
4    Higher-level parser for building ASTs.
5
6>   I sang of leaves, of leaves of gold, and leaves of gold there grew:
7>   Of wind I sang, a wind there came and in the branches blew.
8>   Beyond the Sun, beyond the Moon, the foam was on the Sea,
9>   And by the strand of Ilmarin there grew a golden Tree...
10-}
11
12module Pugs.Parser (
13    ruleBlockBody,
14    possiblyExit,
15    module Pugs.Lexer,
16    module Pugs.Parser.Types,
17    module Pugs.Parser.Unsafe,
18    module Pugs.Parser.Operator,
19
20    -- Circularity: Used in Pugs.Parser.Operator
21    parseTerm, parseNoParenArgList, ruleSubName, ruleSigil,
22
23    -- Circularity: Used in Pugs.Parser.Literal
24    ruleExpression, retInterpolatedBlock,
25    ruleArraySubscript, ruleHashSubscript, ruleCodeSubscript,
26    ruleInvocationParens, verbatimVarNameString, ruleVerbatimBlock, retVerbatimBlock,
27    ruleBlockLiteral, ruleDoBlock, regularVarName, regularVarNameForSigil, ruleNamedMethodCall,
28
29    genParamEntries
30) where
31import Pugs.Internals
32import Pugs.AST
33import qualified Pugs.Exp as Exp
34import Pugs.Types
35import Pugs.Version (versnum)
36import Pugs.Lexer
37import Pugs.Rule
38
39import Pugs.Parser.Types
40import Pugs.Parser.Unsafe
41import Pugs.Parser.Export
42import Pugs.Parser.Operator
43import Pugs.Parser.Doc
44import Pugs.Parser.Literal
45import Pugs.Parser.Util
46import qualified Data.Map as Map
47import qualified Data.Set as Set
48
49-- Lexical units --------------------------------------------------
50
51ruleBlock :: RuleParser BlockInfo
52ruleBlock = do
53    lvl <- gets s_bracketLevel
54    case lvl of
55        StatementBracket    -> ruleBlock'
56        _                   -> lexeme ruleVerbatimBlock
57    where
58    ruleBlock' = do
59        rv <- ruleVerbatimBlock
60        -- Implementation of 'line-ending } terminates statement'
61        -- See L<S04/Statement-ending blocks>.
62        -- We are now at end of closing '}'. Mark the position...
63        prevPos <- getPosition
64        -- Skip whitespaces.  If we go into another line,
65        -- then it's statement-level break
66        whiteSpace
67        currPos <- getPosition
68        if sourceLine prevPos == sourceLine currPos then return rv else do
69            -- Manually insert a ';' symbol here!
70            insertIntoPosition ";"
71            -- Register that this is an eol-block, thus can't be hash composers
72            retBlockWith (Ann (Prag [MkPrag "eol-block" 0])) rv
73
74ruleVerbatimBlock :: RuleParser BlockInfo
75ruleVerbatimBlock = verbatimRule "block" $ do
76    block <- verbatimBraces ruleBlockBody
77    retBlockWith (Syn "block" . (:[])) block
78
79ruleEmptyExp :: RuleParser Exp
80ruleEmptyExp = (<?> "") . expRule $ do
81    symbol ";"
82    return emptyExp
83
84ruleBlockBody :: RuleParser BlockInfo
85ruleBlockBody = localBlock $ do
86    whiteSpace
87    ver     <- option "6" (try (symbol "use" >> rulePerlVersion))
88    case ver of
89        ('5':_) -> do
90            let chunk = many1 (noneOf "{}") <|> verbatimBraces block
91                block = fmap (\x -> ('{':x) ++ "}") body
92                body  = fmap concat (many chunk)
93            p5code <- body
94            return (Syn "block-perl5" [Val (VStr p5code)])
95        _       -> do
96            pre     <- many ruleEmptyExp
97            body    <- option emptyExp ruleStatementList
98            post    <- many ruleEmptyExp
99            whiteSpace
100            return $ foldl1 mergeStmts (pre ++ [body] ++ post)
101
102{-|
103Match a single statement (not including any terminating semicolon).  A
104statement consists of a single 'ruleExpression', followed by an optional
105statement-modifier (e.g. @if $foo@ or @for \@baz@).
106
107One of the sub-rules used by 'ruleStatementList'.
108-}
109ruleStatement :: RuleParser Exp
110ruleStatement = do
111    exp <- ruleExpression
112    f   <- ruleStatementModifier <?> ""
113    f exp
114
115ruleStatementModifier :: RuleParser (Exp -> RuleParser Exp)
116ruleStatementModifier = verbatimRule "statement modifier" . option return $ choice
117    [ s_postConditional
118    , s_postLoop
119    , s_postIterate
120    ]
121
122ruleStatementList :: RuleParser Exp
123ruleStatementList = rule "statements" .
124    enterBracketLevel StatementBracket .
125        sepLoop $ do
126            optional $ try (ruleVerbatimIdentifier >> char ':' >> mandatoryWhiteSpace)
127            choice
128                [ noSep     ruleDocBlock
129                , nonSep    ruleBlockDeclaration
130                , semiSep   ruleDeclaration
131                , nonSep    ruleConstruct
132                , semiSep   ruleStatement
133                ]
134    where
135    nonSep  = doSep many  -- must be followed by 0+ semicolons
136    semiSep = doSep many1 -- must be followed by 1+ semicolons
137    noSep r = fmap (\x -> (x, False)) r
138    doSep sepCount r = do
139        exp <- r
140        terminate <- option True $ do
141            (sepCount (symbol ";") <?> "")
142            return False
143        return (exp, terminate)
144    sepLoop rule = do
145        whiteSpace
146        (eof >> return Noop) <|> do
147            (exp, terminate) <- rule
148            if terminate then return exp else do
149            rest <- option Noop (sepLoop rule)
150            return $ exp `mergeStmts` rest
151
152-- Declarations ------------------------------------------------
153
154ruleBlockDeclaration :: RuleParser Exp
155ruleBlockDeclaration = rule "block declaration" $ choice
156    [ ruleRuleDeclaration
157    , ruleSubDeclaration
158    , ruleClosureTrait False
159    , rulePackageBlockDeclaration
160    ]
161
162ruleDeclaration :: RuleParser Exp
163ruleDeclaration = rule "declaration" $ choice
164    [ rulePackageDeclaration
165    , ruleMemberDeclaration
166    , ruleTraitDeclaration
167    , ruleUseDeclaration
168    , ruleNoDeclaration
169    , ruleInlineDeclaration
170    , ruleRequireDeclaration
171    , ruleTrustsDeclaration
172    ]
173
174data SubModifier = ImplicitNil | ImplicitMulti | ImplicitProto deriving (Eq)
175
176ruleSubHead :: RuleParser (SubModifier, SubType, String)
177ruleSubHead = rule "subroutine head" $ do
178    prefix <- choice
179        [ symbol "multi" >> return ImplicitMulti
180        , symbol "proto" >> return ImplicitProto
181        , return ImplicitNil
182        ]
183
184    -- You're allowed to omit the "sub":
185    --   multi sub foo (...) {...}      # legal
186    --         sub foo (...) {...}      # legal, too
187    let implicitSub | ImplicitNil <- prefix = pzero
188                    | otherwise             = return SubRoutine
189    styp    <- choice
190        [ do symbol "sub"
191             return SubRoutine
192        , do symbol "coro"
193             return SubCoroutine
194        , do (symbol "submethod" <|> symbol "method")
195             return SubMethod
196        , do symbol "macro"
197             return SubMacro
198        , do symbol "subset"
199             return _SubSet_
200        ] <|> implicitSub
201    name    <- ruleSubName
202    return (prefix, styp, name)
203
204-- XXX - Kluged up way to mark a subset parsed as a sub
205_SubSet_ :: SubType
206_SubSet_ = SubPrim
207
208-- | Scope, context, isMulti, styp, name
209type SubDescription = (Scope, String, SubModifier, SubType, String)
210
211ruleSubScopedWithContext :: RuleParser SubDescription
212ruleSubScopedWithContext = tryRule "scoped subroutine with context" $ do
213    scope   <- ruleScope
214    cxt     <- identifier
215    (isMulti, styp, name) <- ruleSubHead
216    return (scope, cxt, isMulti, styp, name)
217
218ruleSubScoped :: RuleParser SubDescription
219ruleSubScoped = tryRule "scoped subroutine" $ do
220    scope <- ruleScope
221    (isMulti, styp, name) <- ruleSubHead
222    return (scope, "Any", isMulti, styp, name)
223
224ruleSubGlobal :: RuleParser SubDescription
225ruleSubGlobal = tryRule "global subroutine" $ do
226    (isMulti, styp, name) <- ruleSubHead
227    return (SOur, "Any", isMulti, styp, name)
228
229ruleRuleDeclaration :: RuleParser Exp
230ruleRuleDeclaration = rule "rule declaration" $ do
231    (mod, withAdvs, name) <- try $ do
232        mod  <- symbol "proto" <|> symbol "multi" <|> return ""
233        advs <- ruleRegexDeclarator
234        fmap ((,,) mod advs) identifier
235    optional (ruleSubParameters ParensMandatory)
236    adverbs <- fmap withAdvs ruleQuoteAdverbs
237    skipMany (symbol "is" >> regularAdverbPair) -- XXX - context
238    if mod == "proto" then return emptyExp else do
239    ch      <- char '{'
240    expr    <- rxLiteralAny adverbs ch (balancedDelim ch)
241    unsafeEvalExp (_Sym SOur ('<':'*':name) mempty (Syn "rx" [expr, adverbs]) Noop)
242    insertIntoPosition $ "method " ++ name ++ " ($_) { $_ ~~ m/<" ++ name ++ ">/ };"
243    return emptyExp
244
245rulePackageBlockDeclaration :: RuleParser Exp
246rulePackageBlockDeclaration = rule "package block declaration" $ do
247    -- scope <- option Nothing $ fmap Just ruleScope
248    rv <- try $ do
249        optional ruleScope -- XXX - not handled yet
250        rv <- rulePackageHead
251        lookAhead (char '{')
252        return rv
253    case rv of
254        Right (_, kind, pkgVal, env) -> do
255            block   <- verbatimBraces ruleBlockBody
256            env'    <- ask
257            putRuleEnv env'{ envPackage = envPackage env }
258            retInterpolatedBlock =<< retBlockWith (\body -> Syn "namespace" [kind, pkgVal, body]) block
259        Left err -> fail err
260
261rulePackageDeclaration :: RuleParser Exp
262rulePackageDeclaration = rule "package declaration" $ do
263    -- scope <- option Nothing $ fmap Just ruleScope
264    rv <- try $ do
265        optional ruleScope -- XXX - not handled yet
266        rulePackageHead
267    case rv of
268        Right (_, kind, pkgVal, _) -> return $ Syn "package" [kind, pkgVal]
269        Left err -> fail err
270
271rulePackageHead :: RuleParser (Either String (String, Exp, Exp, Env))
272rulePackageHead = do
273    scope   <- option Nothing $ fmap Just ruleScope
274    sym     <- choice $ map symbol (words "package module class role grammar")
275    name    <- ruleQualifiedIdentifier
276    optional ruleVersionPart -- v
277    optional ruleAuthorPart  -- a
278    whiteSpace
279    env <- ask
280    newName <- case scope of
281        Just SOur -> return $ cast (envPackage env) ++ "::" ++ name
282        Nothing   -> return name
283        _         -> fail "I only know about package- and global-scoped classes. Sorry."
284    traits  <- many $ ruleTrait ["is", "does"]
285    let pkgClass = case sym of
286                       "package" -> "Package"
287                       "module"  -> "Module"
288                       "class"   -> "Class"
289                       "role"    -> "Class" -- XXX - Wrong - need metamodel
290                       "grammar" -> "Grammar"
291                       _ -> fail "bug"
292        mixinRoles = nub ([ cls | ("does", cls) <- traits])
293        parentClasses = nub ("Object":[ cls | ("is", cls) <- traits, cls /= "also" ])
294    case () of
295        _ | elem name parentClasses -> do
296            return (Left $ "Circular class inheritance detected for " ++ sym ++ " '" ++ name ++ "'")
297        _ | elem name mixinRoles -> do
298            return (Left $ "Circular role composition detected for " ++ sym ++ " '" ++ name ++ "'")
299        _ -> do
300            unsafeEvalExp (newPackage pkgClass newName parentClasses mixinRoles)
301            modify $ \state -> state
302                { s_env = (s_env state)
303                    { envPackage = cast newName
304                    }
305                , s_dynParsers = MkDynParsersEmpty
306                }
307            let pkgVal = Val . VStr $ newName
308                kind   = Val . VStr $ sym
309            return $ Right (newName, kind, pkgVal, env)
310
311ruleTraitsIsOnly :: RuleParser [String]
312ruleTraitsIsOnly = fmap (map snd) . many $ ruleTrait ["is"]
313
314ruleSubDeclaration :: RuleParser Exp
315ruleSubDeclaration = rule "subroutine declaration" $ do
316    -- namePos <- getPosition
317    (scope, typ, isMulti, styp, name) <- choice
318        [ ruleSubScopedWithContext
319        , ruleSubScoped
320        , ruleSubGlobal
321        ]
322    optional $ do { symbol "handles"; ruleExpression }
323    assoc   <- option A_left . try $ do
324        symbol "is"
325        symbol "assoc"
326        lit <- parens qLiteral
327        case unwrap lit of
328            Val (VStr str) -> case str of
329                "left"  -> return A_left
330                "right" -> return A_right
331                "non"   -> return A_non
332                "chain" -> return A_chain
333                "list"  -> return A_list
334                _       -> fail $ "Invalid associativity: " ++ str
335            _   -> fail $ "Invalid associativity: " ++ show lit
336    let returnsOrOf = try (ruleBareTrait "returns" <|> ruleBareTrait "of")
337    typ'    <- option typ returnsOrOf
338
339    -- Here if it's a subset-parsed-as-sub, escape now
340    if styp == _SubSet_ then skipMany (symbol "where" >> parseTerm) >> return emptyExp else do
341
342    formal  <- option Nothing $ ruleSubParameters ParensMandatory
343    typ''   <- option typ' returnsOrOf
344    traits  <- ruleTraitsIsOnly
345
346    env <- ask
347    let pkg = cast (envPackage env)
348        nameQualified | ':' `elem` name     = name
349                      | isGlobal            = name
350                      | isBuiltin           = (head name:'*':tail name)
351                      | otherwise           = name
352        isGlobal = '*' `elem` name
353        isBuiltin = ("builtin" `elem` traits)
354        isExported = ("export" `elem` traits)
355        mkMulti | isMulti /= ImplicitNil = \x -> (cast x){ v_longname = _cast (cast signature) }
356                | otherwise              = cast
357        mkSym sub n x = Sym scope (mkMulti n) mempty (Syn "sub" [Val sub]) x
358        var = mkMulti nameQualified
359        signature = self ++ paramsFor styp formal (maybe [] id formal)
360        self | styp > SubMethod = []
361             | Just (prm:_) <- formal, isInvocant prm = []
362             | otherwise = [selfParam . cast $ envPackage env]
363
364    -- We have the prototype now; install it immediately!
365    --   fill in what we can about the sub before getting the block (below)
366    let sub@(VCode template) = VCode $ mkCode
367            { isMulti       = isMulti /= ImplicitNil
368            , subName       = cast nameQualified
369            , subOuterPads  = envLexPads env
370            , subInnerPad   = emptyPad
371            , subParams     = signature
372            , subPackage    = envPackage env
373            , subType       = if "primitive" `elem` traits
374                then SubPrim else styp
375            , subAssoc      = case v_categ var of
376                C_infix -> assoc
377                _       -> ANil
378            , subReturns    = mkType typ''
379            , subLValue     = "rw" `elem` traits
380            , subBindings   = []
381            , subSlurpLimit = []
382            , subCont       = Nothing
383            }
384
385    -- Don't add the sub if it's unsafe and we're in safemode (XXX repeated below)
386    newPad <- if ("unsafe" `elem` traits && safeMode) then return (mkPad []) else do
387        -- This is ignored for multi-dispatch; see Pugs.Eval.Var comment "PROTO"
388        unsafeEvalLexDiff (mkSym sub nameQualified Noop)
389            `finallyM` clearDynParsers
390
391    -- Generate init pad for each of our params, as well as for ourselves...
392    paramsPad   <- genParamEntries styp signature
393    modify $ \s -> s{ s_protoPad = paramsPad }
394    block       <- ruleBlock
395
396    let (fun, names, _) = extractNamedPlaceholders styp formal (bi_body block)
397
398    -- Check for placeholder vs formal parameters
399    when (isJust formal && (not.null) names) $
400        fail "Cannot mix placeholder variables with formal parameters"
401
402    env <- ask
403
404    let template' = template
405                { subBody       = case isMulti of
406                    ImplicitProto   -> fun -- XXX - Give Proto the tie-breaker status?
407                    _               -> fun
408                , subOuterPads  = envLexPads env
409                , subInnerPad   = bi_pad block
410                , subTraitBlocks= bi_traits block (subTraitBlocks template)
411                }
412        sub = VCode template'
413   
414    -- Don't add the sub if it's unsafe and we're in safemode.
415    if "unsafe" `elem` traits && safeMode then return (Var var) else do
416    (`finallyM` clearDynParsers) $ if not (isLexicalVar var)
417        then do unsafeEvalExp $ mkSym sub nameQualified (Var var)
418        else do
419            let doExportCode = if not isExported then return (Var var) else do
420                    -- we mustn't perform the export immediately upon parse, because
421                    -- then only the first consumer of a module will see it. Instead,
422                    -- make a note of this symbol being exportable, and defer the
423                    -- actual symbol table manipulation to opEval.
424                    -- %*INC<This::Package><exports><&this_sub> = expression-binding-&this_sub
425                    --    ==>
426                    -- %This::Package::EXPORTS<&this_sub> = expression-binding-&this_sub
427                    let VCode cv = sub
428                        (exportedSub, exportedName)
429                            -- "method foo is export" is exported into "multi foo" here.
430                            | SubMethod <- styp = (multiCode, cast var{ v_longname = _cast (cast multiSig) })
431                            | otherwise         = (sub, cast var)
432                            where
433                            multiSig  = map (\x -> x{ isInvocant = False }) (subParams cv)
434                            multiCode = VCode cv
435                                { isMulti   = True
436                                , subParams = multiSig
437                                }
438                    unsafeEvalExp $ Syn "="
439                        [ Syn "{}" [_Var ("%" ++ pkg ++ "::EXPORTS"), Val $ VStr exportedName]
440                        , Val exportedSub
441                        ]
442                    return (Var var)
443            case lookupPad var newPad of
444                Just entry  -> do
445                    Val (VCode code) <- unsafeEvalExp (Syn "sub" [Val sub])
446                    let entry'  = entry{ pe_proto = cv' }
447                        cv'     = MkRef (ICode code)
448                    addBlockPad (adjustPad (const entry') var newPad)
449                    result <- doExportCode
450                    case entry' of
451                        PEConstant{}    -> return result
452                        _               -> return $! unsafePerformSTM $! do
453                            rv  <- writePadEntry entry' cv'
454                            return (rv `seq` result)
455                _           -> error "Impossible: Disappearing entry?"
456
457ruleSubNamePossiblyWithTwigil :: RuleParser String
458ruleSubNamePossiblyWithTwigil = tryVerbatimRule "subroutine name" $ do
459    twigil  <- ruleTwigil
460    name    <- ruleOperatorName <|> ruleQualifiedIdentifier
461    return $ ('&':twigil) ++ name
462
463ruleSubName :: RuleParser String
464ruleSubName = verbatimRule "subroutine name" $ do
465    twigil  <- option "" (string "*")
466    name <- ruleOperatorName <|> ruleQualifiedIdentifier
467    return $ ('&':twigil) ++ name
468
469ruleOperatorName :: RuleParser String
470ruleOperatorName = verbatimRule "operator name" $ do
471    categ   <- choice (map (try . string) grammaticalCategories) `tryLookAhead` (oneOf "\xAB<{")
472    name    <- do
473        -- char ':'
474        sub <- ruleHashSubscript
475        -- Not exactly un-evil
476        let (Syn "{}" [_, expr]) = sub (Val VUndef)
477            exprs = case expr of
478                Syn "," es  -> es
479                e           -> [e]
480        return $ unwords [ str | Val (VStr str) <- exprs ]
481    return $ categ ++ name
482
483
484ruleSubParameters :: ParensOption -> RuleParser (Maybe [Param])
485ruleSubParameters wantParens = rule "subroutine parameters" $ do
486    rv <- ruleParamList wantParens (ruleFormalParam FormalsComplex)
487    case rv of
488        Just (invs:args:_)  -> return . Just $ map setInv invs ++ args
489        _                   -> return Nothing
490    where
491    setInv e = e { isInvocant = True }
492
493ruleFormalParam :: FormalsOption -> RuleParser Param
494ruleFormalParam opt = rule "formal parameter" $ do
495    typ     <- option "" $ ruleType
496    optional $ char '|'  -- XXX hack to parse arglist (|$foo)
497    sigil1  <- option "" $ choice . map symbol $ words " : * "
498    name    <- ruleParamName -- XXX support *[...]
499    sigil2  <- option "" $ choice . map symbol $ words " ? ! "
500    traits  <- ruleTraitsIsOnly
501    -- sigil' is the canonical form of sigil1 and sigil2, e.g.
502    --   $foo is required -->  !$foo
503    --  :$foo             --> ?:$foo
504    --  :$foo!            --> !:$foo
505    --  :$foo is required --> !:$foo
506    isDefaultSpecified <- option False $ do
507        lookAhead $ symbol "="
508        return True
509    let isOptional = isDefaultSpecified
510                  || sigil2 == "?"
511                  || sigil1 == ":" && sigil2 /= "!" && "required" `notElem` traits
512                  || "optional" `elem` traits
513    let sigil'      = (if isOptional then '?' else '!'):sigil1
514        defaultExp  = case name of
515            ('@':_) -> Val (VList [])
516            ('%':_) -> Val (VList [])
517            _       -> Noop
518
519    -- XXX - RIGHT HERE, add this one to CompPad?
520    modify $ \state -> state
521        { s_knownVars = Map.insert (cast name) (fromJust . envCompPad $ s_env state) (s_knownVars state) }
522
523    rv <- case opt of
524        FormalsSimple   -> option emptyExp $ do
525            pseudoAssignment (cxtOfSigilVar var) (Val (VType (if null typ then typeOfSigilVar var else mkType typ)))
526            where
527            var = cast name
528        FormalsComplex  -> ruleParamDefault
529    when (opt == FormalsComplex) . optional $ do
530        symbol "-->"
531        ruleParamList ParensOptional $ choice
532            [ ruleType `sepBy1` symbol "of"
533            , ruleFormalParam FormalsComplex >> return []
534            ]
535    let exp = case rv of
536            Noop -> defaultExp
537            _    -> rv
538    return $ foldr appTrait (buildParam typ sigil' name exp) traits
539    where
540    appTrait "rw"   x = x { isWritable = True }
541    appTrait "copy" x = x { isLValue = False, isWritable = True }
542    appTrait "lazy" x = x { isLazy = True }
543    appTrait "context" x = x { paramName = (paramName x){ v_twigil = TImplicit } }
544    appTrait _      x = x -- error "unknown trait"
545
546ruleParamDefault :: RuleParser Exp
547ruleParamDefault = rule "default value" $ option emptyExp $ do
548    symbol "="
549    parseExpWithItemOps
550
551ruleTrustsDeclaration :: RuleParser Exp
552ruleTrustsDeclaration = do
553    symbol "trusts"
554    lexeme ruleQualifiedIdentifier
555    return emptyExp
556
557ruleTraitDeclaration :: RuleParser Exp
558ruleTraitDeclaration = try $ do
559    -- XXX horrible hack! "is eval(...), ..." should *not* be parsed as a trait
560    -- declaration. So we check whether we're really statement-level, i.e.
561    --   is eval(...) [eof]   # trait
562    --   is eval(...);        # trait
563    --   is eval(...) }       # trait
564    --   is eval(...), ...    # sub call
565    (aux, trait) <- ruleTrait ["is", "does"]
566    lookAhead (eof <|> (oneOf ";}" >> return ()))
567    pkg <- asks envPackage
568    let meta = _Var (':':'*':cast pkg)
569        expMeta = Syn "="
570            [ Syn "{}" [meta, Val (VStr aux)]
571            , Syn "," [Syn "@{}" [Syn "{}" [meta, Val (VStr aux)]], Val (VStr trait)]
572            ]
573        addDoes | "does" <- aux = Stmts (App (_Var "&HOW::does") (Just meta) [Val (VStr trait)])
574                | otherwise     = id
575    unsafeEvalExp $ Syn "if"
576        [ meta
577        , addDoes expMeta
578        , App (_Var "&die") Nothing [Val (VStr $ "Can't add trait to non-class package: " ++ show pkg)]
579        ]
580    return Noop
581
582ruleMemberDeclaration :: RuleParser Exp
583ruleMemberDeclaration = do
584    symbol "has"
585    typ  <- option "" $ do
586        optional (string "::")
587        lexeme $ choice
588            [ ruleQualifiedIdentifier
589            -- ::?CLASS, ::?ROLE, etc.
590            , char '?' >> ruleQualifiedIdentifier >> fmap cast (asks envPackage)
591            ]
592    attr <- ruleVarName
593    (sigil:twigil:key) <- case attr of
594        (_:'.':_)   -> return attr
595        (_:'!':_)   -> return attr
596        (x:xs@(twigil:_))
597            | (isAlpha twigil) || twigil == '_'
598                    -> return (x:'!':xs)
599        _           -> fail $ "Invalid member variable name '" ++ attr ++ "'"
600    traits  <- ruleTraitsIsOnly
601    optional $ do { symbol "handles"; ruleExpression }
602    def     <- ruleParamDefault
603    env     <- ask
604    let self = selfParam $ cast (envPackage env)
605    paramsPad  <- genParamEntries SubMethod [self]
606    -- manufacture an accessor, and register this slot into metaobject
607    let sub = mkPrim
608            { isMulti       = False
609            , subName       = cast name
610            , subReturns    = if null typ then typeOfSigil (cast sigil) else mkType typ
611            , subBody       = fun
612            , subParams     = [self]
613            , subInnerPad   = paramsPad
614            , subLValue     = "rw" `elem` traits
615            , subType       = SubMethod
616            }
617        exp = Syn "sub" [Val $ VCode sub]
618        name | twigil == '.' = '&':(pkg ++ "::" ++ key)
619             | otherwise     = '&':(pkg ++ "::" ++ (twigil:key))
620        fun = Syn (sigil:"{}") [Ann (Cxt (cxtOfSigil $ cast sigil)) (Syn "{}" [_Var "$__SELF__", Val (VStr key)])]
621        pkg = cast (envPackage env)
622        metaObj = _Var (':':'*':pkg)
623        attrDef = Syn "{}" [Syn "{}" [metaObj, Val (VStr "attrs")], Val (VStr key)]
624    unsafeEvalExp (_Sym SOur name mempty exp (Syn "=" [attrDef, def]))
625    return emptyExp
626
627{-|
628Match a @no@ declaration, i.e. the opposite of @use@ (see
629'ruleUseDeclaration').
630
631Works by matching \'@no@\', then trying 'ruleNoVersion' and
632@'ruleUsePackage' False@.
633-}
634ruleNoDeclaration :: RuleParser Exp
635ruleNoDeclaration = rule "no declaration" $ do
636    symbol "no"
637    choice [ ruleNoVersion >> return emptyExp
638              , ruleUsePackage False
639              ]
640    return emptyExp
641
642{-|
643Match a @use@ declaration.
644
645Works by matching \'@use@\', then trying 'ruleUseVersion' and
646@'ruleUsePackage' True@.
647-}
648ruleUseDeclaration :: RuleParser Exp
649ruleUseDeclaration = rule "use declaration" $ do
650    symbol "use"
651    choice [ try ruleUseVersion >> return emptyExp
652           , ruleUsePackage True
653           ]
654
655rulePerlVersion :: RuleParser String
656rulePerlVersion = rule "perl version" $ do
657    optional (string "v" <|> string "Perl-")
658    version <- many1 (choice [ digit, char '.' ])
659    optional ruleAuthorPart
660    {-
661    optional $ do
662        variant <- ruleAuthorPart
663        when (map toLower variant /= "pugs") $ do
664            pos <- getPosition
665            error $ "Perl implementation " ++ tail variant ++ " required--this is only Pugs v" ++ versnum ++ ", stopped at " ++ (show pos)
666    -}
667    return version
668
669{-|
670Match a Perl version number (as part of a @use@ declaration), and abort if
671the version needed is higher than our version.
672-}
673ruleUseVersion :: RuleParser ()
674ruleUseVersion = rule "use version" $ do
675    version <- rulePerlVersion
676    when (version > versnum) $ do
677        pos <- getPosition
678        error $ "Perl v" ++ version ++ " required--this is only v" ++ versnum ++ ", stopped at " ++ (show pos)
679
680{-|
681Match a Perl version number (as part of a @no@ declaration), and abort if
682the version needed is lower than our version.
683-}
684ruleNoVersion :: RuleParser ()
685ruleNoVersion = rule "no version" $ do
686    version <- rulePerlVersion
687    when (version <= versnum) $ do
688        pos <- getPosition
689        error $ "Perls since v" ++ version ++ " too modern--this is v"
690                  ++ versnum ++ ", stopped at " ++ show pos
691
692{-|
693Match the contents of a @use@ or @no@ declaration.
694
695Works by reading the (optional) \'lang\' prefix, then dispatching to either
696'ruleUseJSANModule' or 'ruleUsePerlPackage' as appropriate.
697
698It is assumed that the @use@ or @no@ has already been consumed by
699'ruleUseDeclaration' or 'ruleNoDeclaration'.
700-}
701ruleUsePackage :: Bool -- ^ @True@ for @use@; @False@ for @no@
702               -> RuleParser Exp
703ruleUsePackage use = rule "use package" $ do
704    lang <- ruleUsePackageLang
705    case lang of
706        "jsan" -> if use
707                      then ruleUseJSANModule
708                      else fail "can't 'no' a JSAN module"
709        "jsperl5" -> if use
710                      then ruleUseJSPerl5Module
711                      else fail "can't 'no' a Perl5 module"
712        "java" -> if use
713                      then ruleUseJavaModule
714                      else fail "can't 'no' a Java module"
715        _      -> ruleUsePerlPackage use lang
716    where
717    ruleUsePackageLang = option "perl6" $ try $ do
718        lang <- identifier
719        char ':'
720        notFollowedBy (char ':')
721        return lang
722       
723{-|
724Match the package-name and import-list part of a (non-JSAN) @use@ or @no@
725declaration, then perform the actual import.
726
727The parser itself does not yield a useful value; instead it modifies the
728'Pugs.AST.Internals.Env' part of the parser's state to reflect the results
729of the declaration.  (If the package is a Perl 5 package, the modification
730will be different.)
731
732It is assumed that 'ruleUsePackage' has already consumed the \'lang\' prefix,
733which is passed in as the second argument.
734-}
735ruleUsePerlPackage :: Bool   -- ^ @True@ for @use@; @False@ for @no@
736                   -> String -- ^ \'lang\' prefix (e.g. \"@perl5@\" or
737                             --     \"@perl6@\")
738                   -> RuleParser Exp
739ruleUsePerlPackage use lang = rule "use perl package" $ do
740    -- author and version get thrown away for now
741    (names, _, _) <- rulePackageFullName
742    let name = concat (intersperse "::" names)
743    ruleLoadPerlPackage name use lang
744
745ruleLoadPerlPackage :: String -> Bool -> String -> RuleParser Exp
746ruleLoadPerlPackage pkg use lang = do
747    when use $ do   -- for &no, don't load code
748        env  <- ask
749        env' <- unsafeEvalEnv $ if lang == "perl6"
750            then (App (_Var "&use") Nothing [Val $ VStr pkg])
751            else (App (_Var $ "&require_" ++ lang) Nothing [Val $ VStr pkg])
752        modify $ \state -> state
753            { s_env = env
754                { envGlobal  = envGlobal env'
755                }
756            , s_dynParsers = MkDynParsersEmpty
757            }
758    try (do { verbatimParens whiteSpace ; return emptyExp}) <|> do
759        imp <- option emptyExp ruleExpression
760        let sub = _Var $ ('&':pkg) ++ if use then "::import" else "::unimport"
761
762        Val res <- unsafeEvalExp $ Syn "if"
763            [ sub
764            , App sub (Just $ Val $ VStr $ pkg) [imp]
765            , emptyExp
766            ]
767
768        Val (VList exportList) <- res `seq` unsafeEvalExp $ case lang of
769            -- map { ~$_, ::Pkg.can($_) }, @importlist
770            "perl5" -> App (_Var "&map") Nothing [Syn "sub"
771                [ Val . VCode $ mkPrim
772                    { subBody       = Syn ","
773                        [ App (_Var "&prefix:<~>") (Just $ Var varTopic) []
774                        , App (_Var "&can") (Just $ _Var (':':'*':pkg)) [Var varTopic]
775                        ]
776                    , subParams     = [defaultScalarParam]
777                    , subInnerPad   = defaultScalarPad
778                    }
779                ], imp ]
780            -- %Pkg::EXPORTS.kv
781            _ -> App (_Var "&kv") (Just $ _Var ('%':pkg ++ "::EXPORTS")) []
782
783        let hardcodedScopeFixme = SMy
784            doExportList [] = []
785            doExportList [x] = error $ "doExportList [x]: " ++ show x
786            doExportList (VStr name:ex:xs) =
787                (exportSym hardcodedScopeFixme name ex : doExportList xs)
788            doExportList x = error $ "doExportList x: " ++ show x
789        sequence_ $ doExportList exportList
790        clearDynParsers
791        return emptyExp
792
793{-|
794Match a JSAN module name, returning an appropriate
795sub call 'Pugs.AST.Exp' that will load the module using subs defined in
796@PIL2JS::Internals@.
797
798Used by 'ruleUsePackage' after matching \'@use jsan:@\'.
799
800More info about JSAN can be found at <http://www.openjsan.org/>.
801-}
802ruleUseJSANModule :: RuleParser Exp
803ruleUseJSANModule = do
804    name <- fmap (Val . VStr) (ruleDotOrColonSeparatedModuleName ".")
805    choice
806        [ try $ do
807            verbatimParens whiteSpace
808            return $ App (_Var "&PIL2JS::Internals::use_jsan_module_noimp") Nothing [name]
809        , do
810            exp <- option emptyExp ruleExpression
811            let exp' | exp == emptyExp = []
812                     | otherwise       = [exp]
813            return $ App (_Var "&PIL2JS::Internals::use_jsan_module_imp") Nothing $ name:exp'
814        ]
815
816ruleUseJavaModule :: RuleParser Exp
817ruleUseJavaModule = do
818    name <- ruleDotOrColonSeparatedModuleName "::"
819    ruleLoadPerlPackage name True "java"
820
821ruleDotOrColonSeparatedModuleName :: String -> RuleParser String
822ruleDotOrColonSeparatedModuleName sep = lexeme $ do
823    names <- ruleVerbatimIdentifier `sepBy1` (try (string "::" <|> string "."))
824    optional ruleVersionPart
825    optional ruleAuthorPart
826    return . concat $ intersperse sep names
827
828{-|
829Match a perl5 module for js backend, returning an appropriate
830sub call 'Pugs.AST.Exp' that will load the module using subs defined in
831@PIL2JS::Internals@.
832
833-}
834ruleUseJSPerl5Module :: RuleParser Exp
835ruleUseJSPerl5Module = do
836    name <- fmap (Val . VStr) (ruleDotOrColonSeparatedModuleName "::")
837   
838    choice
839        [ try $ do
840            verbatimParens whiteSpace
841            return $ App (_Var "&PIL2JS::Internals::use_perl5_module_noimp") Nothing [name]
842        , do
843            exp <- option emptyExp ruleExpression
844            let exp' | exp == emptyExp = []
845                     | otherwise       = [exp]
846            return $ App (_Var "&PIL2JS::Internals::use_perl5_module_imp") Nothing $ name:exp'
847        ]
848       
849{-|
850Match a full package name, consisting of:
851
852* A short name, optionally delimited by double colons (@::@)
853
854* An optional version specification
855
856* An optional author specification (e.g. @cpan:JRANDOM@)
857-}
858rulePackageFullName :: RuleParser ( [String]
859                                  , (Maybe String)
860                                  , (Maybe String)
861                                  )
862rulePackageFullName = do
863    name    <- ruleDelimitedIdentifier "::"
864    version <- option Nothing $ fmap Just ruleVersionPart
865    author  <- option Nothing $ fmap Just ruleAuthorPart
866    whiteSpace
867    return (name, version, author)
868
869-- | The version part of a fully-qualified package name.
870ruleVersionPart :: RuleParser String
871ruleVersionPart = do -- version - XXX
872    char '-'
873    str <- many (choice [ digit, char '.', char '(', char ')' ])
874    return ('-':str)
875
876-- | The author part of a fully-qualified package name.
877ruleAuthorPart :: RuleParser String
878ruleAuthorPart = do -- author - XXX
879    char '-'
880    -- this will break if you specify an author AND an imports list
881    str <- many1 (satisfy (/= ';'))
882    return ('-':str)
883{- end of ruleUseDeclaration -}
884
885ruleInlineDeclaration :: RuleParser Exp
886ruleInlineDeclaration = rule "inline declaration" $ do
887    symbol "inline"
888    args <- ruleExpression
889    case args of
890        App (Var var) Nothing exp | var == cast "&infix:=>" -> do
891            return $ Syn "inline" exp
892        _ -> fail "not yet parsed"
893
894{-|
895Match a @require@ declaration, returning a sub call 'Pugs.AST.Exp' that will
896load the package at runtime.
897
898(This should probably be merged with 'ruleUseDeclaration' & friends, if
899anybody has some tuits.)
900-}
901ruleRequireDeclaration :: RuleParser Exp
902ruleRequireDeclaration = tryRule "require declaration" $ do
903    symbol "require"
904    (names, _, _) <- rulePackageFullName
905    return $ App (_Var "&require") Nothing [Val . VStr $ concat (intersperse "::" names)]
906
907ruleDoBlock :: RuleParser Exp
908ruleDoBlock = rule "do block" $ do
909    sym  <- symbol "do" <|> symbol "gather"
910    tree <- choice
911        [ ruleDoOnceBlock
912        , ruleBlockDeclaration
913        , ruleDeclaration
914        , ruleConstruct
915        , ruleStatement
916        ]
917    return $ if sym == "gather"
918        then App (_Var "&gather") Nothing [Val . VCode $ mkSub { subBody = tree }]
919        else tree
920    where
921    ruleDoOnceBlock = do
922        rv <- ruleBareOrPointyBlockLiteralWithoutDefaultParams
923        notFollowedBy (ruleStatementModifier >> return ' ')
924        return $ Syn "postwhile" [Val (castV False), rv]
925
926ruleClosureTrait :: Bool -> RuleParser Exp
927ruleClosureTrait rhs = tryRule "closure trait" $ do
928    let rhsTraits = words " BEGIN CHECK INIT START ENTER FIRST "
929    let names = words " BEGIN CHECK INIT END START ENTER LEAVE KEEP UNDO FIRST NEXT LAST PRE POST CATCH CONTROL"
930    name    <- choice $ map symbol $ names
931    block   <- ruleBlock
932    popClosureTrait
933    when (rhs && not (name `elem` rhsTraits)) $
934        fail (name ++ " may only be used at statement level")
935    let (fun, params) = extractPlaceholderVars (bi_body block) Set.empty
936    -- Check for placeholder vs formal parameters
937    unless (Set.null $ Set.delete varTopic params) $
938        fail "Closure traits take no formal parameters"
939    env <- ask
940    let code = mkSub
941            { subName       = cast name
942            , subType       = SubBlock
943            , subBody       = fun
944            , subPackage    = envPackage env
945            , subInnerPad   = (bi_pad block)
946            , subOuterPads  = (PCompiling (fromJust $ envCompPad env):envLexPads env)
947            }
948    case name of
949        "END"   -> do
950            -- We unshift END blocks to @*END at compile-time.
951            -- They're then run at the end of runtime or at the end of the
952            -- whole program.
953            pkg <- asks envPackage
954            rv  <- unsafeEvalExp $
955                App (_Var "&unshift")
956                    (Just (_Var (if pkg == mainPkg then "@Main::END" else "@*END")))
957                    [Val $ VCode code]
958            return (rv `seq` emptyExp)
959        "BEGIN" -> do
960            -- We have to exit if the user has written code like BEGIN { exit }.
961            val <- possiblyExit =<< unsafeEvalExp (checkForIOLeak code)
962            -- And install any pragmas they've requested.
963            env <- ask
964            let idat = unsafePerformSTM . readTVar $ envInitDat env
965            install $ initPragmas idat
966            clearDynParsers
967            return val
968        "CHECK" -> vcode2checkBlock $ VCode code
969        "INIT"  -> vcode2initBlock $ VCode code
970            -- we need to clone this closure sometimes
971        "START" -> vcode2startBlock $ VCode code
972        _       -> do
973            addClosureTrait name code
974            return emptyExp --retBlock SubBlock Nothing False block
975            -- XXX Not the right thing to return
976    where
977    install [] = return $ ()
978    install prag = do
979        env' <- ask
980        let env'' = envCaller env'  -- not sure about this.
981        case env'' of
982            Just target -> do
983                putRuleEnv target { envPragmas = prag ++ envPragmas target }
984            _ -> fail "no caller env to install pragma in"
985
986{-| Match a @quasi { ... }@ quotation -}
987ruleCodeQuotation :: RuleParser Exp
988ruleCodeQuotation = rule "code quotation" $ do
989    -- XXX - This is entirely kluge; it drops traits in the body too
990    symbol "quasi" >> optional (symbol ":COMPILING")
991    block <- ruleBlockBody
992    return (Syn "quasi" [bi_body block])
993   
994-- | If we've executed code like @BEGIN { exit }@, we've to run all @\@*END@
995--   blocks and then exit. Returns the input expression if there's no need to
996--   exit.
997{-# NOINLINE possiblyExit #-}
998possiblyExit :: Exp -> RuleParser Exp
999possiblyExit (Val (VControl (ControlExit exit))) = do
1000    -- Run all @*END blocks...
1001    rv <- unsafeEvalExp $ Stmts (Syn "for"
1002        [ _Var "@Main::END"
1003        , Syn "sub"
1004            [ Val . VCode $ mkPrim
1005                { subBody       = App (Var varTopic) Nothing []
1006                , subParams     = [defaultScalarParam]
1007                , subInnerPad   = defaultScalarPad
1008                }
1009            ]
1010        ]) (_Var "@Main::END")
1011    -- ...and then exit.
1012    return $ unsafePerformIO $ exitWith (rv `seq` exit)
1013possiblyExit x = return x
1014
1015vcode2memoized :: VCode -> RuleParser VCode
1016vcode2memoized code = do
1017    -- Ok. Now the tricky thing.
1018    -- This is the general idea:
1019    -- START { 42 } is transformed into
1020    -- {
1021    --   # XXX these should not be $? vars!!!
1022    --   state $?START_RESULT;
1023    --   state $?START_RUN;
1024    --   $?START_RUN++ ?? $?START_RESULT !! $?START_RESULT = { 42 }();
1025    -- }
1026    -- These are the two state variables we need.
1027    -- This will soon add our two state vars to our pad
1028
1029    lexDiff <- unsafeEvalLexDiff $
1030        (_Sym SState "$?START_RESULT" mempty emptyExp) .
1031        (_Sym SState "$?START_RUN" mempty emptyExp) $ emptyExp
1032
1033    let body' = Syn "if"
1034                    [ App (_Var "&postfix:++") Nothing [_Var "$?START_RUN"]
1035                    , _Var "$?START_RESULT"
1036                    , Syn "=" [_Var "$?START_RESULT", subBody code]
1037                    ]   --  { $?START_RUN++; $?START_RESULT = 42 };
1038
1039    return $ code
1040        { subBody     = body'
1041        , subInnerPad = subInnerPad code `mappend` lexDiff
1042        }
1043
1044vcode2startBlock :: Val -> RuleParser Exp
1045vcode2startBlock ~(VCode code) = do
1046    code'   <- vcode2memoized code
1047    return $ App (Syn "sub" [Val (VCode code')]) Nothing []
1048
1049vcode2initBlock :: Val -> RuleParser Exp
1050vcode2initBlock ~(VCode code) = do
1051    code'   <- vcode2memoized code
1052    Val res <- unsafeEvalExp $
1053        App (_Var "&push") (Just $ _Var "@*INIT") [ Val (VCode code') ]
1054    return (res `seq` App (Val (VCode code')) Nothing [])
1055
1056vcode2checkBlock :: Val -> RuleParser Exp
1057vcode2checkBlock ~(VCode code) = do
1058    code'   <- vcode2memoized code
1059    Val res <- unsafeEvalExp $
1060        App (_Var "&unshift") (Just $ _Var "@*CHECK") [ Val (VCode code') ]
1061    return (res `seq` App (Val (VCode code')) Nothing [])
1062
1063-- Constructs ------------------------------------------------
1064
1065ruleConstruct :: RuleParser Exp
1066ruleConstruct = rule "construct" $ choice
1067    [ ruleForConstruct
1068    , ruleLoopConstruct
1069    , ruleRepeatConstruct
1070    , ruleCondConstruct
1071    , ruleWhileUntilConstruct
1072    , ruleStandaloneBlock
1073    , ruleWhenConstruct
1074    , ruleMaybeConstruct
1075    , ruleDefaultConstruct
1076    , yadaLiteral
1077    ]
1078
1079ruleStandaloneBlock :: RuleParser Exp
1080ruleStandaloneBlock = tryVerbatimRule "" $ do
1081    body <- ruleBlock
1082    whiteSpace
1083    lookAhead ((oneOf ";}" >> return ()) <|> eof)
1084    retVerbatimBlock SubBlock Nothing False body
1085
1086ruleForConstruct :: RuleParser Exp
1087ruleForConstruct = rule "loop construct" $ do
1088    sym     <- symbol "for" <|> symbol "given"
1089    cond    <- ruleCondPart
1090    body    <- enterBracketLevel ParensBracket $ ruleBlockLiteral
1091    return $ Syn sym [cond, body]
1092
1093ruleLoopConstruct :: RuleParser Exp
1094ruleLoopConstruct = rule "loop construct" $ do
1095    symbol "loop"
1096    choice [ ruleSemiLoopConstruct, ruleBareLoopConstruct ]
1097
1098ruleSemiLoopConstruct :: RuleParser Exp
1099ruleSemiLoopConstruct = rule "for-like loop construct" $ do
1100    conds <- parens $ do
1101        a <- option emptyExp ruleExpression
1102        symbol ";"
1103        b <- option emptyExp ruleExpression
1104        symbol ";"
1105        c <- option emptyExp ruleExpression
1106        return [a,b,c]
1107    block <- retBlockWithoutDefaultParams SubBlock Nothing False =<< ruleBlock
1108    return $ Syn "loop" (conds ++ [block])
1109
1110ruleBareLoopConstruct :: RuleParser Exp
1111ruleBareLoopConstruct = rule "for-like loop construct" $ do
1112    block <- ruleBareBlock
1113    return $ Syn "loop" [block]
1114
1115ruleBareBlock :: RuleParser Exp
1116ruleBareBlock = do
1117    retBlockWithoutDefaultParams SubBlock Nothing False =<< ruleBlock
1118
1119ruleRepeatConstruct :: RuleParser Exp
1120ruleRepeatConstruct = rule "postfix loop construct" $ do
1121    symbol "repeat"
1122    choice [ ruleRepeatPostConstruct, ruleRepeatPreConstruct ]
1123
1124ruleRepeatPostConstruct :: RuleParser Exp
1125ruleRepeatPostConstruct = rule "repeat postfix construct" $ do
1126    block   <- enterBracketLevel ParensBracket $ ruleBareOrPointyBlockLiteralWithoutDefaultParams
1127    name    <- choice [ symbol "while", symbol "until" ]
1128    cond    <- ruleExpression
1129    return $ Syn ("post" ++ name) [cond, block]
1130
1131ruleRepeatPreConstruct :: RuleParser Exp
1132ruleRepeatPreConstruct = rule "repeat prefix construct" $ do
1133    name    <- choice [ symbol "while", symbol "until" ]
1134    cond    <- ruleCondPart
1135    block   <- enterBracketLevel ParensBracket $ ruleBareOrPointyBlockLiteralWithoutDefaultParams
1136    return $ Syn ("post" ++ name) [ cond, block ]
1137
1138ruleCondConstruct :: RuleParser Exp
1139ruleCondConstruct = rule "conditional construct" $ do
1140    csym <- choice [ symbol "if", symbol "unless" ]
1141    ruleCondBody $ csym
1142
1143ruleCondBody :: String -> RuleParser Exp
1144ruleCondBody csym = rule "conditional expression" $ do
1145    cond     <- ruleCondPart
1146    enterBracketLevel ParensBracket $ do
1147        body     <- ruleBareOrPointyBlockLiteralWithoutDefaultParams
1148        bodyElse <- option emptyExp ruleElseConstruct
1149        case csym of
1150            "if"    -> return $ Syn "cond" [cond, body, bodyElse]
1151            _       -> if bodyElse == emptyExp then
1152                             return $ Syn "cond" [cond, bodyElse, body]
1153                        else fail "no else after unless"
1154
1155
1156ruleCondPart :: RuleParser Exp
1157ruleCondPart = enterBracketLevel ConditionalBracket ruleExpression
1158
1159ruleMaybeConstruct :: RuleParser Exp
1160ruleMaybeConstruct = rule "maybe construct" $ do
1161    symbol "maybe"
1162    blocks  <- enterBracketLevel ParensBracket (ruleBareBlock `sepBy` symbol "maybe")
1163    return (Syn "maybe" blocks)
1164
1165ruleElseConstruct :: RuleParser Exp
1166ruleElseConstruct = rule "else or elsif construct" $
1167    do
1168        symbol "else"
1169        ruleBareOrPointyBlockLiteralWithoutDefaultParams
1170    <|> do
1171        symbol "elsif"
1172        body    <- ruleCondBody "if"
1173        return (Syn "block" [body])
1174
1175ruleWhileUntilConstruct :: RuleParser Exp
1176ruleWhileUntilConstruct = rule "while/until construct" $ do
1177    sym     <- choice [ symbol "while", symbol "until" ]
1178    cond    <- ruleCondPart
1179    body    <- enterBracketLevel ParensBracket $ ruleBareOrPointyBlockLiteralWithoutDefaultParams
1180    return $ Syn sym [ cond, body ]
1181
1182ruleWhenConstruct :: RuleParser Exp
1183ruleWhenConstruct = rule "when construct" $ do
1184    sym     <- symbol "when"
1185    match   <- ruleCondPart
1186    body    <- ruleBareBlock
1187    return $ Syn sym [ match, body ]
1188
1189-- XXX: make this translate into when true, when smartmatch
1190-- against true works
1191ruleDefaultConstruct :: RuleParser Exp
1192ruleDefaultConstruct = rule "default construct" $ do
1193    sym     <- symbol "default"
1194    body    <- ruleBareBlock
1195    return $ Syn sym [ body ]
1196
1197-- Expressions ------------------------------------------------
1198
1199ruleExpression :: RuleParser Exp
1200ruleExpression = (<?> "expression") $ parseExpWithOps
1201
1202{-|
1203Match a statement's /conditional/ statement-modifier,
1204e.g. '@say \"hello\" if \$cheerful@' or '@die unless +\@arguments@'.
1205
1206Returns a function that will take the statement proper, and enclose it in an
1207appropriate 'Pugs.AST.Internals.Syn' (either @\"if\"@ or @\"unless\"@).
1208-}
1209s_postConditional :: RuleParser (Exp -> RuleParser Exp)
1210s_postConditional = rule "postfix conditional" $ do
1211    cond <- choice $ map symbol ["if", "unless"]
1212    exp <- ruleExpression
1213    return $ \body -> return $ case cond of
1214        "if"    -> Syn "cond" [exp, Syn "block" [body], Syn "block" [emptyExp]]
1215        _       -> Syn "cond" [exp, Syn "block" [emptyExp], Syn "block" [body]]
1216
1217{-|
1218Match a statement's /looping/ statement-modifier,
1219e.g. '@procrastinate while $bored@' or '@eat until $full@'.
1220
1221Returns a function that will take the statement proper, and enclose it in an
1222appropriate 'Pugs.AST.Internals.Syn' (either @\"while\"@ or @\"until\"@).
1223-}
1224s_postLoop :: RuleParser (Exp -> RuleParser Exp)
1225s_postLoop = rule "postfix loop" $ do
1226    cond    <- choice $ map symbol ["while", "until"]
1227    exp     <- ruleExpression
1228    return $ \body -> do