1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-20 01:57:09 +03:00
mal/elm/Env.elm
2017-06-11 22:44:49 +02:00

179 lines
4.0 KiB
Elm

module Env exposing (global, push, pop, enter, leave, ref, get, set)
import Types exposing (MalExpr, Frame, Env)
import Dict exposing (Dict)
global : Env
global =
{ frames = Dict.singleton 0 (emptyFrame Nothing)
, nextFrameId = 1
, currentFrameId = 0
}
push : Env -> Env
push env =
let
frameId =
env.nextFrameId
newFrame =
emptyFrame (Just env.currentFrameId)
in
{ currentFrameId = frameId
, frames = Dict.insert frameId newFrame env.frames
, nextFrameId = env.nextFrameId + 1
}
-- TODO Dont' return result, Debug.crash instead.
pop : Env -> Result String Env
pop env =
let
frameId =
env.currentFrameId
in
case Dict.get frameId env.frames of
Just currentFrame ->
case currentFrame.outerId of
Just outerId ->
Ok
{ env
| currentFrameId = outerId
, frames = Dict.update frameId deref env.frames
}
Nothing ->
Err "tried to pop global frame"
Nothing ->
Err ("current frame " ++ (toString frameId) ++ " doesn't exist")
setBinds : List ( String, MalExpr ) -> Frame -> Frame
setBinds binds frame =
case binds of
[] ->
frame
( name, expr ) :: rest ->
setBinds rest
{ frame | data = Dict.insert name expr frame.data }
enter : Int -> List ( String, MalExpr ) -> Env -> Env
enter parentFrameId binds env =
let
frameId =
env.nextFrameId
newFrame =
setBinds binds (emptyFrame (Just parentFrameId))
in
{ currentFrameId = frameId
, frames = Dict.insert frameId newFrame env.frames
, nextFrameId = env.nextFrameId + 1
}
leave : Int -> Env -> Env
leave orgFrameId env =
let
frameId =
env.currentFrameId
in
{ env
| currentFrameId = orgFrameId
, frames = Dict.update frameId deref env.frames
}
{-| Increase refCnt for the current frame
-}
ref : Env -> Env
ref env =
let
incRef =
Maybe.map
(\frame ->
{ frame | refCnt = frame.refCnt + 1 }
)
newFrames =
Dict.update env.currentFrameId incRef env.frames
in
{ env | frames = newFrames }
-- TODO: when disposing, deref all function's frames?
-- TODO: is that enough instead of a GC?
deref : Maybe Frame -> Maybe Frame
deref =
Maybe.andThen
(\frame ->
if frame.refCnt == 1 then
Nothing
else
Just { frame | refCnt = frame.refCnt - 1 }
)
-- TODO need a GC.
-- given a Env, see which frames are not reachable.
-- in MalFunction need to refer to the frameId.
emptyFrame : Maybe Int -> Frame
emptyFrame outerId =
{ outerId = outerId
, data = Dict.empty
, refCnt = 1
}
set : String -> MalExpr -> Env -> Env
set name expr env =
let
updateFrame =
Maybe.map
(\frame ->
{ frame | data = Dict.insert name expr frame.data }
)
frameId =
env.currentFrameId
newFrames =
Dict.update frameId updateFrame env.frames
in
{ env | frames = newFrames }
get : String -> Env -> Result String MalExpr
get name env =
let
go frameId =
case Dict.get frameId env.frames of
Nothing ->
Err "frame not found"
Just frame ->
case Dict.get name frame.data of
Just value ->
Ok value
Nothing ->
frame.outerId
|> Maybe.map go
|> Maybe.withDefault (Err "symbol not found")
in
go env.currentFrameId