codec-beam-0.2.0: src/Codec/Beam/Internal/Syntax.hs
module Codec.Beam.Internal.Syntax where
import Data.ByteString.Lazy (ByteString)
import Data.Text (Text)
import Data.Word (Word8)
-- | A virtual machine instruction—the main unit this library deals with.
-- There are a finite number of instructions, enumerated in "Codec.Beam.Instructions".
-- Each new release of Erlang/OTP might introduce a few more and deprecate old ones.
data Op = Op Word8 [Argument]
-- | Mark a spot in the code, so that you can jump to it with a function or condition.
-- __Start with @Label 1@ and go up from there.__
newtype Label = Label Int
deriving (Eq, Ord, Show)
-- | A stack register. These are used to pass function arguments, and @X 0@ stores return values.
newtype X = X Int
deriving (Eq, Ord, Show)
-- | A stack register for saving values across function calls.
-- Anything you put in a 'X' register can be overwritten inside a function call
-- (or inside a function call inside a function call).
-- @Y@ registers let you avoid that—they must be allocated and de-allocated though.
newtype Y = Y Int
deriving (Eq, Ord, Show)
-- | Floating point \"register\" for optimized floating point arithmetic.
-- These are not treated as traditional stack registers.
newtype F = F Int
deriving (Eq, Ord, Show)
-- | The empty list.
data Nil = Nil
deriving (Eq, Ord, Show)
-- | Turn a named function into a @fun@, for use with 'Codec.Beam.Instructions.make_fun2'.
data Lambda = Lambda
{ _lambda_name :: Text -- ^ unique name for this lambda
, _lambda_arity :: Int
, _lambda_label :: Label -- ^ where to find the backing function
, _lambda_free :: Int -- ^ how many variables to capture from calling scope
}
deriving (Eq, Ord, Show)
-- | Erlang literals, stored on the heap.
data Literal
= Integer Int
| Float Double
| Atom Text
| Binary ByteString
| Tuple [Literal]
| List [Literal]
| Map [(Literal, Literal)]
| ExternalFun Import
deriving (Eq, Ord, Show)
-- | Reference a function from another module.
-- For example, @Import "array" "map" 2@ refers to the stdlib function: @array:map/2@.
data Import = Import
{ _import_module :: Text
, _import_function :: Text
, _import_arity :: Int
}
deriving (Eq, Ord, Show)
-- | This constraint marks functions that do not require heap storage,
-- which means they can be called without concern for garbage collection.
class NoGC a
-- | Convert BIF to a normal import with zero arguments,
-- which can be used with 'Codec.Beam.Instructions.call' and friends.
importBif0 :: Bif0 a => a -> Import
importBif0 = unBif 0
class IsBif a => Bif0 a
-- | Convert BIF to a normal import with one argument.
importBif1 :: Bif1 a => a -> Import
importBif1 = unBif 1
class IsBif a => Bif1 a
-- | Convert BIF to a normal import with two arguments.
importBif2 :: Bif2 a => a -> Import
importBif2 = unBif 2
class IsBif a => Bif2 a
-- | Convert BIF to a normal import with three arguments.
importBif3 :: Bif3 a => a -> Import
importBif3 = unBif 3
class IsBif a => Bif3 a
-- | Convert BIF to a normal import with four arguments.
importBif4 :: Bif4 a => a -> Import
importBif4 = unBif 4
class IsBif a => Bif4 a
-- | Either type of stack register, 'X' or 'Y'.
-- Instructions that work with this type, use 'IsRegister' for convenience.
newtype Register = Register { unRegister :: Argument }
class IsRegister a where toRegister :: a -> Register
instance IsRegister Register where toRegister = id
instance IsRegister X where toRegister = Register . FromX
instance IsRegister Y where toRegister = Register . FromY
-- | Any sort of Erlang value.
-- Instructions that work with this type, use 'IsSource' for convenience.
-- Note the 'IsSource' instance for 'Text', which represents a stack atom
-- (in contrast with the 'Atom' constructor in 'Literal', which is heap-stored).
newtype Source = Source { unSource :: Argument }
class IsSource a where toSource :: a -> Source
instance IsSource Source where toSource = id
instance IsSource X where toSource = Source . FromX
instance IsSource Y where toSource = Source . FromY
instance IsSource Nil where toSource = Source . FromNil
instance IsSource Text where toSource = Source . FromAtom
instance IsSource Literal where toSource = Source . FromLiteral
instance IsSource Int where toSource = Source . FromInt
-- | Memory for manipulating 'F', for use with 'Codec.Beam.Instructions.fmove'.
-- Instructions that work with this type, use 'IsRegisterF' for convenience.
newtype RegisterF = RegisterF { unRegisterF :: Argument }
class IsRegisterF a where toRegisterF :: a -> RegisterF
instance IsRegisterF RegisterF where toRegisterF = id
instance IsRegisterF F where toRegisterF = RegisterF . FromF
instance IsRegisterF X where toRegisterF = RegisterF . FromX
instance IsRegisterF Y where toRegisterF = RegisterF . FromY
-- | Something that can be coerced into 'F', for use with 'Codec.Beam.Instructions.fmove'.
-- Instructions that work with this type, use 'IsSourceF' for convenience.
newtype SourceF = SourceF { unSourceF :: Argument }
class IsSourceF a where toSourceF :: a -> SourceF
instance IsSourceF SourceF where toSourceF = id
instance IsSourceF F where toSourceF = SourceF . FromF
instance IsSourceF X where toSourceF = SourceF . FromX
instance IsSourceF Y where toSourceF = SourceF . FromY
instance IsSourceF Literal where toSourceF = SourceF . FromLiteral
-- PRIVATE, not exposed outside of package
data Argument
= FromImport Import
| FromX X
| FromY Y
| FromF F
| FromNewLabel Label
| FromUntagged Int
| FromInt Int
| FromNil Nil
| FromAtom Text
| FromLabel Label
| FromLiteral Literal
| FromLambda Lambda
| FromList [Argument]
| FromNewFunction Text Int
fromRegister :: IsRegister a => a -> Argument
fromRegister = unRegister . toRegister
fromSource :: IsSource a => a -> Argument
fromSource = unSource . toSource
fromRegisterF :: IsRegisterF a => a -> Argument
fromRegisterF = unRegisterF . toRegisterF
fromSourceF :: IsSourceF a => a -> Argument
fromSourceF = unSourceF . toSourceF
fromDestinations :: [(Label, Source)] -> Argument
fromDestinations =
FromList . foldr (\x a -> FromLabel (fst x) : fromSource (snd x) : a) []
-- NOTE: This function reverses tuple order.
-- Seems to be necessary, but it's rather unintuitive,
-- given that 'fromDestinations' has opposite behavior.
fromPairs :: (a -> Argument) -> (b -> Argument) -> [(a, b)] -> Argument
fromPairs fromA fromB =
FromList . foldr (\(a, b) x -> fromB b : fromA a : x) []
class IsBif a where unBif :: Int -> a -> Import