tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

BrowserRuntime.hs (11081B)


      1 {-# LANGUAGE BangPatterns #-}
      2 -- Browser embedding primitives, not an alternate evaluator. All application is
      3 -- Runtime.Tree.apply; observation budgets bound transport, not reduction fuel.
      4 module BrowserRuntime where
      5 import Runtime.Tree
      6 import Runtime.Value
      7 import Control.Exception (evaluate)
      8 import Control.Monad (unless, forM_, foldM)
      9 import Data.Array (Array, listArray, (!), elems)
     10 import Data.Bits ((.|.), shiftL)
     11 import qualified Data.ByteString as BS
     12 import qualified Data.Text as Text
     13 import qualified Data.Text.Encoding as Text
     14 import qualified Data.IntMap.Strict as IM
     15 import qualified Data.IntSet as IS
     16 import Data.IORef
     17 import Data.Char (ord)
     18 import Data.List (intercalate)
     19 import System.Mem.StableName
     20 import Data.Word (Word32)
     21 import Numeric (showHex)
     22 import Text.Read (readMaybe)
     23 
     24 -- JSON output only. Inputs use length-prefixed byte fields, avoiding a second
     25 -- implementation of the site's JSON/literal validator inside the RTS.
     26 data J = S String | N Integer | B Bool | A [J] | O [(String,J)]
     27 json :: J -> String
     28 json (S s)='"':concatMap escape s++"\"" where
     29   escape '"'="\\\""; escape '\\'="\\\\"
     30   escape c | ord c<32 = let h=showHex (ord c) "" in "\\u"++replicate (4-length h) '0'++h
     31            | otherwise=[c]
     32 json (N n)=show n
     33 json (B b)=if b then "true" else "false"
     34 json (A xs)="["++intercalate "," (map json xs)++"]"
     35 json (O xs)="{"++intercalate "," [json(S k)++":"++json v | (k,v)<-xs]++"}"
     36 number :: Int -> J
     37 number=N . toInteger
     38 
     39 word :: BS.ByteString -> Int -> Int
     40 word b at=fromIntegral (foldr (.|.) 0 [fromIntegral(BS.index b (at+j)) `shiftL` (8*j) | j<-[0..3]] :: Word32)
     41 fields :: BS.ByteString -> IO [BS.ByteString]
     42 fields b=go 0 0 where
     43   go at count
     44     | at==BS.length b=pure []
     45     | count>=16384 || BS.length b-at<4=fail "invalid field framing"
     46     | otherwise=do
     47         let n=word b at
     48         unless(n>=0 && n<=BS.length b-at-4)(fail "truncated field")
     49         rest<-go (at+4+n) (count+1)
     50         pure(BS.take n (BS.drop (at+4) b):rest)
     51 utf8 :: BS.ByteString -> IO String
     52 utf8=either (fail.show) (pure.Text.unpack) . Text.decodeUtf8'
     53 index :: Int -> BS.ByteString -> IO Int
     54 index upper b=do
     55   s<-utf8 b
     56   case readMaybe s of Just n | n>=0 && n<upper -> pure n; _->fail "invalid index"
     57 
     58 loadDAG :: BS.ByteString -> IO (Array Int T,Int)
     59 loadDAG b=do
     60   unless(BS.length b>=8)(fail "truncated DAG")
     61   let n=word b 0;root=word b 4
     62   unless(n>0 && n<=1000000 && root>=0 && root<n && BS.length b==8+9*n)(fail "invalid DAG header")
     63   forM_ [0..n-1] $ \i->do
     64     let at=8+9*i;tag=BS.index b at;l=word b (at+1);r=word b(at+5);prior x=x>=0&&x<i
     65     unless(case tag of 0->l==0&&r==0;1->prior l&&r==0;2->prior l&&prior r;_->False)(fail "invalid DAG record")
     66   let trees=listArray (0,n-1) [case BS.index b(8+9*i) of
     67         0->Leaf;1->Stem(trees!word b(9+9*i));_->Fork(trees!word b(9+9*i))(trees!word b(13+9*i)) | i<-[0..n-1]]
     68   forM_ (elems trees) $ \t->evaluate(case t of Leaf->();Stem x->x `seq` ();Fork x y->x `seq` y `seq` ())
     69   pure(trees,n)
     70 
     71 boundedList :: Int -> T -> IO [T]
     72 boundedList limit=go limit where
     73   go n tree=case tree of
     74     Leaf->pure []
     75     Fork a rest | n>0 ->(a:) <$> go(n-1) rest
     76                 | otherwise->fail "List decode limit exceeded"
     77     _->fail "Invalid canonical list"
     78 natural :: T -> IO Integer
     79 natural=go 0 1 0 where
     80   go !bits !place !acc tree
     81     | bits>4096=fail "Natural number decode limit exceeded"
     82     | otherwise=case tree of
     83         Leaf->pure acc
     84         Fork bit rest->case bit of
     85           Leaf->go(bits+1)(2*place)acc rest
     86           Stem Leaf->go(bits+1)(2*place)(acc+place)rest
     87           _->fail "Invalid number bit"
     88         _->fail "Invalid canonical number"
     89 text :: Int -> T -> IO String
     90 text limit tree=boundedList limit tree >>= mapM (\t->natural t >>= either fail pure . toChar)
     91 boolean :: T -> IO Bool
     92 boolean Leaf=pure False
     93 boolean (Stem Leaf)=pure True
     94 boolean _=fail "Invalid canonical Boolean"
     95 pair :: T -> IO (T,T)
     96 pair (Fork a b)=pure(a,b)
     97 pair _=fail "Expected pair"
     98 
     99 -- Local IDs label a snapshot only. Register children WITHOUT evaluating them.
    100 -- Stable-name collisions are resolved by equality; aliases record thunk updates.
    101 -- Hitting the budget never forces the next pending child.
    102 preview :: Int -> T -> IO J
    103 preview limit root=do
    104   unless(limit>=1 && limit<=100000)(fail "Invalid snapshot budget")
    105   memo<-newIORef IM.empty
    106   next<-newIORef 0
    107   done<-newIORef IS.empty
    108   let register t=do
    109         sn<-makeStableName t
    110         seen<-readIORef memo
    111         case lookup sn (IM.findWithDefault [] (hashStableName sn) seen) of
    112           Just ident->pure ident
    113           Nothing->do
    114             ident<-readIORef next;modifyIORef' next (+1)
    115             modifyIORef' memo (IM.insertWith (++) (hashStableName sn) [(sn,ident)])
    116             pure ident
    117       go [] _ rows aliases=pure(rows,aliases,False)
    118       go _ 0 rows aliases=pure(rows,aliases,True)
    119       go ((ident,input):pending) budget rows aliases=do
    120         tree<-evaluate input
    121         sn<-makeStableName tree
    122         seen<-readIORef memo
    123         case lookup sn (IM.findWithDefault [] (hashStableName sn) seen) of
    124           Just other | other/=ident ->go pending budget rows ((ident,other):aliases)
    125           _->do
    126             modifyIORef' memo (IM.insertWith (++) (hashStableName sn) [(sn,ident)])
    127             visited<-readIORef done
    128             if IS.member ident visited then go pending budget rows aliases else do
    129               modifyIORef' done (IS.insert ident)
    130               let (kind,children)=case tree of Leaf->("leaf",[]);Stem a->("stem",[a]);Fork a b->("fork",[a,b])
    131               ids<-mapM register children
    132               go (reverse(zip ids children)++pending) (budget-1) ((ident,kind,ids):rows) aliases
    133   rid<-register root
    134   (rows,aliases,truncated)<-go [(rid,root)] limit [] []
    135   pure(O [("root",number rid),("nodes",A [O[("id",number i),("kind",S k),("children",A(map number cs))] | (i,k,cs)<-reverse rows]),
    136           ("aliases",A[O[("from",number a),("to",number b)] | (a,b)<-reverse aliases]),("truncated",B truncated)])
    137 
    138 view :: String -> T -> IO J
    139 view kind tree=do
    140   value<-case kind of
    141     "tricu"->preview 100000 tree
    142     "triangle"->preview 100000 tree
    143     "dag"->preview 100000 tree
    144     "ternary"->preview 100000 tree
    145     "tree"->preview 64 tree
    146     "string"->S <$> text 4096 tree
    147     "number"->S . show <$> natural tree
    148     "bool"->B <$> boolean tree
    149     "bytes"->do
    150       xs<-boundedList 4096 tree
    151       A <$> mapM (\x->do n<-natural x;unless(n<=255)(fail "Invalid byte");pure(N n)) xs
    152     "list"->boundedList 32 tree >>= fmap A . mapM (preview 8)
    153     "record"->do
    154       rows<-boundedList 32 tree
    155       (_,_,values)<-foldM record ([],0,[]) rows
    156       pure(O(reverse values))
    157     _->fail "Unsupported output codec"
    158   pure(O[("type",S kind),("value",value)])
    159   where
    160     record (names,size,values) row=do
    161       xs<-boundedList 3 row
    162       (key,tag,t)<-case xs of [a,b,c]->pure(a,b,c);_->fail "Record field must be [name tag value]"
    163       name<-text 128 key
    164       unless(not(null name)&&name `notElem` names)(fail "Record field names must be nonempty and unique")
    165       code<-natural tag
    166       (value,len)<-case code of
    167         0->do s<-text 4096 t;pure(S s,length s)
    168         1->do n<-natural t;unless(n<=9007199254740991)(fail "Record number exceeds safe integer range");pure(N n,length(show n))
    169         2->do b<-boolean t;pure(B b,if b then 4 else 5)
    170         _->fail "Unsupported record field tag"
    171       let total=size+length name+len
    172       unless(total<=16384)(fail "Record output limit exceeded")
    173       pure(name:names,total,(name,value):values)
    174 
    175 -- Explicit action machine. Events contain no tree handles; the RTS owns all
    176 -- continuations while the browser is waiting. Each resume consumes a fresh token.
    177 data Machine = Machine T [T] Int Int
    178 data Phase = Before Machine Integer T | ReadLine Machine | Wrote Machine | After Machine String | Done T
    179 
    180 beginIO :: T -> IO Phase
    181 beginIO root=do
    182   (sentinel,body)<-pair root
    183   s<-text 7 sentinel
    184   unless(s=="tricuIO")(fail "Missing tricuIO sentinel")
    185   (version,action)<-pair body
    186   v<-natural version
    187   unless(v==1)(fail "Unsupported tricuIO version")
    188   advance(Machine action [] 0 0)
    189 advance :: Machine -> IO Phase
    190 advance machine@(Machine action _ step _)=do
    191   unless(step<128)(fail "IO action limit exceeded")
    192   (tag,payload)<-pair action
    193   code<-natural tag
    194   unless(code `elem` [0,1,2,10,11])(fail ("Unsupported browser IO action "++show code))
    195   pure(Before machine code payload)
    196 finishValue :: String -> Machine -> T -> Phase
    197 finishValue _ (Machine _ [] _ _) value=Done value
    198 finishValue name (Machine _ (f:fs) step output) value=After (Machine (apply f value) fs step output) name
    199 
    200 -- An after event precedes completion too, so stepping never skips an action.
    201 data Pending = Pending Phase | Complete T Int String
    202 resumeIO :: Pending -> String -> IO Pending
    203 resumeIO (Complete value _ _) _=pure(Pending(Done value))
    204 resumeIO (Pending phase) value=case phase of
    205   Before (Machine action frames step output) code payload->do
    206     let m=Machine action frames (step+1) output
    207         result v=case frames of []->Complete v (step+1) (actionName code);_->Pending(finishValue (actionName code) m v)
    208     case code of
    209       0->pure(result payload)
    210       1->do (left,k)<-pair payload;pure(Pending(After (Machine left (k:frames) (step+1) output) "bind"))
    211       2->fail "Execution aborted (diagnostic payload not forced)."
    212       10->do
    213         s<-text 4096 payload
    214         unless(output+length s<=16384)(fail "Terminal output limit exceeded")
    215         pure(Pending(Wrote(Machine action frames (step+1) (output+length s))))
    216       11->pure(Pending(ReadLine m))
    217       _->fail "Unsupported IO action"
    218   ReadLine m@(Machine _ frames step _)->do
    219     unless(length value<=256 && all (`notElem` "\r\n") value)(fail "Input must be one bounded line")
    220     let t=ofString value
    221     pure(if null frames then Complete t step "getLine" else Pending(finishValue "getLine" m t))
    222   Wrote m@(Machine _ frames step _)->pure(if null frames then Complete Leaf step "putStr" else Pending(finishValue "putStr" m Leaf))
    223   After m _->Pending <$> advance m
    224   Done _->fail "IO already finished"
    225 actionName :: Integer -> String
    226 actionName code=case code of 0->"pure";1->"bind";2->"abortIO";10->"putStr";11->"getLine";_->"unknown"
    227 event :: Integer -> Pending -> IO J
    228 event token pending=do
    229   payload<-case pending of
    230     Complete _ step name->pure[("kind",S "after"),("step",number step),("name",S name),("value",S "retained value")]
    231     Pending phase->case phase of
    232       Before (Machine _ frames step _) code t->do
    233         detail<-if code==10 then text 4096 t else pure "retained value"
    234         pure[("kind",S "before"),("step",number(step+1)),("name",S(actionName code)),("detail",S detail),("depth",number(length frames))]
    235       ReadLine _->pure[("kind",S "read")]
    236       Wrote (Machine action _ _ _)->do (_,t)<-pair action;s<-text 4096 t;pure[("kind",S "write"),("text",S s)]
    237       After (Machine _ _ step _) name->pure[("kind",S "after"),("step",number step),("name",S name),("value",S "retained value")]
    238       Done _->pure[("kind",S "done")]
    239   pure(O(("token",N token):payload))