From 99354b4cbc90dd6b8b4274375121d3438d4703e7 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Wed, 10 Feb 2021 15:49:01 +0100 Subject: [PATCH 01/26] Start simplifying things (I hope). --- .haxerc | 2 +- haxe_libraries/tink_core.hxml | 6 +- haxe_libraries/tink_testrunner.hxml | 6 +- haxe_libraries/tink_unittest.hxml | 8 +- haxe_libraries/travix.hxml | 11 +- src/tink/streams/IdealStream.hx | 18 +- src/tink/streams/RealStream.hx | 20 +- src/tink/streams/Stream.hx | 913 ++++++++-------------------- tests.hxml | 3 +- tests/RunTests.hx | 14 +- 10 files changed, 281 insertions(+), 720 deletions(-) diff --git a/.haxerc b/.haxerc index 9bb4f16..e68f622 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "3.4.7", + "version": "4.1.5", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index ac2b1c6..82fa3de 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ --D tink_core=1.22.0 -# @install: lix --silent download "gh://github.com/haxetink/tink_core#fa752b88f6757c18da92998aeab5523fe4f28853" into tink_core/1.22.0/github/fa752b88f6757c18da92998aeab5523fe4f28853 --cp ${HAXE_LIBCACHE}/tink_core/1.22.0/github/fa752b88f6757c18da92998aeab5523fe4f28853/src +# @install: lix --silent download "gh://github.com/haxetink/tink_core#396705beabca0e97decb66774be6d056b0d904f1" into tink_core/2.0.0-rc.2/github/396705beabca0e97decb66774be6d056b0d904f1 +-cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/396705beabca0e97decb66774be6d056b0d904f1/src +-D tink_core=2.0.0-rc.2 \ No newline at end of file diff --git a/haxe_libraries/tink_testrunner.hxml b/haxe_libraries/tink_testrunner.hxml index 01a09d1..6ddb4d6 100644 --- a/haxe_libraries/tink_testrunner.hxml +++ b/haxe_libraries/tink_testrunner.hxml @@ -1,6 +1,6 @@ --D tink_testrunner=0.7.2 -# @install: lix --silent download "gh://github.com/haxetink/tink_testrunner#9a2e3cbb9ddff7269e08584f30fc425226f10aae" into tink_testrunner/0.7.2/github/9a2e3cbb9ddff7269e08584f30fc425226f10aae -lib ansi -lib tink_macro -lib tink_streams --cp ${HAXE_LIBCACHE}/tink_testrunner/0.7.2/github/9a2e3cbb9ddff7269e08584f30fc425226f10aae/src +-cp ${SCOPE_DIR}/../testrunner/src +-D tink_testrunner=0.8.0 +--macro Sys.println("haxe_libraries/tink_testrunner.hxml:5: [Warning] Using dev version of library tink_testrunner") \ No newline at end of file diff --git a/haxe_libraries/tink_unittest.hxml b/haxe_libraries/tink_unittest.hxml index 454e37b..f7f7999 100644 --- a/haxe_libraries/tink_unittest.hxml +++ b/haxe_libraries/tink_unittest.hxml @@ -1,6 +1,6 @@ --D tink_unittest=0.6.2 -# @install: lix --silent download "gh://github.com/haxetink/tink_unittest#0b0c7de647e522ca42662e2cdfc59e21ed8d4eb4" into tink_unittest/0.6.2/github/0b0c7de647e522ca42662e2cdfc59e21ed8d4eb4 -lib tink_syntaxhub -lib tink_testrunner --cp ${HAXE_LIBCACHE}/tink_unittest/0.6.2/github/0b0c7de647e522ca42662e2cdfc59e21ed8d4eb4/src ---macro tink.unit.AssertionBufferInjector.use() \ No newline at end of file +-cp ${SCOPE_DIR}/../unittest/src +-D tink_unittest=0.7.0 +${SCOPE_DIR}/../unittest/extraParams.hxml +--macro Sys.println("haxe_libraries/tink_unittest.hxml:4: [Warning] Using dev version of library tink_unittest") \ No newline at end of file diff --git a/haxe_libraries/travix.hxml b/haxe_libraries/travix.hxml index f0ede93..5cd33cc 100644 --- a/haxe_libraries/travix.hxml +++ b/haxe_libraries/travix.hxml @@ -1,6 +1,7 @@ -# @install: lix --silent download "gh://github.com/back2dos/travix#90624892ef6bd5b7bb02d359959d1b3d47553999" into travix/0.14.0/github/90624892ef6bd5b7bb02d359959d1b3d47553999 -# @post-install: cd ${HAXE_LIBCACHE}/travix/0.14.0/github/90624892ef6bd5b7bb02d359959d1b3d47553999 && haxe -cp src --run travix.PostDownload -# @run: haxelib run-dir travix ${HAXE_LIBCACHE}/travix/0.14.0/github/90624892ef6bd5b7bb02d359959d1b3d47553999 +# @install: lix --silent download "haxelib:/travix#0.14.1" into travix/0.14.1/haxelib +# @post-install: cd ${HAXE_LIBCACHE}/travix/0.14.1/haxelib && haxe -cp src --run travix.PostDownload +# @run: haxelib run-dir travix ${HAXE_LIBCACHE}/travix/0.14.1/haxelib -lib tink_cli --cp ${HAXE_LIBCACHE}/travix/0.14.0/github/90624892ef6bd5b7bb02d359959d1b3d47553999/src --D travix=0.14.0 +-cp ${HAXE_LIBCACHE}/travix/0.14.1/haxelib/src +-D travix=0.14.1 +--macro travix.Macro.setup() \ No newline at end of file diff --git a/src/tink/streams/IdealStream.hx b/src/tink/streams/IdealStream.hx index e7c7e36..3b7587b 100644 --- a/src/tink/streams/IdealStream.hx +++ b/src/tink/streams/IdealStream.hx @@ -6,26 +6,12 @@ using tink.CoreApi; @:forward @:transitive abstract IdealStream(Stream) from Stream to Stream { - @:from - public static inline function promiseOfIdealStream(p:Promise>):IdealStream - return cast Stream.promise(p); - - @:from - public static inline function promiseOfStreamNoise(p:Promise>):IdealStream - return cast Stream.promise(p); - - public function collect():Future> { - var buf = []; - return this.forEach(function(x) { - buf.push(x); - return Resume; - }).map(function(c) return buf); - } + } typedef IdealStreamObject = StreamObject; class IdealStreamBase extends StreamBase { - override public function idealize(rescue:Error->Stream):IdealStream + override public function idealize(rescue:Error->Stream):IdealStream return this; } \ No newline at end of file diff --git a/src/tink/streams/RealStream.hx b/src/tink/streams/RealStream.hx index c17239b..ae08dae 100644 --- a/src/tink/streams/RealStream.hx +++ b/src/tink/streams/RealStream.hx @@ -9,30 +9,18 @@ abstract RealStream(Stream) from Stream to Strea @:from public static inline function promiseOfIdealStream(p:Promise>):RealStream return cast Stream.promise(p); - + @:from public static inline function promiseOfStreamNoise(p:Promise>):RealStream return cast Stream.promise(p); - + @:from public static inline function promiseOfRealStream(p:Promise>):RealStream return cast Stream.promise(p); - + @:from public static inline function promiseOfStreamError(p:Promise>):RealStream return cast Stream.promise(p); - - public function collect():Promise> { - var buf = []; - return this.forEach(function(x) { - buf.push(x); - return Resume; - }).map(function(c) return switch c { - case Depleted: Success(buf); - case Failed(e): Failure(e); - case Halted(_): throw 'unreachable'; - }); - } } + typedef RealStreamObject = StreamObject; -typedef RealStreamBase = StreamBase; \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 5632bab..a74a45a 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -1,743 +1,328 @@ package tink.streams; -import tink.streams.IdealStream; - +import tink.core.Callback; using tink.CoreApi; @:forward @:transitive -abstract Stream(StreamObject) from StreamObject to StreamObject { - - public var depleted(get, never):Bool; - inline function get_depleted() - return this.depleted; - - @:to function dirty():Stream - return cast this; - - static public function single(i:Item):Stream - return new Single(i); +abstract Stream(StreamObject) from StreamObject { + + static public function generate(generator:()->Surprise):Stream { + function rec():AsyncLink + return Future.irreversible(yield -> { + generator().handle(o -> yield(switch o { + case Success(data): Cons(data, rec()); + case Failure(failure): Fin(failure); + })); + }); - @:from static public function ofIterator(i:Iterator):Stream { - return Generator.stream(function next(step) step(if(i.hasNext()) Link(i.next(), Generator.stream(next)) else End)); - } - - static public function flatten(stream:Stream, Quality>):Stream { - return stream.regroup(function(arr) return Converted(arr[0])); + return new AsyncLinkStream(rec()); } - #if cs - // This is to mitigate an error in the c# generator that it generates paramterized calls - // with type parameters which is not defined in scope - // similar to https://github.com/HaxeFoundation/haxe/issues/6833 - @:from static public function dirtyFuture(f:Future>):Stream - return new FutureStream(f); - #end - - @:from static public function future(f:Future>):Stream - return new FutureStream(f); - - #if cs - // This is to mitigate an error in the c# generator that it generates paramterized calls - // with type parameters which is not defined in scope - // similar to https://github.com/HaxeFoundation/haxe/issues/6833 - @:from static public function dirtyPromise(f:Promise>):Stream - return dirtyFuture(f.map(function (o) return switch o { - case Success(s): s; - case Failure(e): ofError(e); - })); - #end - - @:from static inline function promiseIdeal(f:Promise>):Stream - return cast promise(f); - - @:from static inline function promiseReal(f:Promise>):Stream - return cast promise(f); - - @:from static public function promise(f:Promise>):Stream - return future(f.map(function (o) return switch o { - case Success(s): s.dirty(); - case Failure(e): ofError(e); - })); - - @:from static public function ofError(e:Error):Stream - return new ErrorStream(e); - - #if (nodejs && !macro) - @:noUsing static public inline function ofNodeStream(name:String, r:js.node.stream.Readable.IReadable, ?options:{ ?onEnd:Void->Void }):RealStream { - return tink.streams.nodejs.NodejsStream.wrap(name, r, options == null ? null : options.onEnd); - } - #end -} + static public inline function empty():Stream + return @:privateAccess + #if cs + new Empty(); + #else + cast Empty.INST; + #end -enum RegroupStatus { - Flowing:RegroupStatus; - Errored(e:Error):RegroupStatus; - Ended:RegroupStatus; -} + @:op(a...b) static function concat(a:Stream, b:Stream) + return new StreamPair(a, b); -enum RegroupResult { - Converted(data:Stream, ?untouched:Array):RegroupResult; - Terminated(data:Option>):RegroupResult; - Untouched:RegroupResult; - Errored(e:Error):RegroupResult; -} + @:from static public function ofIterator(t:Iterator):Stream + return AsyncLinkStream.ofIterator(t); -@:forward -abstract Regrouper(RegrouperBase) from RegrouperBase to RegrouperBase { - @:from - public static function ofIgnorance(f:Array->Future>):Regrouper - return {apply: function(i, _) return f(i)}; - @:from - public static function ofIgnoranceSync(f:Array->RegroupResult):Regrouper - return {apply: function(i, _) return Future.sync(f(i))}; - @:from - public static function ofFunc(f:Array->RegroupStatus->Future>):Regrouper - return {apply: f}; - @:from - public static function ofFuncSync(f:Array->RegroupStatus->RegroupResult):Regrouper - return {apply: function(i, s) return Future.sync(f(i, s))}; + @:from static public function promise(p:Promise>):Stream + return new PromiseStream(p); } -private typedef RegrouperBase = { - function apply(input:Array, status:RegroupStatus):Future>; -} +private class PromiseStream implements StreamObject { + final stream:Promise>; -private class RegroupStream extends CompoundStream { - public function new(source:Stream, f:Regrouper, ?prev, ?buf) { - if(prev == null) prev = Empty.make(); - if(buf == null) buf = []; - - var ret = null; - var terminated = false; - var next = Stream.future(source.forEach(function(item) { - buf.push(item); - return f.apply(buf, Flowing).map(function (o):Handled return switch o { - case Converted(v, untouched): - ret = v; - buf = untouched; - Finish; - case Terminated(v): - ret = v.or(Empty.make); - terminated = true; - Finish; - case Untouched: - Resume; - case Errored(e): - Clog(e); - }); - }).map(function(o):Stream return switch o { - case Failed(e): Stream.ofError(e); - case Depleted if(buf.length == 0): Empty.make(); - case Depleted: - Stream.future(f.apply(buf, Ended).map(function(o) return switch o { - case Converted(v): v; - case Terminated(v): v.or(Empty.make); - case Untouched: Empty.make(); - case Errored(e): cast Stream.ofError(e); - })); - case Halted(_) if(terminated): ret; - case Halted(rest): new RegroupStream(rest, f, ret, buf); - case Clogged(e, _): cast new ErrorStream(e); // the regroup stream should terminate when an error occurs during the regroup process - })); - // TODO: get rid of those casts in this function + public function new(stream) + this.stream = stream; - super([prev, next]); - } + public function forEach(f:Consumer):Future> + return stream.next(s -> s.forEach(f)).map(o -> switch o { + case Success(data): + data; + case Failure(e): + Stopped(Stream.empty(), Failure(e)); + }); } -enum Handled { - BackOff:Handled; - Finish:Handled; - Resume:Handled; - Clog(e:Error):Handled; +class SingleItem implements StreamObject { + final item:Item; + public function new(item) + this.item = item; + + public function forEach(f:Consumer) + return new Future>( + trigger -> Helper.trySync( + f.apply(item, Step.new), + s -> trigger(switch s { + case null: + Done; + case v: + Stopped(Stream.empty(), Success(v.unwrap())); + }) + ) + ); } -enum Conclusion { - Halted(rest:Stream):Conclusion; - Clogged(error:Error, at:Stream):Conclusion; - Failed(error:Error):Conclusion; - Depleted:Conclusion; +enum IterationResult { + Done; + Stopped(rest:Stream, result:Outcome); } -enum ReductionStep { - Progress(result:Result):ReductionStep; - Crash(e:Error):ReductionStep; -} -enum Reduction { - Crashed(error:Error, at:Stream):Reduction; - Failed(error:Error):Reduction; - Reduced(result:Result):Reduction; +interface StreamObject { + function forEach(f:Consumer):Future>; } -private class CloggedStream extends StreamBase { - - var rest:Stream; - var error:Error; - - public function new(rest, error) { - this.rest = rest; - this.error = error; - } - - override function next():Future> - return Future.sync(Step.Fail(error)); - - override public function forEach(handler:Handler):Future> - return Future.sync(cast Conclusion.Clogged(error, rest)); +typedef AsyncLink = Future>; +typedef AsyncLinkKind = LinkKind> +enum LinkKind { + Fin(error:Null); + Cons(head:Item, tail:Tail); } -private class ErrorStream extends StreamBase { +abstract Step(Null) from Null { + public inline function new(v:Result) + this = v; - var error:Error; - - public function new(error) - this.error = error; - - override function next():Future> - return Future.sync(Step.Fail(error)); - - override public function forEach(handler:Handler):Future> - return Future.sync(Conclusion.Failed(error)); - -} - -interface StreamObject { - /** - * `true` if there is no data in this stream - */ - var depleted(get, never):Bool; - function next():Future>; - /** - * Create a new stream by performing an N-to-M mapping - */ - function regroup(f:Regrouper):Stream; - /** - * Create a new stream by performing an 1-to-1 mapping - */ - function map(f:Mapping):Stream; - /** - * Create a filtered stream - */ - function filter(f:Filter):Stream; - function retain():Void->Void; - /** - * Create an IdealStream. - * The stream returned from the `rescue` function will be recursively rescued by the same `rescue` function - */ - function idealize(rescue:Error->Stream):IdealStream; - /** - * Append another stream after this - */ - function append(other:Stream):Stream; - /** - * Prepend another stream before this - */ - function prepend(other:Stream):Stream; - function blend(other:Stream):Stream; - function decompose(into:Array>):Void; - /** - * Iterate this stream. - * The handler should return one of the following values (or a `Future` of it) - * - Backoff: stop the iteration before the current item - * - Finish: stop the iteration after the current item - * - Resume: continue the iteration - * - Clog(error): produce an error - * @return A conclusion that indicates how the iteration was ended - * - Depleted: there are no more data in the stream - * - Failed(err): the stream produced an error - * - Halted(rest): the iteration was halted by `Backoff` or `Finish` - * - Clogged(err): the iteration was halted by `Clog(err)` - */ - function forEach(handle:Handler):Future>; - /** - * Think Lambda.fold() - */ - function reduce(initial:Result, reducer:Reducer):Future>; + public inline function unwrap():Result + return + #if debug + switch this { + case null: throw 'ohno!'; + case v: v; + } + #else + cast this; + #end } -class Empty extends StreamBase { - - function new() {} - - override function get_depleted() - return true; - - override function next():Future> - return Future.sync(Step.End); - - override public function forEach(handler:Handler):Future> - return Future.sync(Depleted); - - static var inst = new Empty(); - - static public inline function make():Stream - return (cast inst : Stream); +typedef Consume = (item:Item, done:Result->Step)->Null>>; +abstract Consumer(Consume) from Consume { + public inline function apply(item, done):Future> + return switch this(item, done) { + case null: Future.NOISE; + case v: v; + } } -abstract Mapping(Regrouper) to Regrouper { - - inline function new(o) - this = o; - - @:from static function ofNext(n:Next):Mapping - return new Mapping({ - apply: function (i:Array, _) return n(i[0]).next(function(o) return Converted(Stream.single(o))).recover(Errored), - }); - - @:from static function ofAsync(f:In->Future):Mapping - return new Mapping({ - apply: function (i:Array, _) return f(i[0]).map(function(o) return Converted(Stream.single(o))), - }); +class Empty implements StreamObject { - @:from static function ofSync(f:In->Outcome):Mapping - return new Mapping({ - apply: function (i:Array, _) return Future.sync(switch f(i[0]) { - case Success(v): Converted(Stream.single(v)); - case Failure(e): Errored(e); - }), - }); + static final INST:StreamObject = new Empty(); - @:from static function ofPlain(f:In->Out):Mapping - return new Mapping({ - apply: function (i:Array, _) return Future.sync(Converted(Stream.single(f(i[0])))), - }); + function new() {} + public function forEach(f:Consumer):Future> + return Done; } -abstract Filter(Regrouper) to Regrouper { +class StreamPair implements StreamObject { + final l:Stream; + final r:Stream; - inline function new(o) - this = o; + public function new(l, r) { + this.l = l; + this.r = r; + } - @:from static function ofNext(n:Next):Filter - return new Filter({ - apply: function (i:Array, _) return n(i[0]).next(function (matched) return Converted(if (matched) Stream.single(i[0]) else Empty.make())).recover(Errored), - }); + public function forEach(f:Consumer) + return new Future>(trigger -> { + final ret = new CallbackLinkRef(); - @:from static function ofAsync(f:T->Future):Filter - return new Filter({ - apply: function (i:Array, _) return f(i[0]).map(function (matched) return Converted(if (matched) Stream.single(i[0]) else Empty.make())), - }); - - @:from static function ofSync(f:T->Outcome):Filter - return new Filter({ - apply: function (i:Array, _) return Future.sync(switch f(i[0]) { - case Success(v): Converted(if(v)Stream.single(i[0]) else Empty.make()); - case Failure(e): Errored(e); - }), - }); + ret.link = Helper.trySync(l.forEach(f), res -> switch res { + case Done: + ret.link = Helper.trySync(r.forEach(f), trigger); + case Stopped(rest, result): + trigger(Stopped(new StreamPair(rest, r), result)); + }); - @:from static function ofPlain(f:T->Bool):Filter - return new Filter({ - apply: function (i:Array, _) return Future.sync(Converted(if (f(i[0])) Stream.single(i[0]) else Empty.make())), + return ret; }); - } -class StreamBase implements StreamObject { - - public var depleted(get, never):Bool; - function get_depleted() return false; +class MapStream implements StreamObject { + final source:Stream; + final transform:In->Future; - var retainCount = 0; - - public function retain() { - retainCount++; - var retained = true; - return function () { - if (retained) { - retained = false; - if (--retainCount == 0) - destroy(); - } - } - } - - public function next():Future> { - throw 'not implemented'; - // var item = null; - // return this.forEach(function(i) { - // item = i; - // return Finish; - // }).map(function(o):Step return switch o { - // case Depleted: End; - // case Halted(rest): Link(item, rest); - // case Failed(e): Fail(e); - // }); + public function new(source, transform) { + this.source = source; + this.transform = transform; } - public function regroup(f:Regrouper):Stream - return new RegroupStream(this, f); - - public function map(f:Mapping):Stream - return regroup(f); - - public function filter(f:Filter):Stream - return regroup(f); - - function destroy() {} - - public function append(other:Stream):Stream - return - if (depleted) other; - else CompoundStream.of([this, other]); - - public function prepend(other:Stream):Stream - return - if (depleted) other; - else CompoundStream.of([other, this]); + public function forEach(f:Consumer):Future> + return source.forEach((item, done) -> transform(item).flatMap(out -> f.apply(out, done))); +} - public function blend(other:Stream):Stream - return - if (depleted) other; - else new BlendStream(this, other); +class Grouped implements StreamObject { + final source:Stream, Quality>; - public function decompose(into:Array>) - if (!depleted) - into.push(this); + public function new(source) + this.source = source; - public function idealize(rescue:Error->Stream):IdealStream + public function forEach(f:Consumer):Future> return - if (depleted) Empty.make(); - else new IdealizeStream(this, rescue); - - public function reduce(initial:Result, reducer:Reducer):Future> - return Future.async(function (cb:Reduction->Void) { - forEach(function (item) - return reducer.apply(initial, item).map( - function (o):Handled return switch o { - case Progress(v): initial = v; Resume; - case Crash(e): Clog(e); + source.forEach((group, done) -> + AsyncLinkStream.ofIterator(group.iterator()) + .forEach(f).map(res -> switch res { + case Done: null; + case Stopped(rest, result): done(new Pair(rest, result)); }) - ).handle(function (c) switch c { - case Failed(e): cb(Failed(e)); - case Depleted: cb(Reduced(initial)); - case Halted(_): throw "assert"; - case Clogged(e, rest): cb(Crashed(e, rest)); + ).map(function (o):IterationResult return switch o { + case Done: Done; + case Stopped(rest, result): + var rest = new Grouped(rest); + switch result { + case Success({ a: left, b: res }): + Stopped(new StreamPair(left, rest), res); + case Failure(failure): + Stopped(rest, Failure(failure)); + } }); - }); - - public function forEach(handler:Handler):Future> - return throw 'not implemented'; - } -class IdealizeStream extends IdealStreamBase { - var target:Stream; - var rescue:Error->Stream; - - public function new(target, rescue) { - this.target = target; - this.rescue = rescue; - } - - override function get_depleted() - return target.depleted; - - override function next():Future> - return target.next().flatMap(function(v) return switch v { - case Fail(e): rescue(e).idealize(rescue).next(); - default: Future.sync(cast v); - }); - - override public function forEach(handler:Handler):Future> +private class Helper { + static public function noop(_:Dynamic) {} + static public inline function trySync(f:Future, cb:X->Void) { + var tmp = f.handle(Helper.noop); return - Future.async(function (cb:Conclusion->Void) - target.forEach(handler).handle(function (end) switch end { - case Depleted: - cb(Depleted); - case Halted(rest): - cb(Halted(rest.idealize(rescue))); - case Clogged(e, at): - cb(Clogged(e, at.idealize(rescue))); - case Failed(e): - rescue(e).idealize(rescue).forEach(handler).handle(cb); - }) - ); - + switch f.status { + case Ready(result): + cb(result.get()); + null; + default: + swapHandler(f, tmp, cb); + } + } + static public function swapHandler(f:Future, prev:CallbackLink, cb) { + var ret = f.handle(cb); + prev.cancel(); + return ret; + } } -class Single extends StreamBase { - var value:Lazy; - - public function new(value) - this.value = value; - - override function next():Future> - return Future.sync(Link(value.get(), Empty.make())); - - override public function forEach(handle:Handler) - return handle.apply(value).map(function (step):Conclusion return switch step { - case BackOff: - Halted(this); - case Finish: - Halted(Empty.make()); - case Resume: - Depleted; - case Clog(e): - Clogged(e, this); +class AsyncLinkStream implements StreamObject { + final link:AsyncLink; + + public function new(link) + this.link = link; + + public function forEach(f:Consumer):Future> + return new Future(yield -> { + final wait = new CallbackLinkRef(); + function loop(cur:AsyncLink) { + while (true) { + switch cur.status { + case Ready(result): + switch result.get() { + case Fin(v): + yield(switch v { + case null: Done; + case error: Stopped(Stream.empty(), Failure(error)); + }); + case Cons(item, tail): + function process(progress:Future>) { + switch progress.status { + case Ready(result): + switch result.get() { + case null: + cur = tail; + return true; + case v: + yield(Stopped(new AsyncLinkStream(tail), Success((cast v:Item)))); + } + default: + var tmp = progress.handle(Helper.noop); + if (progress.status.match(Ready(_))) + return process(progress); + else + wait.link = Helper.swapHandler(progress, tmp, _ -> process(progress)); + } + return false; + } + if (process(f.apply(item, Step.new))) continue; + } + default: + wait.link = cur.handle(Helper.noop); + if (cur.status.match(Ready(_))) + continue; + else + wait.link = Helper.swapHandler(cur, wait, _ -> loop(cur));// this is very lazy + } + break; + } + } + loop(link); + return wait; }); -} - -abstract Handler(Item->Future>) { - inline function new(f) - this = f; - - public inline function apply(item):Future> - return this(item); - - @:from static function ofSafeSync(f:Item->Handled):Handler - return new Handler(function (i) return Future.sync(f(i))); - - @:from static function ofUnknownSync(f:Item->Handled):Handler - return new Handler(function (i) return Future.sync(f(i))); - @:from static function ofSafe(f:Item->Future>):Handler - return new Handler(f); + static function iteratorLink(i:Iterator):Future> + return Future.lazy(() -> if (i.hasNext()) Cons(i.next(), iteratorLink(i)) else Fin(null)); - @:from static function ofUnknown(f:Item->Future>):Handler - return new Handler(f); + static public function ofIterator(i:Iterator):Stream + return new AsyncLinkStream(iteratorLink(i)); } -abstract Reducer(Result->Item->Future>) { - inline function new(f) - this = f; - - public inline function apply(res, item):Future> - return this(res, item); - - @:from static function ofSafeSync(f:Result->Item->ReductionStep):Reducer - return new Reducer(function (res, cur) return Future.sync(f(res, cur))); - - @:from static function ofUnknownSync(f:Result->Item->ReductionStep):Reducer - return new Reducer(function (res, cur) return Future.sync(f(res, cur))); - - @:from static function ofSafe(f:Result->Item->Future>):Reducer - return new Reducer(f); - - @:from static function ofPlainSync(f:Result->Item->Result):Reducer - return new Reducer(function (res, cur) return Future.sync(Progress(f(res, cur)))); - - @:from static function ofUnknown(f:Result->Item->Future>):Reducer - return new Reducer(f); - - @:from static function ofPromiseBased(f:Result->Item->Promise) - return new Reducer(function (res, cur) return f(res, cur).map(function (s) return switch s { - case Success(r): Progress(r); - case Failure(e): Crash(e); - })); - -} - -#if (java || cs) -private abstract Parts(Array) { - public var length(get, never):Int; - inline function get_length() return this.length; - - public function new(parts:Array>) - this = parts; - - @:arrayAccess function get(index:Int):Stream - return this[index]; - - @:arrayAccess function set(index:Int, value:Stream):Stream - return this[index] = value; - - public function copy():Parts - return new Parts(cast this.copy()); - - public function slice(start:Int, ?end:Int):Parts - return new Parts(cast this.slice(start, end)); - - @:from static function ofArray(a:Array>) - return new Parts(a); -} -#else -private typedef Parts = Array>; -#end - -private class CompoundStream extends StreamBase { - - var parts:Parts; - - function new(parts) - this.parts = parts; - - override function get_depleted() - return switch parts.length { - case 0: true; - case 1: parts[0].depleted; - default: false; - } - - override function next():Future> { - return if(parts.length == 0) Future.sync(Step.End); - else parts[0].next().flatMap(function(v) return switch v { - case End if(parts.length > 1): parts[1].next(); - case Link(v, rest): - var copy = parts.copy(); - copy[0] = rest; - Future.sync(Link(v, new CompoundStream(copy))); - default: Future.sync(v); - }); - } - - override public function decompose(into:Array>):Void - for (p in parts) - p.decompose(into); - - override public function forEach(handler:Handler):Future> - return Future.async(consumeParts.bind(cast parts, handler, _)); - - static function consumeParts(parts:Parts, handler:Handler, cb:Conclusion->Void) - if (parts.length == 0) - cb(Depleted); - else - (parts[0]:Stream).forEach(handler).handle(function (o) switch o { - case Depleted: - - consumeParts(parts.slice(1), handler, cb); +// typedef SyncLink = LinkKind>>; - case Halted(rest): +// class SyncLinkStream implements StreamObject { +// final link:SyncLink; - parts = parts.copy(); - parts[0] = rest; - cb(Halted(new CompoundStream(parts))); +// public function new(link) +// this.link = link; - case Clogged(e, at): +// public function forEach(f:Consumer) +// return new Future>(trigger -> { +// final wait = new CallbackLinkRef(); +// var running = true; - if (at.depleted) - parts = parts.slice(1); - else { - parts = parts.copy(); - parts[0] = at; - } - - cb(Clogged(e, new CompoundStream(parts))); +// function yield(v) { +// running = false; +// trigger(v); +// } - case Failed(e): +// function process(cur:SyncLink) +// while (running) +// switch cur { +// case Fin(error): +// yield(switch error { +// case null: Done; +// case e: Stopped(Stream.empty(), Failure(e)); +// }); +// case Cons(head, tail): - cb(Failed(e)); +// } - }); +// process(link); - static public function of(streams:Array>):Stream { +// return wait; +// }); +// } - var ret = []; +// class SignalStream - for (s in streams) - s.decompose(ret); +class SignalStream extends AsyncLinkStream { + public function new(signal:Signal>) + super(makeLink(signal)); + static function makeLink(signal:Signal>):AsyncLink return - if (ret.length == 0) Empty.make(); - else new CompoundStream(ret); - } - -} - -class FutureStream extends StreamBase { - var f:Future>; - public function new(f) - this.f = f; - - override function next():Future> - return f.flatMap(function(s) return s.next()); - - override public function forEach(handler:Handler) { - return Future.async(function (cb) { - f.handle(function (s) s.forEach(handler).handle(cb)); - }); - } -} - -class BlendStream extends Generator { - - public function new(a:Stream, b:Stream) { - var first = null; - - function wait(s:Stream) { - return s.next().map(function(o) { - if(first == null) first = s; - return o; - }); - } - - var n1 = wait(a); - var n2 = wait(b); - - super(Future.async(function(cb) { - n1.first(n2).handle(function(o) switch o { - case Link(item, rest): - cb(Link(item, new BlendStream(rest, first == a ? b : a))); - case End: - (first == a ? n2 : n1).handle(cb); - case Fail(e): - cb(Fail(e)); - }); - })); - - } -} - - -class Generator extends StreamBase { - var upcoming:Future>; - - function new(upcoming) - this.upcoming = upcoming; - - override function next():Future> - return upcoming; - - override public function forEach(handler:Handler) - return Future.async(function (cb:Conclusion->Void) - upcoming.handle(function (e) switch e { - case Link(v, then): - handler.apply(v).handle(function (s) switch s { - case BackOff: - cb(Halted(this)); - case Finish: - cb(Halted(then)); - case Resume: - then.forEach(handler).handle(cb); - case Clog(e): - cb(Clogged(e, this)); - }); - case Fail(e): - cb(Failed(e)); - case End: - cb(Depleted); - }) - ); - - static public function stream(step:(Step->Void)->Void) { - return new Generator(Future.async(step)); - } - -} - -enum Step { - Link(value:Item, next:Stream):Step; - Fail(e:Error):Step; - End:Step; -} - -class SignalStream extends Generator { - public function new(signal:Signal>) - super( - signal.nextTime().map(function(o):Step return switch o { - case Data(data): Link(data, new SignalStream(signal)); - case Fail(e): Fail(e); - case End: End; - }).eager() // this must be eager, otherwise the signal will "run away" if there's no consumer for this stream - ); + signal.nextTime().map(function(o):AsyncLinkKind return switch o { + case Data(data): Cons(data, makeLink(signal)); + case Fail(e): Fin(e); + case End: Fin(null); + }).eager(); // this must be eager, otherwise the signal will "run away" if there's no consumer for this stream } enum Yield { - Data(data:Item):Yield; - Fail(e:Error):Yield; - End:Yield; -} + Data(data:Item):Yield; + Fail(e:Error):Yield; + End:Yield; +} \ No newline at end of file diff --git a/tests.hxml b/tests.hxml index dace381..a74b72e 100644 --- a/tests.hxml +++ b/tests.hxml @@ -1,3 +1,4 @@ -cp ./tests/ -main RunTests --lib tink_unittest \ No newline at end of file +-lib tink_unittest +-D no-deprecation-warnings \ No newline at end of file diff --git a/tests/RunTests.hx b/tests/RunTests.hx index 34cb93f..2c39f99 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -8,17 +8,17 @@ using tink.CoreApi; class RunTests { static function main() { - + #if python (cast python.lib.Sys).setrecursionlimit(9999); #end - + Runner.run(TestBatch.make([ - new StreamTest(), - new BlendTest(), - new NextTest(), - new SignalStreamTest(), + // new StreamTest(), + // new BlendTest(), + // new NextTest(), + // new SignalStreamTest(), ])).handle(Runner.exit); } - + } From 92d20f46425f4ed10e37c3628119d085ea01b45f Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Wed, 10 Feb 2021 16:40:52 +0100 Subject: [PATCH 02/26] More fun. --- haxe_libraries/hxnodejs.hxml | 11 +++-- haxe_libraries/tink_unittest.hxml | 6 +-- src/tink/streams/IdealStream.hx | 16 +------ src/tink/streams/RealStream.hx | 25 +--------- src/tink/streams/Stream.hx | 39 ++++++++++++++-- tests/RunTests.hx | 2 +- tests/SignalStreamTest.hx | 42 ++++++++--------- tests/StreamTest.hx | 76 +++++++++++++++---------------- 8 files changed, 107 insertions(+), 110 deletions(-) diff --git a/haxe_libraries/hxnodejs.hxml b/haxe_libraries/hxnodejs.hxml index 21bc573..c075488 100644 --- a/haxe_libraries/hxnodejs.hxml +++ b/haxe_libraries/hxnodejs.hxml @@ -1,6 +1,7 @@ -# @install: lix --silent download haxelib:hxnodejs#4.0.9 into hxnodejs/4.0.9/haxelib --D hxnodejs=4.0.9 --cp ${HAXESHIM_LIBCACHE}/hxnodejs/4.0.9/haxelib/src --D nodejs +# @install: lix --silent download "haxelib:/hxnodejs#12.1.0" into hxnodejs/12.1.0/haxelib +-cp ${HAXE_LIBCACHE}/hxnodejs/12.1.0/haxelib/src +-D hxnodejs=12.1.0 --macro allowPackage('sys') ---macro _hxnodejs.VersionWarning.include() +# should behave like other target defines and not be defined in macro context +--macro define('nodejs') +--macro _internal.SuppressDeprecated.run() diff --git a/haxe_libraries/tink_unittest.hxml b/haxe_libraries/tink_unittest.hxml index f7f7999..b00d844 100644 --- a/haxe_libraries/tink_unittest.hxml +++ b/haxe_libraries/tink_unittest.hxml @@ -1,6 +1,6 @@ +# @install: lix --silent download "gh://github.com/haxetink/tink_unittest#1c26b50064855d3e7810d4d871103964d5ac9fba" into tink_unittest/0.7.0/github/1c26b50064855d3e7810d4d871103964d5ac9fba -lib tink_syntaxhub -lib tink_testrunner --cp ${SCOPE_DIR}/../unittest/src +-cp ${HAXE_LIBCACHE}/tink_unittest/0.7.0/github/1c26b50064855d3e7810d4d871103964d5ac9fba/src -D tink_unittest=0.7.0 -${SCOPE_DIR}/../unittest/extraParams.hxml ---macro Sys.println("haxe_libraries/tink_unittest.hxml:4: [Warning] Using dev version of library tink_unittest") \ No newline at end of file +--macro tink.unit.AssertionBufferInjector.use() \ No newline at end of file diff --git a/src/tink/streams/IdealStream.hx b/src/tink/streams/IdealStream.hx index 3b7587b..f312bbd 100644 --- a/src/tink/streams/IdealStream.hx +++ b/src/tink/streams/IdealStream.hx @@ -1,17 +1,3 @@ package tink.streams; -import tink.streams.Stream; - -using tink.CoreApi; - -@:forward @:transitive -abstract IdealStream(Stream) from Stream to Stream { - -} - -typedef IdealStreamObject = StreamObject; - -class IdealStreamBase extends StreamBase { - override public function idealize(rescue:Error->Stream):IdealStream - return this; -} \ No newline at end of file +typedef IdealStream = Stream; \ No newline at end of file diff --git a/src/tink/streams/RealStream.hx b/src/tink/streams/RealStream.hx index ae08dae..51f8529 100644 --- a/src/tink/streams/RealStream.hx +++ b/src/tink/streams/RealStream.hx @@ -1,26 +1,3 @@ package tink.streams; -import tink.streams.Stream; - -using tink.CoreApi; - -@:forward @:transitive -abstract RealStream(Stream) from Stream to Stream { - @:from - public static inline function promiseOfIdealStream(p:Promise>):RealStream - return cast Stream.promise(p); - - @:from - public static inline function promiseOfStreamNoise(p:Promise>):RealStream - return cast Stream.promise(p); - - @:from - public static inline function promiseOfRealStream(p:Promise>):RealStream - return cast Stream.promise(p); - - @:from - public static inline function promiseOfStreamError(p:Promise>):RealStream - return cast Stream.promise(p); -} - -typedef RealStreamObject = StreamObject; +typedef RealStream = Stream; \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index a74a45a..744b538 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -34,6 +34,9 @@ abstract Stream(StreamObject) from StreamObject(p:Promise>):Stream return new PromiseStream(p); + + @:from static public function ofError(e:Error):Stream + return promise(e); } private class PromiseStream implements StreamObject { @@ -104,7 +107,7 @@ abstract Step(Null) from Null { #end } -typedef Consume = (item:Item, done:Result->Step)->Null>>; +private typedef Consume = (item:Item, done:Result->Step)->Null>>; abstract Consumer(Consume) from Consume { public inline function apply(item, done):Future> @@ -114,7 +117,7 @@ abstract Consumer(Consume) from Consume implements StreamObject { +private class Empty implements StreamObject { static final INST:StreamObject = new Empty(); @@ -124,7 +127,7 @@ class Empty implements StreamObject { return Done; } -class StreamPair implements StreamObject { +private class StreamPair implements StreamObject { final l:Stream; final r:Stream; @@ -148,6 +151,36 @@ class StreamPair implements StreamObject { }); } +private typedef Selector = In->Surprise, Quality>; + +private class SelectStream implements StreamObject { + + final source:Stream; + final selector:Selector; + + public function new(source, selector) { + this.source = source; + this.selector = selector; + } + + public function forEach(f:Consumer):Future> + return + source.forEach( + (item, done) -> selector(item).flatMap(o -> switch o { + case Success(data): + switch data { + case Some(v): + f.apply(v, done).map(Success); + case None: + Success(null); + } + case Failure(failure): + Failure(failure); + }) + ); + +} + class MapStream implements StreamObject { final source:Stream; final transform:In->Future; diff --git a/tests/RunTests.hx b/tests/RunTests.hx index 2c39f99..d37f0a7 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -17,7 +17,7 @@ class RunTests { // new StreamTest(), // new BlendTest(), // new NextTest(), - // new SignalStreamTest(), + new SignalStreamTest(), ])).handle(Runner.exit); } diff --git a/tests/SignalStreamTest.hx b/tests/SignalStreamTest.hx index a572a0b..d099c91 100644 --- a/tests/SignalStreamTest.hx +++ b/tests/SignalStreamTest.hx @@ -15,30 +15,30 @@ class SignalStreamTest { a.trigger(Data(1)); a.trigger(Data(2)); a.trigger(Data(3)); - + var i = 0; var sum = 0; - var result = stream.forEach(function (v) { + var result = stream.forEach(function (v, _) { asserts.assert(++i == v); sum += v; - return Resume; + return null; }); - + a.trigger(Data(4)); a.trigger(Data(5)); a.trigger(End); a.trigger(Data(6)); a.trigger(Data(7)); - + result.handle(function (x) { - asserts.assert(Depleted == x); - asserts.assert(15 == sum); + asserts.assert(x == Done); + asserts.assert(sum == 15); done = true; }); asserts.assert(done); return asserts.done(); } - + public function testError() { var done = false; var a = Signal.trigger(); @@ -46,28 +46,28 @@ class SignalStreamTest { a.trigger(Data(1)); a.trigger(Data(2)); a.trigger(Data(3)); - + var i = 0; var sum = 0; - var result = stream.forEach(function (v) { + var result = stream.forEach(function (v, _) { asserts.assert(++i == v); sum += v; - return Resume; + return null; }); - + a.trigger(Data(4)); a.trigger(Data(5)); a.trigger(Fail(new Error('Failed'))); - + result.handle(function (x) { - asserts.assert(x.match(Failed(_))); + asserts.assert(x.match(Stopped(_, Failure(_)))); asserts.assert(15 == sum); done = true; }); asserts.assert(done); return asserts.done(); } - + public function testReuse() { var a = Signal.trigger(); var stream = new SignalStream(a.asSignal()); @@ -75,22 +75,22 @@ class SignalStreamTest { a.trigger(Data(2)); a.trigger(Data(3)); a.trigger(End); - + var count = 0; function iterate() { var i = 0; var sum = 0; - stream.forEach(function (v) { + stream.forEach(function (v, _) { asserts.assert(++i == v); sum += v; - return Resume; + return null; }).handle(function (x) { - asserts.assert(Depleted == x); - asserts.assert(6 == sum); + asserts.assert(x == Done); + asserts.assert(sum == 6); count++; }); } - + iterate(); iterate(); iterate(); diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index aa25bad..89e64c3 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -13,21 +13,21 @@ class StreamTest { public function testIterator() { var s = Stream.ofIterator(0...100); var sum = 0; - s.forEach(function (v) { + s.forEach(function (v, _) { sum += v; - return Resume; + return null; }).handle(function (x) { - asserts.assert(Depleted == x); + asserts.assert(Done == x); asserts.assert(4950 == sum); asserts.done(); }); return asserts; } - + public function testMapFilter() { - + var s = Stream.ofIterator(0...100); - + s = s.filter(function (i) return i % 2 == 0); s = s.filter(function (i) return Success(i % 3 == 0)); s = s.map(function (i) return i * 3); @@ -37,14 +37,14 @@ class StreamTest { s = s.map(function (i) return Promise.lift(i + 13)); s = s.map(function (i) return Future.sync(i - 3)); s = s.map(function (i) return i * 2); - + var sum = 0; - - s.forEach(function (v) return Future.sync(Resume)).handle(function (c) switch c { + + s.forEach((v, _) -> null).handle(function (c) switch c { case Failed(_): default: }); - + s.idealize(null).forEach(function (v) { sum += v; return Future.sync(Resume); @@ -57,12 +57,12 @@ class StreamTest { }); return asserts; } - + public function testMapError() { var s = Stream.ofIterator(0...100); var mapped = s.map(function(v) return v % 5 == 4 ? Failure(new Error('Fail $v')) : Success(v)); var sum = 0; - + mapped.forEach(function(v) { sum += v; return Resume; @@ -76,14 +76,14 @@ class StreamTest { case Halted(_): asserts.fail('Expected "Failed'); }); - + return asserts; } - + public function testRegroup() { - + var s = Stream.ofIterator(0...100); - + var sum = 0; s.regroup(function (i:Array) return i.length == 5 ? Converted(Stream.single(i[0] + i[4])) : Untouched) .idealize(null).forEach(function (v) { @@ -96,7 +96,7 @@ class StreamTest { case Halted(_): asserts.fail('Expected "Depleted"'); }); - + var sum = 0; s.regroup(function (i:Array, s) { return if(s == Flowing) @@ -114,7 +114,7 @@ class StreamTest { case Halted(_): asserts.fail('Expected "Depleted"'); }); - + var sum = 0; s.regroup(function (i:Array) return Converted([i[0], i[0]].iterator())) .idealize(null).forEach(function (v) { @@ -127,16 +127,16 @@ class StreamTest { case Halted(_): asserts.fail('Expected "Depleted"'); }); - + var sum = 0; s.regroup(function (i:Array, status:RegroupStatus) { var batch = null; - + if(status == Ended) batch = i; else if(i.length > 3) batch = i.splice(0, 3); // leave one item in the buf - + return if(batch != null) Converted(batch.iterator(), i) else @@ -152,15 +152,15 @@ class StreamTest { case Halted(_): asserts.fail('Expected "Depleted"'); }); - + return asserts.done(); } - + public function testNested() { var n = Stream.ofIterator([Stream.ofIterator(0...3), Stream.ofIterator(3...6)].iterator()); var s = Stream.flatten(n); var sum = 0; - + s.forEach(function (v) { sum += v; return Resume; @@ -169,10 +169,10 @@ class StreamTest { asserts.assert(15 == sum); asserts.done(); }); - + return asserts; } - + public function testNestedWithInnerError() { var n = Stream.ofIterator([ Stream.ofIterator(0...3), @@ -181,7 +181,7 @@ class StreamTest { ].iterator()); var s = Stream.flatten(n); var sum = 0; - + s.forEach(function (v) { sum += v; return Resume; @@ -190,20 +190,20 @@ class StreamTest { asserts.assert(6 == sum); asserts.done(); }); - + return asserts; } - + public function testNestedWithOuterError() { var n = ofOutcomes([ Success(Stream.ofIterator(0...3)), Failure(new Error('dummy')), Success(Stream.ofIterator(6...9)), ].iterator()); - + var s = Stream.flatten(n); var sum = 0; - + s.forEach(function (v) { sum += v; return Resume; @@ -212,10 +212,10 @@ class StreamTest { asserts.assert(3 == sum); asserts.done(); }); - + return asserts; } - + #if !java public function casts() { var pi1:Promise> = Promise.NEVER; @@ -224,22 +224,22 @@ class StreamTest { var pr2:Promise> = Promise.NEVER; var r1:RealStream; var r2:Stream; - + r1 = pi1; r2 = pi1; r1 = pi2; r2 = pi2; - + r1 = pr1; r2 = pr1; r1 = pr2; r2 = pr2; - + return asserts.done(); } #end - - + + // maybe useful to be moved to Stream itself inline function ofOutcomes(i:Iterator>) { return Stream.ofIterator(i).map(function(v:Outcome) return v); From f0ce2951003183ec158e3a6e945d6783633c8fd9 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Wed, 10 Feb 2021 19:39:39 +0100 Subject: [PATCH 03/26] Baby steps. --- src/tink/streams/RealStream.hx | 26 ++- src/tink/streams/Return.hx | 26 +++ src/tink/streams/Stream.hx | 8 + tests/StreamTest.hx | 390 ++++++++++++++++----------------- 4 files changed, 254 insertions(+), 196 deletions(-) create mode 100644 src/tink/streams/Return.hx diff --git a/src/tink/streams/RealStream.hx b/src/tink/streams/RealStream.hx index 51f8529..1cbf209 100644 --- a/src/tink/streams/RealStream.hx +++ b/src/tink/streams/RealStream.hx @@ -1,3 +1,27 @@ package tink.streams; -typedef RealStream = Stream; \ No newline at end of file +import tink.streams.Stream.StreamObject; + +using tink.CoreApi; + +typedef RealStream = Stream; + +class RealStreamTools { + // static public function idealize(s:RealStream) + +} + +// private class IdealizedStream implements StreamObject { + +// final stream:RealStream; +// final rescue:Error->RealStream; + +// public function new(stream, rescue) { +// this.stream = stream; +// this.rescue = rescue; +// } + + // public function forEach(f:Consumer):Future> + // return + // stream.forEach() +// } \ No newline at end of file diff --git a/src/tink/streams/Return.hx b/src/tink/streams/Return.hx new file mode 100644 index 0000000..dd263b2 --- /dev/null +++ b/src/tink/streams/Return.hx @@ -0,0 +1,26 @@ +package tink.streams; + +using tink.CoreApi; + + +abstract Return(Surprise) { + + inline function new(v) + this = v; + + @:from static function ofError(e:Error):Return + return ofPromise(e); + + @:from static function ofOutcome(o:Outcome):Return + return new Return(Future.sync(o)); + + @:from static function ofPromise(f:Promise):Return + return new Return(f); + + @:from static function ofFuture(f:Future):Return + return new Return(f.map(Success)); + + @:from static function ofConst(v:T):Return + return ofFuture(v); + +} \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 744b538..3e259a4 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -18,6 +18,14 @@ abstract Stream(StreamObject) from StreamObjectReturn):Stream { + return this; + } + + public function map(f:Item->Return):Stream { + return Stream.empty(); + } + static public inline function empty():Stream return @:privateAccess #if cs diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index 89e64c3..95d9276 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -28,15 +28,15 @@ class StreamTest { var s = Stream.ofIterator(0...100); - s = s.filter(function (i) return i % 2 == 0); - s = s.filter(function (i) return Success(i % 3 == 0)); - s = s.map(function (i) return i * 3); - s = s.filter(function (i) return Future.sync(i % 5 == 0)); - s = s.filter(function (i) return Promise.lift(i > 100)); - s = s.map(function (i) return Success(i << 1)); - s = s.map(function (i) return Promise.lift(i + 13)); - s = s.map(function (i) return Future.sync(i - 3)); - s = s.map(function (i) return i * 2); + s = s.filter(i -> i % 2 == 0); + s = s.filter(i -> Success(i % 3 == 0)); + s = s.map(i -> i * 3); + s = s.filter(i -> Future.sync(i % 5 == 0)); + s = s.filter(i -> Promise.lift(i > 100)); + s = s.map(i -> Success(i << 1)); + s = s.map(i -> Promise.lift(i + 13)); + s = s.map(i -> Future.sync(i - 3)); + s = s.map(i -> i * 2); var sum = 0; @@ -58,190 +58,190 @@ class StreamTest { return asserts; } - public function testMapError() { - var s = Stream.ofIterator(0...100); - var mapped = s.map(function(v) return v % 5 == 4 ? Failure(new Error('Fail $v')) : Success(v)); - var sum = 0; - - mapped.forEach(function(v) { - sum += v; - return Resume; - }).handle(function(o) switch o { - case Depleted: - asserts.fail('Expected "Failed'); - case Failed(e): - asserts.assert(e.message == 'Fail 4'); - asserts.assert(sum == 6); - asserts.done(); - case Halted(_): - asserts.fail('Expected "Failed'); - }); - - return asserts; - } - - public function testRegroup() { - - var s = Stream.ofIterator(0...100); - - var sum = 0; - s.regroup(function (i:Array) return i.length == 5 ? Converted(Stream.single(i[0] + i[4])) : Untouched) - .idealize(null).forEach(function (v) { - sum += v; - return Resume; - }) - .handle(function (x) switch x { - case Depleted: - asserts.assert(1980 == sum); - case Halted(_): - asserts.fail('Expected "Depleted"'); - }); - - var sum = 0; - s.regroup(function (i:Array, s) { - return if(s == Flowing) - i.length == 3 ? Converted(Stream.single(i[0] + i[2])) : Untouched - else - Converted(Stream.single(i[0])); // TODO: test backoff / clog at last step - }) - .idealize(null).forEach(function (v) { - sum += v; - return Resume; - }) - .handle(function (x) switch x { - case Depleted: - asserts.assert(3333 == sum); - case Halted(_): - asserts.fail('Expected "Depleted"'); - }); - - var sum = 0; - s.regroup(function (i:Array) return Converted([i[0], i[0]].iterator())) - .idealize(null).forEach(function (v) { - sum += v; - return Resume; - }) - .handle(function (x) switch x { - case Depleted: - asserts.assert(9900 == sum); - case Halted(_): - asserts.fail('Expected "Depleted"'); - }); - - var sum = 0; - s.regroup(function (i:Array, status:RegroupStatus) { - var batch = null; - - if(status == Ended) - batch = i; - else if(i.length > 3) - batch = i.splice(0, 3); // leave one item in the buf - - return if(batch != null) - Converted(batch.iterator(), i) - else - Untouched; - }) - .idealize(null).forEach(function (v) { - sum += v; - return Resume; - }) - .handle(function (x) switch x { - case Depleted: - asserts.assert(4950 == sum); - case Halted(_): - asserts.fail('Expected "Depleted"'); - }); - - return asserts.done(); - } - - public function testNested() { - var n = Stream.ofIterator([Stream.ofIterator(0...3), Stream.ofIterator(3...6)].iterator()); - var s = Stream.flatten(n); - var sum = 0; - - s.forEach(function (v) { - sum += v; - return Resume; - }).handle(function (x) { - asserts.assert(Depleted == x); - asserts.assert(15 == sum); - asserts.done(); - }); - - return asserts; - } - - public function testNestedWithInnerError() { - var n = Stream.ofIterator([ - Stream.ofIterator(0...3), - ofOutcomes([Success(3), Failure(new Error('dummy')), Success(5)].iterator()), - Stream.ofIterator(6...9), - ].iterator()); - var s = Stream.flatten(n); - var sum = 0; - - s.forEach(function (v) { - sum += v; - return Resume; - }).handle(function (x) { - asserts.assert(x.match(Failed(_))); - asserts.assert(6 == sum); - asserts.done(); - }); - - return asserts; - } - - public function testNestedWithOuterError() { - var n = ofOutcomes([ - Success(Stream.ofIterator(0...3)), - Failure(new Error('dummy')), - Success(Stream.ofIterator(6...9)), - ].iterator()); - - var s = Stream.flatten(n); - var sum = 0; - - s.forEach(function (v) { - sum += v; - return Resume; - }).handle(function (x) { - asserts.assert(x.match(Failed(_))); - asserts.assert(3 == sum); - asserts.done(); - }); - - return asserts; - } - - #if !java - public function casts() { - var pi1:Promise> = Promise.NEVER; - var pi2:Promise> = Promise.NEVER; - var pr1:Promise> = Promise.NEVER; - var pr2:Promise> = Promise.NEVER; - var r1:RealStream; - var r2:Stream; - - r1 = pi1; - r2 = pi1; - r1 = pi2; - r2 = pi2; - - r1 = pr1; - r2 = pr1; - r1 = pr2; - r2 = pr2; - - return asserts.done(); - } - #end - - - // maybe useful to be moved to Stream itself - inline function ofOutcomes(i:Iterator>) { - return Stream.ofIterator(i).map(function(v:Outcome) return v); - } + // public function testMapError() { + // var s = Stream.ofIterator(0...100); + // var mapped = s.map(function(v) return v % 5 == 4 ? Failure(new Error('Fail $v')) : Success(v)); + // var sum = 0; + + // mapped.forEach(function(v) { + // sum += v; + // return Resume; + // }).handle(function(o) switch o { + // case Depleted: + // asserts.fail('Expected "Failed'); + // case Failed(e): + // asserts.assert(e.message == 'Fail 4'); + // asserts.assert(sum == 6); + // asserts.done(); + // case Halted(_): + // asserts.fail('Expected "Failed'); + // }); + + // return asserts; + // } + + // public function testRegroup() { + + // var s = Stream.ofIterator(0...100); + + // var sum = 0; + // s.regroup(function (i:Array) return i.length == 5 ? Converted(Stream.single(i[0] + i[4])) : Untouched) + // .idealize(null).forEach(function (v) { + // sum += v; + // return Resume; + // }) + // .handle(function (x) switch x { + // case Depleted: + // asserts.assert(1980 == sum); + // case Halted(_): + // asserts.fail('Expected "Depleted"'); + // }); + + // var sum = 0; + // s.regroup(function (i:Array, s) { + // return if(s == Flowing) + // i.length == 3 ? Converted(Stream.single(i[0] + i[2])) : Untouched + // else + // Converted(Stream.single(i[0])); // TODO: test backoff / clog at last step + // }) + // .idealize(null).forEach(function (v) { + // sum += v; + // return Resume; + // }) + // .handle(function (x) switch x { + // case Depleted: + // asserts.assert(3333 == sum); + // case Halted(_): + // asserts.fail('Expected "Depleted"'); + // }); + + // var sum = 0; + // s.regroup(function (i:Array) return Converted([i[0], i[0]].iterator())) + // .idealize(null).forEach(function (v) { + // sum += v; + // return Resume; + // }) + // .handle(function (x) switch x { + // case Depleted: + // asserts.assert(9900 == sum); + // case Halted(_): + // asserts.fail('Expected "Depleted"'); + // }); + + // var sum = 0; + // s.regroup(function (i:Array, status:RegroupStatus) { + // var batch = null; + + // if(status == Ended) + // batch = i; + // else if(i.length > 3) + // batch = i.splice(0, 3); // leave one item in the buf + + // return if(batch != null) + // Converted(batch.iterator(), i) + // else + // Untouched; + // }) + // .idealize(null).forEach(function (v) { + // sum += v; + // return Resume; + // }) + // .handle(function (x) switch x { + // case Depleted: + // asserts.assert(4950 == sum); + // case Halted(_): + // asserts.fail('Expected "Depleted"'); + // }); + + // return asserts.done(); + // } + + // public function testNested() { + // var n = Stream.ofIterator([Stream.ofIterator(0...3), Stream.ofIterator(3...6)].iterator()); + // var s = Stream.flatten(n); + // var sum = 0; + + // s.forEach(function (v) { + // sum += v; + // return Resume; + // }).handle(function (x) { + // asserts.assert(Depleted == x); + // asserts.assert(15 == sum); + // asserts.done(); + // }); + + // return asserts; + // } + + // public function testNestedWithInnerError() { + // var n = Stream.ofIterator([ + // Stream.ofIterator(0...3), + // ofOutcomes([Success(3), Failure(new Error('dummy')), Success(5)].iterator()), + // Stream.ofIterator(6...9), + // ].iterator()); + // var s = Stream.flatten(n); + // var sum = 0; + + // s.forEach(function (v) { + // sum += v; + // return Resume; + // }).handle(function (x) { + // asserts.assert(x.match(Failed(_))); + // asserts.assert(6 == sum); + // asserts.done(); + // }); + + // return asserts; + // } + + // public function testNestedWithOuterError() { + // var n = ofOutcomes([ + // Success(Stream.ofIterator(0...3)), + // Failure(new Error('dummy')), + // Success(Stream.ofIterator(6...9)), + // ].iterator()); + + // var s = Stream.flatten(n); + // var sum = 0; + + // s.forEach(function (v) { + // sum += v; + // return Resume; + // }).handle(function (x) { + // asserts.assert(x.match(Failed(_))); + // asserts.assert(3 == sum); + // asserts.done(); + // }); + + // return asserts; + // } + + // #if !java + // public function casts() { + // var pi1:Promise> = Promise.NEVER; + // var pi2:Promise> = Promise.NEVER; + // var pr1:Promise> = Promise.NEVER; + // var pr2:Promise> = Promise.NEVER; + // var r1:RealStream; + // var r2:Stream; + + // r1 = pi1; + // r2 = pi1; + // r1 = pi2; + // r2 = pi2; + + // r1 = pr1; + // r2 = pr1; + // r1 = pr2; + // r2 = pr2; + + // return asserts.done(); + // } + // #end + + + // // maybe useful to be moved to Stream itself + // inline function ofOutcomes(i:Iterator>) { + // return Stream.ofIterator(i).map(function(v:Outcome) return v); + // } } \ No newline at end of file From ca49d9e33fc701d4304c16dbdcc24cc211440044 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Wed, 10 Feb 2021 20:41:02 +0100 Subject: [PATCH 04/26] Get a few more tests to compile. --- src/tink/streams/RealStream.hx | 4 +- src/tink/streams/Return.hx | 22 +++-- src/tink/streams/Stream.hx | 156 ++++++++++++++++----------------- tests/RunTests.hx | 2 +- tests/SignalStreamTest.hx | 12 +-- tests/StreamTest.hx | 52 ++++++----- 6 files changed, 126 insertions(+), 122 deletions(-) diff --git a/src/tink/streams/RealStream.hx b/src/tink/streams/RealStream.hx index 1cbf209..50268ff 100644 --- a/src/tink/streams/RealStream.hx +++ b/src/tink/streams/RealStream.hx @@ -7,8 +7,8 @@ using tink.CoreApi; typedef RealStream = Stream; class RealStreamTools { - // static public function idealize(s:RealStream) - + static public function idealize(s:RealStream, rescue:(error:Error)->RealStream):IdealStream + return cast s; } // private class IdealizedStream implements StreamObject { diff --git a/src/tink/streams/Return.hx b/src/tink/streams/Return.hx index dd263b2..579197f 100644 --- a/src/tink/streams/Return.hx +++ b/src/tink/streams/Return.hx @@ -2,23 +2,33 @@ package tink.streams; using tink.CoreApi; +private enum ReturnKind { + Yay(v:T):ReturnKind; + Nay(e:Error):ReturnKind; +} -abstract Return(Surprise) { +abstract Return(Future>) { inline function new(v) this = v; @:from static function ofError(e:Error):Return - return ofPromise(e); + return new Return(Nay(e)); - @:from static function ofOutcome(o:Outcome):Return - return new Return(Future.sync(o)); + @:from static function ofOutcome(o:Outcome):Return + return new Return(outcome(o)); + + static function outcome(o:Outcome) + return switch o { + case Success(data): Yay(data); + case Failure(failure): Nay(failure); + } @:from static function ofPromise(f:Promise):Return - return new Return(f); + return new Return(f.map(outcome)); @:from static function ofFuture(f:Future):Return - return new Return(f.map(Success)); + return new Return(f.map(Yay)); @:from static function ofConst(v:T):Return return ofFuture(v); diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 3e259a4..4fb7cd2 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -4,6 +4,7 @@ import tink.core.Callback; using tink.CoreApi; @:forward @:transitive +@:using(tink.streams.RealStream) abstract Stream(StreamObject) from StreamObject { static public function generate(generator:()->Surprise):Stream { @@ -58,7 +59,7 @@ private class PromiseStream implements StreamObject { case Success(data): data; case Failure(e): - Stopped(Stream.empty(), Failure(e)); + Failed(Stream.empty(), e); }); } @@ -70,20 +71,21 @@ class SingleItem implements StreamObject { public function forEach(f:Consumer) return new Future>( trigger -> Helper.trySync( - f.apply(item, Step.new), + f(item), s -> trigger(switch s { - case null: + case Some(v): + Stopped(Stream.empty(), v); + case None: Done; - case v: - Stopped(Stream.empty(), Success(v.unwrap())); }) ) ); } enum IterationResult { - Done; - Stopped(rest:Stream, result:Outcome); + Done:IterationResult; + Failed(rest:Stream, e:Error):IterationResult; + Stopped(rest:Stream, result:Result):IterationResult; } @@ -115,14 +117,15 @@ abstract Step(Null) from Null { #end } -private typedef Consume = (item:Item, done:Result->Step)->Null>>; +private typedef Consume = (item:Item)->Future>; +@:callable abstract Consumer(Consume) from Consume { - public inline function apply(item, done):Future> - return switch this(item, done) { - case null: Future.NOISE; - case v: v; - } + // public inline function apply(item, done):Future> + // return switch this(item, done) { + // case null: Future.NOISE; + // case v: v; + // } } private class Empty implements StreamObject { @@ -151,6 +154,8 @@ private class StreamPair implements StreamObject { ret.link = Helper.trySync(l.forEach(f), res -> switch res { case Done: ret.link = Helper.trySync(r.forEach(f), trigger); + case Failed(rest, e): + trigger(Failed(new StreamPair(rest, cast r), e));//TODO: this is GADT bug case Stopped(rest, result): trigger(Stopped(new StreamPair(rest, r), result)); }); @@ -159,75 +164,68 @@ private class StreamPair implements StreamObject { }); } -private typedef Selector = In->Surprise, Quality>; +// private typedef Selector = In->Surprise, Quality>; -private class SelectStream implements StreamObject { +// private class SelectStream implements StreamObject { - final source:Stream; - final selector:Selector; +// final source:Stream; +// final selector:Selector; - public function new(source, selector) { - this.source = source; - this.selector = selector; - } - - public function forEach(f:Consumer):Future> - return - source.forEach( - (item, done) -> selector(item).flatMap(o -> switch o { - case Success(data): - switch data { - case Some(v): - f.apply(v, done).map(Success); - case None: - Success(null); - } - case Failure(failure): - Failure(failure); - }) - ); - -} +// public function new(source, selector) { +// this.source = source; +// this.selector = selector; +// } -class MapStream implements StreamObject { - final source:Stream; - final transform:In->Future; +// public function forEach(f:Consumer):Future> +// return +// source.forEach( +// item -> selector(item).flatMap(o -> switch o { +// case Success(data): +// case Failure(failure): +// }) +// ); - public function new(source, transform) { - this.source = source; - this.transform = transform; - } +// } - public function forEach(f:Consumer):Future> - return source.forEach((item, done) -> transform(item).flatMap(out -> f.apply(out, done))); -} +// class MapStream implements StreamObject { +// final source:Stream; +// final transform:In->Future; -class Grouped implements StreamObject { - final source:Stream, Quality>; +// public function new(source, transform) { +// this.source = source; +// this.transform = transform; +// } - public function new(source) - this.source = source; +// public function forEach(f:Consumer):Future> +// return source.forEach((item, done) -> transform(item).flatMap(out -> f(out, done))); +// } - public function forEach(f:Consumer):Future> - return - source.forEach((group, done) -> - AsyncLinkStream.ofIterator(group.iterator()) - .forEach(f).map(res -> switch res { - case Done: null; - case Stopped(rest, result): done(new Pair(rest, result)); - }) - ).map(function (o):IterationResult return switch o { - case Done: Done; - case Stopped(rest, result): - var rest = new Grouped(rest); - switch result { - case Success({ a: left, b: res }): - Stopped(new StreamPair(left, rest), res); - case Failure(failure): - Stopped(rest, Failure(failure)); - } - }); -} +// class Grouped implements StreamObject { +// final source:Stream, Quality>; + +// public function new(source) +// this.source = source; + +// public function forEach(f:Consumer):Future> +// return +// source.forEach((group, done) -> +// AsyncLinkStream.ofIterator(group.iterator()) +// .forEach(f).map(res -> switch res { +// case Done: null; +// case Stopped(rest, result): done(new Pair(rest, result)); +// }) +// ).map(function (o):IterationResult return switch o { +// case Done: Done; +// case Stopped(rest, result): +// var rest = new Grouped(rest); +// switch result { +// case Success({ a: left, b: res }): +// Stopped(new StreamPair(left, rest), res); +// case Failure(failure): +// Stopped(rest, Failure(failure)); +// } +// }); +// } private class Helper { static public function noop(_:Dynamic) {} @@ -269,15 +267,15 @@ class AsyncLinkStream implements StreamObject { case error: Stopped(Stream.empty(), Failure(error)); }); case Cons(item, tail): - function process(progress:Future>) { + function process(progress:Future>) { switch progress.status { case Ready(result): switch result.get() { - case null: + case Some(v): + yield(Stopped(new AsyncLinkStream(tail), Success(v))); + case None: cur = tail; return true; - case v: - yield(Stopped(new AsyncLinkStream(tail), Success((cast v:Item)))); } default: var tmp = progress.handle(Helper.noop); @@ -288,7 +286,7 @@ class AsyncLinkStream implements StreamObject { } return false; } - if (process(f.apply(item, Step.new))) continue; + if (process(f(item))) continue; } default: wait.link = cur.handle(Helper.noop); @@ -347,8 +345,6 @@ class AsyncLinkStream implements StreamObject { // }); // } -// class SignalStream - class SignalStream extends AsyncLinkStream { public function new(signal:Signal>) super(makeLink(signal)); diff --git a/tests/RunTests.hx b/tests/RunTests.hx index d37f0a7..ba4abe6 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -14,7 +14,7 @@ class RunTests { #end Runner.run(TestBatch.make([ - // new StreamTest(), + new StreamTest(), // new BlendTest(), // new NextTest(), new SignalStreamTest(), diff --git a/tests/SignalStreamTest.hx b/tests/SignalStreamTest.hx index d099c91..4ed55c6 100644 --- a/tests/SignalStreamTest.hx +++ b/tests/SignalStreamTest.hx @@ -18,10 +18,10 @@ class SignalStreamTest { var i = 0; var sum = 0; - var result = stream.forEach(function (v, _) { + var result = stream.forEach(v -> { asserts.assert(++i == v); sum += v; - return null; + None; }); a.trigger(Data(4)); @@ -49,10 +49,10 @@ class SignalStreamTest { var i = 0; var sum = 0; - var result = stream.forEach(function (v, _) { + var result = stream.forEach(v -> { asserts.assert(++i == v); sum += v; - return null; + None; }); a.trigger(Data(4)); @@ -80,10 +80,10 @@ class SignalStreamTest { function iterate() { var i = 0; var sum = 0; - stream.forEach(function (v, _) { + stream.forEach(v -> { asserts.assert(++i == v); sum += v; - return null; + None; }).handle(function (x) { asserts.assert(x == Done); asserts.assert(sum == 6); diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index 95d9276..fe13baa 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -13,9 +13,9 @@ class StreamTest { public function testIterator() { var s = Stream.ofIterator(0...100); var sum = 0; - s.forEach(function (v, _) { + s.forEach(v -> { sum += v; - return null; + None; }).handle(function (x) { asserts.assert(Done == x); asserts.assert(4950 == sum); @@ -40,45 +40,43 @@ class StreamTest { var sum = 0; - s.forEach((v, _) -> null).handle(function (c) switch c { + s.forEach(v -> None).handle(function (c) switch c { case Failed(_): default: }); - s.idealize(null).forEach(function (v) { + s.idealize(null).forEach(v -> { sum += v; - return Future.sync(Resume); - }).handle(function (x) switch x { - case Depleted: + return None; + }).handle(x -> switch x { + case Done: asserts.assert(1840 == sum); asserts.done(); - case Halted(_): + case Stopped(_): asserts.fail('Expected "Depleted'); }); return asserts; } - // public function testMapError() { - // var s = Stream.ofIterator(0...100); - // var mapped = s.map(function(v) return v % 5 == 4 ? Failure(new Error('Fail $v')) : Success(v)); - // var sum = 0; + public function testMapError() { + var s = Stream.ofIterator(0...100); + var mapped = s.map(function(v) return v % 5 == 4 ? Failure(new Error('Fail $v')) : Success(v)); + var sum = 0; - // mapped.forEach(function(v) { - // sum += v; - // return Resume; - // }).handle(function(o) switch o { - // case Depleted: - // asserts.fail('Expected "Failed'); - // case Failed(e): - // asserts.assert(e.message == 'Fail 4'); - // asserts.assert(sum == 6); - // asserts.done(); - // case Halted(_): - // asserts.fail('Expected "Failed'); - // }); + mapped.forEach(v -> { + sum += v; + return None; + }).handle(function(o) switch o { + case Failed(_, e): + asserts.assert(e.message == 'Fail 4'); + asserts.assert(sum == 6); + asserts.done(); + default: + asserts.fail('Expected "Failed'); + }); - // return asserts; - // } + return asserts; + } // public function testRegroup() { From c15c9ca0c706ffdb296a3468a5dfbed7a54ebb94 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 04:08:32 +0100 Subject: [PATCH 05/26] More tests passing. --- .haxerc | 2 +- src/tink/streams/Return.hx | 24 +++----- src/tink/streams/Stream.hx | 123 +++++++++++++++++++++++++------------ tests/SignalStreamTest.hx | 4 +- tests/StreamTest.hx | 109 ++++++++++++++++---------------- 5 files changed, 148 insertions(+), 114 deletions(-) diff --git a/.haxerc b/.haxerc index e68f622..f8137ff 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "4.1.5", + "version": "4.2.0", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/src/tink/streams/Return.hx b/src/tink/streams/Return.hx index 579197f..3cb9e20 100644 --- a/src/tink/streams/Return.hx +++ b/src/tink/streams/Return.hx @@ -2,33 +2,23 @@ package tink.streams; using tink.CoreApi; -private enum ReturnKind { - Yay(v:T):ReturnKind; - Nay(e:Error):ReturnKind; -} - -abstract Return(Future>) { +@:forward +abstract Return(Surprise) from Surprise { inline function new(v) this = v; @:from static function ofError(e:Error):Return - return new Return(Nay(e)); - - @:from static function ofOutcome(o:Outcome):Return - return new Return(outcome(o)); + return ofPromise(e); - static function outcome(o:Outcome) - return switch o { - case Success(data): Yay(data); - case Failure(failure): Nay(failure); - } + @:from static function ofOutcome(o:Outcome):Return + return new Return(Future.sync(o)); @:from static function ofPromise(f:Promise):Return - return new Return(f.map(outcome)); + return new Return(f); @:from static function ofFuture(f:Future):Return - return new Return(f.map(Yay)); + return new Return(f.map(Success)); @:from static function ofConst(v:T):Return return ofFuture(v); diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 4fb7cd2..ad4a116 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -19,13 +19,20 @@ abstract Stream(StreamObject) from StreamObjectReturn):Stream { - return this; - } + public function select(selector):Stream + return new SelectStream(this, selector); - public function map(f:Item->Return):Stream { - return Stream.empty(); - } + public function filter(f:Item->Return):Stream + return select(i -> f(i).map(o -> switch o { + case Success(matched): Success(if (matched) Some(i) else None); + case Failure(failure): Failure(failure); + })); + + public function map(f:Item->Return):Stream + return select(i -> f(i).map(r -> switch r { + case Success(data): Success(Some(data));// BUG: Success(data) compiles + case Failure(failure): Failure(failure); + })); static public inline function empty():Stream return @:privateAccess @@ -46,6 +53,34 @@ abstract Stream(StreamObject) from StreamObject(e:Error):Stream return promise(e); + + static public function flatten(s:Stream, Error>):Stream + return new FlattenStream(s); +} + +private class FlattenStream implements StreamObject { + final source:Stream, Error>; + + public function new(source) + this.source = source; + + public function forEach(f:Consumer):Future> + return + source.forEach(function (child) return child.forEach(f).map(function (r) return switch r { + case Done: None; + case Stopped(rest, result): Some(new Pair(rest, Success(result))); + case Failed(rest, e): Some(new Pair(rest, Failure(e))); + })).map(function (r) return switch r { + case Done: Done; + case Stopped(rest, result): + var rest = new StreamPair(result.a, new FlattenStream(rest)); + switch result.b { + case Success(data): Stopped(rest, data); + case Failure(failure): Failed(rest, failure); + } + case Failed(rest, e): + Failed(new FlattenStream(rest), e); + }); } private class PromiseStream implements StreamObject { @@ -88,7 +123,6 @@ enum IterationResult { Stopped(rest:Stream, result:Result):IterationResult; } - interface StreamObject { function forEach(f:Consumer):Future>; } @@ -117,16 +151,7 @@ abstract Step(Null) from Null { #end } -private typedef Consume = (item:Item)->Future>; - -@:callable -abstract Consumer(Consume) from Consume { - // public inline function apply(item, done):Future> - // return switch this(item, done) { - // case null: Future.NOISE; - // case v: v; - // } -} +typedef Consumer = (item:Item)->Future>; private class Empty implements StreamObject { @@ -164,32 +189,39 @@ private class StreamPair implements StreamObject { }); } -// private typedef Selector = In->Surprise, Quality>; - -// private class SelectStream implements StreamObject { +private typedef Selector = In->Return, Quality>; -// final source:Stream; -// final selector:Selector; +private class SelectStream implements StreamObject { -// public function new(source, selector) { -// this.source = source; -// this.selector = selector; -// } + final source:Stream; + final selector:Selector; -// public function forEach(f:Consumer):Future> -// return -// source.forEach( -// item -> selector(item).flatMap(o -> switch o { -// case Success(data): -// case Failure(failure): -// }) -// ); + public function new(source, selector) { + this.source = source; + this.selector = selector; + } -// } + public function forEach(f:Consumer):Future> + return + source.forEach( + item -> selector(item).flatMap(o -> switch o { + case Success(None): None; + case Success(Some(v)): + f(v).map(o -> o.map(Success)); + case Failure(e): + Some(Failure(e)); + }) + ).map(res -> switch res { + case Done: Done; + case Stopped(rest, Success(result)): + Stopped(rest, result); + case Failed(rest, e) | Stopped(rest, Failure(e)): cast Failed(rest, e);// GADT bug + }); +} // class MapStream implements StreamObject { // final source:Stream; -// final transform:In->Future; +// final transform:In->Return; // public function new(source, transform) { // this.source = source; @@ -197,7 +229,17 @@ private class StreamPair implements StreamObject { // } // public function forEach(f:Consumer):Future> -// return source.forEach((item, done) -> transform(item).flatMap(out -> f(out, done))); +// return source.forEach(item -> transform(item).flatMap(out -> switch out { +// case Success(data): f(data).map(o -> switch o { +// case Some(v): Some(Success(v)); +// case None: None; +// }); +// case Failure(e): trace(e); Some(Failure(e)); +// })).map(res -> switch res { +// case Done: Done; +// case Stopped(rest, Success(result)): Stopped(rest, result); +// case Failed(rest, e) | Stopped(rest, Failure(e)): cast Failed(rest, e); +// }); // } // class Grouped implements StreamObject { @@ -254,7 +296,7 @@ class AsyncLinkStream implements StreamObject { this.link = link; public function forEach(f:Consumer):Future> - return new Future(yield -> { + return new Future((yield:(res:IterationResult)->Void) -> { final wait = new CallbackLinkRef(); function loop(cur:AsyncLink) { while (true) { @@ -264,7 +306,8 @@ class AsyncLinkStream implements StreamObject { case Fin(v): yield(switch v { case null: Done; - case error: Stopped(Stream.empty(), Failure(error)); + case error: + cast Failed(Stream.empty(), cast error);// GADT bug: what's worse, this compiles: Stopped(Stream.empty(), Failure(error)) compiles }); case Cons(item, tail): function process(progress:Future>) { @@ -272,7 +315,7 @@ class AsyncLinkStream implements StreamObject { case Ready(result): switch result.get() { case Some(v): - yield(Stopped(new AsyncLinkStream(tail), Success(v))); + yield(Stopped(new AsyncLinkStream(tail), v)); case None: cur = tail; return true; diff --git a/tests/SignalStreamTest.hx b/tests/SignalStreamTest.hx index 4ed55c6..f5ecf42 100644 --- a/tests/SignalStreamTest.hx +++ b/tests/SignalStreamTest.hx @@ -60,9 +60,9 @@ class SignalStreamTest { a.trigger(Fail(new Error('Failed'))); result.handle(function (x) { - asserts.assert(x.match(Stopped(_, Failure(_)))); + asserts.assert(x.match(Failed(_))); asserts.assert(15 == sum); - done = true; + done = true; }); asserts.assert(done); return asserts.done(); diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index fe13baa..2c3d41f 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -72,6 +72,7 @@ class StreamTest { asserts.assert(sum == 6); asserts.done(); default: + trace(Std.string(o)); asserts.fail('Expected "Failed'); }); @@ -154,65 +155,65 @@ class StreamTest { // return asserts.done(); // } - // public function testNested() { - // var n = Stream.ofIterator([Stream.ofIterator(0...3), Stream.ofIterator(3...6)].iterator()); - // var s = Stream.flatten(n); - // var sum = 0; + public function testNested() { + var n = Stream.ofIterator([Stream.ofIterator(0...3), Stream.ofIterator(3...6)].iterator()); + var s = Stream.flatten(n); + var sum = 0; - // s.forEach(function (v) { - // sum += v; - // return Resume; - // }).handle(function (x) { - // asserts.assert(Depleted == x); - // asserts.assert(15 == sum); - // asserts.done(); - // }); + s.forEach(function (v) { + sum += v; + return None; + }).handle(function (x) { + asserts.assert(Done == x); + asserts.assert(15 == sum); + asserts.done(); + }); - // return asserts; - // } + return asserts; + } - // public function testNestedWithInnerError() { - // var n = Stream.ofIterator([ - // Stream.ofIterator(0...3), - // ofOutcomes([Success(3), Failure(new Error('dummy')), Success(5)].iterator()), - // Stream.ofIterator(6...9), - // ].iterator()); - // var s = Stream.flatten(n); - // var sum = 0; + public function testNestedWithInnerError() { + var n = Stream.ofIterator([ + Stream.ofIterator(0...3), + ofOutcomes([Success(3), Failure(new Error('dummy')), Success(5)].iterator()), + Stream.ofIterator(6...9), + ].iterator()); + var s = Stream.flatten(n); + var sum = 0; - // s.forEach(function (v) { - // sum += v; - // return Resume; - // }).handle(function (x) { - // asserts.assert(x.match(Failed(_))); - // asserts.assert(6 == sum); - // asserts.done(); - // }); + s.forEach(function (v) { + sum += v; + return None; + }).handle(function (x) { + asserts.assert(x.match(Failed(_))); + asserts.assert(6 == sum); + asserts.done(); + }); - // return asserts; - // } + return asserts; + } - // public function testNestedWithOuterError() { - // var n = ofOutcomes([ - // Success(Stream.ofIterator(0...3)), - // Failure(new Error('dummy')), - // Success(Stream.ofIterator(6...9)), - // ].iterator()); + public function testNestedWithOuterError() { + var n = ofOutcomes([ + Success(Stream.ofIterator(0...3)), + Failure(new Error('dummy')), + Success(Stream.ofIterator(6...9)), + ].iterator()); - // var s = Stream.flatten(n); - // var sum = 0; + var s = Stream.flatten(n); + var sum = 0; - // s.forEach(function (v) { - // sum += v; - // return Resume; - // }).handle(function (x) { - // asserts.assert(x.match(Failed(_))); - // asserts.assert(3 == sum); - // asserts.done(); - // }); + s.forEach(function (v) { + sum += v; + return None; + }).handle(function (x) { + asserts.assert(x.match(Failed(_))); + asserts.assert(3 == sum); + asserts.done(); + }); - // return asserts; - // } + return asserts; + } // #if !java // public function casts() { @@ -238,8 +239,8 @@ class StreamTest { // #end - // // maybe useful to be moved to Stream itself - // inline function ofOutcomes(i:Iterator>) { - // return Stream.ofIterator(i).map(function(v:Outcome) return v); - // } + // maybe useful to be moved to Stream itself + inline function ofOutcomes(i:Iterator>) { + return Stream.ofIterator(i).map(function(v:Outcome) return v); + } } \ No newline at end of file From 68fbc76f360b548d525a4e901d548b1b25e8feec Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 04:30:52 +0100 Subject: [PATCH 06/26] Get a few more tests to compile. --- src/tink/streams/Stream.hx | 37 +++++++++++++++++++------------------ tests/NextTest.hx | 36 ++++++++++++++++++------------------ tests/RunTests.hx | 2 +- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index ad4a116..597f25c 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -19,6 +19,14 @@ abstract Stream(StreamObject) from StreamObject> + // BUG: this compiles: return this.forEach(function (i) return Some(i)); + return this.forEach(function (i) return Some(i)).map(function (r):Step return switch r { + case Done: End; + case Stopped(rest, result): Link(result, rest); + case Failed(_, e): Fail(e); + }); + public function select(selector):Stream return new SelectStream(this, selector); @@ -42,8 +50,8 @@ abstract Stream(StreamObject) from StreamObject(a:Stream, b:Stream) - return new StreamPair(a, b); + @:op(a...b) public function append(b:Stream) + return new StreamPair(this, b); @:from static public function ofIterator(t:Iterator):Stream return AsyncLinkStream.ofIterator(t); @@ -56,6 +64,15 @@ abstract Stream(StreamObject) from StreamObject(s:Stream, Error>):Stream return new FlattenStream(s); + + static public function ofSignal(s):Stream + return new SignalStream(s); +} + +enum Step { + Link(value:Item, next:Stream):Step; + Fail(e:Error):Step; + End:Step; } private class FlattenStream implements StreamObject { @@ -135,22 +152,6 @@ enum LinkKind { Cons(head:Item, tail:Tail); } -abstract Step(Null) from Null { - public inline function new(v:Result) - this = v; - - public inline function unwrap():Result - return - #if debug - switch this { - case null: throw 'ohno!'; - case v: v; - } - #else - cast this; - #end -} - typedef Consumer = (item:Item)->Future>; private class Empty implements StreamObject { diff --git a/tests/NextTest.hx b/tests/NextTest.hx index 320d04d..bb9d5d3 100644 --- a/tests/NextTest.hx +++ b/tests/NextTest.hx @@ -15,44 +15,44 @@ class NextTest { check(asserts, b, [1,2,3]); return asserts.done(); } - + public function testFilter() { var a = Stream.ofIterator(0...6); var b = a.filter(function(v) return v % 2 == 1); check(asserts, b, [1,3,5]); return asserts.done(); } - + public function testCompound() { var a = Signal.trigger(); var b = Signal.trigger(); - var compound = new SignalStream(a).append(new SignalStream(b)); + var compound = Stream.ofSignal(a).append(new SignalStream(b)); a.trigger(Data(1)); b.trigger(Data(3)); a.trigger(Data(2)); a.trigger(End); check(asserts, compound, [1,2,3]); - + var a = Stream.ofIterator(0...3); var b = Stream.ofIterator(0...3); var compound = a.append(b); check(asserts, compound, [0,1,2,0,1,2]); return asserts.done(); } - - public function testBlend() { - var a = Signal.trigger(); - var b = Signal.trigger(); - var compound = new SignalStream(a).blend(new SignalStream(b)); - a.trigger(Data(1)); - b.trigger(Data(2)); - a.trigger(Data(3)); - a.trigger(End); - b.trigger(End); - check(asserts, compound, [1,2,3]); - return asserts.done(); - } - + + // public function testBlend() { + // var a = Signal.trigger(); + // var b = Signal.trigger(); + // var compound = new SignalStream(a).blend(new SignalStream(b)); + // a.trigger(Data(1)); + // b.trigger(Data(2)); + // a.trigger(Data(3)); + // a.trigger(End); + // b.trigger(End); + // check(asserts, compound, [1,2,3]); + // return asserts.done(); + // } + function check(asserts:AssertionBuffer, stream:Stream, values:Array, ?pos:haxe.PosInfos) { var current = stream; for(i in 0...values.length) { diff --git a/tests/RunTests.hx b/tests/RunTests.hx index ba4abe6..7c86c1f 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -16,7 +16,7 @@ class RunTests { Runner.run(TestBatch.make([ new StreamTest(), // new BlendTest(), - // new NextTest(), + new NextTest(), new SignalStreamTest(), ])).handle(Runner.exit); } From c2bc8398f949b3d5491c7d8f0e5846f6bfd31b1b Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 05:11:05 +0100 Subject: [PATCH 07/26] Enabled tests pass. --- .haxerc | 2 +- haxe_libraries/tink_core.hxml | 4 ++-- src/tink/streams/Stream.hx | 10 +++++++--- tests/StreamTest.hx | 1 - 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.haxerc b/.haxerc index f8137ff..e68f622 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "4.2.0", + "version": "4.1.5", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index 82fa3de..3b18526 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_core#396705beabca0e97decb66774be6d056b0d904f1" into tink_core/2.0.0-rc.2/github/396705beabca0e97decb66774be6d056b0d904f1 --cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/396705beabca0e97decb66774be6d056b0d904f1/src +# @install: lix --silent download "gh://github.com/haxetink/tink_core#1de5655e914441a6fca86a5da445aa064446a30a" into tink_core/2.0.0-rc.2/github/1de5655e914441a6fca86a5da445aa064446a30a +-cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/1de5655e914441a6fca86a5da445aa064446a30a/src -D tink_core=2.0.0-rc.2 \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 597f25c..a9d7d8b 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -202,6 +202,9 @@ private class SelectStream implements StreamObject + return new SelectStream(source, selector); + public function forEach(f:Consumer):Future> return source.forEach( @@ -215,8 +218,9 @@ private class SelectStream implements StreamObject switch res { case Done: Done; case Stopped(rest, Success(result)): - Stopped(rest, result); - case Failed(rest, e) | Stopped(rest, Failure(e)): cast Failed(rest, e);// GADT bug + Stopped(continued(rest), result); + case Failed(rest, e) | Stopped(rest, Failure(e)): + cast Failed(cast continued(cast rest), e);// GADT bug }); } @@ -346,7 +350,7 @@ class AsyncLinkStream implements StreamObject { return wait; }); - static function iteratorLink(i:Iterator):Future> + static function iteratorLink(i:Iterator):AsyncLink return Future.lazy(() -> if (i.hasNext()) Cons(i.next(), iteratorLink(i)) else Fin(null)); static public function ofIterator(i:Iterator):Stream diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index 2c3d41f..9aee3fb 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -72,7 +72,6 @@ class StreamTest { asserts.assert(sum == 6); asserts.done(); default: - trace(Std.string(o)); asserts.fail('Expected "Failed'); }); From 666fe24da8279d57849382a9ce5681f453b00600 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 05:17:23 +0100 Subject: [PATCH 08/26] Use more arrow functions now that it's safe again. --- src/tink/streams/Stream.hx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index a9d7d8b..0538ad5 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -20,8 +20,7 @@ abstract Stream(StreamObject) from StreamObject> - // BUG: this compiles: return this.forEach(function (i) return Some(i)); - return this.forEach(function (i) return Some(i)).map(function (r):Step return switch r { + return this.forEach(i -> Some(i)).map(function (r):Step return switch r { case Done: End; case Stopped(rest, result): Link(result, rest); case Failed(_, e): Fail(e); @@ -83,11 +82,11 @@ private class FlattenStream implements StreamObject { public function forEach(f:Consumer):Future> return - source.forEach(function (child) return child.forEach(f).map(function (r) return switch r { + source.forEach(child -> child.forEach(f).map(r -> switch r { case Done: None; case Stopped(rest, result): Some(new Pair(rest, Success(result))); case Failed(rest, e): Some(new Pair(rest, Failure(e))); - })).map(function (r) return switch r { + })).map(r -> switch r { case Done: Done; case Stopped(rest, result): var rest = new StreamPair(result.a, new FlattenStream(rest)); @@ -147,7 +146,7 @@ interface StreamObject { typedef AsyncLink = Future>; typedef AsyncLinkKind = LinkKind> -enum LinkKind { +private enum LinkKind { Fin(error:Null); Cons(head:Item, tail:Tail); } @@ -312,7 +311,7 @@ class AsyncLinkStream implements StreamObject { yield(switch v { case null: Done; case error: - cast Failed(Stream.empty(), cast error);// GADT bug: what's worse, this compiles: Stopped(Stream.empty(), Failure(error)) compiles + cast Failed(Stream.empty(), cast error);// GADT bug }); case Cons(item, tail): function process(progress:Future>) { From f242a7857dbf115368db2c14052d0cc57b345225 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 05:22:32 +0100 Subject: [PATCH 09/26] Flattening for all kinds of quality. --- src/tink/streams/Stream.hx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 0538ad5..94b7f34 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -74,28 +74,28 @@ enum Step { End:Step; } -private class FlattenStream implements StreamObject { - final source:Stream, Error>; +private class FlattenStream implements StreamObject { + final source:Stream, Quality>; public function new(source) this.source = source; - public function forEach(f:Consumer):Future> + public function forEach(f:Consumer):Future> return source.forEach(child -> child.forEach(f).map(r -> switch r { case Done: None; case Stopped(rest, result): Some(new Pair(rest, Success(result))); - case Failed(rest, e): Some(new Pair(rest, Failure(e))); + case Failed(rest, e): Some(new Pair(cast rest, Failure(e))); })).map(r -> switch r { case Done: Done; case Stopped(rest, result): var rest = new StreamPair(result.a, new FlattenStream(rest)); switch result.b { case Success(data): Stopped(rest, data); - case Failure(failure): Failed(rest, failure); + case Failure(failure): cast Failed(cast rest, failure); } case Failed(rest, e): - Failed(new FlattenStream(rest), e); + cast Failed(new FlattenStream(rest), e); }); } From d5e04ade79b61476a51cc4ce6ed3c270efe3ecb2 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 08:07:37 +0100 Subject: [PATCH 10/26] Add some benchmarks. --- bench.hxml | 7 ++ src/tink/streams/Stream.hx | 9 +- tests/Benchmark.hx | 204 +++++++++++++++++++++++++++++++++++++ tests/BlendTest.hx | 32 +++--- 4 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 bench.hxml create mode 100644 tests/Benchmark.hx diff --git a/bench.hxml b/bench.hxml new file mode 100644 index 0000000..ec78333 --- /dev/null +++ b/bench.hxml @@ -0,0 +1,7 @@ +-lib tink_streams +-lib hxnodejs +-cp tests +-main Benchmark +--dce full +-D analyzer-optimize +-js bin/bench.js \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 94b7f34..48bdf73 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -7,12 +7,13 @@ using tink.CoreApi; @:using(tink.streams.RealStream) abstract Stream(StreamObject) from StreamObject { - static public function generate(generator:()->Surprise):Stream { + static public function generate(generator:()->Future>):Stream { function rec():AsyncLink return Future.irreversible(yield -> { generator().handle(o -> yield(switch o { - case Success(data): Cons(data, rec()); - case Failure(failure): Fin(failure); + case Data(data): Cons(data, rec()); + case Fail(e): Fin(cast e); + case End: Fin(null); })); }); @@ -329,7 +330,7 @@ class AsyncLinkStream implements StreamObject { if (progress.status.match(Ready(_))) return process(progress); else - wait.link = Helper.swapHandler(progress, tmp, _ -> process(progress)); + wait.link = Helper.swapHandler(progress, tmp, _ -> if (process(progress)) loop(tail)); } return false; } diff --git a/tests/Benchmark.hx b/tests/Benchmark.hx new file mode 100644 index 0000000..3906125 --- /dev/null +++ b/tests/Benchmark.hx @@ -0,0 +1,204 @@ +// import js.node.stream.Readable; +import tink.streams.Stream.Yield; +import js.node.stream.Readable; +import js.node.Buffer; +import tink.streams.*; +import js.node.events.EventEmitter; +import js.node.Fs; + +using sys.io.File; +using sys.FileSystem; +using tink.CoreApi; + +class Iter extends Readable> { + final iterator:Iterator; + public function new(iterator) { + super({ objectMode: true }); + this.iterator = iterator; + } + override function _read(size:Int) { + for (i in 0...size) + if (iterator.hasNext()) { + if (!push(iterator.next())) break; + } + else { + push(null); + break; + } + } +} +class Benchmark { + static function measure(a:Array>>) + switch a { + case []: + case _[0] => task: + var start = haxe.Timer.stamp(); + task.value.handle(function (x) { + if (task.name != null) + trace('${task.name} took ${haxe.Timer.stamp() - start}s producing ${x}'); + measure(a.slice(1)); + }); + } + + static function main() { + numbers(); + files(); + } + + static function files() { + final dummy = 'bin/dummy.txt'; + if (!dummy.exists()) { + var s = 'a'; + for (i in 0...28) + s += s; + dummy.saveContent(s); + } + + final copy = 'bin/dummy_copy.txt'; + + function delete():Future + return Future.irreversible(yield -> { + if (copy.exists()) + copy.deleteFile(); + yield(null); + }); + + function tinkRead() + return new Named('tink', { + var len = 0; + readStream(dummy).forEach(item -> { + len += item.length; + None; + }).map(_ -> len); + }); + + function tinkCopy():Named> + return new Named('tink', { + writeStream(copy, cast readStream(dummy)); + }); + + function nodeRead() + return new Named('node', Future.irreversible(yield -> { + var len = 0; + Fs.createReadStream(dummy) + .on('data', (b:Buffer) -> len += b.length) + .on('end', () -> yield(len)); + // .pipe(Fs.createWriteStream(copy), { end: true }).on('close', () -> yield(Noise)); + })); + + function nodeCopy() + return new Named>('node', Future.irreversible(yield -> { + Fs.createReadStream(dummy) + .pipe(Fs.createWriteStream(copy), { end: true }).on('close', () -> yield(Success(Noise))); + })); + measure([ + new Named(null, delete()), + nodeCopy(), + new Named(null, delete()), + tinkCopy(), + new Named(null, delete()), + nodeCopy(), + new Named(null, delete()), + tinkCopy(), + ]); + } + + static function writeStream(path:String, s:Stream) + return + open(path, WriteCreate).next( + fd -> s.forEach(buf -> { + Future.irreversible(trigger -> { + function flush(start:Int) + Fs.write(fd, buf, start, buf.length - start, (error, written, buf) -> { + if (error != null) trigger(Some(Failure(error))) + else if (written + start == buf.length) trigger(None); + else flush(start + written); + }); + flush(0); + }); + }).next(res -> switch res { + case Done: Noise; + case Stopped(rest, Failure(e)): + tink.core.Error.withData('failed writing to $path', e); + case Stopped(_): + throw 'unreachable'; + }).map(x -> { + Fs.closeSync(fd); + x; + }).eager() + ); + + static function open(path:String, flags) + return new Promise((resolve, reject) -> { + Fs.open(path, flags, null, (?error, fd) -> { + if (error == null) resolve(fd); + else reject(tink.core.Error.withData('failed to open $path', error)); + }); + return null; + }); + + static function readStream(path:String, chunSize:Int = 0x40000) { + var file = open(path, Read); + + function read(fd) + return Future.irreversible(trigger -> { + Fs.read(fd, Buffer.alloc(chunSize), 0, chunSize, null, (error, bytesRead, buffer) -> trigger( + if (error != null) Yield.Fail(null) + else if (bytesRead == 0) { + Fs.closeSync(fd); + Yield.End; + } + else Yield.Data(buffer.slice(0, bytesRead)) + )); + }); + + return Stream.promise(file.next(fd -> Stream.generate(() -> read(fd)))); + } + + static function numbers() { + final total = 100000; + var s = Stream.ofIterator(0...total); + + measure([ + new Named('tink', { + var x = 0; + s.forEach(_ -> { + x += 1; + None; + }).map(_ -> x); + }), + new Named('node', new Future(yield -> { + var i = new Iter(0...total); + var x = 0; + i.on('data', v -> { + x += 1; + }); + i.on('end', () -> yield(x)); + return null; + })), + new Named('tink', { + var s = tink.streams.Stream.ofIterator(0...total); + var x = 0; + s.forEach(_ -> { + x += 1; + None; + }).map(_ -> x); + }), + new Named('node', Future.irreversible(yield -> { + var i = new Iter(0...total); + var x = 0; + i.on('data', v -> { + x += 1; + }); + i.on('end', () -> yield(x)); + })), + new Named('tink repeat', { + var x = 0; + s.forEach(_ -> { + x += 1; + None; + }).map(_ -> x); + }), + ]); + } +} \ No newline at end of file diff --git a/tests/BlendTest.hx b/tests/BlendTest.hx index ed77113..a8f2f3c 100644 --- a/tests/BlendTest.hx +++ b/tests/BlendTest.hx @@ -8,16 +8,16 @@ using tink.CoreApi; @:asserts class BlendTest { public function new() {} - + public function testBlend() { var done = false; var a = Signal.trigger(); var b = Signal.trigger(); - var blended = new SignalStream(a.asSignal()).blend(new SignalStream(b.asSignal())); + var blended = Stream.ofSignal(a.asSignal()).blend(new SignalStream(b.asSignal())); a.trigger(Data(1)); b.trigger(Data(2)); a.trigger(Data(3)); - + var i = 0; var sum = 0; var result = blended.forEach(function (v) { @@ -25,14 +25,14 @@ class BlendTest { sum += v; return Resume; }); - + a.trigger(Data(4)); a.trigger(End); b.trigger(Data(5)); b.trigger(End); b.trigger(Data(6)); a.trigger(Data(7)); - + result.handle(function (x) { asserts.assert(Depleted == x); asserts.assert(15 == sum); @@ -41,7 +41,7 @@ class BlendTest { asserts.assert(done); return asserts.done(); } - + public function testCompound() { var done = false; var a = Signal.trigger(); @@ -52,7 +52,7 @@ class BlendTest { b.trigger(Data(2)); a.trigger(End); c.trigger(Data(3)); - + var i = 0; var sum = 0; var result = blended.forEach(function (v) { @@ -60,14 +60,14 @@ class BlendTest { sum += v; return Resume; }); - + c.trigger(Data(4)); c.trigger(End); b.trigger(Data(5)); b.trigger(End); b.trigger(Data(6)); a.trigger(Data(7)); - + result.handle(function (x) { asserts.assert(Depleted == x); asserts.assert(15 == sum); @@ -76,7 +76,7 @@ class BlendTest { asserts.assert(done); return asserts.done(); } - + public function testError() { var done = false; var a = Signal.trigger(); @@ -85,7 +85,7 @@ class BlendTest { a.trigger(Data(1)); b.trigger(Data(2)); a.trigger(Data(3)); - + var i = 0; var sum = 0; var result = blended.forEach(function (v) { @@ -93,12 +93,12 @@ class BlendTest { sum += v; return Resume; }); - + a.trigger(Data(4)); a.trigger(Data(5)); b.trigger(Fail(new Error('Failed'))); a.trigger(End); - + result.handle(function (x) { asserts.assert(x.match(Failed(_))); asserts.assert(15 == sum); @@ -107,7 +107,7 @@ class BlendTest { asserts.assert(done); return asserts.done(); } - + public function testReuse() { var a = Signal.trigger(); var b = Signal.trigger(); @@ -117,7 +117,7 @@ class BlendTest { b.trigger(End); a.trigger(Data(3)); a.trigger(End); - + var count = 0; function iterate() { var i = 0; @@ -132,7 +132,7 @@ class BlendTest { count++; }); } - + iterate(); iterate(); iterate(); From e0183d22b7c19de2514a3219c7ce8346cebc0240 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 08:17:53 +0100 Subject: [PATCH 11/26] Try to simplify sync looping a little. --- src/tink/streams/Stream.hx | 51 ++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 48bdf73..4a54da8 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -124,7 +124,7 @@ class SingleItem implements StreamObject { return new Future>( trigger -> Helper.trySync( f(item), - s -> trigger(switch s { + (s, _) -> trigger(switch s { case Some(v): Stopped(Stream.empty(), v); case None: @@ -177,9 +177,9 @@ private class StreamPair implements StreamObject { return new Future>(trigger -> { final ret = new CallbackLinkRef(); - ret.link = Helper.trySync(l.forEach(f), res -> switch res { + ret.link = Helper.trySync(l.forEach(f), (res, _) -> switch res { case Done: - ret.link = Helper.trySync(r.forEach(f), trigger); + ret.link = Helper.trySync(r.forEach(f), (v, _) -> trigger(v)); case Failed(rest, e): trigger(Failed(new StreamPair(rest, cast r), e));//TODO: this is GADT bug case Stopped(rest, result): @@ -276,15 +276,15 @@ private class SelectStream implements StreamObject(f:Future, cb:X->Void) { + static public inline function trySync(f:Future, cb:(val:X, sync:Bool)->Void) { var tmp = f.handle(Helper.noop); return switch f.status { case Ready(result): - cb(result.get()); + cb(result.get(), true); null; default: - swapHandler(f, tmp, cb); + swapHandler(f, tmp, cb.bind(_, false)); } } static public function swapHandler(f:Future, prev:CallbackLink, cb) { @@ -300,11 +300,16 @@ class AsyncLinkStream implements StreamObject { public function new(link) this.link = link; - public function forEach(f:Consumer):Future> - return new Future((yield:(res:IterationResult)->Void) -> { + public function forEach(f:Consumer) + return new Future>(trigger -> { final wait = new CallbackLinkRef(); + var streaming = true; + function yield(v) { + streaming = false; + trigger(v); + } function loop(cur:AsyncLink) { - while (true) { + while (streaming) { switch cur.status { case Ready(result): switch result.get() { @@ -315,26 +320,14 @@ class AsyncLinkStream implements StreamObject { cast Failed(Stream.empty(), cast error);// GADT bug }); case Cons(item, tail): - function process(progress:Future>) { - switch progress.status { - case Ready(result): - switch result.get() { - case Some(v): - yield(Stopped(new AsyncLinkStream(tail), v)); - case None: - cur = tail; - return true; - } - default: - var tmp = progress.handle(Helper.noop); - if (progress.status.match(Ready(_))) - return process(progress); - else - wait.link = Helper.swapHandler(progress, tmp, _ -> if (process(progress)) loop(tail)); - } - return false; - } - if (process(f(item))) continue; + wait.link = Helper.trySync(f(item), (val, sync) -> switch val { + case Some(v): + yield(Stopped(new AsyncLinkStream(tail), v)); + case None: + if (sync) cur = tail; + else loop(tail); + }); + if (wait.link == null) continue; } default: wait.link = cur.handle(Helper.noop); From a1f5db5859c7f29da0d0d7fca7f5727bbad12bc5 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 08:38:11 +0100 Subject: [PATCH 12/26] Speed up synchronous streams. --- src/tink/streams/Stream.hx | 83 +++++++++++++++++++++----------------- 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 4a54da8..78b1038 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -54,7 +54,7 @@ abstract Stream(StreamObject) from StreamObject(t:Iterator):Stream - return AsyncLinkStream.ofIterator(t); + return SyncLinkStream.ofIterator(t); @:from static public function promise(p:Promise>):Stream return new PromiseStream(p); @@ -144,9 +144,6 @@ interface StreamObject { function forEach(f:Consumer):Future>; } -typedef AsyncLink = Future>; -typedef AsyncLinkKind = LinkKind> - private enum LinkKind { Fin(error:Null); Cons(head:Item, tail:Tail); @@ -294,7 +291,10 @@ private class Helper { } } -class AsyncLinkStream implements StreamObject { +private typedef AsyncLink = Future>; +private typedef AsyncLinkKind = LinkKind> + +private class AsyncLinkStream implements StreamObject { final link:AsyncLink; public function new(link) @@ -342,49 +342,56 @@ class AsyncLinkStream implements StreamObject { loop(link); return wait; }); - - static function iteratorLink(i:Iterator):AsyncLink - return Future.lazy(() -> if (i.hasNext()) Cons(i.next(), iteratorLink(i)) else Fin(null)); - - static public function ofIterator(i:Iterator):Stream - return new AsyncLinkStream(iteratorLink(i)); } -// typedef SyncLink = LinkKind>>; +private typedef SyncLink = Lazy>>; -// class SyncLinkStream implements StreamObject { -// final link:SyncLink; +private class SyncLinkStream implements StreamObject { + final link:SyncLink; -// public function new(link) -// this.link = link; + public function new(link) + this.link = link; -// public function forEach(f:Consumer) -// return new Future>(trigger -> { -// final wait = new CallbackLinkRef(); -// var running = true; + public function forEach(f:Consumer) + return new Future>(trigger -> { + final wait = new CallbackLinkRef(); + var streaming = true; -// function yield(v) { -// running = false; -// trigger(v); -// } + function yield(v) { + streaming = false; + trigger(v); + } + + function loop(cur:SyncLink) + while (streaming) + switch cur.get() { + case Fin(error): + yield(switch error { + case null: Done; + case e: cast Failed(Stream.empty(), cast e); + }); + case Cons(item, tail): + wait.link = Helper.trySync(f(item), (val, sync) -> switch val { + case Some(v): + yield(Stopped(new SyncLinkStream(tail), v)); + case None: + if (sync) cur = tail; + else loop(tail); + }); + if (wait.link == null) continue; + } -// function process(cur:SyncLink) -// while (running) -// switch cur { -// case Fin(error): -// yield(switch error { -// case null: Done; -// case e: Stopped(Stream.empty(), Failure(e)); -// }); -// case Cons(head, tail): + loop(link); -// } + return wait; + }); -// process(link); + static function iteratorLink(i:Iterator):SyncLink + return () -> if (i.hasNext()) Cons(i.next(), iteratorLink(i)) else Fin(null); -// return wait; -// }); -// } + static public function ofIterator(i:Iterator):Stream + return new SyncLinkStream(iteratorLink(i)); +} class SignalStream extends AsyncLinkStream { public function new(signal:Signal>) From 76835b8f136f2f3e95aa0b6ba5fc6c8de492548a Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 10:03:35 +0100 Subject: [PATCH 13/26] Add back read benchmarks. --- tests/Benchmark.hx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/Benchmark.hx b/tests/Benchmark.hx index 3906125..5cc7f91 100644 --- a/tests/Benchmark.hx +++ b/tests/Benchmark.hx @@ -64,7 +64,7 @@ class Benchmark { }); function tinkRead() - return new Named('tink', { + return new Named>('tink read', { var len = 0; readStream(dummy).forEach(item -> { len += item.length; @@ -72,13 +72,13 @@ class Benchmark { }).map(_ -> len); }); - function tinkCopy():Named> - return new Named('tink', { - writeStream(copy, cast readStream(dummy)); + function tinkCopy() + return new Named>('tink copy', { + writeStream(copy, cast readStream(dummy)).next(_ -> 42); }); function nodeRead() - return new Named('node', Future.irreversible(yield -> { + return new Named>('node read', Future.irreversible(yield -> { var len = 0; Fs.createReadStream(dummy) .on('data', (b:Buffer) -> len += b.length) @@ -87,11 +87,15 @@ class Benchmark { })); function nodeCopy() - return new Named>('node', Future.irreversible(yield -> { + return new Named>('node copy', Future.irreversible(yield -> { Fs.createReadStream(dummy) - .pipe(Fs.createWriteStream(copy), { end: true }).on('close', () -> yield(Success(Noise))); + .pipe(Fs.createWriteStream(copy), { end: true }).on('close', () -> yield(Success(42))); })); measure([ + nodeRead(), + tinkRead(), + nodeRead(), + tinkRead(), new Named(null, delete()), nodeCopy(), new Named(null, delete()), From 1040743ac3796d42d452c9ecbc0bcc0009a991af Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 15:00:39 +0100 Subject: [PATCH 14/26] Optimize select. --- src/tink/streams/Return.hx | 3 + src/tink/streams/Stream.hx | 142 ++++++++++++++++++++++--------------- tests/RunTests.hx | 5 -- tests/StreamTest.hx | 14 ++++ 4 files changed, 100 insertions(+), 64 deletions(-) diff --git a/src/tink/streams/Return.hx b/src/tink/streams/Return.hx index 3cb9e20..30c6358 100644 --- a/src/tink/streams/Return.hx +++ b/src/tink/streams/Return.hx @@ -8,6 +8,9 @@ abstract Return(Surprise) from Surprise { inline function new(v) this = v; + public inline function asFuture() + return this; + @:from static function ofError(e:Error):Return return ofPromise(e); diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 78b1038..ab51cbb 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -3,7 +3,7 @@ package tink.streams; import tink.core.Callback; using tink.CoreApi; -@:forward @:transitive +@:transitive @:using(tink.streams.RealStream) abstract Stream(StreamObject) from StreamObject { @@ -20,6 +20,9 @@ abstract Stream(StreamObject) from StreamObject(item:Item):Stream + return new SingleItem(item); + public function next():Future> return this.forEach(i -> Some(i)).map(function (r):Step return switch r { case Done: End; @@ -27,8 +30,20 @@ abstract Stream(StreamObject) from StreamObject(selector):Stream - return new SelectStream(this, selector); + public function select(selector):Stream { + return + if (Type.getClass(this) == SelectStream) { + SelectStream.chain(cast this, selector); + // var s:SelectStream = cast this; + // new SelectStream(s.source, SelectStream.chain(s.selector, selector)); + } + else new SelectStream(this, selector); + } + + public function forEach(f:Consumer):Future> + return new Future(trigger -> { + this.forEach(f).handle(trigger); + }); public function filter(f:Item->Return):Stream return select(i -> f(i).map(o -> switch o { @@ -115,7 +130,7 @@ private class PromiseStream implements StreamObject { }); } -class SingleItem implements StreamObject { +private class SingleItem implements StreamObject { final item:Item; public function new(item) this.item = item; @@ -191,9 +206,27 @@ private typedef Selector = In->Return, Quality>; private class SelectStream implements StreamObject { + static function chainSelectors( + a:Selector, + b:Selector + ):Selector + return v -> a(v).flatMap(o -> (switch o { + case Success(o): + switch o { + case Some(v): b(v); + case None: Success(None); + } + case Failure(e): Failure(e); + }:Return, Quality>).asFuture()); final source:Stream; final selector:Selector; + static public function chain( + a:SelectStream, + b:Selector + ) + return new SelectStream(a.source, chainSelectors(a.selector, b)); + public function new(source, selector) { this.source = source; this.selector = selector; @@ -205,12 +238,17 @@ private class SelectStream implements StreamObject(f:Consumer):Future> return source.forEach( - item -> selector(item).flatMap(o -> switch o { - case Success(None): None; - case Success(Some(v)): - f(v).map(o -> o.map(Success)); - case Failure(e): - Some(Failure(e)); + item -> new Future(trigger -> { + return + Helper.trySync(selector(item).asFuture(), (val, sync) -> switch val { + case Success(None): trigger(None); + case Success(Some(v)): + Helper.trySync(f(v), (val, sync) -> switch val { + case Some(v): trigger(Some(Success(v))); + case None: trigger(None); + }); + case Failure(e): trigger(Some(Failure(e))); + }); }) ).map(res -> switch res { case Done: Done; @@ -221,55 +259,41 @@ private class SelectStream implements StreamObject implements StreamObject { -// final source:Stream; -// final transform:In->Return; - -// public function new(source, transform) { -// this.source = source; -// this.transform = transform; -// } - -// public function forEach(f:Consumer):Future> -// return source.forEach(item -> transform(item).flatMap(out -> switch out { -// case Success(data): f(data).map(o -> switch o { -// case Some(v): Some(Success(v)); -// case None: None; -// }); -// case Failure(e): trace(e); Some(Failure(e)); -// })).map(res -> switch res { -// case Done: Done; -// case Stopped(rest, Success(result)): Stopped(rest, result); -// case Failed(rest, e) | Stopped(rest, Failure(e)): cast Failed(rest, e); -// }); -// } - -// class Grouped implements StreamObject { -// final source:Stream, Quality>; - -// public function new(source) -// this.source = source; - -// public function forEach(f:Consumer):Future> -// return -// source.forEach((group, done) -> -// AsyncLinkStream.ofIterator(group.iterator()) -// .forEach(f).map(res -> switch res { -// case Done: null; -// case Stopped(rest, result): done(new Pair(rest, result)); -// }) -// ).map(function (o):IterationResult return switch o { -// case Done: Done; -// case Stopped(rest, result): -// var rest = new Grouped(rest); -// switch result { -// case Success({ a: left, b: res }): -// Stopped(new StreamPair(left, rest), res); -// case Failure(failure): -// Stopped(rest, Failure(failure)); -// } -// }); -// } +class Grouped implements StreamObject { + final source:Stream, Quality>; + + public function new(source) + this.source = source; + + public function forEach(f:Consumer):Future> + return + source.forEach( + group -> switch group { + case []: None; + case [item]: + f(item).map(o -> switch o { + case Some(v): Some(new Pair(Stream.empty(), Success(v))); + case None: None; + }); + default: + SyncLinkStream.ofIterator(group.iterator()) + .forEach(f).map(res -> switch res { + case Done: None; + case Stopped(rest, result): Some(new Pair(rest, Success(result))); + case Failed(rest, e): Some(new Pair(rest, Failure(e))); + }); + } + ).map(function (o):IterationResult return switch o { + case Done: Done; + case Failed(rest, e): Failed(new Grouped(rest), e); + case Stopped(rest, { a: left, b: res }): + var rest = new StreamPair(left, new Grouped(cast rest)); + switch res { + case Success(data): Stopped(cast rest, data); + case Failure(e): cast Failed(rest, e); + } + }); +} private class Helper { static public function noop(_:Dynamic) {} diff --git a/tests/RunTests.hx b/tests/RunTests.hx index 7c86c1f..3341929 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -8,11 +8,6 @@ using tink.CoreApi; class RunTests { static function main() { - - #if python - (cast python.lib.Sys).setrecursionlimit(9999); - #end - Runner.run(TestBatch.make([ new StreamTest(), // new BlendTest(), diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index 9aee3fb..1de8ca1 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -192,6 +192,20 @@ class StreamTest { return asserts; } + public function depthTest() { + var s = Stream.single(1); + for (i in 0...10000) + s = s.map(f -> f + 1); + s.forEach(v -> Some(v)).eager().handle(res -> switch res { + case Stopped(rest, result): + asserts.assert(result == 10001); + asserts.done(); + default: + asserts.fail('Expected `Stopped`'); + }); + return asserts; + } + public function testNestedWithOuterError() { var n = ofOutcomes([ Success(Stream.ofIterator(0...3)), From 9103a0cddd9a4b6fda556a150d7db935d61c41d2 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 15:20:01 +0100 Subject: [PATCH 15/26] This is better. --- src/tink/streams/Stream.hx | 55 +++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index ab51cbb..7152c76 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -30,15 +30,12 @@ abstract Stream(StreamObject) from StreamObject(selector):Stream { + public function select(selector):Stream return - if (Type.getClass(this) == SelectStream) { + if (Type.getClass(this) == SelectStream) SelectStream.chain(cast this, selector); - // var s:SelectStream = cast this; - // new SelectStream(s.source, SelectStream.chain(s.selector, selector)); - } - else new SelectStream(this, selector); - } + else + new SelectStream(this, selector); public function forEach(f:Consumer):Future> return new Future(trigger -> { @@ -206,27 +203,9 @@ private typedef Selector = In->Return, Quality>; private class SelectStream implements StreamObject { - static function chainSelectors( - a:Selector, - b:Selector - ):Selector - return v -> a(v).flatMap(o -> (switch o { - case Success(o): - switch o { - case Some(v): b(v); - case None: Success(None); - } - case Failure(e): Failure(e); - }:Return, Quality>).asFuture()); final source:Stream; final selector:Selector; - static public function chain( - a:SelectStream, - b:Selector - ) - return new SelectStream(a.source, chainSelectors(a.selector, b)); - public function new(source, selector) { this.source = source; this.selector = selector; @@ -257,6 +236,32 @@ private class SelectStream implements StreamObject( + a:SelectStream, + b:Selector + ) + return new SelectStream(a.source, chainSelectors(a.selector, b)); + + static function chainSelectors( + a:Selector, + b:Selector + ):Selector + return v -> new Future( + trigger -> { + final inner = new CallbackLinkRef(); + + a(v).handle(o -> switch o { + case Success(None): + trigger(Success(None)); + case Success(Some(v)): + inner.link = b(v).handle(trigger); + case Failure(e): + trigger(Failure(e)); + }).join(inner); + } + ); } class Grouped implements StreamObject { From 442e3a7e908c3c410ef038cfed7e9a96367fb9ec Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 18:09:09 +0100 Subject: [PATCH 16/26] Start adding some tests for laziness. --- src/tink/streams/Stream.hx | 2 +- tests/StreamTest.hx | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 7152c76..cea3408 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -264,7 +264,7 @@ private class SelectStream implements StreamObject implements StreamObject { +private class Grouped implements StreamObject { final source:Stream, Quality>; public function new(source) diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index 1de8ca1..e54a67e 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -251,6 +251,36 @@ class StreamTest { // } // #end + @:include public function laziness() { + var triggers = []; + var s = Stream.generate(() -> { + var t = new FutureTrigger(); + triggers.push(t); + t.asFuture().map(Data); + }); + + var res = s.forEach(t -> if (t < 20) None else Some(t)).eager(); + + for (i in 0...21) { + asserts.assert(triggers.length == i + 1); + asserts.assert(res.status.match(EagerlyAwaited)); + triggers[i].trigger(i); + } + + asserts.assert(res.status.match(Ready(_.get() => Stopped(_, 20)))); + + var res = s.forEach(t -> if (t < 40) None else Some(t)).eager(); + + for (i in 21...41) { + asserts.assert(triggers.length == i + 1); + asserts.assert(res.status.match(EagerlyAwaited)); + triggers[i].trigger(i); + } + + asserts.assert(res.status.match(Ready(_.get() => Stopped(_, 40)))); + + return asserts.done(); + } // maybe useful to be moved to Stream itself inline function ofOutcomes(i:Iterator>) { From 9c8fe27dd304ba58814696f1f509085e63b4d84d Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 11 Feb 2021 19:43:54 +0100 Subject: [PATCH 17/26] Seems better. --- src/tink/streams/Stream.hx | 29 +++++++------- tests/RunTests.hx | 3 +- tests/StreamTest.hx | 77 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 90 insertions(+), 19 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index cea3408..495aca5 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -38,10 +38,7 @@ abstract Stream(StreamObject) from StreamObject(f:Consumer):Future> - return new Future(trigger -> { - this.forEach(f).handle(trigger); - }); - + return this.forEach(f); public function filter(f:Item->Return):Stream return select(i -> f(i).map(o -> switch o { case Success(matched): Success(if (matched) Some(i) else None); @@ -329,7 +326,8 @@ private class AsyncLinkStream implements StreamObject(f:Consumer) + public function forEach(f:Consumer) { + var pos = link; return new Future>(trigger -> { final wait = new CallbackLinkRef(); var streaming = true; @@ -337,11 +335,11 @@ private class AsyncLinkStream implements StreamObject) { + function loop() { while (streaming) { - switch cur.status { - case Ready(result): - switch result.get() { + switch pos.status { + case Ready(_.get() => result): + switch result { case Fin(v): yield(switch v { case null: Done; @@ -353,24 +351,25 @@ private class AsyncLinkStream implements StreamObject loop(cur));// this is very lazy + wait.link = Helper.swapHandler(pos, wait, loop);// this is very lazy } break; } } - loop(link); + loop(); return wait; }); + } } private typedef SyncLink = Lazy>>; diff --git a/tests/RunTests.hx b/tests/RunTests.hx index 3341929..bc8a2c9 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -8,9 +8,10 @@ using tink.CoreApi; class RunTests { static function main() { + // new StreamTest().laziness(null); Runner.run(TestBatch.make([ new StreamTest(), - // new BlendTest(), + // // new BlendTest(), new NextTest(), new SignalStreamTest(), ])).handle(Runner.exit); diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index e54a67e..e4c67c8 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -251,12 +251,55 @@ class StreamTest { // } // #end - @:include public function laziness() { + public function suspend() { + var triggers = []; var s = Stream.generate(() -> { - var t = new FutureTrigger(); + + var t = new FutureTrigger>(); triggers.push(t); - t.asFuture().map(Data); + t.asFuture().map(v -> if (v == null) Yield.End else Data(v)); + }); + + var log = []; + var res = s.forEach(v -> { log.push(v); None; }); + var active = res.handle(function () {}); + + function progress() + triggers[triggers.length - 1].trigger(triggers.length - 1); + + for (i in 0...5) + progress(); + + active.cancel(); + + progress(); + + res.eager(); + triggers[triggers.length - 1].trigger(null); + asserts.assert(res.status.match(Ready(_.get() => Done))); + + asserts.assert(log.join(',') == [for (i in 0...triggers.length - 1) i].join(',')); + + return asserts.done(); + } + + static public var verbose = false; + + public function laziness() { + + var triggers = [], + counter = 0; + var s = Stream.generate(() -> { + + var t = switch triggers[counter] { + case null: triggers[counter] = new FutureTrigger>(); + case v: v; + } + + counter++; + + t.asFuture().map(v -> if (v == null) Yield.End else Data(v)); }); var res = s.forEach(t -> if (t < 20) None else Some(t)).eager(); @@ -279,6 +322,34 @@ class StreamTest { asserts.assert(res.status.match(Ready(_.get() => Stopped(_, 40)))); + var log = []; + var res = s.forEach(t -> { log.push(t); None; }); + var active:CallbackLink = null; + + active = res.handle(function () {}); + + for (i in 0...5) { + asserts.assert(triggers.length == 42 + i); + triggers[triggers.length - 1].trigger(triggers.length - 1); + } + + active.cancel(); + + for (i in 0...5) { + var t = new FutureTrigger(); + t.trigger(triggers.length - 1); + triggers.push(t); + } + + var t = new FutureTrigger(); + t.trigger(null); + triggers.push(t); + + res.eager(); + + asserts.assert(res.status.match(Ready(_.get() => Done))); + asserts.assert(log.join(',') == [for (i in 0...triggers.length - 2) i].join(',')); + return asserts.done(); } From 86b019273fcecd8961db6280287310886e191494 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Fri, 12 Feb 2021 08:03:43 +0100 Subject: [PATCH 18/26] It's getting there. --- src/tink/streams/Stream.hx | 108 +++++++++++++++++++++++-------------- tests/NextTest.hx | 2 +- tests/StreamTest.hx | 9 ++-- 3 files changed, 74 insertions(+), 45 deletions(-) diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 495aca5..f90a96e 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -39,6 +39,7 @@ abstract Stream(StreamObject) from StreamObject(f:Consumer):Future> return this.forEach(f); + public function filter(f:Item->Return):Stream return select(i -> f(i).map(o -> switch o { case Success(matched): Success(if (matched) Some(i) else None); @@ -60,7 +61,7 @@ abstract Stream(StreamObject) from StreamObject) - return new StreamPair(this, b); + return new Compound([this, b]); @:from static public function ofIterator(t:Iterator):Stream return SyncLinkStream.ofIterator(t); @@ -99,7 +100,7 @@ private class FlattenStream implements StreamObject switch r { case Done: Done; case Stopped(rest, result): - var rest = new StreamPair(result.a, new FlattenStream(rest)); + var rest = result.a.append(new FlattenStream(rest)); switch result.b { case Success(data): Stopped(rest, data); case Failure(failure): cast Failed(cast rest, failure); @@ -143,6 +144,7 @@ private class SingleItem implements StreamObject { ); } +@:using(Stream.IterationResultTools) enum IterationResult { Done:IterationResult; Failed(rest:Stream, e:Error):IterationResult; @@ -170,30 +172,44 @@ private class Empty implements StreamObject { return Done; } -private class StreamPair implements StreamObject { - final l:Stream; - final r:Stream; +private class Compound implements StreamObject { + final parts:Array>; - public function new(l, r) { - this.l = l; - this.r = r; + public function new(parts) { + this.parts = parts; } - public function forEach(f:Consumer) + public function forEach(f:Consumer) { + var index = 0, + cur = Future.sync(Done); return new Future>(trigger -> { - final ret = new CallbackLinkRef(); + final wait = new CallbackLinkRef(); - ret.link = Helper.trySync(l.forEach(f), (res, _) -> switch res { - case Done: - ret.link = Helper.trySync(r.forEach(f), (v, _) -> trigger(v)); - case Failed(rest, e): - trigger(Failed(new StreamPair(rest, cast r), e));//TODO: this is GADT bug - case Stopped(rest, result): - trigger(Stopped(new StreamPair(rest, r), result)); - }); + var streaming = true; + function yield(v) { + streaming = false; + trigger(v); + } - return ret; + function loop() + while (streaming) { + wait.link = Helper.trySync(cur, (val, sync) -> switch val { + case Done: + if (index < parts.length) + cur = parts[index++].forEach(f); + else + yield(Done); + + if (!sync) loop(); + case v: + yield(v.withStream(s -> s.append(new Compound(parts.slice(index))))); + }); + if (wait.link != null) break; + } + loop(); + return wait; }); + } } private typedef Selector = In->Return, Quality>; @@ -214,18 +230,21 @@ private class SelectStream implements StreamObject(f:Consumer):Future> return source.forEach( - item -> new Future(trigger -> { - return - Helper.trySync(selector(item).asFuture(), (val, sync) -> switch val { - case Success(None): trigger(None); - case Success(Some(v)): - Helper.trySync(f(v), (val, sync) -> switch val { - case Some(v): trigger(Some(Success(v))); - case None: trigger(None); - }); - case Failure(e): trigger(Some(Failure(e))); - }); - }) + item -> { + var selected = selector(item).asFuture(); + new Future(trigger -> { + return + Helper.trySync(selected, (val, sync) -> switch val { + case Success(None): trigger(None); + case Success(Some(v)): + Helper.trySync(f(v), (val, sync) -> switch val { + case Some(v): trigger(Some(Success(v))); + case None: trigger(None); + }); + case Failure(e): trigger(Some(Failure(e))); + }); + }); + } ).map(res -> switch res { case Done: Done; case Stopped(rest, Success(result)): @@ -289,7 +308,7 @@ private class Grouped implements StreamObject { case Done: Done; case Failed(rest, e): Failed(new Grouped(rest), e); case Stopped(rest, { a: left, b: res }): - var rest = new StreamPair(left, new Grouped(cast rest)); + var rest = left.append(new Grouped(cast rest)); switch res { case Success(data): Stopped(cast rest, data); case Failure(e): cast Failed(rest, e); @@ -297,7 +316,17 @@ private class Grouped implements StreamObject { }); } +class IterationResultTools { + static public function withStream(i:IterationResult, f:Stream->Stream):IterationResult + return switch i { + case Done: Done; + case Failed(rest, e): Failed(f(rest), e); + case Stopped(rest, result): Stopped(f(rest), result); + } +} + private class Helper { + static public function noop(_:Dynamic) {} static public inline function trySync(f:Future, cb:(val:X, sync:Bool)->Void) { var tmp = f.handle(Helper.noop); @@ -380,7 +409,8 @@ private class SyncLinkStream implements StreamObject(f:Consumer) + public function forEach(f:Consumer) { + var pos = link; return new Future>(trigger -> { final wait = new CallbackLinkRef(); var streaming = true; @@ -390,9 +420,9 @@ private class SyncLinkStream implements StreamObject) + function loop() while (streaming) - switch cur.get() { + switch pos.get() { case Fin(error): yield(switch error { case null: Done; @@ -403,16 +433,16 @@ private class SyncLinkStream implements StreamObject(i:Iterator):SyncLink return () -> if (i.hasNext()) Cons(i.next(), iteratorLink(i)) else Fin(null); diff --git a/tests/NextTest.hx b/tests/NextTest.hx index bb9d5d3..06bf6f4 100644 --- a/tests/NextTest.hx +++ b/tests/NextTest.hx @@ -58,7 +58,7 @@ class NextTest { for(i in 0...values.length) { current.next().handle(function(v) switch v { case Link(v, rest): asserts.assert(values[i] == v, pos); current = rest; - default: asserts.fail('Expected Link(_)', pos); + default: asserts.fail('Expected Link(_), got ${v.getName()} @ $i', pos); }); } current.next().handle(function(v) asserts.assert(v.match(End), pos)); diff --git a/tests/StreamTest.hx b/tests/StreamTest.hx index e4c67c8..6232e1b 100644 --- a/tests/StreamTest.hx +++ b/tests/StreamTest.hx @@ -283,9 +283,6 @@ class StreamTest { return asserts.done(); } - - static public var verbose = false; - public function laziness() { var triggers = [], @@ -323,7 +320,7 @@ class StreamTest { asserts.assert(res.status.match(Ready(_.get() => Stopped(_, 40)))); var log = []; - var res = s.forEach(t -> { log.push(t); None; }); + var res = (s...s).forEach(t -> { log.push(t); None; }); var active:CallbackLink = null; active = res.handle(function () {}); @@ -348,7 +345,9 @@ class StreamTest { res.eager(); asserts.assert(res.status.match(Ready(_.get() => Done))); - asserts.assert(log.join(',') == [for (i in 0...triggers.length - 2) i].join(',')); + var expected = [for (i in 0...triggers.length - 2) i].join(','); + expected = '$expected,$expected'; + asserts.assert(log.join(',') == expected); return asserts.done(); } From 15c575bfe20ad4e892fb9274b2963a7d3ee4b88a Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Fri, 12 Feb 2021 16:09:56 +0100 Subject: [PATCH 19/26] Let's see if this passes. --- .travis.yml | 11 +++++------ extraParams.hxml | 2 -- haxe_libraries/hxcpp.hxml | 8 ++++---- haxe_libraries/hxcs.hxml | 4 ++++ haxe_libraries/hxjava.hxml | 8 ++++---- haxe_libraries/tink_testrunner.hxml | 6 +++--- src/tink/streams/Stream.hx | 24 +++++++++++------------- tests.hxml | 3 +-- 8 files changed, 32 insertions(+), 34 deletions(-) delete mode 100644 extraParams.hxml create mode 100644 haxe_libraries/hxcs.hxml diff --git a/.travis.yml b/.travis.yml index e6b5570..12aa424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,14 +13,14 @@ os: # - osx env: - - HAXE_VERSION=3.4.7 + - HAXE_VERSION=stable - HAXE_VERSION=nightly - + install: - npm i -g lix@15.3.13 - lix install haxe $HAXE_VERSION - lix download - + script: # - lix run travix interp # runtime stack overflow - lix run travix neko @@ -28,10 +28,9 @@ script: - lix run travix node - lix run travix js # - lix run travix flash - - lix run travix java - - if [[ "$(haxe -version)" =~ ^4.* ]]; then lix run travix java -D jvm; fi + - lix run travix java -D jvm - lix run travix cpp - # - lix run travix cs # gencs stack overflow, to be investigated + - lix run travix cs -D erase-generics - lix run travix php - lix run travix lua diff --git a/extraParams.hxml b/extraParams.hxml deleted file mode 100644 index f06da6b..0000000 --- a/extraParams.hxml +++ /dev/null @@ -1,2 +0,0 @@ -# temp for development, delete this file when pure branch merged --D pure \ No newline at end of file diff --git a/haxe_libraries/hxcpp.hxml b/haxe_libraries/hxcpp.hxml index b09fbe6..09ac2ce 100644 --- a/haxe_libraries/hxcpp.hxml +++ b/haxe_libraries/hxcpp.hxml @@ -1,4 +1,4 @@ --D hxcpp=4.0.8 -# @install: lix --silent download "haxelib:/hxcpp#4.0.8" into hxcpp/4.0.8/haxelib -# @run: haxelib run-dir hxcpp ${HAXE_LIBCACHE}/hxcpp/4.0.8/haxelib --cp ${HAXE_LIBCACHE}/hxcpp/4.0.8/haxelib/ +# @install: lix --silent download "haxelib:/hxcpp#4.2.1" into hxcpp/4.2.1/haxelib +# @run: haxelib run-dir hxcpp ${HAXE_LIBCACHE}/hxcpp/4.2.1/haxelib +-cp ${HAXE_LIBCACHE}/hxcpp/4.2.1/haxelib/ +-D hxcpp=4.2.1 \ No newline at end of file diff --git a/haxe_libraries/hxcs.hxml b/haxe_libraries/hxcs.hxml new file mode 100644 index 0000000..72f2a5b --- /dev/null +++ b/haxe_libraries/hxcs.hxml @@ -0,0 +1,4 @@ +# @install: lix --silent download "haxelib:/hxcs#4.2.0" into hxcs/4.2.0/haxelib +# @run: haxelib run-dir hxcs ${HAXE_LIBCACHE}/hxcs/4.2.0/haxelib +-cp ${HAXE_LIBCACHE}/hxcs/4.2.0/haxelib/ +-D hxcs=4.2.0 \ No newline at end of file diff --git a/haxe_libraries/hxjava.hxml b/haxe_libraries/hxjava.hxml index 0942a5a..bf7270e 100644 --- a/haxe_libraries/hxjava.hxml +++ b/haxe_libraries/hxjava.hxml @@ -1,5 +1,5 @@ --D hxjava=3.2.0 -# @install: lix --silent download "haxelib:/hxjava#3.2.0" into hxjava/3.2.0/haxelib -# @run: haxelib run-dir hxjava ${HAXE_LIBCACHE}/hxjava/3.2.0/haxelib --cp ${HAXE_LIBCACHE}/hxjava/3.2.0/haxelib/ +# @install: lix --silent download "haxelib:/hxjava#4.2.0" into hxjava/4.2.0/haxelib +# @run: haxelib run-dir hxjava ${HAXE_LIBCACHE}/hxjava/4.2.0/haxelib +-cp ${HAXE_LIBCACHE}/hxjava/4.2.0/haxelib/ +-D hxjava=4.2.0 -java-lib lib/hxjava-std.jar diff --git a/haxe_libraries/tink_testrunner.hxml b/haxe_libraries/tink_testrunner.hxml index 6ddb4d6..d5a2d6f 100644 --- a/haxe_libraries/tink_testrunner.hxml +++ b/haxe_libraries/tink_testrunner.hxml @@ -1,6 +1,6 @@ +# @install: lix --silent download "gh://github.com/haxetink/tink_testrunner#866de8b991be89b969825b0c0f5565d51f96a6f7" into tink_testrunner/0.8.0/github/866de8b991be89b969825b0c0f5565d51f96a6f7 -lib ansi -lib tink_macro -lib tink_streams --cp ${SCOPE_DIR}/../testrunner/src --D tink_testrunner=0.8.0 ---macro Sys.println("haxe_libraries/tink_testrunner.hxml:5: [Warning] Using dev version of library tink_testrunner") \ No newline at end of file +-cp ${HAXE_LIBCACHE}/tink_testrunner/0.8.0/github/866de8b991be89b969825b0c0f5565d51f96a6f7/src +-D tink_testrunner=0.8.0 \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index f90a96e..2cbb2a2 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -37,7 +37,7 @@ abstract Stream(StreamObject) from StreamObject(f:Consumer):Future> + public function forEach(f:(item:Item)->Future>):Future> return this.forEach(f); public function filter(f:Item->Return):Stream @@ -91,7 +91,7 @@ private class FlattenStream implements StreamObject(f:Consumer):Future> + public function forEach(f:(item:Item)->Future>):Future> return source.forEach(child -> child.forEach(f).map(r -> switch r { case Done: None; @@ -116,7 +116,7 @@ private class PromiseStream implements StreamObject { public function new(stream) this.stream = stream; - public function forEach(f:Consumer):Future> + public function forEach(f:(item:Item)->Future>):Future> return stream.next(s -> s.forEach(f)).map(o -> switch o { case Success(data): data; @@ -130,7 +130,7 @@ private class SingleItem implements StreamObject { public function new(item) this.item = item; - public function forEach(f:Consumer) + public function forEach(f:(item:Item)->Future>) return new Future>( trigger -> Helper.trySync( f(item), @@ -152,7 +152,7 @@ enum IterationResult { } interface StreamObject { - function forEach(f:Consumer):Future>; + function forEach(f:(item:Item)->Future>):Future>; } private enum LinkKind { @@ -160,15 +160,13 @@ private enum LinkKind { Cons(head:Item, tail:Tail); } -typedef Consumer = (item:Item)->Future>; - private class Empty implements StreamObject { static final INST:StreamObject = new Empty(); function new() {} - public function forEach(f:Consumer):Future> + public function forEach(f:(item:Item)->Future>):Future> return Done; } @@ -179,7 +177,7 @@ private class Compound implements StreamObject { this.parts = parts; } - public function forEach(f:Consumer) { + public function forEach(f:(item:Item)->Future>) { var index = 0, cur = Future.sync(Done); return new Future>(trigger -> { @@ -227,7 +225,7 @@ private class SelectStream implements StreamObject return new SelectStream(source, selector); - public function forEach(f:Consumer):Future> + public function forEach(f:(item:Out)->Future>):Future> return source.forEach( item -> { @@ -286,7 +284,7 @@ private class Grouped implements StreamObject { public function new(source) this.source = source; - public function forEach(f:Consumer):Future> + public function forEach(f:(item:Item)->Future>):Future> return source.forEach( group -> switch group { @@ -355,7 +353,7 @@ private class AsyncLinkStream implements StreamObject(f:Consumer) { + public function forEach(f:(item:Item)->Future>) { var pos = link; return new Future>(trigger -> { final wait = new CallbackLinkRef(); @@ -409,7 +407,7 @@ private class SyncLinkStream implements StreamObject(f:Consumer) { + public function forEach(f:(item:Item)->Future>) { var pos = link; return new Future>(trigger -> { final wait = new CallbackLinkRef(); diff --git a/tests.hxml b/tests.hxml index a74b72e..dace381 100644 --- a/tests.hxml +++ b/tests.hxml @@ -1,4 +1,3 @@ -cp ./tests/ -main RunTests --lib tink_unittest --D no-deprecation-warnings \ No newline at end of file +-lib tink_unittest \ No newline at end of file From f4478825ef0a30df1187f02a354ec61176b47b8b Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Fri, 12 Feb 2021 16:28:36 +0100 Subject: [PATCH 20/26] Bump deps. --- .travis.yml | 4 ++-- haxe_libraries/tink_core.hxml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 12aa424..dba47bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ stages: - deploy language: node_js -node_js: 8 +node_js: 14 os: - linux @@ -17,7 +17,7 @@ env: - HAXE_VERSION=nightly install: - - npm i -g lix@15.3.13 + - npm i -g lix@15.9.0 - lix install haxe $HAXE_VERSION - lix download diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index 3b18526..09316d6 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_core#1de5655e914441a6fca86a5da445aa064446a30a" into tink_core/2.0.0-rc.2/github/1de5655e914441a6fca86a5da445aa064446a30a --cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/1de5655e914441a6fca86a5da445aa064446a30a/src +# @install: lix --silent download "gh://github.com/haxetink/tink_core#0e362b608d587b524d9e7da7226f547f912814a6" into tink_core/2.0.0-rc.2/github/0e362b608d587b524d9e7da7226f547f912814a6 +-cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/0e362b608d587b524d9e7da7226f547f912814a6/src -D tink_core=2.0.0-rc.2 \ No newline at end of file From 6ce1d025fa3eeebce9871e05cffd350f5d040075 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Mon, 22 Feb 2021 14:43:43 +0100 Subject: [PATCH 21/26] Make nodejs streams work again. --- src/tink/streams/nodejs/NodejsStream.hx | 69 ++++++++++++++++++---- src/tink/streams/nodejs/WrappedReadable.hx | 49 --------------- 2 files changed, 56 insertions(+), 62 deletions(-) delete mode 100644 src/tink/streams/nodejs/WrappedReadable.hx diff --git a/src/tink/streams/nodejs/NodejsStream.hx b/src/tink/streams/nodejs/NodejsStream.hx index 70beccb..d40a353 100644 --- a/src/tink/streams/nodejs/NodejsStream.hx +++ b/src/tink/streams/nodejs/NodejsStream.hx @@ -1,21 +1,64 @@ package tink.streams.nodejs; +import tink.core.Callback; +import js.node.stream.Readable; import tink.streams.Stream; using tink.CoreApi; -class NodejsStream extends Generator { - - function new(target:WrappedReadable) { - super(Future.async(function (cb) { - target.read().handle(function (o) cb(switch o { - case Success(null): End; - case Success(data): Link(data, new NodejsStream(target)); - case Failure(e): Fail(e); - })); - })); +class NodejsStream { + static public function wrap(name:String, native:IReadable) { + + function failure(e:Dynamic) + return Yield.Fail(tink.core.Error.withData('failed to read from $name', e)); + + final ended = new Future(yield -> { + function end(_) + yield(Yield.End); + + function fail(e:Dynamic) + yield(failure(e)); + + native.on('end', end); + native.on('close', end); + native.on('error', fail); + + () -> { + native.off('end', end); + native.off('close', end); + native.off('error', fail); + } + }); + + final becameReadable = new Signal(fire -> { + native.on('readable', fire); + () -> native.off('readable', fire); + }); + + return Stream.generate(() -> ended || new Future>( + yield -> { + if (native.readableEnded) { + yield(End); + return null; + } + + final ret = new CallbackLinkRef(); + + function attempt() + try switch native.read() { + case null: + ret.link = becameReadable.nextTime().handle(attempt); + case v: + yield(Data(v)); + } + catch (e:Dynamic) yield(failure(e)); + + attempt(); + + return ret; + } + )); + } - static public function wrap(name, native, onEnd) - return new NodejsStream(new WrappedReadable(name, native, onEnd)); - + } \ No newline at end of file diff --git a/src/tink/streams/nodejs/WrappedReadable.hx b/src/tink/streams/nodejs/WrappedReadable.hx deleted file mode 100644 index 5a41004..0000000 --- a/src/tink/streams/nodejs/WrappedReadable.hx +++ /dev/null @@ -1,49 +0,0 @@ -package tink.streams.nodejs; - -import js.node.Buffer; -import js.node.stream.Readable.IReadable; - -using tink.CoreApi; - -class WrappedReadable { - - var native:IReadable; - var name:String; - var end:Surprise, Error>; - - public function new(name, native, onEnd) { - this.name = name; - this.native = native; - - end = Future.async(function (cb) { - native.once('end', function () cb(Success(null))); - native.once('close', function () cb(Success(null))); - native.once('error', function (e:{ code:String, message:String }) cb(Failure(new Error('${e.code} - Failed reading from $name because ${e.message}')))); - }) - .eager(); // async laziness fix for tink_core v2 - if (onEnd != null) - end.handle(function () - js.Node.process.nextTick(onEnd) - ); - } - - public function read():Promise>{ - return Future.async(function (cb) { - function attempt() { - try - switch native.read() { - case null: - native.once('readable', attempt); - case object: - cb(Success((cast object:T))); - } - catch (e:Dynamic) { - cb(Failure(Error.withData('Error while reading from $name', e))); - } - } - - attempt(); - //end.handle(cb); - }).first(end); - } -} \ No newline at end of file From d3ed2a1656e625b654276f363ea573edb5ed8a7a Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Mon, 22 Feb 2021 15:06:27 +0100 Subject: [PATCH 22/26] Implement piping. --- src/tink/streams/nodejs/NodejsStream.hx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tink/streams/nodejs/NodejsStream.hx b/src/tink/streams/nodejs/NodejsStream.hx index d40a353..0c17b2d 100644 --- a/src/tink/streams/nodejs/NodejsStream.hx +++ b/src/tink/streams/nodejs/NodejsStream.hx @@ -1,12 +1,27 @@ package tink.streams.nodejs; -import tink.core.Callback; +import js.node.stream.Writable; import js.node.stream.Readable; +import tink.core.Callback; import tink.streams.Stream; using tink.CoreApi; class NodejsStream { + + static public function pipe(s:Stream, name:String, dest:IWritable):Future> { + return s.forEach(item -> Future.irreversible( + yield -> + dest.write(item, (?e:js.lib.Error) -> yield(switch e { + case null: None; + case e: Some(e); + })) + )).map(o -> switch o { + case Done: Done; + case Stopped(rest, e): Failed(cast rest, tink.core.Error.withData('Failed to write to $name', e)); + }); + } + static public function wrap(name:String, native:IReadable) { function failure(e:Dynamic) From 48434d644129e50e9698fa0a874f634165ee5bed Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Wed, 21 Jul 2021 11:42:58 +0200 Subject: [PATCH 23/26] Add back more of the old api. --- .vscode/tasks.json | 27 ++++++++++++++--- src/tink/streams/Stream.hx | 62 +++++++++++++++++++++++++++++--------- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f56a473..82d0735 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,7 +1,26 @@ { - "version": "0.1.0", + "version": "2.0.0", "command": "npm", - "args": ["run","--silent","travix","node"], - "isShellCommand": true, - "problemMatcher": "$haxe" + "args": [ + "run", + "--silent", + "travix", + "node" + ], + "problemMatcher": "$haxe", + "tasks": [ + { + "label": "npm", + "type": "shell", + "command": "npm", + "args": [ + "run", + "--silent", + "travix", + "node" + ], + "problemMatcher": "$haxe", + "group": "build" + } + ] } diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 2cbb2a2..981136c 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -5,19 +5,10 @@ using tink.CoreApi; @:transitive @:using(tink.streams.RealStream) -abstract Stream(StreamObject) from StreamObject { +abstract Stream(StreamObject) from StreamObject to StreamObject { - static public function generate(generator:()->Future>):Stream { - function rec():AsyncLink - return Future.irreversible(yield -> { - generator().handle(o -> yield(switch o { - case Data(data): Cons(data, rec()); - case Fail(e): Fin(cast e); - case End: Fin(null); - })); - }); - - return new AsyncLinkStream(rec()); + static public function generate(generate:()->Future>):Stream { + return new Generator(generate); } static public function single(item:Item):Stream @@ -66,6 +57,9 @@ abstract Stream(StreamObject) from StreamObject(t:Iterator):Stream return SyncLinkStream.ofIterator(t); + @:from static public function future(p:Future>):Stream + return new FutureStream(p); + @:from static public function promise(p:Promise>):Stream return new PromiseStream(p); @@ -110,6 +104,16 @@ private class FlattenStream implements StreamObject implements StreamObject { + final stream:Future>; + + public function new(stream) + this.stream = stream; + + public function forEach(f:(item:Item)->Future>):Future> + return stream.flatMap(s -> s.forEach(f)); + +} private class PromiseStream implements StreamObject { final stream:Promise>; @@ -117,9 +121,9 @@ private class PromiseStream implements StreamObject { this.stream = stream; public function forEach(f:(item:Item)->Future>):Future> - return stream.next(s -> s.forEach(f)).map(o -> switch o { - case Success(data): - data; + return stream.flatMap(o -> switch o { + case Success(s): + s.forEach(f); case Failure(e): Failed(Stream.empty(), e); }); @@ -401,6 +405,34 @@ private class AsyncLinkStream implements StreamObject = Lazy>>; +class Generator extends AsyncLinkStream { + public function new(generate:()->Future>) { + function rec():AsyncLink + return Future.irreversible(yield -> + generate().handle(o -> yield(switch o { + case Data(data): Cons(data, rec()); + case Fail(e): Fin(cast e); + case End: Fin(null); + })) + ); + // TODO: in theory, this should work too + // ... but suspending the step makes StreamTest.laziness fail (stream becomes too lazy) + // ... which has something to do with suspension within compoundstream ... go figure + // { + // var step = generate(); + // new Future(yield -> + // step.handle(o -> yield(switch o { + // case Data(data): Cons(data, rec()); + // case Fail(e): Fin(cast e); + // case End: Fin(null); + // })); + // ) + // } + + super(rec()); + } +} + private class SyncLinkStream implements StreamObject { final link:SyncLink; From 6e179eabc1f4a13601ac3b2611245fcdbdadd6a4 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Fri, 2 Feb 2024 16:27:45 +0100 Subject: [PATCH 24/26] Deal with blend streams. --- .haxerc | 2 +- .vscode/settings.json | 5 ---- .vscode/tasks.json | 40 +++++++++----------------- haxe_libraries/hxnodejs.hxml | 4 +-- haxe_libraries/tink_core.hxml | 6 ++-- src/tink/streams/Stream.hx | 53 +++++++++++++++++++++++++++++++++-- tests/BlendTest.hx | 53 ++++++++++++++++++++--------------- tests/RunTests.hx | 3 +- 8 files changed, 102 insertions(+), 64 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.haxerc b/.haxerc index e68f622..3ee99dd 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "4.1.5", + "version": "4.3.3", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6a40fe7..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "haxe.displayConfigurations": [ - ["dev.hxml"] - ] -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 82d0735..99242b8 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,26 +1,14 @@ -{ - "version": "2.0.0", - "command": "npm", - "args": [ - "run", - "--silent", - "travix", - "node" - ], - "problemMatcher": "$haxe", - "tasks": [ - { - "label": "npm", - "type": "shell", - "command": "npm", - "args": [ - "run", - "--silent", - "travix", - "node" - ], - "problemMatcher": "$haxe", - "group": "build" - } - ] -} +{ + "version": "2.0.0", + "tasks": [ + { + "type": "hxml", + "file": "dev.hxml", + "group": { + "kind": "build", + "isDefault": true + }, + "label": "hxml: dev.hxml" + } + ] +} \ No newline at end of file diff --git a/haxe_libraries/hxnodejs.hxml b/haxe_libraries/hxnodejs.hxml index c075488..7772815 100644 --- a/haxe_libraries/hxnodejs.hxml +++ b/haxe_libraries/hxnodejs.hxml @@ -1,5 +1,5 @@ -# @install: lix --silent download "haxelib:/hxnodejs#12.1.0" into hxnodejs/12.1.0/haxelib --cp ${HAXE_LIBCACHE}/hxnodejs/12.1.0/haxelib/src +# @install: lix --silent download "gh://github.com/HaxeFoundation/hxnodejs#504066dc1ba5ad543afa5f6c3ea019f06136a82b" into hxnodejs/12.1.0/github/504066dc1ba5ad543afa5f6c3ea019f06136a82b +-cp ${HAXE_LIBCACHE}/hxnodejs/12.1.0/github/504066dc1ba5ad543afa5f6c3ea019f06136a82b/src -D hxnodejs=12.1.0 --macro allowPackage('sys') # should behave like other target defines and not be defined in macro context diff --git a/haxe_libraries/tink_core.hxml b/haxe_libraries/tink_core.hxml index 09316d6..2354244 100644 --- a/haxe_libraries/tink_core.hxml +++ b/haxe_libraries/tink_core.hxml @@ -1,3 +1,3 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_core#0e362b608d587b524d9e7da7226f547f912814a6" into tink_core/2.0.0-rc.2/github/0e362b608d587b524d9e7da7226f547f912814a6 --cp ${HAXE_LIBCACHE}/tink_core/2.0.0-rc.2/github/0e362b608d587b524d9e7da7226f547f912814a6/src --D tink_core=2.0.0-rc.2 \ No newline at end of file +# @install: lix --silent download "gh://github.com/haxetink/tink_core#670bc1d256a657cee2e78e3554d7effe31c3682d" into tink_core/2.1.1/github/670bc1d256a657cee2e78e3554d7effe31c3682d +-cp ${HAXE_LIBCACHE}/tink_core/2.1.1/github/670bc1d256a657cee2e78e3554d7effe31c3682d/src +-D tink_core=2.1.1 \ No newline at end of file diff --git a/src/tink/streams/Stream.hx b/src/tink/streams/Stream.hx index 981136c..a894752 100644 --- a/src/tink/streams/Stream.hx +++ b/src/tink/streams/Stream.hx @@ -21,6 +21,9 @@ abstract Stream(StreamObject) from StreamObject):Stream + return new BlendStream(this, other); + public function select(selector):Stream return if (Type.getClass(this) == SelectStream) @@ -51,7 +54,7 @@ abstract Stream(StreamObject) from StreamObject) + @:op(a...b) public function append(b:Stream):Stream return new Compound([this, b]); @:from static public function ofIterator(t:Iterator):Stream @@ -349,7 +352,7 @@ private class Helper { } private typedef AsyncLink = Future>; -private typedef AsyncLinkKind = LinkKind> +private typedef AsyncLinkKind = LinkKind>; private class AsyncLinkStream implements StreamObject { final link:AsyncLink; @@ -433,6 +436,52 @@ class Generator extends AsyncLinkStream { } } +class BlendStream extends Generator { + + public function new(a:Stream, b:Stream) { + var aDone = false, + bDone = false, + flipped = false; + + super( + () -> new Future>(yield -> { + var yielded = false; + var ret = []; + + function progress(s:Stream, onNext, onDone) + ret.push( + s.next().handle(v -> { + switch v { + case Link(value, next): + if (!yielded) { + yielded = true; + onNext(next); + // trace('yielding $value'); + yield(Data(value)); + } + case Fail(e): + yield(Fail(e)); + case End: + onDone(); + if (aDone && bDone) yield(End); + } + }) + ); + + if (!flipped && !aDone) progress(a, s -> a = s, () -> aDone = true); + + if (!bDone) progress(b, s -> b = s, () -> bDone = true); + + if (flipped && !aDone) progress(a, s -> a = s, () -> aDone = true); + + flipped = !flipped; + + ret; + }) + ); + } +} + private class SyncLinkStream implements StreamObject { final link:SyncLink; diff --git a/tests/BlendTest.hx b/tests/BlendTest.hx index a8f2f3c..2d4c9dd 100644 --- a/tests/BlendTest.hx +++ b/tests/BlendTest.hx @@ -14,16 +14,24 @@ class BlendTest { var a = Signal.trigger(); var b = Signal.trigger(); var blended = Stream.ofSignal(a.asSignal()).blend(new SignalStream(b.asSignal())); + a.trigger(Data(1)); b.trigger(Data(2)); a.trigger(Data(3)); var i = 0; var sum = 0; + var result = blended.forEach(function (v) { asserts.assert(++i == v); sum += v; - return Resume; + return None; + }); + + result.handle(function (x) { + asserts.assert(Done == x); + asserts.assert(15 == sum); + done = true; }); a.trigger(Data(4)); @@ -33,11 +41,6 @@ class BlendTest { b.trigger(Data(6)); a.trigger(Data(7)); - result.handle(function (x) { - asserts.assert(Depleted == x); - asserts.assert(15 == sum); - done = true; - }); asserts.assert(done); return asserts.done(); } @@ -47,7 +50,7 @@ class BlendTest { var a = Signal.trigger(); var b = Signal.trigger(); var c = Signal.trigger(); - var blended = new SignalStream(a).append(new SignalStream(c)).blend(new SignalStream(b)); + var blended = Stream.ofSignal(a).append(Stream.ofSignal(c)).blend(Stream.ofSignal(b)); a.trigger(Data(1)); b.trigger(Data(2)); a.trigger(End); @@ -58,7 +61,13 @@ class BlendTest { var result = blended.forEach(function (v) { asserts.assert(++i == v); sum += v; - return Resume; + return None; + }); + + result.handle(function (x) { + asserts.assert(Done == x); + asserts.assert(15 == sum); + done = true; }); c.trigger(Data(4)); @@ -68,11 +77,6 @@ class BlendTest { b.trigger(Data(6)); a.trigger(Data(7)); - result.handle(function (x) { - asserts.assert(Depleted == x); - asserts.assert(15 == sum); - done = true; - }); asserts.assert(done); return asserts.done(); } @@ -81,7 +85,7 @@ class BlendTest { var done = false; var a = Signal.trigger(); var b = Signal.trigger(); - var blended = new SignalStream(a.asSignal()).blend(new SignalStream(b.asSignal())); + var blended = Stream.ofSignal(a).blend(Stream.ofSignal(b)); a.trigger(Data(1)); b.trigger(Data(2)); a.trigger(Data(3)); @@ -91,7 +95,13 @@ class BlendTest { var result = blended.forEach(function (v) { asserts.assert(++i == v); sum += v; - return Resume; + return None; + }); + + result.handle(function (x) { + asserts.assert(x.match(Failed(_))); + asserts.assert(15 == sum); + done = true; }); a.trigger(Data(4)); @@ -99,11 +109,6 @@ class BlendTest { b.trigger(Fail(new Error('Failed'))); a.trigger(End); - result.handle(function (x) { - asserts.assert(x.match(Failed(_))); - asserts.assert(15 == sum); - done = true; - }); asserts.assert(done); return asserts.done(); } @@ -111,7 +116,9 @@ class BlendTest { public function testReuse() { var a = Signal.trigger(); var b = Signal.trigger(); - var blended = new SignalStream(a.asSignal()).blend(new SignalStream(b.asSignal())); + + var blended = Stream.ofSignal(a).blend(Stream.ofSignal(b)); + a.trigger(Data(1)); b.trigger(Data(2)); b.trigger(End); @@ -125,9 +132,9 @@ class BlendTest { blended.forEach(function (v) { asserts.assert(++i == v); sum += v; - return Resume; + return None; }).handle(function (x) { - asserts.assert(Depleted == x); + asserts.assert(Done == x); asserts.assert(6 == sum); count++; }); diff --git a/tests/RunTests.hx b/tests/RunTests.hx index bc8a2c9..c6b5b0f 100644 --- a/tests/RunTests.hx +++ b/tests/RunTests.hx @@ -8,10 +8,9 @@ using tink.CoreApi; class RunTests { static function main() { - // new StreamTest().laziness(null); Runner.run(TestBatch.make([ new StreamTest(), - // // new BlendTest(), + new BlendTest(), new NextTest(), new SignalStreamTest(), ])).handle(Runner.exit); From 825fe8a9a3dd54bb8c404d41a1393d0998dfc4a0 Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 27 Jun 2024 13:04:51 +0200 Subject: [PATCH 25/26] Unearth old test. --- tests/NextTest.hx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/NextTest.hx b/tests/NextTest.hx index 06bf6f4..b1f7d8d 100644 --- a/tests/NextTest.hx +++ b/tests/NextTest.hx @@ -40,18 +40,18 @@ class NextTest { return asserts.done(); } - // public function testBlend() { - // var a = Signal.trigger(); - // var b = Signal.trigger(); - // var compound = new SignalStream(a).blend(new SignalStream(b)); - // a.trigger(Data(1)); - // b.trigger(Data(2)); - // a.trigger(Data(3)); - // a.trigger(End); - // b.trigger(End); - // check(asserts, compound, [1,2,3]); - // return asserts.done(); - // } + public function testBlend() { + var a = Signal.trigger(); + var b = Signal.trigger(); + var compound = Stream.ofSignal(a).blend(Stream.ofSignal(b)); + a.trigger(Data(1)); + b.trigger(Data(2)); + a.trigger(Data(3)); + a.trigger(End); + b.trigger(End); + check(asserts, compound, [1,2,3]); + return asserts.done(); + } function check(asserts:AssertionBuffer, stream:Stream, values:Array, ?pos:haxe.PosInfos) { var current = stream; From aa8a05f7a82e5b881ae3bf8df4aa219bd56fec0f Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Thu, 27 Jun 2024 13:22:02 +0200 Subject: [PATCH 26/26] bump travix --- haxe_libraries/travix.hxml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/haxe_libraries/travix.hxml b/haxe_libraries/travix.hxml index 5cd33cc..a7e0a62 100644 --- a/haxe_libraries/travix.hxml +++ b/haxe_libraries/travix.hxml @@ -1,7 +1,7 @@ -# @install: lix --silent download "haxelib:/travix#0.14.1" into travix/0.14.1/haxelib -# @post-install: cd ${HAXE_LIBCACHE}/travix/0.14.1/haxelib && haxe -cp src --run travix.PostDownload -# @run: haxelib run-dir travix ${HAXE_LIBCACHE}/travix/0.14.1/haxelib +# @install: lix --silent download "haxelib:/travix#0.15.3" into travix/0.15.3/haxelib +# @post-install: cd ${HAXE_LIBCACHE}/travix/0.15.3/haxelib && haxe -cp src --run travix.PostDownload +# @run: haxelib run-dir travix "${HAXE_LIBCACHE}/travix/0.15.3/haxelib" -lib tink_cli --cp ${HAXE_LIBCACHE}/travix/0.14.1/haxelib/src --D travix=0.14.1 +-cp ${HAXE_LIBCACHE}/travix/0.15.3/haxelib/src +-D travix=0.15.3 --macro travix.Macro.setup() \ No newline at end of file