Helpful library updates
This commit is contained in:
@@ -2,45 +2,27 @@
|
|||||||
!import "../../lib/io.tri" !Local
|
!import "../../lib/io.tri" !Local
|
||||||
!import "../../lib/socket.tri" !Local
|
!import "../../lib/socket.tri" !Local
|
||||||
|
|
||||||
-- Preserve the host-driver Result shape on error, run okCase on success.
|
-- Main accept+echo loop. Recursion via y.
|
||||||
onOk = action okCase :
|
echoLoop = y (self : server :
|
||||||
bind action (result :
|
withAccepted_ server
|
||||||
matchResult
|
(err :
|
||||||
(err rest : pure result)
|
bind (putStrLn (append "accept error: " err)) (_ :
|
||||||
okCase
|
self server))
|
||||||
result)
|
(clientSock addr :
|
||||||
|
bind (putStrLn (append "client from " addr)) (_ :
|
||||||
-- Convenience: print a string and continue.
|
onResult_ (recv clientSock 4096)
|
||||||
printLn = s : bind (putStr (append s "\n")) (_ : pure t)
|
(err :
|
||||||
|
bind (closeSocket clientSock) (_ :
|
||||||
-- Main accept+echo loop. Recursion via y.
|
self server))
|
||||||
echoLoop = y (self server :
|
(msg :
|
||||||
bind (accept server) (acceptResult :
|
bind (send clientSock msg) (_ :
|
||||||
matchResult
|
bind (closeSocket clientSock) (_ :
|
||||||
(err rest :
|
self server))))))
|
||||||
bind (printLn (append "accept error: " err)) (_ :
|
|
||||||
self server))
|
|
||||||
(accepted rest :
|
|
||||||
matchPair
|
|
||||||
(clientSock addr :
|
|
||||||
bind (printLn (append "client from " addr)) (_ :
|
|
||||||
bind (recv clientSock 4096) (msgResult :
|
|
||||||
matchResult
|
|
||||||
(err rest :
|
|
||||||
bind (closeSocket clientSock) (_ :
|
|
||||||
self server))
|
|
||||||
(msg rest :
|
|
||||||
bind (send clientSock msg) (_ :
|
|
||||||
bind (closeSocket clientSock) (_ :
|
|
||||||
self server)))
|
|
||||||
msgResult)))
|
|
||||||
accepted)
|
|
||||||
acceptResult))
|
|
||||||
|
|
||||||
main = io (
|
main = io (
|
||||||
onOk socket (server rest :
|
onOk_ socket (server :
|
||||||
onOk (bindSocket server "127.0.0.1" 0) (_ rest :
|
onOk_ (bindSocket server "127.0.0.1" 0) (_ :
|
||||||
onOk (listen server 5) (_ rest :
|
onOk_ (listen server 5) (_ :
|
||||||
onOk (getSocketName server) (port rest :
|
onOk_ (getSocketName server) (port :
|
||||||
bind (printLn (append "Echo server listening on port " (showNumber port))) (_ :
|
bind (putStrLn (append "Echo server listening on port " (showNumber port))) (_ :
|
||||||
echoLoop server))))))
|
echoLoop server))))))
|
||||||
|
|||||||
100
lib/base.tri
100
lib/base.tri
@@ -33,6 +33,15 @@ lOr = (triage
|
|||||||
|
|
||||||
matchPair = a : triage _ _ a
|
matchPair = a : triage _ _ a
|
||||||
|
|
||||||
|
fst = p : matchPair (a b : a) p
|
||||||
|
snd = p : matchPair (a b : b) p
|
||||||
|
|
||||||
|
resultIsOk = result :
|
||||||
|
matchResult (err rest : false) (val rest : true) result
|
||||||
|
|
||||||
|
resultIsErr = result :
|
||||||
|
matchResult (err rest : true) (val rest : false) result
|
||||||
|
|
||||||
not? = matchBool false true
|
not? = matchBool false true
|
||||||
and? = matchBool id (_ : false)
|
and? = matchBool id (_ : false)
|
||||||
|
|
||||||
@@ -87,3 +96,94 @@ matchResult = (errCase okCase result :
|
|||||||
tag)
|
tag)
|
||||||
payload)
|
payload)
|
||||||
result)
|
result)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Maybe / Option type
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
nothing = t
|
||||||
|
just = x : t x
|
||||||
|
|
||||||
|
matchMaybe = (nothingCase justCase maybe :
|
||||||
|
triage
|
||||||
|
nothingCase
|
||||||
|
justCase
|
||||||
|
(_ _ : nothingCase)
|
||||||
|
maybe)
|
||||||
|
|
||||||
|
maybe = default f m : matchMaybe default f m
|
||||||
|
maybeMap = f m : matchMaybe nothing (x : just (f x)) m
|
||||||
|
maybeBind = m f : matchMaybe nothing f m
|
||||||
|
maybeOr = default m : matchMaybe default id m
|
||||||
|
maybe? = matchMaybe false (_ : true)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Basic arithmetic
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pred = y (self : triage
|
||||||
|
0
|
||||||
|
(_ : 0)
|
||||||
|
(bit rest :
|
||||||
|
matchBool
|
||||||
|
(matchBool
|
||||||
|
0
|
||||||
|
(pair 0 rest)
|
||||||
|
(equal? rest 0))
|
||||||
|
(matchBool
|
||||||
|
0
|
||||||
|
(pair 1 (self rest))
|
||||||
|
(equal? rest 0))
|
||||||
|
bit))
|
||||||
|
|
||||||
|
isZero? = triage true (_ : false) (_ _ : false)
|
||||||
|
|
||||||
|
add = y (self x y :
|
||||||
|
triage
|
||||||
|
y
|
||||||
|
(_ : succ y)
|
||||||
|
(_ _ : succ (self (pred x) y))
|
||||||
|
x)
|
||||||
|
|
||||||
|
sub = y (self a b :
|
||||||
|
matchBool
|
||||||
|
a
|
||||||
|
(self (pred a) (pred b))
|
||||||
|
(isZero? b))
|
||||||
|
|
||||||
|
lt? = a b : not? (isZero? (sub b a))
|
||||||
|
lte? = a b : isZero? (sub a b)
|
||||||
|
|
||||||
|
mul = y (self a b :
|
||||||
|
matchBool
|
||||||
|
0
|
||||||
|
(add a (self a (pred b)))
|
||||||
|
(isZero? b))
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Result combinators
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
mapResult = (f result :
|
||||||
|
matchResult
|
||||||
|
(code rest : err code rest)
|
||||||
|
(value rest : ok (f value) rest)
|
||||||
|
result)
|
||||||
|
|
||||||
|
bindResult = (result f :
|
||||||
|
matchResult
|
||||||
|
(code rest : err code rest)
|
||||||
|
(value rest : f value rest)
|
||||||
|
result)
|
||||||
|
|
||||||
|
resultOr = (default result :
|
||||||
|
matchResult
|
||||||
|
(_ _ : default)
|
||||||
|
(value _ : value)
|
||||||
|
result)
|
||||||
|
|
||||||
|
resultMapErr = (f result :
|
||||||
|
matchResult
|
||||||
|
(code rest : err (f code) rest)
|
||||||
|
(value rest : ok value rest)
|
||||||
|
result)
|
||||||
|
|||||||
@@ -54,19 +54,65 @@ expectU8 = (expected bs :
|
|||||||
(byteEq? actual expected))
|
(byteEq? actual expected))
|
||||||
(readU8 bs))
|
(readU8 bs))
|
||||||
|
|
||||||
mapResult = (f result :
|
|
||||||
matchResult
|
|
||||||
(code rest : err code rest)
|
|
||||||
(value rest : ok (f value) rest)
|
|
||||||
result)
|
|
||||||
|
|
||||||
bindResult = (result f :
|
|
||||||
matchResult
|
|
||||||
(code rest : err code rest)
|
|
||||||
(value rest : f value rest)
|
|
||||||
result)
|
|
||||||
|
|
||||||
read2 = (bs : readBytes 2 bs)
|
read2 = (bs : readBytes 2 bs)
|
||||||
read4 = (bs : readBytes 4 bs)
|
read4 = (bs : readBytes 4 bs)
|
||||||
readU16BEBytes = (bs : read2 bs)
|
readU16BEBytes = (bs : read2 bs)
|
||||||
readU32BEBytes = (bs : read4 bs)
|
readU32BEBytes = (bs : read4 bs)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Parser combinators
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pureParser = value bs : ok value bs
|
||||||
|
failParser = code bs : err code bs
|
||||||
|
|
||||||
|
mapParser = f p bs : mapResult f (p bs)
|
||||||
|
bindParser = p f bs : bindResult (p bs) f
|
||||||
|
thenParser = p q bs : bindResult (p bs) (_ : q)
|
||||||
|
|
||||||
|
orParser = (p q bs :
|
||||||
|
matchResult
|
||||||
|
(_ _ : q bs)
|
||||||
|
(value rest : ok value rest)
|
||||||
|
(p bs))
|
||||||
|
|
||||||
|
readWhile_ = y (self pred bs acc :
|
||||||
|
matchResult
|
||||||
|
(code rest : ok (reverse acc) bs)
|
||||||
|
(value rest :
|
||||||
|
matchBool
|
||||||
|
(self pred rest (pair value acc))
|
||||||
|
(ok (reverse acc) (pair value rest))
|
||||||
|
(pred value))
|
||||||
|
(readU8 bs))
|
||||||
|
|
||||||
|
readWhile = pred bs : readWhile_ pred bs t
|
||||||
|
|
||||||
|
readUntil = pred : readWhile (x : not? (pred x))
|
||||||
|
|
||||||
|
readRemaining = bs : ok bs t
|
||||||
|
|
||||||
|
peekU8 = (bs :
|
||||||
|
matchResult
|
||||||
|
(code rest : err code bs)
|
||||||
|
(value rest : ok value bs)
|
||||||
|
(readU8 bs))
|
||||||
|
|
||||||
|
eof? = (bs :
|
||||||
|
matchBool
|
||||||
|
(ok t bs)
|
||||||
|
(err errUnexpectedEof bs)
|
||||||
|
(emptyList? bs))
|
||||||
|
|
||||||
|
expectAscii = expectBytes
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Endian / int conversion helpers
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
u16BE = bytes : add (mul 256 (head bytes)) (head (tail bytes))
|
||||||
|
|
||||||
|
u16LE = bytes : add (mul 256 (head (tail bytes))) (head bytes)
|
||||||
|
|
||||||
|
readU16BE = bs : bindParser read2 (bytes rest : ok (u16BE bytes) rest) bs
|
||||||
|
readU16LE = bs : bindParser read2 (bytes rest : ok (u16LE bytes) rest) bs
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
!import "base.tri" !Local
|
!import "base.tri" !Local
|
||||||
!import "list.tri" !Local
|
!import "list.tri" !Local
|
||||||
|
|
||||||
nothing = t
|
|
||||||
just = x : t x
|
|
||||||
|
|
||||||
bytesNil? = emptyList?
|
bytesNil? = emptyList?
|
||||||
|
|
||||||
bytesHead = matchList nothing (h _ : just h)
|
bytesHead = matchList nothing (h _ : just h)
|
||||||
|
|||||||
@@ -1,23 +1,6 @@
|
|||||||
!import "base.tri" !Local
|
!import "base.tri" !Local
|
||||||
!import "list.tri" !Local
|
!import "list.tri" !Local
|
||||||
|
|
||||||
pred = y (self : triage
|
|
||||||
0
|
|
||||||
(_ : 0)
|
|
||||||
(bit rest :
|
|
||||||
matchBool
|
|
||||||
-- odd: 2n + 1 -> 2n
|
|
||||||
(matchBool
|
|
||||||
0
|
|
||||||
(pair 0 rest)
|
|
||||||
(equal? rest 0))
|
|
||||||
-- even: 2n -> 2n - 1
|
|
||||||
(matchBool
|
|
||||||
0
|
|
||||||
(pair 1 (self rest))
|
|
||||||
(equal? rest 0))
|
|
||||||
bit))
|
|
||||||
|
|
||||||
incDecRev = y (self : matchList
|
incDecRev = y (self : matchList
|
||||||
"1"
|
"1"
|
||||||
(digit rest :
|
(digit rest :
|
||||||
|
|||||||
72
lib/io.tri
72
lib/io.tri
@@ -37,6 +37,55 @@ sleep = ms : pair 63 ms
|
|||||||
|
|
||||||
thenIO = a b : bind a (_ : b)
|
thenIO = a b : bind a (_ : b)
|
||||||
mapIO = action f : bind action (x : pure (f x))
|
mapIO = action f : bind action (x : pure (f x))
|
||||||
|
void = action : bind action (_ : pure t)
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Conditional execution
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
when = cond action : matchBool action (pure t) cond
|
||||||
|
unless = cond action : matchBool (pure t) action cond
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Infinite loop
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
forever = y (self : action :
|
||||||
|
bind action (_ :
|
||||||
|
self action))
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Result-aware combinators
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- Propagate driver Result on error; run okCase on success.
|
||||||
|
onOk = action okCase :
|
||||||
|
bind action (result :
|
||||||
|
matchResult
|
||||||
|
(err rest : pure result)
|
||||||
|
okCase
|
||||||
|
result)
|
||||||
|
|
||||||
|
-- Same as onOk, but the okCase only receives the value (rest is dropped).
|
||||||
|
onOk_ = action okCase :
|
||||||
|
bind action (result :
|
||||||
|
matchResult
|
||||||
|
(err rest : pure result)
|
||||||
|
(val _ : okCase val)
|
||||||
|
result)
|
||||||
|
|
||||||
|
-- Generalized Result handler with explicit branches.
|
||||||
|
onResult = action errCase okCase :
|
||||||
|
bind action (result :
|
||||||
|
matchResult errCase okCase result)
|
||||||
|
|
||||||
|
-- Same as onResult, but handlers only receive the value/msg (rest is dropped).
|
||||||
|
onResult_ = action errCase okCase :
|
||||||
|
bind action (result :
|
||||||
|
matchResult
|
||||||
|
(err _ : errCase err)
|
||||||
|
(val _ : okCase val)
|
||||||
|
result)
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- Convenience helpers
|
-- Convenience helpers
|
||||||
@@ -49,13 +98,9 @@ putStrLn = s : bind (putStr (append s "\n")) (_ : pure t)
|
|||||||
-- Result-aware file helpers
|
-- Result-aware file helpers
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
onReadFile = (path errCase okCase :
|
onReadFile = path : onResult (readFile path)
|
||||||
bind (readFile path) (result :
|
|
||||||
matchResult errCase okCase result))
|
|
||||||
|
|
||||||
onWriteFile = (path contents errCase okCase :
|
onWriteFile = path contents : onResult (writeFile path contents)
|
||||||
bind (writeFile path contents) (result :
|
|
||||||
matchResult errCase okCase result))
|
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- Convenience helpers for the common cases
|
-- Convenience helpers for the common cases
|
||||||
@@ -84,3 +129,18 @@ copyFile = (src dst :
|
|||||||
(ok rest : pure t)
|
(ok rest : pure t)
|
||||||
wr))
|
wr))
|
||||||
result))
|
result))
|
||||||
|
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
-- Resource-safe combinators
|
||||||
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
finally = action cleanup :
|
||||||
|
bind action (result :
|
||||||
|
bind cleanup (_ :
|
||||||
|
pure result))
|
||||||
|
|
||||||
|
bracket = acquire release use :
|
||||||
|
bind acquire (resource :
|
||||||
|
bind (use resource) (result :
|
||||||
|
bind (release resource) (_ :
|
||||||
|
pure result)))
|
||||||
|
|||||||
173
lib/list.tri
173
lib/list.tri
@@ -37,9 +37,13 @@ length = y (self : matchList
|
|||||||
0
|
0
|
||||||
(_ tail : succ (self tail)))
|
(_ tail : succ (self tail)))
|
||||||
|
|
||||||
reverse = y (self : matchList
|
reverse_ = y (self xs acc :
|
||||||
t
|
matchList
|
||||||
(head tail : append (self tail) (pair head t)))
|
acc
|
||||||
|
(h r : self r (pair h acc))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
reverse = xs : reverse_ xs t
|
||||||
|
|
||||||
snoc = y (self x : matchList
|
snoc = y (self x : matchList
|
||||||
(pair x t)
|
(pair x t)
|
||||||
@@ -80,3 +84,166 @@ nth_ = y (self n xs i :
|
|||||||
xs)
|
xs)
|
||||||
|
|
||||||
nth = n xs : nth_ n xs 0
|
nth = n xs : nth_ n xs 0
|
||||||
|
|
||||||
|
headMaybe = matchList nothing (h _ : just h)
|
||||||
|
|
||||||
|
lastMaybe = y (self : matchList
|
||||||
|
nothing
|
||||||
|
(hd tl : matchBool
|
||||||
|
(just hd)
|
||||||
|
(self tl)
|
||||||
|
(emptyList? tl)))
|
||||||
|
|
||||||
|
nthMaybe_ = y (self n xs i :
|
||||||
|
matchList
|
||||||
|
nothing
|
||||||
|
(h r :
|
||||||
|
matchBool
|
||||||
|
(just h)
|
||||||
|
(self n r (succ i))
|
||||||
|
(equal? i n))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
nthMaybe = n xs : nthMaybe_ n xs 0
|
||||||
|
|
||||||
|
take_ = y (self n xs i :
|
||||||
|
matchList
|
||||||
|
t
|
||||||
|
(h r :
|
||||||
|
matchBool
|
||||||
|
t
|
||||||
|
(pair h (self n r (succ i)))
|
||||||
|
(equal? i n))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
take = n xs : take_ n xs 0
|
||||||
|
|
||||||
|
drop_ = y (self n xs i :
|
||||||
|
matchBool
|
||||||
|
xs
|
||||||
|
(matchList
|
||||||
|
t
|
||||||
|
(_ r : self n r (succ i))
|
||||||
|
xs)
|
||||||
|
(equal? i n))
|
||||||
|
|
||||||
|
drop = n xs : drop_ n xs 0
|
||||||
|
|
||||||
|
splitAt = n xs : pair (take n xs) (drop n xs)
|
||||||
|
|
||||||
|
concatMap_ = y (self f xs :
|
||||||
|
matchList
|
||||||
|
t
|
||||||
|
(h r : append (f h) (self f r))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
concatMap = f xs : concatMap_ f xs
|
||||||
|
|
||||||
|
find = y (self pred xs :
|
||||||
|
matchList
|
||||||
|
nothing
|
||||||
|
(h r : matchBool (just h) (self pred r) (pred h))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
partition_ = y (self pred xs trues falses :
|
||||||
|
matchList
|
||||||
|
(pair (reverse trues) (reverse falses))
|
||||||
|
(h r :
|
||||||
|
matchBool
|
||||||
|
(self pred r (pair h trues) falses)
|
||||||
|
(self pred r trues (pair h falses))
|
||||||
|
(pred h))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
partition = pred xs : partition_ pred xs t t
|
||||||
|
|
||||||
|
partition = pred xs : partition_ pred xs t t
|
||||||
|
|
||||||
|
strLength = length
|
||||||
|
strAppend = append
|
||||||
|
strEq? = equal?
|
||||||
|
strEmpty? = emptyList?
|
||||||
|
|
||||||
|
startsWith? = y (self prefix str :
|
||||||
|
matchList
|
||||||
|
true
|
||||||
|
(ph pr :
|
||||||
|
matchList
|
||||||
|
false
|
||||||
|
(sh sr :
|
||||||
|
matchBool
|
||||||
|
(self pr sr)
|
||||||
|
false
|
||||||
|
(equal? ph sh))
|
||||||
|
str)
|
||||||
|
prefix)
|
||||||
|
|
||||||
|
endsWith? = prefix str : startsWith? (reverse prefix) (reverse str)
|
||||||
|
|
||||||
|
contains? = y (self needle haystack :
|
||||||
|
matchBool
|
||||||
|
true
|
||||||
|
(matchList
|
||||||
|
false
|
||||||
|
(_ r : self needle r)
|
||||||
|
haystack)
|
||||||
|
(startsWith? needle haystack))
|
||||||
|
|
||||||
|
lines_ = y (self str :
|
||||||
|
matchList
|
||||||
|
(acc current : append acc [(reverse current)])
|
||||||
|
(h r :
|
||||||
|
acc current :
|
||||||
|
matchBool
|
||||||
|
(self r (append acc [(reverse current)]) t)
|
||||||
|
(self r acc (pair h current))
|
||||||
|
(equal? h 10))
|
||||||
|
str)
|
||||||
|
|
||||||
|
lines = str : lines_ str t t
|
||||||
|
|
||||||
|
unlines = y (self lines :
|
||||||
|
matchList
|
||||||
|
""
|
||||||
|
(h r : append h (append "\n" (self r)))
|
||||||
|
lines)
|
||||||
|
|
||||||
|
words_ = y (self str :
|
||||||
|
matchList
|
||||||
|
(acc current :
|
||||||
|
matchBool
|
||||||
|
acc
|
||||||
|
(append acc [(reverse current)])
|
||||||
|
(emptyList? current))
|
||||||
|
(h r :
|
||||||
|
acc current :
|
||||||
|
matchBool
|
||||||
|
(matchBool
|
||||||
|
(self r acc current)
|
||||||
|
(self r (append acc [(reverse current)]) t)
|
||||||
|
(emptyList? current))
|
||||||
|
(self r acc (pair h current))
|
||||||
|
(equal? h 32))
|
||||||
|
str)
|
||||||
|
|
||||||
|
words = str : words_ str t t
|
||||||
|
|
||||||
|
unwords = y (self words :
|
||||||
|
matchList
|
||||||
|
""
|
||||||
|
(h r :
|
||||||
|
matchBool
|
||||||
|
h
|
||||||
|
(append h (append " " (self r)))
|
||||||
|
(emptyList? r))
|
||||||
|
words)
|
||||||
|
|
||||||
|
zipWith = y (self f xs ys :
|
||||||
|
matchList
|
||||||
|
t
|
||||||
|
(xh xt :
|
||||||
|
matchList
|
||||||
|
t
|
||||||
|
(yh yt : pair (f xh yh) (self f xt yt))
|
||||||
|
ys)
|
||||||
|
xs)
|
||||||
|
|||||||
@@ -16,48 +16,68 @@ recv = sock maxBytes : pair 76 (pair sock maxBytes)
|
|||||||
send = sock bytes : pair 77 (pair sock bytes)
|
send = sock bytes : pair 77 (pair sock bytes)
|
||||||
getSocketName = sock : pair 78 sock
|
getSocketName = sock : pair 78 sock
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- Result-aware wrappers over raw socket actions.
|
||||||
-- Convenience helpers
|
onSocket = onResult socket
|
||||||
-- ---------------------------------------------------------------------------
|
onBindSocket = sock addr port : onResult (bindSocket sock addr port)
|
||||||
|
onListen = sock backlog : onResult (listen sock backlog)
|
||||||
|
onAccept = sock : onResult (accept sock)
|
||||||
|
onConnect = sock addr port : onResult (connect sock addr port)
|
||||||
|
onRecv = sock maxBytes : onResult (recv sock maxBytes)
|
||||||
|
onSend = sock bytes : onResult (send sock bytes)
|
||||||
|
onGetSocketName = sock : onResult (getSocketName sock)
|
||||||
|
|
||||||
onSocket = (action errCase okCase :
|
-- Result-aware wrappers that drop the useless 'rest' parameter.
|
||||||
bind action (result :
|
onSocket_ = onResult_ socket
|
||||||
matchResult errCase okCase result))
|
onBindSocket_ = sock addr port : onResult_ (bindSocket sock addr port)
|
||||||
|
onListen_ = sock backlog : onResult_ (listen sock backlog)
|
||||||
|
onAccept_ = sock : onResult_ (accept sock)
|
||||||
|
onConnect_ = sock addr port : onResult_ (connect sock addr port)
|
||||||
|
onRecv_ = sock maxBytes : onResult_ (recv sock maxBytes)
|
||||||
|
onSend_ = sock bytes : onResult_ (send sock bytes)
|
||||||
|
onGetSocketName_ = sock : onResult_ (getSocketName sock)
|
||||||
|
|
||||||
|
-- Close a socket, ignoring errors.
|
||||||
|
closeSocket_ = sock : void (closeSocket sock)
|
||||||
|
|
||||||
-- Create a listening socket bound to an address and port.
|
-- Create a listening socket bound to an address and port.
|
||||||
-- Returns ok listenSocket or err message.
|
-- Returns ok listenSocket or err message.
|
||||||
listenSocket = addr port backlog :
|
listenSocket = addr port backlog :
|
||||||
bind (socket) (result :
|
onOk_ socket (server :
|
||||||
matchResult
|
onOk_ (bindSocket server addr port) (_ :
|
||||||
(err rest : pure (err "socket creation failed"))
|
onOk_ (listen server backlog) (_ :
|
||||||
(sock rest :
|
pure (ok server))))
|
||||||
bind (bindSocket sock addr port) (bindResult :
|
|
||||||
matchResult
|
|
||||||
(err rest : pure (err "bind failed"))
|
|
||||||
(_ rest :
|
|
||||||
bind (listen sock backlog) (listenResult :
|
|
||||||
matchResult
|
|
||||||
(err rest : pure (err "listen failed"))
|
|
||||||
(_ rest : pure (ok sock))
|
|
||||||
listenResult))
|
|
||||||
bindResult))
|
|
||||||
result)
|
|
||||||
|
|
||||||
-- Accept a connection and return (clientSocket, peerAddr).
|
-- Accept a connection with explicit error and ok branches.
|
||||||
-- The returned peerAddr is a string like "127.0.0.1:8080".
|
-- okHandler receives (clientSocket, peerAddr).
|
||||||
onAccept = (sock errCase okCase :
|
withAccepted = (server errHandler okHandler :
|
||||||
bind (accept sock) (result :
|
onResult (accept server)
|
||||||
matchResult errCase okCase result))
|
errHandler
|
||||||
|
(accepted rest :
|
||||||
|
okHandler (fst accepted) (snd accepted)))
|
||||||
|
|
||||||
-- Receive all available bytes up to maxBytes.
|
-- Same as withAccepted, but handlers drop the useless 'rest' parameter.
|
||||||
onRecv = (sock maxBytes errCase okCase :
|
withAccepted_ = (server errHandler okHandler :
|
||||||
bind (recv sock maxBytes) (result :
|
onResult_ (accept server)
|
||||||
matchResult errCase okCase result))
|
errHandler
|
||||||
|
(accepted :
|
||||||
|
okHandler (fst accepted) (snd accepted)))
|
||||||
|
|
||||||
-- Send bytes and return number of bytes sent.
|
serveOnce = (server handler :
|
||||||
onSend = (sock bytes errCase okCase :
|
withAccepted_ server
|
||||||
bind (send sock bytes) (result :
|
(err : pure t)
|
||||||
matchResult errCase okCase result))
|
(client peer :
|
||||||
|
handler client peer))
|
||||||
|
|
||||||
-- Close a socket, ignoring errors.
|
serveForkingOnce = (server handler :
|
||||||
closeSocket_ = sock : bind (closeSocket sock) (_ : pure t)
|
withAccepted_ server
|
||||||
|
(err : pure t)
|
||||||
|
(client peer :
|
||||||
|
fork (handler client peer)))
|
||||||
|
|
||||||
|
serveForever = (server handler :
|
||||||
|
forever (serveForkingOnce server handler))
|
||||||
|
|
||||||
|
connectTo = (addr port :
|
||||||
|
onOk socket (client rest :
|
||||||
|
onOk (connect client addr port) (_ rest :
|
||||||
|
pure (ok client rest))))
|
||||||
|
|||||||
@@ -1,749 +0,0 @@
|
|||||||
Below is the implementation handoff for replacing the current recursive/rebuilding IO small-step interpreter with an explicit machine stack, primarily to support `Reader` via `ask` and `local`, while setting up the right shape for eventual async.
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
Refactor `IODriver` from this model:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
stepIO :: IOPermissions -> T -> IO Step
|
|
||||||
|
|
||||||
data Step
|
|
||||||
= Done T
|
|
||||||
| Continue T
|
|
||||||
```
|
|
||||||
|
|
||||||
to an explicit abstract machine:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
Machine = Runtime + current action + continuation frames
|
|
||||||
```
|
|
||||||
|
|
||||||
This is required because `local` is dynamically scoped. It needs to modify the Reader environment for a sub-computation, then restore the previous environment exactly when that sub-computation completes. The current “rebuild `Bind left' k`” approach has nowhere to store that restoration behavior.
|
|
||||||
|
|
||||||
This change should support:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
ask
|
|
||||||
local
|
|
||||||
```
|
|
||||||
|
|
||||||
now, and keep the structure compatible with future async suspension/resumption.
|
|
||||||
|
|
||||||
Do not implement async in this pass.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New action tags
|
|
||||||
|
|
||||||
Extend the tricu IO action language with Reader tags:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
ask = _ : pair 6 t
|
|
||||||
local = f action : pair 7 (pair f action)
|
|
||||||
```
|
|
||||||
|
|
||||||
Host-side:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Action
|
|
||||||
= APure T
|
|
||||||
| ABind T T
|
|
||||||
| APutStr T
|
|
||||||
| AGetLine
|
|
||||||
| AReadFile T
|
|
||||||
| AWriteFile T T
|
|
||||||
| AAsk
|
|
||||||
| ALocal T T
|
|
||||||
deriving (Show)
|
|
||||||
```
|
|
||||||
|
|
||||||
Recommended tag allocation:
|
|
||||||
|
|
||||||
```text
|
|
||||||
0 = pure
|
|
||||||
1 = bind
|
|
||||||
2 = putStr
|
|
||||||
3 = getLine
|
|
||||||
4 = readFile
|
|
||||||
5 = writeFile
|
|
||||||
6 = ask
|
|
||||||
7 = local
|
|
||||||
```
|
|
||||||
|
|
||||||
State tags can come later:
|
|
||||||
|
|
||||||
```text
|
|
||||||
8 = get
|
|
||||||
9 = put
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not add `bindR`, `bindS`, or `bindRS` yet. Reader is being added as an effect inside the existing IO action language, so the existing IO `bind` remains the only sequencing operator.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New runtime model
|
|
||||||
|
|
||||||
Add a runtime context:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Runtime = Runtime
|
|
||||||
{ rtPerms :: IOPermissions
|
|
||||||
, rtEnv :: T
|
|
||||||
}
|
|
||||||
deriving (Show)
|
|
||||||
```
|
|
||||||
|
|
||||||
Later this can become:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Runtime = Runtime
|
|
||||||
{ rtPerms :: IOPermissions
|
|
||||||
, rtEnv :: T
|
|
||||||
, rtState :: T
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
but for this pass, keep it minimal unless State is implemented at the same time.
|
|
||||||
|
|
||||||
Add continuation frames:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Frame
|
|
||||||
= BindFrame T
|
|
||||||
| LocalFrame T
|
|
||||||
deriving (Show)
|
|
||||||
```
|
|
||||||
|
|
||||||
Frame meanings:
|
|
||||||
|
|
||||||
```text
|
|
||||||
BindFrame k:
|
|
||||||
When the current action produces value x, continue with apply k x.
|
|
||||||
|
|
||||||
LocalFrame oldEnv:
|
|
||||||
When the current action produces value x, restore oldEnv, then continue with x.
|
|
||||||
```
|
|
||||||
|
|
||||||
Add the machine state:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Machine = Machine
|
|
||||||
{ machineRuntime :: Runtime
|
|
||||||
, machineCurrent :: T
|
|
||||||
, machineFrames :: [Frame]
|
|
||||||
}
|
|
||||||
deriving (Show)
|
|
||||||
```
|
|
||||||
|
|
||||||
Frames should be treated as a stack, with the head as the top:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
push frame machine = machine { machineFrames = frame : machineFrames machine }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## New step result
|
|
||||||
|
|
||||||
Replace the current `Step` with machine-oriented stepping:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
data Step
|
|
||||||
= Halt Runtime T
|
|
||||||
| Continue Machine
|
|
||||||
deriving (Show)
|
|
||||||
```
|
|
||||||
|
|
||||||
`Halt runtime value` means the entire IO program is done.
|
|
||||||
|
|
||||||
`Continue machine` means the machine can take another step.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Core stepping semantics
|
|
||||||
|
|
||||||
The central function should become:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
stepMachine :: Machine -> IO Step
|
|
||||||
```
|
|
||||||
|
|
||||||
It should decode `machineCurrent`.
|
|
||||||
|
|
||||||
### `pure`
|
|
||||||
|
|
||||||
When the current action is `APure value`, do not immediately halt. First inspect the frame stack.
|
|
||||||
|
|
||||||
Pseudo-code:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
finishValue :: Machine -> T -> IO Step
|
|
||||||
finishValue machine value =
|
|
||||||
case machineFrames machine of
|
|
||||||
[] ->
|
|
||||||
pure (Halt (machineRuntime machine) value)
|
|
||||||
|
|
||||||
BindFrame k : rest ->
|
|
||||||
pure (Continue machine
|
|
||||||
{ machineCurrent = apply k value
|
|
||||||
, machineFrames = rest
|
|
||||||
})
|
|
||||||
|
|
||||||
LocalFrame oldEnv : rest ->
|
|
||||||
let runtime' = (machineRuntime machine) { rtEnv = oldEnv }
|
|
||||||
in pure (Continue machine
|
|
||||||
{ machineRuntime = runtime'
|
|
||||||
, machineCurrent = pureAction value
|
|
||||||
, machineFrames = rest
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
You will need a helper:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
pureAction :: T -> T
|
|
||||||
pureAction x = Fork (ofNumber 0) x
|
|
||||||
```
|
|
||||||
|
|
||||||
This is important: restoring a `LocalFrame` should not discard the value. It restores the environment and re-enters the machine as `pure value`, allowing the next frame to receive the value.
|
|
||||||
|
|
||||||
### `bind`
|
|
||||||
|
|
||||||
For:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
ABind left k
|
|
||||||
```
|
|
||||||
|
|
||||||
do not recursively step `left`, and do not rebuild `Bind left' k`.
|
|
||||||
|
|
||||||
Instead:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
Continue machine
|
|
||||||
{ machineCurrent = left
|
|
||||||
, machineFrames = BindFrame k : machineFrames machine
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is the major refactor. Continuations move out of the tree and into the frame stack.
|
|
||||||
|
|
||||||
### `ask`
|
|
||||||
|
|
||||||
For:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
AAsk
|
|
||||||
```
|
|
||||||
|
|
||||||
produce the current Reader environment:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
finishValue machine (rtEnv (machineRuntime machine))
|
|
||||||
```
|
|
||||||
|
|
||||||
or equivalently:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
Continue machine { machineCurrent = pureAction currentEnv }
|
|
||||||
```
|
|
||||||
|
|
||||||
Prefer `finishValue` because it avoids an extra step.
|
|
||||||
|
|
||||||
### `local`
|
|
||||||
|
|
||||||
For:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
ALocal f action
|
|
||||||
```
|
|
||||||
|
|
||||||
do:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
let runtime = machineRuntime machine
|
|
||||||
oldEnv = rtEnv runtime
|
|
||||||
newEnv = apply f oldEnv
|
|
||||||
runtime' = runtime { rtEnv = newEnv }
|
|
||||||
|
|
||||||
Continue machine
|
|
||||||
{ machineRuntime = runtime'
|
|
||||||
, machineCurrent = action
|
|
||||||
, machineFrames = LocalFrame oldEnv : machineFrames machine
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is the central correctness point.
|
|
||||||
|
|
||||||
`local` enters a scoped environment by pushing a restoration frame. When the scoped action finishes, `LocalFrame oldEnv` restores the previous environment and passes the produced value onward.
|
|
||||||
|
|
||||||
Nested `local` works naturally because frames stack:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
local f (
|
|
||||||
local g ask
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
becomes:
|
|
||||||
|
|
||||||
```text
|
|
||||||
push LocalFrame env0
|
|
||||||
set env = f env0
|
|
||||||
|
|
||||||
push LocalFrame env1
|
|
||||||
set env = g env1
|
|
||||||
|
|
||||||
ask returns env2
|
|
||||||
|
|
||||||
pop LocalFrame env1
|
|
||||||
restore env1
|
|
||||||
|
|
||||||
pop LocalFrame env0
|
|
||||||
restore env0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Normal IO actions
|
|
||||||
|
|
||||||
For host IO actions, perform the side effect and then call `finishValue`.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
APutStr str ->
|
|
||||||
case decodeString str "PutStr" of
|
|
||||||
Right s -> do
|
|
||||||
putStr s
|
|
||||||
finishValue machine Leaf
|
|
||||||
Left _ ->
|
|
||||||
finishValue machine (errResult 6)
|
|
||||||
```
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
AReadFile path ->
|
|
||||||
case decodeString path "ReadFile" of
|
|
||||||
Right p -> do
|
|
||||||
result <- ...
|
|
||||||
finishValue machine result
|
|
||||||
Left _ ->
|
|
||||||
finishValue machine (errResult 6)
|
|
||||||
```
|
|
||||||
|
|
||||||
Important: IO actions should no longer return `Done value` directly. They should return a value to the frame stack via `finishValue`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decode changes
|
|
||||||
|
|
||||||
Extend `decodeAction`:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
decodeAction :: T -> Either String Action
|
|
||||||
decodeAction tree =
|
|
||||||
case tree of
|
|
||||||
Fork tag payload ->
|
|
||||||
case toNumber tag of
|
|
||||||
Right 0 -> Right (APure payload)
|
|
||||||
|
|
||||||
Right 1 -> case payload of
|
|
||||||
Fork left k -> Right (ABind left k)
|
|
||||||
_ -> Left "Invalid Bind: expected pair action continuation"
|
|
||||||
|
|
||||||
Right 2 -> Right (APutStr payload)
|
|
||||||
|
|
||||||
Right 3 -> Right AGetLine
|
|
||||||
|
|
||||||
Right 4 -> Right (AReadFile payload)
|
|
||||||
|
|
||||||
Right 5 -> case payload of
|
|
||||||
Fork path contents -> Right (AWriteFile path contents)
|
|
||||||
_ -> Left "Invalid WriteFile: expected pair path contents"
|
|
||||||
|
|
||||||
Right 6 -> Right AAsk
|
|
||||||
|
|
||||||
Right 7 -> case payload of
|
|
||||||
Fork f action -> Right (ALocal f action)
|
|
||||||
_ -> Left "Invalid Local: expected pair function action"
|
|
||||||
|
|
||||||
Right n -> Left $ "Unknown IO action tag: " ++ show n
|
|
||||||
|
|
||||||
Left err -> Left $ "Invalid action tag: " ++ err
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
Left $ "Invalid action tree: expected pair tag payload, got " ++ show tree
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Runner API
|
|
||||||
|
|
||||||
Add a new Reader-aware runner:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
runIOWithEnv :: IOPermissions -> T -> T -> IO T
|
|
||||||
runIOWithEnv perms env action = loop initialMachine
|
|
||||||
where
|
|
||||||
initialMachine = Machine
|
|
||||||
{ machineRuntime = Runtime
|
|
||||||
{ rtPerms = perms
|
|
||||||
, rtEnv = env
|
|
||||||
}
|
|
||||||
, machineCurrent = action
|
|
||||||
, machineFrames = []
|
|
||||||
}
|
|
||||||
|
|
||||||
loop machine = do
|
|
||||||
step <- stepMachine machine
|
|
||||||
case step of
|
|
||||||
Halt _ value -> pure value
|
|
||||||
Continue machine' -> loop machine'
|
|
||||||
```
|
|
||||||
|
|
||||||
Keep the existing API as a compatibility wrapper:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
runIO :: IOPermissions -> T -> IO T
|
|
||||||
runIO perms action =
|
|
||||||
runIOWithEnv perms Leaf action
|
|
||||||
```
|
|
||||||
|
|
||||||
If State is added in the same branch, prefer:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
runIOWith :: IOPermissions -> T -> T -> T -> IO (T, T)
|
|
||||||
```
|
|
||||||
|
|
||||||
where:
|
|
||||||
|
|
||||||
```text
|
|
||||||
permissions
|
|
||||||
initial reader env
|
|
||||||
initial state
|
|
||||||
action
|
|
||||||
```
|
|
||||||
|
|
||||||
returns:
|
|
||||||
|
|
||||||
```text
|
|
||||||
final result
|
|
||||||
final state
|
|
||||||
```
|
|
||||||
|
|
||||||
But if this handoff is only for Reader, use `runIOWithEnv`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Permission helpers
|
|
||||||
|
|
||||||
The current permission helper functions can mostly stay as-is, but they should read permissions from runtime:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
let perms = rtPerms (machineRuntime machine)
|
|
||||||
```
|
|
||||||
|
|
||||||
The current helpers are nested inside `stepIO`. After the refactor, either:
|
|
||||||
|
|
||||||
1. keep them in a `where` block under `stepMachine`, or
|
|
||||||
2. lift them to top-level helper functions.
|
|
||||||
|
|
||||||
Prefer lifting pure/reusable helpers to top-level if this file is getting large:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
decodeString :: T -> String -> Either String String
|
|
||||||
canonicalizeSafe :: FilePath -> IO (Either String FilePath)
|
|
||||||
pathAllowed :: FilePath -> [FilePath] -> IO Bool
|
|
||||||
tryReadFile :: FilePath -> IO T
|
|
||||||
tryWriteFile :: FilePath -> String -> IO T
|
|
||||||
okResult :: T -> T
|
|
||||||
errResult :: Integer -> T
|
|
||||||
ioErrorCode :: IOException -> Integer
|
|
||||||
```
|
|
||||||
|
|
||||||
This will make `stepMachine` much easier to read.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## `io.tri` changes
|
|
||||||
|
|
||||||
Add the Reader constructors:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
ask = _ : pair 6 t
|
|
||||||
local = f action : pair 7 (pair f action)
|
|
||||||
```
|
|
||||||
|
|
||||||
No new bind is required.
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
program =
|
|
||||||
bind ask (env :
|
|
||||||
putStrLn env)
|
|
||||||
```
|
|
||||||
|
|
||||||
Example `local` usage:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
program =
|
|
||||||
bind ask (outer :
|
|
||||||
bind (local (env : append env "-inner")
|
|
||||||
(bind ask (inner :
|
|
||||||
pure inner)))
|
|
||||||
(result :
|
|
||||||
bind ask (after :
|
|
||||||
pure result)))
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected behavior:
|
|
||||||
|
|
||||||
```text
|
|
||||||
outer ask sees original env
|
|
||||||
inner ask sees transformed env
|
|
||||||
after ask sees original env again
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tests to add
|
|
||||||
|
|
||||||
Add tests around behavior, not implementation details.
|
|
||||||
|
|
||||||
### 1. `ask` returns initial environment
|
|
||||||
|
|
||||||
Program:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
io (bind ask (env : pure env))
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with env:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"dev"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"dev"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. `local` transforms environment
|
|
||||||
|
|
||||||
Program:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
io (
|
|
||||||
local (env : append env "-local")
|
|
||||||
(bind ask (env : pure env))
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Initial env:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"root"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected result:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"root-local"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. `local` restores environment afterward
|
|
||||||
|
|
||||||
Program structure:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
bind ask (before :
|
|
||||||
bind (local transform scopedAsk) (inside :
|
|
||||||
bind ask (after :
|
|
||||||
pure (pair before (pair inside after)))))
|
|
||||||
```
|
|
||||||
|
|
||||||
Initial env:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"root"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
|
|
||||||
```text
|
|
||||||
pair "root" (pair "root-local" "root")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. nested `local` composes correctly
|
|
||||||
|
|
||||||
Program:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
local f (
|
|
||||||
local g ask
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Initial env:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"root"
|
|
||||||
```
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
f = x : append x "-f"
|
|
||||||
g = x : append x "-g"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected inner ask:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"root-f-g"
|
|
||||||
```
|
|
||||||
|
|
||||||
Also verify after both locals, environment is restored by doing a final `ask`.
|
|
||||||
|
|
||||||
### 5. `local` result passes through bind correctly
|
|
||||||
|
|
||||||
Program:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
bind
|
|
||||||
(local transform (pure "value"))
|
|
||||||
(x : pure x)
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"value"
|
|
||||||
```
|
|
||||||
|
|
||||||
This catches a common bug where `LocalFrame` restores env but loses the value.
|
|
||||||
|
|
||||||
### 6. IO still works through bind
|
|
||||||
|
|
||||||
Existing IO tests should continue passing unchanged through `runIO`.
|
|
||||||
|
|
||||||
### 7. IO inside local
|
|
||||||
|
|
||||||
Program:
|
|
||||||
|
|
||||||
```tricu
|
|
||||||
local transform (
|
|
||||||
bind ask (env :
|
|
||||||
bind (putStrLn env) (_ :
|
|
||||||
pure env))
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected:
|
|
||||||
|
|
||||||
```text
|
|
||||||
prints transformed env
|
|
||||||
returns transformed env
|
|
||||||
```
|
|
||||||
|
|
||||||
Then optionally ask after local to verify restoration.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Invariants to preserve
|
|
||||||
|
|
||||||
The implementation should maintain these invariants:
|
|
||||||
|
|
||||||
```text
|
|
||||||
1. The current action is always the next instruction to evaluate.
|
|
||||||
|
|
||||||
2. The frame stack contains all pending continuations and cleanup scopes.
|
|
||||||
|
|
||||||
3. Bind does not recursively step its left side.
|
|
||||||
It pushes BindFrame and switches current to the left action.
|
|
||||||
|
|
||||||
4. local does not run its action to completion.
|
|
||||||
It pushes LocalFrame and switches current to the scoped action.
|
|
||||||
|
|
||||||
5. Only LocalFrame restores Reader environment.
|
|
||||||
|
|
||||||
6. State, when added later, should not be restored by LocalFrame.
|
|
||||||
|
|
||||||
7. Existing runIO behavior remains source-compatible.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common failure modes
|
|
||||||
|
|
||||||
The likely bugs are:
|
|
||||||
|
|
||||||
```text
|
|
||||||
Bug: local leaks environment.
|
|
||||||
Cause: setting rtEnv but never restoring it.
|
|
||||||
Fix: push LocalFrame oldEnv and restore in finishValue.
|
|
||||||
|
|
||||||
Bug: local restores environment but loses result.
|
|
||||||
Cause: popping LocalFrame and halting directly.
|
|
||||||
Fix: after restoration, continue with pureAction value.
|
|
||||||
|
|
||||||
Bug: bind continuations run under the wrong env.
|
|
||||||
Cause: LocalFrame and BindFrame pop order is wrong.
|
|
||||||
Fix: use stack head as top. Push LocalFrame when entering local; push BindFrame when entering bind. Pop exactly one frame when a value is produced.
|
|
||||||
|
|
||||||
Bug: existing IO bind tests fail.
|
|
||||||
Cause: IO actions halt instead of passing result to finishValue.
|
|
||||||
Fix: every completed primitive action should call finishValue.
|
|
||||||
|
|
||||||
Bug: nested binds still rebuild trees.
|
|
||||||
Cause: old ABind logic left in place.
|
|
||||||
Fix: ABind should only push BindFrame and switch current to left.
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Async relevance, but not implementation
|
|
||||||
|
|
||||||
This machine representation is intentionally compatible with async.
|
|
||||||
|
|
||||||
A future scheduler can store:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
Machine runtime current frames
|
|
||||||
```
|
|
||||||
|
|
||||||
when a task blocks, then resume it later by restoring the same `Machine`.
|
|
||||||
|
|
||||||
Do not implement any of these now:
|
|
||||||
|
|
||||||
```haskell
|
|
||||||
AFork
|
|
||||||
AAwait
|
|
||||||
ASleep
|
|
||||||
TaskId
|
|
||||||
Scheduler
|
|
||||||
Runnable queue
|
|
||||||
Blocked table
|
|
||||||
```
|
|
||||||
|
|
||||||
But avoid designs that would make future suspension impossible, especially recursive “run sub-computation to completion” implementations of `local`. The point of the frame machine is that every effect remains small-step and resumable.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommended implementation order
|
|
||||||
|
|
||||||
1. Add `Runtime`, `Frame`, and `Machine`.
|
|
||||||
2. Add `pureAction`.
|
|
||||||
3. Replace `Step` with `Halt Runtime T | Continue Machine`.
|
|
||||||
4. Implement `finishValue`.
|
|
||||||
5. Rewrite `ABind` to push `BindFrame`.
|
|
||||||
6. Rewrite existing primitive IO actions to call `finishValue`.
|
|
||||||
7. Add `AAsk` and `ALocal`.
|
|
||||||
8. Add `runIOWithEnv`.
|
|
||||||
9. Rewrite `runIO` as a wrapper.
|
|
||||||
10. Add `ask` and `local` to `io.tri`.
|
|
||||||
11. Add Reader behavior tests.
|
|
||||||
12. Run all existing IO tests and confirm no regressions.
|
|
||||||
|
|
||||||
The key handoff instruction is: implement `local` as a continuation frame, not as a recursive nested run. This keeps the interpreter genuinely small-step and gives the eventual async runtime the exact representation it will need for suspension and resumption.
|
|
||||||
262
notes/stdlib-todo.md
Normal file
262
notes/stdlib-todo.md
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
# Standard Library TODO / Expansion Plan
|
||||||
|
|
||||||
|
> Foundational expansions that improve safety, composability, and ergonomics across the entire tricu ecosystem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Extract a `maybe.tri` / Option layer
|
||||||
|
|
||||||
|
**Motivation:** `head`, `tail`, `last`, `nth`, `bytesHead`, and `bytesTail` currently return `t` on failure, which is ambiguous because `t` is also a valid tree value. A proper option layer makes all of these APIs safer and self-describing.
|
||||||
|
|
||||||
|
**Additions:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
nothing = t
|
||||||
|
just = x : t x
|
||||||
|
|
||||||
|
matchMaybe = nothingCase justCase maybe :
|
||||||
|
triage
|
||||||
|
nothingCase
|
||||||
|
justCase
|
||||||
|
(_ _ : nothingCase)
|
||||||
|
maybe
|
||||||
|
|
||||||
|
maybe = default f m : matchMaybe default f m
|
||||||
|
maybeMap = f m : matchMaybe nothing (x : just (f x)) m
|
||||||
|
maybeBind = m f : matchMaybe nothing f m
|
||||||
|
maybeOr = default m : matchMaybe default id m
|
||||||
|
maybe? = matchMaybe false (_ : true)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Then update existing list/bytes primitives:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
headMaybe
|
||||||
|
lastMaybe
|
||||||
|
nthMaybe
|
||||||
|
bytesHead
|
||||||
|
bytesTail
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Move `Result` combinators from `binary.tri` into `base.tri`
|
||||||
|
|
||||||
|
**Motivation:** `Result` is defined in `base.tri`, but `mapResult` and `bindResult` currently live in `binary.tri`. This makes them unavailable to IO, socket, and file helpers unless every consumer imports binary parsing.
|
||||||
|
|
||||||
|
**Move / add to `base.tri`:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
mapResult = f result :
|
||||||
|
matchResult
|
||||||
|
(code rest : err code rest)
|
||||||
|
(value rest : ok (f value) rest)
|
||||||
|
result
|
||||||
|
|
||||||
|
bindResult = result f :
|
||||||
|
matchResult
|
||||||
|
(code rest : err code rest)
|
||||||
|
(value rest : f value rest)
|
||||||
|
result
|
||||||
|
|
||||||
|
resultOr = default result :
|
||||||
|
matchResult
|
||||||
|
(_ _ : default)
|
||||||
|
(value _ : value)
|
||||||
|
result
|
||||||
|
|
||||||
|
resultMapErr = f result :
|
||||||
|
matchResult
|
||||||
|
(code rest : err (f code) rest)
|
||||||
|
(value rest : ok value rest)
|
||||||
|
result
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Add basic numeric comparison and arithmetic
|
||||||
|
|
||||||
|
**Motivation:** Without comparison and arithmetic, list slicing, parser limits, counters, socket loops, and CLI utilities are awkward or impossible.
|
||||||
|
|
||||||
|
**Priority order:**
|
||||||
|
|
||||||
|
1. `isZero?`
|
||||||
|
2. `add`
|
||||||
|
3. `sub`
|
||||||
|
4. `lt?`
|
||||||
|
5. `lte?`
|
||||||
|
6. `mul`
|
||||||
|
|
||||||
|
Even simple Peano / binary-tree versions unlock a huge amount of functionality.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Expand `list.tri` with safer and more complete primitives
|
||||||
|
|
||||||
|
**Motivation:** `reverse` currently uses `append` recursively, which is quadratic. Many common operations (`take`, `drop`, `concatMap`) are missing entirely.
|
||||||
|
|
||||||
|
**High-impact additions:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
take
|
||||||
|
drop
|
||||||
|
splitAt
|
||||||
|
concatMap
|
||||||
|
find
|
||||||
|
partition
|
||||||
|
zipWith
|
||||||
|
```
|
||||||
|
|
||||||
|
**Performance fix:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
reverse_ = y (self xs acc :
|
||||||
|
matchList
|
||||||
|
acc
|
||||||
|
(h r : self r (pair h acc))
|
||||||
|
xs)
|
||||||
|
|
||||||
|
reverse = xs : reverse_ xs t
|
||||||
|
```
|
||||||
|
|
||||||
|
This is low-effort, high-impact because `reverse` is already used by `binary.tri` and `conversions.tri`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Add string aliases / helpers
|
||||||
|
|
||||||
|
**Motivation:** IO and CLI programs almost always need line/string manipulation. Socket protocols are also easier with canonical string utilities.
|
||||||
|
|
||||||
|
**Highest value:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
startsWith?
|
||||||
|
contains?
|
||||||
|
lines
|
||||||
|
unlines
|
||||||
|
```
|
||||||
|
|
||||||
|
Also consider:
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
strLength
|
||||||
|
strAppend
|
||||||
|
strEq?
|
||||||
|
strEmpty?
|
||||||
|
words
|
||||||
|
unwords
|
||||||
|
endsWith?
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Expand `binary.tri` into a small parser-combinator layer
|
||||||
|
|
||||||
|
**Motivation:** `binary.tri` already has `readU8`, `readBytes`, `expectBytes`, etc. A thin combinator layer makes binary parsing much more ergonomic and reusable for protocols/file formats.
|
||||||
|
|
||||||
|
**Suggested additions:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
pureParser = value bs : ok value bs
|
||||||
|
failParser = code bs : err code bs
|
||||||
|
|
||||||
|
mapParser = mapResult
|
||||||
|
bindParser = bindResult
|
||||||
|
thenParser = p q : bindResult p (_ : q)
|
||||||
|
orParser = p q bs :
|
||||||
|
matchResult
|
||||||
|
(_ _ : q bs)
|
||||||
|
(value rest : ok value rest)
|
||||||
|
(p bs)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then common parsers:
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
readWhile
|
||||||
|
readUntil
|
||||||
|
readRemaining
|
||||||
|
peekU8
|
||||||
|
eof?
|
||||||
|
expectAscii
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Add endian / int conversion helpers
|
||||||
|
|
||||||
|
**Motivation:** `readU16BEBytes` and `readU32BEBytes` return raw byte lists. Either rename them or add actual numeric decoding.
|
||||||
|
|
||||||
|
**Suggested additions:**
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
u16BE
|
||||||
|
u16LE
|
||||||
|
u32BE
|
||||||
|
u32LE
|
||||||
|
|
||||||
|
readU16BE
|
||||||
|
readU16LE
|
||||||
|
readU32BE
|
||||||
|
readU32LE
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Add resource-safe IO helpers
|
||||||
|
|
||||||
|
**Motivation:** Sockets, files, and process-like resources need predictable cleanup.
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
finally = action cleanup :
|
||||||
|
bind action (result :
|
||||||
|
bind cleanup (_ :
|
||||||
|
pure result))
|
||||||
|
|
||||||
|
bracket = acquire release use :
|
||||||
|
bind acquire (resource :
|
||||||
|
bind (use resource) (result :
|
||||||
|
bind (release resource) (_ :
|
||||||
|
pure result)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Add socket server loops
|
||||||
|
|
||||||
|
**Motivation:** Almost every socket example repeats the same `forever` + `withAccepted_` scaffolding.
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
serveForever = server handler :
|
||||||
|
forever
|
||||||
|
(withAccepted_ server
|
||||||
|
(err : pure t)
|
||||||
|
(client peer :
|
||||||
|
fork (handler client peer)))
|
||||||
|
```
|
||||||
|
|
||||||
|
Also consider:
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
connectTo = addr port :
|
||||||
|
onOk_ socket (client :
|
||||||
|
onOk_ (connect client addr port) (_ :
|
||||||
|
pure (ok client)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Add a curated `prelude.tri`
|
||||||
|
|
||||||
|
**Motivation:** As libraries grow, users need a stable starting point.
|
||||||
|
|
||||||
|
```tricu
|
||||||
|
!import "base.tri" !Local
|
||||||
|
!import "maybe.tri" !Local
|
||||||
|
!import "list.tri" !Local
|
||||||
|
!import "bytes.tri" !Local
|
||||||
|
!import "conversions.tri" !Local
|
||||||
|
```
|
||||||
|
|
||||||
|
This gives a standard baseline without importing IO / socket / binary by default.
|
||||||
|
|
||||||
1052
test/Spec.hs
1052
test/Spec.hs
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user