|
1 | | -{-# LANGUAGE DeriveTraversable #-} |
2 | 1 | {-# LANGUAGE FlexibleContexts #-} |
3 | 2 | {-# LANGUAGE LambdaCase #-} |
4 | 3 | -- | This belongs in @semantic-python@ instead of @semantic-analysis@, but for the sake of expedience… |
5 | 4 | module Analysis.Syntax.Python |
6 | 5 | ( -- * Syntax |
7 | | - Term |
8 | | -, Python(..) |
| 6 | + Term(..) |
| 7 | +, subterms |
9 | 8 | -- * Abstract interpretation |
10 | 9 | , eval0 |
11 | 10 | , eval |
| 11 | + -- * Parsing |
| 12 | +, parse |
12 | 13 | ) where |
13 | 14 |
|
14 | 15 | import Analysis.Effect.Domain hiding ((:>>>)) |
15 | 16 | import qualified Analysis.Effect.Statement as S |
16 | 17 | import Analysis.Name |
17 | 18 | import Analysis.Reference |
18 | | -import qualified Analysis.Syntax as T |
19 | 19 | import Analysis.VM |
20 | 20 | import Control.Effect.Labelled |
21 | 21 | import Control.Effect.Reader |
| 22 | +import Control.Monad (foldM) |
| 23 | +import Data.Foldable (for_) |
22 | 24 | import Data.Function (fix) |
23 | | -import Data.List.NonEmpty (NonEmpty) |
24 | | -import Data.Text (Text) |
25 | | -import Source.Span (Span) |
| 25 | +import Data.List.NonEmpty (nonEmpty) |
| 26 | +import Data.Maybe (mapMaybe) |
| 27 | +import qualified Data.Set as Set |
| 28 | +import Data.Text (pack) |
| 29 | +import qualified Language.Python.Common as Py |
| 30 | +import Language.Python.Version3.Parser |
| 31 | +import Source.Span (Pos (..), Span (..), point) |
| 32 | +import System.FilePath (takeBaseName) |
26 | 33 |
|
27 | 34 | -- Syntax |
28 | 35 |
|
29 | | -type Term = T.Term Python Name |
| 36 | +data Term |
| 37 | + = Module (Py.Module Py.SrcSpan) |
| 38 | + | Statement (Py.Statement Py.SrcSpan) |
| 39 | + | Expr (Py.Expr Py.SrcSpan) |
| 40 | + deriving (Eq, Ord, Show) |
30 | 41 |
|
31 | | -data Python t |
32 | | - = Noop |
33 | | - | Iff t t t |
34 | | - | Bool Bool |
35 | | - | String Text |
36 | | - | Throw t |
37 | | - | Let Name t t |
38 | | - | t :>> t |
39 | | - | Import (NonEmpty Text) |
40 | | - | Function Name [Name] t |
41 | | - | Call t [t] |
42 | | - | Locate Span t |
43 | | - deriving (Eq, Foldable, Functor, Ord, Show, Traversable) |
44 | | - |
45 | | -infixl 1 :>> |
| 42 | +-- | Non-generic production of the recursive set of subterms. |
| 43 | +-- |
| 44 | +-- This should be exactly the set of nodes which 'eval' can visit, i.e. it excludes comments, etc. |
| 45 | +subterms :: Term -> Set.Set Term |
| 46 | +subterms t = Set.insert t $ case t of |
| 47 | + Module (Py.Module ss) -> suite ss |
| 48 | + Statement (Py.Conditional cts e _) -> foldMap (\ (c, t) -> subterms (Expr c) <> suite t) cts <> suite e |
| 49 | + Statement (Py.Raise (Py.RaiseV3 e) _) -> maybe Set.empty (subterms . Expr . fst) e |
| 50 | + -- FIXME: Py.RaiseV2 |
| 51 | + -- FIXME: whatever the second field is |
| 52 | + Statement (Py.StmtExpr e _) -> subterms (Expr e) |
| 53 | + Statement (Py.Fun _ _ _ ss _) -> suite ss |
| 54 | + -- FIXME: include 'subterms' of any default values |
| 55 | + Expr (Py.Call f as _) -> subterms (Expr f) <> foldMap (\case { Py.ArgExpr e _ -> subterms (Expr e) ; _ -> Set.empty }) as |
| 56 | + -- FIXME: support keyword args &c. |
| 57 | + _ -> Set.empty -- TBD, and terminals |
| 58 | + where |
| 59 | + suite = foldMap (subterms . Statement) |
46 | 60 |
|
47 | 61 |
|
48 | 62 | -- Abstract interpretation |
49 | 63 |
|
50 | | -eval0 :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m, Has (Reader Reference) sig m, Has S.Statement sig m) => Term -> m val |
| 64 | +eval0 :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m, Has (Reader Reference) sig m, Has S.Statement sig m, MonadFail m) => Term -> m val |
51 | 65 | eval0 = fix eval |
52 | 66 |
|
53 | 67 | eval |
54 | | - :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m, Has (Reader Reference) sig m, Has S.Statement sig m) |
| 68 | + :: (Has (Env addr) sig m, HasLabelled Store (Store addr val) sig m, Has (Dom val) sig m, Has (Reader Reference) sig m, Has S.Statement sig m, MonadFail m) |
55 | 69 | => (Term -> m val) |
56 | 70 | -> (Term -> m val) |
57 | 71 | eval eval = \case |
58 | | - T.Var n -> lookupEnv n >>= maybe (dvar n) fetch |
59 | | - T.Term s -> case s of |
60 | | - Noop -> dunit |
61 | | - Iff c t e -> do |
62 | | - c' <- eval c |
63 | | - dif c' (eval t) (eval e) |
64 | | - Bool b -> dbool b |
65 | | - String s -> dstring s |
66 | | - Throw e -> eval e >>= ddie |
67 | | - Let n v b -> do |
68 | | - v' <- eval v |
69 | | - let' n v' (eval b) |
70 | | - t :>> u -> do |
71 | | - t' <- eval t |
72 | | - u' <- eval u |
73 | | - t' >>> u' |
74 | | - Import ns -> S.simport ns >> dunit |
75 | | - Function n ps b -> letrec n (dabs ps (foldr (\ (p, a) m -> let' p a m) (eval b) . zip ps)) |
76 | | - Call f as -> do |
77 | | - f' <- eval f |
78 | | - as' <- traverse eval as |
79 | | - dapp f' as' |
80 | | - Locate s t -> local (setSpan s) (eval t) |
| 72 | + Module (Py.Module ss) -> suite ss |
| 73 | + Statement (Py.Import is sp) -> setSpan sp $ do |
| 74 | + for_ is $ \ Py.ImportItem{ Py.import_item_name = ns } -> case nonEmpty ns of |
| 75 | + Nothing -> pure () |
| 76 | + Just ss -> S.simport (pack . Py.ident_string <$> ss) |
| 77 | + dunit |
| 78 | + Statement (Py.Pass sp) -> setSpan sp dunit |
| 79 | + Statement (Py.Conditional cts e sp) -> setSpan sp $ foldr (\ (c, t) e -> do |
| 80 | + c' <- eval (Expr c) |
| 81 | + dif c' (suite t) e) (suite e) cts |
| 82 | + Statement (Py.Raise (Py.RaiseV3 e) sp) -> setSpan sp $ case e of |
| 83 | + Just (e, _) -> eval (Expr e) >>= ddie -- FIXME: from clause |
| 84 | + Nothing -> dunit >>= ddie |
| 85 | + -- FIXME: RaiseV2 |
| 86 | + -- FIXME: whatever the second field is |
| 87 | + Statement (Py.StmtExpr e sp) -> setSpan sp (eval (Expr e)) |
| 88 | + Statement (Py.Fun n ps _r ss sp) -> let ps' = mapMaybe (\case { Py.Param n _ _ _ -> Just (ident n) ; _ -> Nothing }) ps in setSpan sp $ letrec (ident n) (dabs ps' (foldr (\ (p, a) m -> let' p a m) (suite ss) . zip ps')) |
| 89 | + Expr (Py.Var n sp) -> setSpan sp $ let n' = ident n in lookupEnv n' >>= maybe (dvar n') fetch |
| 90 | + Expr (Py.Bool b sp) -> setSpan sp $ dbool b |
| 91 | + Expr (Py.Strings ss sp) -> setSpan sp $ dstring (pack (mconcat ss)) |
| 92 | + Expr (Py.Call f as sp) -> setSpan sp $ do |
| 93 | + f' <- eval (Expr f) |
| 94 | + as' <- traverse eval (mapMaybe (\case { Py.ArgExpr e _ -> Just (Expr e) ; _ -> Nothing }) as) |
| 95 | + -- FIXME: support keyword args &c. |
| 96 | + dapp f' as' |
| 97 | + _ -> fail "TBD" |
81 | 98 | where |
82 | | - setSpan s r = r{ refSpan = s } |
| 99 | + setSpan s = case fromSpan s of |
| 100 | + Just s -> local (\ r -> r{ refSpan = s }) |
| 101 | + _ -> id |
| 102 | + fromSpan Py.SpanEmpty = Nothing |
| 103 | + fromSpan (Py.SpanPoint _ l c) = Just (point (Pos l c)) |
| 104 | + fromSpan (Py.SpanCoLinear _ l c1 c2) = Just (Span (Pos l c1) (Pos l c2)) |
| 105 | + fromSpan (Py.SpanMultiLine _ l1 l2 c1 c2) = Just (Span (Pos l1 c1) (Pos l2 c2)) |
| 106 | + suite [] = dunit |
| 107 | + suite (s:ss) = do |
| 108 | + s' <- eval (Statement s) |
| 109 | + foldM (\ into each -> do |
| 110 | + each' <- eval (Statement each) |
| 111 | + into >>> each') s' ss |
| 112 | + ident = name . pack . Py.ident_string |
| 113 | + |
| 114 | + |
| 115 | +-- Parsing |
| 116 | + |
| 117 | +parse :: FilePath -> IO Term |
| 118 | +parse path = do |
| 119 | + src <- readFile path |
| 120 | + case parseModule src (takeBaseName path) of |
| 121 | + Left err -> fail (show err) |
| 122 | + Right (Py.Module ss, _) -> pure (Module (Py.Module ss)) |
0 commit comments