diff --git a/.gitignore b/.gitignore index 40542b3e0..7e14cb777 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ bench test/bench .koka scratch +.cache diff --git a/doc/dev-learnings.kk.md b/doc/dev-learnings.kk.md new file mode 100644 index 000000000..8bc5ced55 --- /dev/null +++ b/doc/dev-learnings.kk.md @@ -0,0 +1,17 @@ +# What Tim Learned Designing LibUV + +- Autogenerated bindings are a good idea (they are less work to maintain, more consistent and less error prone) +- However, they lack the flexibility of hand-written bindings (including some marginal efficiency gain in rare cases) +- In particular for synchronous APIs (like the filesystem API when you do not provide a callback), +we can use stack allocated `uv_fs_req` objects. +It would be require more work on the compiler to do the same and make the guarantees we need to not escape the stack. +- Additionally autogenerated bindings typically end up with a large file, which often takes a while to compile. Not sure if that is partially due to the C compiler instead of the Koka compiler. +- Most apis with callbacks require us to define a +callback function in C for the binding anyways. +To do the same from Koka we would need a specialized type signaling to the compiler to emit a c callback wrapper function without a `kk_context_t` parameter for us. (Getting the `kk_context` instead from the thread local variable). I believe these sorts of functions are called trampolines. The trampoline also needs to know how to get data from it's environment (like the `uv_fs_req` object) to call the appropriate Koka closure, and we need to set it beforehand. +- Handle types differ from request types in that they are long lived, and their callbacks can be called multiple times. Currently this is awkward in some cases when managing memory (from Koka). The handwritten bindings have less of a problem here. +- We should really merge something like (https://github.com/koka-lang/koka/pull/494) for strongly typing external objects, and allowing those external objects to be reference counted and managed by Koka. +- When managing stream like objects that also require state or buffering, a named handler works rather well. +- We really could use conditional imports for different platforms. Especially needed if we want to support autogenerated bindings, without having to create stubs in all of the externs for wasm for example. +- Most bindings don't require many complicated types or conversions, and so we can get away with simple extern definitions. Most types can be interacted with solely via abstraction (opaque pointers), and +the interface in (https://github.com/koka-lang/koka/pull/494) is needed for that. More to the point however, we need a really robust and performant bytes and string buffer library. I wouldn't complain about string interpolation as well. diff --git a/kklib/include/kklib.h b/kklib/include/kklib.h index 104213f8c..e87e0d826 100644 --- a/kklib/include/kklib.h +++ b/kklib/include/kklib.h @@ -430,6 +430,7 @@ typedef struct kk_context_s { kk_yield_t yield; // inlined yield structure (for efficiency) int32_t marker_unique; // unique marker generation kk_block_t* delayed_free; // list of blocks that still need to be freed + void* loop; // a reference to an event loop (e.g. uv_loop_t* or NULL) kk_integer_t unique; // thread local unique number generation size_t thread_id; // unique thread id kk_box_any_t kk_box_any; // used when yielding as a value of any type diff --git a/kklib/include/kklib/bytes.h b/kklib/include/kklib/bytes.h index 62d41109e..fe4203617 100644 --- a/kklib/include/kklib/bytes.h +++ b/kklib/include/kklib/bytes.h @@ -92,6 +92,7 @@ static inline kk_bytes_t kk_bytes_dup(kk_bytes_t b, kk_context_t* ctx) { // Adds a terminating zero at the end. Return the raw buffer pointer in `buf` if non-NULL kk_decl_export kk_bytes_t kk_bytes_alloc_len(kk_ssize_t len, kk_ssize_t plen, const uint8_t* p, uint8_t** buf, kk_context_t* ctx); kk_decl_export kk_bytes_t kk_bytes_adjust_length(kk_bytes_t p, kk_ssize_t newlen, kk_context_t* ctx); +kk_decl_export kk_bytes_t kk_bytes_advance(kk_bytes_t p, kk_ssize_t count, kk_context_t* ctx); // allocate uninitialized bytes static inline kk_bytes_t kk_bytes_alloc_buf(kk_ssize_t len, uint8_t** buf, kk_context_t* ctx) { @@ -157,7 +158,15 @@ static inline const char* kk_bytes_cbuf_borrow(const kk_bytes_t b, kk_ssize_t* l return (const char*)kk_bytes_buf_borrow(b, len, ctx); } +static inline int8_t kk_bytes_at(kk_bytes_t p, uint64_t i, kk_context_t* ctx){ + const uint8_t* buf = kk_bytes_buf_borrow(p, NULL, ctx); + return (int8_t)buf[i]; +} +static inline void kk_bytes_set(kk_bytes_t p, uint64_t i, int8_t b, kk_context_t* ctx){ + uint8_t* buf = (uint8_t*)kk_bytes_buf_borrow(p, NULL, ctx); + buf[i] = (uint8_t)b; +} /*-------------------------------------------------------------------------------------------------- Length, compare diff --git a/kklib/src/bytes.c b/kklib/src/bytes.c index 274dbc17a..7b556e831 100644 --- a/kklib/src/bytes.c +++ b/kklib/src/bytes.c @@ -85,6 +85,21 @@ kk_bytes_t kk_bytes_adjust_length(kk_bytes_t b, kk_ssize_t newlen, kk_context_t* } } +kk_bytes_t kk_bytes_advance(kk_bytes_t b, kk_ssize_t count, kk_context_t* ctx) { + kk_ssize_t len; + const uint8_t* s = kk_bytes_buf_borrow(b,&len,ctx); + if (len == count) { + kk_bytes_drop(b, ctx); + return kk_bytes_empty(); + } else { + // copy the rest + kk_ssize_t newlen = len - count; + kk_bytes_t tb = kk_bytes_alloc_dupn(newlen, s + count, ctx); + kk_bytes_drop(b, ctx); + return tb; + } +} + /*-------------------------------------------------------------------------------------------------- Compare diff --git a/kklib/src/init.c b/kklib/src/init.c index 802866daf..2d5738aab 100644 --- a/kklib/src/init.c +++ b/kklib/src/init.c @@ -62,7 +62,7 @@ void kk_free_fun(void* p, kk_block_t* b, kk_context_t* ctx) { kk_string_t kk_get_host(kk_context_t* ctx) { kk_unused(ctx); - kk_define_string_literal(static, host, 5, "libc", ctx); + kk_define_string_literal(static, host, 4, "libc", ctx); return kk_string_dup(host,ctx); } diff --git a/kklib/src/string.c b/kklib/src/string.c index ab5fbf58a..a35c8b11a 100644 --- a/kklib/src/string.c +++ b/kklib/src/string.c @@ -865,6 +865,7 @@ kk_string_t kk_string_trim_right(kk_string_t str, kk_context_t* ctx) { kk_unit_t kk_println(kk_string_t s, kk_context_t* ctx) { // TODO: set locale to utf-8? puts(kk_string_cbuf_borrow(s, NULL, ctx)); // todo: allow printing embedded 0 characters? + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } @@ -872,6 +873,7 @@ kk_unit_t kk_println(kk_string_t s, kk_context_t* ctx) { kk_unit_t kk_print(kk_string_t s, kk_context_t* ctx) { // TODO: set locale to utf-8? fputs(kk_string_cbuf_borrow(s, NULL, ctx), stdout); // todo: allow printing embedded 0 characters? + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } @@ -879,6 +881,7 @@ kk_unit_t kk_print(kk_string_t s, kk_context_t* ctx) { kk_unit_t kk_trace(kk_string_t s, kk_context_t* ctx) { fputs(kk_string_cbuf_borrow(s, NULL, ctx), stderr); // todo: allow printing embedded 0 characters? fputs("\n", stderr); + fflush(stdout); kk_string_drop(s, ctx); return kk_Unit; } diff --git a/lib/std/async.kk b/lib/std/async.kk new file mode 100644 index 000000000..befda53d5 --- /dev/null +++ b/lib/std/async.kk @@ -0,0 +1,663 @@ +/*--------------------------------------------------------------------------- + Copyright 2012-2021, Microsoft Research, Daan Leijen. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +/* Asynchronous primitives + +This module is based closely on [@Leijen:async] and aims to have robust and composable asynchronous +primitives. In particular, any outstanding asynchronous operation can be canceled (through `cancel`) +within a certain scope which allows for composable primitives like `timeout` and `firstof`. + + +## References {- +~ Bibliography { caption:"0"~~ BibItem { #Leijen:async; bibitem-label:"[1]"; searchterm:"Leijen+Daan+Structured+Asynchrony+with+Algebraic+Effects"Daan Leijen. +_Structured Asynchrony with Algebraic Effects_. +Microsoft Research technical report MSR-TR-2017-21, May 2017. +[pdf](https://www.microsoft.com/en-us/research/wp-content/uploads/2017/05/asynceffects-msr-tr-2017-21.pdf) +~~ +~ +\/ +*/ +module std/async + +import std/data/dict +import std/data/array +import std/num/int32 +import std/num/ddouble // for C# backend +import std/time/duration +import std/os/null +import std/core/unsafe +pub import std/num/ddouble +pub import std/num/float64 +import std/time/timestamp + + // js is just using primitives + +// A type alias for asynchronous operations that can raise exceptions non-deterministically. +// This is common for almost all `:async` operations since `cancel` and `timeout` can +// cancel operations non-deterministically which raises the `Cancel` exception and cancels +// outstanding asynchronous requests. +pub alias asyncx = + +pub alias async-exn = + +// ---------------------------------------------------------------------------- +// Promises +// ---------------------------------------------------------------------------- + +// A _promise_ that carries a value of type `:a`. A promise is initially empty +// but can be `await`ed asynchronously until it gets `resolve`d unblocking any +// `await` operations. After that a promise stays resolved and any `await` will +// return immediately. It is an error to try to resolve a promise more than once. +abstract struct promise + state : ref> + + +abstract value type promise-state + Resolved( value : a ) + Awaiting( listeners : list io ()> ) + +// Create a new promise. +pub fun promise() : async promise + async-iox { Promise(ref(Awaiting([]))) } + +// Await a promise; returns immediately if the promise was already resolved and otherwise +// waits asynchronously. +pub fun promise/await( p : promise ) : asyncx a + fun setup(cb : _ -> io-noexn ()) + val r = p.state + match (!r) + Awaiting(listeners) -> r := Awaiting(Cons(cb,listeners)) + Resolved(value) -> io-noexn1(cb,value) // resume right away; should not happen due to try-await + match p.try-await + Just(v) -> v + Nothing -> await1(setup) + +// Returns immediately if the promise was already resolved and otherwise return `Nothing`. +pub fun try-await( p : promise ) : maybe + async-io-noexn + val r = p.state + match !r + Resolved(value) -> Just(value) + _ -> Nothing + +// Resolve a promise to `value`. Raises an exception if the promise was already resolved. +pub fun resolve( p : promise, value : a ) : asyncx () + async-io + val r = p.state + match !r + Awaiting(listeners) -> + r := Resolved(value) + listeners.foreach fn(cbx) // todo: through set-immediate? + cbx(value) // set-immediate1( cbx, value ) + _ -> throw("Promise was already resolved") + +// ---------------------------------------------------------------------------- +// Channels +// ---------------------------------------------------------------------------- + +// A _channel_ of values of type `:a`. Values can be asynchronously `emit`ed into +// a channel, and asynchronously `receive`d. +abstract value struct channel( + chid : int, + state : ref> +) + +// todo: use queue data type for the values and listeners for better complexity +abstract type channel-state + Empty + Values( value : a, values : list = [] ) + Waiting( listener : a -> io-noexn (), listeners : list io-noexn ()> = [] ) + +fun from-values(values : list ) : channel-state + match values + Nil -> Empty + Cons(v,vs) -> Values(v,vs) + +fun from-waiting(listeners : list io-noexn ()>) : channel-state + match listeners + Nil -> Empty + Cons(l,ls) -> Waiting(l,ls) + +// Create a new asynchronous channel. +pub fun channel() : async channel + async-iox + Channel(unique(), ref(Empty)) + +// Receive (and remove) a value from the channel: returns immediately if a value is available and otherwise +// waits asynchronously until a value becomes available. +pub fun receive( ch : channel ) : asyncx a + ch.receivex.untry + +fun receivex( ch : channel, cancelable : bool = True ) : error + fun setup( cb : (_,_) -> io-noexn () ) + fun cbr(x) cb(Ok(x),True) + val r = ch.state + match !r + Empty -> r := Waiting(cbr,[]) + Waiting(l,ls) -> r := Waiting(l,ls ++ [cbr]) + Values(v,vs)-> // this case should not happen due to `try-receive` + r := from-values(vs) + cbr(v) + Nothing + + match ch.try-receive + Just(v) -> Ok(v) + Nothing -> do-await(setup,empty-scope,cancelable) + +// Return immediately if a value is available on the channel and otherwise returns `Nothing`. +pub fun try-receive( ch : channel ) : maybe + async-io-noexn + val r = ch.state + match (!r) + Values(v, vs) -> + r := from-values(vs) + Just(v) + _ -> Nothing + +fun emit-io( ch : channel, value : a ) : io-noexn () + val r = ch.state + match !r + Empty -> r := Values(value, []) + Values(v,vs) -> r := Values(v,vs ++ [value]) + Waiting(l,ls) -> + r := from-waiting(ls) + l(value) + +// Emit a value asynchronously into a channel. +pub fun emit( ch : channel, value : a ) : asyncx () + async-io + emit-io(ch,value) + +fun trace-channel( msg : string, ch : channel ) : () + async-io-noexn + trace-channel-io( msg, ch ) + +fun trace-channel-io( msg : string, ch : channel ) : io-noexn () + val msgx = msg ++ ": id=" ++ ch.chid.show + val r = ch.state + match !r + Empty -> trace(msgx ++ ", empty") + Values(v,vs) -> trace-any(msgx ++ ", full: " ++ (1 + vs.length).show ++ ": ", v ) + Waiting(_,ls) -> trace(msgx ++ ", listeners: " ++ (1 + ls.length).show) + +fun trace-anyx( s : string, x : a ) : async () + trace-any(s,x) + +// ---------------------------------------------------------------------------- +// Asynchronous timeout and waiting +// ---------------------------------------------------------------------------- + +// Execute `action` but if it is not finished within `secs` seconds duration +// `cancel` it (and return `Nothing`). Due to the generality of `cancel`, this `timeout` +// abstraction can reliably time out over any composition of asynchronous operations +// and is therefore quite expressive. + +pub fun timeout( secs : duration, action : () -> a, ?set-timeout: (unit-cb, int32) -> io-noexn any, ?clear-timeout: (any) -> io-noexn () ) : maybe + firstof { wait(secs); Nothing} { Just(action()) } + +// Execute `a` and `b` interleaved. As soon as one of them finishes, +// `cancel` the other one and return the result of the first. +pub fun firstof( a : () -> a, b : () -> a ) : a + cancelable + val (ra,rb) = interleavedx { val x = mask behind{ a() }; cancel(); x } + { val x = mask behind{ b() }; cancel(); x } + match ra + Error(exn) | exn.is-cancel -> rb.untry + _ -> ra.untry + + + +// Wait (asynchronously) for `secs` seconds as a `:double`. +// Use `yield()` to yield to other asynchronous operations. +pub fun float/wait( secs : float64, ?set-timeout: (unit-cb, int32) -> io-noexn any, ?clear-timeout: (any) -> io-noexn () ) : asyncx () + wait(secs.duration) + +// Wait (asynchronously) for optional `secs` seconds `:duration` (`= 0.seconds`). +// Use `yield()` to yield generally to other asynchronous operations. +pub fun wait( secs : duration = zero, ?set-timeout: (unit-cb, int32) -> io-noexn any, ?clear-timeout: (any) -> io-noexn () ) : asyncx () + if secs <= zero then return yield() + val msecs = max(zero:int32,secs.milli-seconds.int32) + await fn(cb) + val tid = async/set-timeout( fn(){ cb(Ok(())) }, msecs ) + Just( { async/clear-timeout(tid) } ) + +// Yield to other asynchronous operations. Same as `wait(0)`. +pub fun yield(?set-timeout: (unit-cb, int32) -> io-noexn any) : asyncx () + await0 fn(cb) + async/set-timeout( cb, int32/zero ) + () + +// abstract wid for timeout handlers +abstract struct timeout-id( + timer : any +) + +alias unit-cb = () -> io-noexn () + +fun async/set-timeout( cb : unit-cb, ms : int32, ?set-timeout: (unit-cb, int32) -> io-noexn any) : io-noexn timeout-id + Timeout-id(?set-timeout(cb,max(ms,zero))) + + +fun async/clear-timeout( tid : timeout-id , ?clear-timeout: (any) -> io-noexn ()) : io-noexn () + ?clear-timeout(tid.timer) + +// ---------------------------------------------------------------------------- +// Interleaved strands of execution +// ---------------------------------------------------------------------------- + +// Interleave two actions around their asynchronous operations. +pub fun two/interleaved( action1 : () -> a, action2 : () -> b ) : (a,b) + val (ra,rb) = interleavedx( {mask behind(action1)}, {mask behind(action2)} ) + [ra.maybe-exn,rb.maybe-exn].ordered_throw + (ra.untry,rb.untry) + +// Interleave a list of actions around their asynchronous operations. +pub fun list/interleaved( xs : list<() -> a> ) : list + val ress = xs.map( fn(f) { return { mask behind(f) } } ).interleavedx + //ress.map(maybe).ordered_throw + ress.map(untry) + +fun maybe-exn( err : error ) : maybe + match err + Error(exn) -> Just(exn) + _ -> Nothing + +fun ordered_throw( xs : list> ) : exn () + var mexn := Nothing + xs.foreach fn(x) + match x + Nothing -> () + Just(exn) -> match mexn + Nothing -> mexn := x + Just(exnx) -> + if ((exn.is-finalize && !exnx.is-finalize) || (exnx.is-cancel && !exn.is-cancel)) + then mexn := x + match mexn + Just(exn) -> rethrow(exn) + Nothing -> () + +// Interleave two actions around their asynchronous operations and explicitly returning either +// their result or their exception. +pub fun interleavedx( action1 : () -> a, action2 : () -> b ) : (error,error) + fun act1() Left(action1()) + fun act2() Right(action2()) + match interleavedx([act1,act2]) + Cons(x,Cons(y)) -> (x.unleft,y.unright) + _ -> + // this will never happen.. + val exn = Exception("invalid interleaved result",ExnInternal("std/async/interleavedx(action1,action2)")) + (Error(exn),Error(exn)) + +fun unleft( x : error> ) : error + match x + Ok(Left(l)) -> Ok(l) + Error(exn) -> Error(exn) + _ -> Error(Exception("invalid left interleaved result",ExnInternal("std/async/interleavedx(action1,action2)"))) +fun unright( x : error> ) : error + match x + Ok(Right(r)) -> Ok(r) + Error(exn) -> Error(exn) + _ -> Error(Exception("invalid right interleaved result",ExnInternal("std/async/interleavedx(action1,action2)"))) + +// Private effect to keep track of when a strand in an interleaving is done. +// Preferred over using built-in state as this works well if there is an outer handler +// over the state that resumes more than once -- redoing part of the interleaving. +// See `test/algeff/async5.js` +effect strands + // Are there still strands that need to be resumed? + fun strands-are-busy() : bool + // Call this when a strand is done. + fun strand-done(idx : int, result : error) : () + +// Insert in order with an accumulating list. +fun insert-acc( xs : list<(int,a)>, idx : int, value : a, acc : list<(int,a)> ) : list<(int,a)> + match xs + Cons(x,xx) | x.fst < idx -> insert-acc(xx, idx, value, Cons(x,acc)) + _ -> reverse-append( acc, Cons((idx,value),xs) ) + +// Insert in order +fun insert( xs : list<(int,a)>, idx : int, value : a, n : int = 0 ) : list<(int,a)> + if n > 100 + then insert-acc( xs, idx, value, [] ) + else match xs + Cons(x,xx) | x.fst < idx -> Cons(x, insert(xx, idx, value, n + 1)) + _ -> Cons((idx,value),xs) + + +// Interleave a list actions around their asynchronous operations and explicitly returning either +// either their result or their exception. +pub fun list/interleavedx( xs : list<() -> a> ) : list> + val n = xs.length + if n==0 then [] + elif n==1 then xs.map(unsafe-try-all) + else interleavedn(n,xs) + +fun interleavedn( n : int, xs : list<() -> a> ) : list> + unsafe-no-ndet-div + var cr : some (int,list<(int,error)>) := (n,[]) + with handler + return(x) + cr.snd.map( snd ) + fun strands-are-busy() + cr.fst > 0 + fun strand-done(idx,res) + cr := (cr.fst - 1, cr.snd.insert(idx,res)) + interleaved-div(xs) + + +inline extern unsafe-no-ndet-div-cast : forall (() -> a) -> (() -> e a) + inline "#1" + +fun unsafe-no-ndet-div( action : () -> a ) : e a + unsafe-no-ndet-div-cast(action)() + +inline extern inject-effects : forall (() -> e a) -> total (() -> ,ndet,div|e> a) + inline "#1" + +fun error/is-finalize( t : error ) : bool + match t + Error(exn) -> exn.is-finalize + _ -> False + +fun error/is-cancel( t : error ) : bool + match t + Error(exn) -> exn.is-cancel + _ -> False + +fun interleaved-div( xs : list<() -> a> ) : |e> () + val strands = xs.map-indexed fn(i,action) + return fn() + val res = unsafe-try-all(inject-effects(action)) + strand-done(i,res) + if res.is-finalize then cancel() // cancel others if finalization happens + val ch : some channel<() -> a> = channel() + val handle-strand = handler + raw ctl do-await(setup,scope,c) + no-await( setup, scope, c) fn(res) + // emit a resumption of this strand into the channel + ch.emit-io( /* no-cps */ { rcontext.resume(res) } ) + () // stop the strand at this point + // redirect all other operations + fun no-await(setup,scope,c,f) + no-await(setup,scope,c,f) + fun async-iox(f) + async-iox(f) + fun cancel(scope) + cancel(scope) + + strands.foreach fn(strand) + handle-strand{ mask behind(strand) } + + while { strands-are-busy() } // while there are resumptions on the strands.. + // the only way receive can throw is through a cancelation -- but in that case + // we should not cancel but instead await all canceled strands; so keep listening on the channel. + match(ch.receivex(False)) + Error(_exn) -> () // ignore cancelation on receive + Ok(strand-resume) -> strand-resume() + () + + + +// ---------------------------------------------------------------------------- +// Await wrappers +// ---------------------------------------------------------------------------- + +// Convenience function for awaiting a NodeJS style callback where the first argument is a possible exception. +pub fun await-exn0( setup : (cb : (null) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : asyncx () + await fn(cb) + setup( fn(nexn) cb(nexn.unnull(())) ) + +// Convenience function for awaiting a NodeJS style callback where the first argument is a possible exception +// and the second argument the possible result value. +pub fun await-exn1( setup : (cb : (null,a) -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : asyncx a + await fn(cb) + setup( fn(nexn,x) cb(nexn.unnull(x)) ) + +fun unnull( nexn : null, x : a ) : error + match nexn.maybe + Nothing -> Ok(x) + Just(exn) -> Error(exn) + +// Convenience function for awaiting a zero argument callback. +pub fun await0( setup : (cb : () -> io-noexn () ) -> io () ) : asyncx () + await fn(cb) + setup( fn() cb(Ok(())) ) + Nothing + +// Convenience function for awaiting a single argument callback. +pub fun await1( setup : (cb : (a) -> io-noexn () ) -> io () ) : asyncx a + await fn(cb) + setup( fn(x) cb(Ok(x)) ) + Nothing + +// Execute `setup` to set up an asynchronous callback with the host platform. Invoke `cb` as the callback: +// it takes either an exception or a result `a`. Usually `setup` returns `Nothing` but you can return a `Just(cleanup)` +// value where the `cleanup` functions is invoked on cancellation to dispose of any resources (see the implementation of `wait`). +// The callback should be invoked exactly once -- when that happens `await` is resumed with the result using `untry` +// either raise an exception or return the plain result. +pub fun setup/await( setup : (cb : error -> io-noexn () ) -> io maybe<() -> io-noexn ()> ) : asyncx a + await-exn(setup).untry + + + + +// ---------------------------------------------------------------------------- +// Async effect +// ---------------------------------------------------------------------------- +alias await-result = error +alias await-setup = (cb : (error,bool) -> io-noexn ()) -> io-noexn (maybe<() -> io-noexn ()>) + +// Asynchronous operations have the `:async` effect. +pub effect async + ctl do-await( setup : await-setup, scope : scope, cancelable : bool ) : error + ctl no-await( setup : await-setup, scope : scope, cancelable : bool, f : error -> io-noexn () ) : () + ctl async-iox( action : () -> io-noexn a ) : a + ctl cancel( scope : scope ) : () + + +// The `cancel` operations cancels any outstanding asynchronous operation under the innermost +// `cancelable` handler by returning the `Cancel` exception. The `cancel` operation itself returns normally +// without raising a `Cancel` exception. +pub fun noscope/cancel() : async () + cancel(empty-scope) + +// Primitive: Execute `setup` to set up an asynchronous callback with the host platform. Invoke `cb` as the callback: +// it takes either an exception or a result `a`. Usually `setup` returns `Nothing` but you can return a `Just(cleanup)` +// value where the `cleanup` functions is invoked on cancellation to dispose of any resources (see the implementation of `wait`). +// The callback should be invoked exactly once -- when that happens `await-exn` is resumed with the result. +pub fun await-exn( setup : (cb : (error) -> io-noexn ()) -> io (maybe<() -> io-noexn ()>) ) : async error + do-await(fn(cb) + match (try{ setup(fn(res) cb(res,True) ) }) + Ok(mcleanup) -> mcleanup + Error(exn) -> + cb(Error(exn),True) + Nothing + , empty-scope, True) + +// Primitive: Execute `setup` to set up an asynchronous callback with the host platform. Invoke `cb` as the callback: it takes either +// an exception or a result value, together with boolean parameter whether the callback is done. +// The callback `cb` will eventually emit the result into the given channel `ch` after applying the transformation `f` to the result.\ +// Note: once you exit the `cancelable` scope where `await-to-channel` was called, the callback is invoked with a `Cancel` exception. +// The channel should always be awaited within the same `cancelable` scope as the `await-to-channel` invokation. +pub fun await-to-channel( setup : (cb : (error,bool) -> io-noexn ()) -> io (maybe<() -> io-noexn ()>), ch : channel, f : error -> b ) : async channel + no-await(fn(cb) + match(try{setup(cb)}) + Ok(mcleanup) -> mcleanup + Error(exn) -> + cb(Error(exn),True) + Nothing + , empty-scope,True, fn(res) + ch.emit-io( f(res) ) + ) + ch + +fun async-io-noexn( f : () -> io-noexn a ) : a + async-iox(f) + +// Perform an I/O operation at the outer level; exceptions are propagated back. +fun async-io( f : () -> io a ) : asyncx a + async-io-noexn( { try(f) } ).untry + + +// ---------------------------------------------------------------------------- +// Async handlers: cancelable +// ---------------------------------------------------------------------------- + +inline extern interject-async( action : () -> a) : total ( () -> a) + inline "#1" + +// Execute `action` in a cancelable scope. If `cancel` is called within `action`, +// any outstanding asynchronous operations started in the cancelable scope are canceled. +// (Outstanding operations outside the cancelable scope are not canceled). +pub fun cancelable( action : () -> a ) : a + val cid = async-iox{ unique() } + fun extend(scope : scope ) + parent-scope(cid,scope) + + handle ({mask behind(action)}) + return(x) -> + // cancel any outstanding operations still in our scope. + // this might be needed for `no-await` operations. + cancel(empty-scope.extend) + x + fun do-await(setup,scope,c) -> do-await(setup,scope.extend,c) + fun no-await(setup,scope,c,f) -> no-await(setup,scope.extend,c,f) + fun cancel(scope) -> cancel(scope.extend) + fun async-iox(f) -> async-iox(f) + +// ---------------------------------------------------------------------------- +// Async handle +// ---------------------------------------------------------------------------- + +pub fun @default-async(action) + async/handle(action) + +fun nodispose() : io-noexn () + () + +// The outer `:async` effect handler. This is automatically applied by the compiler +// around the `main` function if it has an `:async` effect. +pub fun async/handle(action : () -> () ) : io-noexn () + val callbacks : ref io-noexn ())>> = unsafe-total{ref([])} + fun handle-await( setup : await-setup, scope : scope, f : error -> io-noexn (), cancelable : bool) : io-noexn () + val cscope = child-scope(unique(),scope) + val dispose = ref(nodispose) + fun cb( res : error<_>, is-done : bool ) : io-noexn () + if ((!callbacks).contains(cscope)) then + if is-done then + callbacks := (!callbacks).remove(cscope) + if res.is-error then try(!dispose).default(()) + f(res) + + // trace("register: " + cscope.show) + callbacks := Cons((cscope, if cancelable then fn(){ cb(Error(Exception("cancel",Cancel)),True) } else nodispose), !callbacks) + try { + // setup the callback which returns a possible dispose function + match(setup(cb)) + Just(d) -> dispose := d + Nothing -> () + } fn(exn) + // if setup fails, immediately resume with the exception + cb(Error(exn),True) + + fun handle-cancel( scope : scope ) : io-noexn () + (!callbacks).foreach fn(entry) + val (cscope,cb) = entry + if (cscope.in-scope-of(scope)) then cb() + + handle(action) + raw ctl do-await( setup, scope, c ) + handle-await(setup,scope, fn(x) rcontext.resume(x), c) // returns to outer event loop + fun no-await( setup, scope, c, f ) + handle-await(setup,scope,f,c) + fun cancel( scope ) + handle-cancel(scope) + fun async-iox( f ) + f() + +fun io-noexn( f : () -> io-noexn a ) : io a + f() + +fun io-noexn1( f : (a1) -> io-noexn a, x1 : a1 ) : io a + f(x1) + +// ---------------------------------------------------------------------------- +// Scope identifiers +// ---------------------------------------------------------------------------- + +abstract struct scope( : list ) + +val empty-scope = Scope([]) + +fun parent-scope( cid : int, scope : scope ) : scope + match scope + Scope(cids) -> Scope(Cons(cid,cids)) + +fun child-scope( id : int, scope : scope ) : scope + match scope + Scope(cids) -> Scope(cids ++ [id]) + +fun ids/in-scope-of( child : list, parent : list ) : bool + match parent + Nil -> True + Cons(p,ps) -> match child + Cons(c,cs) -> (c == p && in-scope-of(cs,ps)) + Nil -> False + +fun in-scope-of( child : scope, parent : scope ) : bool + match parent + Scope(pids) -> match child + Scope(cids) -> in-scope-of(cids,pids) + +fun scope/(==)(scope1 : scope, scope2 : scope ) : bool + match scope1 + Scope(ids1) -> match scope2 + Scope(ids2) -> ids1==ids2 + +// Convenience functions for scope maps +fun remove( xs : list<(scope,a)>, scope : scope ) : list<(scope,a)> + xs.remove( fn(x:(scope,_)) { x.fst == scope }) + +fun lookup( xs : list<(scope,a)>, scope : scope ) : maybe + xs.lookup( fn(x:scope) { x == scope }) + +fun contains( xs : list<(scope,a)>, scope : scope ) : bool + xs.lookup(scope).bool + +fun show( s : scope ) : string + match s + Scope(ids) -> ids.map(show).join("-") + + + +abstract extend type exception-info + con Cancel + con Finalize(yld:yield-info) + +// Was this a cancelation exception? +fun exn/is-cancel( exn : exception ) : bool + match exn.info + Cancel -> True + _ -> False + +// Was this a finalization exception? +fun exn/is-finalize(exn : exception) : bool + match exn.info + Finalize -> True + _ -> False + +fun unsafe-try-all( action : () -> a ) : e error + val fin = unsafe-try-finalize{ try(action) } + match fin + Right(t) -> t + Left(yld) -> Error(Exception("finalize",Finalize(yld))) + +fun rethrow( exn : exception ) : exn a + match exn.info + Finalize(yld) -> unsafe-reyield(yld) + _ -> throw-exn(exn) diff --git a/lib/std/async/readline-inline.cs b/lib/std/async/readline-inline.cs new file mode 100644 index 000000000..6c59b4903 --- /dev/null +++ b/lib/std/async/readline-inline.cs @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------- + Copyright 2017-2021, Microsoft Research, Daan Leijen. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +static class _Readline +{ + public static Primitive.EventloopEntry Readline( Fun2 cb ) { + return Primitive.RunBlocking( + () => { return Console.In.ReadLine(); }, + (Exception exn,string result) => { cb.Apply(exn,result); } + ); + } + + public static void CancelReadline( object obj ) { + IDisposable d = obj as IDisposable; + if (d != null) d.Dispose(); + } +} diff --git a/lib/std/async/readline-inline.js b/lib/std/async/readline-inline.js new file mode 100644 index 000000000..264c6e90c --- /dev/null +++ b/lib/std/async/readline-inline.js @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------- + Copyright 2012-2021, Microsoft Research, Daan Leijen. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +var _readline = function(action) { + $std_core.error("std/readline/readline is not supported on this platform."); + return (function(){ }); +}; + +var _cancel_readline = function() { + return; +} + +/*------------------------------------------------ + Console for Node +------------------------------------------------*/ +if ($std_core.host() === "node") { + var mod_readline = await import("readline"); + (function(){ + var nodein = null; + + function get_nodein() { + if (!nodein) { + nodein = mod_readline.createInterface( { input: process.stdin /*, output: process.stdout */ }); + nodein.listeners = []; + nodein.on('line', function(s) { + //console.log("line event"); + if (nodein.listeners.length > 0) { + var action = nodein.listeners.shift(); + action(null,s); + } + if (nodein.listeners.length === 0) { + nodein.close(); // no more readlines -> close the app + nodein = null; + } + }); + } + return nodein; + } + + _cancel_readline = function() { + var nodein = get_nodein(); + if (!nodein) return; + if (nodein.listeners.length > 0) { + nodein.listeners.shift(); + } + if (nodein.listeners.length === 0) { + nodein.close(); // no more readlines -> close the app + nodein = null; + } + } + + _readline = function(action) { + var rl = get_nodein(); + if (rl) { + rl.listeners.push(action); + } + } + })(); +} + +/*------------------------------------------------ + Console for Browser +------------------------------------------------*/ +if ($std_core.host() === "browser") { + (function() { + var escapes = { + '&': '&', // & first! + '<': '<', + '>': '>', + '\'': ''', + '"': '"', + '\n': '
', + '\r': '', + }; + var escapes_regex = new RegExp("[" + Object.keys(escapes).join("") + "]", "g"); + + function html_escape(txt) { + return txt.replace(escapes_regex, function (s) { + var r = escapes[s]; + return (r ? r : ""); + }); + } + + var prompt = "> "; + + function caret_to_end(elem) { + if (!elem || !elem.value || !elem.value.length) return; + var pos = elem.value.length; + if (pos===0) return; + + if (elem.createTextRange) { /* ie */ + var rng = elem.createTextRange(); + rng.collapse(true); + rng.moveEnd("character",pos); + rng.moveStart("character",pos); + rng.select(); + } + else if (elem.setSelectionRange) { /* the rest */ + elem.setSelectionRange(pos,pos); + } + } + + function overlap(s,t) { + if (!s || !t) return 0; + var len = s.length; + if (len < t.length) len = t.length; + var i = 0; + while(i < len) { + if (s[i] !== t[i]) return i; + i++; + } + return i; + } + + var input = null; + var console_input_init = false; + function get_console_input() + { + if (input) return input; + + input = document.getElementById("koka-console-in"); + if (!input) { + $std_core.print(""); // ensure there is an output pane + var cons = document.getElementById("koka-console"); + if (!cons) return null; + + input = document.createElement("input"); + input.type = "text"; + input.id = "koka-console-in"; + input.style.fontFamily = "Consolas,Monaco,'Ubuntu Mono','Droid Sans Mono','Source Code Pro',monospace" + input.style.fontSize = "12pt"; + input.style.width = "99%"; + input.style.border = "gray solid 1px"; + input.style.padding = "2px"; + input.style.margin = "2px"; + cons.appendChild(input); + } + + if (!console_input_init) { + console_input_init = true; + var output = document.getElementById("koka-console-out"); + input.value = prompt; + input.listeners = []; + input.onfocus = function() { + caret_to_end(input); /* needed on IE 10 */ + } + input.onkeypress = function(ev) { + ev = ev || window.event; + if(ev.keyCode == 13) { + var content = input.value; + input.value = prompt; + var i = overlap(prompt,content); // remove prompt prefix (if present) + if (i > 0) content = content.substring(i); + if (output != null && typeof output.print_html === "function") { + output.print_html("" + html_escape(prompt + content) + "
") + } + if (input.listeners.length > 0) { + var action = input.listeners.shift(); + action(null,content); + } + } + }; + } + + return input; + } + + _readline = function(action) { + var inp = get_console_input(); + if (inp) { + inp.listeners.push(action); + if (input.focus) input.focus(); + } + } + + _cancel_readline = function() { + var inp = get_console_input(); + if (inp) { + if (inp.listeners.length > 0) { + inp.listeners.shift(); + inp.value = prompt; + } + } + }; + })(); +} diff --git a/lib/std/core/inline/types.js b/lib/std/core/inline/types.js index 323b04062..0ad3ff6d2 100644 --- a/lib/std/core/inline/types.js +++ b/lib/std/core/inline/types.js @@ -259,7 +259,7 @@ export function _int64_clamp_uint32(x) { } export function _int64_imul( x, y ) { - const z = x*y; BigInt.as + const z = x*y; BigInt.asUintN(64,z); const lo = BigInt.asUintN(64,z); const hi = z >> 64n; return $std_core_types.Tuple2(hi,lo); diff --git a/lib/std/core/types.kk b/lib/std/core/types.kk index 8a7df5c36..4766edc8f 100644 --- a/lib/std/core/types.kk +++ b/lib/std/core/types.kk @@ -107,6 +107,9 @@ pub ref type extern-owned // The type of an external (i.e. C) value where the memory is borrowed and managed by external code pub ref type extern-borrowed +// A raw wrapper around a uint8 character array. +pub type bytes + // An any type. Used for external calls. pub type any diff --git a/lib/std/data/bytes.kk b/lib/std/data/bytes.kk new file mode 100644 index 000000000..2aec22c32 --- /dev/null +++ b/lib/std/data/bytes.kk @@ -0,0 +1,45 @@ + +// ---------------------------------------------------------------------------- +// Bytes +// ---------------------------------------------------------------------------- + +pub extern bytes/empty(): bytes + c inline "kk_bytes_empty()" + cs "Primitive.BytesEmpty" + js inline "new Uint8Array(0)" + +pub extern bytes/alloc( n : ssize_t ) : bytes + c inline "kk_bytes_alloc_buf(#1, NULL, kk_context())" + cs "Primitive.BytesAlloc" + js inline "new Uint8Array(#1).fill(#2)" + +pub extern bytes/string( n : bytes ) : string + c "kk_string_convert_from_qutf8" + cs "Primitive.BytesToString" + js inline "String.fromCharCode.apply(null, #1)" + +pub extern string/bytes( s : string ) : bytes + c inline "#1.bytes" + cs "Primitive.BytesFromString" + js inline "new TextEncoder().encode(#1)" + +pub extern bytes/length( ^b : bytes ) : ssize_t + c "kk_bytes_len_borrow" + cs "Primitive.BytesLen" + js inline "#1.length" + +pub extern bytes/adjust-length(b: bytes, len: ssize_t): bytes + c "kk_bytes_adjust_length" + +pub extern bytes/advance(b: bytes, count: ssize_t): bytes + c "kk_bytes_advance" + +pub extern bytes/index( ^b : bytes, ^i : ssize_t ) : int8 + c "kk_bytes_at" + cs "Primitive.BytesAt" + js inline "#1[#2]" + +pub extern bytes/int8/assign( ^b : bytes, ^i : ssize_t, x : int8 ) : () + c "kk_bytes_set" + cs "Primitive.BytesSet" + js inline "#1[#2] = #3" diff --git a/lib/std/os/file-async.kk b/lib/std/os/file-async.kk new file mode 100644 index 000000000..bbb626227 --- /dev/null +++ b/lib/std/os/file-async.kk @@ -0,0 +1,315 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +/// This module provides a set of functions for asynchronous file I/O. +module std/os/file-async + +pub import std/async +pub import uv/file +import std/num/int64 +import std/os/path + +pub fun read-as-string(path: path) + val length = stat(path).size + val fd = open(path, o_RDONLY, 0.int32) + val res = await fn(cb) + read(fd, length.int.int32, 0.int32) fn(bytes1) + match bytes1 + Ok((bytes, bytesRead)) -> + cb(Ok(bytes)) + if bytesRead.int64 != length then + // TODO: Actually read it all? + cb(Error(Exception("Not Full Read", ExnInternal("Not Full Read")))) + else () + Error(e) -> + cb(Error(e)) + Just({ fd.close(fn(_) ())}) + res.string + +pub fun read-file(path: path) + val stream = stream-file(path) + val done = ref(False) + val str = ref("") + while { ! !done } + match stream.receive + Ok(bytes) -> str := !str ++ bytes.string + Error(Exception("EOF",_ )) -> done := True + Error(e) -> + done := True + throw-exn(e) + !str + +pub fun open(path: path, flags: int32, mode: int32): asyncx uv-file + await fn(cb) + open(path.string, flags, mode) fn(fd) + cb(fd) + Nothing + +pub fun stream-file(path: path, chunkSize: int32 = 0x1024.int32) + val ch = channel() + val file = open(path, o_RDONLY, 0.int32) + await-to-channel( + (fn(cb) + read-file_(file, chunkSize, 0.int32, cb) + Just({ file.close(fn(_) ())}) + ), + ch, + fn(e) e + ) + +fun read-file_(fd: uv-file, chunkSize: int32, offset: int32, cb: (error, bool) -> io-noexn ()) + read(fd, chunkSize, offset) fn(bytes1) + match bytes1 + Ok((bytes, bytesRead)) -> + cb(Ok(bytes), False) + if bytesRead.int32 == chunkSize then + read-file_(fd, chunkSize, offset+bytesRead.int32, cb) + else + cb(Error(Exception("EOF", ExnInternal("EOF"))), True) + Error(e) -> + cb(Error(e), True) + +fun cb/write-to-file(fd: uv-file, bts: bytes, offset: int64, cb: (error<()>) -> io-noexn ()): io-noexn () + write(fd, bts, offset) fn(bytesWritten) + match bytesWritten + Ok(bytes) -> + if bytes == bts.length.int then cb(Ok(())) + else write-to-file(fd, bts.advance(bytes.ssize_t), offset + bytes.int64, cb) + Error(e) -> cb(Error(e)) + +fun bytes/write-to-file(fd: uv-file, bts: bytes, offset: int64): io-noexn error<()> + val bytesWritten = write-sync(fd, bts, offset) + match bytesWritten + Ok(bytes) -> + if bytes == bts.length.int then Ok(()) + else write-to-file(fd, bts.advance(bytes.ssize_t), offset + bytes.int64) + Error(e) -> Error(e) + +pub fun write-to-file(fd: uv-file, s: string): asyncx () + await fn(cb) + write-to-file(fd, s.bytes, 0.int64, cb) + Just({fd.close(fn(_) ())}) + +pub fun write-to-file-sync(fd: uv-file, s: string): io-noexn error<()> + write-to-file(fd, s.bytes, 0.int64) + +pub fun close(fd: uv-file): asyncx () + await fn(cb) + fd.close(fn(_) cb(Ok(()))) + Nothing + +pub fun unlink(path: path): asyncx () + await fn(cb) + unlink(path.string) fn(_) cb(Ok(())) + Nothing + +pub inline fun delete(path: path): asyncx () + unlink(path) + +pub fun mkdir(path: path, mode: int32 = 0.int32): asyncx () + await fn(cb) + mkdir(path.string, mode) fn(_) cb(Ok(())) + Nothing + +pub inline fun create-dir(path: path, mode: int32 = 0.int32): asyncx () + mkdir(path, mode) + +pub fun make-temp-dir(p: path) + await fn(cb) + mkdtemp(p.string ++ "XXXXXX") fn(p1) + match p1 + Ok(p1') -> cb(Ok(p1'.path)) + Error(e) -> cb(Error(e)) + Nothing + +pub inline fun create-temp-dir(path: path): asyncx path + make-temp-dir(path) + +pub fun make-temp-file(p: path) + await fn(cb) + mkstemp(p.string ++ "XXXXXX") fn(p1) + match p1 + Ok((file, p1')) -> cb(Ok((p1'.path, file))) + Error(e) -> cb(Error(e)) + Nothing + +pub inline fun create-temp-file(path: path): asyncx (path, uv-file) + make-temp-file(path) + +// TODO: Add recursive delete && force delete with children +pub fun rmdir(path: path): asyncx () + await fn(cb) + rmdir(path.string) fn(_) cb(Ok(())) + Nothing + +pub inline fun remove-dir(path: path): asyncx () + rmdir(path) + +pub inline fun delete-directory(path: path): asyncx () + rmdir(path) + +fun dir-list(req: uv-fs-req): io-noexn list + match scandir-next(req) + Ok(dirent) -> Cons(dirent, dir-list(req)) + Error(_) -> Nil + +pub fun list-directory(path: path): asyncx list + await fn(cb) + scandir(path.string) fn(e) + match e + Ok(req) -> cb(Ok(dir-list(req))) + Error(e) -> cb(Error(e)) + Nothing // TODO: Does req need cleanup? + +pub fun path/stat(path: path): asyncx fstat + await fn(cb) + stat(path.string) fn(e) + cb(e) + Nothing + +pub fun lstat(path: path): asyncx fstat + await fn(cb) + lstat(path.string) fn(e) + cb(e) + Nothing + +pub fun file/stat(fd: uv-file): asyncx fstat + await fn(cb) + fstat(fd) fn(e) + cb(e) + Nothing + +pub fun rename(path: path, newPath: path): asyncx () + await fn(cb) + rename(path.string, newPath.string) fn(e) + cb(e) + Nothing + +pub fun fsync(fd: uv-file): asyncx () + await fn(cb) + fsync(fd) fn(e) + cb(e) + Nothing + +pub fun fdatasync(fd: uv-file): asyncx () + await fn(cb) + fdatasync(fd) fn(e) + cb(e) + Nothing + +pub inline fun flush(fd: uv-file): asyncx () + fsync(fd) + +pub fun truncate(fd: uv-file, offset: int64): asyncx () + await fn(cb) + ftruncate(fd, offset) fn(e) + cb(e) + Nothing + +pub fun copyfile(path: path, newPath: path, flags: int32 = 0.int32): asyncx () + await fn(cb) + copyfile(path.string, newPath.string, flags) fn(e) + cb(e) + Nothing + +pub fun redirect(outFd: uv-file, inFd: uv-file, inOffset: int64, length: int64): asyncx int + await fn(cb) + sendfile(outFd, inFd, inOffset, length.int.ssize_t) fn(e) + cb(e) + Nothing + +pub fun access(path: path, mode: int32): asyncx bool + await fn(cb) + access(path.string, mode) fn(e) + match e + Ok(_) -> cb(Ok(True)) + _ -> cb(Ok(False)) + Nothing + +pub inline fun can-access(path: path, mode: int32): asyncx bool + access(path, mode) + +pub fun path/chmod(path: path, mode: int32): asyncx () + await fn(cb) + chmod(path.string, mode) fn(e) + cb(e) + Nothing + +pub fun file/chmod(fd: uv-file, mode: int32): asyncx () + await fn(cb) + fchmod(fd, mode) fn(e) + cb(e) + Nothing + +pub fun path/update-time(path: path, access-time: float64, modified-time: float64): asyncx () + await fn(cb) + utime(path.string, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun file/update-time(fd: uv-file, access-time: float64, modified-time: float64): asyncx () + await fn(cb) + futime(fd, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun link-update-time(path: path, access-time: float64, modified-time: float64): asyncx () + await fn(cb) + lutime(path.string, access-time, modified-time) fn(e) + cb(e) + Nothing + +pub fun link(path: path, newPath: path): asyncx () + await fn(cb) + link(path.string, newPath.string) fn(e) + cb(e) + Nothing + +pub fun symlink(path: path, newPath: path, flags: int32 = 0.int32): asyncx () + await fn(cb) + symlink(path.string, newPath.string, flags) fn(e) + cb(e) + Nothing + +pub fun readlink(path0: path): asyncx path + await fn(cb) + readlink(path0.string) fn(e) + match e + Ok(p) -> cb(Ok(p.path)) + Error(e) -> cb(Error(e)) + Nothing + +// Follows all symlinks to get the real path (max depth 32) +pub fun real-path(path0: path): asyncx path + await fn(cb) + realpath(path0.string) fn(e) + match e + Ok(p:string) -> cb(Ok(p.path)) + Error(e) -> cb(Error(e)) + Nothing + +// Only works on unix systems +pub fun path/chown(path: path, uid: int32, gid: int32): asyncx () + await fn(cb) + chown(path.string, uid, gid) fn(e) + cb(e) + Nothing + +// Only works on unix systems +pub fun file/chown(fd: uv-file, uid: int32, gid: int32): asyncx () + await fn(cb) + fchown(fd, uid, gid) fn(e) + cb(e) + Nothing + +// Only works on unix systems +pub fun link-chown(path: path, uid: int32, gid: int32): asyncx () + await fn(cb) + lchown(path.string, uid, gid) fn(e) + cb(e) + Nothing \ No newline at end of file diff --git a/lib/std/os/net.kk b/lib/std/os/net.kk new file mode 100644 index 000000000..5c36c8fcf --- /dev/null +++ b/lib/std/os/net.kk @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +/// This module provides a set of functions for asynchronous network I/O. +module std/os/net + +pub import std/async +import uv/net + +pub fun tcp(): io uv-tcp + tcp-init().untry + +pub fun tcp/bind(tcp: uv-tcp, addr: string, port: int): () + net/bind(tcp, Sock-addr(AF_INET, addr, Just(port.int32)), 0.int32).untry + +pub fun listen(tcp: uv-tcp): channel> + val ch = channel() + await-to-channel( + fn(cb) + stream/listen(tcp.stream, 0.int32) fn(err) + match tcp-init() + Ok(t) -> + val str = t.stream + val err2 = tcp.stream.accept(str) + match err2 + UV_OK -> cb(Ok(str), False) + _ -> cb(Error(Exception(err2.message, AsyncExn(err2))), True) + _ -> cb(Error(Exception(err.message, AsyncExn(err))), True) + Just({ + // If canceled close the stream (also closes the handle) + // ignoring errors for now + tcp.stream.shutdown(fn(e) ()) + }) + ,ch, + fn(e) e + ) + +pub fun write(stream: uv-stream, bts: bytes): asyncx () + await fn(cb) + stream.write([bts], fn(err) + match err + UV_OK -> cb(Ok(())) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + ) + Nothing + +pub fun read(stream: uv-stream): asyncx bytes + await fn(cb) + stream.read-start(fn(bts) + cb(Ok(bts)) + ) + Just({ + stream.read-stop() + () + }) + +pub fun stream(stream: uv-stream): asyncx channel + val ch = channel() + await-to-channel(fn(cb) { + stream.read-start(fn(bts) + cb(Ok(bts), False) + ) + Just({ + stream.read-stop() + () + }) + }, + ch, + fn(e) e.default("".bytes)) + +pub fun addr/connect(tcp: uv-tcp, address: sock-addr): asyncx uv-stream + await fn(cb) + tcp.connect(address) fn(err) + match err + UV_OK -> cb(Ok(tcp.stream)) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + Nothing + +pub fun connect(tcp: uv-tcp, address: string, port: int=80): asyncx uv-stream + await fn(cb) + tcp.connect(Sock-addr(AF_INET, address, Just(port.int32))) fn(err) + match err + UV_OK -> cb(Ok(tcp.stream)) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + Nothing + +pub fun stream/shutdown(stream: uv-stream): () + await fn(cb) + stream.shutdown(fn(err) + match err + UV_OK -> cb(Ok(())) + _ -> cb(Error(Exception(err.message, AsyncExn(err)))) + ) + Nothing + stream.tcp.shutdown() + +// Closes the tcp handle +pub fun tcp/shutdown(tcp: uv-tcp): asyncx () + await fn(cb) + tcp.uv-handle.close fn() + cb(Ok(())) + Nothing diff --git a/lib/std/time/timer-inline.c b/lib/std/time/timer-inline.c index 6e4224a78..79049aee5 100644 --- a/lib/std/time/timer-inline.c +++ b/lib/std/time/timer-inline.c @@ -1,3 +1,5 @@ +// #include "std_time_timer.h"; +#include "std_core_types.h" /*--------------------------------------------------------------------------- Copyright 2020-2021, Microsoft Research, Daan Leijen. @@ -6,16 +8,16 @@ found in the LICENSE file at the root of this distribution. ---------------------------------------------------------------------------*/ -static kk_std_core_types__tuple2 kk_timer_ticks_tuple(kk_context_t* ctx) { - kk_duration_t d = kk_timer_ticks(ctx); +static kk_std_core_types__tuple2 kk_timer_ticks_tuple(kk_context_t* _ctx) { + kk_duration_t d = kk_timer_ticks(_ctx); // the conversion has about 15 digits of precision // we cannot do this more precisely as the api expects the fraction between 0.0 and 2.0 (for leap seconds). double secs = (double)d.seconds; double frac = (double)d.attoseconds * 1e-18; - return kk_std_core_types__new_Tuple2( kk_double_box(secs,ctx), kk_double_box(frac,ctx), ctx ); + return kk_std_core_types__new_Tuple2( kk_double_box(secs, _ctx), kk_double_box(frac, _ctx), _ctx ); } -static double kk_timer_dresolution(kk_context_t* ctx) { - int64_t asecs = kk_timer_resolution(ctx); +static double kk_timer_dresolution(kk_context_t* _ctx) { + int64_t asecs = kk_timer_resolution(_ctx); return (double)asecs * 1e-18; } diff --git a/lib/std/time/timer.kk b/lib/std/time/timer.kk index 7ce93a2ca..7b0debbd0 100644 --- a/lib/std/time/timer.kk +++ b/lib/std/time/timer.kk @@ -10,10 +10,12 @@ */ module std/time/timer +import std/num/int64 import std/num/float64 import std/num/ddouble import std/time/duration import std/time/instant +pub import std/time/timestamp extern import c file "timer-inline.c" @@ -59,4 +61,3 @@ pub fun print-elapsed( action : () -> a, msg : string = "elapse val (t,x) = elapsed(action) println( msg ++ " " ++ t.show(3) ) x - diff --git a/lib/toc.kk b/lib/toc.kk index af2e238bc..09731fb43 100644 --- a/lib/toc.kk +++ b/lib/toc.kk @@ -18,6 +18,8 @@ pub import std/core/unsafe pub import std/core/undiv pub import std/core +pub import std/data/bytes + pub import std/os/env pub import std/os/flags pub import std/os/file diff --git a/lib/uv/event-loop.kk b/lib/uv/event-loop.kk new file mode 100644 index 000000000..c8c5ddae8 --- /dev/null +++ b/lib/uv/event-loop.kk @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/event-loop + +pub import std/time/duration +pub import std/time/timestamp +pub import uv/timer +import std/num/int32 +import uv/utils + +extern import + c { conan="libuv[>=1.47.0]"; vcpkg="libuv"; library="uv" } +// TODO: wasm {} + +extern import + c file "inline/event-loop.c" + cs file "inline/event-loop.cs" + +// Sets a timeout for libuv / wasm / javscript / C# event loops +pub extern set-timeout( cb : () -> io-noexn (), ms : int32 ) : io-noexn any + cs "_Async.SetTimeout" + js "setTimeout" + c "kk_set_timeout" + +// Clears a timeout for libuv / wasm / javscript / C# event loops +pub extern clear-timeout( tid : any) : io-noexn () + cs "_Async.ClearTimeout" + js "clearTimeout" + c "kk_clear_timeout" + +// Handles a uv loop +pub fun default-event-loop(action) + if host() == "libc" then + handle-loop(action) + else // TODO: Support event loop on other platforms + action() + +// Configures uv with Koka's allocators when importing this file +val @initialize = init-uv-alloc() + +// Runs a UV loop +fun handle-loop(action) + init-loop() + val res = action() + run-loop() + close-loop() + res + +// Runs a uv loop (or the emscripten loop on wasm) +extern run-loop(): io-noexn () + c "kk_uv_loop_run" + wasm "kk_emscripten_loop_run" + js inline "" + cs inline "" + +// Initilizes a uv loop +extern init-loop(): io-noexn () + c "kk_uv_loop_init" + wasm inline "kk_Unit" + js inline "" + cs inline "" + +// initializes only the allocators (not an event loop) +// needed for some file operations which alloc - kk_uv_fs_mkdtemp, kk_uv_fs_mkstemp +pub extern init-uv-alloc(): () + c "kk_uv_alloc_init" + wasm inline "kk_Unit" + cs inline "" + js inline "" + +// Closes a uv loop +extern close-loop(): io-noexn () + c "kk_uv_loop_close" + wasm inline "kk_Unit" + cs inline "" + js inline "" + +// Closes a uv handle +pub extern close(hnd: uv-handle, callback: () -> io-noexn ()): io-noexn () + c "kk_uv_close" + wasm inline "kk_Unit" + cs inline "" + js inline "" diff --git a/lib/uv/file.kk b/lib/uv/file.kk new file mode 100644 index 000000000..ccee4a5ea --- /dev/null +++ b/lib/uv/file.kk @@ -0,0 +1,444 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +// Provides low level file operations using libuv callbacks +module uv/file + +pub import uv/event-loop +pub import std/data/bytes +pub import std/num/int32 +pub import std/num/int64 +pub import uv/utils + +extern import + c header-end-file "inline/file.h" + +extern import + c file "inline/file.c" + +// A Koka representation of the timespec struct +pub value struct timespec + sec: int64 + nsec: int32; + +pub fun timespec/show(t: timespec): string + t.sec.show ++ "." ++ t.nsec.show.pad-left(9, '0') + +// A Koka representation of the fstat struct +pub struct fstat + dev: int64 + mode: int64 + nlink: int64 + uid: int64 + gid: int64 + rdev: int64 + ino: int64 + size: int64 + blksize: int64 + blocks: int64 + flags: int64 + atime: timespec + mtime: timespec + ctime: timespec + birthtime: timespec + +pub fun fstat/show(f: fstat): string + "{dev=" ++ f.dev.show ++ ", mode=" ++ f.mode.show ++ ", nlink=" ++ f.nlink.show ++ ", uid=" ++ f.uid.show ++ + ", gid=" ++ f.gid.show ++ ", rdev=" ++ f.rdev.show ++ ", ino=" ++ f.ino.show ++ ", size=" ++ f.size.show ++ + ", blksize=" ++ f.blksize.show ++ ", blocks=" ++ f.blocks.show ++ ", flags=" ++ f.flags.show ++ ", atime=" ++ + f.atime.show ++ ", mtime=" ++ f.mtime.show ++ ", ctime=" ++ f.ctime.show ++ ", birthtime=" ++ f.birthtime.show ++ "}" + +// A Koka representation of the statfs struct +pub struct statfs + filetype: int64 + bsize: int64 + blocks: int64 + bfree: int64 + bavail: int64 + files: int64 + ffree: int64 + fspare: vector // Length 4 Convert to tuple? + +pub fun statfs/show(f: statfs): string + "{filetype=" ++ f.filetype.show ++ ", bsize=" ++ f.bsize.show ++ ", blocks=" ++ f.blocks.show ++ ", bfree=" ++ + f.bfree.show ++ ", bavail=" ++ f.bavail.show ++ ", files=" ++ f.files.show ++ ", ffree=" ++ f.ffree.show ++ + ", fspare=[" ++ f.fspare.map(fn(s) s.show).list.join(",") ++ "]}" + +// Directory entry types +pub type dirent-type + UNKNOWN_DIRECTORY_ENTRY + FILE + DIR + LINK + FIFO + SOCKET + CHAR + BLOCK + +// A Koka representation of a directory entry +pub value struct dirent + name: string + entity-type: dirent-type; + +pub alias create-mode = int32 +// Octal 0o700 // Read, write, execute by owner. +pub val s_IRWXU = (7 * 64 + 0 + 0).int32 +// Octal 0o400 // Read permission, owner. +pub val s_IRUSR = (4 * 64 + 0 + 0).int32 +// Octal 0o200 // Write permission, owner. +pub val s_IWUSR = (2 * 64 + 0 + 0).int32 +// Octal 0o100 // Execute/search permission, owner. +pub val s_IXUSR = (1 * 64 + 0 + 0).int32 +// Octal 0o070 // Read, write, execute by group. +pub val s_IRWXG = (0 + 7 * 8 + 0).int32 +// Octal 0o040 // Read permission, group +pub val s_IRGRP = (0 + 4 * 8 + 0).int32 +// Octal 0o020 // Write permission, group.. +pub val s_IWGRP = (0 + 2 * 8 + 0).int32 +// Octal 0o010 // Execute/search permission, group. +pub val s_IXGRP = (0 + 1 * 8 + 0).int32 +// Octal 0o007 // Read, write, execute by others. +pub val s_IRWXO = (0 + 0 + 7).int32 +// Octal 0o004 // Read permission, others. +pub val s_IROTH = (0 + 0 + 4).int32 +// Octal 0o002 // Write permission, others. +pub val s_IWOTH = (0 + 0 + 2).int32 +// Octal 0o001 // Execute/search permission, others. +pub val s_IXOTH = (0 + 0 + 1).int32 + +// Octal 0o4000 // Set-user-ID +pub val s_ISUID = (4 * 512 + 0 + 0 + 0).int32 +// Octal 0o2000 // Set-group-ID (see inode(7)) +pub val s_ISGID = (0 + 2 * 512 + 0 + 0).int32 +// Octal 0o1000 // Sticky bit (see inode(7)) +pub val s_ISVTX = (0 + 0 + 1 * 512 + 0).int32 + +// Read-only access. +pub val o_RDONLY = oRDONLY() +extern oRDONLY(): int32 + c inline "UV_FS_O_RDONLY" +// Write-only access. +pub val o_WRONLY = oWRONLY() +extern oWRONLY(): int32 + c inline "UV_FS_O_WRONLY" +// Read-write access. +pub val o_RDWR = oRDWR() +extern oRDWR(): int32 + c inline "UV_FS_O_RDWR" +// Create the file if it does not exist. +pub val o_CREAT = oCREAT() +extern oCREAT(): int32 + c inline "UV_FS_O_CREAT" +// If pathname already exists, then fail the open with the error EEXIST. +pub val o_EXCL = oEXCL() +extern oEXCL(): int32 + c inline "UV_FS_O_EXCL" +// If pathname refers to a terminal device, don't allocate controlling terminal for this process. +pub val o_NOCTTY = oNOCTTY() +extern oNOCTTY(): int32 + c inline "UV_FS_O_NOCTTY" +// If the file exists and is a regular file, and the file is successfully opened O_RDWR or O_WRONLY, its length shall be truncated to 0. (Octal 0o1000) +pub val o_TRUNC = oTRUNC() +extern oTRUNC(): int32 + c inline "UV_FS_O_TRUNC" +// Before each write(2), the file offset is positioned at the end of the file, as if with lseek(2). +pub val o_APPEND = oAPPEND() +extern oAPPEND(): int32 + c inline "UV_FS_O_APPEND" +// Open the file in nonblocking mode if possible. +pub val o_NONBLOCK = oNONBLOCK() +extern oNONBLOCK(): int32 + c inline "UV_FS_O_NONBLOCK" +// The file is opened for synchronous I/O. Write operations will complete once all data and a minimum of metadata are flushed to disk. +pub val o_DSYNC = oDSYNC() +extern oDSYNC(): int32 + c inline "UV_FS_O_DSYNC" +// direct disk access hint +pub val o_DIRECT = oDIRECT() +extern oDIRECT(): int32 + c inline "UV_FS_O_DIRECT" +// must be a directory +pub val o_DIRECTORY = oDIRECTORY() +extern oDIRECTORY(): int32 + c inline "UV_FS_O_DIRECTORY" +// don't follow links +pub val o_NOFOLLOW = oNOFOLLOW() +extern oNOFOLLOW(): int32 + c inline "UV_FS_O_NOFOLLOW" +// Do not update the file access time when the file is read +pub val o_NOATIME = oNOATIME() +extern oNOATIME(): int32 + c inline "UV_FS_O_NOATIME" +// Create an unnamed temporary file. pathname specifie a directory +pub val o_TMPFILE = oTMPFILE() +extern oTMPFILE(): int32 + c inline "UV_FS_O_TEMPORARY" + +// A uv filesystem request type +pub value struct uv-fs-req { internal : any }; + +// A uv file type +pub value struct uv-file { internal : intptr_t }; + +// A uv directory type +pub value struct uv-dir { internal : intptr_t }; + +pub extern uv-fs-req(): io-noexn uv-fs-req + c inline "kk_uv_fs_init(kk_context())" + +// Cleanup request. Must be called after a request is finished to deallocate any memory libuv might have allocated. +pub extern req_cleanup(req: uv-fs-req): io-noexn () + c "kk_uv_fs_req_cleanup" // ?? Is there a better way? + +// Equivalent to close(2) +pub extern uv/close(file: uv-file, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_close" + +pub extern uv/close-sync(file: uv-file): io-noexn error<()> + c "kk_uv_fs_close_sync" + +// Equivalent to open(2). +// WARNING: On Windows libuv uses CreateFileW and thus the file is always opened in binary mode. Because of this the O_BINARY and O_TEXT flags are not supported. +pub extern uv/open(path: string, flags: int32, mode: int32, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_open" + +pub extern open-sync_(path: string, flags: int32, mode: int32): io-noexn error + c "kk_uv_fs_open_sync" + +pub fun open-sync(path: string, flags: int32, mode: int32=0.int32): io-noexn error + if mode == 0.int32 && (flags == o_TMPFILE || flags == o_CREAT) then + Error(Exception("Cannot open with no permission flags when creating " ++ path, ExnAssert)) + else open-sync_(path, flags, mode) + +pub fun open(path: string, flags: int32, cb: (error) -> io-noexn (), mode: int32 = 0.int32): io-noexn () + if mode == 0.int32 && (flags == o_TMPFILE || flags == o_CREAT) then + cb(Error(Exception("Cannot open with no permission flags when creating " ++ path, ExnAssert))) + open(path, flags, mode, cb) + +// Equivalent to preadv(2). If the offset argument is -1, then the current file offset is used and updated. +// WARNING: On Windows, under non-MSVC environments (e.g. when GCC or Clang is used to build libuv), files opened using UV_FS_O_FILEMAP may cause a fatal crash if the memory mapped read operation fails. +pub extern uv/read(file: uv-file, length: int32, offset: int32, cb: (error<(bytes, int)>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_read" + +pub fun read(file: uv-file, length: int32, cb: (error<(bytes, int)>) -> io-noexn ()): io-noexn () + read(file, 0.int32, length, cb) +// TODO: Version of read that takes in bytes to reuse? + +pub extern uv/read-sync(file: uv-file, length: int32, offset: int32): io-noexn error<(bytes, int)> + c "kk_uv_fs_read_sync" + +pub fun read-sync(file: uv-file, length: int32, offset: int32 = 0.int32): io-noexn error<(bytes, int)> + uv/read-sync(file, length, offset) + +pub extern unlink(path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_unlink" + +pub extern unlink-sync(path: string): io-noexn error<()> + c "kk_uv_fs_unlink_sync" + +pub extern write(file: uv-file, data: bytes, offset: int64, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_write" + +pub extern write-sync(file: uv-file, data: bytes, offset: int64): io-noexn error + c "kk_uv_fs_write_sync" + +pub extern mkdir(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkdir" + +pub extern mkdir-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_mkdir_sync" + +pub extern mkdtemp(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkdtemp" + +pub extern mkdtemp-sync(path: string): io-noexn error + c "kk_uv_fs_mkdtemp_sync" + +pub extern mkstemp(path: string, cb: (error<(uv-file, string)>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_mkstemp" + +pub extern mkstemp-sync(path: string): io-noexn error<(uv-file, string)> + c "kk_uv_fs_mkstemp_sync" + +pub extern rmdir(path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_rmdir" + +pub extern rmdir-sync(path: string): io-noexn error<()> + c "kk_uv_fs_rmdir_sync" + +pub extern opendir(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_opendir" + +pub extern opendir-sync(path: string): io-noexn error + c "kk_uv_fs_opendir_sync" + +pub extern closedir(dir: uv-dir, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_closedir" + +pub extern closedir-sync(dir: uv-dir): io-noexn error<()> + c "kk_uv_fs_closedir_sync" + +pub extern readdir(dir: uv-dir, cb: (error>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_readdir" + +pub extern readdir-sync(dir: uv-dir): io-noexn error> + c "kk_uv_fs_readdir_sync" + +pub extern scandir(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_scandir" + +pub extern scandir-sync(path: string): io-noexn error + c "kk_uv_fs_scandir_sync" + +pub extern scandir-next(req: uv-fs-req): io-noexn error + c "kk_uv_fs_scandir_next" + +pub extern stat(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_stat" + +pub extern stat-sync(path: string): io-noexn error + c "kk_uv_fs_stat_sync" + +pub extern fstat(path: uv-file, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fstat" + +pub extern fstat-sync(path: uv-file): io-noexn error + c "kk_uv_fs_fstat_sync" + +// Returns the same as stat(), except that if the path is a symbolic link, it returns information about the link itself +pub extern lstat(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lstat" + +pub extern lstat-sync(path: string): io-noexn error + c "kk_uv_fs_lstat_sync" + +pub extern rename(path: string, new_path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_rename" + +pub extern rename-sync(path: string, new_path: string): io-noexn error<()> + c "kk_uv_fs_rename_sync" + +pub extern fsync(file: uv-file, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fsync" + +pub extern fsync-sync(file: uv-file): io-noexn error<()> + c "kk_uv_fs_fsync_sync" + +pub extern fdatasync(file: uv-file, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fdatasync" + +pub extern fdatasync-sync(file: uv-file): io-noexn error<()> + c "kk_uv_fs_fdatasync_sync" + +pub extern ftruncate(file: uv-file, offset: int64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_ftruncate" + +pub extern ftruncate-sync(file: uv-file, offset: int64): io-noexn error<()> + c "kk_uv_fs_ftruncate_sync" + +pub val uv_fs_COPYFILE_EXCL = uv_fs_COPYFILE_EXCL_() +extern uv_fs_COPYFILE_EXCL_(): int32 + c inline "UV_FS_COPYFILE_EXCL" + +pub val uv_fs_COPYFILE_FICLONE = uv_fs_COPYFILE_FICLONE_() +extern uv_fs_COPYFILE_FICLONE_(): int32 + c inline "UV_FS_COPYFILE_FICLONE" + +pub val uv_fs_COPYFILE_FICLONE_FORCE = uv_fs_COPYFILE_FICLONE_FORCE_() +extern uv_fs_COPYFILE_FICLONE_FORCE_(): int32 + c inline "UV_FS_COPYFILE_FICLONE_FORCE" + +pub extern copyfile(path: string, new_path: string, flags: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_copyfile" + +pub extern copyfile-sync(path: string, new_path: string, flags: int32): io-noexn error<()> + c "kk_uv_fs_copyfile_sync" + +pub extern sendfile(out_file: uv-file, in_file: uv-file, in_offset: int64, length: ssize_t, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_sendfile" + +pub extern sendfile-sync(out_file: uv-file, in_file: uv-file, in_offset: int64, length: ssize_t): io-noexn error + c "kk_uv_fs_sendfile_sync" + +pub extern access(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_access" + +pub extern access-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_access_sync" + +pub extern chmod(path: string, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_chmod" + +pub extern chmod-sync(path: string, mode: int32): io-noexn error<()> + c "kk_uv_fs_chmod_sync" + +pub extern fchmod(file: uv-file, mode: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fchmod" + +pub extern fchmod-sync(file: uv-file, mode: int32): io-noexn error<()> + c "kk_uv_fs_fchmod_sync" + +pub extern utime(path: string, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_utime" + +pub extern utime-sync(path: string, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_utime_sync" + +pub extern futime(file: uv-file, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_futime" + +pub extern futime-sync(file: uv-file, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_futime_sync" + +pub extern lutime(path: string, atime: float64, mtime: float64, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lutime" + +pub extern lutime-sync(path: string, atime: float64, mtime: float64): io-noexn error<()> + c "kk_uv_fs_lutime_sync" + +pub extern link(path: string, new_path: string, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_link" + +pub extern link-sync(path: string, new_path: string): io-noexn error<()> + c "kk_uv_fs_link_sync" + +pub extern symlink(path: string, new_path: string, flags: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_symlink" + +pub extern symlink-sync(path: string, new_path: string, flags: int32): io-noexn error<()> + c "kk_uv_fs_symlink_sync" + +pub extern readlink(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_readlink" + +pub extern readlink-sync(path: string): io-noexn error + c "kk_uv_fs_readlink_sync" + +pub extern realpath(path: string, cb: (error) -> io-noexn ()): io-noexn () + c "kk_uv_fs_realpath" + +pub extern realpath-sync(path: string): io-noexn error + c "kk_uv_fs_realpath_sync" + +pub extern chown(path: string, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_chown" + +pub extern chown-sync(path: string, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_chown_sync" + +pub extern fchown(file: uv-file, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_fchown" + +pub extern fchown-sync(file: uv-file, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_fchown_sync" + +pub extern lchown(path: string, uid: int32, gid: int32, cb: (error<()>) -> io-noexn ()): io-noexn () + c "kk_uv_fs_lchown" + +pub extern lchown-sync(path: string, uid: int32, gid: int32): io-noexn error<()> + c "kk_uv_fs_lchown_sync" diff --git a/lib/uv/fs-event.kk b/lib/uv/fs-event.kk new file mode 100644 index 000000000..d056d3eb7 --- /dev/null +++ b/lib/uv/fs-event.kk @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------- + Copyright 2024 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/fs-event +import uv/utils +pub import uv/file +pub import std/num/int32 + +extern import + c file "inline/fs-event.c" + +// FS Event handles allow the user to monitor a given path for changes, for example, if the file was renamed or there was a generic change in it. +// This handle uses the best backend for the job on each platform. +value struct uv-fs-event {internal: any} + +// Event types that uv_fs_event_t handles monitor. +pub type uv-fs-event-kind + UV_RENAME // 1 + UV_CHANGE // 2 + +pub fun event-kind/show(k: uv-fs-event-kind): string + match k + UV_RENAME -> "rename" + UV_CHANGE -> "change" + +pub fun int/event-kinds(i: int32): list + val kinds = Nil + val kinds' = if i.and(1.int32) != 0.int32 then Cons(UV_RENAME, kinds) else kinds + return (if i.and(2.int32) != 0.int32 then Cons(UV_CHANGE, kinds') else kinds') + +// Flags that can be passed to uv_fs_event_start() to control its behavior. +pub type uv-fs-event-flags + // By default, if the fs event watcher is given a directory name, we will + // watch for all events in that directory. This flags overrides this behavior + // and makes fs_event report only changes to the directory entry itself. This + // flag does not affect individual files watched. + // This flag is currently not implemented yet on any backend. + UV_FS_EVENT_WATCH_ENTRY // 1 + // By default uv_fs_event will try to use a kernel interface such as inotify + // or kqueue to detect events. This may not work on remote file systems such + // as NFS mounts. This flag makes fs_event fall back to calling stat() on a + // regular interval. + // This flag is currently not implemented yet on any backend. + UV_FS_EVENT_STAT // 2 + // By default, event watcher, when watching directory, is not registering + // (is ignoring) changes in its subdirectories. + // This flag will override this behaviour on platforms that support it.v + UV_FS_EVENT_RECURSIVE // 4 + +pub fun flags/int(l: list): int32 + match l + Cons(UV_FS_EVENT_WATCH_ENTRY, l') -> 1.int32.or(flags/int(l')) + Cons(UV_FS_EVENT_STAT, l') -> 2.int32.or(flags/int(l')) + Cons(UV_FS_EVENT_RECURSIVE, l') -> 4.int32.or(flags/int(l')) + Nil -> 0.int32 + +// Initialize the handle. +pub extern uv-fs-event-init(): error + c "kk_uv_fs_event_init" + +// Start the handle with the given callback, which will watch the specified path for changes. +// flags can be an ORed mask of `uv_fs_event_flags`. +pub extern uv-extern-fs-event-start(^event: uv-fs-event, path: string, flags: int32, cb: error<(string, int32)> -> io-noexn ()): io-noexn error<()> + c "kk_uv_fs_event_start" + +// Start the handle with the given callback, which will watch the specified path for changes. +// flags can be an list of `uv_fs_event_flags`. +pub fun uv-fs-event-start(^event: uv-fs-event, path: string, flags: list, cb: error<(string, list)> -> io-noexn ()): io-noexn error<()> + uv-extern-fs-event-start(event, path, flags/int(flags)) fn(e) + match e + Error(e) -> cb(Error(e)) + Ok((p, i)) -> cb(Ok((p, event-kinds(i)))) + +// Stop the handle, the callback will no longer be called. +pub extern uv-fs-event-stop(event: uv-fs-event): io-noexn error<()> + c "kk_uv_fs_event_stop" + +// Get the path being monitored by the handle. +pub extern uv-fs-event-getpath(^event: uv-fs-event): io-noexn error + c "kk_uv_fs_event_getpath" diff --git a/lib/uv/inline/event-loop.c b/lib/uv/inline/event-loop.c new file mode 100644 index 000000000..5f37c23ef --- /dev/null +++ b/lib/uv/inline/event-loop.c @@ -0,0 +1,82 @@ +#ifdef __EMSCRIPTEN__ +#include +#include + +void one_iter() { + // Can do a render loop to the screen here, etc. (this is the tick..) + // puts("one iteration"); + return; +} +void kk_emscripten_loop_run(kk_context_t* _ctx){ + emscripten_set_main_loop(one_iter, 0, true); +} +#else + +// UV allocator helpers, getting thread local context + +static inline void* kk_malloc_ctx(size_t size) { + return kk_malloc(size, kk_get_context()); +} + +static inline void* kk_realloc_ctx(void* p, size_t size) { + return kk_realloc(p, size, kk_get_context()); +} + +static inline void* kk_calloc_ctx(size_t count, size_t size) { + void* p = kk_malloc(count*size, kk_get_context()); + kk_memset(p, 0, count*size); + return p; +} + +static inline void kk_free_ctx(void* p) { + kk_free(p, kk_get_context()); +} + +static inline void kk_uv_alloc_init(kk_context_t* _ctx){ + uv_replace_allocator(kk_malloc_ctx, kk_realloc_ctx, kk_calloc_ctx, kk_free_ctx); +} + +static void kk_uv_loop_init(kk_context_t* _ctx) { + uv_loop_t* loop = kk_malloc(sizeof(uv_loop_t), kk_context()); + uv_loop_init(loop); + kk_context_t* ctx = loop->data = kk_context(); + ctx->loop = loop; +} + +void kk_uv_loop_run(kk_context_t* _ctx){ + // Run the event loop after the initial startup of the program + int ret = uv_run(uvloop(), UV_RUN_DEFAULT); + if (ret != 0){ + kk_warning_message("Event loop closed with status %s", uv_err_name(ret)); + } +} + +static void kk_uv_loop_close(kk_context_t* _ctx) { + uv_loop_close(uvloop()); + kk_free(uvloop(), _ctx); +} + +static void kk_uv_handle_close_callback(uv_handle_t* handle){ + kk_uv_hnd_get_callback(handle, kk_hnd, callback) + handle->data = NULL; + kk_unit_callback(callback) + kk_box_drop(kk_hnd, kk_context()); +} + +static void kk_uv_close(kk_uv_utils__uv_handle handle, kk_function_t callback, kk_context_t* _ctx) { + kk_set_hnd_cb(uv_handle_t, handle, uvhnd, callback) + return uv_close(uvhnd, kk_uv_handle_close_callback); +} +#endif + +kk_box_t kk_set_timeout(kk_function_t cb, int64_t time, kk_context_t* _ctx) { + kk_uv_timer__timer t = kk_uv_timer_timer_init(_ctx); + kk_uv_timer_timer_start(t, time, 0, cb, _ctx); + return kk_uv_timer__timer_box(t, _ctx); +} + +kk_unit_t kk_clear_timeout(kk_box_t boxed_timer, kk_context_t* _ctx) { + kk_uv_timer__timer timer = kk_uv_timer__timer_unbox(boxed_timer, KK_OWNED, _ctx); + kk_uv_timer_timer_stop(timer, _ctx); + return kk_Unit; +} diff --git a/lib/uv/inline/event-loop.cs b/lib/uv/inline/event-loop.cs new file mode 100644 index 000000000..b92a492b9 --- /dev/null +++ b/lib/uv/inline/event-loop.cs @@ -0,0 +1,53 @@ +/*--------------------------------------------------------------------------- + Copyright 2017-2021, Microsoft Research, Daan Leijen. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ +using System.Threading; + +static class _Async +{ + public class ThreadTimer : IDisposable { + private Primitive.EventloopEntry entry; + private Timer timer; + + public ThreadTimer( Fun0 cb, int ms ) { + entry = Primitive.GetEventloopEntry(); + if (ms <= 0) { + timer = null; + entry.Post(() => { cb.Apply(); }); + } + else { + timer = new Timer( (object state0) => { + if (entry != null) entry.Post(() => { cb.Apply(); }); + }, null, ms, Timeout.Infinite ); + } + } + + public void Dispose() { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) { + if (entry != null) { + entry.Dispose(); + entry = null; + } + if (timer != null) { + timer.Dispose(); + timer = null; + } + } + } + + public static ThreadTimer SetTimeout( Fun0 cb, int ms ) { + return new ThreadTimer(cb,ms); + } + + public static void ClearTimeout( object obj ) { + IDisposable d = obj as IDisposable; + if (d != null) d.Dispose(); + } +} diff --git a/lib/uv/inline/file.c b/lib/uv/inline/file.c new file mode 100644 index 000000000..3a8e17d5d --- /dev/null +++ b/lib/uv/inline/file.c @@ -0,0 +1,835 @@ + +#include "kklib/box.h" +#include "uv_event_dash_loop.h" + + +void kk_free_fs(void* p, kk_block_t* block, kk_context_t* _ctx) { + uv_fs_t* req = (uv_fs_t*)p; + uv_fs_req_cleanup(req); + kk_info_message("Freeing fs request\n", kk_context()); + kk_free(p, _ctx); +} + +#define kk_new_fs_req_cb(req, cb) kk_new_req_cb(uv_fs_t, req, cb) +#define kk_fs_to_uv(req) kk_owned_handle_to_uv_handle(uv_fs_t, req) +#define kk_uv_to_fs(req) uv_handle_to_owned_kk_handle(req, kk_free_fs, file, fs_req) +#define kk_uv_to_fs_box(req) uv_handle_to_owned_kk_handle_box(req, kk_free_fs, file, fs_req) + +// Need variants of the check macros that cleanup the request before returning +// Sometimes the return value is a file descriptor which is why this is a < UV_OK check instead of == UV_OK +#define kk_uv_fs_check_return(req, err, result) \ + kk_std_core_exn__error internal_ret; \ + if (err < UV_OK) { \ + internal_ret = kk_async_error_from_errno(err, kk_context()); \ + } else { \ + internal_ret = kk_std_core_exn__new_Ok(result, kk_context()); \ + } \ + uv_fs_req_cleanup(req); \ + return internal_ret; +#define kk_uv_fs_check(req, err) kk_uv_fs_check_return(req, err, kk_unit_box(kk_Unit)) + +#define kk_callback_result(req, cb, res) \ + kk_context_t* _ctx = kk_get_context(); \ + kk_function_t cb = kk_function_from_ptr(req->data, _ctx); \ + ssize_t res = req->result; + +static kk_uv_file__uv_fs_req kk_uv_fs_init(kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + return kk_uv_to_fs(fs_req); +} + +static kk_unit_t kk_uv_fs_req_cleanup(kk_uv_file__uv_fs_req req, kk_context_t* _ctx) { + uv_fs_req_cleanup(kk_fs_to_uv(req)); + return kk_Unit; +} + +static void kk_std_os_fs_unit_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, req->result) + } else { + kk_uv_okay_callback(callback, kk_unit_box(kk_Unit)) + } +} + +static kk_unit_t kk_uv_fs_close(kk_uv_file__uv_file file, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_close(uvloop(), fs_req, (uv_file)(file.internal), kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_close_sync(kk_uv_file__uv_file file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_uv_check_return(uv_fs_close(uvloop(), &fs_req, (uv_file)(file.internal), NULL), kk_unit_box(kk_Unit)) +} + +static void kk_std_os_file_open_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, result) + } else { + kk_uv_okay_callback(callback, kk_uv_file__uv_file_box(kk_uv_file__new_Uv_file((intptr_t)result, _ctx), _ctx)) + } +} + +static kk_unit_t kk_uv_fs_open(kk_string_t path, int32_t flags, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_open(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), flags, mode, kk_std_os_file_open_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_open_sync(kk_string_t path, int32_t flags, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int fd = uv_fs_open(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), flags, mode, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, fd, kk_uv_file__uv_file_box(kk_uv_file__new_Uv_file((intptr_t)fd, _ctx), _ctx)) +} + +typedef struct kk_uv_buff_callback_s { + kk_function_t callback; + kk_bytes_raw_t bytes; +} kk_uv_buff_callback_t; + +static void kk_free_bytes(void* p, kk_block_t* bytes, kk_context_t* _ctx) { + kk_free(((kk_bytes_raw_t)bytes)->cbuf, _ctx); +} + +static inline kk_uv_buff_callback_t* kk_new_uv_buff_callback(kk_function_t cb, uv_handle_t* handle, int32_t num_bytes, kk_context_t* _ctx) { + kk_uv_buff_callback_t* c = kk_malloc(sizeof(kk_uv_buff_callback_t), _ctx); + c->callback = cb; + uint8_t* buff = kk_malloc(num_bytes, _ctx); + c->bytes = kk_datatype_as(kk_bytes_raw_t, kk_bytes_alloc_raw_len((kk_ssize_t)num_bytes, buff, false, _ctx), _ctx); + c->bytes->free = &kk_free_bytes; + handle->data = c; + return c; +} + +static void kk_std_os_file_buff_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_buff_callback_t* wrapper = (kk_uv_buff_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + kk_bytes_raw_t bytes = wrapper->bytes; + kk_bytes_t bts = kk_datatype_from_base(&bytes->_base, _ctx); + // kk_info_message("Clength %d", req->result); + ssize_t result = req->result; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_bytes_drop(bts, _ctx); + kk_uv_error_callback(callback, result) + } else { + kk_bytes_t btsadj = kk_bytes_adjust_length(bts, (kk_ssize_t)result + 1, _ctx); + kk_bytes_set(btsadj, (uint64_t)result, (int8_t)'\0', _ctx); + // TODO?: Maybe would be better to not add a terminating null byte, and fix it when converting to a string instead? + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_bytes_box(btsadj), kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + kk_uv_okay_callback(callback, tupleboxed) + } +} + +static kk_unit_t kk_uv_fs_read(kk_uv_file__uv_file file, int32_t num_bytes, int32_t offset, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_uv_buff_callback_t* wrapper = kk_new_uv_buff_callback(cb, (uv_handle_t*)fs_req, num_bytes, _ctx); + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t)*1, _ctx); + uv_buffs[0].base = (char*)wrapper->bytes->cbuf; + uv_buffs[0].len = wrapper->bytes->clength; + uv_fs_read(uvloop(), fs_req, (uv_file)file.internal, uv_buffs, 1, offset, kk_std_os_file_buff_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_read_sync(kk_uv_file__uv_file file, int32_t num_bytes, int32_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t uv_buffs[1] = {0}; + uint8_t* buff = kk_malloc(num_bytes, _ctx); + kk_bytes_raw_t bytes = kk_datatype_as(kk_bytes_raw_t, kk_bytes_alloc_raw_len((kk_ssize_t)num_bytes, buff, false, _ctx), _ctx); + bytes->free = &kk_free_bytes; + uv_buffs[0].base = (char*)bytes->cbuf; + uv_buffs[0].len = bytes->clength; + ssize_t result = uv_fs_read(uvloop(), &fs_req, (uv_file)file.internal, uv_buffs, 1, offset, NULL); + if (result < 0) { + kk_bytes_drop(kk_datatype_from_base(&bytes->_base, _ctx), _ctx); + return kk_async_error_from_errno(result, _ctx); + } else { + kk_bytes_t btsadj = kk_bytes_adjust_length(kk_datatype_from_base(&bytes->_base, _ctx), (kk_ssize_t)result, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_bytes_box(btsadj), kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + return kk_std_core_exn__new_Ok(tupleboxed, _ctx); + } +} + +static kk_unit_t kk_uv_fs_unlink(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_unlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_unlink_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_unlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static void kk_std_os_fs_write_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, result) + } else { + kk_uv_okay_callback(callback, kk_integer_box(kk_integer_from_ssize_t(result, _ctx), _ctx)) + } +} + +static kk_unit_t kk_uv_fs_write(kk_uv_file__uv_file file, kk_bytes_t bytes, int64_t offset, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_buf_t* uv_buffs = kk_bytes_to_uv_buffs(bytes, _ctx); + uv_fs_write(uvloop(), fs_req, (uv_file)file.internal, uv_buffs, 1, offset, kk_std_os_fs_write_cb); + kk_bytes_drop(bytes, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_write_sync(kk_uv_file__uv_file file, kk_bytes_t bytes, int64_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t* uv_buffs = kk_bytes_to_uv_buffs(bytes, _ctx); + int64_t result = uv_fs_write(uvloop(), &fs_req, (uv_file)file.internal, uv_buffs, 1, offset, NULL); + kk_bytes_drop(bytes, _ctx); + kk_uv_fs_check_return(&fs_req, result, kk_integer_box(kk_integer_from_int64(result, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_mkdir(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_mkdir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_mkdir_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_mkdir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static void kk_std_os_fs_mkdtemp_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + if (result < 0) { + uv_fs_req_cleanup(req); + kk_uv_error_callback(callback, result) + } else { + kk_string_t str = kk_string_alloc_raw((const char*) req->path, true, _ctx); + uv_fs_req_cleanup(req); + kk_uv_okay_callback(callback, kk_string_box(str)) + } +} + +static kk_unit_t kk_uv_fs_mkdtemp(kk_string_t tpl, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_mkdtemp(uvloop(), fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), kk_std_os_fs_mkdtemp_cb); + kk_string_drop(tpl, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_mkdtemp_sync(kk_string_t tpl, kk_context_t* _ctx) { + kk_ssize_t len; + uv_fs_t fs_req = {0}; + int status = uv_fs_mkdtemp(uvloop(), &fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), NULL); + kk_string_drop(tpl, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_string_box(kk_string_alloc_from_utf8((const char*) fs_req.path, _ctx))) +} + +static void kk_std_os_fs_mkstemp_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + if (result < 0) { + uv_fs_req_cleanup(req); + kk_uv_error_callback(callback, result) + } else { + kk_string_t str = kk_string_alloc_raw((const char*) req->path, true, _ctx); + uv_fs_req_cleanup(req); + kk_uv_file__uv_file file = kk_uv_file__new_Uv_file((intptr_t)result, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_string_box(str), kk_uv_file__uv_file_box(file, _ctx), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + kk_uv_okay_callback(callback, tupleboxed) + } +} + +static kk_unit_t kk_uv_fs_mkstemp(kk_string_t tpl, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_mkstemp(uvloop(), fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), kk_std_os_fs_mkdtemp_cb); + kk_string_drop(tpl, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_mkstemp_sync(kk_string_t tpl, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_mkstemp(uvloop(), &fs_req, kk_string_cbuf_borrow(tpl, &len, _ctx), NULL); + kk_string_drop(tpl, _ctx); + if (status < 0) { + return kk_async_error_from_errno(status, _ctx); + } else { + kk_string_t str = kk_string_alloc_raw((const char*) fs_req.path, true, _ctx); + kk_uv_file__uv_file file = kk_uv_file__new_Uv_file((intptr_t)status, _ctx); + kk_std_core_types__tuple2 tuple = kk_std_core_types__new_Tuple2(kk_uv_file__uv_file_box(file, _ctx), kk_string_box(str), _ctx); /*(1004, 1005)*/ + kk_box_t tupleboxed = kk_std_core_types__tuple2_box(tuple, _ctx); + return kk_std_core_exn__new_Ok(tupleboxed, _ctx); + } +} + +static kk_unit_t kk_uv_fs_rmdir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_rmdir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_rmdir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_rmdir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +void kk_std_os_fs_opendir_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_dir_t* dir = (uv_dir_t*)req->ptr; + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, result) + } else { + kk_uv_file__uv_dir d = kk_uv_file__new_Uv_dir((intptr_t)dir, _ctx); + kk_uv_okay_callback(callback, kk_uv_file__uv_dir_box(d, _ctx)) + } +} + +static kk_unit_t kk_uv_fs_opendir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_opendir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_opendir_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_opendir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_opendir(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_uv_file__uv_dir_box(kk_uv_file__new_Uv_dir((intptr_t)fs_req.ptr, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_closedir(kk_uv_file__uv_dir dir, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_closedir(uvloop(), fs_req, (uv_dir_t*)dir.internal, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_closedir_sync(kk_uv_file__uv_dir dir, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_uv_fs_check_return(&fs_req, uv_fs_closedir(uvloop(), &fs_req, (uv_dir_t*)dir.internal, NULL), kk_unit_box(kk_Unit)) +} + +typedef struct kk_uv_dir_callback_s { + kk_function_t callback; + uv_dir_t* uvdir; +} kk_uv_dir_callback_t; + + +static inline kk_uv_dir_callback_t* kk_new_uv_dir_callback(kk_function_t cb, uv_handle_t* handle, uv_dir_t* uvdir, int32_t num_entries, kk_context_t* _ctx) { + kk_uv_dir_callback_t* c = kk_malloc(sizeof(kk_uv_dir_callback_t), _ctx); + c->callback = cb; + uvdir->dirents = kk_malloc(sizeof(uv_dirent_t)*500, _ctx); + uvdir->nentries = 500; + handle->data = c; + return c; +} + +kk_box_t kk_uv_dirent_to_dirent(uv_dirent_t* dirent, kk_box_t* loc, kk_context_t* _ctx) { + if (loc == NULL) { + loc = kk_malloc(sizeof(kk_box_t*), _ctx); + } + kk_string_t name = kk_string_alloc_raw((const char*) dirent->name, true, _ctx); + kk_uv_file__dirent_type type; + switch (dirent->type) { + case UV_DIRENT_FILE: type = kk_uv_file__new_FILE(_ctx); break; + case UV_DIRENT_DIR: type = kk_uv_file__new_DIR(_ctx); break; + case UV_DIRENT_LINK: type = kk_uv_file__new_LINK(_ctx); break; + case UV_DIRENT_FIFO: type = kk_uv_file__new_FIFO(_ctx); break; + case UV_DIRENT_SOCKET: type = kk_uv_file__new_SOCKET(_ctx); break; + case UV_DIRENT_CHAR: type = kk_uv_file__new_CHAR(_ctx); break; + case UV_DIRENT_BLOCK: type = kk_uv_file__new_BLOCK(_ctx); break; + default: type = kk_uv_file__new_UNKNOWN__DIRECTORY__ENTRY(_ctx); break; + } + kk_box_t box = kk_uv_file__dirent_box(kk_uv_file__new_Dirent(name, type, _ctx), _ctx); + *loc = box; + return box; +} + +kk_vector_t kk_uv_dirents_to_vec(uv_dir_t* uvdir, kk_ssize_t num_entries, kk_context_t* _ctx) { + kk_box_t* dirs; + kk_vector_t dirents = kk_vector_alloc_uninit(num_entries, &dirs, _ctx); + for (kk_ssize_t i = 0; i < num_entries; i++){ + kk_uv_dirent_to_dirent(&(uvdir->dirents[i]), &dirs[i], _ctx); + } + return dirents; +} + +void kk_std_os_fs_readdir_cb(uv_fs_t* req) { + kk_context_t* _ctx = kk_get_context(); + kk_uv_dir_callback_t* wrapper = (kk_uv_dir_callback_t*)req->data; + kk_function_t callback = wrapper->callback; + ssize_t result = req->result; + uv_dir_t* uvdir = wrapper->uvdir; + kk_free(wrapper, _ctx); + uv_fs_req_cleanup(req); + if (result < 0) { + kk_free(uvdir->dirents, _ctx); + kk_uv_error_callback(callback, result) + } else { + kk_vector_t dirents = kk_uv_dirents_to_vec(uvdir, (kk_ssize_t)result, _ctx); + kk_free(uvdir->dirents, _ctx); + kk_uv_okay_callback(callback, kk_vector_box(dirents, _ctx)) + } +} + +static kk_unit_t kk_uv_fs_readdir(kk_uv_file__uv_dir dir, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + // Read up to 500 entries in the directory + kk_uv_dir_callback_t* wrapper = kk_new_uv_dir_callback(cb, (uv_handle_t*)fs_req, (uv_dir_t*) dir.internal, 500, _ctx); + uv_fs_readdir(uvloop(), fs_req, wrapper->uvdir, kk_std_os_fs_readdir_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_readdir_sync(kk_uv_file__uv_dir dir, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + uv_dir_t* uvdir = (uv_dir_t*) dir.internal; + uvdir->dirents = kk_malloc(sizeof(uv_dirent_t)*500, _ctx); + uvdir->nentries = 500; + int status = uv_fs_readdir(uvloop(), fs_req, uvdir, NULL); + if (status < 0) { + kk_free(uvdir->dirents, _ctx); + return kk_async_error_from_errno(status, _ctx); + } else { + kk_vector_t dirents = kk_uv_dirents_to_vec(uvdir, (kk_ssize_t)status, _ctx); + kk_free(uvdir->dirents, _ctx); + return kk_std_core_exn__new_Ok(kk_vector_box(dirents, _ctx), _ctx); + } +} + +void kk_std_os_fs_scandir_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + if (result < 0){ + uv_fs_req_cleanup(req); + kk_uv_error_callback(callback, result) + } else { // TODO: This is not correct, we don't want a new reference counted box here + kk_uv_okay_callback(callback, kk_uv_to_fs_box(req)) + } +} + +static kk_unit_t kk_uv_fs_scandir(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_scandir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), 0, kk_std_os_fs_scandir_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_scandir_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + kk_ssize_t len; + int status = uv_fs_scandir(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), 0, NULL); + kk_string_drop(path, _ctx); + kk_uv_check_return(status, kk_uv_to_fs_box(fs_req)) +} + +static kk_std_core_exn__error kk_uv_fs_scandir_next(kk_uv_file__uv_fs_req req, kk_context_t* _ctx) { + uv_dirent_t ent = {0}; + int status = uv_fs_scandir_next(kk_fs_to_uv(req), &ent); + kk_uv_check_return(status, kk_uv_dirent_to_dirent(&ent, NULL, _ctx)) +} + +static void kk_std_os_fs_stat_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_free(stat, _ctx); + kk_uv_error_callback(callback, result) + } else { + kk_box_t s = kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(&req->statbuf, _ctx), _ctx); + kk_uv_okay_callback(callback, s) + } +} + +static kk_unit_t kk_uv_fs_stat(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_stat(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_stat_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_stat_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_stat(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_fstat(kk_uv_file__uv_file file, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_fstat(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_stat_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_fstat_sync(kk_uv_file__uv_file file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fstat(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_uv_fs_check_return(&fs_req, status, kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_lstat(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_lstat(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_stat_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_lstat_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lstat(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(&fs_req.statbuf, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_rename(kk_string_t path, kk_string_t new_path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + kk_ssize_t new_len; + uv_fs_rename(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_rename_sync(kk_string_t path, kk_string_t new_path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_rename(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_fsync(kk_uv_file__uv_file file, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_fsync(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_fsync_sync(kk_uv_file__uv_file file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fsync(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_fdatasync(kk_uv_file__uv_file file, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_fdatasync(uvloop(), fs_req, (uv_file)file.internal, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_fdatasync_sync(kk_uv_file__uv_file file, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fdatasync(uvloop(), &fs_req, (uv_file)file.internal, NULL); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_ftruncate(kk_uv_file__uv_file file, int64_t offset, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_ftruncate(uvloop(), fs_req, (uv_file)file.internal, offset, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_ftruncate_sync(kk_uv_file__uv_file file, int64_t offset, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_ftruncate(uvloop(), &fs_req, (uv_file)file.internal, offset, NULL); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_copyfile(kk_string_t path, kk_string_t new_path, int32_t flags, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + kk_ssize_t new_len; + uv_fs_copyfile(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_copyfile_sync(kk_string_t path, kk_string_t new_path, int32_t flags, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_copyfile(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static void kk_std_os_fs_int_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, result) + } else { + kk_uv_okay_callback(callback, kk_integer_box(kk_integer_from_int64(result, _ctx), _ctx)) + } +} + +static kk_unit_t kk_uv_fs_sendfile(kk_uv_file__uv_file out_fd, kk_uv_file__uv_file in_fd, int64_t in_offset, kk_ssize_t length, kk_function_t cb, kk_context_t* _ctx) { + uv_fs_t* fs_req = kk_malloc(sizeof(uv_fs_t), _ctx); + uv_buf_t buf = uv_buf_init(NULL, 0); + fs_req->data = kk_function_as_ptr(cb, _ctx); + uv_fs_sendfile(uvloop(), fs_req, (uv_file)out_fd.internal, (uv_file)in_fd.internal, in_offset, (size_t)length, kk_std_os_fs_int_cb); + kk_uv_file__uv_file_drop(out_fd, _ctx); + kk_uv_file__uv_file_drop(in_fd, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_sendfile_sync(kk_uv_file__uv_file out_fd, kk_uv_file__uv_file in_fd, int64_t in_offset, kk_ssize_t length, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + uv_buf_t buf = uv_buf_init(NULL, 0); + int status = uv_fs_sendfile(uvloop(), &fs_req, (uv_file)out_fd.internal, (uv_file)in_fd.internal, in_offset, (size_t)length, NULL); + kk_uv_file__uv_file_drop(out_fd, _ctx); + kk_uv_file__uv_file_drop(in_fd, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_integer_box(kk_integer_from_int64(fs_req.result, _ctx), _ctx)) +} + +static kk_unit_t kk_uv_fs_access(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_access(uvloop(), fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_access_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_access(uvloop(), &fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_chmod(kk_string_t path, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_chmod(uvloop(), fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_chmod_sync(kk_string_t path, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_chmod(uvloop(), &fs_req, kk_string_cbuf_borrow(path, NULL, _ctx), mode, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_fchmod(kk_uv_file__uv_file file, int32_t mode, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_fchmod(uvloop(), fs_req, (uv_file)file.internal, mode, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_fchmod_sync(kk_uv_file__uv_file file, int32_t mode, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fchmod(uvloop(), &fs_req, (uv_file)file.internal, mode, NULL); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_utime(kk_string_t path, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_utime(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_utime_sync(kk_string_t path, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_utime(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_futime(kk_uv_file__uv_file file, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_futime(uvloop(), fs_req, (uv_file)file.internal, atime, mtime, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_futime_sync(kk_uv_file__uv_file file, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_uv_fs_check_return(&fs_req, uv_fs_futime(uvloop(), &fs_req, (uv_file)file.internal, atime, mtime, NULL), kk_unit_box(kk_Unit)) +} + +static kk_unit_t kk_uv_fs_lutime(kk_string_t path, double atime, double mtime, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_lutime(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_lutime_sync(kk_string_t path, double atime, double mtime, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lutime(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), atime, mtime, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_link(kk_string_t path, kk_string_t new_path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + kk_ssize_t new_len; + uv_fs_link(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_link_sync(kk_string_t path, kk_string_t new_path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_link(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_symlink(kk_string_t path, kk_string_t new_path, int32_t flags, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + kk_ssize_t new_len; + uv_fs_symlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_symlink_sync(kk_string_t path, kk_string_t new_path, int32_t flags, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + kk_ssize_t new_len; + int status = uv_fs_symlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_string_cbuf_borrow(new_path, &new_len, _ctx), flags, NULL); + kk_string_drop(path, _ctx); + kk_string_drop(new_path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +void kk_std_os_fs_string_cb(uv_fs_t* req) { + kk_callback_result(req, callback, result) + uv_fs_req_cleanup(req); + if (result < 0) { + kk_uv_error_callback(callback, result) + } else { + kk_string_t s = kk_string_alloc_raw((const char*)req->ptr, true, _ctx); + kk_uv_okay_callback(callback, kk_string_box(s)) + } +} + +static kk_unit_t kk_uv_fs_readlink(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_readlink(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_string_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_readlink_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_readlink(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_string_box(kk_string_alloc_raw((const char*)fs_req.ptr, true, _ctx))) +} + +static kk_unit_t kk_uv_fs_realpath(kk_string_t path, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_realpath(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), kk_std_os_fs_string_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_realpath_sync(kk_string_t path, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_realpath(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check_return(&fs_req, status, kk_string_box(kk_string_alloc_raw((const char*)fs_req.ptr, true, _ctx))) +} + +static kk_unit_t kk_uv_fs_chown(kk_string_t path, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_chown(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_chown_sync(kk_string_t path, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_chown(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_fchown(kk_uv_file__uv_file file, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + kk_new_fs_req_cb(fs_req, cb) + uv_fs_fchown(uvloop(), fs_req, (uv_file)file.internal, uid, gid, kk_std_os_fs_unit_cb); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_fchown_sync(kk_uv_file__uv_file file, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + int status = uv_fs_fchown(uvloop(), &fs_req, (uv_file)file.internal, uid, gid, NULL); + kk_uv_fs_check(&fs_req, status) +} + +static kk_unit_t kk_uv_fs_lchown(kk_string_t path, int32_t uid, int32_t gid, kk_function_t cb, kk_context_t* _ctx) { + kk_ssize_t len; + kk_new_fs_req_cb(fs_req, cb) + uv_fs_lchown(uvloop(), fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, kk_std_os_fs_unit_cb); + kk_string_drop(path, _ctx); + return kk_Unit; +} + +static kk_std_core_exn__error kk_uv_fs_lchown_sync(kk_string_t path, int32_t uid, int32_t gid, kk_context_t* _ctx) { + uv_fs_t fs_req = {0}; + kk_ssize_t len; + int status = uv_fs_lchown(uvloop(), &fs_req, kk_string_cbuf_borrow(path, &len, _ctx), uid, gid, NULL); + kk_string_drop(path, _ctx); + kk_uv_fs_check(&fs_req, status) +} diff --git a/lib/uv/inline/file.h b/lib/uv/inline/file.h new file mode 100644 index 000000000..ed26720cf --- /dev/null +++ b/lib/uv/inline/file.h @@ -0,0 +1,23 @@ + +static kk_uv_file__fstat kk_uv_stat_from_uv_stat(uv_stat_t* uvstat, kk_context_t* _ctx) { + return kk_uv_file__new_Fstat( + kk_reuse_null, + 0, // cpath + uvstat->st_dev, + uvstat->st_mode, + uvstat->st_nlink, + uvstat->st_uid, + uvstat->st_gid, + uvstat->st_rdev, + uvstat->st_ino, + uvstat->st_size, + uvstat->st_blksize, + uvstat->st_blocks, + uvstat->st_flags, + kk_uv_file__new_Timespec(uvstat->st_atim.tv_sec, uvstat->st_atim.tv_nsec, _ctx), + kk_uv_file__new_Timespec(uvstat->st_mtim.tv_sec, uvstat->st_mtim.tv_nsec, _ctx), + kk_uv_file__new_Timespec(uvstat->st_ctim.tv_sec, uvstat->st_ctim.tv_nsec, _ctx), + kk_uv_file__new_Timespec(uvstat->st_birthtim.tv_sec, uvstat->st_birthtim.tv_nsec, _ctx), + _ctx + ); +} \ No newline at end of file diff --git a/lib/uv/inline/fs-event.c b/lib/uv/inline/fs-event.c new file mode 100644 index 000000000..377e97b6f --- /dev/null +++ b/lib/uv/inline/fs-event.c @@ -0,0 +1,35 @@ +void kk_uv_fs_event_callback(uv_fs_event_t* handle, const char *filename, int events, int status) { + kk_uv_hnd_get_callback(handle, hnd, callback) + kk_function_dup(callback, kk_context()); + if (status < 0) { + // TODO: Does the callback really need duping for this condition? + kk_uv_error_callback(callback, status) + } else { + kk_uv_okay_callback(callback, kk_std_core_types__tuple2_box(kk_std_core_types__new_Tuple2(kk_string_box(kk_string_alloc_from_utf8(filename, _ctx)), kk_int32_box(events, _ctx), _ctx), _ctx)) + } +} + +kk_std_core_exn__error kk_uv_fs_event_init(kk_context_t* _ctx) { + uv_fs_event_t* fs_event = kk_malloc(sizeof(uv_fs_event_t), _ctx); + int status = uv_fs_event_init(uvloop(), fs_event); + kk_uv_check_return_err_drops(status, uv_handle_to_owned_kk_handle_box(fs_event, kk_handle_free, fs_dash_event, fs_event), {kk_free(fs_event, _ctx);}) +} + +kk_std_core_exn__error kk_uv_fs_event_start(kk_uv_fs_dash_event__uv_fs_event handle, kk_string_t path, int32_t interval, kk_function_t callback, kk_context_t* _ctx) { + kk_set_hnd_cb(uv_fs_event_t, handle, fs_event, callback) + kk_ssize_t len; + kk_uv_check_err_drops(uv_fs_event_start(fs_event, kk_uv_fs_event_callback, kk_string_cbuf_borrow(path, &len, _ctx), interval), {kk_function_drop(callback, _ctx);}) +} + +kk_std_core_exn__error kk_uv_fs_event_stop(kk_uv_fs_dash_event__uv_fs_event handle, kk_context_t* _ctx) { + uv_fs_event_t* hnd = kk_owned_handle_to_uv_handle(uv_fs_event_t, handle); + kk_uv_check_ok_drops(uv_fs_event_stop(hnd), {kk_box_drop(handle.internal, _ctx);}) +} + +kk_std_core_exn__error kk_uv_fs_event_getpath(kk_uv_fs_dash_event__uv_fs_event handle, kk_context_t* _ctx) { + uv_fs_event_t* fs_event = kk_owned_handle_to_uv_handle(uv_fs_event_t, handle); + size_t len = 2048; + char* path = kk_malloc(len, _ctx); + int status = uv_fs_event_getpath(fs_event, path, &len); + kk_uv_check_return_err_drops(status, kk_string_box(kk_string_alloc_raw_len(len, (const char*)path, true, _ctx)), {kk_free(path, kk_context());}) +} \ No newline at end of file diff --git a/lib/uv/inline/net.c b/lib/uv/inline/net.c new file mode 100644 index 000000000..8ddeb96bb --- /dev/null +++ b/lib/uv/inline/net.c @@ -0,0 +1,162 @@ +// #include "std_os_net.h" +#include "uv_event_dash_loop.h" + +static struct sockaddr* to_sockaddr(kk_uv_net__sock_addr addr, kk_context_t* _ctx){ + int p; + if (kk_std_core_types__is_Just(addr.port, _ctx)){ + p = (int)kk_int32_unbox(addr.port._cons.Just.value, KK_BORROWED, _ctx); + } else { + p = 80; + } + if (kk_uv_net__is_AF__INET(addr.family, _ctx)){ + struct sockaddr_in* addrIn = kk_malloc(sizeof(struct sockaddr_in), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip4_addr(str, p, addrIn); + return (struct sockaddr*)addrIn; + } else if (kk_uv_net__is_AF__INET6(addr.family, _ctx)){ + struct sockaddr_in6* addrIn6 = kk_malloc(sizeof(struct sockaddr_in6), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip6_addr(str, p, addrIn6); + return (struct sockaddr*)addrIn6; + } else { + // family = AF_UNSPEC; + // Assume INET + struct sockaddr_in* addrIn = kk_malloc(sizeof(struct sockaddr_in), _ctx); + kk_ssize_t len; + const char* str = kk_string_cbuf_borrow(addr.data, &len, _ctx); + uv_ip4_addr(str, p, addrIn); + return (struct sockaddr*)addrIn; + // TODO: return error type error? + } +} + +static kk_uv_net__sock_addr to_kk_sockaddr(struct sockaddr* addr, kk_context_t* _ctx){ + enum kk_uv_net__net_family_e family = kk_uv_net_AF__ANY; + + kk_std_core_types__maybe portMaybe; + if (addr->sa_family == AF_INET){ + family = kk_uv_net_AF__INET; + portMaybe = kk_std_core_types__new_Just(kk_int32_box(((struct sockaddr_in*)addr)->sin_port, _ctx), _ctx); + } else if (addr->sa_family == AF_INET6){ + family = kk_uv_net_AF__INET6; + portMaybe = kk_std_core_types__new_Just(kk_int32_box(((struct sockaddr_in6*)addr)->sin6_port, _ctx), _ctx); + } else { + portMaybe = kk_std_core_types__new_Nothing(_ctx); + } + char ip[50]= ""; + inet_ntop(addr->sa_family, &addr->sa_data[2], ip, sizeof(ip)); + + kk_string_t ipStr = kk_string_alloc_from_qutf8(ip, _ctx); + return kk_uv_net__new_Sock_addr(family, ipStr, portMaybe, _ctx); +} + +static void kk_addrinfo_cb(uv_getaddrinfo_t* req, int status, struct addrinfo* res){ + kk_context_t* _ctx = kk_get_context(); + kk_function_t callback = kk_function_from_ptr(req->data, _ctx); + + if (status < UV_OK) { + kk_info_message("Addr info callback returned error code %d %s\n", status, uv_strerror(status)); + kk_function_call(void, (kk_function_t, kk_std_core_types__list, kk_context_t*), callback, (callback, kk_std_core_types__new_Nil(_ctx), _ctx), _ctx); + uv_freeaddrinfo(res); + return; + } + + kk_std_core_types__list list = kk_std_core_types__new_Nil(_ctx); + for (struct addrinfo* p = res; p != NULL; p = p->ai_next) { + enum kk_uv_net__net_family_e family = kk_uv_net_AF__ANY; + if (p->ai_family == AF_INET){ + family = kk_uv_net_AF__INET; + } else if (p->ai_family == AF_INET6){ + family = kk_uv_net_AF__INET6; + } + enum kk_uv_net__sock_type_e sockType = kk_uv_net_SOCK__ANY; + if (p->ai_socktype == SOCK_DGRAM){ + sockType = kk_uv_net_SOCK__DGRAM; + } else if (p->ai_socktype == SOCK_STREAM){ + sockType = kk_uv_net_SOCK__STREAM; + } + kk_string_t canonName = kk_string_empty(); + + if (p->ai_canonname) { + canonName = kk_string_alloc_from_qutf8(p->ai_canonname, _ctx); + } + + kk_uv_net__sock_addr addr = to_kk_sockaddr(p->ai_addr, _ctx); + kk_uv_net__addr_info addrInfo = kk_uv_net__new_Addr_info(kk_reuse_null, 0, p->ai_flags, family, sockType, p->ai_protocol, addr, canonName, _ctx); + kk_std_core_types__list head = kk_std_core_types__new_Cons(kk_reuse_null, 0, kk_uv_net__addr_info_box(addrInfo, _ctx), list, _ctx); + list = head; + } + uv_freeaddrinfo(res); + kk_function_call(void, (kk_function_t, kk_std_core_types__list, kk_context_t*), callback, (callback, list, _ctx), _ctx); +} + + +static kk_std_core_exn__error kk_get_addrinfo(kk_string_t node, kk_string_t service, kk_std_core_types__maybe hints, kk_function_t callback, kk_context_t* _ctx) { + kk_new_req_cb(uv_getaddrinfo_t, req, callback) + const char* nodeChars = kk_string_cbuf_borrow(node, NULL, _ctx); + const char* serviceChars = kk_string_cbuf_borrow(service, NULL, _ctx); + int result = uv_getaddrinfo(uvloop(), req, &kk_addrinfo_cb, nodeChars, serviceChars, NULL); + kk_uv_check_err_drops(result, {kk_function_drop(callback, _ctx); kk_free(req, _ctx);}) +} + +static kk_std_core_exn__error kk_uv_tcp_init(kk_context_t* _ctx) { + uv_tcp_t* tcp = kk_malloc(sizeof(uv_tcp_t), _ctx); + int status = uv_tcp_init(uvloop(), tcp); + kk_uv_check_return_err_drops(status, uv_handle_to_owned_kk_handle_box(tcp, kk_handle_free, net, tcp), {kk_free(tcp, _ctx);}) +} + +static kk_std_core_exn__error kk_uv_tcp_init_ex(int32_t flags, kk_context_t* _ctx) { + uv_tcp_t* tcp = kk_malloc(sizeof(uv_tcp_t), _ctx); + int status = uv_tcp_init_ex(uvloop(), tcp, (unsigned int)flags); + kk_uv_check_return_err_drops(status, uv_handle_to_owned_kk_handle_box(tcp, kk_handle_free, net, tcp), {kk_free(tcp, _ctx);}) +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_open(kk_uv_net__uv_tcp handle, kk_uv_net__uv_os_sock sock, kk_context_t* _ctx) { + return kk_uv_utils_int_fs_status_code(uv_tcp_open(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), *((uv_os_sock_t*)sock.internal)), _ctx); +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_nodelay(kk_uv_net__uv_tcp handle, bool enable, kk_context_t* _ctx) { + return kk_uv_utils_int_fs_status_code(uv_tcp_nodelay(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), enable), _ctx); +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_keepalive(kk_uv_net__uv_tcp handle, bool enable, uint32_t delay, kk_context_t* _ctx) { + return kk_uv_utils_int_fs_status_code(uv_tcp_keepalive(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), enable, delay), _ctx); +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_simultaneous_accepts(kk_uv_net__uv_tcp handle, bool enable, kk_context_t* _ctx) { + return kk_uv_utils_int_fs_status_code(uv_tcp_simultaneous_accepts(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), enable), _ctx); +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_bind(kk_uv_net__uv_tcp handle, kk_uv_net__sock_addr addr, uint32_t flags, kk_context_t* _ctx) { + struct sockaddr* sockAddr = to_sockaddr(addr, _ctx); + return kk_uv_utils_int_fs_status_code(uv_tcp_bind(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), sockAddr, flags), _ctx); +} + +static kk_std_core_exn__error kk_uv_tcp_getsockname(kk_uv_net__uv_tcp handle, kk_context_t* _ctx) { + struct sockaddr_storage sockAddr; + int len; + int status = uv_tcp_getsockname(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), (struct sockaddr*)&sockAddr, &len); + kk_uv_check_return(status, kk_uv_net__sock_addr_box(to_kk_sockaddr((struct sockaddr*)&sockAddr, _ctx), _ctx)) +} + +static kk_std_core_exn__error kk_uv_tcp_getpeername(kk_uv_net__uv_tcp handle, kk_context_t* _ctx) { + struct sockaddr_storage sockAddr; + int len; + int status = uv_tcp_getpeername(kk_owned_handle_to_uv_handle(uv_tcp_t, handle), (struct sockaddr*)&sockAddr, &len); + kk_uv_check_return(status, kk_uv_net__sock_addr_box(to_kk_sockaddr((struct sockaddr*)&sockAddr, _ctx), _ctx)) +} + + +static void kk_uv_tcp_connect_callback(uv_connect_t* req, int status) { + kk_uv_get_callback(req, callback) + kk_function_call(void, (kk_function_t, kk_uv_utils__uv_status_code, kk_context_t*), callback, (callback, kk_uv_utils_int_fs_status_code(status, _ctx), _ctx), _ctx); + kk_free(req, _ctx); +} + +static kk_uv_utils__uv_status_code kk_uv_tcp_connect(kk_uv_net__uv_tcp handle, kk_uv_net__sock_addr addr, kk_function_t callback, kk_context_t* _ctx) { + struct sockaddr* sockAddr = to_sockaddr(addr, _ctx); + kk_new_req_cb(uv_connect_t, req, callback) + kk_uv_check_status_drops(uv_tcp_connect(req, kk_owned_handle_to_uv_handle(uv_tcp_t, handle), sockAddr, kk_uv_tcp_connect_callback), {kk_function_drop(callback, _ctx);}) +} \ No newline at end of file diff --git a/lib/uv/inline/poll.c b/lib/uv/inline/poll.c new file mode 100644 index 000000000..429b7a333 --- /dev/null +++ b/lib/uv/inline/poll.c @@ -0,0 +1,37 @@ +void kk_uv_fs_poll_callback(uv_fs_poll_t* handle, int status, const uv_stat_t* prev, const uv_stat_t* curr) { + kk_uv_hnd_get_callback(handle, hnd, callback) + kk_function_dup(callback, kk_context()); + if (status < 0) { + // TODO: Does the callback really need duping here? + kk_uv_error_callback(callback, status) + } else { + kk_box_t prevb = kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(prev, _ctx), _ctx); + kk_box_t currb = kk_uv_file__fstat_box(kk_uv_stat_from_uv_stat(curr, _ctx), _ctx); + kk_uv_okay_callback(callback, kk_std_core_types__tuple2_box(kk_std_core_types__new_Tuple2(prevb, currb, _ctx), _ctx)) + } +} + +kk_std_core_exn__error kk_uv_fs_poll_init(kk_context_t* _ctx) { + uv_fs_poll_t* fs_poll = kk_malloc(sizeof(uv_fs_poll_t), _ctx); + int status = uv_fs_poll_init(uvloop(), fs_poll); + kk_uv_check_return_err_drops(status, uv_handle_to_owned_kk_handle_box(fs_poll, kk_handle_free, poll, fs_poll), {kk_free(fs_poll, _ctx);}) +} + +kk_std_core_exn__error kk_uv_fs_poll_start(kk_uv_poll__uv_fs_poll handle, kk_string_t path, kk_integer_t interval, kk_function_t callback, kk_context_t* _ctx) { + kk_set_hnd_cb(uv_fs_poll_t, handle, fs_poll, callback) + kk_ssize_t len; + kk_uv_check_err_drops(uv_fs_poll_start(fs_poll, kk_uv_fs_poll_callback, kk_string_cbuf_borrow(path, &len, _ctx), kk_integer_clamp32(interval, _ctx)), {kk_function_drop(callback, _ctx);}) +} + +kk_std_core_exn__error kk_uv_fs_poll_stop(kk_uv_poll__uv_fs_poll handle, kk_context_t* _ctx) { + uv_fs_poll_t* hnd = kk_owned_handle_to_uv_handle(uv_fs_poll_t, handle); + kk_uv_check_ok_drops(uv_fs_poll_stop(hnd), {kk_box_drop(handle.internal, kk_context());}) +} + +kk_std_core_exn__error kk_uv_fs_poll_getpath(kk_uv_poll__uv_fs_poll handle, kk_context_t* _ctx) { + uv_fs_poll_t* fs_poll = kk_owned_handle_to_uv_handle(uv_fs_poll_t, handle); + size_t len = 2048; + char* path = kk_malloc(len, _ctx); + int status = uv_fs_poll_getpath(fs_poll, path, &len); + kk_uv_check_return_err_drops(status, kk_string_box(kk_string_alloc_raw_len(len, (const char*)path, true, _ctx)), {kk_free(path, _ctx);}) +} diff --git a/lib/uv/inline/signal.c b/lib/uv/inline/signal.c new file mode 100644 index 000000000..1d7c3f1c1 --- /dev/null +++ b/lib/uv/inline/signal.c @@ -0,0 +1,40 @@ +#include "kklib/box.h" + +static kk_uv_signal__uv_signal kk_uv_signal_alloc(kk_context_t* _ctx){ + uv_signal_t* sig = kk_malloc(sizeof(uv_signal_t), _ctx); + uv_signal_init(uvloop(), sig); + return uv_handle_to_owned_kk_handle(sig, kk_handle_free, signal, signal); +} + +static void kk_uv_signal_callback(uv_signal_t* sig, int signum){ + kk_uv_hnd_get_callback(sig, hnd, callback) + kk_function_dup(callback, _ctx); + kk_box_dup(hnd, _ctx); + kk_function_call(void, (kk_function_t, kk_uv_signal__uv_signal, kk_context_t*), callback, (callback, kk_uv_signal__new_Uv_signal(hnd, kk_context()), _ctx), _ctx); +} + +static void kk_uv_signal_oneshot_callback(uv_signal_t* sig, int signum){ + kk_uv_hnd_get_callback(sig, hnd, callback) + kk_unit_callback(callback) +} + +static int32_t kk_uv_signal_start(kk_uv_signal__uv_signal handle, kk_function_t callback, int32_t signum, kk_context_t* _ctx){ + kk_set_hnd_cb(uv_signal_t, handle, uvhnd, callback) + return uv_signal_start(uvhnd, kk_uv_signal_callback, signum); +} + +static int32_t kk_uv_signal_start_oneshot(kk_uv_signal__uv_signal handle, kk_function_t callback, int32_t signum, kk_context_t* _ctx){ + kk_set_hnd_cb(uv_signal_t, handle, uvhnd, callback) + return uv_signal_start_oneshot(uvhnd, kk_uv_signal_oneshot_callback, signum); +} + +static int32_t kk_uv_signal_stop(kk_uv_signal__uv_signal handle, kk_context_t* _ctx){ + uv_signal_t* sig = kk_owned_handle_to_uv_handle(uv_signal_t, handle); + int32_t result = uv_signal_stop(sig); + kk_box_drop(handle.internal, kk_context()); + return result; +} + +static int32_t kk_uv_signal_num(kk_uv_signal__uv_signal handle, kk_context_t* _ctx){ + return kk_owned_handle_to_uv_handle(uv_signal_t, handle)->signum; +} diff --git a/lib/uv/inline/stream.c b/lib/uv/inline/stream.c new file mode 100644 index 000000000..628c7ff09 --- /dev/null +++ b/lib/uv/inline/stream.c @@ -0,0 +1,98 @@ +// #include "std_os_stream.h" +#include "uv_event_dash_loop.h" + +void kk_uv_alloc_callback(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { + buf->base = kk_malloc(suggested_size, kk_get_context()); + buf->len = suggested_size; +} + +static void kk_uv_shutdown_callback(uv_shutdown_t* req, int status){ + kk_uv_get_callback(req, callback) + kk_uv_status_code_callback(callback, status) + kk_free(req, kk_context()); +} + +static int kk_uv_shutdown(kk_uv_stream__uv_stream handle, kk_function_t callback, kk_context_t* _ctx){ + kk_new_req_cb(uv_shutdown_t, uv_req, callback) + return uv_shutdown(uv_req, kk_owned_handle_to_uv_handle(uv_stream_t, handle), kk_uv_shutdown_callback); +} + +static void kk_uv_connection_callback(uv_stream_t* server, int status){ + kk_uv_hnd_get_callback(server, hnd, callback) + kk_function_dup(callback, kk_context()); + kk_uv_status_code_callback(callback, status) + // Don't free the server handle, it's still in use +} + +static int kk_uv_listen(kk_uv_stream__uv_stream stream, int32_t backlog, kk_function_t callback, kk_context_t* _ctx){ + kk_set_hnd_cb(uv_stream_t, stream, uvstream, callback) + return uv_listen(uvstream, backlog, kk_uv_connection_callback); +} + +static int kk_uv_accept(kk_uv_stream__uv_stream server, kk_uv_stream__uv_stream client, kk_context_t* _ctx) { + return uv_accept(kk_owned_handle_to_uv_handle(uv_stream_t,server), kk_owned_handle_to_uv_handle(uv_stream_t, client)); +} + +static void kk_uv_read_callback(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf){ + kk_uv_hnd_get_callback(stream, hnd, callback) + kk_bytes_t bytes = kk_bytes_alloc_len((kk_ssize_t)nread,nread, buf->base, NULL, _ctx); + kk_function_dup(callback, kk_context()); + kk_function_call(void, (kk_function_t, kk_bytes_t, kk_context_t*), callback, (callback, bytes, _ctx), _ctx); +} + +static int kk_uv_read_start(kk_uv_stream__uv_stream stream, kk_function_t read_cb, kk_context_t* _ctx){ + kk_set_hnd_cb(uv_stream_t, stream, uvstream, read_cb) + return uv_read_start(uvstream, kk_uv_alloc_callback, kk_uv_read_callback); +} + +static kk_uv_utils__uv_status_code kk_uv_read_stop(kk_uv_stream__uv_stream stream, kk_context_t* _ctx){ + return kk_uv_utils_int_fs_status_code(uv_read_stop(kk_owned_handle_to_uv_handle(uv_stream_t, stream)), _ctx); +} + +static void kk_uv_write_callback(uv_write_t* write, int status){ + kk_uv_get_callback(write, callback) + // TODO Free bytes? + kk_uv_status_code_callback(callback, status) +} + +static void kk_uv_write(kk_uv_stream__uv_stream stream, kk_std_core_types__list buffs, kk_function_t cb, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + kk_new_req_cb(uv_write_t, write, cb) + uv_write(write, kk_owned_handle_to_uv_handle(uv_stream_t, stream), uv_buffs, list_len, kk_uv_write_callback); +} + +static void kk_uv_write2(kk_uv_stream__uv_stream stream, kk_std_core_types__list buffs, kk_uv_stream__uv_stream send_handle, kk_function_t cb, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + kk_new_req_cb(uv_write_t, write, cb) + uv_write2(write, kk_owned_handle_to_uv_handle(uv_stream_t, stream), uv_buffs, list_len, kk_owned_handle_to_uv_handle(uv_stream_t, send_handle), kk_uv_write_callback); +} + +static int32_t kk_uv_try_write(kk_uv_stream__uv_stream stream, kk_std_core_types__list buffs, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + return uv_try_write(kk_owned_handle_to_uv_handle(uv_stream_t, stream), uv_buffs, list_len); +} + +static int32_t kk_uv_try_write2(kk_uv_stream__uv_stream stream, kk_std_core_types__list buffs, kk_uv_stream__uv_stream send_handle, kk_context_t* _ctx){ + int list_len; + const uv_buf_t* uv_buffs = kk_bytes_list_to_uv_buffs(buffs, &list_len, _ctx); + return uv_try_write2(kk_owned_handle_to_uv_handle(uv_stream_t, stream), uv_buffs, list_len, kk_owned_handle_to_uv_handle(uv_stream_t, send_handle)); +} + +static int32_t kk_uv_is_readable(kk_uv_stream__uv_stream stream, kk_context_t* _ctx){ + return uv_is_readable(kk_owned_handle_to_uv_handle(uv_stream_t, stream)); +} + +static int32_t kk_uv_is_writable(kk_uv_stream__uv_stream stream, kk_context_t* _ctx){ + return uv_is_writable(kk_owned_handle_to_uv_handle(uv_stream_t, stream)); +} + +static kk_uv_utils__uv_status_code kk_uv_stream_set_blocking(kk_uv_stream__uv_stream stream, bool blocking, kk_context_t* _ctx){ + return kk_uv_utils_int_fs_status_code(uv_stream_set_blocking(kk_owned_handle_to_uv_handle(uv_stream_t, stream), blocking), _ctx); +} + +static int32_t kk_uv_stream_get_write_queue_size(kk_uv_stream__uv_stream stream, kk_context_t* _ctx){ + return uv_stream_get_write_queue_size(kk_owned_handle_to_uv_handle(uv_stream_t, stream)); +} \ No newline at end of file diff --git a/lib/uv/inline/timer.c b/lib/uv/inline/timer.c new file mode 100644 index 000000000..e002c4198 --- /dev/null +++ b/lib/uv/inline/timer.c @@ -0,0 +1,142 @@ + +#if __EMSCRIPTEN__ + +#define kk_unit_callback(callback) \ + kk_function_call(kk_unit_t, (kk_function_t, kk_context_t*), callback, (callback, kk_context()), kk_context()); + +void kk_handle_free(void *p, kk_block_t *block, kk_context_t *_ctx) { + kk_wasm_timer_t* hndcb = (kk_wasm_timer_t*)p; + kk_function_drop(hndcb->callback, kk_context()); // Drop the callback + kk_free(hndcb, kk_context()); // Free the memory used for the callback and box + // p will be freed by uv. +} + +#define kk_tm_to_uv(hnd) kk_owned_handle_to_uv_handle(kk_wasm_timer_t, hnd) +#define kk_uv_to_tm(hnd) handle_to_owned_kk_handle(hnd, kk_handle_free, timer, Timer) +#define kk_uv_to_tm_box(hnd) handle_to_owned_kk_handle_box(hnd, kk_handle_free, timer, timer, Timer) + +EMSCRIPTEN_KEEPALIVE void wasm_timer_callback(kk_wasm_timer_t* timer_info){ + kk_context_t* _ctx = kk_get_context(); + kk_function_t callback = timer_info->callback; + kk_box_t kkhandle = timer_info->kkhandle; + if (timer_info->repeat_ms == 0) { + kk_unit_callback(callback) + } else { + kk_function_dup(callback, _ctx); + kk_box_dup(kkhandle, _ctx); + kk_unit_callback(callback) + } +} + +EM_JS(int, start_timer, (int64_t timeout, int64_t repeat, kk_wasm_timer_t* timer_info), { + function wasm_callback() { + _wasm_timer_callback(timer_info); + } + const rp = Number(repeat); + const msx = Number(timeout); + if (rp != 0) { + return setInterval(wasm_callback, rp); + } else { + return setTimeout(wasm_callback, msx); + } +}); + +EM_JS(void, stop_timer, (int timer, bool repeating), { + if (timer) { + if (repeating) { + clearInterval(timer); + } else { + clearTimeout(timer); + } + } +}); + +kk_uv_timer__timer kk_timer_init(kk_context_t* _ctx) { + kk_wasm_timer_t* timer_info = kk_malloc(sizeof(kk_wasm_timer_t), _ctx); + kk_uv_timer__timer hnd = kk_uv_to_tm(timer_info); + timer_info->kkhandle = hnd.internal; + return hnd; +} + +kk_unit_t kk_timer_stop(kk_uv_timer__timer timer, kk_context_t* _ctx) { + kk_wasm_timer_t* timer_info = kk_tm_to_uv(timer); + if (timer_info == nullptr) return kk_Unit; + if (timer_info->timer != 0) { + stop_timer(timer_info->timer, timer_info->repeat_ms != 0); + } + kk_uv_timer__timer_drop(timer, _ctx); + return kk_Unit; +} + +kk_std_core_exn__error kk_timer_start(kk_uv_timer__timer timer, int64_t timeout, int64_t repeat, kk_function_t callback, kk_context_t* _ctx) { + kk_wasm_timer_t* timer_info = kk_tm_to_uv(timer); + timer_info->callback = callback; + timer_info->repeat_ms = repeat; + int ret = start_timer(timeout, repeat, timer_info); + if (ret < 0) { + kk_function_drop(callback, _ctx); + kk_define_string_literal(, err_msg, 22, "Failed to start timer", _ctx); + return kk_std_core_exn__new_Error(kk_std_core_exn__new_Exception( err_msg, kk_std_core_exn__new_ExnSystem(kk_reuse_null, 0, kk_integer_from_int(-1,_ctx), _ctx), _ctx), _ctx ); + } else { + timer_info->timer = ret; + return kk_std_core_exn__new_Ok(kk_unit_box(kk_Unit), _ctx); + } +} + +#else + + +#define kk_tm_to_uv(hnd) kk_owned_handle_to_uv_handle(uv_timer_t, hnd) +#define kk_uv_to_tm(hnd) handle_to_owned_kk_handle(hnd, kk_free_fun, timer, Timer) +#define kk_uv_to_tm_box(hnd) handle_to_owned_kk_handle_box(hnd, kk_free_fun, timer, timer, Timer) + +kk_uv_timer__timer kk_timer_init(kk_context_t* _ctx) { + uv_timer_t* t = kk_malloc(sizeof(uv_timer_t), _ctx); + uv_timer_init(uvloop(), t); + return kk_uv_to_tm(t); +} + +kk_unit_t kk_timer_stop(kk_uv_timer__timer timer, kk_context_t* _ctx) { + uv_timer_t* uv_timer = kk_tm_to_uv(timer); + uv_timer_stop(uv_timer); + kk_box_drop(timer.internal, _ctx); + return kk_Unit; +} + +void kk_uv_timer_unit_callback(uv_timer_t* uv_timer) { + kk_uv_hnd_get_callback(uv_timer, hnd, callback) + if (uv_timer_get_repeat(uv_timer) == 0) { + kk_unit_callback(callback) + } else { + kk_function_dup(callback, _ctx); + kk_box_dup(hnd, _ctx); + kk_unit_callback(callback) + } +} + +kk_std_core_exn__error kk_timer_start(kk_uv_timer__timer timer, int64_t timeout, int64_t repeat, kk_function_t callback, kk_context_t* _ctx) { + kk_set_hnd_cb(uv_timer_t, timer, uv_timer, callback) + int status = uv_timer_start(uv_timer, kk_uv_timer_unit_callback, timeout, repeat); + kk_uv_check_err_drops(status, {kk_function_drop(callback, _ctx);}) +} + +kk_std_core_exn__error kk_timer_again(kk_uv_timer__timer timer, kk_context_t* _ctx) { + int status = uv_timer_again(kk_tm_to_uv(timer)); + kk_uv_check(status) +} + +kk_unit_t kk_timer_set_repeat(kk_uv_timer__timer timer, int64_t repeat, kk_context_t* _ctx) { + uv_timer_set_repeat(kk_tm_to_uv(timer), repeat); + return kk_Unit; +} + +int64_t kk_timer_get_repeat(kk_uv_timer__timer timer, kk_context_t* _ctx) { + uint64_t repeat = uv_timer_get_repeat(kk_tm_to_uv(timer)); + return repeat; +} + +int64_t kk_timer_get_due_in(kk_uv_timer__timer timer, kk_context_t* _ctx) { + uint64_t due_in = uv_timer_get_due_in(kk_tm_to_uv(timer)); + return due_in; +} +#endif diff --git a/lib/uv/inline/timer.cs b/lib/uv/inline/timer.cs new file mode 100644 index 000000000..1c7e6c3bf --- /dev/null +++ b/lib/uv/inline/timer.cs @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------- + Copyright 2016-2021, Microsoft Research, Daan Leijen. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ +using System.Diagnostics; + +static class _Timer { + private static Stopwatch ticker = Stopwatch.StartNew(); + private static long frequency = (Stopwatch.Frequency > 0 ? Stopwatch.Frequency : 1000); + private static double resolution = 1.0 / (double)frequency; + + public static __std_core_types._Tuple2_ Ticks() { + long ticks = ticker.ElapsedTicks; + // maintain precision .. + double secs = (double)(ticks / frequency); + double fsecs = (double)(ticks % frequency) * resolution; + return new __std_core_types._Tuple2_(secs,fsecs); + } + + public static double TicksResolution() { + return resolution; + } +} diff --git a/lib/uv/inline/timer.h b/lib/uv/inline/timer.h new file mode 100644 index 000000000..f08d113cc --- /dev/null +++ b/lib/uv/inline/timer.h @@ -0,0 +1,14 @@ +#ifdef __EMSCRIPTEN__ +#include + +typedef struct kk_wasm_timer_s { + kk_function_t callback; + kk_box_t kkhandle; + int64_t repeat_ms; + int timer; +} kk_wasm_timer_t; + +EMSCRIPTEN_KEEPALIVE void wasm_timer_callback(kk_wasm_timer_t* timer_info); +#else +#include +#endif diff --git a/lib/uv/inline/timer.js b/lib/uv/inline/timer.js new file mode 100644 index 000000000..75eed6c3e --- /dev/null +++ b/lib/uv/inline/timer.js @@ -0,0 +1,25 @@ +function _init_timer(){ + return {}; +} + +function _start_timer(timer, ms, repeat, fcn){ + const rp = Number(repeat) + const msx = Number(ms) + if (rp != 0) { + timer.id = setInterval(fcn, rp); + timer.repeat = rp; + } else { + timer.id = setTimeout(fcn, msx); + } +} + +function _stop_timer(timer){ + if (timer.id) { + if (timer.repeat != 0) { + clearInterval(timer.id); + } else { + clearTimeout(timer.id); + } + timer.id = null; + } +} \ No newline at end of file diff --git a/lib/uv/inline/tty.c b/lib/uv/inline/tty.c new file mode 100644 index 000000000..782e2bea9 --- /dev/null +++ b/lib/uv/inline/tty.c @@ -0,0 +1,65 @@ +#include "uv.h" + +kk_std_core_exn__error kk_uv_tty_init(int32_t fd, kk_context_t* _ctx) { + uv_tty_t* tty = kk_malloc(sizeof(uv_tty_t), _ctx); + int res = uv_tty_init(uvloop(), tty, fd, 0); // Last parameter is unused + kk_uv_check_return_err_drops(res, uv_handle_to_owned_kk_handle_box(tty, kk_handle_free, tty, tty), {kk_free(tty, _ctx);}) +} + +uv_tty_mode_t kk_tty_mode_to_uv_tty_mode(kk_uv_tty__tty_mode mode, kk_context_t* _ctx) { + if (kk_uv_tty__is_UV__TTY__MODE__NORMAL(mode, _ctx)) { + return UV_TTY_MODE_NORMAL; + } else if (kk_uv_tty__is_UV__TTY__MODE__RAW(mode, _ctx)) { + return UV_TTY_MODE_RAW; + } else if (kk_uv_tty__is_UV__TTY__MODE__IO(mode, _ctx)) { + return UV_TTY_MODE_IO; + } else { + kk_fatal_error(-1, "Invalid enum value to uv ffi tty_mode"); + } +} + +kk_uv_tty__tty_mode kk_uv_tty_mode_to_tty_mode(uv_tty_mode_t mode, kk_context_t* _ctx) { + switch (mode) { + case UV_TTY_MODE_NORMAL: + return kk_uv_tty__new_UV__TTY__MODE__NORMAL(_ctx); + case UV_TTY_MODE_RAW: + return kk_uv_tty__new_UV__TTY__MODE__RAW(_ctx); + case UV_TTY_MODE_IO: + return kk_uv_tty__new_UV__TTY__MODE__IO(_ctx); + default: + kk_fatal_error(-1, "Invalid enum value from uv ffi tty_mode"); + } +} + +uv_tty_vtermstate_t kk_tty_vtermstate_to_uv_tty_vtermstate(kk_uv_tty__tty_vtermstate state, kk_context_t* _ctx) { + if (kk_uv_tty__is_UV__TTY__SUPPORTED(state, _ctx)) { + return UV_TTY_SUPPORTED; + } else if (kk_uv_tty__is_UV__TTY__UNSUPPORTED(state, _ctx)) { + return UV_TTY_UNSUPPORTED; + } else { + kk_fatal_error(-1, "Invalid enum value to uv ffi tty_vtermstate"); + } +} + +kk_uv_tty__tty_vtermstate kk_uv_tty_vtermstate_to_tty_vtermstate(uv_tty_vtermstate_t state, kk_context_t* _ctx) { + switch (state) { + case UV_TTY_SUPPORTED: + return kk_uv_tty__new_UV__TTY__SUPPORTED(_ctx); + case UV_TTY_UNSUPPORTED: + return kk_uv_tty__new_UV__TTY__UNSUPPORTED(_ctx); + default: + kk_fatal_error(-1, "Invalid enum value from uv ffi tty_vtermstate"); + } +} + +kk_std_core_exn__error kk_uv_tty_get_winsize(kk_uv_tty__uv_tty handle, kk_context_t* _ctx) { + int32_t width, height; + int status = uv_tty_get_winsize(kk_owned_handle_to_uv_handle(uv_tty_t, handle), &width, &height); + kk_uv_check_return(status, kk_std_core_types__tuple2_box(kk_std_core_types__new_Tuple2(kk_int32_box(width, _ctx), kk_int32_box(height, _ctx), _ctx), _ctx)) +} + +kk_std_core_exn__error kk_uv_tty_get_vtermstate(kk_context_t* _ctx) { + uv_tty_vtermstate_t uv_state; + int status = uv_tty_get_vterm_state(&uv_state); + kk_uv_check_return(status, kk_uv_tty__tty_vtermstate_box(kk_uv_tty_vtermstate_to_tty_vtermstate(uv_state, _ctx), _ctx)) +} \ No newline at end of file diff --git a/lib/uv/inline/utils.c b/lib/uv/inline/utils.c new file mode 100644 index 000000000..d98b48328 --- /dev/null +++ b/lib/uv/inline/utils.c @@ -0,0 +1,221 @@ +#ifndef __EMSCRIPTEN__ + +void kk_handle_free(void *p, kk_block_t *block, kk_context_t *_ctx) { + uv_handle_t *hnd = (uv_handle_t *)p; + kk_hnd_callback_t* hndcb = (kk_hnd_callback_t*)hnd->data; + kk_function_drop(hndcb->cb, kk_context()); // Drop the callback + kk_free(hndcb, kk_context()); // Free the memory used for the callback and box + hnd->data = NULL; // Clear the reference to this + uv_close(hnd, NULL); + // p will be freed by uv. +} + +static kk_uv_utils__uv_status_code kk_uv_status_to_status_code(int32_t status, + kk_context_t *_ctx) { + switch (status) { + case 0: + return kk_uv_utils_UV__OK; + case UV_E2BIG: + return kk_uv_utils_UV__E2BIG; + case UV_EACCES: + return kk_uv_utils_UV__EACCES; + case UV_EADDRINUSE: + return kk_uv_utils_UV__EADDRINUSE; + case UV_EADDRNOTAVAIL: + return kk_uv_utils_UV__EADDRNOTAVAIL; + case UV_EAFNOSUPPORT: + return kk_uv_utils_UV__EAFNOSUPPORT; + case UV_EAGAIN: + return kk_uv_utils_UV__EAGAIN; + case UV_EAI_ADDRFAMILY: + return kk_uv_utils_UV__EAI__ADDRFAMILY; + case UV_EAI_AGAIN: + return kk_uv_utils_UV__EAI__AGAIN; + case UV_EAI_BADFLAGS: + return kk_uv_utils_UV__EAI__BADFLAGS; + case UV_EAI_BADHINTS: + return kk_uv_utils_UV__EAI__BADHINTS; + case UV_EAI_CANCELED: + return kk_uv_utils_UV__EAI__CANCELED; + case UV_EAI_FAIL: + return kk_uv_utils_UV__EAI__FAIL; + case UV_EAI_FAMILY: + return kk_uv_utils_UV__EAI__FAMILY; + case UV_EAI_MEMORY: + return kk_uv_utils_UV__EAI__MEMORY; + case UV_EAI_NODATA: + return kk_uv_utils_UV__EAI__NODATA; + case UV_EAI_NONAME: + return kk_uv_utils_UV__EAI__NONAME; + case UV_EAI_OVERFLOW: + return kk_uv_utils_UV__EAI__OVERFLOW; + case UV_EAI_PROTOCOL: + return kk_uv_utils_UV__EAI__PROTOCOL; + case UV_EAI_SERVICE: + return kk_uv_utils_UV__EAI__SERVICE; + case UV_EAI_SOCKTYPE: + return kk_uv_utils_UV__EAI__SOCKTYPE; + case UV_EALREADY: + return kk_uv_utils_UV__EALREADY; + case UV_EBADF: + return kk_uv_utils_UV__EBADF; + case UV_EBUSY: + return kk_uv_utils_UV__EBUSY; + case UV_ECANCELED: + return kk_uv_utils_UV__ECANCELED; + case UV_ECHARSET: + return kk_uv_utils_UV__ECHARSET; + case UV_ECONNABORTED: + return kk_uv_utils_UV__ECONNABORTED; + case UV_ECONNREFUSED: + return kk_uv_utils_UV__ECONNREFUSED; + case UV_ECONNRESET: + return kk_uv_utils_UV__ECONNRESET; + case UV_EDESTADDRREQ: + return kk_uv_utils_UV__EDESTADDRREQ; + case UV_EEXIST: + return kk_uv_utils_UV__EEXIST; + case UV_EFAULT: + return kk_uv_utils_UV__EFAULT; + case UV_EFBIG: + return kk_uv_utils_UV__EFBIG; + case UV_EHOSTUNREACH: + return kk_uv_utils_UV__EHOSTUNREACH; + case UV_EINTR: + return kk_uv_utils_UV__EINTR; + case UV_EINVAL: + return kk_uv_utils_UV__EINVAL; + case UV_EIO: + return kk_uv_utils_UV__EIO; + case UV_EISCONN: + return kk_uv_utils_UV__EISCONN; + case UV_EISDIR: + return kk_uv_utils_UV__EISDIR; + case UV_ELOOP: + return kk_uv_utils_UV__ELOOP; + case UV_EMFILE: + return kk_uv_utils_UV__EMFILE; + case UV_EMSGSIZE: + return kk_uv_utils_UV__EMSGSIZE; + case UV_ENAMETOOLONG: + return kk_uv_utils_UV__ENAMETOOLONG; + case UV_ENETDOWN: + return kk_uv_utils_UV__ENETDOWN; + case UV_ENETUNREACH: + return kk_uv_utils_UV__ENETUNREACH; + case UV_ENFILE: + return kk_uv_utils_UV__ENFILE; + case UV_ENOBUFS: + return kk_uv_utils_UV__ENOBUFS; + case UV_ENODEV: + return kk_uv_utils_UV__ENODEV; + case UV_ENOENT: + return kk_uv_utils_UV__ENOENT; + case UV_ENOMEM: + return kk_uv_utils_UV__ENOMEM; + case UV_ENONET: + return kk_uv_utils_UV__ENONET; + case UV_ENOPROTOOPT: + return kk_uv_utils_UV__ENOPROTOOPT; + case UV_ENOSPC: + return kk_uv_utils_UV__ENOSPC; + case UV_ENOSYS: + return kk_uv_utils_UV__ENOSYS; + case UV_ENOTCONN: + return kk_uv_utils_UV__ENOTCONN; + case UV_ENOTDIR: + return kk_uv_utils_UV__ENOTDIR; + case UV_ENOTEMPTY: + return kk_uv_utils_UV__ENOTEMPTY; + case UV_ENOTSOCK: + return kk_uv_utils_UV__ENOTSOCK; + case UV_ENOTSUP: + return kk_uv_utils_UV__ENOTSUP; + case UV_EOVERFLOW: + return kk_uv_utils_UV__EOVERFLOW; + case UV_EPERM: + return kk_uv_utils_UV__EPERM; + case UV_EPIPE: + return kk_uv_utils_UV__EPIPE; + case UV_EPROTO: + return kk_uv_utils_UV__EPROTO; + case UV_EPROTONOSUPPORT: + return kk_uv_utils_UV__EPROTONOSUPPORT; + case UV_EPROTOTYPE: + return kk_uv_utils_UV__EPROTOTYPE; + case UV_ERANGE: + return kk_uv_utils_UV__ERANGE; + case UV_EROFS: + return kk_uv_utils_UV__EROFS; + case UV_ESHUTDOWN: + return kk_uv_utils_UV__ESHUTDOWN; + case UV_ESPIPE: + return kk_uv_utils_UV__ESPIPE; + case UV_ESRCH: + return kk_uv_utils_UV__ESRCH; + case UV_ETIMEDOUT: + return kk_uv_utils_UV__ETIMEDOUT; + case UV_ETXTBSY: + return kk_uv_utils_UV__ETXTBSY; + case UV_EXDEV: + return kk_uv_utils_UV__EXDEV; + case UV_UNKNOWN: + return kk_uv_utils_UV__UNKNOWN; + case UV_EOF: + return kk_uv_utils_UV__EOF; + case UV_ENXIO: + return kk_uv_utils_UV__ENXIO; + case UV_EMLINK: + return kk_uv_utils_UV__EMLINK; + case UV_ENOTTY: + return kk_uv_utils_UV__ENOTTY; + case UV_EFTYPE: + return kk_uv_utils_UV__EFTYPE; + case UV_EILSEQ: + return kk_uv_utils_UV__EILSEQ; + case UV_ESOCKTNOSUPPORT: + return kk_uv_utils_UV__ESOCKTNOSUPPORT; + default: + return kk_uv_utils_UV__UNKNOWN; + // case UV_EUNACH: + // return kk_uv_utils_UV__EUNATCH; + } +} + +uv_buf_t* kk_bytes_list_to_uv_buffs(kk_std_core_types__list buffs, int* size, kk_context_t* _ctx){ + kk_std_core_types__list_dup(buffs, _ctx); + kk_integer_t klist_len = kk_std_core_list_length(buffs, _ctx); + int list_len = kk_integer_clamp32(klist_len, _ctx); + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t) * list_len, _ctx); + kk_std_core_types__list list = buffs; + for (int i = 0; i < list_len; i++){ + struct kk_std_core_types_Cons* cons = kk_std_core_types__as_Cons(list, _ctx); + kk_bytes_t bytes = kk_bytes_unbox(cons->head); + kk_ssize_t len; + const char* chars = kk_bytes_cbuf_borrow(bytes, &len, _ctx); + uv_buffs[i].base = kk_malloc(len, _ctx); + kk_memcpy(uv_buffs[i].base, chars, len); + uv_buffs[i].len = len; + list = cons->tail; + } + *size = list_len; + return uv_buffs; +} + +uv_buf_t* kk_bytes_to_uv_buffs(kk_bytes_t bytes, kk_context_t* _ctx){ + uv_buf_t* uv_buffs = kk_malloc(sizeof(uv_buf_t) * 1, _ctx); + kk_ssize_t len; + const char* chars = kk_bytes_cbuf_borrow(bytes, &len, _ctx); + uv_buffs[0].base = kk_malloc(sizeof(char)*len, _ctx); + kk_memcpy(uv_buffs[0].base, chars, len); + uv_buffs[0].len = len; + kk_bytes_drop(bytes, _ctx); + return uv_buffs; +} + +kk_std_core_exn__error kk_async_error_from_errno( int err, kk_context_t* _ctx ) { + kk_uv_utils__uv_status_code code = kk_uv_status_to_status_code(err, _ctx); + kk_string_t msg = kk_uv_utils_message(code, _ctx); + return kk_std_core_exn__new_Error( kk_std_core_exn__new_Exception( msg, kk_uv_utils__new_AsyncExn(kk_reuse_null, 0, code, _ctx), _ctx), _ctx ); +} +#endif diff --git a/lib/uv/inline/utils.h b/lib/uv/inline/utils.h new file mode 100644 index 000000000..ae8e12665 --- /dev/null +++ b/lib/uv/inline/utils.h @@ -0,0 +1,124 @@ +#define kk_function_as_ptr(f,ctx) ((void*)kk_datatype_as_ptr(f, ctx)) +#define kk_function_from_ptr(p,ctx) (kk_datatype_from_ptr(p, ctx)) + +#define kk_owned_handle_to_uv_handle(tp, hndl) ((tp*)kk_cptr_unbox_borrowed(hndl.internal, kk_context())) +#define uv_handle_to_owned_kk_handle(hndl, free_fn, mod, tp) \ + kk_uv_##mod##__new_Uv_##tp(kk_cptr_raw_box(&free_fn, (void*)hndl, kk_context()), kk_context()) +#define uv_handle_to_owned_kk_handle_box(hndl, free_fn, mod, tp) \ + kk_uv_##mod##__uv_##tp##_box(uv_handle_to_owned_kk_handle(hndl,free_fn,mod,tp), kk_context()) + +#define handle_to_owned_kk_handle(hndl, free_fn, mod, tp) \ + kk_uv_##mod##__new_##tp(kk_cptr_raw_box(&free_fn, (void*)hndl, kk_context()), kk_context()) +#define handle_to_owned_kk_handle_box(hndl, free_fn, mod, lctp, tp) \ + kk_uv_##mod##__##lctp##_box(uv_handle_to_owned_kk_handle(hndl,free_fn,mod,tp), kk_context()) + +#ifdef __EMSCRIPTEN__ +#include +#else +#include + +#define kk_unit_callback(callback) \ + kk_function_call(void, (kk_function_t, kk_context_t*), callback, (callback, kk_context()), kk_context()); + +#define uvloop() ((uv_loop_t*)(kk_context()->loop)) +#define UV_OK 0 +kk_std_core_exn__error kk_async_error_from_errno( int err, kk_context_t* ctx ); + +uv_buf_t* kk_bytes_list_to_uv_buffs(kk_std_core_types__list buffs, int* size, kk_context_t* _ctx); +uv_buf_t* kk_bytes_to_uv_buffs(kk_bytes_t bytes, kk_context_t* _ctx); + +void kk_handle_free(void* p, kk_block_t* block, kk_context_t* _ctx); + +#define kk_uv_exn_callback(callback, result) \ + kk_function_call(void, (kk_function_t, kk_std_core_exn__error, kk_context_t*), callback, (callback, result, kk_context()), kk_context()); + +#define kk_uv_status_code_callback(callback, status) \ + kk_function_call(void, (kk_function_t, kk_uv_utils__uv_status_code, kk_context_t*), callback, (callback, kk_uv_utils_int_fs_status_code(status, _ctx), kk_context()), kk_context()); + +#define kk_uv_error_callback(callback, result) \ + kk_uv_exn_callback(callback, kk_async_error_from_errno(result, kk_context())) + +#define kk_uv_okay_callback(callback, result) \ + kk_uv_exn_callback(callback, kk_std_core_exn__new_Ok(result, kk_context())) + +typedef struct { + kk_function_t cb; + kk_box_t hnd; +} kk_hnd_callback_t; + +// Sets the data of the handle to point to the callback +// TODO: Check if data is already assigned? +#define kk_set_hnd_cb(hnd_t, handle, uvhnd, callback) \ + hnd_t* uvhnd = kk_owned_handle_to_uv_handle(hnd_t, handle); \ + kk_hnd_callback_t* uvhnd_cb = kk_malloc(sizeof(kk_hnd_callback_t), kk_context()); \ + uvhnd_cb->cb = callback; \ + uvhnd_cb->hnd = handle.internal; \ + uvhnd->data = uvhnd_cb; + +// TODO: Should I get the ctx from the loop? +#define kk_uv_hnd_get_callback(handle, kk_hnd, callback) \ + kk_context_t* _ctx = kk_get_context(); \ + kk_hnd_callback_t* hndcb = (kk_hnd_callback_t*)handle->data; \ + kk_box_t kk_hnd = hndcb->hnd; \ + kk_function_t callback = hndcb->cb; + +#define kk_new_req_cb(req_t, req, cb) \ + req_t* req = kk_malloc(sizeof(req_t), kk_context()); \ + req->data = kk_function_as_ptr(cb, kk_context()); + +#define kk_uv_get_callback(req, cb) \ + kk_context_t* _ctx = kk_get_context(); \ + kk_function_t cb = kk_function_from_ptr(req->data, kk_context()); + + + + +// TODO: Change all apis to return status code or return error, not a mix + +// If the status is not OK, drop before returning the status code +#define kk_uv_check_status_drops(status, drops) \ + if (status < UV_OK) { \ + do drops while (0); \ + } \ + return kk_uv_utils_int_fs_status_code(status, kk_context()); \ + +// Sometimes the return value is a file descriptor which is why this is a < UV_OK check instead of == UV_OK +#define kk_uv_check_return(err, result) \ + if (err < UV_OK) { \ + return kk_async_error_from_errno(err, kk_context()); \ + } else { \ + return kk_std_core_exn__new_Ok(result, kk_context()); \ + } + +// Typically used to clean up when an error occurs +#define kk_uv_check_return_err_drops(err, result, drops) \ + if (err < UV_OK) { \ + do drops while (0); \ + return kk_async_error_from_errno(err, kk_context()); \ + } else { \ + return kk_std_core_exn__new_Ok(result, kk_context()); \ + } + +// Typically used when cleaning up a handle +#define kk_uv_check_return_ok_drops(err, result, drops) \ + if (err < UV_OK) { \ + return kk_async_error_from_errno(err, kk_context()); \ + } else { \ + do drops while (0); \ + return kk_std_core_exn__new_Ok(result, kk_context()); \ + } + +// Check the uv status code and return a kk_std_core_exn__error Ok or Error +#define kk_uv_check(err) kk_uv_check_return(err, kk_unit_box(kk_Unit)) + +// Check the uv status code and return a kk_std_core_exn__error Ok or Error +// Dropping the references if it was an error +#define kk_uv_check_err_drops(err, drops) \ + kk_uv_check_return_err_drops(err, kk_unit_box(kk_Unit), drops) + +// Check the uv status code and return a kk_std_core_exn__error Ok or Error +// Dropping the references if the result is Okay +#define kk_uv_check_ok_drops(err, drops) \ + kk_uv_check_return_ok_drops(err, kk_unit_box(kk_Unit), drops) + +#endif diff --git a/lib/uv/net.kk b/lib/uv/net.kk new file mode 100644 index 000000000..99be0d4e3 --- /dev/null +++ b/lib/uv/net.kk @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +// A Koka wrapper around low level libuv networking functions +module uv/net + +pub import std/num/int32 +pub import std/data/bytes +pub import uv/event-loop +pub import uv/stream +import uv/utils + +extern import + c file "inline/net.c" + +// Socket types +pub type sock-type + SOCK_ANY + SOCK_STREAM + SOCK_DGRAM + +// Internet address families +pub type net-family + AF_INET + AF_INET6 + AF_ANY + +pub fun net-family/show(family: net-family): string + match family + AF_INET -> "AF_INET" + AF_INET6 -> "AF_INET6" + AF_ANY -> "AF_ANY" + +// Internet address info +pub struct addr-info + flags: int32 + pub family: net-family + pub socktype: sock-type + protocol: int32 + pub addr: sock-addr + pub canonName: string; + +// Socket addresses +pub value struct sock-addr + pub family: net-family + pub data: string + pub port: maybe = Nothing; + +// A uv tcp handle +pub value struct uv-tcp { internal: any } +// A uv OS socket handle +pub value struct uv-os-sock { internal: intptr_t } + +pub inline fun get-addr-info(host : string, callback: (list) -> io-noexn (), hints : maybe = Nothing): io-noexn () + uv-get-addr-info(host, "", hints, callback) + +extern uv-get-addr-info(node : string, service : string, hints : maybe, callback: (list) -> io-noexn ()): io-noexn () + c "kk_get_addrinfo" + +// Initialize the handle. No socket is created as of yet. +pub extern tcp-init(): io-noexn error + c "kk_uv_tcp_init" + +// Bind the handle to an address and port. addr should point to an initialized struct sockaddr_in or struct sockaddr_in6. +// When the port is already taken, you can expect to see an UV_EADDRINUSE error from `listen()`` or `connect()`. +// That is, a successful call to this function does not guarantee that the call to `listen()` or `connect()` will succeed as well. +// +// flags can contain `UV_TCP_IPV6ONLY`, in which case dual-stack support is disabled and only IPv6 is used. +pub extern tcp/bind(^tcp: uv-tcp, addr: sock-addr, flags: int32): io-noexn uv-status-code + c "kk_uv_tcp_bind" + +// Not all tcp handles are streams (e.g. servers) +// Ensure there are safe wrappers +pub inline fun tcp/stream(^tcp: uv-tcp): io-noexn uv-stream + Uv-stream(tcp.internal) + +// Any uv can be a handle, so it's always safe to cast +pub inline fun tcp/uv-handle(^tcp: uv-tcp): io-noexn uv-handle + Uv-handle(tcp.internal) + +// Streams are not necessarily always tcp -- they could be files, pipes, udp, etc. +// Ensure there are safe wrappers +pub inline fun stream/tcp(^tcp: uv-stream): io-noexn uv-tcp + Uv-tcp(tcp.internal) + +// Establish an IPv4 or IPv6 TCP connection. Provide an initialized TCP handle. +// The callback is made when the connection has been established or when a connection error happened. +pub extern tcp/connect(^tcp: uv-tcp, addr: sock-addr, callback: (uv-status-code) -> io-noexn ()): io-noexn uv-status-code + c "kk_uv_tcp_connect" diff --git a/lib/uv/poll.kk b/lib/uv/poll.kk new file mode 100644 index 000000000..62841a37a --- /dev/null +++ b/lib/uv/poll.kk @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------- + Copyright 2024 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/poll +import uv/utils +pub import uv/file + +extern import + c file "inline/poll.c" + +value struct uv-fs-poll {internal: any } + +// Initialize the handle. +pub extern uv-fs-poll-init(): io-noexn error + c "kk_uv_fs_poll_init" + +// Check the file at path for changes every interval milliseconds. +// For maximum portability, use multi-second intervals. +// Sub-second intervals will not detect all changes on many file systems. +pub extern uv-fs-poll-start(poll: uv-fs-poll, path: string, interval: int, cb: error<(fstat, fstat)> -> io-noexn ()): io-noexn error<()> + c "kk_uv_fs_poll_start" + +// Stop the handle, the callback will no longer be called. +pub extern uv-fs-poll-stop(poll: uv-fs-poll): io-noexn error<()> + c "kk_uv_fs_poll_stop" + +// Get the path being monitored by the handle. +pub extern uv-fs-poll-getpath(poll: uv-fs-poll): io-noexn error + c "kk_uv_fs_poll_getpath" diff --git a/lib/uv/signal.kk b/lib/uv/signal.kk new file mode 100644 index 000000000..354700fa0 --- /dev/null +++ b/lib/uv/signal.kk @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +pub import uv/event-loop +import std/num/int32 +pub import uv/utils + +extern import + c file "inline/signal.c" + +// A uv signal handle +value struct uv-signal {internal: any } + +// Create and initilize a uv signal handle +extern uv-signal-init(): io-noexn uv-signal + c "kk_uv_signal_alloc" + +// Start the handle with the given callback, watching for the given signal. +extern uv-signal-start(h: uv-signal, cb: (uv-signal) -> io-noexn (), signal: int32): io-noexn int32 + c "kk_uv_signal_start" + +// Same functionality as uv-signal-start() but the signal handler is reset the moment the signal is received. +extern uv-signal-start-one-shot(h: uv-signal, cb: () -> io-noexn (), signal: int32): io-noexn int32 + c "kk_uv_signal_start_oneshot" + +// Stop the handle, the callback will no longer be called. +extern uv-signal-stop(h: uv-signal): io-noexn int32 + c "kk_uv_signal_stop" + +// Get the signal number the handle is monitoring +pub extern signum(^h: uv-signal): io-noexn int32 + c "kk_uv_signal_num" + +// The sigint signal +pub val sSIGINT = ssSIGINT() +extern ssSIGINT(): int32 + c inline "SIGINT" + +// Start watching for the given signal, calling the callback on the next event loop +pub fun signal-start(signal: int32, cb: (uv-signal) -> io-noexn ()): io-noexn uv-status-code + uv-signal-start(uv-signal-init(), cb, signal).status-code + +// Start watching for the given signal, calling the callback on the next event loop, and resetting the signal handler after the first callback +pub fun signal-start-oneshot(signal: int32, cb: () -> io-noexn ()): io-noexn uv-status-code + uv-signal-start-one-shot(uv-signal-init(), cb, signal).status-code + +// Stop watching for the signal +pub fun signal-stop(h: uv-signal): io-noexn uv-status-code + uv-signal-stop(h).status-code diff --git a/lib/uv/stream.kk b/lib/uv/stream.kk new file mode 100644 index 000000000..f7239d46e --- /dev/null +++ b/lib/uv/stream.kk @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/stream +pub import uv/event-loop +pub import std/data/bytes +pub import uv/utils + +extern import + c file "inline/stream.c" + +pub value struct uv-stream { internal : any }; + +// Casts the stream to the underlying uv handle type +pub inline extern uv-handle(tcp: uv-stream): io-noexn uv-handle + c inline "kk_uv_utils__new_Uv_handle(#1.internal, kk_context())" + +// Shutdown the outgoing (write) side of a duplex stream. +// It waits for pending write requests to complete. +// The stream handle should refer to a initialized stream. +// The `callback` is called after shutdown is complete. +pub extern shutdown(hnd: uv-stream, callback: (uv-status-code) -> io-noexn ()): io-noexn () + c "kk_uv_shutdown" + +// Start listening for incoming connections. +// backlog indicates the number of connections the kernel might queue, same as listen(2). +// When a new incoming connection is received the `callback` is called. +pub extern listen(stream: uv-stream, backlog: int32, callback: (uv-status-code) -> io-noexn ()): io-noexn () + c "kk_uv_listen" + +// This call is used in conjunction with `listen()` to accept incoming connections. +// Call this function after receiving a connection callback to accept the connection. +// Before calling this function the client handle must be initialized. < 0 return value indicates an error. +// When the connection callback is called it is guaranteed that this function will complete successfully the first time. +// If you attempt to use it more than once, it may fail. +// It is suggested to only call this function once per connection callback. +pub extern accept(server: uv-stream, client: uv-stream): io-noexn uv-status-code + c "kk_uv_accept" + +// Read data from an incoming stream. The `callback` will be made several times until there is no more data to read or `read-stop()` is called. +// Returns `UV_EALREADY` when called twice, and `UV_EINVAL` when the stream is closing. +pub extern read-start(stream: uv-stream, callback: (bytes) -> io-noexn ()): io-noexn () + c "kk_uv_read_start" + +// Stop reading data from the stream. The read callback will no longer be called. +// This function is idempotent and may be safely called on a stopped stream. +// A non-zero return indicates that finishing releasing resources may be pending on the next input event on that TTY on Windows, and does not indicate failure. +pub extern read-stop(stream: uv-stream): io-noexn uv-status-code + c "kk_uv_read_stop" + +// Write data to stream. Buffers are written in order. +pub extern stream/write(stream: uv-stream, data: list, callback: (uv-status-code) -> io-noexn ()): io-noexn () + c "kk_uv_write" + +// Extended write function for sending handles over a pipe. +// The pipe must be initialized with ipc == 1. +// send_handle must be a TCP, pipe and UDP handle on Unix, or a TCP handle on Windows, which is a server or a connection (listening or connected state). Bound sockets or pipes will be assumed to be servers. +pub extern send/write(hnd: uv-stream, data: list, send-handle: uv-stream, callback: (uv-status-code) -> io-noexn ()): io-noexn () + c "kk_uv_write2" + +// Same as `stream/write`, but will not queue a write request if it cannot be completed immediately +// Will return either: +// * > 0: number of bytes written (can be less than the supplied buffer size). +// * < 0: negative error code (UV_EAGAIN is returned if no data can be sent immediately). +pub extern stream/try-write(hnd: uv-stream, data: list): io-noexn int32 + c "kk_uv_try_write" + +// Same as `stream/try_write` an extended write function for sending handles over a pipe +// Try to send a handle is not supported on Windows, where it returns UV_EAGAIN. +pub extern send/try-write(hnd: uv-stream, data: list, send-handle: uv-stream): io-noexn int32 + c "kk_uv_try_write2" + +// Returns whether the stream is readable +pub inline extern is-readable(hnd: uv-stream): io-noexn bool + c "kk_uv_is_readable" + +// Returns whether the stream is writable +pub inline extern is-writable(hnd: uv-stream): io-noexn bool + c "kk_uv_is_writable" + +// Enable or disable blocking mode for a stream. +// +// When blocking mode is enabled all writes complete synchronously. The interface remains unchanged otherwise, +// e.g. completion or failure of the operation will still be reported through a callback which is made asynchronously. +// Currently only works on Windows for uv pipe handles. On UNIX platforms, all uv_stream_t handles are supported. +pub inline extern set-blocking(hnd: uv-stream, blocking: bool): io-noexn uv-status-code + c "kk_uv_stream_set_blocking" + +// Returns the write queue size of a uv stream +pub inline extern write-queue-size(hnd: uv-stream): io-noexn int32 + c "kk_uv_stream_get_write_queue_size" diff --git a/lib/uv/timer.kk b/lib/uv/timer.kk new file mode 100644 index 000000000..1896d7dbb --- /dev/null +++ b/lib/uv/timer.kk @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ +module uv/timer +pub import std/time/duration +pub import std/time/timestamp +import std/num/ddouble +import std/num/int64 +import uv/utils + +extern import + c file "inline/timer" + cs file "inline/timer.cs" + js file "inline/timer.js" + +abstract struct timer ( + internal: any +) + +pub extern timer-init(): io-noexn timer + c inline "kk_timer_init(kk_context())" + js inline "_init_timer()" + cs inline "" + +// Start the timer. timeout and repeat are in milliseconds. +// +// If timeout is zero, the callback fires on the next event loop iteration. +// If repeat is non-zero, the callback fires first after timeout milliseconds and then repeatedly after repeat milliseconds. +pub extern timer-start(^t: timer, timeout: int64, repeat: int64, cb: () -> io-noexn ()): io-noexn error<()> + c "kk_timer_start" + js inline "_start_timer(#1,#2,#3,#4)" + cs inline "" + +pub extern timer-stop(t: timer): io-noexn () + c "kk_timer_stop" + js inline "_stop_timer(#1)" + cs inline "" + +// Stop the timer, and if it is repeating restart it using the repeat value as the timeout. +// If the timer has never been started before it returns UV_EINVAL +extern timer-again(^t: timer): io-noexn error<()> + c "kk_timer_again" + wasm inline "kk_std_core_exn__new_Ok(kk_unit_box(kk_Unit), kk_context())" + js inline "" + cs inline "" + +// Set the repeat interval value in milliseconds. +// +// The timer will be scheduled to run on the given interval, +// regardless of the callback execution duration, and will follow +// normal timer semantics in the case of a time-slice overrun. +// +// For example, if a 50ms repeating timer first runs for 17ms, +// it will be scheduled to run again 33ms later. If other tasks +// consume more than the 33ms following the first timer callback, +// then the next timer callback will run as soon as possible. +// +// NOTE: If the repeat value is set from a timer callback it does not immediately take effect. +// If the timer was non-repeating before, it will have been stopped. If it was repeating, +// then the old repeat value will have been used to schedule the next timeout +extern timer-set-repeat(^t: timer, repeat: int64): io-noexn () + c "kk_timer_set_repeat" + wasm inline "kk_Unit" + js inline "" + cs inline "" + +extern timer-get-repeat(^t: timer): io-noexn int64 + c "kk_timer_get_repeat" + wasm inline "-1" + js inline "" + cs inline "" + +// Get the timer due value or 0 if it has expired. -1 is returned on unsupported platforms +// The time is relative to uv_now() +extern timer-get-due-in(^t: timer): io-noexn int64 + c "kk_timer_get_due_in" + wasm inline "-1" + js inline "" + cs inline "" + +// Creates a timer that repeats every `d` duration and calls `f` with the timer as argument. +// +// The timer stops repeating when `f` returns `False`. +pub fun timer(d: duration, f: (timer) -> io-noexn bool): io-noexn timer + val ms = d.milli-seconds.int64 + val t = timer-init() + t.timer-start(ms, ms) fn() + if !f(t) then + t.timer-stop() + () + t diff --git a/lib/uv/tty.kk b/lib/uv/tty.kk new file mode 100644 index 000000000..bd7d5f1fc --- /dev/null +++ b/lib/uv/tty.kk @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------- + Copyright 2024 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/tty +pub import std/data/bytes +pub import uv/event-loop +pub import uv/stream +pub import uv/utils + +extern import + c file "inline/tty.c" + +// A uv tty handle +pub value struct uv-tty { internal : any }; + +// Get the underlying stream handle from a TTY handle +pub fun tty/stream(tty: uv-tty): uv-stream + Uv-stream(tty.internal) + +pub type tty-mode + // Initial/normal terminal mode + UV_TTY_MODE_NORMAL + // Raw input mode (On Windows, ENABLE_WINDOW_INPUT is also enabled) + UV_TTY_MODE_RAW + // Binary-safe I/O mode for IPC (Unix-only) + UV_TTY_MODE_IO + +pub type tty-vtermstate + // The console supports handling of virtual terminal sequences + // (Windows10 new console, ConEmu) + UV_TTY_SUPPORTED + // The console cannot process virtual terminal sequences. (Legacy console) + UV_TTY_UNSUPPORTED + +// Initialize a new TTY stream with the given file descriptor. Usually the file descriptor will be: +// - 0 = stdin +// - 1 = stdout +// - 2 = stderr +// On Unix this function will determine the path of the fd of the terminal using ttyname_r(3), open it, and use it if the passed file descriptor refers to a TTY. +// This lets libuv put the tty in non-blocking mode without affecting other processes that share the tty. +// This function is not thread safe on systems that don’t support ioctl TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and Solaris. +pub extern uv-tty-init(fd: int32): io-noexn error + c "kk_uv_tty_init" + +// Set the TTY using the specified terminal mode. +pub extern uv-tty-set-mode(^tty: uv-tty, mode: tty-mode): io-noexn int32 + c inline "uv_tty_set_mode(kk_owned_handle_to_uv_handle(uv_tty_t, #1), kk_tty_mode_to_uv_tty_mode(#2, kk_context()))" + +// To be called when the program exits. Resets TTY settings to default values for the next process to take over. +// This function is async signal-safe on Unix platforms but can fail with error code UV_EBUSY if you call it when execution is inside uv_tty_set_mode(). +pub extern uv-tty-reset-mode(): io-noexn int32 + c inline "uv_tty_reset_mode()" + +// Gets the current Window size. On success it returns 0. +pub extern uv-tty-get-winsize(^tty: uv-tty): io-noexn error<(int32, int32)> + c "kk_uv_tty_get_winsize" + +// Controls whether console virtual terminal sequences are processed by libuv or console. Useful in particular for enabling ConEmu support of ANSI X3.64 and Xterm 256 colors. Otherwise Windows10 consoles are usually detected automatically. +// This function is only meaningful on Windows systems. On Unix it is silently ignored. +pub extern uv-tty-set-vtermstate(^state: tty-vtermstate): io-noexn () + c inline "uv_tty_set_vterm_state(kk_tty_vtermstate_to_uv_tty_vtermstate(#1, kk_context()))" + +// Get the current state of whether console virtual terminal sequences are handled by libuv or the console. +// This function is not implemented on Unix, where it returns UV_ENOTSUP. +pub extern uv-tty-get-vtermstate(): io-noexn error + c "kk_uv_tty_get_vtermstate" diff --git a/lib/uv/utils.kk b/lib/uv/utils.kk new file mode 100644 index 000000000..9eb065c94 --- /dev/null +++ b/lib/uv/utils.kk @@ -0,0 +1,208 @@ +/*--------------------------------------------------------------------------- + Copyright 2023 Tim Whiting. + + This is free software; you can redistribute it and/or modify it under the + terms of the Apache License, Version 2.0. A copy of the License can be + found in the LICENSE file at the root of this distribution. +---------------------------------------------------------------------------*/ + +module uv/utils +import std/num/int32 + +extern import + c file "inline/utils" + +// The base uv handle type +pub value struct uv-handle { internal: any } + +// Exception info for uv errors +abstract extend type exception-info + pub con AsyncExn( status-code : uv-status-code ) + +// Throw a uv error if the status code is not uv_OK otherwise return `result` +pub inline fun result/untry(code: uv-status-code, result: a): exn a + if code.is-uv_OK then result + else throw-exn(Exception(code.message, AsyncExn(code))) // TODO: Errno refactoring + +// Throw a uv error if the status code is not uv_OK +pub inline fun unit/untry(code: uv-status-code): exn () + if code.is-uv_OK then () + else throw-exn(Exception(code.message, AsyncExn(code))) + +// Convert the integer representation of a status code to a uv-status-code +pub extern int/status-code(code: int32): uv-status-code + c "kk_uv_status_to_status_code" + wasm inline "kk_uv_utils_UV__OK" + cs inline "" + js inline "" + +// UV status codes +pub type uv-status-code + UV_OK + UV_E2BIG + UV_EACCES + UV_EADDRINUSE + UV_EADDRNOTAVAIL + UV_EAFNOSUPPORT + UV_EAGAIN + UV_EAI_ADDRFAMILY + UV_EAI_AGAIN + UV_EAI_BADFLAGS + UV_EAI_BADHINTS + UV_EAI_CANCELED + UV_EAI_FAIL + UV_EAI_FAMILY + UV_EAI_MEMORY + UV_EAI_NODATA + UV_EAI_NONAME + UV_EAI_OVERFLOW + UV_EAI_PROTOCOL + UV_EAI_SERVICE + UV_EAI_SOCKTYPE + UV_EALREADY + UV_EBADF + UV_EBUSY + UV_ECANCELED + UV_ECHARSET + UV_ECONNABORTED + UV_ECONNREFUSED + UV_ECONNRESET + UV_EDESTADDRREQ + UV_EEXIST + UV_EFAULT + UV_EFBIG + UV_EHOSTUNREACH + UV_EINTR + UV_EINVAL + UV_EIO + UV_EISCONN + UV_EISDIR + UV_ELOOP + UV_EMFILE + UV_EMSGSIZE + UV_ENAMETOOLONG + UV_ENETDOWN + UV_ENETUNREACH + UV_ENFILE + UV_ENOBUFS + UV_ENODEV + UV_ENOENT + UV_ENOMEM + UV_ENONET + UV_ENOPROTOOPT + UV_ENOSPC + UV_ENOSYS + UV_ENOTCONN + UV_ENOTDIR + UV_ENOTEMPTY + UV_ENOTSOCK + UV_ENOTSUP + UV_EOVERFLOW + UV_EPERM + UV_EPIPE + UV_EPROTO + UV_EPROTONOSUPPORT + UV_EPROTOTYPE + UV_ERANGE + UV_EROFS + UV_ESHUTDOWN + UV_ESPIPE + UV_ESRCH + UV_ETIMEDOUT + UV_ETXTBSY + UV_EXDEV + UV_UNKNOWN + UV_EOF + UV_ENXIO + UV_EMLINK + UV_ENOTTY + UV_EFTYPE + UV_EILSEQ + UV_ESOCKTNOSUPPORT + UV_EUNATCH + +// UV status code messages +pub fun message(code: uv-status-code): string + match code + UV_OK -> "no error" + UV_E2BIG -> "argument list too long" + UV_EACCES -> "permission denied" + UV_EADDRINUSE -> "address already in use" + UV_EADDRNOTAVAIL -> "address not available" + UV_EAFNOSUPPORT -> "address family not supported" + UV_EAGAIN -> "resource temporarily unavailable" + UV_EAI_ADDRFAMILY -> "address family not supported" + UV_EAI_AGAIN -> "temporary failure" + UV_EAI_BADFLAGS -> "bad ai_flags value" + UV_EAI_BADHINTS -> "invalid value for hints" + UV_EAI_CANCELED -> "request canceled" + UV_EAI_FAIL -> "permanent failure" + UV_EAI_FAMILY -> "ai_family not supported" + UV_EAI_MEMORY -> "out of memory" + UV_EAI_NODATA -> "no address" + UV_EAI_NONAME -> "unknown node or service" + UV_EAI_OVERFLOW -> "argument buffer overflow" + UV_EAI_PROTOCOL -> "resolved protocol is unknown" + UV_EAI_SERVICE -> "service not available for socket type" + UV_EAI_SOCKTYPE -> "socket type not supported" + UV_EALREADY -> "connection already in progress" + UV_EBADF -> "bad file descriptor" + UV_EBUSY -> "resource busy or locked" + UV_ECANCELED -> "operation canceled" + UV_ECHARSET -> "invalid Unicode character" + UV_ECONNABORTED -> "software caused connection abort" + UV_ECONNREFUSED -> "connection refused" + UV_ECONNRESET -> "connection reset by peer" + UV_EDESTADDRREQ -> "destination address required" + UV_EEXIST -> "file already exists" + UV_EFAULT -> "bad address in system call argument" + UV_EFBIG -> "file too large" + UV_EHOSTUNREACH -> "host is unreachable" + UV_EINTR -> "interrupted system call" + UV_EINVAL -> "invalid argument" + UV_EIO -> "i/o error" + UV_EISCONN -> "socket is already connected" + UV_EISDIR -> "illegal operation on a directory" + UV_ELOOP -> "too many symbolic links encountered" + UV_EMFILE -> "too many open files" + UV_EMSGSIZE -> "message too long" + UV_ENAMETOOLONG -> "name too long" + UV_ENETDOWN -> "network is down" + UV_ENETUNREACH -> "network is unreachable" + UV_ENFILE -> "file table overflow" + UV_ENOBUFS -> "no buffer space available" + UV_ENODEV -> "no such device" + UV_ENOENT -> "no such file or directory" + UV_ENOMEM -> "not enough memory" + UV_ENONET -> "machine is not on the network" + UV_ENOPROTOOPT -> "protocol not available" + UV_ENOSPC -> "no space left on device" + UV_ENOSYS -> "function not implemented" + UV_ENOTCONN -> "socket is not connected" + UV_ENOTDIR -> "not a directory" + UV_ENOTEMPTY -> "directory not empty" + UV_ENOTSOCK -> "socket operation on non-socket" + UV_ENOTSUP -> "operation not supported on socket" + UV_EOVERFLOW -> "value too large to be stored in data type" + UV_EPERM -> "operation not permitted" + UV_EPIPE -> "broken pipe" + UV_EPROTO -> "protocol error" + UV_EPROTONOSUPPORT -> "protocol not supported" + UV_EPROTOTYPE -> "protocol wrong type for socket" + UV_ERANGE -> "result too large" + UV_EROFS -> "read-only file system" + UV_ESHUTDOWN -> "cannot send after transport endpoint shutdown" + UV_ESPIPE -> "invalid seek" + UV_ESRCH -> "no such process" + UV_ETIMEDOUT -> "connection timed out" + UV_ETXTBSY -> "text file is busy" + UV_EXDEV -> "cross-device link not permitted" + UV_UNKNOWN -> "unknown error" + UV_EOF -> "end of file" + UV_ENXIO -> "no such device or address" + UV_EMLINK -> "too many links" + UV_ENOTTY -> "inappropriate ioctl for device" + UV_EFTYPE -> "inappropriate file type or format" + UV_EILSEQ -> "illegal byte sequence" + UV_ESOCKTNOSUPPORT -> "socket type not supported" + UV_EUNATCH -> "protocol driver not attached" diff --git a/samples/async/file-async.kk b/samples/async/file-async.kk new file mode 100644 index 000000000..c909a091f --- /dev/null +++ b/samples/async/file-async.kk @@ -0,0 +1,17 @@ +import std/os/file-async +import std/os/path +import std/async + +fun main() + "Read file".println + "---------".println + val res = read-as-string("stack.yaml".path) + res.println + "---------".println + "Done!".println + "Write file".println + "---------".println + val file = open("scratch/test.txt".path, o_RDWR.or(o_CREAT), s_IRWXU) + write-to-file(file, "Hello, world!") + "---------".println + "Done!".println \ No newline at end of file diff --git a/samples/async/file-basic.kk b/samples/async/file-basic.kk new file mode 100644 index 000000000..55af6cbe0 --- /dev/null +++ b/samples/async/file-basic.kk @@ -0,0 +1,71 @@ +import uv/file + +val readCount = 1024 +fun readFileX(fd: uv-file, offset: int = 0, str: ref) + read(fd, readCount.int32, offset.int32) fn(bytes1) + match bytes1 + Ok((bytes, len)) -> + str := !str ++ bytes.string + if len == readCount then + readFileX(fd, offset + readCount, str=str) + else + (!str).println + println("\n\nDone!") + _ -> () + +fun main() + with default-event-loop + // Low Level Async API example (see async-read-file for example using async library) + // open("stack.yaml", o_RDONLY) fn(fd1) + // match fd1 + // Ok(fd) -> + // println("Got fd " ++ fd.internal.int.show ++ "\n") + // readFileX(fd, 0, ref("")) + // _ -> () + // Sync API Example + val stackerr = open-sync("stack.yaml", o_RDONLY, 0.int32) + match stackerr + Ok(stack) -> + val reserr = stack.read-sync(2048.int32) + match reserr + Ok((bytes, _)) -> + println("Got fd sync " ++ stack.internal.int.show ++ "\n") + println(bytes.string) + println("\n\nDone!") + _ -> () + _ -> () + val dir = mkdir-sync("scratch/test", s_IRWXU) + val dir2 = mkdtemp-sync("scratch/test/tmpXXXXXX") + match dir2 + Ok(d) -> + println("Created dir " ++ d ++ "\n") + val tmpf = mkstemp-sync(d ++ "/tmpXXXXXX") + val tmpfile = open-sync("scratch/test/tmpfile.txt", o_CREAT.or(o_RDWR), s_IRWXU) + match tmpfile + Ok(f) -> + println("Created tmp file\n") + match f.write-sync("Hello World!".bytes, 0.int64) + Ok(n) -> ("Wrote " ++ n.show ++ " bytes").println + Error(e) -> throw-exn(e) + Error(e) -> throw-exn(e) + match tmpf + Ok((f, name)) -> + println("Created tmp file " ++ name ++ "\n") + match f.write-sync("Hello World!".bytes, 0.int64) + Ok(n) -> + ("Wrote " ++ n.show ++ " bytes").println + unlink-sync(name) + () + Error(e) -> throw-exn(e) + Error(e) -> throw-exn(e) + rmdir-sync(d) + () + _ -> () + val x = stat-sync("stack.yaml") + match x + Ok(s) -> println(s.show) + Error(e) -> throw-exn(e) + rename-sync("scratch/test.kk", "scratch/test2.kk") + copyfile-sync("scratch/test2.kk", "scratch/test.kk", uv_fs_COPYFILE_EXCL) + unlink-sync("scratch/test2.kk") + () diff --git a/samples/async/fs-event.kk b/samples/async/fs-event.kk new file mode 100644 index 000000000..a3db50fc0 --- /dev/null +++ b/samples/async/fs-event.kk @@ -0,0 +1,16 @@ +import uv/fs-event +import uv/event-loop +import std/async + +val scratch = "scratch" +fun main() + val event = uv-fs-event-init().untry + event.uv-fs-event-start(scratch, []) fn (res) + match res + Ok((path, kind)) -> + path.trace + kind.show.trace + Error(err) -> + err.show.trace + wait(5.seconds) + event.uv-fs-event-stop().untry diff --git a/samples/async/net-async.kk b/samples/async/net-async.kk new file mode 100644 index 000000000..f5486fd27 --- /dev/null +++ b/samples/async/net-async.kk @@ -0,0 +1,31 @@ + + +import uv/net +import std/os/net +import uv/stream +import std/num/int32 +import std/async + +fun handleStream(connection) + val buf = connection.read + connection.write(buf) + connection.shutdown() + +fun handleConnections(connections) + match connections.receive + Ok(c) -> handleStream(c) + _ -> throw("Error accepting connection") + +fun main() + val str = "GET / HTTP/1.1\r\n\r\n" + val server = tcp() + server.bind("0.0.0.0", 8000) + val connections = server.listen + interleaved({while({True}, {handleConnections(connections)})}) fn() + val stream = tcp().connect("127.0.0.1", 8000) + stream.write(str.bytes) + val buf = stream.read + buf.string.println + server.shutdown() + stream.shutdown() // also closes the tcp client handle + println("Done") \ No newline at end of file diff --git a/samples/async/net-echo.kk b/samples/async/net-echo.kk new file mode 100644 index 000000000..dca245298 --- /dev/null +++ b/samples/async/net-echo.kk @@ -0,0 +1,46 @@ + +import uv/net +import std/time +import uv/stream +import std/num/int32 + +// Async Library, but using low level uv functions from std/os/net instead of the high level interface in std/os/net-async + +fun main() + val str = "GET / HTTP/1.1\r\n\r\n" + val client = tcp-init().untry + val server = tcp-init().untry + val sa = Sock-addr(AF_INET, "0.0.0.0", Just(8001.int32)) + server.bind(sa, 0.int32) + server.stream.listen(0.int32) fn(err) + match tcp-init() + Ok(x) -> + server.stream.accept(x.stream) + x.stream.read-start fn(buf) + ("Server got: " ++ buf.string).println + x.stream.write([buf]) fn(e) + ("Write: " ++ e.message).println + x.stream.read-stop() + x.stream.shutdown fn(ers2) + ("Shutdown3: " ++ ers2.message).println + x.uv-handle.close fn() + ("Close3").println + () + () + _ -> () + val a = Sock-addr(AF_INET, "127.0.0.1", Just(8001.int32)) + val er = client.connect(a) fn (err) + ("Connect: " ++ err.message).println + client.stream.read-start fn (buf) + ("Server got: " ++ buf.string).println + client.stream.read-stop() + server.uv-handle.close fn() + ("Close1").println + client.stream.shutdown fn(ers2) + ("Shutdown2: " ++ ers2.message).println + client.uv-handle.close fn() + ("Close2").println + () + client.stream.write([str.bytes]) fn(er2) + ("Write Sync: " ++ er2.message).println + ("Connect Sync: " ++ er.message).println \ No newline at end of file diff --git a/samples/async/poll.kk b/samples/async/poll.kk new file mode 100644 index 000000000..3b637eb9f --- /dev/null +++ b/samples/async/poll.kk @@ -0,0 +1,16 @@ +import uv/poll +import uv/event-loop +import std/async + +fun main() + val poll = uv-fs-poll-init().untry + poll.uv-fs-poll-start("scratch/test.kk", 1000) fn (res) + match res + Ok((old, new)) -> + old.fstat/show.trace + new.fstat/show.trace + Error(err) -> + err.show.trace + wait(5.seconds) + poll.uv-fs-poll-stop() + () diff --git a/samples/async/signal.kk b/samples/async/signal.kk new file mode 100644 index 000000000..08b5b47c4 --- /dev/null +++ b/samples/async/signal.kk @@ -0,0 +1,17 @@ +import uv/signal + +fun main() + with default-event-loop + val n = ref(0) + println("Running main") + signal-start-oneshot(sSIGINT) fn() + println("Got SIGINT!") + signal-start(sSIGINT) fn(s) + if !n == 5 then + s.signal-stop() + () + else + println("Got SIGINT! x " ++ (!n).show) + n := !n + 1 + () + () diff --git a/samples/async/timer.kk b/samples/async/timer.kk new file mode 100644 index 000000000..7b5f93d91 --- /dev/null +++ b/samples/async/timer.kk @@ -0,0 +1,16 @@ +import std/async +import uv/timer +import uv/event-loop +import std/time/duration +import std/time/timestamp + +fun main() + val x = ref(0) + timer(1000.milli-seconds) fn(t) + "Hello From Timer".println; + x := !x + 1 + if !x == 3 then False else True + wait(1.seconds) + "Before timeout".println + wait(10.seconds) + "Hello After Timeout".println \ No newline at end of file diff --git a/samples/async/tty.kk b/samples/async/tty.kk new file mode 100644 index 000000000..4eac622ff --- /dev/null +++ b/samples/async/tty.kk @@ -0,0 +1,10 @@ +import uv/tty +import uv/stream +import std/num/int32 + +fun main() + with default-event-loop + val uv = uv-tty-init(0.int32).untry + uv.stream.try-write(["Hello, world!\n".bytes]) + val size = uv.uv-tty-get-winsize().untry + println("Window size is " ++ size.show) \ No newline at end of file diff --git a/src/Backend/C/FromCore.hs b/src/Backend/C/FromCore.hs index adb10ed35..84a5e39ba 100644 --- a/src/Backend/C/FromCore.hs +++ b/src/Backend/C/FromCore.hs @@ -70,7 +70,7 @@ externalNames cFromCore :: Bool -> CTarget -> BuildType -> FilePath -> Pretty.Env -> Platform -> Newtypes -> Borrowed -> Int -> Bool -> Bool -> Bool -> Bool -> Bool -> Int -> Maybe (Name,Bool) -> String -> Core -> (Doc,Doc,Maybe Doc,Core) cFromCore separateMain ctarget buildType sourceDir penv0 platform newtypes borrowed uniq enableReuse enableSpecialize enableReuseSpecialize enableBorrowInference eagerPatBind stackSize mbMain mainName core - = case runAsm uniq (Env moduleName moduleName False penv externalNames newtypes platform eagerPatBind) + = case runAsm uniq (Env moduleName moduleName False penv externalNames newtypes platform ctarget eagerPatBind) (genModule separateMain ctarget buildType sourceDir penv platform newtypes borrowed enableReuse enableSpecialize enableReuseSpecialize enableBorrowInference stackSize mbMain mainName core) of ((bcore,mainDoc),cdoc,hdoc) -> (cdoc,hdoc,mainDoc,bcore) where @@ -434,7 +434,7 @@ unitSemi tp genTopLevelStringLiteral :: Name -> Visibility -> String -> Asm () genTopLevelStringLiteral name vis s = do let (cstr,clen) = cstring s - decl = if (isPublic vis) then empty else text "static" + decl = if (isPublic vis) then empty else text "" if (clen > 0) then do emitToC (text "kk_declare_string_literal" <.> tupled [decl,ppName name,pretty clen,cstr] {- <.> semi -}) emitToInit (text "kk_init_string_literal" <.> arguments [ppName name]) @@ -784,7 +784,7 @@ genBoxCall tp arg ctx = contextDoc in case cType tp of CFun _ _ -> primName_t prim "function_t" <.> tupled ([arg,ctx]) - CPrim val | val == "kk_unit_t" || val == "bool" || val == "kk_string_t" -- || val == "kk_integer_t" + CPrim val | val == "kk_unit_t" || val == "bool" || val == "kk_string_t" || val == "kk_bytes_t" -- || val == "kk_integer_t" -> primName_t prim val <.> parens arg -- no context CData name -> primName prim (ppName name) <.> tupled [arg,ctx] _ -> primName_t prim (show (ppType tp)) <.> tupled [arg,ctx] -- kk_box_t, int32_t @@ -801,7 +801,7 @@ genUnboxCall tp arg argBorrow ctx = contextDoc in case cType tp of CFun _ _ -> primName_t prim "function_t" <.> tupled [arg,ctx] -- no borrow - CPrim val | val == "kk_unit_t" || val == "bool" || val == "kk_string_t" + CPrim val | val == "kk_unit_t" || val == "bool" || val == "kk_string_t" || val == "kk_bytes_t" -> primName_t prim val <.> parens arg -- no borrow, no context | otherwise -> primName_t prim val <.> tupled ([arg] ++ (if (cPrimCanBeBoxed val) then [argBorrow] else []) ++ [ctx]) @@ -1075,7 +1075,7 @@ genDupDropCallX prim tp args = case cType tp of CFun _ _ -> [(primName_t prim "function_t") <.> args] CBox -> [(primName_t prim "box_t") <.> args] - CPrim val | val == "kk_integer_t" || val == "kk_string_t" || val == "kk_vector_t" || val == "kk_evv_t" || val == "kk_ref_t" || val == "kk_reuse_t" || val == "kk_box_t" + CPrim val | val == "kk_integer_t" || val == "kk_bytes_t" || val == "kk_string_t" || val == "kk_vector_t" || val == "kk_evv_t" || val == "kk_ref_t" || val == "kk_reuse_t" || val == "kk_box_t" -> [(primName_t prim val) <.> args] | otherwise -> -- trace ("** skip dup/drop call: " ++ prim ++ ": " ++ show args) $ @@ -1126,6 +1126,7 @@ genHoleCall tp = -- ppType tp <.> text "_hole()") case cType tp of CPrim "kk_integer_t" -> text "kk_integer_zero" CPrim "kk_string_t" -> text "kk_string_empty()" + CPrim "kk_bytes_t" -> text "kk_bytes_empty()" CPrim "kk_vector_t" -> text "kk_vector_empty()" _ -> text "kk_datatype_null()" @@ -1317,6 +1318,8 @@ cTypeCon c then CPrim "kk_box_t" else if (name == nameTpExternBorrowed) then CPrim "kk_box_t" + else if (name == nameTpBytes) + then CPrim "kk_bytes_t" else if (name == nameTpVector) then CPrim "kk_vector_t" else if (name == nameTpEvv) @@ -2220,12 +2223,14 @@ genExprExternal tname formats [fieldDoc,argDoc] | getName tname == nameCFieldSet -- normal external genExprExternal tname formats argDocs0 - = let name = getName tname - format = getFormat tname formats - argDocs = map (\argDoc -> if (all (\c -> isAlphaNum c || c == '_') (asString argDoc)) then argDoc else parens argDoc) argDocs0 - in return $ case map (\fmt -> ppExternalF name fmt argDocs) $ lines format of - [] -> ([],empty) - ds -> (init ds, last ds) + = do + ctarget <- getCTarget + let name = getName tname + format = getFormat ctarget tname formats + argDocs = map (\argDoc -> if (all (\c -> isAlphaNum c || c == '_') (asString argDoc)) then argDoc else parens argDoc) argDocs0 + return $ case map (\fmt -> ppExternalF name fmt argDocs) $ lines format of + [] -> ([],empty) + ds -> (init ds, last ds) where ppExternalF :: Name -> String -> [Doc] -> Doc ppExternalF name [] args @@ -2244,11 +2249,11 @@ genExprExternal tname formats argDocs0 ppExternalF name (x:xs) args = char x <.> ppExternalF name xs args -getFormat :: TName -> [(Target,String)] -> String -getFormat tname formats - = case lookupTarget (C CDefault) formats of -- TODO: pass real ctarget from flags +getFormat :: CTarget -> TName -> [(Target,String)] -> String +getFormat ctarget tname formats + = case lookupTarget (C ctarget) formats of -- TODO: pass real ctarget from flags Nothing -> -- failure ("backend does not support external in " ++ show tname ++ ": " ++ show formats) - trace( "warning: C backend does not support external in " ++ show tname ) $ + trace( "warning: C backend does not support external in " ++ show tname ++ " looking in " ++ show formats ) $ ("kk_unsupported_external(\"" ++ (show tname) ++ "\")") Just s -> s @@ -2409,6 +2414,7 @@ data Env = Env { moduleName :: Name -- | current modul , substEnv :: [(TName, Doc)] -- | substituting names , newtypes :: Newtypes , platform :: Platform + , ctarget :: CTarget , eagerPatBind :: Bool } @@ -2496,6 +2502,11 @@ getModule = do env <- getEnv return (moduleName env) +getCTarget :: Asm CTarget +getCTarget + = do env <- getEnv + return (ctarget env) + newDefVarName :: String -> Asm Name newDefVarName s = do env <- getEnv diff --git a/src/Common/NamePrim.hs b/src/Common/NamePrim.hs index 25d1501e5..51ad6f058 100644 --- a/src/Common/NamePrim.hs +++ b/src/Common/NamePrim.hs @@ -48,6 +48,7 @@ module Common.NamePrim , nameIntAdd, nameIntSub -- Effects + , nameTpEventLoop , nameTpHTag, nameHTag , nameTpClause, namePerform , nameTpEvv, nameEvvAt, nameEvvIndex, nameEvvIndexMask @@ -122,7 +123,7 @@ module Common.NamePrim , nameTpBool, nameTpInt, nameTpChar , nameTpFloat, nameTpFloat32, nameTpFloat16 - , nameTpString + , nameTpString, nameTpBytes -- , nameTpByte , nameTpInt8, nameTpInt16, nameTpInt32, nameTpInt64 , nameTpSSizeT,nameTpIntPtrT @@ -227,6 +228,7 @@ nameTpPure = preludeName "pure" nameTpAsync = newQualified "std/async" "async" nameTpAsyncX = newQualified "std/async" "asyncx" +nameTpEventLoop = preludeName "event-loop" nameTpBuilder = newQualified "std/text/string" "builder" nameTpArray = newQualified "std/data/array" "array" nameTpMDict = qualify nameDict (newName "mdict") @@ -473,6 +475,7 @@ nameTpFloat16 = coreTypesName "float16" nameTpChar = coreTypesName "char" nameTpString = coreTypesName "string" +nameTpBytes = coreTypesName "bytes" nameTpAny = coreTypesName "any" nameTpVector = coreTypesName "vector" diff --git a/src/Compile/Build.hs b/src/Compile/Build.hs index f55371752..745014b9f 100644 --- a/src/Compile/Build.hs +++ b/src/Compile/Build.hs @@ -311,7 +311,7 @@ moduleCodeGen mainEntries parsedMap tcheckedMap optimizedMap codegenMap inlines = inlinesFromModules imports mbEntry <- getMainEntry (defsGamma defs) mainEntries mod seqIO <- sequentialIO - link <- pooledIO $ codeGen term flags seqIO + link <- pooledIO $! codeGen term flags seqIO (defsNewtypes defs) (defsBorrowed defs) (defsKGamma defs) (defsGamma defs) mbEntry imports mod let mod' = mod{ modPhase = PhaseCodeGen } diff --git a/src/Compile/BuildContext.hs b/src/Compile/BuildContext.hs index d78a276d9..01fe6ee0f 100644 --- a/src/Compile/BuildContext.hs +++ b/src/Compile/BuildContext.hs @@ -56,7 +56,7 @@ import qualified Data.Map.Strict as M import Platform.Config import Lib.PPrint import Common.Name -import Common.NamePrim (nameSystemCore, nameTpNamed, nameTpAsync, isSystemCoreName) +import Common.NamePrim (nameSystemCore, nameTpNamed, nameTpAsync, isSystemCoreName, nameTpEventLoop) import Common.Range import Common.File import Common.Error @@ -398,17 +398,23 @@ buildcThrowOnError buildc buildcCompileMainBody :: Bool -> String -> [String] -> FilePath -> Name -> Name -> Type -> BuildContext -> Build (BuildContext,Maybe (Type, Maybe (FilePath,IO ()))) buildcCompileMainBody addShow expr importDecls sourcePath mainModName exprName tp buildc1 = do -- then compile with a main function - (tp,showIt,mainBody,extraImports) <- completeMain True exprName tp buildc1 + (tp,showIt,mainBody,extraImports,needsEventLoop) <- completeMain True exprName tp buildc1 let mainName = qualify mainModName (newName "@main") mainDef = bunlines $ importDecls ++ extraImports ++ [ "pub fun @expr() : _ ()", "#line 1", " " ++ showIt expr, - "", - "pub fun @main() : io-noexn ()", - " " ++ mainBody, - "" - ] + ""] ++ + if needsEventLoop then + ["pub fun @main() : io-noexn ()", + " default-event-loop(fn() " ++ mainBody ++ ")", + ""] + else + ["pub fun @main() : io-noexn ()", + " " ++ mainBody, + "" + ] + -- trace (show mainDef) $ return () withVirtualFile sourcePath mainDef $ \_ -> do buildc2 <- buildcBuildEx False [mainModName] [mainName] buildc1 hasErr <- buildcHasError buildc2 @@ -425,15 +431,19 @@ bunlines xs = stringToBString $ unlines xs -- complete a main function by adding a show function (if `addShow` is `True`), and -- adding any required default effect handlers (for async, utc etc.) (these are named `@default-`) -completeMain :: Bool -> Name -> Type -> BuildContext -> Build (Type,String -> String,String,[String]) +completeMain :: Bool -> Name -> Type -> BuildContext -> Build (Type,String -> String,String,[String], Bool) completeMain addShow exprName tp buildc = case splitFunScheme tp of Just (_,_,_,eff,resTp) -> let (ls,_) = extractHandledEffect eff -- only effect that are in the evidence vector in do print <- printExpr resTp (mainBody,extraImports) <- addDefaultHandlers rangeNull eff ls [] callExpr - return (resTp,print,mainBody,extraImports) - _ -> return (tp, id, callExpr, []) -- todo: given an error? + if any (\x -> labelName x == nameTpAsync) (fst (extractEffectExtend eff)) then + -- trace "eventloop" $ + return (resTp,print,mainBody,"import uv/event-loop":extraImports,True) + else -- trace ("noeventloop " ++ (show (extractEffectExtend eff))) $ + return (resTp,print,mainBody,extraImports,False) + _ -> return (tp, id, callExpr, [],False) -- todo: given an error? where callExpr = show exprName ++ "()" diff --git a/src/Compile/CodeGen.hs b/src/Compile/CodeGen.hs index f934709a4..def1415a2 100644 --- a/src/Compile/CodeGen.hs +++ b/src/Compile/CodeGen.hs @@ -117,7 +117,7 @@ codeGen term flags sequential newtypes borrowed kgamma gamma entry imported mod link <- backend term flags sequential entry outBase core -- return the link as an action to increase concurrency - return $ \fullImports -> + return $! \fullImports -> -- compilerCatch ("linking in " ++ show (modName mod)) term LinkDone $ do mbRun <- link fullImports -- write interface file last so on any error it will not be written @@ -149,7 +149,7 @@ codeGen term flags sequential newtypes borrowed kgamma gamma entry imported mod -- imported modules. newtypesAll = foldr1 newtypesCompose (map (extractNewtypes . modCore) (loadedModule loaded : loadedModules loaded)) in -} - codeGenC (modSourcePath mod) newtypes borrowed 0 {-unique-} + codeGenC (modSourcePath mod) newtypes borrowed imported 0 {-unique-} {--------------------------------------------------------------- @@ -267,10 +267,10 @@ codeGenJS term flags sequential entry outBase core C backend ---------------------------------------------------------------} -codeGenC :: FilePath -> Newtypes -> Borrowed -> Int +codeGenC :: FilePath -> Newtypes -> Borrowed -> [Module] -> Int -> Terminal -> Flags -> (IO () -> IO ()) -> Maybe (Name,Type) ->FilePath -> Core.Core -> IO Link -codeGenC sourceFile newtypes borrowed0 unique0 term flags sequential entry outBase core0 +codeGenC sourceFile newtypes borrowed0 imported unique0 term flags sequential entry outBase core0 = do let outC = outBase ++ ".c" outH = outBase ++ ".h" sourceDir = dirname sourceFile @@ -302,9 +302,11 @@ codeGenC sourceFile newtypes borrowed0 unique0 term flags sequential entry outBa when (showAsmC flags) (termInfo term (hdoc cdoc)) -- copy libraries - let cc = ccomp flags - eimports = externalImportsFromCore (target flags) bcore - clibs = clibsFromCore flags bcore + let importcores = map (fromJust . modCore) imported + cores = bcore:importcores + cc = ccomp flags + eimports = concatMap (externalImportsFromCore (target flags)) cores + clibs = concatMap (clibsFromCore flags) cores extraIncDirs <- concat <$> mapM (copyCLibrary term flags sequential cc (dirname outBase)) eimports -- return the C compilation and final link as a separate IO action to increase concurrency diff --git a/src/Compile/Options.hs b/src/Compile/Options.hs index 1915360d4..fe62f8a1a 100644 --- a/src/Compile/Options.hs +++ b/src/Compile/Options.hs @@ -1210,6 +1210,7 @@ ccFromPath flags path ccFlagStack = (\stksize -> if stksize > 0 then ["-Wl,--stack," ++ show stksize] else []) } emcc = gcc{ ccFlagsCompile = ccFlagsCompile gcc ++ ["-D__wasi__"], + ccFlagsLink = ccFlagsLink gcc ++ ["-s","WASM_BIGINT"], ccFlagStack = (\stksize -> if stksize == 0 then [] else ["-s","TOTAL_STACK=" ++ show stksize]), ccFlagHeap = (\hpsize -> if hpsize == 0 then [] else ["-s","TOTAL_MEMORY=" ++ show hpsize]), ccTargetExe = (\out -> ["-o", out ++ targetExeExtension (target flags)]), diff --git a/src/Syntax/Parse.hs b/src/Syntax/Parse.hs index 41a3ac4d8..a15bfcbaa 100644 --- a/src/Syntax/Parse.hs +++ b/src/Syntax/Parse.hs @@ -563,6 +563,12 @@ externalCall externalTarget = do specialId "c" return (C CDefault) + <|> + do specialId "wasm" + return (C Wasm) + <|> + do specialId "wasmweb" + return (C WasmWeb) <|> do specialId "cs" return CS diff --git a/support/vscode/koka.language-koka/syntaxes/koka.json b/support/vscode/koka.language-koka/syntaxes/koka.json index 3a6898dec..460b91098 100644 --- a/support/vscode/koka.language-koka/syntaxes/koka.json +++ b/support/vscode/koka.language-koka/syntaxes/koka.json @@ -296,7 +296,7 @@ }, "externid": - { "match": "(?:c|cs|js|inline)\\s+(?:inline\\s+)?(?:(?:file|header-file|header-end-file)\\s+)?(?=[\\\"\\{]|r#*\")" + { "match": "(?:c|cs|js|wasm|wasmweb|inline)\\s+(?:inline\\s+)?(?:(?:file|header-file|header-end-file)\\s+)?(?=[\\\"\\{]|r#*\")" , "name": "keyword.control koka.id.extern.$1" },