Elm step A
[jackhill/mal.git] / elm / Env.elm
index 19158e8..658ee1a 100644 (file)
@@ -1,8 +1,11 @@
 module Env
     exposing
-        ( global
+        ( debug
+        , globalFrameId
+        , global
         , push
         , pop
+        , jump
         , enter
         , leave
         , ref
@@ -20,6 +23,14 @@ import Array
 import Set
 
 
+debug : Env -> String -> a -> a
+debug env msg value =
+    if env.debug then
+        Debug.log msg value
+    else
+        value
+
+
 globalFrameId : Int
 globalFrameId =
     0
@@ -32,9 +43,15 @@ global =
     , currentFrameId = globalFrameId
     , atoms = Dict.empty
     , nextAtomId = 0
+    , debug = False
     }
 
 
+jump : Int -> Env -> Env
+jump frameId env =
+    { env | currentFrameId = frameId }
+
+
 push : Env -> Env
 push env =
     let
@@ -91,8 +108,7 @@ enter : Int -> List ( String, MalExpr ) -> Env -> Env
 enter parentFrameId binds env =
     let
         frameId =
-            Debug.log "enter #"
-                env.nextFrameId
+            debug env "enter #" env.nextFrameId
 
         newFrame =
             setBinds binds (emptyFrame (Just parentFrameId))
@@ -108,8 +124,7 @@ leave : Int -> Env -> Env
 leave orgFrameId env =
     let
         frameId =
-            Debug.log "leave #"
-                env.currentFrameId
+            debug env "leave #" env.currentFrameId
     in
         { env
             | currentFrameId = orgFrameId
@@ -134,12 +149,6 @@ ref env =
         { env | frames = newFrames }
 
 
-
--- TODO: when disposing, deref all function's frames?
--- TODO: is that enough instead of a GC? no: don't know how often the function is referenced.
--- TODO: consideration: keep refCnt for MalFunction, or implement a light GC.
-
-
 deref : Maybe Frame -> Maybe Frame
 deref =
     Maybe.andThen
@@ -151,12 +160,6 @@ deref =
         )
 
 
-
--- TODO need a GC.
--- given a Env, see which frames are not reachable.
--- in MalFunction need to refer to the frameId.
-
-
 {-| Given an Env see which frames are not reachable from the
 global frame. Return a new Env without the unreachable frames.
 -}
@@ -170,11 +173,11 @@ gc env =
             data |> Dict.values |> countList acc
 
         countRefs expr acc =
-            Debug.log (toString expr) <|
+            debug env ("gc-visit " ++ (toString expr)) <|
                 case expr of
                     MalFunction (UserFunc { frameId }) ->
                         if not (Set.member frameId acc) then
-                            Debug.log "counting" <|
+                            debug env "gc-counting" <|
                                 case Dict.get frameId env.frames of
                                     Just frame ->
                                         countFrame (Set.insert frameId acc) frame
@@ -198,6 +201,11 @@ gc env =
 
         initSet =
             Set.fromList [ globalFrameId, env.currentFrameId ]
+
+        reportUnused frames used =
+            Dict.diff frames used
+                |> debug env "unused frames"
+                |> (\_ -> frames)
     in
         case Dict.get globalFrameId env.frames of
             Nothing ->
@@ -206,10 +214,11 @@ gc env =
             Just globalFrame ->
                 countFrame initSet globalFrame
                     |> Set.toList
-                    |> Debug.log "used frames"
+                    |> debug env "used frames"
                     |> List.map (\frameId -> ( frameId, emptyFrame Nothing ))
                     |> Dict.fromList
-                    |> Dict.intersect (Debug.log "cur frames" env.frames)
+                    |> reportUnused env.frames
+                    |> Dict.intersect env.frames
                     |> (\frames -> { env | frames = frames })
 
 
@@ -221,8 +230,8 @@ emptyFrame outerId =
     }
 
 
-set : String -> MalExpr -> Env -> Env
-set name expr env =
+setInFrame : Int -> String -> MalExpr -> Env -> Env
+setInFrame frameId name expr env =
     let
         updateFrame =
             Maybe.map
@@ -230,15 +239,17 @@ set name expr env =
                     { frame | data = Dict.insert name expr frame.data }
                 )
 
-        frameId =
-            env.currentFrameId
-
         newFrames =
             Dict.update frameId updateFrame env.frames
     in
         { env | frames = newFrames }
 
 
+set : String -> MalExpr -> Env -> Env
+set name expr env =
+    setInFrame env.currentFrameId name expr env
+
+
 get : String -> Env -> Result String MalExpr
 get name env =
     let
@@ -255,7 +266,7 @@ get name env =
                         Nothing ->
                             frame.outerId
                                 |> Maybe.map go
-                                |> Maybe.withDefault (Err "symbol not found")
+                                |> Maybe.withDefault (Err <| "'" ++ name ++ "' not found")
     in
         go env.currentFrameId