# Tiny UNIX revision 2. typename Option(a) = [|None|Some:a|]; sig fail : () {Fail:Zero |_}-> a fun fail() { switch (do Fail) {} } sig optionalise : (Comp(a, {Fail:Zero |e})) {Fail{_} |e}~> Option(a) fun optionalise(m) { handle(m()) { case Return(x) -> Some(x) case Fail(_) -> None } } sig withDefault : (a, Comp(a, {Fail:Zero |e})) {Fail{_} |e}~> a fun withDefault(x', m) { handle(m()) { case Return(x) -> x case Fail(_) -> x' } } sig lookup : (a, [(a, b)]) {Fail:Zero |_}~> b fun lookup(k, kvs) { switch (kvs) { case [] -> fail() case (k', v) :: kvs' -> if (k == k') v else lookup(k, kvs') } } sig modify : (a, b, [(a, b)]) ~> [(a, b)] fun modify(k, v, kvs) { switch (kvs) { case [] -> [] case (k', v') :: kvs' -> if (k == k') (k, v) :: kvs' else (k', v') :: modify(k, v, kvs') } } sig remove : (a, [(a, b)]) ~> [(a, b)] fun remove(k, kvs) { switch (kvs) { case [] -> [] case (k', v') :: kvs' -> if (k == k') kvs' else (k', v') :: remove(k, kvs') } } sig has : (a, [(a, b)]) ~> Bool fun has(k, kvs) { switch (kvs) { case [] -> false case (k', _) :: kvs' -> k == k' || has(k, kvs') } } ## ## Basic i/o ## typename File = String; typename FileDescr = Int; sig stdout : FileDescr var stdout = 1; sig basicIO : (Comp(a, {Write:(FileDescr,String) -> () |e})) {Write{_} |e}~> (a, File) fun basicIO(m) { handle(m()) { case Return(res) -> (res, "") case Write(_, s, resume) -> var (res, file) = resume(()); (res, s ^^ file) } } sig echo : (String) {Write:(FileDescr,String) -> () |_}~> () fun echo(cs) { do Write(stdout,cs) } fun example0() { basicIO(fun() { echo("Hello"); echo("World") }) } ## ## Exceptions: non-local exits ## sig exit : (Int) {Exit:(Int) -> Zero |_}-> a fun exit(n) { switch(do Exit(n)) {} } sig status : (Comp(a, {Exit:(Int) -> Zero |e})) {Exit{_} |e}~> Int fun status(m) { handle(m()) { case Return(_) -> 0 case Exit(n, _) -> n } } fun example1() { basicIO(fun() { status(fun() { echo("dead"); exit(1); echo("code") }) }) } ## ## Dynamic binding: user-specific environments. ## typename User = [|Alice|Bob|Root|]; sig whoami : () {Ask:String |_}-> String fun whoami() { do Ask } sig env : (User, Comp(a, {Ask:String |e})) {Ask{_} |e}~> a fun env(user, m) { handle(m()) { case Return(x) -> x case Ask(resume) -> switch (user) { case Alice -> resume("alice") case Bob -> resume("bob") case Root -> resume("root") } } } fun example2() { env(Root, whoami) } ### ### Session management. ### sig su : (User) {Su:(User) -> () |_}-> () fun su(user) { do Su(user) } sig sessionmgr : (User, Comp(a, {Ask:String,Su:(User) -> () |e})) {Ask{_},Su{_} |e}~> a fun sessionmgr(user, m) { env(user, fun() { handle(m()) { case Return(x) -> x case Su(user', resume) -> env(user', fun() { resume(()) }) } }) } fun example3() { basicIO(fun() { sessionmgr(Root, fun() { status(fun() { su(Alice); echo(whoami()); echo(" "); su(Bob); echo(whoami()); echo(" "); su(Root); echo(whoami()) }) }) }) } ## ## Nondeterminism: time sharing. ## sig fork : () {Fork:Bool |_}-> Bool fun fork() { do Fork } sig nondet : (Comp(a, {Fork:Bool |e})) {Fork{_} |e}~> [a] fun nondet(m) { handle(m()) { case Return(res) -> [res] case Fork(resume) -> resume(true) ++ resume(false) } } sig interrupt : () {Interrupt:() |_}-> () fun interrupt() { do Interrupt } typename Pstate(a,e::Eff) = forall q::Presence. [|Done:a |Paused:() {Interrupt{q} |e}~> Pstate(a, { |e})|]; sig slice : (Comp(a, {Interrupt:() |e})) {Interrupt{_} |e}~> Pstate(a, { |e}) fun slice(m) { handle(m()) { case Return(res) -> Done(res) case Interrupt(resume) -> Paused(fun() { resume(()) }) } } sig schedule : ([Pstate(a, { Fork:Bool |e})]) {Fork{_},Interrupt{_} |e}~> [a] fun schedule(ps) { # sig run : (Pstate (a, { Fork:() {}-> Bool|e })) {Fork{_},Interrupt{_} |e}~> [a] # fun run(p) { # switch(p) { # case Done(res) -> [res] # case Paused(resume) -> runNext(nondet(resume)) # } # } # concatMap(run, ps) fun schedule(ps, done) { switch (ps) { case [] -> done case Done(res) :: ps' -> schedule(ps', res :: done) case Paused(resume) :: ps' -> schedule(ps' ++ nondet(resume), done) } } schedule(ps, []) } sig timeshare : (Comp(a, {Fork:Bool,Interrupt:() |e})) {Fork{_},Interrupt{_} |e}~> [a] fun timeshare(m) { var p = Paused(fun() { slice(m) }); schedule([p]) } fun example4() { basicIO(fun() { timeshare(fun() { sessionmgr(Root, fun() { status(fun() { var parent = fork(); (if (parent) su(Alice) else su(Bob)); echo(whoami() ^^ "> Hello "); interrupt(); echo(whoami() ^^ "> Bye "); interrupt(); if (parent) exit(0) else { var parent = fork(); interrupt(); (if (parent) su(Root) else { echo(whoami() ^^ "> oops "); exit(1) }); echo(whoami() ^^ "> !! ") } }) }) }) }) } fun forktest(n) { fun loop(i, n) { if (i >= n) (-1) else { var x = fork(); println("< x = " ^^ (if (x) "true" else "false") ^^ ", i = " ^^ intToString(i)); ignore(loop(i+1,n)); println("> x = " ^^ (if (x) "true" else "false") ^^ ", i = " ^^ intToString(i)); exit(i) } } loop(0, n) } fun test(i) { if (i == 2) () else { println("< i = " ^^ intToString(i)); var x = fork(); test(i+1); println("> i = " ^^ intToString(i)) } } fun ritchie() { echo("UNIX is basically "); echo("a simple operating system, "); echo("but "); echo("you have to be a genius to understand the simplicity.\n") } fun hamlet() { echo("To be, or not to be,\n"); echo("that is the question:\n"); echo("Whether 'tis nobler in the mind to suffer\n") } fun example5() { basicIO(fun() { nondet(fun() { sessionmgr(Root, fun() { status(fun() { if (fork()) { su(Alice); ritchie() } else { su(Bob); hamlet() } }) }) }) }) } fun interruptWrite(m) { handle(m()) { case Return(res) -> res case Write(fd, cs, resume) -> interrupt(); resume(do Write(fd, cs)) } } fun example5'() { basicIO(fun() { timeshare(fun() { interruptWrite(fun() { sessionmgr(Root, fun() { status(fun() { if (fork()) { su(Alice); ritchie() } else { su(Bob); hamlet() } }) }) }) }) }) } ## ## Generic state handling ## sig get : () {Get:s |_}-> s fun get() { do Get } sig put : (s) {Put:(s) -> () |_}-> () fun put(st) { do Put(st) } sig runState : (s, Comp(a, {Get:() -> s,Put:(s) -> () |e})) {Get{_},Put{_} |e}~> (a, s) fun runState(st0, m) { var f = handle(m()) { case Return(x) -> fun(st) { (x, st) } case Get(resume) -> fun(st) { resume(st)(st) } case Put(st,resume) -> fun(_) { resume(())(st) } }; f(st0) } fun example6() { runState(0, fun() { var x = 3; put(x); assert(x == get(), "Put;Get"); var y = get(); var z = get(); assert(y == z, "Get;Get"); put(x+1); put(x+2); assert(get() == x + 2, "Put;Put") }) } ## ## State: file i/o ## typename FilePtr = Option(FileDescr); typename INode = (loc:Int,lno:Int); typename IList = [(Int, INode)]; # INode index -> INode typename Directory = [(String, Int)]; # Filename -> INode index typename DataRegion = [(Int, File)]; # Loc -> File typename FileSystem = (dir:Directory,dregion:DataRegion,inodes:IList ,lnext:Int ,inext:Int ); sig fsys0 : FileSystem var fsys0 = (dir = [("stdout", 0)], inodes = [(0, (loc=0, lno=1))], dregion = [(0, "")], lnext = 1, inext = 1); sig fopen : (String, FileSystem) {Fail:Zero |_}~> (Int, FileSystem) fun fopen(fname, fsys) { (lookup(fname, fsys.dir), fsys) } sig ftruncate : (Int, FileSystem) {Fail:Zero |_}~> FileSystem fun ftruncate(ino, fsys) { var inode = lookup(ino, fsys.inodes); var dregion = modify(inode.loc, "", fsys.dregion); (fsys with =dregion) } sig fcreate : (String, FileSystem) {Fail:Zero |_}~> (Int, FileSystem) fun fcreate(fname, fsys) { if (has(fname, fsys.dir)) { var (ino, fsys) = fopen(fname, fsys); (ino, ftruncate(ino, fsys)) } else { var loc = fsys.lnext; var dregion = (loc, "") :: fsys.dregion; var ino = fsys.inext; var inode = (loc=loc,lno=1); var inodes = (ino, inode) :: fsys.inodes; var dir = (fname, ino) :: fsys.dir; (ino, (=dir, =dregion, =inodes, lnext=loc+1, inext=ino+1)) } } sig ftruncate : (Int, FileSystem) {Fail:Zero |_}~> FileSystem fun ftruncate(ino, fsys) { var inode = lookup(ino, fsys.inodes); var dregion = modify(inode.loc, "", fsys.dregion); (fsys with =dregion) } sig fopen : (String, FileSystem) {Fail:Zero |_}~> Int fun fopen(fname, fsys) { lookup(fname, fsys.dir) } sig fclose : (Int, FileSystem) ~> FileSystem fun fclose(_, fsys) { fsys } sig fwrite : (Int, String, FileSystem) {Fail:Zero |_}~> FileSystem fun fwrite(ino, cs, fsys) { var inode = lookup(ino, fsys.inodes); var file = lookup(inode.loc, fsys.dregion); var file' = file ^^ cs; (fsys with dregion = modify(inode.loc, file', fsys.dregion)) } sig fread : (Int, FileSystem) {Fail:Zero |_}~> String fun fread(ino, fsys) { var inode = lookup(ino, fsys.inodes); lookup(inode.loc, fsys.dregion) } sig flink : (String, String, FileSystem) {Fail:Zero |_}~> FileSystem fun flink(src, dest, fsys) { var ino = lookup(dest, fsys.dir); var inode = lookup(ino, fsys.inodes); var inode' = (inode with lno = inode.lno + 1); var inodes = modify(ino, inode', fsys.inodes); var dir = (src, ino) :: fsys.dir; (fsys with inodes = inodes, dir = dir) } sig funlink : (String, FileSystem) {Fail:Zero |_}~> FileSystem fun funlink(fname, fsys) { var i = lookup(fname, fsys.dir); var dir = remove(fname, fsys.dir); var inode = lookup(i, fsys.inodes); var inode' = (inode with lno = inode.lno - 1); if (inode'.lno > 0) { var inodes = modify(i, inode', fsys.inodes); (fsys with inodes = inodes, dir = dir) } else { var dregion = remove(inode'.loc, fsys.dregion); var inodes = remove(i, fsys.inodes); (fsys with inodes = inodes, dir = dir, dregion = dregion) } } sig create : (String) {Create:(String) -> FileDescr |_}-> FileDescr fun create(fname) { do Create(fname) } sig truncate : (FileDescr) {Truncate:(FileDescr) -> () |_}-> () fun truncate(fd) { do Truncate(fd) } sig open' : (String) {Open:(String) -> Option(FileDescr) |_}-> Option(FileDescr) fun open'(fname) { do Open(fname) } sig close : (FileDescr) {Close:(FileDescr) -> () |_}-> () fun close(fd) { do Close(fd) } sig write : (FileDescr, String) {Write:(FileDescr, String) -> () |_}-> () fun write(fd, cs) { do Write(fd, cs) } sig read : (FileDescr) {Read:(FileDescr) -> Option(String) |_}-> Option(String) fun read(fd) { do Read(fd) } sig link : (String, String) {Link:(String, String) -> () |_}-> () fun link(src, dest) { do Link(src, dest) } sig unlink : (String) {Unlink:(String) -> () |_}-> () fun unlink(fname) { do Unlink(fname) } sig injectState : (() { |e}~> a) -> () {Get:s,Put:(s) -> () |e}~> a fun injectState(f) { (f : (() {Get:s,Put:(s) -> () |e}~> a) <- (() { |e}~> a)) } sig fileIO : (Comp(a, { #Close:(FileDescr) -> () Create:(String) -> FileDescr , Read:(FileDescr) -> Option(String) , Open:(String) -> Option(FileDescr) , Truncate:(FileDescr) -> () , Write:(FileDescr, String) -> () , Fail{p}|e})) {Create{_},Read{_},Open{_},Truncate{_},Write{_},Get:FileSystem,Put:(FileSystem) -> (),Fail{p} |e}~> a fun fileIO(m) { handle(injectState(m)()) { case Return(x) -> x case Create(fname, resume) -> var ino = withDefault(-1, fun() { var (ino, fsys) = fcreate(fname, get()); put(fsys); ino }); resume(ino) case Read(ino, resume) -> var contents = optionalise(fun() { fread(ino, get()) }); resume(contents) case Open(fname, resume) -> var ino = optionalise(fun() { fopen(fname, get()) }); resume(ino) case Truncate(ino, resume) -> withDefault((), fun() { var fsys = ftruncate(ino, get()); put(fsys) }); resume(()) case Write(ino, cs, resume) -> withDefault((), fun() { var fsys = fwrite(ino, cs, get()); put(fsys) }); resume(()) } } fun init(fsys, main) { runState(fsys, fun() { fileIO(fun() { timeshare(fun() { sessionmgr(Root, fun() { status(fun() { if(fork()) exit(0) else main() }) }) }) }) }) } ### ### Stream redirection ### sig >> : (Comp(a, { Create: (String) -> FileDescr , Write:(FileDescr, String) -> () |e}), String) { Create:(String) -> FileDescr , Write:(FileDescr, String) -> () |e}~> a op f >> fname { var fd = create(fname); handle(f()) { case Return(x) -> x case Write(_, cs, resume) -> resume(write(fd, cs)) } } sig >>> : (Comp(a, { Create: (String) -> FileDescr , Open: (String) -> Option (FileDescr) , Write:(FileDescr, String) -> () |e}), String) { Create:(String) -> FileDescr , Open:(String) -> Option (FileDescr) , Write:(FileDescr, String) -> () |e}~> () op f >>> fname { var fd = switch (open'(fname)) { case None -> create(fname) case Some(fd) -> fd }; handle(f()) { case Return(_) -> () case Write(_, cs, resume) -> resume(write(fd, cs)) } } fun example7() { if (fork()) { su(Alice); ritchie >> "ritchie.txt" } else { su(Bob); hamlet >> "hamlet" } } ### ### TCP threeway handshake ### sig strsplit : (Char, String) ~> [String] fun strsplit(c, str) { fun loop(c, str, i, j) { if (i >= strlen(str)) [strsub(str, j, i - j)] else if (charAt(str, i) == c) strsub(str, j, i - j) :: loop(c, str, i+1, i+1) else loop(c,str, i+1, j) } loop(c, str, 0, 0) } sig read1 : (FileDescr) {Read:(FileDescr) -> Option(String) |e}-> Option(String) fun read1(fd) { switch (read(fd)) { case Some("") -> None case x -> x } } sig truncread : (FileDescr) {Read:(FileDescr) -> Option(String),Truncate:(FileDescr) -> () |_}-> Option(String) fun truncread(fd) { var cs = read1(fd); truncate(fd); cs } sig synced : (Comp(Option(a), {Interrupt:() |e})) {Interrupt:() |e}~> a fun synced(f) { switch (f()) { case None -> interrupt(); synced(f) case Some(x) -> x } } sig fail : () {Fail:Zero |_}-> a fun fail() { switch(do Fail) {} } fun tcpclient(seq, inp, out) { write(out, "SYN " ^^ intToString(seq)); var resp = synced(fun() { truncread(inp) }); var [syn, ack] = strsplit(';', resp); var seqn = stringToInt(strsub(syn, 4, strlen(syn) - 4)); var ackn = stringToInt(strsub(ack, 4, strlen(ack) - 4)); if (ackn <> seq + 1) fail() else write(out, "ACK " ^^ intToString(seqn + 1)) } fun tcpserver(seq, inp, out) { var req = synced(fun() { truncread(inp) }); var reqn = stringToInt(strsub(req, 4, strlen(req) - 4)); var resp = "SYN " ^^ intToString(seq) ^^ ";ACK " ^^ intToString(reqn + 1); write(out, resp); var resp' = synced(fun() { truncread(inp) }); var ackn = stringToInt(strsub(resp', 4, strlen(resp') - 4)); if (ackn <> seq + 1) fail() else () } fun performTCP(tcpf, seq, inp, out) { var fd = create(whoami() ^^ ".log"); handle(tcpf(seq, inp, out)) { case Return(_) -> write(fd, "Handshake completed.") case Fail(_) -> write(fd, "Handshake failed.") } } fun tcphandshake() { var (cfd, sfd) = (create("client.sock"), create("server.sock")); if (fork()) { su(Alice); performTCP(tcpclient, 42, cfd, sfd) } else { su(Bob); performTCP(tcpserver, 84, sfd, cfd) } } fun tcphandshakeFail() { var (cfd, sfd) = (create("client.sock"), create("server.sock")); if (fork()) { su(Alice); handle(performTCP(tcpclient, 42, cfd, sfd)) { case Write(fd, cs, resume) -> resume(if (strsub(cs, 0, 3) == "ACK") write(fd, "ACK 0") else write(fd, cs)) } } else { su(Bob); performTCP(tcpserver, 84, sfd, cfd) } } # # Grep # #sig grep : (String) {Await:Char,Yield:(Char) -> () |_}~> () fun grep(str) { var cs = explode(str); fun match(c,cs) { switch (cs) { case c' :: cs' -> if (c == '\n') fail() if (c == c') } } }