-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathGraphTerm.hs
316 lines (289 loc) · 12.3 KB
/
GraphTerm.hs
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
{-# LANGUAGE NoMonomorphismRestriction, DoRec #-}
module GraphTerm where
import qualified Data.Map as M
import qualified Data.Set as S
import Data.List
import Data.Maybe
import Control.Monad.State
import Data.Graph.Inductive.Query.Monad ((><))
-- TODO: will probably need this later for ad-hoc evaluation
{-bn_lift idx target =-}
{-if idx >= target then idx + 1 else idx-}
{-bn_lower idx target =-}
{-if idx == target then Nothing else-}
{-Just (if idx > target then idx - 1 else idx)-}
{--- TODO: lift to make room for a new lambda binding; does this operation make sense on bn?-}
{--- bn_abstract ... =-}
{-bn_substitute idx target val =-}
{-case bn_lower idx target of-}
{-Nothing -> val-}
{-Just bname -> Var bname-}
{--- TODO: term wrappers?-}
{-term_substitute rtsub term target val = tsub term-}
{-where-}
{-tsub (Var bname) = bn_substitute bname target val-}
{-tsub (Lam body) = Lam $ rtsub body (target + 1) val-}
{-tsub (App proc arg) = App (rtsub proc target val) (rtsub arg target val)-}
-- coinductive data dealt with by change in evaluation strategy to normal order
-- no need for explicit delay/force syntax (reserved for lower level languages?)
-- functions parameters are analyzed to see which can endanger termination
-- when termination is jeopardized by coinductively defined data, evaluation of
-- applications switches to normal order, thunking/delaying expressions
-- strictness analysis done to figure out when to force thunks before case scrutiny?
-- function cost analysis: per iteration/step, full undelayed process (may be infinite), parameterized by function parameters
-- possible nontermination as effect
-- termination analysis indicates dependency on the finiteness of certain parameters, maybe just in a sentence using AND and OR; failing this dependency indicates the function application needs to be delayed until forced (recursive calls will naturally do the same)
-- when operating dynamically, finding "delayed" expressions in these positions triggers further delay
type Address = Int
type Name = Int
type Nat = Integer
type SymUid = String
type SymName = String
type SymSet = (SymUid, [SymName])
type Symbol = (SymUid, SymName)
data ConstFiniteSet = CSNat [Nat] | CSInt [Integer] | CSSym SymSet
deriving (Show, Eq)
data Constant = CNat Nat | CInt Integer | CSym Symbol
| CInterpreted ConstFiniteSet Nat
deriving (Show, Eq)
cfs_index (CSNat nats) (CNat nat) = _cfs_elemIndex nat nats
cfs_index (CSInt ints) (CInt int) = _cfs_elemIndex int ints
cfs_index (CSSym (uid0, names)) (CSym (uid1, name)) =
if uid0 == uid1 then _cfs_elemIndex name names
else Left "cfs_index: mismatching symbol family"
cfs_index _ _ = Left "cfs_index: mismatching constant and set type"
_cfs_elemIndex elem elems = Right . CNat $ toInteger idx
where default_idx = length elems
idx = fromMaybe default_idx $ elemIndex elem elems
-- TODO
-- recursion-friendly pretty printing
-- switch to zipper contexts
-- small-step
-- generic context over subterms that are: Either still-a-term already-a-value
-- allows ad-hoc eval order (think user interaction)
-- any fixed eval order can also be defined
-- maintain and traverse a sequence of get/put functions over a context's remaining 'still-a-term' subterms
-- eval/substitute at arbitrary term positions
-- define evaluation orders as zipper traversals
-- try to simplify EvalCtrl
-- const/mutable regions?
-- need garbage collection of memory store?
-- NOTES
-- (ordered) (sub)sets of: nats; ints; symbols
-- unique ids for sets so tags can be distinguished/unforgeable
-- layering/association of ordered sets over nats to describe records on top of tuples
-- tuples:
-- allocation
-- indicate allocations performed in a mutable region: (mutable expr-that-allocates)
-- 'mutable' means that mutability can be observed from a distance; implies sharing
-- linear/unshared tuples can be modified without having been allocated in a mutable region
-- allows efficient initialization dynamically-sized of yet-to-be-shared 'constant' tuples
data ValueT term env value1 value2 =
Lam term env
| Tuple [value1]
| Const Constant
| ConstFinSet ConstFiniteSet
| Tagged Constant value2
| Undefined String
deriving (Show, Eq)
data TermT term = Value (ValueT term () term term)
| Var Name
| LetRec [term] term
| App term term
| TupleAlloc term
| TupleRead term term
| TupleWrite term term term
| ConstFinSetIndex term term
| TaggedGetConst term
| TaggedGetPayload term
deriving (Show, Eq)
data EvalCtrl a b c d e f g h = EvalCtrl
{ ctrl_eval :: a
, ctrl_wrap :: b
, ctrl_unwrap :: c
, ctrl_env_lookup :: d
, ctrl_env_extend :: e
, ctrl_store_lookup :: f
, ctrl_store_extend :: g
, ctrl_store_update :: h }
evalT ctrl term env = do
result <- evT term
return $ case result of
Left msg -> Undefined msg
Right val -> val
where
eval = ctrl_eval ctrl
wrap = ctrl_wrap ctrl
unwrap = ctrl_unwrap ctrl
evalUnwrap term = liftM unwrap $ eval term env
env_lookup = ctrl_env_lookup ctrl
env_extend = ctrl_env_extend ctrl
store_deref address = do
store <- get
return $ fromMaybe undef $ ctrl_store_lookup ctrl store address
where undef = wrap $ Undefined "dereferenced invalid address"
store_allocate values = do
store <- get
let (store', addresses) = ctrl_store_extend ctrl store values
put store'
return addresses
store_assign address value = do
store <- get
case ctrl_store_update ctrl store address value of
Nothing -> return . Right $ Undefined "assigned value to invalid address"
Just store' -> put store' >> (return . Right $ Tuple [])
apply proc arg = case unwrap proc of
Lam body penv -> liftM (Right . unwrap) $ eval body env'
where env' = env_extend penv arg
otherwise -> return $ Left "expected Lam"
untag ttagged = do
tagged <- eval ttagged env
return $ case unwrap tagged of
Tagged const payload -> Right (const, payload)
otherwise -> Left "expected Tagged"
construct (Lam body ()) = return $ Lam body env
construct (Tuple vals) =
liftM Tuple $ store_allocate =<< mapM (`eval` env) vals
construct (Const const) = return $ Const const
construct (ConstFinSet cfs) = return $ ConstFinSet cfs
construct (Tagged const val) = liftM (Tagged const) $ eval val env
construct (Undefined description) = return $ Undefined description
asTup (Tuple vals) = Right vals
asTup _ = Left "expected Tuple"
evalTup = liftM asTup . evalUnwrap
asConst (Const const) = Right const
asConst _ = Left "expected Const"
asNat val = do
cnat <- asConst val
case cnat of
CNat nat -> Right $ fromInteger nat
otherwise -> Left "expected Nat"
evalNat = liftM asNat . evalUnwrap
asCfs (ConstFinSet cfs) = Right cfs
asCfs _ = Left "expected ConstFinSet"
evT (Value val) = liftM Right $ construct val
evT (Var name) = return . Right $ env_lookup env name
evT (LetRec bindings body) = do
rec env' <- liftM (foldl env_extend env) $ mapM (`eval` env') bindings
liftM (Right . unwrap) $ eval body env'
evT (App tproc targ) = do
proc <- eval tproc env
arg <- eval targ env
apply proc arg
evT (TupleAlloc tsize) = do
esize <- evalNat tsize
case esize of
Left msg -> return $ Left msg
Right size ->
liftM (Right . Tuple) $ store_allocate $ replicate size undef
where undef = wrap $ Undefined "TupleAlloc: uninitialized slot"
evT (TupleRead ttup tidx) = do
etup <- evalTup ttup
eidx <- evalNat tidx
case (do
tup <- etup
idx <- eidx
if idx < length tup then Right $ tup !! idx
else Left "TupleRead: index out of bounds") of
Left msg -> return $ Left msg
Right address -> liftM (Right . unwrap) $ store_deref address
evT (TupleWrite ttup tidx targ) = do
etup <- evalTup ttup
eidx <- evalNat tidx
arg <- eval targ env
case (do
tup <- etup
idx <- eidx
if idx < length tup then Right (tup !! idx, arg)
else Left "TupleWrite: index out of bounds") of
Left msg -> return $ Left msg
Right (address, arg) -> store_assign address arg
evT (ConstFinSetIndex tcfs tconst) = do
vcfs <- evalUnwrap tcfs
vconst <- evalUnwrap tconst
return (do
cfs <- asCfs vcfs
const <- asConst vconst
liftM Const $ cfs_index cfs const)
evT (TaggedGetConst ttagged) = withTagged (Const . fst) ttagged
evT (TaggedGetPayload ttagged) = withTagged (unwrap . snd) ttagged
withTagged f = liftM (liftM f) . untag
----------------------------------------------------------------
-- Simple guiding example
----------------------------------------------------------------
newtype SimpleTerm = SimpleTerm { simple_term :: TermT SimpleTerm }
deriving (Show, Eq)
newtype SimpleValue = SimpleValue {
simple_value :: ValueT SimpleTerm SimpleEnv Address SimpleValue }
deriving (Show, Eq)
newtype SimpleEnv = SimpleEnv [SimpleValue]
deriving (Show, Eq)
data SimpleStore = SimpleStore
{ sstore_values :: M.Map Address SimpleValue
, sstore_next :: Address }
deriving (Show, Eq)
senv_lookup (SimpleEnv vals) name = simple_value $ vals !! name
senv_extend (SimpleEnv vals) val = SimpleEnv $ val : vals
sstore_empty = SimpleStore M.empty 0
sstore_lookup store address = M.lookup address $ sstore_values store
sstore_extend store vals = (store', addresses)
where current = sstore_next store
next = current + length vals
addresses = [current .. next - 1]
assocs = zip addresses vals
values' = foldr (uncurry M.insert) (sstore_values store) assocs
store' = store {sstore_values = values', sstore_next = next}
sstore_update store address val =
if M.member address values then Just store' else Nothing
where values = sstore_values store
store' = store { sstore_values = M.insert address val values }
simple_ctrl =
EvalCtrl simple_eval SimpleValue simple_value
senv_lookup senv_extend
sstore_lookup sstore_extend sstore_update
simple_eval (SimpleTerm term) env = do
value <- evalT simple_ctrl term env
return $ SimpleValue value
app tp ta = SimpleTerm $ App tp ta
lam body = SimpleTerm . Value $ Lam body ()
var = SimpleTerm . Var
value = SimpleTerm . Value
tuple = value . Tuple
tupalloc sz = SimpleTerm $ TupleAlloc sz
tupread tup idx = SimpleTerm $ TupleRead tup idx
tupwrite tup idx val = SimpleTerm $ TupleWrite tup idx val
constant = value . Const
cnat = constant . CNat
cfsidx cfs const = SimpleTerm $ ConstFinSetIndex cfs const
-- TODO: recursion-friendly pretty-printing
test_recfunc0 = lam $ var 0
test_recfunc1 = lam $ app (var 3) $ cnat 64
test_recfunc2 = lam $ app (var 2) $ var 0
test_letrec = SimpleTerm $ LetRec [test_recfunc0, test_recfunc1, test_recfunc2] $ app (var 0) $ cnat 72
-- TODO: saner way to build test cases; most of the pain is manually tracking de Bruijn indices
test_seq taction tresult = app (lam tresult) taction
test_writer = lam $ lam $ app
(lam (test_seq (tupwrite (var 0) (cnat 0) (var 2))
(test_seq (tupwrite (var 1) (cnat 1) (var 2))
(var 2))))
(tupalloc (cnat 2))
test_sym = constant . CSym $ ("global", "two")
test_cfs = value . ConstFinSet $ CSSym ("global", ["one", "two", "three"])
test_tup0 = tuple [cnat 0, cnat 1, cnat 2, cnat 3, cnat 4, cnat 5, cnat 6]
test_tup1 = tuple [cnat 7, cnat 8, cnat 9, cnat 10, cnat 11, cnat 12]
test_term = tuple [cnat 4,
app (lam $ var 0) (lam $ lam $ var 1),
tupread (tuple [cnat 11, cnat 421]) (cnat 1),
tupalloc (cnat 2),
app (app test_writer (cnat 987)) (cnat 654),
cfsidx test_cfs test_sym,
test_letrec]
test = runState (simple_eval test_term $ SimpleEnv []) sstore_empty
----------------------------------------------------------------
-- Somewhat more heavy-duty approach
----------------------------------------------------------------
-- TODO: where to track open binders?
type Labeled term = ((), term)
type Addressed term = Either Address term
newtype ALTerm = ALTerm (Addressed (Labeled (TermT ALTerm)))
deriving (Show, Eq)