summaryrefslogtreecommitdiff
path: root/fig-bless/src/Fig/Bless/Runtime.hs
blob: 4de75e04bef7026a47c9dc7a783b65e97f281cbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
{-# Language ImplicitParams #-}

module Fig.Bless.Runtime
  ( ValueF(..)
  , EffectF(..)
  , ValueSort(..), valueSort
  , RuntimeError(..)
  , RunningTop, Running
  , BuiltinProgram, Builtin, Builtins
  , VM(..)
  , initialize
  , runProgram, runWord, run
  ) where

import Fig.Prelude

import Control.Exception.Safe (Typeable)

import qualified Data.Text as Text
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map

import qualified Data.Aeson as Aeson

import Fig.Bless.Types
import qualified Fig.Bless.Syntax as Syn

data ValueF t
  = ValueInteger Integer
  | ValueDouble Double
  | ValueString Text
  | ValueProgram (Syn.ProgramF t)
  | ValueArray [ValueF t]
  deriving (Show, Eq, Ord, Generic)
instance Aeson.ToJSON t => Aeson.ToJSON (ValueF t)
instance Pretty t => Pretty (ValueF t) where
  pretty (ValueInteger i) = tshow i
  pretty (ValueDouble d) = tshow d
  pretty (ValueString s) = tshow s
  pretty (ValueProgram p) = pretty p
  pretty (ValueArray vs) = mconcat
    [ "{"
    , unwords $ pretty <$> vs
    , "}"
    ]

data EffectF t
  = EffectPrint (ValueF t)
  | EffectPrintBackwards (ValueF t)
  | EffectSoundboard (ValueF t)
  | EffectModelToggle (ValueF t)
  deriving (Show, Eq, Ord, Generic)
instance Aeson.ToJSON t => Aeson.ToJSON (EffectF t)
instance Pretty t => Pretty (EffectF t) where
  pretty (EffectPrint x) =  "(print " <> pretty x <> ")"
  pretty (EffectPrintBackwards x) =  "(print-backwards " <> pretty x <> ")"
  pretty (EffectSoundboard x) =  "(soundboard " <> pretty x <> ")"
  pretty (EffectModelToggle x) =  "(moddle-toggle " <> pretty x <> ")"

data ValueSort
  = ValueSortInteger
  | ValueSortDouble
  | ValueSortString
  | ValueSortWord
  | ValueSortProgram
  | ValueSortArray
  deriving (Show, Eq, Ord, Generic)
instance Aeson.ToJSON ValueSort
instance Pretty ValueSort where
  pretty ValueSortInteger = "integer"
  pretty ValueSortDouble = "double"
  pretty ValueSortString = "string"
  pretty ValueSortWord = "word"
  pretty ValueSortProgram = "program"
  pretty ValueSortArray = "list"
valueSort :: ValueF t -> ValueSort
valueSort (ValueInteger _) = ValueSortInteger
valueSort (ValueDouble _) = ValueSortDouble
valueSort (ValueString _) = ValueSortString
valueSort (ValueProgram _) = ValueSortProgram
valueSort (ValueArray _) = ValueSortArray

data RuntimeError t
  = RuntimeErrorWordNotFound (Maybe t) Syn.Word
  | RuntimeErrorOutOfFuel (Maybe t)
  | RuntimeErrorStackUnderflow (Maybe t)
  | RuntimeErrorSortMismatch (Maybe t) ValueSort ValueSort
  deriving (Show, Eq, Ord, Generic)
instance (Show t, Typeable t) => Exception (RuntimeError t)
instance Aeson.ToJSON t => Aeson.ToJSON (RuntimeError t)
runtimeErrorPrefix :: Pretty t => Maybe t -> Text
runtimeErrorPrefix Nothing = ""
runtimeErrorPrefix (Just t) = mconcat
  [ "while evaluating term: ", pretty t, "\n"
  ]
instance Pretty t => Pretty (RuntimeError t) where
  pretty (RuntimeErrorWordNotFound t w) = mconcat
    [ runtimeErrorPrefix t
    , "word definition not found for: ", pretty w
    ]
  pretty (RuntimeErrorOutOfFuel t) = mconcat
    [ runtimeErrorPrefix t
    , "out of fuel"
    ]
  pretty (RuntimeErrorStackUnderflow t) = mconcat
    [ runtimeErrorPrefix t
    , "stack underflow"
    ]
  pretty (RuntimeErrorSortMismatch t expected actual) = mconcat
    [ runtimeErrorPrefix t
    , "sort mismatch at runtime (this probably shouldn't happen, please report it as a bug):\n"
    , "expected: ", pretty expected, "\n"
    , "actual: ", pretty actual
    ]

type RunningTop m t = (MonadThrow m, Typeable t, Show t)
type Running m t = (RunningTop m t, ?term :: Maybe t)
type BuiltinProgram m t = VM m t -> m (VM m t)
type Builtin m t = (BuiltinProgram m t, BProgType)
type Builtins m t = Maybe t -> Map Syn.Word (Builtin m t)
data VM m t = VM
  { fuel :: Maybe Integer
  , bindings :: Syn.DictionaryF t
  , builtins :: Builtins m t
  , stack :: [ValueF t]
  , effects :: [EffectF t]
  }

initialize :: Running m t => Maybe Integer -> Syn.DictionaryF t -> Builtins m t -> VM m t
initialize fuel bindings builtins = VM{..}
  where
    stack = []
    effects = []

checkFuel :: Running m t => VM m t -> m (VM m t)
checkFuel vm
  | Just f <- vm.fuel
  = if f <= 0
    then throwM $ RuntimeErrorOutOfFuel ?term
    else pure vm { fuel = Just $ f - 1 }
checkFuel vm = pure vm

push :: Running m t => ValueF t -> VM m t -> VM m t
push v vm = vm
  { stack = v : vm.stack
  }

runProgram :: Running m t => Syn.Extractor m t -> Syn.ProgramF t -> VM m t -> m (VM m t)
runProgram f (Syn.Program p) vm = foldM (flip (run f)) vm p

runWord :: Running m t => Syn.Extractor m t -> Syn.Word -> VM m t -> m (VM m t)
runWord _ w vm | Just (b, _) <- Map.lookup w $ vm.builtins ?term = b vm
runWord f w vm | Just p <- lookup w vm.bindings.defs = runProgram f p vm
runWord _ w _ = throwM $ RuntimeErrorWordNotFound ?term w

literalValue :: Syn.LiteralF t -> ValueF t
literalValue (Syn.LiteralInteger i) = ValueInteger i
literalValue (Syn.LiteralDouble i) = ValueDouble i
literalValue (Syn.LiteralString i) = ValueString i
literalValue (Syn.LiteralArray xs) = ValueArray $ literalValue <$> xs
literalValue (Syn.LiteralQuote p) = ValueProgram p

run :: Running m t => Syn.Extractor m t -> t -> VM m t -> m (VM m t)
run f v vm =
  let ?term = Just v in f v >>= \case
    Syn.TermLiteral l -> push (literalValue l) <$> checkFuel vm
    Syn.TermWord w -> runWord f w =<< checkFuel vm