1
1
mirror of https://github.com/github/semantic.git synced 2024-11-28 18:23:44 +03:00

Merge pull request #1892 from github/values-as-ordinary-adt

Values as ordinary ADT
This commit is contained in:
Rob Rix 2018-05-28 14:38:37 -04:00 committed by GitHub
commit 4843b03186
6 changed files with 96 additions and 254 deletions

View File

@ -11,198 +11,35 @@ import Data.Scientific (Scientific)
import Data.Scientific.Exts
import Data.Semigroup.Reducer
import qualified Data.Set as Set
import Data.Sum
import Prologue hiding (TypeError, project)
import Prelude hiding (Float, Integer, String, Rational)
import qualified Prelude
import Prologue
type ValueConstructors location
= '[Array
, Boolean
, Class location
, Closure location
, Float
, Hash
, Integer
, KVPair
, Namespace location
, Null
, Rational
, String
, Symbol
, Tuple
, Unit
, Hole
]
-- | Open union of primitive values that terms can be evaluated to.
-- Fix by another name.
newtype Value location = Value (Sum (ValueConstructors location) (Value location))
data Value location
= Closure PackageInfo ModuleInfo [Name] Label (Environment location)
| Unit
| Boolean Bool
| Integer (Number.Number Integer)
| Rational (Number.Number Rational)
| Float (Number.Number Scientific)
| String ByteString
| Symbol ByteString
| Tuple [Value location]
| Array [Value location]
| Class Name (Environment location)
| Namespace Name (Environment location)
| KVPair (Value location) (Value location)
| Hash [Value location]
| Null
| Hole
deriving (Eq, Show, Ord)
-- | Identical to 'inject', but wraps the resulting sub-entity in a 'Value'.
injValue :: (f :< ValueConstructors location) => f (Value location) -> Value location
injValue = Value . inject
-- | Identical to 'prj', but unwraps the argument out of its 'Value' wrapper.
prjValue :: (f :< ValueConstructors location) => Value location -> Maybe (f (Value location))
prjValue (Value v) = project v
-- | Convenience function for projecting two values.
prjPair :: (f :< ValueConstructors location , g :< ValueConstructors location)
=> (Value location, Value location)
-> Maybe (f (Value location), g (Value location))
prjPair = bitraverse prjValue prjValue
-- TODO: Parameterize Value by the set of constructors s.t. each language can have a distinct value union.
-- | A function value consisting of a package & module info, a list of parameter 'Name's, a 'Label' to jump to the body of the function, and an 'Environment' of bindings captured by the body.
data Closure location value = Closure PackageInfo ModuleInfo [Name] Label (Environment location)
deriving (Eq, Generic1, Ord, Show)
instance Eq location => Eq1 (Closure location) where liftEq = genericLiftEq
instance Ord location => Ord1 (Closure location) where liftCompare = genericLiftCompare
instance Show location => Show1 (Closure location) where liftShowsPrec = genericLiftShowsPrec
-- | The unit value. Typically used to represent the result of imperative statements.
data Unit value = Unit
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Unit where liftEq = genericLiftEq
instance Ord1 Unit where liftCompare = genericLiftCompare
instance Show1 Unit where liftShowsPrec = genericLiftShowsPrec
data Hole value = Hole
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Hole where liftEq = genericLiftEq
instance Ord1 Hole where liftCompare = genericLiftCompare
instance Show1 Hole where liftShowsPrec = genericLiftShowsPrec
-- | Boolean values.
newtype Boolean value = Boolean { getBoolean :: Bool }
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Boolean where liftEq = genericLiftEq
instance Ord1 Boolean where liftCompare = genericLiftCompare
instance Show1 Boolean where liftShowsPrec = genericLiftShowsPrec
-- | Arbitrary-width integral values.
newtype Integer value = Integer (Number.Number Prelude.Integer)
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Integer where liftEq = genericLiftEq
instance Ord1 Integer where liftCompare = genericLiftCompare
instance Show1 Integer where liftShowsPrec = genericLiftShowsPrec
-- | Arbitrary-width rational values values.
newtype Rational value = Rational (Number.Number Prelude.Rational)
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Rational where liftEq = genericLiftEq
instance Ord1 Rational where liftCompare = genericLiftCompare
instance Show1 Rational where liftShowsPrec = genericLiftShowsPrec
-- | String values.
newtype String value = String ByteString
deriving (Eq, Generic1, Ord, Show)
instance Eq1 String where liftEq = genericLiftEq
instance Ord1 String where liftCompare = genericLiftCompare
instance Show1 String where liftShowsPrec = genericLiftShowsPrec
-- | Possibly-interned Symbol values.
-- TODO: Should this store a 'Text'?
newtype Symbol value = Symbol ByteString
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Symbol where liftEq = genericLiftEq
instance Ord1 Symbol where liftCompare = genericLiftCompare
instance Show1 Symbol where liftShowsPrec = genericLiftShowsPrec
-- | Float values.
newtype Float value = Float (Number.Number Scientific)
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Float where liftEq = genericLiftEq
instance Ord1 Float where liftCompare = genericLiftCompare
instance Show1 Float where liftShowsPrec = genericLiftShowsPrec
-- | Zero or more values. Fixed-size at interpretation time.
-- TODO: Investigate whether we should use Vector for this.
-- TODO: Should we have a Some type over a nonemmpty list? Or does this merit one?
newtype Tuple value = Tuple [value]
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Tuple where liftEq = genericLiftEq
instance Ord1 Tuple where liftCompare = genericLiftCompare
instance Show1 Tuple where liftShowsPrec = genericLiftShowsPrec
-- | Zero or more values. Dynamically resized as needed at interpretation time.
-- TODO: Vector? Seq?
newtype Array value = Array [value]
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Array where liftEq = genericLiftEq
instance Ord1 Array where liftCompare = genericLiftCompare
instance Show1 Array where liftShowsPrec = genericLiftShowsPrec
-- | Class values. There will someday be a difference between classes and objects,
-- but for the time being we're pretending all languages have prototypical inheritance.
data Class location value = Class
{ _className :: Name
, _classScope :: Environment location
} deriving (Eq, Generic1, Ord, Show)
instance Eq location => Eq1 (Class location) where liftEq = genericLiftEq
instance Ord location => Ord1 (Class location) where liftCompare = genericLiftCompare
instance Show location => Show1 (Class location) where liftShowsPrec = genericLiftShowsPrec
data Namespace location value = Namespace
{ namespaceName :: Name
, namespaceScope :: Environment location
} deriving (Eq, Generic1, Ord, Show)
instance Eq location => Eq1 (Namespace location) where liftEq = genericLiftEq
instance Ord location => Ord1 (Namespace location) where liftCompare = genericLiftCompare
instance Show location => Show1 (Namespace location) where liftShowsPrec = genericLiftShowsPrec
data KVPair value = KVPair value value
deriving (Eq, Generic1, Ord, Show)
instance Eq1 KVPair where liftEq = genericLiftEq
instance Ord1 KVPair where liftCompare = genericLiftCompare
instance Show1 KVPair where liftShowsPrec = genericLiftShowsPrec
-- You would think this would be a @Map value value@ or a @[(value, value)].
-- You would be incorrect, as we can't derive a Generic1 instance for the above,
-- and in addition a 'Map' representation would lose information given hash literals
-- that assigned multiple values to one given key. Instead, this holds KVPair
-- values. The smart constructor for hashes in 'AbstractValue' ensures that these are
-- only populated with pairs.
newtype Hash value = Hash [value]
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Hash where liftEq = genericLiftEq
instance Ord1 Hash where liftCompare = genericLiftCompare
instance Show1 Hash where liftShowsPrec = genericLiftShowsPrec
data Null value = Null
deriving (Eq, Generic1, Ord, Show)
instance Eq1 Null where liftEq = genericLiftEq
instance Ord1 Null where liftCompare = genericLiftCompare
instance Show1 Null where liftShowsPrec = genericLiftShowsPrec
instance Ord location => ValueRoots location (Value location) where
valueRoots v
| Just (Closure _ _ _ _ env) <- prjValue v = Env.addresses env
| otherwise = mempty
| Closure _ _ _ _ env <- v = Env.addresses env
| otherwise = mempty
instance AbstractHole (Value location) where
hole = injValue Hole
hole = Hole
instance ( Members '[ Allocator location (Value location)
, Reader (Environment location)
@ -222,11 +59,11 @@ instance ( Members '[ Allocator location (Value location)
packageInfo <- currentPackage
moduleInfo <- currentModule
l <- label body
injValue . Closure packageInfo moduleInfo parameters l . Env.bind (foldr Set.delete freeVariables parameters) <$> getEnv
Closure packageInfo moduleInfo parameters l . Env.bind (foldr Set.delete freeVariables parameters) <$> getEnv
call op params = do
case prjValue op of
Just (Closure packageInfo moduleInfo names label env) -> do
case op of
Closure packageInfo moduleInfo names label env -> do
body <- goto label
-- Evaluate the bindings and body with the closures package/module info in scope in order to
-- charge them to the closure's origin.
@ -237,7 +74,7 @@ instance ( Members '[ Allocator location (Value location)
assign a v
Env.insert name a <$> rest) (pure env) (zip names params)
localEnv (mergeEnvs bindings) (body `catchReturn` \ (Return value) -> pure value)
Nothing -> throwValueError (CallError op)
_ -> throwValueError (CallError op)
-- | Construct a 'Value' wrapping the value arguments (if any).
@ -256,51 +93,51 @@ instance ( Members '[ Allocator location (Value location)
, Show location
)
=> AbstractValue location (Value location) (Goto effects (Value location) ': effects) where
unit = pure . injValue $ Unit
integer = pure . injValue . Integer . Number.Integer
boolean = pure . injValue . Boolean
string = pure . injValue . String
float = pure . injValue . Float . Number.Decimal
symbol = pure . injValue . Symbol
rational = pure . injValue . Rational . Number.Ratio
unit = pure Unit
integer = pure . Integer . Number.Integer
boolean = pure . Boolean
string = pure . String
float = pure . Float . Number.Decimal
symbol = pure . Symbol
rational = pure . Rational . Number.Ratio
multiple = pure . injValue . Tuple
array = pure . injValue . Array
multiple = pure . Tuple
array = pure . Array
kvPair k = pure . injValue . KVPair k
kvPair k = pure . KVPair k
null = pure . injValue $ Null
null = pure Null
asPair val
| Just (KVPair k v) <- prjValue val = pure (k, v)
| KVPair k v <- val = pure (k, v)
| otherwise = throwValueError $ KeyValueError val
hash = pure . injValue . Hash . fmap (injValue . uncurry KVPair)
hash = pure . Hash . map (uncurry KVPair)
klass n [] env = pure . injValue $ Class n env
klass n [] env = pure $ Class n env
klass n supers env = do
product <- foldl mergeEnvs emptyEnv . catMaybes <$> traverse scopedEnvironment supers
pure . injValue $ Class n (mergeEnvs product env)
pure $ Class n (mergeEnvs product env)
namespace n env = do
maybeAddr <- lookupEnv n
env' <- maybe (pure emptyEnv) (asNamespaceEnv <=< deref) maybeAddr
pure (injValue (Namespace n (Env.mergeNewer env' env)))
pure (Namespace n (Env.mergeNewer env' env))
where asNamespaceEnv v
| Just (Namespace _ env') <- prjValue v = pure env'
| otherwise = throwValueError $ NamespaceError ("expected " <> show v <> " to be a namespace")
| Namespace _ env' <- v = pure env'
| otherwise = throwValueError $ NamespaceError ("expected " <> show v <> " to be a namespace")
scopedEnvironment o
| Just (Class _ env) <- prjValue o = pure (Just env)
| Just (Namespace _ env) <- prjValue o = pure (Just env)
| Class _ env <- o = pure (Just env)
| Namespace _ env <- o = pure (Just env)
| otherwise = pure Nothing
asString v
| Just (String n) <- prjValue v = pure n
| otherwise = throwValueError $ StringError v
| String n <- v = pure n
| otherwise = throwValueError $ StringError v
ifthenelse cond if' else' = do
bool <- maybe (throwValueError (BoolError cond)) (pure . getBoolean) (prjValue cond)
bool <- case cond of { Boolean b -> pure b ; _ -> throwValueError (BoolError cond) }
if bool then if' else else'
index = go where
@ -308,26 +145,26 @@ instance ( Members '[ Allocator location (Value location)
| ii > genericLength list = throwValueError (BoundsError list ii)
| otherwise = pure (genericIndex list ii)
go arr idx
| (Just (Array arr, Integer (Number.Integer i))) <- prjPair (arr, idx) = tryIdx arr i
| (Just (Tuple tup, Integer (Number.Integer i))) <- prjPair (arr, idx) = tryIdx tup i
| (Array arr, Integer (Number.Integer i)) <- (arr, idx) = tryIdx arr i
| (Tuple tup, Integer (Number.Integer i)) <- (arr, idx) = tryIdx tup i
| otherwise = throwValueError (IndexError arr idx)
liftNumeric f arg
| Just (Integer (Number.Integer i)) <- prjValue arg = integer $ f i
| Just (Float (Number.Decimal d)) <- prjValue arg = float $ f d
| Just (Rational (Number.Ratio r)) <- prjValue arg = rational $ f r
| Integer (Number.Integer i) <- arg = integer $ f i
| Float (Number.Decimal d) <- arg = float $ f d
| Rational (Number.Ratio r) <- arg = rational $ f r
| otherwise = throwValueError (NumericError arg)
liftNumeric2 f left right
| Just (Integer i, Integer j) <- prjPair pair = tentative f i j & specialize
| Just (Integer i, Rational j) <- prjPair pair = tentative f i j & specialize
| Just (Integer i, Float j) <- prjPair pair = tentative f i j & specialize
| Just (Rational i, Integer j) <- prjPair pair = tentative f i j & specialize
| Just (Rational i, Rational j) <- prjPair pair = tentative f i j & specialize
| Just (Rational i, Float j) <- prjPair pair = tentative f i j & specialize
| Just (Float i, Integer j) <- prjPair pair = tentative f i j & specialize
| Just (Float i, Rational j) <- prjPair pair = tentative f i j & specialize
| Just (Float i, Float j) <- prjPair pair = tentative f i j & specialize
| (Integer i, Integer j) <- pair = tentative f i j & specialize
| (Integer i, Rational j) <- pair = tentative f i j & specialize
| (Integer i, Float j) <- pair = tentative f i j & specialize
| (Rational i, Integer j) <- pair = tentative f i j & specialize
| (Rational i, Rational j) <- pair = tentative f i j & specialize
| (Rational i, Float j) <- pair = tentative f i j & specialize
| (Float i, Integer j) <- pair = tentative f i j & specialize
| (Float i, Rational j) <- pair = tentative f i j & specialize
| (Float i, Float j) <- pair = tentative f i j & specialize
| otherwise = throwValueError (Numeric2Error left right)
where
tentative x i j = attemptUnsafeArithmetic (x i j)
@ -341,13 +178,13 @@ instance ( Members '[ Allocator location (Value location)
pair = (left, right)
liftComparison comparator left right
| Just (Integer (Number.Integer i), Integer (Number.Integer j)) <- prjPair pair = go i j
| Just (Integer (Number.Integer i), Float (Number.Decimal j)) <- prjPair pair = go (fromIntegral i) j
| Just (Float (Number.Decimal i), Integer (Number.Integer j)) <- prjPair pair = go i (fromIntegral j)
| Just (Float (Number.Decimal i), Float (Number.Decimal j)) <- prjPair pair = go i j
| Just (String i, String j) <- prjPair pair = go i j
| Just (Boolean i, Boolean j) <- prjPair pair = go i j
| Just (Unit, Unit) <- prjPair pair = boolean True
| (Integer (Number.Integer i), Integer (Number.Integer j)) <- pair = go i j
| (Integer (Number.Integer i), Float (Number.Decimal j)) <- pair = go (fromIntegral i) j
| (Float (Number.Decimal i), Integer (Number.Integer j)) <- pair = go i (fromIntegral j)
| (Float (Number.Decimal i), Float (Number.Decimal j)) <- pair = go i j
| (String i, String j) <- pair = go i j
| (Boolean i, Boolean j) <- pair = go i j
| (Unit, Unit) <- pair = boolean True
| otherwise = throwValueError (ComparisonError left right)
where
-- Explicit type signature is necessary here because we're passing all sorts of things
@ -365,11 +202,11 @@ instance ( Members '[ Allocator location (Value location)
liftBitwise operator target
| Just (Integer (Number.Integer i)) <- prjValue target = integer $ operator i
| Integer (Number.Integer i) <- target = integer $ operator i
| otherwise = throwValueError (BitwiseError target)
liftBitwise2 operator left right
| Just (Integer (Number.Integer i), Integer (Number.Integer j)) <- prjPair pair = integer $ operator i j
| (Integer (Number.Integer i), Integer (Number.Integer j)) <- pair = integer $ operator i j
| otherwise = throwValueError (Bitwise2Error left right)
where pair = (left, right)

View File

@ -37,14 +37,14 @@ spec = parallel $ do
it "subclasses" $ do
((res, _), _) <- evaluate "subclass.py"
res `shouldBe` Right [injValue (String "\"bar\"")]
res `shouldBe` Right [String "\"bar\""]
it "handles multiple inheritance left-to-right" $ do
((res, _), _) <- evaluate "multiple_inheritance.py"
res `shouldBe` Right [injValue (String "\"foo!\"")]
res `shouldBe` Right [String "\"foo!\""]
where
ns n = Just . Latest . Last . Just . injValue . Namespace n
ns n = Just . Latest . Last . Just . Namespace n
addr = Address . Precise
fixtures = "test/fixtures/python/analysis/"
evaluate entry = evalPythonProject (fixtures <> entry)

View File

@ -22,7 +22,7 @@ spec = parallel $ do
describe "Ruby" $ do
it "evaluates require_relative" $ do
((res, state), _) <- evaluate "main.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 1))]
res `shouldBe` Right [Value.Integer (Number.Integer 1)]
Env.names (environment state) `shouldContain` ["foo"]
it "evaluates load" $ do
@ -36,47 +36,47 @@ spec = parallel $ do
it "evaluates subclass" $ do
((res, state), _) <- evaluate "subclass.rb"
res `shouldBe` Right [injValue (String "\"<bar>\"")]
res `shouldBe` Right [String "\"<bar>\""]
Env.names (environment state) `shouldContain` [ "Bar", "Foo" ]
(derefQName (heap state) ("Bar" :| []) (environment state) >>= deNamespace) `shouldBe` Just ("Bar", ["baz", "foo", "inspect"])
it "evaluates modules" $ do
((res, state), _) <- evaluate "modules.rb"
res `shouldBe` Right [injValue (String "\"<hello>\"")]
res `shouldBe` Right [String "\"<hello>\""]
Env.names (environment state) `shouldContain` [ "Bar" ]
it "handles break correctly" $ do
((res, _), _) <- evaluate "break.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 3))]
res `shouldBe` Right [Value.Integer (Number.Integer 3)]
it "handles break correctly" $ do
((res, _), _) <- evaluate "next.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 8))]
res `shouldBe` Right [Value.Integer (Number.Integer 8)]
it "calls functions with arguments" $ do
((res, _), _) <- evaluate "call.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 579))]
res `shouldBe` Right [Value.Integer (Number.Integer 579)]
it "evaluates early return statements" $ do
((res, _), _) <- evaluate "early-return.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 123))]
res `shouldBe` Right [Value.Integer (Number.Integer 123)]
it "has prelude" $ do
((res, _), _) <- evaluate "preluded.rb"
res `shouldBe` Right [injValue (String "\"<foo>\"")]
res `shouldBe` Right [String "\"<foo>\""]
it "evaluates __LINE__" $ do
((res, _), _) <- evaluate "line.rb"
res `shouldBe` Right [injValue (Value.Integer (Number.Integer 4))]
res `shouldBe` Right [Value.Integer (Number.Integer 4)]
it "resolves builtins used in the prelude" $ do
((res, _), traces) <- evaluate "puts.rb"
res `shouldBe` Right [injValue Unit]
res `shouldBe` Right [Unit]
traces `shouldContain` [ "\"hello\"" ]
where
ns n = Just . Latest . Last . Just . injValue . Namespace n
ns n = Just . Latest . Last . Just . Namespace n
addr = Address . Precise
fixtures = "test/fixtures/ruby/analysis/"
evaluate entry = evalRubyProject (fixtures <> entry)

View File

@ -36,7 +36,7 @@ spec = parallel $ do
it "evaluates early return statements" $ do
((res, _), _) <- evaluate "early-return.ts"
res `shouldBe` Right [injValue (Value.Float (Number.Decimal 123.0))]
res `shouldBe` Right [Value.Float (Number.Decimal 123.0)]
where
fixtures = "test/fixtures/typescript/analysis/"

View File

@ -20,13 +20,13 @@ spec :: Spec
spec = parallel $ do
it "constructs integers" $ do
(expected, _) <- evaluate (integer 123)
expected `shouldBe` Right (injValue (Value.Integer (Number.Integer 123)))
expected `shouldBe` Right (Value.Integer (Number.Integer 123))
it "calls functions" $ do
(expected, _) <- evaluate $ do
identity <- closure [name "x"] lowerBound (variable (name "x"))
call identity [integer 123]
expected `shouldBe` Right (injValue (Value.Integer (Number.Integer 123)))
expected `shouldBe` Right (Value.Integer (Number.Integer 123))
evaluate
= runM

View File

@ -24,7 +24,7 @@ import Data.Abstract.FreeVariables as X
import Data.Abstract.Heap as X
import Data.Abstract.ModuleTable as X hiding (lookup)
import Data.Abstract.Name as X
import Data.Abstract.Value (Namespace(..), Value, ValueError, injValue, prjValue, runValueError)
import Data.Abstract.Value (Value(..), ValueError, runValueError)
import Data.Bifunctor (first)
import Data.Blob as X
import Data.ByteString.Builder (toLazyByteString)
@ -92,13 +92,18 @@ testEvaluating
. runTermEvaluator @_ @Precise
deNamespace :: Value Precise -> Maybe (Name, [Name])
deNamespace = fmap (namespaceName &&& Env.names . namespaceScope) . prjValue @(Namespace Precise)
deNamespace (Namespace name scope) = Just (name, Env.names scope)
deNamespace _ = Nothing
namespaceScope :: Value Precise -> Maybe (Environment Precise)
namespaceScope (Namespace _ scope) = Just scope
namespaceScope _ = Nothing
derefQName :: Heap Precise (Cell Precise) (Value Precise) -> NonEmpty Name -> Environment Precise -> Maybe (Value Precise)
derefQName heap = go
where go (n1 :| ns) env = Env.lookup n1 env >>= flip heapLookup heap >>= getLast . unLatest >>= case ns of
[] -> Just
(n2 : ns) -> fmap namespaceScope . prjValue @(Namespace Precise) >=> go (n2 :| ns)
(n2 : ns) -> namespaceScope >=> go (n2 :| ns)
newtype Verbatim = Verbatim ByteString
deriving (Eq)