packages feed

blip 0.2.0 → 0.2.1

raw patch · 11 files changed

+3431/−1 lines, 11 files

Files

blip.cabal view
@@ -1,5 +1,5 @@ Name:                blip -Version:             0.2.0+Version:             0.2.1 Synopsis:            Python to bytecode compiler. Homepage:            https://github.com/bjpop/blip   License:             BSD3@@ -35,4 +35,15 @@      old-time==1.1.*,      pretty==1.1.*      -- utf8-string==0.3.*+  other-modules:+     Assemble+     Desugar+     Monad+     Scope+     State+     Utils+     Compile+     ProgName+     StackDepth+     Types }
+ src/Assemble.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Assemble+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Convert the jump targets in the annotated bytecode to real offsets.+--+-----------------------------------------------------------------------------++module Assemble (assemble) where+ +import Utils (isRelativeJump, isAbsoluteJump)+import Types (BlockState (..), AnnotatedCode (..), LabelMap)+import State (getBlockState, getLabelMap, modifyBlockState)+import Blip.Bytecode (Bytecode (..), BytecodeArg (..), bytecodeSize)+import Monad (Compile (..))+import Data.Map as Map (lookup)+import Data.Word (Word16)++assemble :: Compile ()+assemble = do+   -- The bytecode instructions within the compiler state are+   -- in a list in reverse order.+   annotatedCode <- reverse `fmap` getBlockState state_instructions+   labelMap <- getLabelMap+   let finalAnnotatedCode = applyLabelMap labelMap annotatedCode+   modifyBlockState $ \s -> s { state_instructions = finalAnnotatedCode }++applyLabelMap :: LabelMap -> [AnnotatedCode] -> [AnnotatedCode]+applyLabelMap labelMap code =+   map fixJumpTarget code+   where+   fixJumpTarget :: AnnotatedCode -> AnnotatedCode+   fixJumpTarget annotatedCode =+      annotatedCode { annotatedCode_bytecode = newBytecode }+      where+      thisOpCode = opcode bytecode+      newBytecode+         | isRelativeJump thisOpCode = relativeTarget bytecode index jumpTarget+         | isAbsoluteJump thisOpCode = absoluteTarget bytecode jumpTarget+         | otherwise = bytecode+      bytecode = annotatedCode_bytecode annotatedCode+      index = annotatedCode_index annotatedCode+      jumpTarget =+         case args bytecode of+            Nothing ->+               error $ "Jump instruction without argument: " ++ show code +            Just (Arg16 label) -> +               case Map.lookup label labelMap of+                  Nothing ->+                     error $ "Jump instruction to unknown target label: " ++ show code+                  Just target -> target++relativeTarget :: Bytecode -> Word16 -> Word16 -> Bytecode+relativeTarget code@(Bytecode {..}) index target+   = code { args = Just $ Arg16 newTarget } +   where+   newTarget = target - (index + (fromIntegral $ bytecodeSize code))++absoluteTarget :: Bytecode -> Word16 -> Bytecode+absoluteTarget code@(Bytecode {..}) target+   = code { args = Just $ Arg16 target }
+ src/Compile.hs view
@@ -0,0 +1,1413 @@+{-# LANGUAGE TypeFamilies, TypeSynonymInstances, FlexibleInstances,+    PatternGuards, RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Compile+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Compilation of Python 3 source code into bytecode.+-- +-- Basic algorithm:+--+-- 1) Parse the source code into an AST.+-- 2) Compute the scope of all variables in the module+--    (one pass over the AST).+-- 3) Compile the AST for the whole module into a (possibly nested)+--    code object (one pass over the AST).+-- 4) Write the code object to a .pyc file.+--+-- The following Python constructs are compiled into code objects:+--    - The top-level of the module.+--    - Function definitions (def and lambda).+--    - Class definitions.+--    - Comprehensions.+--+-- The statements and expressions in each of the above constructs are+-- recursively compiled into bytecode instructions. Initially, the actual+-- addresses of jump instruction targets are not known. Instead the jump+-- targets are just labels. At the end of the compilation of each+-- construct the labelled instructions are converted into jumps to+-- actual addresses (one pass over the bytecode stream).+-- Also the maximum stack size of each code object is computed (one pass+-- over the bytecode stream).+--+-- We currently make no attempt to optimise the generated code.+--+-- Bytecode is generated directly from the AST, there is no intermediate+-- language, and no explict control-flow graph.+--+-----------------------------------------------------------------------------++module Compile (compileFile) where++import Prelude hiding (mapM)+import Desugar (desugarComprehension, desugarWith, resultName)+import Utils +   ( isPureExpr, isPyObjectExpr, mkAssignVar, mkList+   , mkVar, mkMethodCall, mkStmtExpr, mkSet, mkDict, mkAssign+   , mkSubscript, mkReturn, mkYield, spanToScopeIdentifier )+import StackDepth (maxStackDepth)+import ProgName (progName)+import State+   ( setBlockState, getBlockState, initBlockState, initState+   , emitCodeNoArg, emitCodeArg, compileConstantEmit+   , compileConstant, getFileName, newLabel, labelNextInstruction+   , getObjectName, setObjectName+   , getNestedScope, ifDump, getLocalScope+   , indexedVarSetKeys, emitReadVar, emitWriteVar, emitDeleteVar+   , lookupNameVar, lookupClosureVar, setFlag+   , peekFrameBlock, withFrameBlock, setFastLocals, setArgCount+   , setLineNumber, setFirstLineNumber )+import Assemble (assemble)+import Monad (Compile (..), runCompileMonad)+import Types+   ( Identifier, CompileConfig (..)+   , CompileState (..), BlockState (..)+   , AnnotatedCode (..), Dumpable (..), IndexedVarSet, VarInfo (..)+   , FrameBlockInfo (..), Context (..), ParameterTypes (..), LocalScope (..) )+import Scope (topScope, renderScope)+import Blip.Marshal as Blip+   ( writePyc, PycFile (..), PyObject (..), co_generator )+import Blip.Bytecode (Opcode (..), encode)+import Language.Python.Version3.Parser (parseModule)+import Language.Python.Common.AST as AST+   ( Annotated (..), ModuleSpan, Module (..), StatementSpan, Statement (..)+   , ExprSpan, Expr (..), Ident (..), ArgumentSpan, Argument (..)+   , OpSpan, Op (..), Handler (..), HandlerSpan, ExceptClause (..)+   , ExceptClauseSpan, ImportItem (..), ImportItemSpan, ImportRelative (..)+   , ImportRelativeSpan, FromItems (..), FromItemsSpan, FromItem (..)+   , FromItemSpan, DecoratorSpan, Decorator (..), ComprehensionSpan+   , Comprehension (..), SliceSpan, Slice (..), AssignOpSpan, AssignOp (..)+   , ParameterSpan, Parameter (..), RaiseExpr (..), RaiseExprSpan )+import Language.Python.Common (prettyText)+import Language.Python.Common.StringEscape (unescapeString)+import Language.Python.Common.SrcLocation (SrcSpan (..))+import System.FilePath ((<.>), takeBaseName)+-- XXX Commented out to avoid bug in unix package when building on OS X, +-- The unix package is depended on by the directory package.+-- import System.Directory (getModificationTime, canonicalizePath)+-- import System.Time (ClockTime (..))+import System.IO (openFile, IOMode(..), hClose, hFileSize, hGetContents)+import Data.Word (Word32, Word16)+import Data.Int (Int32)+import Data.Traversable as Traversable (mapM)+import qualified Data.ByteString.Lazy as B (pack)+import Data.String (fromString)+import Data.List (intersperse)+import Control.Monad (unless, forM_, when, replicateM_, foldM)+import Control.Exception (try)+import Control.Monad.Trans (liftIO)+import Data.Bits ((.|.), shiftL)++-- Compile Python source code to bytecode and write the+-- result out to a .pyc file. The name of the output+-- file is based on the name of the input file. For example+-- the input 'foo.py' will result in an output file called 'foo.pyc'.++compileFile :: CompileConfig -- Configuration options+            -> FilePath      -- The file path of the input Python source+            -> IO ()+compileFile config path = do+   r <- try $ do+      pyHandle <- openFile path ReadMode+      sizeInBytes <- hFileSize pyHandle+      fileContents <- hGetContents pyHandle+      -- modifiedTime <- getModificationTime path+      -- let modSeconds = case modifiedTime of TOD secs _picoSecs -> secs+      let modSeconds = (0 :: Integer)+      pyModule <- parseAndCheckErrors fileContents path+      (moduleLocals, nestedScope) <- topScope pyModule+      -- canonicalPath <- canonicalizePath path +      canonicalPath <- return path +      let state = initState ModuleContext moduleLocals +                     nestedScope config canonicalPath+      pyc <- compileModule state (fromIntegral modSeconds)+                (fromIntegral sizeInBytes) pyModule+      let pycFilePath = takeBaseName path <.> ".pyc"+      pycHandle <- openFile pycFilePath WriteMode +      writePyc pycHandle pyc+      hClose pycHandle+   -- XXX maybe we want more customised error messages for different kinds of+   -- IOErrors?+   case r of+      Left e -> putStrLn $ progName ++ ": " ++ show (e :: IOError)+      Right () -> return ()++-- Parse the Python source into an AST, check for any syntax errors.+parseAndCheckErrors :: String -> FilePath -> IO ModuleSpan+parseAndCheckErrors fileContents sourceName =+   case parseModule fileContents sourceName of+      Left e -> error $ "parse error: " ++ prettyText e+      Right (pyModule, _comments) -> return pyModule++compileModule :: CompileState   -- initial compiler state+              -> Word32         -- modification time+              -> Word32         -- size in bytes+              -> ModuleSpan     -- AST+              -> IO PycFile+compileModule state pyFileModifiedTime pyFileSizeBytes mod = do+   obj <- compiler mod state+   return $ PycFile+      { magic = compileConfig_magic $ state_config state+      , modified_time = pyFileModifiedTime +      , size = pyFileSizeBytes+      , object = obj }++compiler :: Compilable a => a -> CompileState -> IO (CompileResult a)+compiler = runCompileMonad . compile++class Compilable a where+   type CompileResult a :: *+   compile :: a -> Compile (CompileResult a)++instance Compilable a => Compilable [a] where+   type CompileResult [a] = [CompileResult a]+   compile = mapM compile++instance Compilable ModuleSpan where+   type CompileResult ModuleSpan = PyObject+   compile ast@(Module stmts) = do+      maybeDumpScope +      maybeDumpAST ast+      setObjectName "<module>"+      compileClassModuleDocString stmts+      compile $ Body stmts++-- body of module, function and class+newtype Body = Body [StatementSpan]++instance Compilable Body where+   type CompileResult Body = PyObject+   compile (Body stmts) = do+      mapM_ compile stmts+      -- XXX we could avoid this 'return None' if all branches in the code+      -- ended with a return statement. Can fix this in an optimisation step+      -- with control flow analysis.+      returnNone+      assemble+      makeObject++-- Build an object from all the state computed during compilation, such+-- as the bytecode sequence, variable information and so on.+-- argcount is the number of arguments, not counting *varargs or **kwargs.+makeObject :: Compile PyObject+makeObject = do+   annotatedCode <- getBlockState state_instructions+   let stackDepth = maxStackDepth annotatedCode+   names <- getBlockState state_names+   constants <- getBlockState state_constants+   freeVars <- getBlockState state_freeVars+   cellVars <- getBlockState state_cellVars+   argcount <- getBlockState state_argcount+   flags <- getBlockState state_flags+   fastLocals <- getBlockState state_fastLocals+   firstLineNumber <- getBlockState state_firstLineNumber+   lineNumberTable <- compileLineNumberTable firstLineNumber+   let code = map annotatedCode_bytecode annotatedCode +       localVarNames = map Unicode $ indexedVarSetKeys fastLocals+       maxStackDepth = maxBound +   if stackDepth > maxStackDepth+      -- XXX make a better error message+      then error $ "Maximum stack depth " ++ show maxStackDepth +++                   " exceeded: " ++ show stackDepth+      else do+         pyFileName <- getFileName+         objectName <- getObjectName+         let obj = Code+                   { argcount = argcount+                   , kwonlyargcount = 0+                   , nlocals = fromIntegral $ length localVarNames+                   , stacksize = stackDepth +                   , flags = flags +                   , code = String $ encode code+                   , consts = makeConstants constants+                   , names = makeNames names+                   , varnames = Blip.Tuple localVarNames+                   , freevars = makeVarSetTuple freeVars+                   , cellvars = makeVarSetTuple cellVars+                   , filename = Unicode pyFileName+                   , name = Unicode objectName+                   , firstlineno = firstLineNumber+                   , lnotab = lineNumberTable+                   }+         return obj+   where+   makeVarSetTuple :: IndexedVarSet -> PyObject+   makeVarSetTuple varSet =+      Blip.Tuple $ map Unicode $ indexedVarSetKeys varSet+   makeConstants :: [PyObject] -> PyObject+   makeConstants = Blip.Tuple . reverse+   makeNames :: [Identifier] -> PyObject+   makeNames = Blip.Tuple . map Unicode . reverse ++instance Compilable StatementSpan where+   type CompileResult StatementSpan = ()+   compile stmt =+      setLineNumber (annot stmt) >>+      compileStmt stmt+  +compileStmt :: StatementSpan -> Compile ()+compileStmt (Assign {..}) = do+   compile assign_expr+   compileAssignments assign_to+compileStmt (AugmentedAssign {..}) =+   case aug_assign_to of+      Var {..} -> do+         let varIdent = ident_string var_ident+         emitReadVar varIdent+         compile aug_assign_expr+         compile aug_assign_op+         emitWriteVar varIdent+      Subscript {..} -> do+         compile subscriptee+         compile subscript_expr+         emitCodeNoArg DUP_TOP_TWO -- avoids re-doing the above two later when we store+         emitCodeNoArg BINARY_SUBSCR+         compile aug_assign_expr+         compile aug_assign_op+         emitCodeNoArg ROT_THREE+         emitCodeNoArg STORE_SUBSCR+      SlicedExpr {..} -> do+         compile slicee+         compileSlices slices+         emitCodeNoArg DUP_TOP_TWO -- avoids re-doing the above two later when we store+         emitCodeNoArg BINARY_SUBSCR+         compile aug_assign_expr+         compile aug_assign_op+         emitCodeNoArg ROT_THREE+         emitCodeNoArg STORE_SUBSCR+      expr@(BinaryOp { operator = Dot {}, right_op_arg = Var {..}}) -> do+         compile $ left_op_arg expr+         emitCodeNoArg DUP_TOP+         index <- lookupNameVar $ ident_string $ var_ident+         emitCodeArg LOAD_ATTR index +         compile aug_assign_expr+         compile aug_assign_op+         emitCodeNoArg ROT_TWO+         emitCodeArg STORE_ATTR index +      other -> error $ "unexpected expression in augmented assignment: " ++ prettyText other+compileStmt (Return { return_expr = Nothing }) = returnNone+compileStmt (Return { return_expr = Just expr }) =  +   compile expr >> emitCodeNoArg RETURN_VALUE+compileStmt (Pass {}) = return ()+compileStmt (StmtExpr {..}) = +   unless (isPureExpr stmt_expr) $ +      compile stmt_expr >> emitCodeNoArg POP_TOP+compileStmt (Conditional {..}) = do+   restLabel <- newLabel+   mapM_ (compileGuard restLabel) cond_guards +   mapM_ compile cond_else+   labelNextInstruction restLabel+compileStmt (While {..}) = do+   startLoop <- newLabel+   endLoop <- newLabel+   anchor <- newLabel+   emitCodeArg SETUP_LOOP endLoop+   withFrameBlock (FrameBlockLoop startLoop) $ do+       labelNextInstruction startLoop+       compile while_cond+       emitCodeArg POP_JUMP_IF_FALSE anchor+       mapM_ compile while_body+       emitCodeArg JUMP_ABSOLUTE startLoop+       labelNextInstruction anchor +       emitCodeNoArg POP_BLOCK+   mapM_ compile while_else+   labelNextInstruction endLoop+compileStmt (For {..}) = do+   startLoop <- newLabel+   endLoop <- newLabel+   withFrameBlock (FrameBlockLoop startLoop) $ do+      anchor <- newLabel+      emitCodeArg SETUP_LOOP endLoop+      compile for_generator+      emitCodeNoArg GET_ITER+      labelNextInstruction startLoop+      emitCodeArg FOR_ITER anchor+      let num_targets = length for_targets+      when (num_targets > 1) $ do+         emitCodeArg UNPACK_SEQUENCE $ fromIntegral num_targets+      mapM_ compileAssignTo for_targets +      mapM_ compile for_body +      emitCodeArg JUMP_ABSOLUTE startLoop+      labelNextInstruction anchor+      emitCodeNoArg POP_BLOCK+   mapM_ compile for_else+   labelNextInstruction endLoop+compileStmt stmt@(Fun {..}) = compileFun stmt []+compileStmt stmt@(Class {..}) = compileClass stmt []+-- XXX assertions appear to be turned off if the code is compiled+-- for optimisation+-- If the assertion expression is a tuple of non-zero length, then+-- it is always True: CPython warns about this+compileStmt (Assert {..}) = do+   case assert_exprs of+      test_expr:restAssertExprs -> do+         compile test_expr+         end <- newLabel+         emitCodeArg POP_JUMP_IF_TRUE end+         assertionErrorVar <- lookupNameVar "AssertionError"+         emitCodeArg LOAD_GLOBAL assertionErrorVar+         case restAssertExprs of+            assertMsgExpr:_ -> do+               compile assertMsgExpr+               emitCodeArg CALL_FUNCTION 1+            _other -> return ()+         emitCodeArg RAISE_VARARGS 1+         labelNextInstruction end+      _other -> error "assert with no test"+compileStmt stmt@(Try {..}) = compileTry stmt+compileStmt (Import {..}) = mapM_ compile import_items+-- XXX need to handle from __future__ +compileStmt (FromImport {..}) = do+   let level = 0 -- XXX this should be the level of nesting+   compileConstantEmit $ Blip.Int level+   let names = fromItemsIdentifiers from_items +       namesTuple = Blip.Tuple $ map Unicode names+   compileConstantEmit namesTuple+   compileFromModule from_module+   case from_items of+      ImportEverything {} -> do+         emitCodeNoArg IMPORT_STAR+      FromItems {..} -> do+         forM_ from_items_items $ \FromItem {..} -> do+             index <- lookupNameVar $ ident_string from_item_name+             emitCodeArg IMPORT_FROM index+             let storeName = case from_as_name of+                    Nothing -> from_item_name+                    Just asName -> asName+             emitWriteVar $ ident_string storeName+         emitCodeNoArg POP_TOP+-- XXX should check that we are inside a loop+compileStmt (Break {}) = emitCodeNoArg BREAK_LOOP+compileStmt (Continue {}) = do+   maybeFrameBlockInfo <- peekFrameBlock+   case maybeFrameBlockInfo of+      Nothing -> error loopError+      Just (FrameBlockLoop label) -> emitCodeArg JUMP_ABSOLUTE label +      Just FrameBlockFinallyEnd ->+         error finallyError+      Just _other -> checkFrameBlocks+   where+   -- keep blocking the frame block stack until we either find+   -- a loop entry, otherwise generate an error+   checkFrameBlocks :: Compile ()+   checkFrameBlocks = do+      maybeFrameBlockInfo <- peekFrameBlock+      case maybeFrameBlockInfo of+         Nothing -> error loopError+         Just FrameBlockFinallyEnd -> error finallyError +         Just (FrameBlockLoop label) ->+            emitCodeArg CONTINUE_LOOP label+         Just _other -> checkFrameBlocks+   loopError = "'continue' not properly in loop"+   finallyError = "'continue' not supported inside 'finally' clause"+compileStmt (NonLocal {}) = return ()+compileStmt (Global {}) = return ()+compileStmt (Decorated {..}) =+   case decorated_def of+      Fun {} -> compileFun decorated_def decorated_decorators+      Class {} -> compileClass decorated_def decorated_decorators+      other -> error $ "Decorated statement is not a function or a class: " ++ prettyText other+compileStmt (Delete {..}) = mapM_ compileDelete del_exprs+compileStmt stmt@(With {..})+   -- desugar with statements containing multiple contexts into nested+   -- with statements containing single contexts+   | length with_context > 1 = compileWith $ desugarWith stmt +   | otherwise = compileWith stmt+compileStmt (Raise {..}) = compile raise_expr+compileStmt other = error $ "Unsupported statement:\n" ++ prettyText other++instance Compilable ExprSpan where+   type CompileResult ExprSpan = ()+   compile expr = +      setLineNumber (annot expr) >>+      compileExpr expr++compileExpr :: ExprSpan -> Compile ()+compileExpr (Var { var_ident = ident }) = do+   emitReadVar $ ident_string ident+compileExpr expr@(AST.Strings {}) =+   compileConstantEmit $ constantToPyObject expr +compileExpr expr@(AST.ByteStrings {}) =+   compileConstantEmit $ constantToPyObject expr +compileExpr expr@(AST.Int {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr expr@(AST.Float {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr expr@(AST.Imaginary {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr expr@(AST.Bool {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr expr@(AST.None {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr expr@(AST.Ellipsis {}) =+   compileConstantEmit $ constantToPyObject expr+compileExpr (AST.Paren {..}) = compile paren_expr+compileExpr (AST.CondExpr {..}) = do+   compile ce_condition+   falseLabel <- newLabel+   emitCodeArg POP_JUMP_IF_FALSE falseLabel+   compile ce_true_branch+   restLabel <- newLabel+   emitCodeArg JUMP_FORWARD restLabel+   labelNextInstruction falseLabel +   compile ce_false_branch+   labelNextInstruction restLabel+compileExpr expr@(AST.Tuple {..})+   | isPyObjectExpr expr =+        compileConstantEmit $ constantToPyObject expr+   | otherwise = do+        mapM_ compile tuple_exprs+        emitCodeArg BUILD_TUPLE $ fromIntegral $ length tuple_exprs+compileExpr (AST.List {..}) = do+   mapM_ compile list_exprs+   emitCodeArg BUILD_LIST $ fromIntegral $ length list_exprs+compileExpr (AST.Set {..}) = do+   mapM_ compile set_exprs+   emitCodeArg BUILD_SET $ fromIntegral $ length set_exprs+compileExpr (Dictionary {..}) = do+   emitCodeArg BUILD_MAP $ fromIntegral $ length dict_mappings+   forM_ dict_mappings $ \(key, value) -> do+      compile value+      compile key+      emitCodeNoArg STORE_MAP+compileExpr (ListComp {..}) = do+   let initStmt = [mkAssignVar resultName (mkList [])]+       updater = \expr -> mkStmtExpr $ mkMethodCall (mkVar $ resultName) "append" expr+       returnStmt = [mkReturn $ mkVar $ resultName]+   compileComprehension "<listcomp>" initStmt updater returnStmt list_comprehension+compileExpr (SetComp {..}) = do+   let initStmt = [mkAssignVar resultName (mkSet [])]+       updater = \expr -> mkStmtExpr $ mkMethodCall (mkVar $ resultName) "add" expr+       returnStmt = [mkReturn $ mkVar $ resultName]+   compileComprehension "<setcomp>" initStmt updater returnStmt set_comprehension+compileExpr (DictComp {..}) = do+   let initStmt = [mkAssignVar resultName (mkDict [])]+       updater = \(key, val) -> +          mkAssign (mkSubscript (mkVar $ resultName) key) val+       returnStmt = [mkReturn $ mkVar $ resultName]+   compileComprehension "<dictcomp>" initStmt updater returnStmt dict_comprehension+compileExpr (Generator {..}) = do+   let updater = \expr -> mkStmtExpr $ mkYield expr+   compileComprehension "<gencomp>" [] updater [] gen_comprehension+compileExpr (Yield { yield_expr = Nothing }) =+   compileConstantEmit Blip.None >> emitCodeNoArg YIELD_VALUE >> setFlag co_generator+compileExpr (Yield { yield_expr = Just expr }) =+   compile expr >> emitCodeNoArg YIELD_VALUE >> setFlag co_generator+compileExpr (Call {..}) = do+   compile call_fun+   compileCall 0 call_args+compileExpr (Subscript {..}) = do+   compile subscriptee+   compile subscript_expr+   emitCodeNoArg BINARY_SUBSCR+compileExpr (SlicedExpr {..}) = do+   compile slicee+   compileSlices slices+   emitCodeNoArg BINARY_SUBSCR+compileExpr exp@(BinaryOp {..})+   | isBoolean operator = compileBoolOpExpr exp+   | isComparison operator = compileCompareOpExpr exp+   | isDot operator = compileDot exp +   | otherwise = do +        compile left_op_arg+        compile right_op_arg+        compileOp operator +compileExpr (UnaryOp {..}) = do+   compile op_arg+   compileUnaryOp operator+compileExpr (Lambda {..}) = do+   funBodyObj <- nestedBlock FunctionContext expr_annot $ do+      -- make the first constant None, to indicate no doc string+      -- for the lambda+      _ <- compileConstant Blip.None+      compile lambda_body+      emitCodeNoArg RETURN_VALUE+      assemble+      makeObject+   numDefaults <- compileDefaultParams lambda_args+   compileClosure "<lambda>" funBodyObj numDefaults+compileExpr other = error $ "Unsupported expr:\n" ++ prettyText other++instance Compilable AssignOpSpan where+   type CompileResult AssignOpSpan = ()+   compile = emitCodeNoArg . assignOpCode++instance Compilable DecoratorSpan where+   type CompileResult DecoratorSpan = ()+   compile dec@(Decorator {..}) = do+      compileDottedName decorator_name+      let numDecorators = length decorator_args+      when (numDecorators > 0) $ +          compileCall 0 decorator_args+      where+      compileDottedName (name:rest) = do+         emitReadVar $ ident_string name+         forM_ rest $ \var -> do+            index <- lookupNameVar $ ident_string var+            emitCodeArg LOAD_ATTR index+      compileDottedName [] =+         error $ "decorator with no name: " ++ prettyText dec++instance Compilable ArgumentSpan where+   type CompileResult ArgumentSpan = ()+   compile (ArgExpr {..}) = compile arg_expr+   compile other = error $ "Unsupported argument:\n" ++ prettyText other++instance Compilable ImportItemSpan where+   type CompileResult ImportItemSpan = ()+   compile (ImportItem {..}) = do+      compileConstantEmit $ Blip.Int 0 -- this always seems to be zero+      compileConstantEmit Blip.None+      let dottedNames = map ident_string import_item_name+      -- assert (length dottedNames > 0)+      let dottedNameStr =+             concat $ intersperse "." dottedNames+      index <- lookupNameVar dottedNameStr+      emitCodeArg IMPORT_NAME index+      storeName <- +         case import_as_name of+            Nothing -> return $ head import_item_name+            Just asName -> do+               forM_ (tail dottedNames) $ \attribute -> do+                  index <- lookupNameVar attribute+                  emitCodeArg LOAD_ATTR index +               return asName+      emitWriteVar $ ident_string storeName++instance Compilable RaiseExprSpan where+   type CompileResult RaiseExprSpan = ()+   compile (RaiseV3 maybeRaiseArg) = do+      n <- case maybeRaiseArg of+              Nothing -> return 0+              Just (raiseExpr, maybeFrom) -> do+                 compile raiseExpr+                 case maybeFrom of+                    Nothing -> return 1+                    Just fromExpr -> do+                       compile fromExpr+                       return 2+      emitCodeArg RAISE_VARARGS n +   compile stmt@(RaiseV2 _) =+      error $ "Python version 2 raise statement encountered: " ++ prettyText stmt++{-+   From CPython compile.c++   Code generated for "try: S except E1 as V1: S1 except E2 as V2: S2 ...":+   (The contents of the value stack is shown in [], with the top+   at the right; 'tb' is trace-back info, 'val' the exception's+   associated value, and 'exc' the exception.)++   Value stack          Label   Instruction     Argument+   []                           SETUP_EXCEPT    L1+   []                           <code for S>+   []                           POP_BLOCK+   []                           JUMP_FORWARD    L0++   [tb, val, exc]       L1:     DUP                             )+   [tb, val, exc, exc]          <evaluate E1>                   )+   [tb, val, exc, exc, E1]      COMPARE_OP      EXC_MATCH       ) only if E1+   [tb, val, exc, 1-or-0]       POP_JUMP_IF_FALSE       L2      )+   [tb, val, exc]               POP+   [tb, val]                    <assign to V1>  (or POP if no V1)+   [tb]                         POP+   []                           <code for S1>+                                POP_EXCEPT+                                JUMP_FORWARD    L0++   [tb, val, exc]       L2:     DUP+   .............................etc.......................++   [tb, val, exc]       Ln+1:   END_FINALLY     # re-raise exception++   []                   L0:     <next statement>++   Of course, parts are not generated if Vi or Ei is not present.+-}++compileTry :: StatementSpan -> Compile ()+compileTry stmt@(Try {..})+   | length try_finally == 0 = compileTryExcept stmt+   | otherwise = compileTryFinally stmt+compileTry other =+   error $ "Unexpected statement when compiling a try-except: " ++ prettyText other ++compileTryFinally :: StatementSpan -> Compile ()+compileTryFinally stmt@(Try {..}) = do+   end <- newLabel+   emitCodeArg SETUP_FINALLY end+   body <- newLabel+   labelNextInstruction body+   withFrameBlock FrameBlockFinallyTry $ do+      if length try_excepts > 0+         then compileTryExcept stmt +         else mapM_ compile try_body+      emitCodeNoArg POP_BLOCK+   _ <- compileConstantEmit Blip.None+   labelNextInstruction end+   withFrameBlock FrameBlockFinallyEnd $ do+      mapM_ compile try_finally+      emitCodeNoArg END_FINALLY+compileTryFinally other =+   error $ "Unexpected statement when compiling a try-except: " ++ prettyText other ++compileTryExcept :: StatementSpan -> Compile ()+compileTryExcept (Try {..}) = do+   firstHandler <- newLabel                      -- L1+   emitCodeArg SETUP_EXCEPT firstHandler         -- pushes handler onto block stack+   withFrameBlock FrameBlockExcept $ do+      mapM_ compile try_body                     -- <code for S>+      emitCodeNoArg POP_BLOCK                    -- pops handler off block stack+   orElse <- newLabel+   emitCodeArg JUMP_FORWARD orElse +   end <- newLabel                               -- L0+   compileHandlers end firstHandler try_excepts+   labelNextInstruction orElse+   mapM_ compile try_else+   labelNextInstruction end                      -- L0: <next statement>+compileTryExcept other =+   error $ "Unexpected statement when compiling a try-except: " ++ prettyText other ++-- Compile a sequence of exception handlers+compileHandlers :: Word16 -> Word16 -> [HandlerSpan] -> Compile ()+compileHandlers _end handlerLabel [] = do+   labelNextInstruction handlerLabel             -- Ln+1, # re-raise exception+   emitCodeNoArg END_FINALLY+compileHandlers end handlerLabel (Handler {..} : rest) = do+   labelNextInstruction handlerLabel+   nextLabel <- newLabel +   compileHandlerClause nextLabel handler_clause+   emitCodeNoArg POP_TOP                         -- pop the traceback (tb) off the stack+   withFrameBlock FrameBlockFinallyTry $ do+      mapM_ compile handler_suite                   -- <code for S1, S2 ..>+      emitCodeNoArg POP_EXCEPT                      -- pop handler off the block stack+   emitCodeArg JUMP_FORWARD end+   compileHandlers end nextLabel rest ++-- enter here with stack == (s ++ [tb, val, exc]), leave with stack == s+compileHandlerClause :: Word16 -> ExceptClauseSpan -> Compile ()+compileHandlerClause nextHandler (ExceptClause {..}) = do+   case except_clause of+      Nothing -> do+         emitCodeNoArg POP_TOP                  -- pop exc off the stack+         emitCodeNoArg POP_TOP                  -- pop val off the stack+      Just (target, asExpr) -> do+         emitCodeNoArg DUP_TOP                  -- duplicate exc on stack+         compile target                         -- <evaluate E1>+         emitCodeArg COMPARE_OP exactMatchOp    -- compare E1 to exc+         emitCodeArg POP_JUMP_IF_FALSE nextHandler -- pop True/False and if no match try next handler+         emitCodeNoArg POP_TOP                  -- pop exc off the stack+         case asExpr of+            Nothing -> emitCodeNoArg POP_TOP    -- pop val off the stack+            -- XXX we should del this name at the end.+            Just expr -> compileAssignTo expr   -- assign the exception to the as name, will remove val from stack+   where+   -- The code for an exact match operator.+   exactMatchOp :: Word16+   exactMatchOp = 10++withDecorators :: [DecoratorSpan] -> Compile () -> Compile ()+withDecorators decorators comp = do+   -- push each of the decorators on the stack+   mapM_ compile decorators+   -- run the enclosed computation+   comp+   -- call each of the decorators+   replicateM_ (length decorators) $ +      emitCodeArg CALL_FUNCTION 1++nestedBlock :: Context -> SrcSpan -> Compile a -> Compile a+nestedBlock context span comp = do+   -- save the current block state+   oldBlockState <- getBlockState id+   -- set the new block state to initial values, and the+   -- scope of the current definition+   (name, localScope) <- getLocalScope $ spanToScopeIdentifier span +   setBlockState $ initBlockState context localScope+   -- set the new object name+   setObjectName name+   -- set the first line number of the block+   setFirstLineNumber span+   -- run the nested computation+   result <- comp+   -- restore the original block state+   setBlockState oldBlockState+   return result++-- Compile a function definition, possibly with decorators.+compileFun :: StatementSpan -> [DecoratorSpan] -> Compile ()+compileFun (Fun {..}) decorators = do+   let funName = ident_string $ fun_name+   withDecorators decorators $ do+      funBodyObj <- nestedBlock FunctionContext stmt_annot $ do+         compileFunDocString fun_body+         compile $ Body fun_body+      numDefaults <- compileDefaultParams fun_args+      compileClosure funName funBodyObj numDefaults+   emitWriteVar funName+compileFun other _decorators = error $ "compileFun applied to a non function: " ++ prettyText other++-- Compile a class definition, possibly with decorators.+compileClass :: StatementSpan -> [DecoratorSpan] -> Compile ()+compileClass (Class {..}) decorators = do+   let className = ident_string $ class_name+   withDecorators decorators $ do+      classBodyObj <- nestedBlock ClassContext stmt_annot $ do+         -- classes have a special argument called __locals__+         -- it is the only argument they have in the byte code, but it+         -- does not come from the source code, so we have to add it.+         setFastLocals ["__locals__"]+         setArgCount 1+         emitCodeArg LOAD_FAST 0+         emitCodeNoArg STORE_LOCALS+         emitReadVar "__name__"+         emitWriteVar "__module__"+         compileConstantEmit $ Unicode className+         emitWriteVar "__qualname__"+         compileClassModuleDocString class_body+         compile $ Body class_body+      emitCodeNoArg LOAD_BUILD_CLASS+      compileClosure className classBodyObj 0+      compileConstantEmit $ Unicode className+      compileCall 2 class_args+   emitWriteVar className+compileClass other _decorators = error $ "compileClass applied to a non class: " ++ prettyText other++-- XXX CPython uses a "qualified" name for the code object. For instance+-- nested functions look like "f.<locals>.g", whereas we currently use+-- just "g".++-- The free variables in a code object will either be cell variables+-- or free variables in the enclosing object. If there are no free+-- variables then we can avoid building the closure, and just make the function.+compileClosure :: String -> PyObject -> Word16 -> Compile ()+compileClosure name obj numDefaults = do+   -- get the list of free variables from the code object+   let Blip.Tuple freeVarStringObjs = freevars obj+       freeVarIdentifiers = map unicode freeVarStringObjs+       numFreeVars = length freeVarIdentifiers+   if numFreeVars == 0+      then do+         compileConstantEmit obj +         compileConstantEmit $ Unicode name+         emitCodeArg MAKE_FUNCTION numDefaults  +      else do+         forM_ freeVarIdentifiers $ \var -> do+            maybeVarInfo <- lookupClosureVar var+            -- we don't use emitReadVar because it would generate+            -- LOAD_DEREF instructions, but we want LOAD_CLOSURE+            -- instead.+            case maybeVarInfo of+               Just (CellVar index) -> emitCodeArg LOAD_CLOSURE index+               Just (FreeVar index) -> emitCodeArg LOAD_CLOSURE index+               _other -> error $ name ++ " closure free variable not cell or free var in outer context: " ++ var+         emitCodeArg BUILD_TUPLE $ fromIntegral numFreeVars+         compileConstantEmit obj +         compileConstantEmit $ Unicode name+         emitCodeArg MAKE_CLOSURE numDefaults++-- Compile default parameters and return how many there are+compileDefaultParams :: [ParameterSpan] -> Compile Word16+compileDefaultParams = foldM compileParam 0+   where+   compileParam :: Word16 -> ParameterSpan -> Compile Word16+   compileParam count (Param {..}) = do+      case param_default of+         Nothing -> return count+         Just expr -> do+            compile expr+            return $ count + 1+   compileParam count _other = return count++-- Compile a 'from module import'.+compileFromModule :: ImportRelativeSpan -> Compile ()+-- XXX what to do about the initial dots?+compileFromModule (ImportRelative {..}) = do+   let moduleName =+          case import_relative_module of+             Nothing -> ""+             Just dottedNames ->+                concat $ intersperse "." $ map ident_string dottedNames+   index <- lookupNameVar moduleName +   emitCodeArg IMPORT_NAME index++fromItemsIdentifiers :: FromItemsSpan -> [Identifier]+fromItemsIdentifiers (ImportEverything {}) = ["*"]+fromItemsIdentifiers (FromItems {..}) =+   map fromItemIdentifier from_items_items+   where+   fromItemIdentifier :: FromItemSpan -> Identifier+   fromItemIdentifier (FromItem {..}) = ident_string $ from_item_name++-- compile multiple possible assignments:+-- x = y = z = rhs+compileAssignments :: [ExprSpan] -> Compile ()+compileAssignments [] = return ()+compileAssignments [e] = compileAssignTo e+compileAssignments (e1:e2:rest) = do+   emitCodeNoArg DUP_TOP+   compileAssignTo e1+   compileAssignments (e2:rest)++-- the lhs of an assignment statement+-- we can assume that the parser has only accepted the appropriate+-- subset of expression types+compileAssignTo :: ExprSpan -> Compile ()+compileAssignTo (Var {..}) =+   emitWriteVar $ ident_string var_ident+compileAssignTo (Subscript {..}) = +   compile subscriptee >>+   compile subscript_expr >>+   emitCodeNoArg STORE_SUBSCR+-- XXX this can be optimised in places where the rhs is a+-- manifest list or tuple, avoiding the building list/tuple+-- only to deconstruct again+compileAssignTo (AST.Tuple {..}) = do+   emitCodeArg UNPACK_SEQUENCE $ fromIntegral $ length tuple_exprs+   mapM_ compileAssignTo tuple_exprs+compileAssignTo (AST.List {..}) = do+   emitCodeArg UNPACK_SEQUENCE $ fromIntegral $ length list_exprs+   mapM_ compileAssignTo list_exprs+compileAssignTo (AST.Paren {..}) = compileAssignTo paren_expr+compileAssignTo expr@(BinaryOp { operator = Dot {}, right_op_arg = Var {..}}) = do+   compile $ left_op_arg expr+   index <- lookupNameVar $ ident_string $ var_ident+   emitCodeArg STORE_ATTR index+compileAssignTo (SlicedExpr {..}) = do+   compile slicee+   compileSlices slices+   emitCodeNoArg STORE_SUBSCR  +compileAssignTo other = error $ "assignment to unexpected expression:\n" ++ prettyText other++compileDelete :: ExprSpan -> Compile ()+compileDelete (Var {..}) = do+   emitDeleteVar $ ident_string var_ident+compileDelete (Subscript {..}) =+   compile subscriptee >>+   compile subscript_expr >>+   emitCodeNoArg DELETE_SUBSCR+compileDelete (AST.Paren {..}) = compileDelete paren_expr+compileDelete (expr@(BinaryOp { operator = Dot {}, right_op_arg = Var {..}})) = do+   compile $ left_op_arg expr+   index <- lookupNameVar $ ident_string $ var_ident+   emitCodeArg DELETE_ATTR index+compileDelete (SlicedExpr {..}) = do+   compile slicee+   compileSlices slices+   emitCodeNoArg DELETE_SUBSCR  +compileDelete other = error $ "delete of unexpected expression:\n" ++ prettyText other++compileWith :: StatementSpan -> Compile ()+compileWith stmt@(With {..}) = +   case with_context of+      [(context, maybeAs)] -> do+         blockLabel <- newLabel+         finallyLabel <- newLabel+         compile context+         emitCodeArg SETUP_WITH finallyLabel+         labelNextInstruction blockLabel+         withFrameBlock FrameBlockFinallyTry $ do+            case maybeAs of+               -- Discard result from context.__enter__()+               Nothing -> emitCodeNoArg POP_TOP+               Just expr -> compileAssignTo expr+            mapM_ compile with_body+            emitCodeNoArg POP_BLOCK+         _ <- compileConstantEmit Blip.None+         labelNextInstruction finallyLabel+         withFrameBlock FrameBlockFinallyEnd $ do+            emitCodeNoArg WITH_CLEANUP+            emitCodeNoArg END_FINALLY+      _other -> error $ "compileWith applied to non desugared with statement: " ++ prettyText stmt +compileWith other = error $ "compileWith applied to non with statement: " ++ prettyText other++-- Check for a docstring in the first statement of a function body.+-- The first constant in the corresponding code object is inspected+-- by the interpreter for the docstring. If there is no docstring+-- then the first constant must be None+compileFunDocString :: [StatementSpan] -> Compile ()+compileFunDocString (firstStmt:_stmts)+   | StmtExpr {..} <- firstStmt,+     Strings {} <- stmt_expr+        = compileConstant (constantToPyObject stmt_expr) >> return ()+   | otherwise = compileConstant Blip.None >> return ()+compileFunDocString [] = compileConstant Blip.None >> return ()++compileClassModuleDocString :: [StatementSpan] -> Compile ()+compileClassModuleDocString (firstStmt:_stmts)+   | StmtExpr {..} <- firstStmt,+     Strings {} <- stmt_expr+        -- XXX what if another __doc__ is in scope?+        = do compileConstantEmit $ constantToPyObject stmt_expr+             emitWriteVar "__doc__"+   | otherwise = return ()+compileClassModuleDocString [] = return ()++-- Compile a conditional guard+compileGuard :: Word16 -> (ExprSpan, [StatementSpan]) -> Compile ()+compileGuard restLabel (expr, stmts) = do+   compile expr+   falseLabel <- newLabel+   emitCodeArg POP_JUMP_IF_FALSE falseLabel+   mapM_ compile stmts+   emitCodeArg JUMP_FORWARD restLabel+   labelNextInstruction falseLabel ++-- Desugar the comprehension into a zero-arity function (body) with+-- a (possibly nested) for loop, then call the function.+compileComprehension+   :: Identifier +   -> [StatementSpan]+   -> (a -> StatementSpan) +   -> [StatementSpan]+   -> ComprehensionSpan a+   -> Compile ()+compileComprehension name initStmt updater returnStmt comprehension = do+   let desugaredComp = desugarComprehension initStmt updater returnStmt comprehension +       comprehensionSpan = comprehension_annot comprehension+   funObj <- nestedBlock+                FunctionContext+                comprehensionSpan +                (compile $ Body desugaredComp)+   compileClosure name funObj 0+   (_name, localScope) <- getLocalScope $ spanToScopeIdentifier comprehensionSpan+   let parameterNames = parameterTypes_pos $ localScope_params localScope +   mapM_ emitReadVar parameterNames+   emitCodeArg CALL_FUNCTION $ fromIntegral $ length parameterNames++-- Convert a constant expression into the equivalent object. This+-- only works for expressions which have a counterpart in the object+-- representation used in .pyc files.+constantToPyObject :: ExprSpan -> PyObject+constantToPyObject (AST.Int {..})+   | int_value > (fromIntegral max32BitSignedInt) ||+     int_value < (fromIntegral min32BitSignedInt)+      =  Blip.Long int_value+   | otherwise = Blip.Int $ fromIntegral int_value+   where+   max32BitSignedInt :: Int32+   max32BitSignedInt = maxBound +   min32BitSignedInt :: Int32+   min32BitSignedInt = minBound +constantToPyObject (AST.Float {..}) = Blip.Float $ float_value +-- XXX we could optimise the case where we have 'float + imaginary j',+-- to generate a Complex number directly, rather than by doing+-- the addition operation.+constantToPyObject (AST.Imaginary {..}) =+   Blip.Complex { real = 0.0, imaginary = imaginary_value }+constantToPyObject (AST.Bool { bool_value = True }) = Blip.TrueObj+constantToPyObject (AST.Bool { bool_value = False }) = Blip.FalseObj+constantToPyObject (AST.None {}) = Blip.None+constantToPyObject (AST.Ellipsis {}) = Blip.Ellipsis+-- assumes all the tuple elements are constant+constantToPyObject (AST.Tuple {..}) =+   Blip.Tuple { elements = map constantToPyObject tuple_exprs }+constantToPyObject (AST.Strings {..}) =+   Blip.Unicode { unicode = concat $ map normaliseString strings_strings }+constantToPyObject (AST.ByteStrings {..}) =+  -- error $ show $ map normaliseString byte_string_strings+  Blip.String { string = fromString $ concat $ map normaliseString byte_string_strings }+constantToPyObject other =+   error $ "constantToPyObject applied to an unexpected expression: " ++ prettyText other++-- The strings in the AST retain their original quote marks which+-- need to be removed, we have to remove single or triple quotes.+-- We assume the parser has correctly matched the quotes.+-- Escaped characters such as \n \t are parsed as multiple characters+-- and need to be converted back into single characters.+normaliseString :: String -> String+normaliseString ('r':'b':rest) = removeQuotes rest+normaliseString ('b':'r':rest) = removeQuotes rest+normaliseString ('b':rest) = unescapeString $ removeQuotes rest+normaliseString ('r':rest) = removeQuotes rest+normaliseString other = unescapeString $ removeQuotes other ++removeQuotes :: String -> String+removeQuotes ('\'':'\'':'\'':rest) = take (length rest - 3) rest+removeQuotes ('"':'"':'"':rest) = take (length rest - 3) rest+removeQuotes ('\'':rest) = init rest+removeQuotes ('"':rest) = init rest+removeQuotes other = error $ "bad literal string: " ++ other++data CallArgs =+   CallArgs+   { callArgs_pos :: !Word16+   , callArgs_keyword :: !Word16+   , callArgs_varPos :: !Bool+   , callArgs_varKeyword :: !Bool+   }++initCallArgs :: CallArgs+initCallArgs =+   CallArgs+   { callArgs_pos = 0+   , callArgs_keyword = 0+   , callArgs_varPos = False+   , callArgs_varKeyword = False+   }++-- Compile the arguments to a call and+-- decide which particular CALL_FUNCTION bytecode to emit.+-- numExtraArgs counts any additional arguments the function+-- might have been applied to, which is necessary for classes+-- which get extra arguments beyond the ones mentioned in the+-- program source.+compileCall :: Word16 -> [ArgumentSpan] -> Compile ()+compileCall numExtraArgs args = do+   CallArgs {..} <- compileCallArgs args +   let opArg = (callArgs_pos + numExtraArgs) .|. callArgs_keyword `shiftL` 8+   case (callArgs_varPos, callArgs_varKeyword) of+      (False, False) -> emitCodeArg CALL_FUNCTION opArg +      (True, False) -> emitCodeArg CALL_FUNCTION_VAR opArg +      (False, True) -> emitCodeArg CALL_FUNCTION_KW opArg +      (True, True) -> emitCodeArg CALL_FUNCTION_VAR_KW opArg ++-- Compile the arguments to a function call and return the number+-- of positional arguments, and the number of keyword arguments.+compileCallArgs :: [ArgumentSpan] -> Compile CallArgs+compileCallArgs = foldM compileArg initCallArgs +   where+   compileArg :: CallArgs  -> ArgumentSpan -> Compile CallArgs +   compileArg callArgs@(CallArgs {..}) (ArgExpr {..}) = do+      compile arg_expr+      return $ callArgs { callArgs_pos = callArgs_pos + 1 }+   compileArg callArgs@(CallArgs {..}) (ArgKeyword {..}) = do+      compileConstantEmit $ Unicode $ ident_string arg_keyword+      compile arg_expr+      return $ callArgs { callArgs_keyword = callArgs_keyword + 1 }+   compileArg callArgs@(CallArgs {..}) (ArgVarArgsPos {..}) = do+      compile arg_expr+      return $ callArgs { callArgs_varPos = True }+   compileArg callArgs@(CallArgs {..}) (ArgVarArgsKeyword {..}) = do+      compile arg_expr+      return $ callArgs { callArgs_varKeyword = True }++-- XXX need to handle extended slices, slice expressions and ellipsis+compileSlices :: [SliceSpan] -> Compile ()+compileSlices [SliceProper {..}] = do+   case slice_lower of+      Nothing -> compileConstantEmit Blip.None+      Just expr -> compile expr+   case slice_upper of+      Nothing -> compileConstantEmit Blip.None+      Just expr -> compile expr+   case slice_stride of+      Nothing -> emitCodeArg BUILD_SLICE 2+      -- Not sure about this, maybe it is None+      Just Nothing -> emitCodeArg BUILD_SLICE 2+      Just (Just expr) -> do+         compile expr+         emitCodeArg BUILD_SLICE 3+compileSlices other = error $ "unsupported slice: " ++ show other++-- Return the opcode for a given assignment operator.+assignOpCode :: AssignOpSpan -> Opcode+assignOpCode assign = +   case assign of+      PlusAssign {} -> INPLACE_ADD+      MinusAssign {} -> INPLACE_SUBTRACT+      MultAssign {} -> INPLACE_MULTIPLY+      DivAssign {} -> INPLACE_TRUE_DIVIDE+      ModAssign {} -> INPLACE_MODULO+      PowAssign {} -> INPLACE_POWER+      BinAndAssign {} -> INPLACE_AND+      BinOrAssign {} -> INPLACE_OR+      BinXorAssign {} -> INPLACE_XOR+      LeftShiftAssign {} -> INPLACE_LSHIFT+      RightShiftAssign {} -> INPLACE_RSHIFT+      FloorDivAssign {} -> INPLACE_FLOOR_DIVIDE++isDot :: OpSpan -> Bool+isDot (Dot {}) = True+isDot _other = False++isBoolean :: OpSpan -> Bool+isBoolean (And {}) = True+isBoolean (Or {}) = True+isBoolean _other = False++isComparison :: OpSpan -> Bool+isComparison (LessThan {}) = True+isComparison (GreaterThan {}) = True+isComparison (Equality {}) = True+isComparison (GreaterThanEquals {}) = True+isComparison (LessThanEquals {}) = True+isComparison (NotEquals  {}) = True+isComparison (In {}) = True+isComparison (NotIn {}) = True+isComparison (IsNot {}) = True+isComparison (Is {}) = True+isComparison _other = False++compileDot :: ExprSpan -> Compile ()+compileDot (BinaryOp {..}) = do+   compile left_op_arg+   case right_op_arg of+      Var {..} -> do+         -- the right argument should be treated like name variable+         varInfo <- lookupNameVar $ ident_string var_ident+         emitCodeArg LOAD_ATTR varInfo +      other -> error $ "right argument of dot operator not a variable:\n" ++ prettyText other+compileDot other =+   error $ "compileDot applied to an unexpected expression: " ++ prettyText other++compileBoolOpExpr :: ExprSpan -> Compile ()+compileBoolOpExpr (BinaryOp {..}) = do+   endLabel <- newLabel+   compile left_op_arg+   case operator of+      And {..} -> emitCodeArg JUMP_IF_FALSE_OR_POP endLabel+      Or {..} ->  emitCodeArg JUMP_IF_TRUE_OR_POP endLabel+      other -> error $ "Unexpected boolean operator:\n" ++ prettyText other+   compile right_op_arg+   labelNextInstruction endLabel+compileBoolOpExpr other =+   error $ "compileBoolOpExpr applied to an unexpected expression: " ++ prettyText other++compileOp :: OpSpan -> Compile ()+compileOp operator =+   emitCodeNoArg $ case operator of+      BinaryOr {} -> BINARY_OR+      Xor {} -> BINARY_XOR+      BinaryAnd {} -> BINARY_AND+      ShiftLeft {} -> BINARY_LSHIFT+      ShiftRight {} -> BINARY_RSHIFT+      Exponent {} -> BINARY_POWER+      Multiply {} -> BINARY_MULTIPLY+      Plus {} -> BINARY_ADD+      Minus {} -> BINARY_SUBTRACT+      Divide {} -> BINARY_TRUE_DIVIDE+      FloorDivide {} -> BINARY_FLOOR_DIVIDE+      Modulo {} -> BINARY_MODULO+      _other -> error $ "Unexpected operator:\n" ++ prettyText operator++compileUnaryOp :: OpSpan -> Compile ()+compileUnaryOp operator =+   emitCodeNoArg $ case operator of+      Minus {} -> UNARY_NEGATIVE+      Plus {} -> UNARY_POSITIVE+      Not {} -> UNARY_NOT+      Invert {} -> UNARY_INVERT+      other ->  error $ "Unexpected unary operator: " ++ prettyText other++{-+from object.h++#define Py_LT 0+#define Py_LE 1+#define Py_EQ 2+#define Py_NE 3+#define Py_GT 4+#define Py_GE 5++and from opcode.h ++enum cmp_op {PyCmp_LT=Py_LT, PyCmp_LE=Py_LE, PyCmp_EQ=Py_EQ, PyCmp_NE=Py_NE, PyCmp_GT=Py_GT, PyCmp_GE=Py_GE,+             PyCmp_IN, PyCmp_NOT_IN, PyCmp_IS, PyCmp_IS_NOT, PyCmp_EXC_MATCH, PyCmp_BAD};+-}++{- Operator chaining:++   The parser treats comparison operators as left associative.++   So: w < x < y < z is parsed as++   (((w < x) < y) < z)++   We want to compile this to:++         [w]+         [x]+         DUP_TOP   # make a copy of the result of x+         ROT_THREE # put the copy of [x] to the bottom+         <+         JUMP_IF_FALSE_OR_POP cleanup+         [y]+         DUP_TOP   # make a copy of [y]+         ROT_THREE # put the copy of [y] to the bottom+         <+         JUMP_IF_FALSE_OR_POP cleanup+         [z]+         <+         JUMP_FORWARD end+   cleanup:+         ROT_TWO  # put the result of the last comparison on the bottom +                  # and put the duplicated [y] on the top+         POP_TOP  # remove the duplicated [y] from the top+   end:+         # whatever code follows+-}++compileCompareOpExpr :: ExprSpan -> Compile ()+compileCompareOpExpr expr@(BinaryOp {}) =+   compileChain numOps chain+   where+   chain :: [ChainItem]+   chain = flattenComparisonChain [] expr+   numOps :: Int+   numOps = length chain `div` 2++   compileChain :: Int -> [ChainItem] -> Compile ()+   compileChain numOps (Comparator e1 : internal@(Operator op : Comparator e2 : _rest)) = do+      compile e1+      if numOps == 1+         then do+            compile e2+            emitCodeArg COMPARE_OP $ comparisonOpCode op+         else do+            cleanup <- newLabel+            (lastOp, lastArg) <- compileChainInternal cleanup internal +            compile lastArg+            emitCodeArg COMPARE_OP $ comparisonOpCode lastOp+            end <- newLabel+            emitCodeArg JUMP_FORWARD end+            labelNextInstruction cleanup+            emitCodeNoArg ROT_TWO+            emitCodeNoArg POP_TOP+            labelNextInstruction end+   compileChain _numOps _items = error $ "bad operator chain: " ++ prettyText expr+   compileChainInternal :: Word16 -> [ChainItem] -> Compile (OpSpan, ExprSpan)+   compileChainInternal _cleanup [Operator op, Comparator exp] = return (op, exp)+   compileChainInternal cleanup (Operator op : Comparator e : rest) = do+      compile e+      emitCodeNoArg DUP_TOP+      emitCodeNoArg ROT_THREE+      emitCodeArg COMPARE_OP $ comparisonOpCode op+      emitCodeArg JUMP_IF_FALSE_OR_POP cleanup+      compileChainInternal cleanup rest+   compileChainInternal _cleanup _other = error $ "bad comparison chain: " ++ prettyText expr +        +   comparisonOpCode :: OpSpan -> Word16+   comparisonOpCode (LessThan {}) = 0 +   comparisonOpCode (LessThanEquals {}) = 1+   comparisonOpCode (Equality {}) = 2 +   comparisonOpCode (NotEquals {}) = 3 +   comparisonOpCode (GreaterThan {}) = 4 +   comparisonOpCode (GreaterThanEquals {}) = 5 +   comparisonOpCode (In {}) = 6+   comparisonOpCode (NotIn {}) = 7+   comparisonOpCode (Is {}) = 8+   comparisonOpCode (IsNot {}) = 9+   -- XXX we don't appear to have an exact match operator in the AST+   comparisonOpCode operator = error $ "Unexpected comparison operator:\n" ++ prettyText operator+compileCompareOpExpr other = error $ "Unexpected comparison operator:\n" ++ prettyText other ++data ChainItem = Comparator ExprSpan | Operator OpSpan++flattenComparisonChain :: [ChainItem] -> ExprSpan -> [ChainItem] +flattenComparisonChain acc opExpr@(BinaryOp {..}) +   | isComparison operator+        = flattenComparisonChain newAcc left_op_arg+   | otherwise = [Comparator opExpr] ++ acc +   where+   newAcc = [Operator operator, Comparator right_op_arg] ++ acc+flattenComparisonChain acc other = [Comparator other] ++ acc+ +-- Emit an instruction that returns the None contant.+returnNone :: Compile ()+returnNone = compileConstantEmit Blip.None >> emitCodeNoArg RETURN_VALUE++-- Print out the variable scope of the module if requested on the command line.+maybeDumpScope :: Compile ()+maybeDumpScope = +   ifDump DumpScope $ do+      nestedScope <- getNestedScope+      liftIO $ putStrLn $ renderScope nestedScope++-- Print out the AST of the module if requested on the command line.+maybeDumpAST :: ModuleSpan -> Compile ()+maybeDumpAST ast = do+   ifDump DumpAST $ do+      liftIO $ putStrLn "Abstract Syntax Tree:"+      liftIO $ putStrLn $ show ast++{- +   From Cpython: Objects/lnotab_notes.txt++Code objects store a field named co_lnotab.  This is an array of unsigned bytes+disguised as a Python string. It is used to map bytecode offsets to source code+line #s for tracebacks and to identify line number boundaries for line tracing.++The array is conceptually a compressed list of+    (bytecode offset increment, line number increment)+pairs.  The details are important and delicate, best illustrated by example:++    byte code offset    source code line number+        0                   1+        6                   2+       50                   7+      350                 307+      361                 308++Instead of storing these numbers literally, we compress the list by storing only+the increments from one row to the next.  Conceptually, the stored list might+look like:++    0, 1,  6, 1,  44, 5,  300, 300,  11, 1++The above doesn't really work, but it's a start. Note that an unsigned byte+can't hold negative values, or values larger than 255, and the above example+contains two such values. So we make two tweaks:  +++ (a) there's a deep assumption that byte code offsets and their corresponding+ line #s both increase monotonically, and+ (b) if at least one column jumps by more than 255 from one row to the next,+ more than one pair is written to the table. In case #b, there's no way to know+ from looking at the table later how many were written.  That's the delicate+ part.  A user of co_lnotab desiring to find the source line number+ corresponding to a bytecode address A should do something like this++    lineno = addr = 0+    for addr_incr, line_incr in co_lnotab:+        addr += addr_incr+        if addr > A:+            return lineno+        lineno += line_incr++(In C, this is implemented by PyCode_Addr2Line().)  In order for this to work,+when the addr field increments by more than 255, the line # increment in each+pair generated must be 0 until the remaining addr increment is < 256.  So, in+the example above, assemble_lnotab in compile.c should not (as was actually done+until 2.2) expand 300, 300 to+    255, 255, 45, 45,+but to+    255, 0, 45, 255, 0, 45.+-}++-- Returns the bytestring representation of the compressed line number table+compileLineNumberTable :: Word32 -> Compile PyObject+compileLineNumberTable firstLineNumber = do+   offsetToLine <- reverse `fmap` getBlockState state_lineNumberTable+   let compressedTable = compress (0, firstLineNumber) offsetToLine +       bs = B.pack $ concat +               [ [fromIntegral offset, fromIntegral line] | +                    (offset, line) <- compressedTable ]+   return Blip.String { string = bs }+   where+   compress :: (Word16, Word32) -> [(Word16, Word32)] -> [(Word16, Word32)]+   compress _prev [] = []+   compress (prevOffset, prevLine) (next@(nextOffset, nextLine):rest)+      -- make sure all increments are non-negative+      -- skipping any entries which are less than the predecessor+      | nextLine < prevLine || nextOffset < prevOffset =+           compress (prevOffset, prevLine) rest+      | otherwise = chunkDeltas (offsetDelta, lineDelta) ++ compress next rest+      where +      offsetDelta = nextOffset - prevOffset+      lineDelta = nextLine - prevLine++-- both offsetDelta and lineDelta must be non-negative+chunkDeltas :: (Word16, Word32) -> [(Word16, Word32)]+chunkDeltas (offsetDelta, lineDelta)+   | offsetDelta < 256 =+      if lineDelta < 256+         then [(offsetDelta, lineDelta)]+         else (offsetDelta, 255) : chunkDeltas (0, lineDelta - 255)+   -- we must wait until offsetDelta is less than 256 before reducing lineDelta+   | otherwise = (255, 0) : chunkDeltas (offsetDelta - 255, lineDelta)
+ src/Desugar.hs view
@@ -0,0 +1,159 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Desugar +-- Copyright   : (c) 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Desugar Python syntax.+--+-- For example, comprehensions are dsugared into functions with for loops+-- in their bodies.+-- +-- 'with' statements containing multiple context managers are turned into+-- nested 'with' statements with a single context manager each.+--+-----------------------------------------------------------------------------++module Desugar (desugarComprehension, resultName, desugarWith) where++import Prelude hiding (mapM)+import Utils (mkIdent)+import Language.Python.Common.AST as AST+   ( StatementSpan, Statement (..), ExprSpan, Comprehension (..)+   , ComprehensionSpan, CompFor (..), CompForSpan, CompIf (..), CompIfSpan+   , CompIter (..), CompIterSpan, IdentSpan ) +import Language.Python.Common.SrcLocation (SrcSpan (..))+import Language.Python.Common (prettyText)++{-++Desugaring of comprehensions.++Comprehensions are desugared into functions containing for loops.+We use a function because the local variables for a for loop are not+in scope outside the loop (unlike Python's actual for loops). Putting+the loop inside the function gives us the desired scope behaviour.++For example:++    [ x + 1 for x in y if x > 2 ]++becomes:++    def f():+        $result = []+        for x in y:+            if x > 2:+                $result.append(x)+        return $result+    f()++In practice we don't need to generate a name for our function, because+we can just make a function byte code object and then call it directly.++A problem with the above scheme occurs when we have list comprehensions+in the body of a class, which refer to other variables local to the class:++    class C():+        a = [1,2,3]+        b = [ x + 1 for x in a ]++The "obvious" way to desugar that is:++    class C():+        a = [1,2,3]+        def f():+            $result = []+            for x in a:+                $result.append(x)+            return $result+        b = f()++The problem is that the variable 'a' is free in the definition of f.+The scope rules of classes do not allow 'a' to be in scope inside+functions defined in the class (this is different than normal+nested functions). We'd have to refer to the variable as 'C.a'.++We could use the class name to qualify the scope of such free variables.+But another, perhaps simpler way is to provide them as arguments to +the new function:++    class C():+        a = [1,2,3]+        def f(a):+            $result = []+            for x in a:+                $result.append(x)+            return $result+        b = f(a)++-}++-- Special free variable which cannot appear in the source of the program+-- and is guaranteed to be unique in the comprehension.+-- Nested comprehensions get desugared into nested functions so there+-- is no danger of a name clash.+resultName :: IdentSpan +resultName = mkIdent "$result"++desugarComprehension+   :: [StatementSpan]      -- Initialiser of the stmt (e.g. $result = [])+   -> (a -> StatementSpan) -- Update the accumulator (e.g. $result.append(x)) +   -> [StatementSpan]      -- Return the accumulator (e.g. return $result)+   -> ComprehensionSpan a  -- Comprehension to desugar+   -> [StatementSpan]      -- Body of the desugared function+desugarComprehension initStmt updater returnStmt (Comprehension {..}) =+   initStmt ++ [forLoop] ++ returnStmt+   where+   updateStmt = updater comprehension_expr+   forLoop = desugarCompFor updateStmt comprehension_for++desugarCompFor :: StatementSpan -> CompForSpan -> StatementSpan+desugarCompFor updateStmt (CompFor {..}) =+   For { for_targets = comp_for_exprs+       , for_generator = comp_in_expr+       , for_body = [forBody]+       , for_else = []+       , stmt_annot = SpanEmpty }+   where+   forBody :: StatementSpan+   forBody = case comp_for_iter of+                Nothing -> updateStmt+                Just iter -> desugarCompIter updateStmt iter ++desugarCompIter :: StatementSpan -> CompIterSpan -> StatementSpan+desugarCompIter updateStmt (IterFor {..}) =+   desugarCompFor updateStmt comp_iter_for+desugarCompIter updateStmt (IterIf {..}) =+   desugarCompIf updateStmt comp_iter_if++desugarCompIf :: StatementSpan -> CompIfSpan -> StatementSpan+desugarCompIf updateStmt (CompIf {..}) =+   Conditional { cond_guards = guards+               , cond_else = []+               , stmt_annot = SpanEmpty }+   where+   guards :: [(ExprSpan, [StatementSpan])]+   guards = [(comp_if, [conditionBody])]+   conditionBody =+      case comp_if_iter of+         Nothing -> updateStmt+         Just iter -> desugarCompIter updateStmt iter++desugarWith :: StatementSpan -> StatementSpan+desugarWith stmt@(With {..}) =+   case with_context of+      [] -> error $ "with containing no context manager: " ++ prettyText stmt+      [_] -> stmt+      (context1:context2:rest) ->+         With { with_context = [context1]+              , with_body =+                   [ desugarWith $ With { with_context = context2:rest+                                        , with_body = with_body+                                        , stmt_annot = stmt_annot } ]+              , stmt_annot = stmt_annot }+desugarWith other = error $ "desigarWith applied to non with statement: " ++ prettyText other
+ src/Monad.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Monad+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Custom monad support for the compiler.+--+-----------------------------------------------------------------------------++module Monad (Compile (..), runCompileMonad)  where++import Types (CompileState (..))+import Control.Monad.State.Strict as State hiding (State)+-- import Control.Monad.State.Class (MonadState (..))+import Control.Applicative (Applicative (..))++newtype Compile a+   = Compile (StateT CompileState IO a)+   deriving (Monad, Functor, MonadIO, Applicative)++instance MonadState CompileState Compile where+   get = Compile get+   put s = Compile $ put s++runCompileMonad :: Compile a -> CompileState -> IO a+runCompileMonad (Compile comp) = evalStateT comp
+ src/ProgName.hs view
@@ -0,0 +1,18 @@+-----------------------------------------------------------------------------+-- |+-- Module      : ProgName+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Define the name of the compiler program, for consistent use in the rest+-- of the code, such as error messages.+--+-----------------------------------------------------------------------------++module ProgName (progName) where++progName :: String+progName = "blip"
+ src/Scope.hs view
@@ -0,0 +1,517 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, RecordWildCards, PatternGuards, ExistentialQuantification #-}++-----------------------------------------------------------------------------+-- |+-- Module      : Scope+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- A variable can be:+--    explicit global+--    implicit global+--    local+--    free+--    cellvar +--+-- Global variables are either:+--    - defined (assigned) at the top level of a module+--    OR+--    - declared global in a nested scope+--+-- Local variables are (with respect to the current scope) either:+--    - Assigned in the current local scope AND not declared global or non-local.+--    OR+--    - Parameters to a function definition.+--+-- Free variables are (with respect to the current scope):+--    - Local to an enclosing scope AND either:+--        - Declared non-local in the current scope.+--        OR+--        - Read from but not assigned-to in the current local scope. +--+-- Cellvars are:+--    - Local to the current scope.+--    AND+--    - Free variables of a scope which is nested from the current scope.+--+-- Cellvars are used to implement closures such that modifications to the+-- variable binding itself are visible in the closure. They are implemented+-- as a pointer to a heap allocated cell, which itself points to a Python+-- object. The extra level of indirection allows the cell to be updated to+-- point to something else.+-- +-----------------------------------------------------------------------------++module Scope+   (topScope, renderScope)+   where++import Types+   ( Identifier, VarSet, LocalScope (..)+   , NestedScope (..), ScopeIdentifier, ParameterTypes (..) )+import Data.Set as Set+   ( empty, singleton, fromList, union, difference+   , intersection, toList, size )+import Data.Map as Map (empty, insert, toList, union)+import Data.List (foldl', intersperse)+import Language.Python.Common.AST as AST+   ( Statement (..), StatementSpan, Ident (..), Expr (..), ExprSpan+   , Argument (..), ArgumentSpan, RaiseExpr (..), RaiseExprSpan+   , Slice (..), SliceSpan, ModuleSpan, Module (..), ParameterSpan+   , Parameter (..), Op (..), Comprehension (..), ComprehensionSpan+   , CompIter (..), CompIterSpan, CompFor (..), CompForSpan, CompIf (..)+   , CompIfSpan, Handler (..), HandlerSpan, ExceptClause (..), ExceptClauseSpan  )+import Data.Monoid (Monoid (..))+import Control.Monad (mapAndUnzipM)+import Control.Monad.Reader (ReaderT, local, ask, runReaderT)+import Text.PrettyPrint.HughesPJ as Pretty+   ( Doc, ($$), nest, text, vcat, hsep, ($+$), (<+>), empty+   , render, parens, comma, int, hcat )+import Blip.Pretty (Pretty (..))+import State (emptyVarSet, emptyParameterTypes)+import Utils ( identsFromParameters, spanToScopeIdentifier+             , fromIdentString, maybeToList )++type ScopeM a = ReaderT VarSet IO a++instance Pretty ScopeIdentifier where+   pretty (row1, col1, row2, col2) =+      parens $ hcat $ intersperse comma $ map int [row1, col1, row2, col2]++instance Pretty NestedScope where+   pretty (NestedScope scope) =+      vcat $ map prettyLocalScope identsScopes+      where+      identsScopes = Map.toList scope+      prettyLocalScope :: (ScopeIdentifier, (String, LocalScope)) -> Doc+      prettyLocalScope (span, (identifier, defScope)) =+         text identifier <+> pretty span <+> text "->" $$ +         nest 5 (pretty defScope)++instance Pretty LocalScope where+   pretty (LocalScope {..}) =+      text "params:" <+> (nest 5 $ pretty localScope_params) $$+      prettyVarSet "locals:" localScope_locals $$+      prettyVarSet "freevars:" localScope_freeVars $$+      prettyVarSet "cellvars:" localScope_cellVars $$+      prettyVarSet "globals:" localScope_explicitGlobals++instance Pretty ParameterTypes where+   pretty (ParameterTypes {..}) =+      prettyVarList "positional:" parameterTypes_pos $$+      prettyVarList "varArgPos:" (maybeToList parameterTypes_varPos) $$+      prettyVarList "varArgKeyword:" (maybeToList parameterTypes_varKeyword)++prettyVarList :: String -> [Identifier] -> Doc+prettyVarList label list +   | length list == 0 = Pretty.empty+   | otherwise =+        text label <+> (hsep $ map text list)++prettyVarSet :: String -> VarSet -> Doc+prettyVarSet label varSet+   | Set.size varSet == 0 = Pretty.empty+   | otherwise =+        text label <+>+        (hsep $ map text $ Set.toList varSet)++renderScope :: NestedScope -> String+renderScope = render . prettyScope++prettyScope :: NestedScope -> Doc+prettyScope nestedScope =+   text "nested scope:" $+$+      (nest 5 $ pretty nestedScope)++-- class, function, lambda, or comprehension+data Definition+   = DefStmt StatementSpan -- class, or def+   | DefLambda ExprSpan -- lambda+   | forall e . VarUsage e => DefComprehension (ComprehensionSpan e) -- comprehension++data Usage =+   Usage+   { usage_assigned :: !VarSet     -- variables assigned to (written to) in this scope+   , usage_nonlocals :: !VarSet    -- variables declared nonlocal in this scope+   , usage_globals :: !VarSet      -- variables declared global in this scope+   , usage_referenced :: !VarSet   -- variables referred to (read from) in this scope+   , usage_definitions :: ![Definition] -- locally defined lambdas, classes, functions, comprehensions+   }++emptyNestedScope :: NestedScope+emptyNestedScope = NestedScope Map.empty++-- returns the 'local' scope of the top-level of the module and+-- the nested scope of the module (anything not at the top level)+topScope :: ModuleSpan -> IO (LocalScope, NestedScope)+topScope (Module suite) = do+   -- XXX should check that nothing was declared global at the top level+   let Usage {..} = varUsage suite+       moduleLocals =+          LocalScope+          { localScope_params = emptyParameterTypes+          , localScope_locals = usage_assigned+          , localScope_freeVars = Set.empty+          , localScope_cellVars = Set.empty+          , localScope_explicitGlobals = Set.empty }+   (nested, _freeVars) <- runReaderT (foldNestedScopes usage_definitions) emptyVarSet+   return (moduleLocals, nested)++insertNestedScope :: ScopeIdentifier -> (String, LocalScope) -> NestedScope -> NestedScope+insertNestedScope key value (NestedScope scope) = +   NestedScope $ Map.insert key value scope ++joinNestedScopes :: NestedScope -> NestedScope -> NestedScope+joinNestedScopes (NestedScope scope1) (NestedScope scope2)+   = NestedScope $ Map.union scope1 scope2++joinVarSets :: VarSet -> VarSet -> VarSet+joinVarSets = Set.union++foldNestedScopes :: [Definition] -> ScopeM (NestedScope, VarSet)+foldNestedScopes defs = do+   (scopes, vars) <- mapAndUnzipM buildNestedScope defs+   let joinedScopes = foldl' joinNestedScopes emptyNestedScope scopes+       joinedVars = foldl' joinVarSets emptyVarSet vars+   seq joinedScopes $ seq joinedVars $ return (joinedScopes, joinedVars)++buildNestedScope :: Definition -> ScopeM (NestedScope, VarSet)+buildNestedScope (DefStmt (Fun {..})) = do+   let usage = varUsage fun_body `mappend`+               varUsage fun_result_annotation+       parameterTypes = parseParameterTypes fun_args+   functionNestedScope usage parameterTypes+      (spanToScopeIdentifier stmt_annot) $ fromIdentString fun_name+buildNestedScope (DefLambda (Lambda {..})) = do+   let usage = varUsage lambda_body+       parameterTypes = parseParameterTypes lambda_args+   functionNestedScope usage parameterTypes+      (spanToScopeIdentifier expr_annot) "<lambda>" ++buildNestedScope (DefComprehension (Comprehension {..})) = do+   -- we introduce a new local variable called $result when compiling+   -- comprehensions, when they are desugared into functions+   let resultVarSet = Set.singleton "$result"+       usage = mempty { usage_assigned = resultVarSet+                      , usage_referenced = resultVarSet } `mappend`+               varUsage comprehension_expr `mappend`+               varUsage comprehension_for+   -- Comprehensions are turned into functions whose parameters are the+   -- variables which are free in the comprehension. This is equal+   -- to the variables which are referenced but not assigned.+       parameters = usage_referenced usage `Set.difference` usage_assigned usage+       parameterTypes = emptyParameterTypes { parameterTypes_pos = Set.toList parameters } +   functionNestedScope usage parameterTypes+      (spanToScopeIdentifier comprehension_annot) "<comprehension>" ++{-+   Classes can have freeVars, but they don't have cellVars.++   We have a problem where a class can have a free variable with the same+   name as a "locally" defined variable. ++	def f():+	   y = 3+	   class C():+	      y = 5+	      def g():+		 nonlocal y+		 print(y)++   The g() method of the C() class prints the value 3, because its free+   variable y is bound in the body of f, not in the class definition.++   The bases of a class are actually in the enclosing scope of the class+   definition.++   We record both instances of the variable, and are careful to disambiguate+   when the variables are looked-up in the scope during compilation.+-}++buildNestedScope (DefStmt (Class {..})) = do+   let Usage {..} = varUsage class_body +       locals = usage_assigned+   (thisNestedScope, nestedFreeVars) <- foldNestedScopes usage_definitions+   enclosingScope <- ask+   let directFreeVars +          = ((usage_referenced `Set.difference` locals) `Set.union`+              usage_nonlocals) `Set.intersection` enclosingScope+       freeVars = directFreeVars `Set.union` nestedFreeVars+   let thisLocalScope =+          LocalScope+          { localScope_params = emptyParameterTypes+          , localScope_locals = locals+          , localScope_freeVars = freeVars +          , localScope_cellVars = Set.empty+          , localScope_explicitGlobals = usage_globals }+   let newScope =+          insertNestedScope (spanToScopeIdentifier stmt_annot)+             (fromIdentString class_name, thisLocalScope)+             thisNestedScope+   return (newScope, freeVars)++buildNestedScope _def =+   error $ "buildNestedScope called on unexpected definition"++functionNestedScope :: Usage +                    -> ParameterTypes +                    -> ScopeIdentifier +                    -> String +                    -> ScopeM (NestedScope, VarSet)+functionNestedScope (Usage {..}) parameters scopeIdentifier name = do+   let locals = (usage_assigned `Set.difference` +                 usage_globals `Set.difference`+                 usage_nonlocals) `Set.union` +                 (Set.fromList $ identsFromParameters parameters)+   (thisNestedScope, nestedFreeVars) <-+      local (Set.union locals) $ foldNestedScopes usage_definitions+   enclosingScope <- ask+   let -- get all the variables which are free in the top level of+       -- this current nested scope+       -- variables which are free in nested scopes and bound in the current scope+       cellVars = locals `Set.intersection` nestedFreeVars+       -- variables which are referenced in the current scope but not local,+       -- or declared nonlocal and are bound in an enclosing scope +       -- (hence free in the current scope).+       directFreeVars +          = ((usage_referenced `Set.difference` locals) `Set.union`+              usage_nonlocals) `Set.intersection` enclosingScope+       -- free variables from nested scopes which are not bound in the+       -- current scope, and thus are free in the current scope+       indirectFreeVars = nestedFreeVars `Set.difference` cellVars+       freeVars = directFreeVars `Set.union` indirectFreeVars+       thisLocalScope =+          LocalScope+          { localScope_params = parameters +          , localScope_locals = locals+          , localScope_freeVars = freeVars +          , localScope_cellVars = cellVars+          , localScope_explicitGlobals = usage_globals }+   let newScope =+          insertNestedScope scopeIdentifier (name, thisLocalScope) thisNestedScope +   return (newScope, freeVars)++-- separate the positional parameters from the positional varargs and the+-- keyword varargs+parseParameterTypes :: [ParameterSpan] -> ParameterTypes+parseParameterTypes = parseAcc [] Nothing Nothing+   where+   parseAcc :: [Identifier] -> Maybe Identifier -> Maybe Identifier -> [ParameterSpan] -> ParameterTypes+   parseAcc pos varPos varKeyword [] =+      ParameterTypes { parameterTypes_pos = reverse pos+                     , parameterTypes_varPos = varPos+                     , parameterTypes_varKeyword = varKeyword }+   parseAcc pos varPos varKeyword (param:rest) =+      case param of+         Param {..} -> parseAcc (fromIdentString param_name : pos) varPos varKeyword rest +         VarArgsPos {..} -> parseAcc pos (Just $ fromIdentString param_name) varKeyword rest+         VarArgsKeyword {..} -> parseAcc pos varPos (Just $ fromIdentString param_name) rest+         _other -> parseAcc pos varPos varKeyword rest++instance Monoid Usage where+   mempty = Usage+               { usage_assigned = Set.empty+               , usage_nonlocals = Set.empty+               , usage_globals = Set.empty+               , usage_referenced = Set.empty+               , usage_definitions = [] }+   mappend x y+      = Usage+        { usage_assigned = usage_assigned x `mappend` usage_assigned y+        , usage_nonlocals = usage_nonlocals x `mappend` usage_nonlocals y+        , usage_referenced = usage_referenced x `mappend` usage_referenced y+        , usage_globals = usage_globals x `mappend` usage_globals y+        , usage_definitions = usage_definitions x `mappend` usage_definitions y }++instance Monoid ParameterTypes where+   mempty =+      ParameterTypes+      { parameterTypes_pos = []+      , parameterTypes_varPos = Nothing+      , parameterTypes_varKeyword = Nothing+      }++   mappend (ParameterTypes pos1 varPos1 varKeyword1)+           (ParameterTypes pos2 varPos2 varKeyword2)+      = ParameterTypes (pos1 `mappend` pos2)+                       (varPos1 `mappend` varPos2)+                       (varKeyword1 `mappend` varKeyword2)++-- determine the set of variables which are either assigned to or explicitly+-- declared global or nonlocal in the current scope.+class VarUsage t where+   varUsage :: t -> Usage++instance VarUsage t => VarUsage [t] where+   varUsage = mconcat . Prelude.map varUsage++instance (VarUsage t1, VarUsage t2) => VarUsage (t1, t2) where+   varUsage (x, y) = varUsage x `mappend` varUsage y++instance VarUsage a => VarUsage (Maybe a) where+   varUsage Nothing = mempty+   varUsage (Just x) = varUsage x++instance VarUsage StatementSpan where+   varUsage (While {..})+      = varUsage while_cond `mappend`+        varUsage while_body `mappend`+        varUsage while_else+   varUsage (For {..})+      = varUsage (AssignTargets $ for_targets) `mappend` +        varUsage for_generator `mappend`+        varUsage for_body `mappend` +        varUsage for_else+   -- Any varUsage made inside a function body are not collected.+   -- The function name _is_ collected, because it is assigned in the current scope,+   -- likewise for the class name.+   varUsage stmt@(Fun {..})+      = mempty { usage_assigned = singleVarSet fun_name+               , usage_definitions = [DefStmt stmt] }+   -- the bases of the Class are referenced within the scope that defines the class+   -- as opposed to being referenced in the body of the class+   varUsage stmt@(Class {..})+      = mempty { usage_assigned = singleVarSet class_name+               , usage_definitions = [DefStmt stmt] } `mappend`+        varUsage class_args+   varUsage (Conditional {..})+      = varUsage cond_guards `mappend` varUsage cond_else+   varUsage (Assign {..})+      = varUsage (AssignTargets assign_to) `mappend` varUsage assign_expr+   varUsage (AugmentedAssign {..})+      = varUsage [aug_assign_to] `mappend` varUsage aug_assign_expr+   varUsage (Decorated {..})+       = varUsage decorated_def+   varUsage (Try {..})+       = varUsage try_body `mappend` varUsage  try_excepts `mappend`+         varUsage try_else `mappend`  varUsage try_finally+   varUsage (With {..})+      = varUsage with_context `mappend`+        varUsage with_body+   varUsage (Global {..})+      = mempty { usage_globals = Set.fromList $ Prelude.map fromIdentString global_vars }+   varUsage (NonLocal {..})+      = mempty { usage_nonlocals = Set.fromList $ Prelude.map fromIdentString nonLocal_vars }+   varUsage (StmtExpr {..}) = varUsage stmt_expr+   varUsage (Assert {..}) = varUsage assert_exprs+   varUsage (Return {..}) = varUsage return_expr+   varUsage (Raise {..}) = varUsage raise_expr+   varUsage (Delete {..}) = varUsage del_exprs+   varUsage _other = mempty++instance VarUsage HandlerSpan where+   varUsage (Handler {..}) = varUsage handler_clause `mappend` varUsage handler_suite++instance VarUsage ExceptClauseSpan where+   varUsage (ExceptClause {..}) =+      case except_clause of+         Nothing -> mempty+         Just (except, maybeAs) ->+            case maybeAs of+               Nothing -> varUsage except+               Just asName -> varUsage except `mappend` (varUsage $ AssignTargets [asName])++instance VarUsage RaiseExprSpan where+   varUsage (RaiseV3 maybeExpr) = varUsage maybeExpr+   -- the parser should never generate the following, but we need+   -- code to make non-exhaustive pattern warnings go away.+   varUsage _other = error $ "varUsage on Python version 2 style raise statement"++instance VarUsage ExprSpan where+   varUsage (Var {..}) =+      mempty { usage_referenced = singleVarSet var_ident }+   varUsage (Call {..}) =+      varUsage call_fun `mappend` varUsage call_args +   varUsage (Subscript {..}) =+      varUsage subscriptee `mappend`+      varUsage subscript_expr+   varUsage (SlicedExpr {..}) =+      varUsage slicee `mappend` varUsage slices+   varUsage (CondExpr {..}) =+      varUsage ce_true_branch `mappend`+      varUsage ce_condition `mappend`+      varUsage ce_false_branch+   -- if it is a dot operator then the right argument must be a global name+   -- but it is not defined in this module so we can ignore it+   varUsage (BinaryOp {..})+      | Dot {} <- operator = varUsage left_op_arg +      | otherwise = varUsage left_op_arg `mappend` varUsage right_op_arg+   varUsage (UnaryOp {..}) = varUsage op_arg+   varUsage expr@(Lambda {..}) = mempty { usage_definitions = [DefLambda expr] }+   varUsage (Tuple {..}) = varUsage tuple_exprs+   varUsage (Yield {..}) = varUsage yield_expr +   varUsage (Generator {..}) =+      mempty { usage_definitions = [DefComprehension gen_comprehension] }+   varUsage (ListComp {..}) =+      mempty { usage_definitions = [DefComprehension list_comprehension] }+   varUsage (List {..}) = varUsage list_exprs+   varUsage (Dictionary {..}) = varUsage dict_mappings+   varUsage (DictComp {..}) = +      mempty { usage_definitions = [DefComprehension dict_comprehension] }+   varUsage (Set {..}) = varUsage set_exprs+   varUsage (SetComp {..}) =+      mempty { usage_definitions = [DefComprehension set_comprehension] } +   varUsage (Starred {..}) = varUsage starred_expr+   varUsage (Paren {..}) = varUsage paren_expr+   varUsage _other = mempty++instance VarUsage ArgumentSpan where+   varUsage (ArgExpr {..}) = varUsage arg_expr+   varUsage (ArgVarArgsPos {..}) = varUsage arg_expr+   varUsage (ArgVarArgsKeyword {..}) = varUsage arg_expr+   varUsage (ArgKeyword {..}) = varUsage arg_expr++instance VarUsage SliceSpan where+   varUsage (SliceProper {..}) =+      varUsage slice_lower `mappend`+      varUsage slice_upper `mappend`+      varUsage slice_stride+   varUsage (SliceExpr {..}) = varUsage slice_expr+   varUsage (SliceEllipsis {}) = mempty++instance VarUsage a => VarUsage (ComprehensionSpan a) where+   varUsage (Comprehension {..}) = +      varUsage comprehension_expr `mappend`+      varUsage comprehension_for++instance VarUsage CompForSpan where+   varUsage (CompFor {..}) = +      varUsage (AssignTargets comp_for_exprs) `mappend` +      varUsage comp_in_expr `mappend`+      varUsage comp_for_iter++instance VarUsage CompIterSpan where+   varUsage (IterFor {..}) = varUsage comp_iter_for+   varUsage (IterIf {..}) = varUsage comp_iter_if++instance VarUsage CompIfSpan where+   varUsage (CompIf {..}) = +      varUsage comp_if `mappend`+      varUsage comp_if_iter++newtype AssignTargets = AssignTargets [ExprSpan]++-- Collect all the variables which are assigned to in a list of expressions (patterns).+-- XXX we should support starred assign targets.+instance VarUsage AssignTargets where+   varUsage (AssignTargets exprs) = foldl' addUsage mempty exprs+      where+      addUsage :: Usage -> ExprSpan -> Usage +      addUsage usage expr = targetUsage expr `mappend` usage+      targetUsage :: ExprSpan -> Usage+      targetUsage (Var {..}) = mempty { usage_assigned = singleVarSet var_ident }+      targetUsage (List {..}) = varUsage $ AssignTargets list_exprs +      targetUsage (Tuple {..}) = varUsage $ AssignTargets tuple_exprs+      targetUsage (Paren {..}) = targetUsage paren_expr+      -- all variables mentioned in a subscript, attribute lookup+      -- and sliced expr are read from, not written to+      targetUsage expr@(Subscript {..}) = varUsage expr+      targetUsage expr@(BinaryOp{..}) = varUsage expr+      targetUsage expr@(SlicedExpr{..}) = varUsage expr+      targetUsage other = error $ "Unsupported assignTarget: " ++ show other++singleVarSet :: AST.Ident a -> VarSet+singleVarSet = Set.singleton . fromIdentString
+ src/StackDepth.hs view
@@ -0,0 +1,320 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      : StackDepth+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Compute an upper bound on the stack usage of a block of bytecode. +--  It is safe to make the stack too big (but+-- it would waste memory), but if it is too small then the interpreter will+-- probably crash (or worse, keep running in an undefined state).+--+-----------------------------------------------------------------------------++module StackDepth (maxStackDepth) where+ +import Types (AnnotatedCode (..))+import Utils (isJumpBytecode, isRelativeJump, isConditionalJump)+import Blip.Bytecode (Bytecode (..), BytecodeArg (..), Opcode (..), bytecodeSize)+import Data.Word (Word32, Word16)+import Control.Monad.RWS.Strict (RWS, runRWS, ask, local, gets, modify, when)+import qualified Data.Map as Map+import qualified Data.Set as Set (insert, member, Set, empty)+import Data.Bits ((.&.), shiftR)++type StackDepth = Word32+type InstructionIndex = Word16+type InstructionSeen = Set.Set InstructionIndex+-- Mapping from byte address (jump target) to sequence of bytecode+-- from that address onwards.+type BytecodeMap = Map.Map InstructionIndex [AnnotatedCode]+type StackDepthCache = Map.Map InstructionIndex StackDepth+type CalcStackDepth = RWS InstructionSeen () StackDepthState++maxStackDepth :: [AnnotatedCode] -> StackDepth+maxStackDepth code = +   stackDepth_maxDepth finalState+   where+   (_, finalState, _) = runRWS (maxStackDepthM 0 code)+                               Set.empty $ initStackDepthState $+                               makeBytecodeMap code++makeBytecodeMap :: [AnnotatedCode] -> BytecodeMap+makeBytecodeMap = makeBytecodeMapAcc Map.empty+   where+   makeBytecodeMapAcc :: BytecodeMap -> [AnnotatedCode] -> BytecodeMap+   makeBytecodeMapAcc map [] = map+   makeBytecodeMapAcc map code@(instruction@(AnnotatedCode {..}) : rest)+      | isLabelled instruction = do+          let newMap = Map.insert annotatedCode_index code map+          makeBytecodeMapAcc newMap rest+      | otherwise = makeBytecodeMapAcc map rest++data StackDepthState =+   StackDepthState+   { stackDepth_bytecodeMap :: BytecodeMap+   , stackDepth_maxDepth :: !StackDepth+   , stackDepth_cache :: StackDepthCache +   }++initStackDepthState :: BytecodeMap -> StackDepthState+initStackDepthState bytecodeMap =+   StackDepthState+   { stackDepth_bytecodeMap = bytecodeMap+   , stackDepth_maxDepth = 0 +   , stackDepth_cache = Map.empty }+++isLabelled :: AnnotatedCode -> Bool+isLabelled (AnnotatedCode {..}) = not $ null annotatedCode_labels++isLoopBack :: InstructionIndex -> CalcStackDepth Bool+isLoopBack index = do+   seen <- ask+   return (index `Set.member` seen)++-- record that we've visited this jump target at this depth+-- in case we visit it again in any path. There is no point+-- traversing further if the previous visit was at an equal+-- or greater depth.++visitedDeeper :: InstructionIndex -> StackDepth -> CalcStackDepth Bool+visitedDeeper index newDepth = do+   stackDepthCache <- gets stackDepth_cache +   case Map.lookup index stackDepthCache of+       -- not been here before at any depth+       Nothing -> return False+       Just oldDepth -> return (oldDepth >= newDepth)++recordDepth :: InstructionIndex -> StackDepth -> CalcStackDepth ()+recordDepth index depth = do+   stackDepthCache <- gets stackDepth_cache+   let newCache = Map.insert index depth stackDepthCache+   modify $ \s -> s { stackDepth_cache = newCache }++maxStackDepthM :: StackDepth -> [AnnotatedCode] -> CalcStackDepth ()+maxStackDepthM _depth [] = return ()+maxStackDepthM depth code@(instruction@(AnnotatedCode {..}) : _rest) = do+   -- check if this instruction is a jump target+   if isLabelled instruction+      then do+         seenBeforeOnPath <- isLoopBack annotatedCode_index +         seenDeeper <- visitedDeeper annotatedCode_index depth+         if seenBeforeOnPath || seenDeeper+            -- we've seen this instruction before on this path, or+            -- we've visisted it on any path at this depth or+            -- deeper, no point in going further down this path.+            then return ()+            else local (Set.insert annotatedCode_index) $ do+                    recordDepth annotatedCode_index depth+                    maxStackDepthFurther depth code+      else+         maxStackDepthFurther depth code+   where+   maxStackDepthFurther :: StackDepth -> [AnnotatedCode] -> CalcStackDepth ()+   maxStackDepthFurther depth (instruction@(AnnotatedCode {..}) : rest) = do+       let newDepth = depth + codeStackEffect annotatedCode_bytecode+       updateMaxDepth newDepth+       when (isJumpBytecode annotatedCode_bytecode) $+           -- follow the path of the jump+           maxStackDepthJump newDepth instruction+       -- follow the remaining instructions+       -- unless the current instruction is an unconditional jump+       when (isConditionalBytecode annotatedCode_bytecode) $+          maxStackDepthM newDepth rest+   maxStackDepthFurther _depth [] =+      error $ "maxStackDepthFurther called on empty sequence of code"++isConditionalBytecode :: Bytecode -> Bool+isConditionalBytecode (Bytecode {..}) = isConditionalJump opcode++{-+   from CPython, compile.c:++        if (instr->i_opcode == FOR_ITER) {+                target_depth = depth-2;+            } else if (instr->i_opcode == SETUP_FINALLY ||+                       instr->i_opcode == SETUP_EXCEPT) {+                target_depth = depth+3;+                if (target_depth > maxdepth)+                    maxdepth = target_depth;+            }+-}++maxStackDepthJump :: StackDepth -> AnnotatedCode -> CalcStackDepth ()+maxStackDepthJump depth instruction@(AnnotatedCode {..}) = do+   let targetDepth =+          case opcode annotatedCode_bytecode of+             FOR_ITER -> depth - 2+             SETUP_FINALLY -> depth + 3+             SETUP_EXCEPT -> depth + 3+             _other -> depth+   updateMaxDepth targetDepth +   code <- getJumpToCode instruction+   maxStackDepthM targetDepth code ++getJumpToCode :: AnnotatedCode -> CalcStackDepth [AnnotatedCode]+getJumpToCode instruction@(AnnotatedCode {..}) = do+   let jumpTarget = +          if isRelativeJump $ opcode annotatedCode_bytecode +             then relativeTarget instruction+             else absoluteTarget instruction+   bytecodeMap <- gets stackDepth_bytecodeMap+   case Map.lookup jumpTarget bytecodeMap of+       Nothing -> error $ "Jump to uknown target: " ++ show instruction+       Just code -> return code++relativeTarget :: AnnotatedCode -> InstructionIndex+relativeTarget instruction@(AnnotatedCode {..}) =+   target + (annotatedCode_index + instructionSize)+   where+   instructionSize = fromIntegral $ bytecodeSize annotatedCode_bytecode+   target = getJumpTarget instruction++absoluteTarget :: AnnotatedCode -> InstructionIndex+absoluteTarget instruction@(AnnotatedCode {..}) +   = getJumpTarget instruction++getJumpTarget :: AnnotatedCode -> InstructionIndex+getJumpTarget instruction@(AnnotatedCode {..}) =+   case args annotatedCode_bytecode of+      Nothing -> error $ "Jump instruction without argument: " ++ show instruction+      Just (Arg16 label) -> label++updateMaxDepth :: StackDepth -> CalcStackDepth ()+updateMaxDepth depth = do+   currentMaxDepth <- gets stackDepth_maxDepth+   when (depth > currentMaxDepth) $ +      modify $ \s -> s { stackDepth_maxDepth = depth }++-- Compute the effect of each opcode on the depth of the stack.+-- This is used to compute an upper bound on the depth of the stack+-- for each code object. It is safe to over-estimate the depth of the+-- effect, but it is unsafe to underestimate it. Over-estimation will+-- potentially result in the stack being bigger than needed, which would+-- waste memory but otherwise be safe. Under-estimation will likely result+-- in the stack being too small and a serious fatal error in the interpreter, such+-- as segmentation fault (or reading/writing some other part of memory).+-- Some opcodes have different effect on depth depending on other factors, this function+-- convservatively takes the largest possible value.+-- This function is supposed to be identical in behaviour to opcode_stack_effect+-- in Python/compile.c.++codeStackEffect :: Bytecode -> StackDepth+codeStackEffect bytecode@(Bytecode {..}) = +   case opcode of+      POP_TOP -> -1+      ROT_TWO -> 0+      ROT_THREE -> 0+      DUP_TOP -> 1+      DUP_TOP_TWO -> 2+      UNARY_POSITIVE -> 0+      UNARY_NEGATIVE -> 0+      UNARY_NOT -> 0+      UNARY_INVERT -> 0+      SET_ADD -> -1+      LIST_APPEND -> -1+      MAP_ADD -> -2+      BINARY_POWER -> -1+      BINARY_MULTIPLY -> -1+      BINARY_MODULO -> -1+      BINARY_ADD -> -1+      BINARY_SUBTRACT -> -1+      BINARY_SUBSCR -> -1+      BINARY_FLOOR_DIVIDE -> -1+      BINARY_TRUE_DIVIDE -> -1+      INPLACE_FLOOR_DIVIDE -> -1+      INPLACE_TRUE_DIVIDE -> -1+      INPLACE_ADD -> -1+      INPLACE_SUBTRACT -> -1+      INPLACE_MULTIPLY -> -1+      INPLACE_MODULO -> -1+      STORE_SUBSCR -> -3+      STORE_MAP -> -2+      DELETE_SUBSCR -> -2+      BINARY_LSHIFT -> -1+      BINARY_RSHIFT -> -1+      BINARY_AND -> -1+      BINARY_XOR -> -1+      BINARY_OR -> -1+      INPLACE_POWER -> -1+      GET_ITER -> 0+      PRINT_EXPR -> -1+      LOAD_BUILD_CLASS -> 1+      INPLACE_LSHIFT -> -1+      INPLACE_RSHIFT -> -1+      INPLACE_AND -> -1+      INPLACE_XOR -> -1+      INPLACE_OR -> -1+      BREAK_LOOP -> 0+      SETUP_WITH -> 7+      WITH_CLEANUP -> -1 -- Sometimes more+      STORE_LOCALS -> -1+      RETURN_VALUE -> -1+      IMPORT_STAR -> -1+      YIELD_VALUE -> 0+      YIELD_FROM -> -1+      POP_BLOCK -> 0+      POP_EXCEPT -> 0  -- -3 except if bad bytecode+      END_FINALLY -> -1 -- or -2 or -3 if exception occurred+      STORE_NAME -> -1+      DELETE_NAME -> 0+      UNPACK_SEQUENCE -> withArg $ \oparg -> oparg - 1+      UNPACK_EX -> withArg $ \oparg -> (oparg .&. 0xFF) + (oparg `shiftR` 8)+      FOR_ITER -> 1 -- or -1, at end of iterator+      STORE_ATTR -> -2+      DELETE_ATTR -> -1+      STORE_GLOBAL -> -1+      DELETE_GLOBAL -> 0+      LOAD_CONST -> 1+      LOAD_NAME -> 1+      BUILD_TUPLE -> withArg $ \oparg -> 1 - oparg+      BUILD_LIST -> withArg $ \oparg -> 1 - oparg+      BUILD_SET -> withArg $ \oparg -> 1 - oparg+      BUILD_MAP -> 1+      LOAD_ATTR -> 0+      COMPARE_OP -> -1+      IMPORT_NAME -> -1+      IMPORT_FROM -> 1+      JUMP_FORWARD -> 0+      JUMP_IF_TRUE_OR_POP -> 0 -- -1 if jump not taken+      JUMP_IF_FALSE_OR_POP -> 0 -- ditto+      JUMP_ABSOLUTE -> 0+      POP_JUMP_IF_FALSE -> -1+      POP_JUMP_IF_TRUE -> -1+      LOAD_GLOBAL -> 1+      CONTINUE_LOOP -> 0+      SETUP_LOOP -> 0+      SETUP_EXCEPT -> 6+      SETUP_FINALLY -> 6 -- can push 3 values for the new exception+                         -- plus 3 others for the previous exception state+      LOAD_FAST -> 1+      STORE_FAST -> -1+      DELETE_FAST -> 0+      RAISE_VARARGS -> withArg $ \oparg -> -1 * oparg+      CALL_FUNCTION -> withArg $ \oparg -> -1 * nargs oparg+      CALL_FUNCTION_VAR -> withArg $ \oparg -> (-1 * nargs oparg) - 1+      CALL_FUNCTION_KW -> withArg $ \oparg -> (-1 * nargs oparg) - 1 +      CALL_FUNCTION_VAR_KW -> withArg $ \oparg -> (-1 * nargs oparg) - 2+      MAKE_FUNCTION -> withArg $ \oparg -> -1 - (nargs oparg) - ((oparg `shiftR` 16) .&. 0xffff)+      MAKE_CLOSURE -> withArg $ \oparg -> -2 - (nargs oparg) - ((oparg `shiftR` 16) .&. 0xffff)+      BUILD_SLICE -> withArg $ \oparg -> if oparg == 3 then -2 else -1+      LOAD_CLOSURE -> 1+      LOAD_DEREF -> 1+      STORE_DEREF -> -1+      DELETE_DEREF -> 0+      _other -> error $ "unexpected opcode in codeStackEffect: " ++ show bytecode+   where+   -- #define NARGS(o) (((o) % 256) + 2*(((o) / 256) % 256)) +   nargs :: Word32 -> Word32+   nargs o = (o `mod` 256) + (2 * ((o `div` 256) `mod` 256))+   withArg :: (Word32 -> Word32) -> Word32+   withArg f+      = case args of+           Nothing -> error $ "codeStackEffect: " ++ (show opcode) ++ " missing argument"+           Just (Arg16 word16) -> f $ fromIntegral word16+           -- other -> error $ "codeStackEffect unexpected opcode argument: " ++ show other
+ src/State.hs view
@@ -0,0 +1,543 @@+{-# LANGUAGE RecordWildCards #-}++-----------------------------------------------------------------------------+-- |+-- Module      : State+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Management of state for the compiler. There is global state which+-- persists through the whole compilation (such as command line flags), and+-- there is block state, which is local for the compilation of a block+-- of code.+--+-----------------------------------------------------------------------------+module State+   ( setBlockState, getBlockState, initBlockState, initState, modifyBlockState+   , emitCode, emitCodeNoArg, emitCodeArg, compileConstant+   , getFileName, newLabel, compileConstantEmit, labelNextInstruction+   , getObjectName, setObjectName, getLabelMap+   , getNestedScope, ifDump, emptyVarSet, emptyLocalScope+   , getLocalScope, indexedVarSetKeys, lookupNameVar+   , emitReadVar, emitWriteVar, emitDeleteVar, lookupClosureVar, setFlag+   , pushFrameBlock, popFrameBlock, peekFrameBlock, withFrameBlock +   , setFastLocals, setArgCount, emptyParameterTypes, setLineNumber+   , setFirstLineNumber )+   where++import Monad (Compile (..))+import Types+   ( Identifier, CompileConfig (..), VarIndex, IndexedVarSet+   , ConstantID, CompileState (..), BlockState (..)+   , AnnotatedCode (..), LabelMap, Dumpable, VarSet, NestedScope (..)+   , LocalScope (..), VarInfo (..), ScopeIdentifier+   , FrameBlockInfo (..), Context (..), ParameterTypes (..) )+import Blip.Bytecode+   (Bytecode (..), Opcode (..), BytecodeArg (..), bytecodeSize)+import Blip.Marshal (PyObject (..), CodeObjectFlagMask, co_varargs, co_varkeywords)+import Data.Word (Word16, Word32)+import qualified Data.Map as Map+import qualified Data.Set as Set+import Control.Monad.State.Strict as State hiding (State)+import Data.List (sort)+import Data.Bits ((.|.))+import Utils (identsFromParameters, countPosParameters, getSpanLine)+import Language.Python.Common.SrcLocation (SrcSpan (..))++emptyVarSet :: VarSet+emptyVarSet = Set.empty++emptyParameterTypes :: ParameterTypes+emptyParameterTypes =+   ParameterTypes { parameterTypes_pos = []+                  , parameterTypes_varPos = Nothing+                  , parameterTypes_varKeyword = Nothing+                  }++emptyLocalScope :: LocalScope+emptyLocalScope =+   LocalScope+   { localScope_params = emptyParameterTypes +   , localScope_locals = emptyVarSet+   , localScope_freeVars = emptyVarSet+   , localScope_cellVars = emptyVarSet+   , localScope_explicitGlobals = emptyVarSet+   }++initBlockState :: Context -> LocalScope -> BlockState+initBlockState context (LocalScope {..}) = BlockState+   { state_label = 0+   , state_instructions = []+   , state_labelNextInstruction = [] +   , state_constants = [] +   , state_constantCache = Map.empty+   , state_nextConstantID = 0+   , state_names = []+   , state_nameCache = Map.empty+   , state_nextNameID = 0+   , state_objectName = ""+   , state_instruction_index = 0+   , state_labelMap = Map.empty+   , state_locals = localScope_locals+   , state_fastLocals =+        if context == FunctionContext+           then makeLocalsIndexedSet (identsFromParameters localScope_params)+                   localScope_locals +           else Map.empty+   -- the indices for cellvars and freevars are used as offsets into an array+   -- within the code object. The cellvars come first, followed by the+   -- freevars. These indices are used by the LOAD_DEREF and STORE_DEREF+   -- bytecode instructions.+   -- cellvars are indexed from 0 upwards+   , state_cellVars = indexedVarSet 0 $ localScope_cellVars +   -- freevars are indexed from (length cellvars) +   , state_freeVars = indexedVarSet+                         (fromIntegral $ Set.size localScope_cellVars) +                         localScope_freeVars +   , state_explicitGlobals = localScope_explicitGlobals+   , state_argcount = fromIntegral $ countPosParameters localScope_params+   , state_flags = varArgsFlags localScope_params 0+   , state_frameBlockStack = []+   , state_context = context+   , state_lineNumber = 0+   , state_lineNumberTable = []+   , state_firstLineNumber = 0+   }++varArgsFlags :: ParameterTypes -> Word32 -> Word32+varArgsFlags (ParameterTypes {..}) flags =+   flags .|. posVarArgsMask .|. keywordVarArgsMask+   where+   posVarArgsMask :: CodeObjectFlagMask+   posVarArgsMask = +      maybe 0 (const co_varargs) parameterTypes_varPos+   keywordVarArgsMask :: CodeObjectFlagMask+   keywordVarArgsMask =+      maybe 0 (const co_varkeywords) parameterTypes_varKeyword ++-- Local variables are indexed starting with parameters first, in the order+-- that they appear in the function head, followed by the other+-- locally defined variables, which can appear in any order.+makeLocalsIndexedSet :: [Identifier] -> VarSet -> IndexedVarSet+makeLocalsIndexedSet params locals =+   Map.fromList $ zip (params ++ Set.toList localsNotParams) [0..]+   where+   localsNotParams = locals `Set.difference` Set.fromList params++indexedVarSet :: VarIndex -> VarSet -> IndexedVarSet+indexedVarSet from set =+   Map.fromList $ sort $ zip (Set.toList set) [from..]++-- Return the keys of an IndexedVarSet in ascending order of the indices+indexedVarSetKeys :: IndexedVarSet -> [Identifier]+indexedVarSetKeys varset =+   map snd $ sort [ (index, name) | (name, index) <- Map.assocs varset ]++incInstructionIndex :: Bytecode -> Compile Word16+incInstructionIndex bytecode = do+   currentIndex <- getBlockState state_instruction_index+   let nextIndex = currentIndex + (fromIntegral $ bytecodeSize bytecode)+   modifyBlockState $ \s -> s { state_instruction_index = nextIndex }+   return currentIndex++setFastLocals :: [Identifier] -> Compile ()+setFastLocals idents = do+   let localsVarSet = Set.fromList idents+   modifyBlockState $ \s -> s { state_fastLocals = indexedVarSet 0 localsVarSet }++setArgCount :: Word32 -> Compile ()+setArgCount n = modifyBlockState $ \s -> s { state_argcount = n }++initState :: Context       -- module, class or function?+          -> LocalScope    -- local scope of the top-level of the module+          -> NestedScope   -- nested scope of the rest of the module (not at the top-level)+          -> CompileConfig -- configuration options+          -> FilePath      -- file path of the Python source+          -> CompileState+initState context localScope nestedScope config pyFilename = CompileState+   { state_config = config+   , state_blockState = initBlockState context localScope+   , state_filename = pyFilename+   , state_nestedScope = nestedScope+   }++ifDump :: Dumpable -> Compile () -> Compile ()+ifDump dumpable action = do+   state <- get+   if dumpable `Set.member` (compileConfig_dumps $ state_config state)+      then action+      else return () ++-- get the nested scope for the current block+getNestedScope :: Compile NestedScope+getNestedScope = gets state_nestedScope++getLocalScope :: ScopeIdentifier -> Compile (String, LocalScope)+getLocalScope scopeIdent = do+   NestedScope nestedScope <- getNestedScope+   case Map.lookup scopeIdent nestedScope of+      Just scope -> return scope+      -- this case should never happen+      Nothing -> error $ "no scope found for: " ++ show scopeIdent++getFileName :: Compile FilePath+getFileName = gets state_filename++getObjectName :: Compile String+getObjectName = getBlockState state_objectName++setObjectName :: String -> Compile ()+setObjectName str = modifyBlockState $ \s -> s { state_objectName = str }++setBlockState :: BlockState -> Compile ()+setBlockState blockState = do+   oldState <- get+   put $ oldState { state_blockState = blockState }++getBlockState :: (BlockState -> a) -> Compile a +getBlockState f = gets (f . state_blockState)++modifyBlockState :: (BlockState -> BlockState) -> Compile ()+modifyBlockState f = do+   state <- getBlockState id+   setBlockState $! f state++newLabel :: Compile Word16+newLabel = do+   currentLabel <- getBlockState state_label+   let newLabel = currentLabel + 1+   modifyBlockState $ \s -> s { state_label = newLabel }+   return currentLabel++-- prefix this new label onto the existing ones+labelNextInstruction :: Word16 -> Compile ()+labelNextInstruction newLabel = do+   currentLabels <- getBlockState state_labelNextInstruction+   modifyBlockState $ \ s -> s { state_labelNextInstruction = newLabel : currentLabels }++{-+          | Free  | Cell  | Local | Explicit Global | Implicit Global+---------------------------------------------------------------------+Class     | Deref | X     | Name  | Global          | Name          +Module    | X     | X     | Name  | X               | Name+Funcition | Deref | Deref | Fast  | Global          | Global++-}++data VarOpcodeType = Deref | Name | Global | Fast++emitReadVar :: Identifier -> Compile ()+emitReadVar ident = do+   (opcodeType, index) <- getVarOpcodeType ident+   case opcodeType of+      Deref -> emitCodeArg LOAD_DEREF index+      Name -> emitCodeArg LOAD_NAME index+      Global -> emitCodeArg LOAD_GLOBAL index+      Fast -> emitCodeArg LOAD_FAST index++emitWriteVar :: Identifier -> Compile ()+emitWriteVar ident = do+   (opcodeType, index) <- getVarOpcodeType ident+   case opcodeType of+      Deref -> emitCodeArg STORE_DEREF index+      Name -> emitCodeArg STORE_NAME index+      Global -> emitCodeArg STORE_GLOBAL index+      Fast -> emitCodeArg STORE_FAST index++emitDeleteVar :: Identifier -> Compile ()+emitDeleteVar ident = do+   (opcodeType, index) <- getVarOpcodeType ident+   case opcodeType of+      Deref -> emitCodeArg DELETE_DEREF index+      Name -> emitCodeArg DELETE_NAME index+      Global -> emitCodeArg DELETE_GLOBAL index+      Fast -> emitCodeArg DELETE_FAST index++getVarOpcodeType :: Identifier -> Compile (VarOpcodeType, VarIndex)+getVarOpcodeType ident = do+   context <- getBlockState state_context+   varInfo <- lookupVar ident+   getVarInContext context varInfo+   where+   getVarInContext :: Context -> VarInfo -> Compile (VarOpcodeType, VarIndex)+   getVarInContext ClassContext info =+      case info of+         FreeVar index -> return (Deref, index)+         LocalVar -> do+            index <- lookupNameVar ident+            return (Name, index)+         ExplicitGlobal -> do+            index <- lookupNameVar ident+            return (Global, index)+         ImplicitGlobal -> do+            index <- lookupNameVar ident+            return (Name, index)+         CellVar _index -> error $ "class with a cell variable: " ++ ident+   getVarInContext ModuleContext info =+      case info of+         LocalVar -> do+            index <- lookupNameVar ident+            return (Name, index)+         ImplicitGlobal -> do+            index <- lookupNameVar ident+            return (Name, index)+         FreeVar _index ->+            error $ "module with a free variable: " ++ ident+         CellVar _index ->+            error $ "module with a cell variable: " ++ ident+         ExplicitGlobal ->+            error $ "module with an explicit global variable: " ++ ident+   getVarInContext FunctionContext info =+      case info of+         FreeVar index -> return (Deref, index)+         CellVar index -> return (Deref, index)+         LocalVar -> do+            fastLocals <- getBlockState state_fastLocals +            case Map.lookup ident fastLocals of+               Just index -> return (Fast, index)+               Nothing -> error $ "local function variable not in fast locals: " ++ ident+         ExplicitGlobal -> do+            index <- lookupNameVar ident+            return (Global, index)+         ImplicitGlobal -> do+            index <- lookupNameVar ident+            return (Global, index)++emitCodeArg :: Opcode -> Word16 -> Compile ()+emitCodeArg opCode arg = emitCode $ Bytecode opCode (Just $ Arg16 arg)++emitCodeNoArg :: Opcode -> Compile ()+emitCodeNoArg opCode = emitCode $ Bytecode opCode Nothing++emitCode :: Bytecode -> Compile ()+emitCode instruction = do+   -- Attach a label to the instruction if necesary.+   labels <- getBlockState state_labelNextInstruction+   -- Ensure current labels are used only once.+   modifyBlockState $ \s -> s { state_labelNextInstruction = [] }+   instructionIndex <- incInstructionIndex instruction+   -- add a mapping from instruction offset to source code line number+   updateLineNumberTable instructionIndex+   -- Map each label to its instruction index+   forM_ labels $ \label -> updateLabelMap label instructionIndex+   let annotatedInstruction =+          AnnotatedCode { annotatedCode_bytecode = instruction+                        , annotatedCode_labels = labels+                        , annotatedCode_index = instructionIndex } +   oldInstructions <- getBlockState state_instructions+   modifyBlockState $+      \s -> s { state_instructions = annotatedInstruction : oldInstructions }++getLabelMap :: Compile LabelMap+getLabelMap = getBlockState state_labelMap++updateLabelMap :: Word16 -> Word16 -> Compile ()+updateLabelMap label index = do+   oldLabelMap <- getBlockState state_labelMap+   let newLabelMap = Map.insert label index oldLabelMap+   modifyBlockState $ \s -> s { state_labelMap = newLabelMap }++updateLineNumberTable :: Word16 -> Compile ()+updateLineNumberTable offset = do+   lineNumber <- getBlockState state_lineNumber+   oldTable <- getBlockState state_lineNumberTable+   let updateTable =+          modifyBlockState $ \s -> s { state_lineNumberTable = (offset, lineNumber) : oldTable }+   case oldTable of+      [] -> updateTable +      (_prevOffset, prevLineNumber):_rest+         -- don't update the table if the current line number is not less+         -- than the previously stored line number+         | prevLineNumber >= lineNumber -> return ()+         | otherwise -> updateTable++compileConstant :: PyObject -> Compile ConstantID+-- Code objects are not cached to avoid complex equality comparisons+compileConstant obj@(Code {}) = do+   oldConstants <- getBlockState state_constants+   constantID <- getBlockState state_nextConstantID +   modifyBlockState $+      \s -> s { state_constants = obj : oldConstants+              , state_nextConstantID = constantID + 1 }+   return constantID+compileConstant obj = do+   blockState <- getBlockState id+   let constantCache = state_constantCache blockState+   case Map.lookup obj constantCache of+      -- We haven't seen this (non-code) constant before+      Nothing -> do+         let constantID = state_nextConstantID blockState+             newConstantCache = Map.insert obj constantID constantCache +             oldConstants = state_constants blockState+         setBlockState $ blockState+            { state_nextConstantID = constantID + 1+            , state_constantCache = newConstantCache+            , state_constants = obj : oldConstants }+         return constantID+      Just constantID -> return constantID++compileConstantEmit :: PyObject -> Compile ()+compileConstantEmit obj = do+   constantID <- compileConstant obj+   emitCodeArg LOAD_CONST constantID++{-++check if var is:++  cellvar+  localvar+  freevar+  explicit global+  implicit global ++We check local vars first before free vars because classes can+have a variable with the same name being local and also free.+If a local version of the variable is defined, that is the+one we want to see (not the free variable). If we need to+see the free variable, then we can look it up specially.++If we can't find it defined anywhere then we presume it+to be an implicit global variable.++-}++lookupVar :: Identifier -> Compile VarInfo+lookupVar identifier = do+   -- cell+   cellvars <- getBlockState state_cellVars+   case Map.lookup identifier cellvars of+      Just index -> return $ CellVar index+      Nothing -> do+         -- local+         locals <- getBlockState state_locals+         if identifier `Set.member` locals+            then return LocalVar+            else do+               -- free+               freevars <- getBlockState state_freeVars+               case Map.lookup identifier freevars of+                  Just index -> return $ FreeVar index+                  Nothing -> do+                     -- explicit global+                     explicitGlobals <- getBlockState state_explicitGlobals+                     if identifier `Set.member` explicitGlobals+                        then return ExplicitGlobal+                        -- implicit global +                        else return ImplicitGlobal++{- lookup a variable in cell vars or free vars only.+   We avoid looking in other places because, for example,+   classes can have free variables with the same name as+   locally defined variables, and we don't want to get them+   confused.+-}++lookupClosureVar :: Identifier -> Compile (Maybe VarInfo)+lookupClosureVar identifier = do+   cellvars <- getBlockState state_cellVars+   case Map.lookup identifier cellvars of+      Just index -> return $ Just $ CellVar index+      Nothing -> do+         freevars <- getBlockState state_freeVars+         case Map.lookup identifier freevars of+            Just index -> return $ Just $ FreeVar index+            Nothing -> return Nothing++-- look up a variable in the "names" vector. Add it if it is not there.+-- return the index of the variable in the vector.+lookupNameVar :: Identifier -> Compile VarIndex+lookupNameVar ident = do+   blockState <- getBlockState id+   let nameCache = state_nameCache blockState+   case Map.lookup ident nameCache of+      -- We haven't seen this name before+      Nothing -> do+         let index = state_nextNameID blockState+             newNameCache = Map.insert ident index nameCache+             oldNames = state_names blockState +         setBlockState $+            blockState { state_nextNameID = index + 1+                       , state_nameCache = newNameCache +                       , state_names = ident : oldNames }+         return index +      Just index -> return index ++-- set a flag in the code object by applying a mask +setFlag :: CodeObjectFlagMask -> Compile ()+setFlag mask = do+    oldFlags <- getBlockState state_flags +    let newFlags = oldFlags .|. mask+    modifyBlockState $ \state -> state { state_flags = newFlags }++pushFrameBlock :: FrameBlockInfo -> Compile ()+pushFrameBlock info = do+   oldFrameStack <- getBlockState state_frameBlockStack+   let newFrameStack = info : oldFrameStack+   modifyBlockState $ \state -> state { state_frameBlockStack = newFrameStack }++popFrameBlock :: Compile FrameBlockInfo+popFrameBlock = do+   oldFrameStack <- getBlockState state_frameBlockStack+   case oldFrameStack of+      [] -> error "attempt to pop from an empty frame block stack"+      top:rest -> do+         modifyBlockState $ \state -> state { state_frameBlockStack = rest }+         return top++peekFrameBlock :: Compile (Maybe FrameBlockInfo)+peekFrameBlock = do+   oldFrameStack <- getBlockState state_frameBlockStack+   case oldFrameStack of+      [] -> return Nothing+      top:_rest -> return $ Just top++withFrameBlock :: FrameBlockInfo -> Compile a -> Compile a+withFrameBlock pushedInfo comp = do +    pushFrameBlock pushedInfo+    result <- comp+    poppedInfo <- popFrameBlock+    if pushedInfo /= poppedInfo+       then error $ "pushed frame block not equal to popped frame block"+       else return result++{- ++   From Python/compile.c++   The line number is reset in the following cases:+   - when entering a new scope+   - on each statement+   - on each expression that start a new line+   - before the "except" clause+   - before the "for" and "while" expressions++   Our own remarks:++   - the CPython compiler does not seem to follow the above comment+     for "for" and "while" statements (not expressions).+   - I'm not sure why they do something special for the "except" clause+     and why not the "else" and "finally"?+-}++setLineNumber :: SrcSpan -> Compile ()+setLineNumber span =+   case getSpanLine span of+      Nothing -> return ()+      Just line -> do+         let lineWord32 = fromIntegral line+         oldLineNumber <- getBlockState state_lineNumber+         -- We ensure that line numbers are monotonically increasing.+         if lineWord32 > oldLineNumber+            then modifyBlockState $ \s -> s { state_lineNumber = lineWord32 }+            else return ()++setFirstLineNumber :: SrcSpan -> Compile ()+setFirstLineNumber span = +   case getSpanLine span of+      Nothing -> return ()+      Just line ->+         modifyBlockState $ \state -> state { state_firstLineNumber = fromIntegral line }
+ src/Types.hs view
@@ -0,0 +1,143 @@+-----------------------------------------------------------------------------+-- |+-- Module      : Types+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Type definitions which are used in multiple modules.+--+-----------------------------------------------------------------------------+module Types +   ( Identifier, CompileConfig (..), VarIndex, IndexedVarSet+   , ConstantID, ConstantCache, CompileState (..), BlockState (..)+   , AnnotatedCode (..), LabelMap, Dumpable (..), VarSet+   , LocalScope (..), NestedScope (..), VarInfo (..)+   , ScopeIdentifier, FrameBlockInfo (..), Context (..), ParameterTypes (..)+   ) where++import Data.Set (Set)+import Blip.Bytecode (Bytecode (..))+import Blip.Marshal (PyObject (..))+import Data.Word (Word32, Word16)+import qualified Data.Map as Map++-- The context in which a variable is used affects the bytecode+-- related to that use.+data Context+   = ModuleContext+   | ClassContext+   | FunctionContext+   deriving (Eq, Ord, Show)++-- information about how a variable is bound plus its offset into+-- the appropriate structure+data VarInfo+   = LocalVar+   | CellVar VarIndex +   | FreeVar VarIndex +   | ExplicitGlobal+   | ImplicitGlobal++type VarSet = Set Identifier++-- XXX need to handle keyword only paramters+data ParameterTypes+   = ParameterTypes+     { parameterTypes_pos :: ![Identifier]+     , parameterTypes_varPos :: !(Maybe Identifier)+     , parameterTypes_varKeyword :: !(Maybe Identifier)+     }+   deriving (Eq, Show)++data LocalScope+   = LocalScope+     { localScope_params :: !ParameterTypes+     , localScope_locals :: !VarSet+     , localScope_freeVars :: !VarSet+     , localScope_cellVars :: !VarSet+     , localScope_explicitGlobals :: !VarSet+     }+     deriving Show++-- start and end coordinates of span (row, col, row, col)+type ScopeIdentifier = (Int, Int, Int, Int)++-- mapping from source location to pair of (scope name, local scope)+newtype NestedScope =+   NestedScope (Map.Map ScopeIdentifier (String, LocalScope))+   deriving Show++data Dumpable = DumpScope | DumpAST+   deriving (Eq, Ord, Show)++data AnnotatedCode+   = AnnotatedCode +     { annotatedCode_bytecode :: Bytecode+     , annotatedCode_labels :: ![Word16]   -- instruction can be labelled zero or more times+     , annotatedCode_index :: !Word16 }    -- byte offset of the instruction within this sequence of bytecode+   deriving Show++type Identifier = String -- a variable name++data CompileConfig =+   CompileConfig+   { compileConfig_magic :: Word32+   , compileConfig_dumps :: Set Dumpable+   }+   deriving (Eq, Show)++type ConstantID = Word16+type ConstantCache = Map.Map PyObject ConstantID ++data CompileState = CompileState+   { state_config :: CompileConfig+   , state_blockState :: BlockState+   , state_filename :: FilePath+   , state_nestedScope :: NestedScope+   }++-- Map from Label to Instruction offset.+-- The same instruction can be labelled multiple times,+-- but each label is attached to exactly one instruction.+type LabelMap = Map.Map Word16 Word16++type VarIndex = Word16+type IndexedVarSet = Map.Map Identifier VarIndex++data BlockState = BlockState +   { state_label :: !Word16+   , state_instructions :: [AnnotatedCode]+   , state_labelNextInstruction :: [Word16] -- zero or more labels for the next instruction+   , state_constants :: [PyObject] +   , state_constantCache :: ConstantCache+   , state_nextConstantID :: !ConstantID+   , state_names :: [Identifier]+   , state_nameCache :: IndexedVarSet+   , state_nextNameID :: !VarIndex+   , state_objectName :: String+   , state_instruction_index :: !Word16+   , state_labelMap :: LabelMap+   , state_locals :: VarSet+   , state_fastLocals :: IndexedVarSet+   , state_freeVars :: IndexedVarSet+   , state_cellVars :: IndexedVarSet+   , state_explicitGlobals :: VarSet+   , state_argcount :: !Word32+   , state_flags :: !Word32+   , state_frameBlockStack :: [FrameBlockInfo]+   , state_context :: !Context+   , state_lineNumber :: !Word32+   , state_lineNumberTable :: ![(Word16, Word32)] -- mapping from bytecode offset to source line number+   , state_firstLineNumber :: !Word32+   }+   deriving (Show)++data FrameBlockInfo+   = FrameBlockLoop !Word16+   | FrameBlockExcept +   | FrameBlockFinallyTry +   | FrameBlockFinallyEnd+   deriving (Eq, Show)
+ src/Utils.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE RecordWildCards #-}+-----------------------------------------------------------------------------+-- |+-- Module      : Utils+-- Copyright   : (c) 2012, 2013 Bernie Pope+-- License     : BSD-style+-- Maintainer  : florbitous@gmail.com+-- Stability   : experimental+-- Portability : ghc+--+-- Utility functions which are used in multiple modules, or don't belong+-- anywhere else.+--+-----------------------------------------------------------------------------+module Utils+   ( isJump, isRelativeJump, isAbsoluteJump, isJumpBytecode, isPureExpr+   , isPyObjectExpr, isUnconditionalJump, isConditionalJump, mkVar, mkReturn+   , mkIdent, mkAssign, mkAssignVar, mkList, mkMethodCall, mkStmtExpr, mkSet+   , mkDict , mkSubscript, mkYield, identsFromParameters+   , spanToScopeIdentifier, fromIdentString, countPosParameters+   , maybeToList, getSpanLine )+   where ++import Blip.Bytecode (Opcode (..), Bytecode (..))+import Language.Python.Common.AST as AST+   ( ExprSpan, Expr (..), Statement (..), StatementSpan, Ident (..)+   , IdentSpan, Op (..), OpSpan, Argument (..), ArgumentSpan )+import Language.Python.Common.SrcLocation (SrcSpan (..))+import Types (Identifier, ScopeIdentifier, ParameterTypes (..))++getSpanLine :: SrcSpan -> Maybe Int+getSpanLine (SpanCoLinear {..}) = Just span_row+getSpanLine (SpanMultiLine {..}) = Just span_start_row+getSpanLine (SpanPoint {..}) = Just span_row+getSpanLine SpanEmpty = Nothing++maybeToList :: Maybe a -> [a]+maybeToList Nothing = []+maybeToList (Just x) = [x]++fromIdentString :: AST.Ident a -> Identifier+fromIdentString (Ident {..}) = ident_string++spanToScopeIdentifier :: SrcSpan -> ScopeIdentifier+spanToScopeIdentifier (SpanCoLinear {..})+   = (span_row, span_start_column, span_row, span_end_column)+spanToScopeIdentifier (SpanMultiLine {..})+   = (span_start_row, span_start_column, span_end_row, span_end_column)+spanToScopeIdentifier (SpanPoint {..})+   = (span_row, span_column, span_row, span_column)+spanToScopeIdentifier SpanEmpty+   = error "empty source span for scope identifier"++identsFromParameters :: ParameterTypes -> [Identifier]+identsFromParameters (ParameterTypes {..}) =+   parameterTypes_pos ++ maybeToList parameterTypes_varPos +++   maybeToList parameterTypes_varKeyword+{-+identsFromParameters :: [ParameterSpan] -> [Identifier]+identsFromParameters = concatMap getIdent+   where+   getIdent :: ParameterSpan -> [Identifier]+   getIdent (Param {..}) = [fromIdentString $ param_name]+   getIdent (VarArgsPos {..}) = [fromIdentString $ param_name]+   getIdent (VarArgsKeyword {..}) = [fromIdentString $ param_name]+   getIdent _other = []+-}++countPosParameters :: ParameterTypes -> Int+countPosParameters (ParameterTypes {..}) = length parameterTypes_pos+{-+countPosParameters :: [ParameterSpan] -> Int+countPosParameters = length . filter isPosParameter+   where+   isPosParameter :: ParameterSpan -> Bool+   isPosParameter (Param {}) = True+   isPosParameter _other = False +-}++-- True if an expression can be represented directly as a PyObject constant.+isPyObjectExpr :: ExprSpan -> Bool+isPyObjectExpr (AST.Int {}) = True+-- XXX not sure about longint+-- isPyObjectExpr (AST.LongInt {}) = True  +isPyObjectExpr (AST.Float {}) = True+-- XXX not sure about imaginary+-- isPyObjectExpr (AST.Imaginary {}) = True+isPyObjectExpr (AST.Bool {}) = True+isPyObjectExpr (AST.None {}) = True+isPyObjectExpr (AST.ByteStrings {}) = True+isPyObjectExpr (AST.Strings {}) = True+isPyObjectExpr (AST.UnicodeStrings {}) = True+isPyObjectExpr (AST.Tuple { tuple_exprs = exprs }) = all isPyObjectExpr exprs+isPyObjectExpr _other = False++-- True if evaluating an expression has no observable side effect+-- Raising an exception is a side-effect, so variables are not pure.+isPureExpr :: ExprSpan -> Bool+isPureExpr (AST.Int {}) = True+isPureExpr (AST.LongInt {}) = True+isPureExpr (AST.Float {}) = True+isPureExpr (AST.Imaginary {}) = True+isPureExpr (AST.Bool {}) = True+isPureExpr (AST.None {}) = True+isPureExpr (AST.ByteStrings {}) = True+isPureExpr (AST.Strings {}) = True+isPureExpr (AST.UnicodeStrings {}) = True+isPureExpr (AST.Tuple { tuple_exprs = exprs }) = all isPureExpr exprs+isPureExpr (AST.List { list_exprs = exprs }) = all isPureExpr exprs+isPureExpr (AST.Set { set_exprs = exprs }) = all isPureExpr exprs+isPureExpr (AST.Paren { paren_expr = expr }) = isPureExpr expr+isPureExpr (AST.Dictionary { dict_mappings = mappings }) =+   all (\(e1, e2) -> isPureExpr e1 && isPureExpr e2) mappings+-- XXX what about Lambda?+isPureExpr _other = False++isJumpBytecode :: Bytecode -> Bool+isJumpBytecode (Bytecode {..}) = isJump opcode++-- test if an opcode is a jump instruction+isJump :: Opcode -> Bool+isJump x = isRelativeJump x || isAbsoluteJump x++isRelativeJump :: Opcode -> Bool+isRelativeJump JUMP_FORWARD = True+isRelativeJump SETUP_LOOP = True+isRelativeJump FOR_ITER = True+isRelativeJump SETUP_FINALLY = True+isRelativeJump SETUP_EXCEPT = True+isRelativeJump SETUP_WITH = True+isRelativeJump _ = False++isAbsoluteJump :: Opcode -> Bool+isAbsoluteJump POP_JUMP_IF_FALSE = True+isAbsoluteJump POP_JUMP_IF_TRUE = True+isAbsoluteJump JUMP_ABSOLUTE = True+isAbsoluteJump CONTINUE_LOOP = True+isAbsoluteJump JUMP_IF_FALSE_OR_POP = True+isAbsoluteJump JUMP_IF_TRUE_OR_POP = True+isAbsoluteJump _ = False++isUnconditionalJump :: Opcode -> Bool+isUnconditionalJump JUMP_FORWARD = True+isUnconditionalJump JUMP_ABSOLUTE = True+isUnconditionalJump _other = False++isConditionalJump :: Opcode -> Bool+isConditionalJump = not . isUnconditionalJump++mkIdent :: String -> IdentSpan+mkIdent str = Ident { ident_string = str, ident_annot = SpanEmpty }++mkReturn :: ExprSpan -> StatementSpan+mkReturn expr = Return { return_expr = Just expr, stmt_annot = SpanEmpty }++mkYield :: ExprSpan -> ExprSpan+mkYield expr = Yield { yield_expr = Just expr, expr_annot = SpanEmpty }++mkVar :: IdentSpan -> ExprSpan+mkVar ident = Var { var_ident = ident, expr_annot = SpanEmpty }++mkAssignVar :: IdentSpan -> ExprSpan -> StatementSpan+mkAssignVar ident expr = mkAssign (mkVar ident) expr++mkAssign :: ExprSpan -> ExprSpan -> StatementSpan+mkAssign lhs rhs =+   Assign { assign_to = [lhs]+          , assign_expr = rhs +          , stmt_annot = SpanEmpty }++mkList :: [ExprSpan] -> ExprSpan+mkList exprs = List { list_exprs = exprs, expr_annot = SpanEmpty }++mkSet :: [ExprSpan] -> ExprSpan+mkSet exprs = Set { set_exprs = exprs, expr_annot = SpanEmpty }++mkDict :: [(ExprSpan, ExprSpan)] -> ExprSpan+mkDict exprs = Dictionary { dict_mappings = exprs, expr_annot = SpanEmpty }++mkMethodCall :: ExprSpan -> String -> ExprSpan -> ExprSpan+mkMethodCall object methodName argument =+   mkCall (mkAttributeLookup object methodName) [argument]++mkAttributeLookup :: ExprSpan -> String -> ExprSpan+mkAttributeLookup object methodName =+   BinaryOp { operator = dot+            , left_op_arg = object+            , right_op_arg = mkVar (mkIdent methodName)+            , expr_annot = SpanEmpty }++dot :: OpSpan+dot = Dot { op_annot = SpanEmpty }++mkCall :: ExprSpan -> [ExprSpan] -> ExprSpan +mkCall fun args = +   Call { call_fun = fun+        , call_args = map mkArgument args+        , expr_annot = SpanEmpty }++mkArgument :: ExprSpan -> ArgumentSpan+mkArgument expr = ArgExpr { arg_expr = expr, arg_annot = SpanEmpty }++mkStmtExpr :: ExprSpan -> StatementSpan+mkStmtExpr expr = StmtExpr { stmt_expr = expr, stmt_annot = SpanEmpty }++mkSubscript :: ExprSpan -> ExprSpan -> ExprSpan+mkSubscript object index =+   Subscript { subscriptee = object, subscript_expr = index, expr_annot = SpanEmpty }