blip-0.2.1: src/Desugar.hs
{-# 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