From 2459d5c80377b9db8ecaade5088a6cd168b065c9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Mar 2021 10:26:20 +0300 Subject: [PATCH 01/92] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ed035910..68318b60 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" (For 0.99.x documentation look at README at 0.99x branch: https://github.com/rssh/scala-gopher/tree/0.99x) -The main differences between 0.99 and 2.0.0 is described in https://github.com/rssh/scala-gopher/blob/develop/docs/changes-2.0.0.md +The main differences between 0.99 and 2.0.0 is described in https://github.com/rssh/scala-gopher/blob/master/docs/changes-2.0.0.md Scala-gopher is open source (license is Apache2); binaries are available from the maven-central repository. From 73d5da6018773926e64fc401320dbce5b6fb46da Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 2 Apr 2021 23:01:29 +0300 Subject: [PATCH 02/92] sbt-scalajs-1.5.1 --- build.sbt | 2 +- project/plugins.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 9b498515..6eb9c8f2 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC2" +ThisBuild/version := "2.0.0-RC3-SNAPSHOT" val sharedSettings = Seq( organization := "com.github.rssh", diff --git a/project/plugins.sbt b/project/plugins.sbt index 26bbb96a..6a6ff46e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,5 +3,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") From abf95efcf909d28736cc294adfc9716669047989 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 20 Apr 2021 22:47:09 +0300 Subject: [PATCH 03/92] scala 2.0.0-RC3 support --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 6eb9c8f2..12dab86d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0-RC2" +val dottyVersion = "3.0.0-RC3" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC3-SNAPSHOT" +ThisBuild/version := "2.0.0-RC3" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.5.0", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.23" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) From 8f3856f7a4e48ef6047f1b07b52e8aa47466dd22 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 20 Apr 2021 22:52:15 +0300 Subject: [PATCH 04/92] update deps in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 68318b60..1b1cc96c 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ### Dependences: -For scala 3.0.0-RC2: +For scala 3.0.0-RC3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC3" For scala2: From 86002943fea5f4959a4c3c1edfab197885663f5b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 21 Apr 2021 23:21:33 +0300 Subject: [PATCH 05/92] restored reference section --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1b1cc96c..d34a4725 100644 --- a/README.md +++ b/README.md @@ -231,17 +231,37 @@ val multiplexed = select amap { ## Done signals. - Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Such inputs are named 'CloseableInputs' and provides a way to receive close notification in selector using `done` pseudo-type. + Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When channel is closed, all readers of done channels receive notifications. ~~~ scala while(!done) select{ case x:ch.read => Console.println(s"received: ${x}") - case _:ch.done => Console.println(s"done") + case _:ch.done.read => Console.println(s"done") done = true } ~~~ +# References: +---------------------- + +## Current implementation +* source code: https://github.com/rssh/scala-gopher + +## Obsolete [0.99.x] implementation: +* source code: https://github.com/rssh/scala-gopher/tree/0.99x +* presentations: + * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 + * Wix R&D meetup. Mart 2016: http://www.slideshare.net/rssh1/csp-scala-wixmeetup2016 + * Scala Symposium. Oct. 2016. Amsterdam. http://www.slideshare.net/rssh1/scalagopher-cspstyle-programming-techniques-with-idiomatic-scala +* techreport: https://arxiv.org/abs/1611.00602 + + +## CSP-Related links: +* [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) +* [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) +* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) + From cf65fd281666f52970ae009405bccf1e5c9f4339 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 21 Apr 2021 23:23:44 +0300 Subject: [PATCH 06/92] cosmetics --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index d34a4725..dc2dec66 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,6 @@ val multiplexed = select amap { ## CSP-Related links: * [Communicating Sequential Processes book by Tony Hoare](http://www.usingcsp.com) * [brief history of CSP in Bell-labs](http://swtch.com/~rsc/thread/) -* [introduction article about go defer/recover](http://blog.golang.org/defer-panic-and-recover) From 74f7469ee17139d0c33110b22303bf00869c9ac9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 22 Apr 2021 21:04:57 +0300 Subject: [PATCH 07/92] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dc2dec66..a7d0d3f4 100644 --- a/README.md +++ b/README.md @@ -246,10 +246,10 @@ val multiplexed = select amap { # References: ---------------------- -## Current implementation +## 2.0.x implementation * source code: https://github.com/rssh/scala-gopher -## Obsolete [0.99.x] implementation: +## [0.99.x] implementation: * source code: https://github.com/rssh/scala-gopher/tree/0.99x * presentations: * Odessa Java/Scala Labs; Kiev Scala Meetup: Oct. 2014: http://www.slideshare.net/rssh1/scala-gopher2014 From fb2700080422ed393aa81d998cced2a5394dbdb1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 26 Apr 2021 08:29:59 +0300 Subject: [PATCH 08/92] update to use dotty-cps-async 0.6.2 --- README.md | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc2dec66..47319021 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3.0.0-RC3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.0-RC3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.1-RC3" For scala2: diff --git a/build.sbt b/build.sbt index 12dab86d..2d55a156 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,14 @@ val dottyVersion = "3.0.0-RC3" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.0-RC3" +ThisBuild/version := "2.0.1-RC3" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.2", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) From c5432ca43d6f7fb16c5df17b42630c628afb682e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 3 May 2021 14:34:29 +0300 Subject: [PATCH 09/92] added streaming monads --- .../src/main/scala/gopher/ReadChannel.scala | 21 +++- .../impl/ChFlatMappedTryReadChannel.scala | 51 ++++++++ .../gopher/monads/ReadChannelCpsMonad.scala | 32 +++++ .../monads/ReadTryChannelCpsMonad.scala | 66 +++++++++++ .../gopher/channels/SelectSimpleSuite.scala | 44 +++++++ .../gopher/monads/ChannelMonadSuite.scala | 112 ++++++++++++++++++ .../gopher/monads/ChannelTryMonadSuite.scala | 68 +++++++++++ 7 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala create mode 100644 shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala create mode 100644 shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala create mode 100644 shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala create mode 100644 shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala create mode 100644 shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 811c4e76..99ae588c 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -35,8 +35,15 @@ trait ReadChannel[F[_], A]: def aread():F[A] = asyncMonad.adoptCallbackStyle(f => addReader(SimpleReader(f))) + /** + * blocked read: if currently not element available - wait for one. + * Can be used only inside async block + **/ transparent inline def read(): A = await(aread())(using rAsyncMonad) + /** + * Synonim for read. + */ transparent inline def ? : A = await(aread())(using rAsyncMonad) /** @@ -57,7 +64,10 @@ trait ReadChannel[F[_], A]: case ex: ChannelClosedException => } b.result() - } + } + + transparent inline def take(n: Int): IndexedSeq[A] = + await(atake(n))(using rAsyncMonad) def aOptRead(): F[Option[A]] = asyncMonad.adoptCallbackStyle( f => @@ -191,6 +201,12 @@ end ReadChannel object ReadChannel: + + def empty[F[_],A](using Gopher[F]): ReadChannel[F,A] = + val retval = summon[Gopher[F]].makeChannel[A]() + retval.close() + retval + def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() @@ -205,12 +221,15 @@ object ReadChannel: retval + def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = futureInput(f) def fromValues[F[_],A](values: A*)(using Gopher[F]): ReadChannel[F,A] = fromIterable(values) + + end ReadChannel diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala new file mode 100644 index 00000000..cbec571b --- /dev/null +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala @@ -0,0 +1,51 @@ +package gopher.impl + +import cps._ +import gopher._ +import scala.util._ +import scala.util.control._ + +class ChFlatMappedTryReadChannel[F[_], A, B](prev: ReadChannel[F,Try[A]], f: Try[A]=>ReadChannel[F,Try[B]]) extends ReadChannel[F,Try[B]] { + + def addReader(reader: Reader[Try[B]]): Unit = + bChannel.addReader(reader) + + + def addDoneReader(reader: Reader[Unit]): Unit = { + bChannel.addDoneReader(reader) + } + + def gopherApi:Gopher[F] = prev.gopherApi + + val bChannel = gopherApi.makeChannel[Try[B]]() + + def run(): F[Unit] = { + given CpsSchedulingMonad[F] = gopherApi.asyncMonad + async[F]{ + while{ + prev.optRead() match + case None => false + case Some(v) => + val internal: ReadChannel[F,Try[B]] = + try + f(v) + catch + case NonFatal(ex) => + ReadChannel.fromValues[F,Try[B]](Failure(ex))(using gopherApi) + while{ + internal.optRead() match + case None => false + case Some(v) => + bChannel.write(v) + true + } do () + true + } do () + bChannel.close() + } + } + + gopherApi.asyncMonad.spawn(run()) + + +} diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala new file mode 100644 index 00000000..15a89eae --- /dev/null +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -0,0 +1,32 @@ +package gopher.monads + +import gopher._ +import cps._ + +import gopher.impl._ + + + + +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with + + def pure[T](t:T): ReadChannel[F,T] = + ReadChannel.fromValues[F,T](t) + + def map[A,B](fa: ReadChannel[F,A])(f: A=>B): ReadChannel[F,B] = + fa.map(f) + + def flatMap[A,B](fa: ReadChannel[F,A])(f: A=>ReadChannel[F,B]): ReadChannel[F,B] = + new ChFlatMappedReadChannel[F,A,B](fa,f) + + +given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A]=>>ReadChannel[F,A]] with + + def apply[T](m: CpsMonad[F], mg: CpsMonad[[A] =>> ReadChannel[F,A]], ft: F[T]): ReadChannel[F,T] = + futureInput(ft) + + + + + + diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala new file mode 100644 index 00000000..fd2e56ee --- /dev/null +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -0,0 +1,66 @@ +package gopher.monads + +import scala.util._ +import gopher._ +import cps._ + +import gopher.impl._ + + +given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> ReadChannel[F,Try[A]] ] with + + type FW[T] = [A] =>> ReadChannel[F,Try[A]] + + def pure[T](t:T): ReadChannel[F,Try[T]] = + ReadChannel.fromValues[F,Try[T]](Success(t)) + + def map[A,B](fa: ReadChannel[F,Try[A]])(f: A=>B): ReadChannel[F,Try[B]] = + fa.map{ + case Success(a) => + try{ + Success(f(a)) + } catch { + case ex: Throwable => Failure(ex) + } + case Failure(ex) => Failure(ex) + } + + def flatMap[A,B](fa: ReadChannel[F,Try[A]])(f: A=>ReadChannel[F,Try[B]]): ReadChannel[F,Try[B]] = + new ChFlatMappedTryReadChannel(fa,{ + case Success(a) => f(a) + case Failure(ex) => ReadChannel.fromValues[F,Try[B]](Failure(ex )) + }) + + def flatMapTry[A,B](fa: ReadChannel[F,Try[A]])(f: Try[A] => ReadChannel[F,Try[B]]): ReadChannel[F,Try[B]] = + new ChFlatMappedTryReadChannel(fa,f) + + def error[A](e: Throwable): ReadChannel[F,Try[A]] = + val r = makeChannel[Try[A]]() + given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + fm.spawn{ async[F] { + r.write(Failure(e)) + r.close() + } } + r + + + def adoptCallbackStyle[A](source: (Try[A]=>Unit) => Unit): ReadChannel[F,Try[A]] = { + val r = makeOnceChannel[Try[A]]() + given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val fv = fm.adoptCallbackStyle(source) + fm.spawn{ + fm.flatMapTry( fv ){ tryV => + r.awrite(tryV) + } + } + r + } + + +given readChannelToTryReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with + + def apply[T](m: CpsMonad[[A]=>>ReadChannel[F,A]], mg: CpsMonad[[A] =>> ReadChannel[F,Try[A]]], ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = + ft.map(x => Success(x)) + + + diff --git a/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala b/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala new file mode 100644 index 00000000..17e08703 --- /dev/null +++ b/shared/src/test/scala/gopher/channels/SelectSimpleSuite.scala @@ -0,0 +1,44 @@ +package gopher.channels + + +import munit._ +import scala.language.postfixOps +import scala.concurrent._ +import scala.concurrent.duration._ + +import cps._ +import gopher._ +import cps.monads.FutureAsyncMonad + +class SelectSimpleSuite extends FunSuite +{ + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("simple select in a loop") { + val ch = makeChannel[Int]() + var sum = 0 + val loop = async { + var done = false + while(!done) { + select { + case x: ch.read => + sum = sum+x + if (x > 100) { + done = true + } + } + } + } + ch.awriteAll(1 to 200) + + async { + await(loop) + assert( sum == (1 to 101).sum) + } + } + + +} diff --git a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala new file mode 100644 index 00000000..8760ce74 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala @@ -0,0 +1,112 @@ +package gopher.monadexample + +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.SortedSet + +import cps.monads.FutureAsyncMonad +import gopher.monads.given + +class ChannelMonadSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("using channel as monad and read inside") { + + val chX = ReadChannel.fromValues(1,2,3,4,5) + val chY = ReadChannel.fromValues(1,2,3,4,5) + + val squares = async[[X] =>> ReadChannel[Future,X]] { + val x = await(chX) + //println(s"reading from X $x") + val y = chY.read() + //println(s"reading from Y $y") + x*y + } + + async[Future] { + val a1 = squares.read() + //println(s"a1==${a1}") + assert(a1 == 1) + val a2 = squares.read() + //println(s"a2==${a2}") + assert(a2 == 4) + val a3 = squares.read() + assert(a3 == 9) + val a4 = squares.read() + assert(a4 == 16) + val a5 = squares.read() + assert(a5 == 25) + } + + } + + test("using channel with flatMap") { + + val chX = ReadChannel.fromValues(1,2,3,4,5) + + val r = async[[X] =>> ReadChannel[Future,X]] { + val x = await(chX) + val fy = if (x %2 == 0) ReadChannel.empty[Future,Int] else ReadChannel.fromIterable(1 to x) + await(fy)*x + } + + async[Future] { + val seq = r.take(20) + //println(seq) + assert(seq(0)==1) + assert(seq(1)==3) + assert(seq(2)==6) + assert(seq(3)==9) + assert(seq(4)==5) + assert(seq(5)==10) + } + + + } + + + test("sieve inside channel monad") { + + val n = 100 + val r = async[[X] =>> ReadChannel[Future,X]] { + var initial = ReadChannel.fromIterable[Future,Int](2 to n) + val drop = ReadChannel.empty[Future,Int] + var prevs = IndexedSeq.empty[Int] + var done = false + val x = await(initial) + if !prevs.isEmpty then + var pi = 0 + while{ + var p = prevs(pi) + if (x % p == 0) then + val r = await(drop) + pi = pi+1 + p*p < x + } do () + prevs = prevs :+ x + x + } + + async[Future] { + val primes = r.take(20) + //println(s"primes: $primes") + assert(primes(0)==2) + assert(primes(1)==3) + assert(primes(2)==5) + assert(primes(3)==7) + assert(primes(6)==17) + assert(primes(9)==29) + } + + + } + + +} diff --git a/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala new file mode 100644 index 00000000..421049b1 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/ChannelTryMonadSuite.scala @@ -0,0 +1,68 @@ +package gopher.monadexample + +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.util.* + +import cps.monads.FutureAsyncMonad +import gopher.monads.given + +class ChannelTryMonadSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + + test("failure inside async with ReadTryChannelCpsMonad") { + + val r = async[[X]=>>ReadChannel[Future,Try[X]]] { + val ch1 = ReadChannel.fromIterable[Future,Int](1 to 10) + val x = await(ch1) + if (x % 3 == 0) then + throw new RuntimeException("AAA") + x + } + + async[Future] { + val a1 = r.read() + val a2 = r.read() + val a3 = r.read() + assert(a1 == Success(1)) + assert(a2 == Success(2)) + assert(a3.isFailure) + } + + } + + test("using try/catch along with ReadTryChannelCpsMonad") { + + val r = async[[X]=>>ReadChannel[Future,Try[X]]] { + val ch1 = ReadChannel.fromIterable[Future,Int](1 to 10) + val x = await(ch1) + try { + if (x % 3 == 0) { + throw (new RuntimeException("AAA")) + } + x + } catch { + case ex: RuntimeException => + 100 + } + } + + async[Future] { + val a1 = r.read() + val a2 = r.read() + val a3 = r.read() + assert(a1 == Success(1)) + assert(a2 == Success(2)) + assert(a3 == Success(100)) + } + + } + +} \ No newline at end of file From 632445b12f827329c49fddc37bd2ff65829fdaa1 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 14:07:39 +0300 Subject: [PATCH 10/92] Fiexed order of read-done and read in DuplicatedChannel, to make done always be called first. --- jvm/src/main/scala/gopher/JVMGopher.scala | 2 + .../gopher/impl/GuardedSPSCBaseChannel.scala | 10 ++- .../impl/GuardedSPSCBufferedChannel.scala | 1 + .../DuppedChannelsMultipleSuite.scala | 87 +++++++++++++++++++ .../src/main/scala/gopher/ReadChannel.scala | 3 + .../src/main/scala/gopher/SelectGroup.scala | 2 + shared/src/main/scala/gopher/SelectLoop.scala | 7 +- .../main/scala/gopher/impl/DuppedInput.scala | 22 ++--- 8 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 77763367..e6b0c7a0 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -72,4 +72,6 @@ object JVMGopher extends GopherAPI: } + final val MAX_SPINS = 400 + val Gopher = JVMGopher diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 9f05114d..9265d6b8 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -12,6 +12,8 @@ import scala.util.Try import scala.util.Success import scala.util.Failure +import java.util.logging.{Level => LogLevel} + /** * Guarded channel work in the next way: @@ -33,7 +35,6 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA protected val stepGuard = new AtomicInteger(STEP_FREE) protected val stepRunnable: Runnable = (()=>entryStep()) - def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then @@ -66,6 +67,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA protected def entryStep(): Unit = var done = false + var nSpins = 0 while(!done) { if (stepGuard.compareAndSet(STEP_FREE,STEP_BUSY)) { done = true @@ -77,6 +79,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA done = true } else { // other set updates, we should spinLock + nSpins = nSpins + 1 Thread.onSpinWait() } } @@ -99,7 +102,7 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA while(!readers.isEmpty) { val r = readers.poll() if (!(r eq null) && !r.isExpired) then - r.capture() match + r.capture() match case Some(f) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) @@ -173,9 +176,12 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA if (!v.isExpired) if (queue.isEmpty) Thread.onSpinWait() + // if (nSpins > JVMGopher.MAX_SPINS) + // Thread.`yield`() queue.addLast(v) + object GuardedSPSCBaseChannel: final val STEP_FREE = 0 diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 69b39879..641de118 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -9,6 +9,7 @@ import scala.util.Try import scala.util.Success import scala.util.Failure +import java.util.logging.{Level => LogLevel} class GuardedSPSCBufferedChannel[F[_]:CpsAsyncMonad,A](gopherApi: JVMGopher[F], bufSize: Int, controlExecutor: ExecutorService, diff --git a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala new file mode 100644 index 00000000..b598437f --- /dev/null +++ b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala @@ -0,0 +1,87 @@ +package gopher.channels + +import cps._ +import cps.monads.FutureAsyncMonad +import gopher._ +import munit._ + +import scala.concurrent._ +import scala.concurrent.duration._ +import scala.language.postfixOps +import scala.util._ + +class DuppedChannelsMultipleSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given gopherApi: Gopher[Future] = SharedGopherAPI.apply[Future]() + + val inMemoryLog = new java.util.concurrent.ConcurrentLinkedQueue[(Int, Long, String, Throwable)]() + + + test("on closing of main stream dupped outputs also closed N times.") { + val N = 1000 + var logIndex = 0 + gopherApi.setLogFun( (level,msg, ex) => inMemoryLog.add((logIndex, Thread.currentThread().getId(), msg,ex)) ) + for(i <- 1 to N) { + logIndex = i + val ch = makeChannel[Int](1) + val (in1, in2) = ch.dup() + val f1 = async{ + ch.write(1) + ch.close() + } + val f = for{ fx <- f1 + x <- in1.aread() + r <- in1.aread().transformWith { + case Success(u) => + Future failed new IllegalStateException("Mist be closed") + case Failure(u) => + Future successful (assert(x == 1)) + } + } yield { + r + } + try { + val r = Await.result(f, 30 seconds); + }catch{ + case ex: TimeoutException => + showTraces(20) + println("---") + showInMemoryLog() + throw ex + } + } + + } + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + def showInMemoryLog(): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + +} + + diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 99ae588c..4852a3b2 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -7,6 +7,9 @@ import scala.util.Success import scala.util.Failure import scala.concurrent.duration.Duration +import java.util.logging.{Level => LogLevel} + + trait ReadChannel[F[_], A]: thisReadChannel => diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 3ed8c797..e88e28c3 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -10,6 +10,8 @@ import scala.util._ import scala.concurrent.duration._ import scala.language.postfixOps +import java.util.logging.{Level => LogLevel} + /** * Select group is a virtual 'lock' object, where only diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index c60d251f..d97b5207 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -5,6 +5,9 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ +import java.util.logging.{Level => LogLevel} + + class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): @@ -25,7 +28,3 @@ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Uni } - - - - diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index c69aaac0..1d4b6e5b 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -8,6 +8,7 @@ import scala.util._ import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger +import java.util.logging.{Level => LogLevel} @@ -21,15 +22,16 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop given CpsSchedulingMonad[F] = api.asyncMonad - val runner = SelectLoop[F](api).onReadAsync(origin){a => async{ - val f1 = sink1.write(a) - val f2 = sink2.write(a) - true - }}.onRead(origin.done){ _ => - sink1.close() - sink2.close() - false - }.runAsync() - api.asyncMonad.spawn(runner) + val runner = SelectLoop[F](api). + onRead(origin.done){ _ => + sink1.close() + sink2.close() + false + }.onReadAsync(origin){a => async{ + val f1 = sink1.write(a) + val f2 = sink2.write(a) + true + }}.runAsync() + api.asyncMonad.spawn(runner) } From e8e2e7a7c10369e09d32db05fbc3d14dfb3d844a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 15:22:38 +0300 Subject: [PATCH 11/92] order caseDefs in select, to make doneCases be first during building reading callback expressions. This is needed for case, when done event was fired and we attach build expression to it. --- shared/src/main/scala/gopher/Select.scala | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 905deb1f..c84a1a4e 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -106,7 +106,7 @@ object Select: type Monad[X] = F[X] def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] - case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = '{ $base.onRead($ch)($f) } @@ -183,7 +183,16 @@ object Select: //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { // report.error("default is not supported") //} - builder(cases.map(parseCaseDef[F,A,B](_))) + val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) + // done should be + val (isDone,notDone) = unorderedCases.partition{ x => + x match + case DoneExression(_,_) => true + case ReadExpression(_,_,isDone) => isDone + case _ => false + } + val doneFirstCases = isDone ++ notDone + builder(doneFirstCases) def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = @@ -195,7 +204,11 @@ object Select: val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) tp.asType match - case '[a] => ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S]) + case '[a] => + val isDone = channel match + case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true + case _ => false + ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) case _ => reportError("can't determinate read type", caseDef.pattern.asExpr) else From bb47755334fa9ee2bfe91693090abc5691bea20e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 8 May 2021 16:09:13 +0300 Subject: [PATCH 12/92] log failures which can be in spawned operations --- shared/src/main/scala/gopher/Gopher.scala | 12 ++++++++++-- shared/src/main/scala/gopher/ReadChannel.scala | 4 ++-- shared/src/main/scala/gopher/Select.scala | 2 +- shared/src/main/scala/gopher/SelectGroup.scala | 6 +++--- .../scala/gopher/impl/ChFlatMappedReadChannel.scala | 2 +- .../gopher/impl/ChFlatMappedTryReadChannel.scala | 2 +- shared/src/main/scala/gopher/impl/DuppedInput.scala | 2 +- .../main/scala/gopher/impl/FilteredReadChannel.scala | 2 +- .../main/scala/gopher/impl/MappedReadChannel.scala | 2 +- .../scala/gopher/monads/ReadTryChannelCpsMonad.scala | 4 ++-- 10 files changed, 23 insertions(+), 15 deletions(-) diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 8276c413..b30034e1 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -2,7 +2,7 @@ package gopher import cps._ import scala.concurrent.duration.Duration -import scala.util.Try +import scala.util._ import java.util.logging.{Level => LogLevel} @@ -33,6 +33,14 @@ trait Gopher[F[_]:CpsSchedulingMonad]: protected[gopher] def logImpossible(ex: Throwable): Unit = log(LogLevel.WARNING, "impossible", ex) + protected[gopher] def spawnAndLogFail[T](op: =>F[T]): F[Unit] = + asyncMonad.mapTry(asyncMonad.spawn(op)){ + case Success(_) => () + case Failure(ex) => + log(LogLevel.WARNING, "exception in spawned process", ex) + () + } + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = @@ -46,7 +54,7 @@ def select(using g:Gopher[?]):Select[g.Monad] = def futureInput[F[_],A](f: F[A])(using g: Gopher[F]): ReadChannel[F,A] = val ch = g.makeOnceChannel[Try[A]]() - g.asyncMonad.spawn{ + g.spawnAndLogFail{ g.asyncMonad.flatMapTry(f)(r => ch.awrite(r)) } ch.map(_.get) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 4852a3b2..d3dc8a86 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -149,7 +149,7 @@ trait ReadChannel[F[_], A]: def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad val retval = gopherApi.makeChannel[(A,B)]() - asyncMonad.spawn(async[F]{ + gopherApi.spawnAndLogFail(async[F]{ var done = false while(!done) { this.optRead() match @@ -213,7 +213,7 @@ object ReadChannel: def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() - asyncMonad.spawn(async{ + summon[Gopher[F]].spawnAndLogFail(async{ val it = c.iterator while(it.hasNext) { val a = it.next() diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index c84a1a4e..6bf9f88f 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -60,7 +60,7 @@ class Select[F[_]](api: Gopher[F]): def mapAsync[A](step: SelectGroup[F,A] => F[A]): ReadChannel[F,A] = val r = makeChannel[A]()(using api) given CpsSchedulingMonad[F] = api.asyncMonad - api.asyncMonad.spawn{ + api.spawnAndLogFail{ async{ var done = false while(!done) diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index e88e28c3..3d62d7e0 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -169,7 +169,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: if waitState.compareAndSet(0,1) then Some(v => { timeoutScheduled.foreach(_.cancel()) - m.spawn( + api.spawnAndLogFail( m.mapTry(action(v))(x => call(x)) ) }) @@ -189,7 +189,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: if waitState.compareAndSet(0,1) then Some((element, (v:Try[Unit]) => { timeoutScheduled.foreach(_.cancel()) - m.spawn( + api.spawnAndLogFail( m.mapTry(action(v))(x=>call(x)) )} )) @@ -204,7 +204,7 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: def capture(): Option[Try[FiniteDuration] => Unit] = if (waitState.compareAndSet(0,1)) then Some((v:Try[FiniteDuration]) => - m.spawn(m.mapTry(action(v))(x => call(x))) + api.spawnAndLogFail(m.mapTry(action(v))(x => call(x))) ) else None diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala index d14355b3..ba7fe7a4 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedReadChannel.scala @@ -40,7 +40,7 @@ class ChFlatMappedReadChannel[F[_], A, B](prev: ReadChannel[F,A], f: A=>ReadChan bChannel.close() } - gopherApi.asyncMonad.spawn(run()) + gopherApi.spawnAndLogFail(run()) diff --git a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala index cbec571b..d39f56ed 100644 --- a/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/ChFlatMappedTryReadChannel.scala @@ -45,7 +45,7 @@ class ChFlatMappedTryReadChannel[F[_], A, B](prev: ReadChannel[F,Try[A]], f: Try } } - gopherApi.asyncMonad.spawn(run()) + gopherApi.spawnAndLogFail(run()) } diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index 1d4b6e5b..2485c76b 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -32,6 +32,6 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop val f2 = sink2.write(a) true }}.runAsync() - api.asyncMonad.spawn(runner) + api.spawnAndLogFail(runner) } diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 868a3d2e..41eeca95 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -62,7 +62,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole def wrappedFun(fun: (Try[A] => Unit) ): (Try[A] => Unit) = { case Success(a) => - gopherApi.asyncMonad.spawn( + gopherApi.spawnAndLogFail( gopherApi.asyncMonad.mapTry(p(a)){ case Success(v) => if (v) { diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index 27ee86f9..de8a0fa9 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -51,7 +51,7 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => try{ - asyncMonad.spawn( + gopherApi.spawnAndLogFail( asyncMonad.mapTry(f(a))(fun) ) }catch{ diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index fd2e56ee..39350134 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -37,7 +37,7 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read def error[A](e: Throwable): ReadChannel[F,Try[A]] = val r = makeChannel[Try[A]]() given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad - fm.spawn{ async[F] { + summon[Gopher[F]].spawnAndLogFail{ async[F] { r.write(Failure(e)) r.close() } } @@ -48,7 +48,7 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read val r = makeOnceChannel[Try[A]]() given fm: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val fv = fm.adoptCallbackStyle(source) - fm.spawn{ + summon[Gopher[F]].spawnAndLogFail{ fm.flatMapTry( fv ){ tryV => r.awrite(tryV) } From 8eacc84068bcc3d5e4f893fb2ee7f8a89c2e24da Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 9 May 2021 16:40:59 +0300 Subject: [PATCH 13/92] added example of NQueens backtracing algorithm using ReadChannel[F] as cps monad --- build.sbt | 2 +- project/build.properties | 2 +- .../src/test/scala/gopher/monads/Queens.scala | 69 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 shared/src/test/scala/gopher/monads/Queens.scala diff --git a/build.sbt b/build.sbt index 2d55a156..189a6db6 100644 --- a/build.sbt +++ b/build.sbt @@ -9,7 +9,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.6.2", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/build.properties b/project/build.properties index dbae93bc..f0be67b9 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.4.9 +sbt.version=1.5.1 diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala new file mode 100644 index 00000000..0a8f4a17 --- /dev/null +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -0,0 +1,69 @@ +package gopher.monadexample +import cps.* +import gopher.* +import munit.* + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.collection.SortedSet + +import cps.monads.FutureAsyncMonad +//import gopher.monads.given +import gopher.monads.ReadChannelCpsMonad + + +class QueensSuite extends FunSuite { + + import scala.concurrent.ExecutionContext.Implicits.global + given Gopher[Future] = SharedGopherAPI.apply[Future]() + + case class State( + busyRows:Set[Int], + busyColumns:Set[Int], + busyDiagonals:Set[Int], + queens: Set[(Int,Int)] + ); + + val N = 8 + + def putQueen(state:State): ReadChannel[Future,State] = + val ch = makeChannel[State]() + async[Future] { + for{ + i <- 0 until N if !state.busyRows.contains(i) + j <- 0 until N if !state.busyColumns.contains(j) && + !(state.busyDiagonals.contains(i-j)) + } { + val newPos = (i,j) + val nState = state.copy( busyRows = state.busyRows + i, + busyColumns = state.busyColumns + j, + busyDiagonals = state.busyDiagonals + (i-j), + queens = state.queens + newPos ) + ch.write(nState) + } + ch.close() + } + ch + + def solutions(state: State): ReadChannel[Future,State] = + async[[X] =>> ReadChannel[Future,X]] { + if(state.queens.size < 8) then + val nextState = await(putQueen(state)) + await(solutions(nextState)) + else + state + } + + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty) + + test("first solution for 8 queens problem") { + async[Future] { + val r = solutions(emptyState).take(1) + assert(!r.isEmpty) + println(r.head.queens) + } + } + + + +} \ No newline at end of file From e5452fbb5fa19f881edfb5b83a469c80f41d5366 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 9 May 2021 17:44:44 +0300 Subject: [PATCH 14/92] cosmetics --- shared/src/test/scala/gopher/monads/Queens.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 0a8f4a17..9a475c96 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -8,8 +8,7 @@ import scala.concurrent.duration.* import scala.collection.SortedSet import cps.monads.FutureAsyncMonad -//import gopher.monads.given -import gopher.monads.ReadChannelCpsMonad +import gopher.monads.given class QueensSuite extends FunSuite { From ff2d1104571104c03311bb75df7f0aec940fc6e9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 10 May 2021 06:29:10 +0300 Subject: [PATCH 15/92] fixed error with forgotten check for RL diagonal in 8 queens --- shared/src/test/scala/gopher/monads/Queens.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 9a475c96..8304c359 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -19,7 +19,8 @@ class QueensSuite extends FunSuite { case class State( busyRows:Set[Int], busyColumns:Set[Int], - busyDiagonals:Set[Int], + busyLRDiagonals:Set[Int], + busyRLDiagonals:Set[Int], queens: Set[(Int,Int)] ); @@ -31,12 +32,14 @@ class QueensSuite extends FunSuite { for{ i <- 0 until N if !state.busyRows.contains(i) j <- 0 until N if !state.busyColumns.contains(j) && - !(state.busyDiagonals.contains(i-j)) + !state.busyLRDiagonals.contains(i-j) && + !state.busyRLDiagonals.contains(i+j) } { val newPos = (i,j) val nState = state.copy( busyRows = state.busyRows + i, busyColumns = state.busyColumns + j, - busyDiagonals = state.busyDiagonals + (i-j), + busyLRDiagonals = state.busyLRDiagonals + (i-j), + busyRLDiagonals = state.busyRLDiagonals + (i+j), queens = state.queens + newPos ) ch.write(nState) } @@ -53,7 +56,7 @@ class QueensSuite extends FunSuite { state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty) + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Set.empty) test("first solution for 8 queens problem") { async[Future] { @@ -64,5 +67,4 @@ class QueensSuite extends FunSuite { } - } \ No newline at end of file From 76403f2ed1ff5d753e8ae37ae9294dadd60e624b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 10 May 2021 09:02:27 +0300 Subject: [PATCH 16/92] optimization of 8 queens problen --- .../src/test/scala/gopher/monads/Queens.scala | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 8304c359..d7d53519 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -21,7 +21,7 @@ class QueensSuite extends FunSuite { busyColumns:Set[Int], busyLRDiagonals:Set[Int], busyRLDiagonals:Set[Int], - queens: Set[(Int,Int)] + queens: Vector[(Int,Int)] ); val N = 8 @@ -29,40 +29,43 @@ class QueensSuite extends FunSuite { def putQueen(state:State): ReadChannel[Future,State] = val ch = makeChannel[State]() async[Future] { - for{ - i <- 0 until N if !state.busyRows.contains(i) - j <- 0 until N if !state.busyColumns.contains(j) && + val i = state.queens.length + if i < N then + for{ + j <- 0 until N if !state.busyColumns.contains(j) && !state.busyLRDiagonals.contains(i-j) && !state.busyRLDiagonals.contains(i+j) - } { - val newPos = (i,j) - val nState = state.copy( busyRows = state.busyRows + i, + } { + val newPos = (i,j) + val nState = state.copy( busyRows = state.busyRows + i, busyColumns = state.busyColumns + j, busyLRDiagonals = state.busyLRDiagonals + (i-j), busyRLDiagonals = state.busyRLDiagonals + (i+j), - queens = state.queens + newPos ) - ch.write(nState) - } + queens = state.queens :+ newPos ) + ch.write(nState) + } ch.close() } ch def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.queens.size < 8) then + if(state.queens.size < N) then + //println("state:"+state.queens) val nextState = await(putQueen(state)) + //println("next-state:"+state.queens) await(solutions(nextState)) else state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Set.empty) + val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Vector.empty) - test("first solution for 8 queens problem") { + test("two first solution for 8 queens problem") { async[Future] { - val r = solutions(emptyState).take(1) + val r = solutions(emptyState).take(2) assert(!r.isEmpty) - println(r.head.queens) + println(r.map(_.queens)) } } From b3c2dcd30e1e5a8853a4aaa9b206778f2ea7a2b5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 12 May 2021 22:37:37 +0300 Subject: [PATCH 17/92] CpsMonadConversion[F[_],G[_]] => Conversion[F[T],G[T]] --- .../main/scala/gopher/monads/ReadChannelCpsMonad.scala | 8 ++------ .../main/scala/gopher/monads/ReadTryChannelCpsMonad.scala | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 15a89eae..0ad8dd3e 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,8 +6,6 @@ import cps._ import gopher.impl._ - - given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = @@ -20,11 +18,9 @@ given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[ new ChFlatMappedReadChannel[F,A,B](fa,f) -given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A]=>>ReadChannel[F,A]] with - - def apply[T](m: CpsMonad[F], mg: CpsMonad[[A] =>> ReadChannel[F,A]], ft: F[T]): ReadChannel[F,T] = - futureInput(ft) +given futureToReadChannel[F[_],T](using Gopher[F]): Conversion[F[T], ReadChannel[F,T]] with + def apply(ft: F[T]): ReadChannel[F,T] = futureInput(ft) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 39350134..8f5cd3cf 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -57,10 +57,10 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read } -given readChannelToTryReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with - - def apply[T](m: CpsMonad[[A]=>>ReadChannel[F,A]], mg: CpsMonad[[A] =>> ReadChannel[F,Try[A]]], ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = - ft.map(x => Success(x)) +given readChannelToTryReadChannel[F[_],T](using Gopher[F]): Conversion[ ReadChannel[F,T], ReadChannel[F,Try[T]]] with + + def apply(ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] =ft.map(x => Success(x)) + From 245a05d89cf1a0a9280305eb8c68c850f5749c44 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 13 May 2021 21:45:57 +0300 Subject: [PATCH 18/92] release for scala-3.0.0 --- build.sbt | 10 +++++----- project/plugins.sbt | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 189a6db6..3eff5a22 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0-RC3" +val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.1-RC3" +ThisBuild/version := "2.0.2" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0-SNAPSHOT", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.25" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) @@ -32,7 +32,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), ).jsSettings( - libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").withDottyCompat(scalaVersion.value), + libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, diff --git a/project/plugins.sbt b/project/plugins.sbt index 6a6ff46e..02f86b3f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,3 @@ -addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3") addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") From 7cbdfa3dee3746604bcb17456b1139bedf8d0596 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 13 May 2021 21:52:45 +0300 Subject: [PATCH 19/92] updated version in README + phrase abotu handling channel and done.channel in one select. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9d771c73..53cc80d7 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ ### Dependences: -For scala 3.0.0-RC3: +For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.1-RC3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.2" For scala2: @@ -231,7 +231,7 @@ val multiplexed = select amap { ## Done signals. - Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When channel is closed, all readers of done channels receive notifications. + Sometimes it is useful to receive a message when some `ReadChannel` becomes closed. Exists a way to receive close notification in selector using `done` pseudo-channel, which is available for each 'normal' channel. When the channel is closed, all readers of done channels receive notifications. ~~~ scala while(!done) @@ -242,6 +242,8 @@ val multiplexed = select amap { } ~~~ + Note, that if we query some channel and it's done channel in the same select, and done channel is not aliased in some vairable, then done handler will be called first after channel close. + # References: ---------------------- From aa5e7e988bcb71868cb3c2d2a3183f9d2eeaf179 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 19 May 2021 18:27:30 +0300 Subject: [PATCH 20/92] adopted to dotty-cps-async 0.8.0 snapshot --- build.sbt | 4 ++-- .../src/main/scala/gopher/monads/ReadChannelCpsMonad.scala | 4 ++-- .../main/scala/gopher/monads/ReadTryChannelCpsMonad.scala | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 3eff5a22..c026d2dd 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,14 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.2" +ThisBuild/version := "2.0.3-SNAPSHOT" val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.7.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.0-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 0ad8dd3e..98c91f6a 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -18,9 +18,9 @@ given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[ new ChFlatMappedReadChannel[F,A,B](fa,f) -given futureToReadChannel[F[_],T](using Gopher[F]): Conversion[F[T], ReadChannel[F,T]] with +given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A] =>> ReadChannel[F,A]] with - def apply(ft: F[T]): ReadChannel[F,T] = futureInput(ft) + def apply[T](ft: F[T]): ReadChannel[F,T] = futureInput(ft) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 8f5cd3cf..2516f735 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -58,9 +58,10 @@ given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> Read -given readChannelToTryReadChannel[F[_],T](using Gopher[F]): Conversion[ ReadChannel[F,T], ReadChannel[F,Try[T]]] with +given readChannelToTryReadChannel[F[_]](using Gopher[F]): + CpsMonadConversion[ [A]=>>ReadChannel[F,A], [A]=>>ReadChannel[F,Try[A]]] with - def apply(ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] =ft.map(x => Success(x)) + def apply[T](ft: ReadChannel[F,T]): ReadChannel[F,Try[T]] = ft.map(x => Success(x)) From 61041faedf2485e29e04184cd9772e969f721f50 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 23 May 2021 15:08:42 +0300 Subject: [PATCH 21/92] latest published version is 2.0.3 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53cc80d7..56822878 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.3" For scala2: From 034ce58fd42ddc60db73c764de0dd4f106f405db Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 23 May 2021 15:09:28 +0300 Subject: [PATCH 22/92] added versionign policy --- build.sbt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index c026d2dd..d3ca66d4 100644 --- a/build.sbt +++ b/build.sbt @@ -2,14 +2,15 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.3-SNAPSHOT" +ThisBuild/version := "2.0.3" +ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.0-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) From bb27fe7ff23a1d7347d56b01a9ee3b2dcb322e90 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 30 May 2021 18:45:38 +0300 Subject: [PATCH 23/92] 2.0.4-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d3ca66d4..fec5eb36 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.3" +ThisBuild/version := "2.0.4-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 3fba32a09c359f287336b6b9b8c131202cd843fe Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 30 May 2021 18:50:09 +0300 Subject: [PATCH 24/92] added ReadChannel.unfold fiexe situation, where exception in map is not propagated to reader --- .../src/main/scala/gopher/ReadChannel.scala | 31 ++++++++- .../gopher/impl/FilteredReadChannel.scala | 2 +- .../scala/gopher/impl/MappedReadChannel.scala | 24 ++++--- .../channels/ReadChannelFactoryTest.scala | 68 +++++++++++++++++++ 4 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d3dc8a86..232bf236 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -5,6 +5,7 @@ import gopher.impl._ import scala.util.Try import scala.util.Success import scala.util.Failure +import scala.util.control.NonFatal import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} @@ -231,7 +232,35 @@ object ReadChannel: def fromValues[F[_],A](values: A*)(using Gopher[F]): ReadChannel[F,A] = fromIterable(values) - + def unfold[S,F[_],A](s:S)(f:S => Option[(A,S)])(using Gopher[F]): ReadChannel[F,A] = + unfoldAsync[S,F,A](s)( state => summon[Gopher[F]].asyncMonad.tryPure(f(state)) ) + + def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A]= + given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val retval = makeChannel[Try[A]]() + summon[Gopher[F]].spawnAndLogFail(async{ + var done = false + var state = s + try + while(!done) { + await(f(state)) match + case Some((a,next)) => + retval.write(Success(a)) + state = next + case None => + done = true + } + catch + case NonFatal(ex) => + retval.write(Failure(ex)) + finally + retval.close(); + }) + retval.map{ + case Success(x) => x + case Failure(ex) => + throw ex + } end ReadChannel diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 41eeca95..4c0a221f 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -68,7 +68,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole if (v) { if (markedUsed.get()) { nested.markUsed() - } + } fun(Success(a)) } else { nested.markFree() diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index de8a0fa9..8641d663 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -11,8 +11,12 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => - val b = f(a) - fun(Success(b)) + try + val b = f(a) + fun(Success(b)) + catch + case NonFatal(ex) => + fun(Failure(ex)) case Failure(ex) => fun(Failure(ex)) } @@ -50,14 +54,14 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) def wrappedFun(fun: (Try[B] => Unit) ): (Try[A] => Unit) = { case Success(a) => - try{ - gopherApi.spawnAndLogFail( - asyncMonad.mapTry(f(a))(fun) - ) - }catch{ - case NonFatal(ex) => - fun(Failure(ex)) - } + gopherApi.spawnAndLogFail( + try + asyncMonad.mapTry(f(a))(fun) + catch + case NonFatal(ex) => + fun(Failure(ex)) + asyncMonad.pure(()) + ) case Failure(ex) => fun(Failure(ex)) } diff --git a/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala b/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala new file mode 100644 index 00000000..527cda0b --- /dev/null +++ b/shared/src/test/scala/gopher/channels/ReadChannelFactoryTest.scala @@ -0,0 +1,68 @@ +package gopher.channels + +import gopher._ +import cps._ +import munit._ + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.{Channel=>_,_} +import scala.concurrent.duration._ + +import cps.monads.FutureAsyncMonad + +class ReadChannelFactoryTest extends FunSuite { + + given Gopher[Future] = Gopher[Future]() + + + test("unfoldAsync produce stream simple") { + val ch = ReadChannel.unfoldAsync(0){ + (x: Int) => + if (x > 10) then + Future successful None + else + Future successful Some(x,x+1) + } + + ch.atake(20).map{ values => + assert(values(0) == 0) + assert(values(1) == 1) + assert(values(2) == 2) + assert(values.size == 11) + } + + } + + + test("unfoldAsync prodce stream with error") { + val ch = ReadChannel.unfoldAsync(0){ + (x: Int) => + if (x > 3) then + Future failed new RuntimeException("state is too big") + else + Future successful Some(x,x+1) + } + + async { + val r0 = ch.read() + assert(r0 == 0) + val r1 = ch.read() + assert(r1 == 1) + val r2 = ch.read() + assert(r2 == 2) + val r3 = ch.read() + assert(r3 == 3) + var wasTooBig = false + try { + val r4 = ch.read() + }catch{ + case e: RuntimeException => + wasTooBig = true + } + assert(wasTooBig) + } + + + } + +} From a179bae99f7aed42ad3d12785f7d53c754f4de9f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 11 Jun 2021 17:45:50 +0300 Subject: [PATCH 25/92] scaladoc added --- build.sbt | 9 ++++++-- project/plugins.sbt | 2 +- .../src/main/scala/gopher/ReadChannel.scala | 5 +++- shared/src/main/scala/gopher/Select.scala | 23 +++++++++++++++---- 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index fec5eb36..9ece1d8f 100644 --- a/build.sbt +++ b/build.sbt @@ -19,10 +19,12 @@ lazy val root = project .in(file(".")) .aggregate(gopher.js, gopher.jvm) .settings( - Sphinx / sourceDirectory := baseDirectory.value / "docs", git.remoteRepo := "git@github.com:rssh/scala-gopher.git", + SiteScaladocPlugin.scaladocSettings(GopherJVM, gopher.jvm / Compile / packageDoc / mappings, "api/jvm"), + SiteScaladocPlugin.scaladocSettings(GopherJS, gopher.js / Compile / packageDoc / mappings, "api/js"), + siteDirectory := baseDirectory.value / "target" / "site", publishArtifact := false, - ).enablePlugins(GhpagesPlugin) + ).enablePlugins(GhpagesPlugin, SiteScaladocPlugin) @@ -30,6 +32,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .in(file(".")) .settings(sharedSettings) .disablePlugins(SitePlugin) + .disablePlugins(SitePreviewPlugin) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), ).jsSettings( @@ -39,3 +42,5 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) scalaJSUseMainModuleInitializer := true, ) +lazy val GopherJVM = config("gopher.jvm") +lazy val GopherJS = config("gopher.js") diff --git a/project/plugins.sbt b/project/plugins.sbt index 02f86b3f..9ed7f66a 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,5 +1,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") -addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 232bf236..d1b8b5a6 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -10,7 +10,10 @@ import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} - +/** + * ReadChannel: Interface providing reading API. + * + **/ trait ReadChannel[F[_], A]: thisReadChannel => diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 6bf9f88f..ba22e154 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -6,9 +6,26 @@ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ - +/** Organize waiting for read/write from multiple async channels + * + * Gopher[F] provide a function `select` of this type. + */ class Select[F[_]](api: Gopher[F]): + /** wait until some channels from the list in pf . + * + *```Scala + *async{ + * .... + * select { + * case vx:xChannel.read => doSomethingWithX + * case vy:yChannel.write if (vy == valueToWrite) => doSomethingAfterWrite(vy) + * case t: Time.after if (t == 1.minute) => processTimeout + * } + * ... + *} + *``` + */ transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ Select.onceImpl[F,A]('pf, 'api ) @@ -51,9 +68,7 @@ class Select[F[_]](api: Gopher[F]): def afold_async[S](s0:S)(step: S => F[S | SelectFold.Done[S]]) : F[S] = fold_async(s0)(step) - - //def map[A](step: PartialFunction[SelectGroup[F,A],A|SelectFold.Done[Unit]]): ReadChannel[F,A] = - + def map[A](step: SelectGroup[F,A] => A): ReadChannel[F,A] = mapAsync[A](x => api.asyncMonad.pure(step(x))) From ee2a04e4bf013a127ddc90556cf33bf0ebac9e9a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 28 Jun 2021 11:23:59 +0300 Subject: [PATCH 26/92] updated to scala 3.0.1-RC2 (with fixed https://github.com/lampepfl/dotty/issues/12791 ) --- build.sbt | 2 +- shared/src/main/scala/gopher/Select.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 9ece1d8f..e8612102 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.0" +val dottyVersion = "3.0.1-RC2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.0.4-SNAPSHOT" diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index ba22e154..a37120cc 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -7,9 +7,9 @@ import scala.compiletime._ import scala.concurrent.duration._ /** Organize waiting for read/write from multiple async channels - * - * Gopher[F] provide a function `select` of this type. - */ + * + * Gopher[F] provide a function `select` of this type. + */ class Select[F[_]](api: Gopher[F]): /** wait until some channels from the list in pf . From 438880490b6bd092473852a486746abbc86db0e5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:30:26 +0300 Subject: [PATCH 27/92] adopted do scala3.0.1 and dotty-cps-async 0.9.0 --- build.sbt | 6 +- project/build.properties | 2 +- shared/src/main/scala/gopher/Select.scala | 290 +--------------- .../src/main/scala/gopher/SelectForever.scala | 11 +- .../src/main/scala/gopher/SelectGroup.scala | 4 +- shared/src/main/scala/gopher/SelectLoop.scala | 2 +- .../src/main/scala/gopher/SelectMacro.scala | 316 ++++++++++++++++++ .../channels/ForeverTerminationSuite.scala | 9 +- 8 files changed, 353 insertions(+), 287 deletions(-) create mode 100644 shared/src/main/scala/gopher/SelectMacro.scala diff --git a/build.sbt b/build.sbt index e8612102..d9494fe9 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.1-RC2" +val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.4-SNAPSHOT" +ThisBuild/version := "2.0.4" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.8.1", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.0", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/project/build.properties b/project/build.properties index f0be67b9..10fd9eee 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.1 +sbt.version=1.5.5 diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index a37120cc..f94531f7 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -28,9 +28,13 @@ class Select[F[_]](api: Gopher[F]): */ transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = ${ - Select.onceImpl[F,A]('pf, 'api ) + SelectMacro.onceImpl[F,A]('pf, 'api ) } + /*** + * create select groop + *@see [gopher.SelectGroup] + **/ def group[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) def once[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) @@ -92,287 +96,25 @@ class Select[F[_]](api: Gopher[F]): } r - def forever: SelectForever[F] = new SelectForever[F](api ) + /** + * create forever runner. + **/ + def forever: SelectForever[F] = new SelectForever[F](api) + /** + * run forever expression in `pf`, return + **/ transparent inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = - async(using api.asyncMonad).apply { - val runner = new SelectForever[F](api) - runner.apply(pf) - } + ${ SelectMacro.aforeverImpl('pf, 'api) } - def aforever_async(pf: PartialFunction[Any,F[Unit]]): F[Unit] = + /* + transparent inline def aforever_async(inline pf: PartialFunction[Any,F[Unit]]): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad async(using api.asyncMonad).apply { val runner = new SelectForever[F](api) runner.applyAsync(pf) } - + */ -object Select: - - import cps.forest.TransformUtil - - sealed trait SelectGroupExpr[F[_],S, R]: - def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] - - sealed trait SelectorCaseExpr[F[_]:Type, S:Type, R:Type]: - type Monad[X] = F[X] - def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] - - case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onRead($ch)($f) } - - case class WriteExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onWrite($ch,$a)($f) } - - case class TimeoutExpression[F[_]:Type,S:Type, R:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onTimeout($t)($f) } - - case class DoneExression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S,R]: - def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = - '{ $base.onRead($ch.done)($f) } - - def selectListenerBuilder[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( - constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[R] = - val s0 = constructor - val g = caseDefs.foldLeft(s0){(s,e) => - e.appended(s) - } - // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } - r.asExprOf[R] - - - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = - def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { - val s0 = '{ - new SelectGroup[F,A]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl(builder, pf) - - def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { - val s0 = '{ - new SelectLoop[F]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl( builder, pf) - - - def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = - def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { - val s0 = '{ - new SelectForever[F]($api) - } - selectListenerBuilder(s0, caseDefs, api) - } - runImpl(builder, pf) - - - - def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A,B]]=>Expr[B], - pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = - import quotes.reflect._ - runImplTree[F,A,B](builder, pf.asTerm) - - def runImplTree[F[_]:Type, A:Type, B:Type](using Quotes)( - builder: List[SelectorCaseExpr[F,A,B]] => Expr[B], - pf: quotes.reflect.Term - ): Expr[B] = - import quotes.reflect._ - pf match - case Lambda(valDefs, body) => - runImplTree[F,A,B](builder, body) - case Inlined(_,List(),body) => - runImplTree[F,A,B](builder, body) - case Match(scrutinee,cases) => - //val caseExprs = cases map(x => parseCaseDef[F,A](x)) - //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { - // report.error("default is not supported") - //} - val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) - // done should be - val (isDone,notDone) = unorderedCases.partition{ x => - x match - case DoneExression(_,_) => true - case ReadExpression(_,_,isDone) => isDone - case _ => false - } - val doneFirstCases = isDone ++ notDone - builder(doneFirstCases) - - - def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = - import quotes.reflect._ - - val caseDefGuard = parseCaseDefGuard(caseDef) - - def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = - val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) - if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) - tp.asType match - case '[a] => - val isDone = channel match - case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true - case _ => false - ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) - case _ => - reportError("can't determinate read type", caseDef.pattern.asExpr) - else - reportError("read pattern is not a read channel", channel.asExpr) - - def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = - val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) - val e = caseDefGuard.getOrElse(valName, - reportError(s"not found binding ${valName} in write condition", channel.asExpr) - ) - if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then - tp.asType match - case '[a] => - WriteExpression(channel.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) - case _ => - reportError("Can't determinate type of write", caseDef.pattern.asExpr) - else - reportError("Write channel expected", channel.asExpr) - - def extractType[F[_]:Type](name: "read"|"write", channelTerm: Term, pat: Tree): TypeRepr = - import quotes.reflect._ - pat match - case Typed(_,tp) => tp.tpe - case _ => - TypeSelect(channelTerm,name).tpe - - - caseDef.pattern match - case Inlined(_,List(),body) => - parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => - handleRead(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => - handleRead(b,v,ch,tp.tpe) - case tp@Typed(expr, TypeSelect(ch,"read")) => - // todo: introduce 'dummy' val - reportError("binding var in read expression is mandatory", caseDef.pattern.asExpr) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => - handleWrite(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => - handleWrite(b,v,ch,tp.tpe) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => - val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) - val e = caseDefGuard.getOrElse(v, reportError(s"can't find condifion for $v",caseDef.pattern.asExpr)) - if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) - TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) - else - reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) - case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"done"))) => - val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) - tp.tpe.asType match - case '[a] => - if (ch.tpe <:< TypeRepr.of[ReadChannel[F,a]]) then - DoneExression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[Unit=>S]) - else - reportError("done base is not a read channel", ch.asExpr) - case _ => - reportError("can't determinate read type", caseDef.pattern.asExpr) - case pat@Unapply(TypeApply(quotes.reflect.Select( - quotes.reflect.Select(chobj,nameReadOrWrite), - "unapply"),targs), - impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => - if (chobj.tpe == '{gopher.Channel}.asTerm.tpe) - val chExpr = caseDefGuard.getOrElse(ch,reportError(s"select condition for ${ch} is not found",caseDef.pattern.asExpr)) - nameReadOrWrite match - case "Read" => - val elementType = extractType("read",chExpr, ePat) - handleRead(b,e,chExpr,elementType) - case "Write" => - val elementType = extractType("write",chExpr, ePat) - handleWrite(b,e,chExpr,elementType) - case _ => - reportError(s"Read or Write expected, we have ${nameReadOrWrite}", caseDef.pattern.asExpr) - else - reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chobj.asExpr) - case _ => - report.error( - s""" - expected one of: - v: channel.read - v: channel.write if v == expr - v: Time.after if v == expr - we have - ${caseDef.pattern.show} - (tree: ${caseDef.pattern}) - """, caseDef.pattern.asExpr) - reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) - - end parseCaseDef - - - def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = - import quotes.reflect._ - caseDef.guard match - case Some(condition) => - parseSelectCondition(condition, Map.empty) - case None => - Map.empty - - - def parseSelectCondition(using Quotes)(condition: quotes.reflect.Term, - entries:Map[String,quotes.reflect.Term]): Map[String,quotes.reflect.Term] = - import quotes.reflect._ - condition match - case Apply(quotes.reflect.Select(Ident(v1),"=="),List(expr)) => - entries.updated(v1, expr) - case Apply(quotes.reflect.Select(frs, "&&" ), List(snd)) => - parseSelectCondition(snd, parseSelectCondition(frs, entries)) - case _ => - reportError( - s"""Invalid select guard form, expected one of - channelName == channelEpxr - writeBind == writeExpresion - condition && condition - we have - ${condition.show} - """, - condition.asExpr) - - - def makeLambda(using Quotes)(argName: String, - argType: quotes.reflect.TypeRepr, - oldArgSymbol: quotes.reflect.Symbol, - body: quotes.reflect.Term): quotes.reflect.Term = - import quotes.reflect._ - val widenReturnType = TransformUtil.veryWiden(body.tpe) - val mt = MethodType(List(argName))(_ => List(argType.widen), _ => widenReturnType) - Lambda(Symbol.spliceOwner, mt, (owner,args) => - substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) - - - def substIdent(using Quotes)(term: quotes.reflect.Term, - fromSym: quotes.reflect.Symbol, - toTerm: quotes.reflect.Term, - owner: quotes.reflect.Symbol): quotes.reflect.Term = - import quotes.reflect._ - val argTransformer = new TreeMap() { - override def transformTerm(tree: Term)(owner: Symbol):Term = - tree match - case Ident(name) if tree.symbol == fromSym => toTerm - case _ => super.transformTerm(tree)(owner) - } - argTransformer.transformTerm(term)(owner) - - - def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = - import quotes.reflect._ - report.error(message, posExpr) - throw new RuntimeException(s"Error in macro: $message") - - - diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index eb3d8f32..bf26c1c1 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -6,16 +6,21 @@ import scala.compiletime._ import scala.concurrent.duration._ +/** + * forever Apply + **/ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): transparent inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = ${ - Select.foreverImpl('pf,'api) + SelectMacro.foreverImpl('pf,'api) } - transparent inline def applyAsync(inline pf: PartialFunction[Any,F[Unit]]): Unit = - ??? + transparent inline def applyAsync(inline pf: PartialFunction[Any,Unit]): F[Unit] = + ${ + SelectMacro.aforeverImpl('pf, 'api) + } def runAsync(): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 3d62d7e0..a1cbaa98 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -65,12 +65,12 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: transparent inline def apply(inline pf: PartialFunction[Any,S]): S = ${ - Select.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api ) } transparent inline def select(inline pf: PartialFunction[Any,S]): S = ${ - Select.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api ) } /** diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index d97b5207..c02a1f3c 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -13,7 +13,7 @@ class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Uni transparent inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = ${ - Select.loopImpl[F]('pf, 'api ) + SelectMacro.loopImpl[F]('pf, 'api ) } def runAsync(): F[Unit] = diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala new file mode 100644 index 00000000..5bcd5863 --- /dev/null +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -0,0 +1,316 @@ +package gopher + + +import cps._ + +import scala.quoted._ +import scala.compiletime._ +import scala.concurrent.duration._ + + + + +object SelectMacro: + + import cps.macros.forest.TransformUtil + + sealed trait SelectGroupExpr[F[_],S, R]: + def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] + + sealed trait SelectorCaseExpr[F[_]:Type, S:Type, R:Type]: + type Monad[X] = F[X] + def appended[L <: SelectListeners[F,S,R] : Type](base: Expr[L])(using Quotes): Expr[L] + + case class ReadExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[A => S], isDone: Boolean) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onRead($ch)($f) } + + case class WriteExpression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[WriteChannel[F,A]], a: Expr[A], f: Expr[A => S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onWrite($ch,$a)($f) } + + case class TimeoutExpression[F[_]:Type,S:Type, R:Type](t: Expr[FiniteDuration], f: Expr[ FiniteDuration => S ]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onTimeout($t)($f) } + + case class DoneExression[F[_]:Type, A:Type, S:Type, R:Type](ch: Expr[ReadChannel[F,A]], f: Expr[Unit=>S]) extends SelectorCaseExpr[F,S,R]: + def appended[L <: SelectListeners[F,S,R]: Type](base: Expr[L])(using Quotes): Expr[L] = + '{ $base.onRead($ch.done)($f) } + + + def selectListenerBuilder1[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]])(using Quotes): Expr[L] = + val s0 = constructor + caseDefs.foldLeft(s0){(s,e) => + e.appended(s) + } + + + def buildSelectListenerRun[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]], + api:Expr[Gopher[F]])(using Quotes): Expr[R] = + val g = selectListenerBuilder1(constructor, caseDefs) + // dotty bug if g.run + val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } + r.asExprOf[R] + + def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + constructor: Expr[L], + caseDefs: List[SelectorCaseExpr[F,S,R]], + api:Expr[Gopher[F]])(using Quotes): Expr[F[R]] = + val g = selectListenerBuilder1(constructor, caseDefs) + // dotty bug if g.run + val r = '{ $g.runAsync() } + r.asExprOf[F[R]] + + + + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { + val s0 = '{ + new SelectGroup[F,A]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl(builder, pf) + + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { + val s0 = '{ + new SelectLoop[F]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl( builder, pf) + + + def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { + val s0 = '{ + new SelectForever[F]($api) + } + buildSelectListenerRun(s0, caseDefs, api) + } + runImpl(builder, pf) + + def aforeverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[F[Unit]] = + import quotes.reflect._ + def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[F[Unit]] = { + val s0 = '{ + new SelectForever[F]($api) + } + buildSelectListenerRunAsync(s0, caseDefs, api) + } + runImplTree(builder, pf.asTerm) + + + def runImpl[F[_]:Type, A:Type,B :Type](builder: List[SelectorCaseExpr[F,A,B]]=>Expr[B], + pf: Expr[PartialFunction[Any,A]])(using Quotes): Expr[B] = + import quotes.reflect._ + runImplTree[F,A,B,B](builder, pf.asTerm) + + def runImplTree[F[_]:Type, A:Type, B:Type, C:Type](using Quotes)( + builder: List[SelectorCaseExpr[F,A,B]] => Expr[C], + pf: quotes.reflect.Term + ): Expr[C] = + import quotes.reflect._ + pf match + case Lambda(valDefs, body) => + runImplTree[F,A,B,C](builder, body) + case Inlined(_,List(),body) => + runImplTree[F,A,B,C](builder, body) + case Match(scrutinee,cases) => + //val caseExprs = cases map(x => parseCaseDef[F,A](x)) + //if (caseExprs.find(_.isInstanceOf[DefaultExpression[?]]).isDefined) { + // report.error("default is not supported") + //} + val unorderedCases = cases.map(parseCaseDef[F,A,B](_)) + // done should be + val (isDone,notDone) = unorderedCases.partition{ x => + x match + case DoneExression(_,_) => true + case ReadExpression(_,_,isDone) => isDone + case _ => false + } + val doneFirstCases = isDone ++ notDone + builder(doneFirstCases) + + + def parseCaseDef[F[_]:Type,S:Type,R:Type](using Quotes)(caseDef: quotes.reflect.CaseDef): SelectorCaseExpr[F,S,R] = + import quotes.reflect._ + + val caseDefGuard = parseCaseDefGuard(caseDef) + + def handleRead(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = + val readFun = makeLambda(valName,tp,bind.symbol,caseDef.rhs) + if (channel.tpe <:< TypeRepr.of[ReadChannel[F,?]]) + tp.asType match + case '[a] => + val isDone = channel match + case quotes.reflect.Select(ch1,"done") if (ch1.tpe <:< TypeRepr.of[ReadChannel[F,?]]) => true + case _ => false + ReadExpression(channel.asExprOf[ReadChannel[F,a]],readFun.asExprOf[a=>S],isDone) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + else + reportError("read pattern is not a read channel", channel.asExpr) + + def handleWrite(bind: Bind, valName: String, channel:Term, tp:TypeRepr): SelectorCaseExpr[F,S,R] = + val writeFun = makeLambda(valName,tp, bind.symbol, caseDef.rhs) + val e = caseDefGuard.getOrElse(valName, + reportError(s"not found binding ${valName} in write condition", channel.asExpr) + ) + if (channel.tpe <:< TypeRepr.of[WriteChannel[F,?]]) then + tp.asType match + case '[a] => + WriteExpression(channel.asExprOf[WriteChannel[F,a]],e.asExprOf[a], writeFun.asExprOf[a=>S]) + case _ => + reportError("Can't determinate type of write", caseDef.pattern.asExpr) + else + reportError("Write channel expected", channel.asExpr) + + def extractType[F[_]:Type](name: "read"|"write", channelTerm: Term, pat: Tree): TypeRepr = + import quotes.reflect._ + pat match + case Typed(_,tp) => tp.tpe + case _ => + TypeSelect(channelTerm,name).tpe + + def handleUnapply(chObj: Term, nameReadOrWrite: String, bind: Bind, valName: String, ePat: Tree, ch: String): SelectorCaseExpr[F,S,R] = + import quotes.reflect._ + if (chObj.tpe == '{gopher.Channel}.asTerm.tpe) + val chExpr = caseDefGuard.getOrElse(ch,reportError(s"select condition for ${ch} is not found",caseDef.pattern.asExpr)) + nameReadOrWrite match + case "Read" => + val elementType = extractType("read",chExpr, ePat) + handleRead(bind,valName,chExpr,elementType) + case "Write" => + val elementType = extractType("write",chExpr, ePat) + handleWrite(bind,valName,chExpr,elementType) + case _ => + reportError(s"Read or Write expected, we have ${nameReadOrWrite}", caseDef.pattern.asExpr) + else + reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chObj.asExpr) + + + + caseDef.pattern match + case Inlined(_,List(),body) => + parseCaseDef(CaseDef(body, caseDef.guard, caseDef.rhs)) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"read"))) => + handleRead(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"read"),_))) => + handleRead(b,v,ch,tp.tpe) + case tp@Typed(expr, TypeSelect(ch,"read")) => + // todo: introduce 'dummy' val + reportError("binding var in read expression is mandatory", caseDef.pattern.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"write"))) => + handleWrite(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, Annotated(TypeSelect(ch,"write"),_))) => + handleWrite(b,v,ch,tp.tpe) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"after"))) => + val timeoutFun = makeLambda(v, tp.tpe, b.symbol, caseDef.rhs) + val e = caseDefGuard.getOrElse(v, reportError(s"can't find condifion for $v",caseDef.pattern.asExpr)) + if (ch.tpe <:< TypeRepr.of[gopher.Time] || ch.tpe <:< TypeRepr.of[gopher.Time.type]) + TimeoutExpression(e.asExprOf[FiniteDuration], timeoutFun.asExprOf[FiniteDuration => S]) + else + reportError(s"Expected Time, we have ${ch.show}", ch.asExpr) + case b@Bind(v, tp@Typed(expr, TypeSelect(ch,"done"))) => + val readFun = makeLambda(v,tp.tpe,b.symbol,caseDef.rhs) + tp.tpe.asType match + case '[a] => + if (ch.tpe <:< TypeRepr.of[ReadChannel[F,a]]) then + DoneExression(ch.asExprOf[ReadChannel[F,a]],readFun.asExprOf[Unit=>S]) + else + reportError("done base is not a read channel", ch.asExpr) + case _ => + reportError("can't determinate read type", caseDef.pattern.asExpr) + case pat@Unapply(TypeApply(quotes.reflect.Select( + quotes.reflect.Select(chObj,nameReadOrWrite), + "unapply"),targs), + impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => + handleUnapply(chObj, nameReadOrWrite, b, e, ePat, ch) + case pat@Typed(Unapply(TypeApply(quotes.reflect.Select( + quotes.reflect.Select(chobj,nameReadOrWrite), + "unapply"),targs), + impl,List(b@Bind(e,ePat),Bind(ch,chPat))),a) => + handleUnapply(chobj, nameReadOrWrite, b, e, ePat, ch) + case _ => + report.error( + s""" + expected one of: + v: channel.read + v: channel.write if v == expr + v: Time.after if v == expr + we have + ${caseDef.pattern.show} + (tree: ${caseDef.pattern}) + """, caseDef.pattern.asExpr) + reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) + + end parseCaseDef + + + def parseCaseDefGuard(using Quotes)(caseDef: quotes.reflect.CaseDef): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + caseDef.guard match + case Some(condition) => + parseSelectCondition(condition, Map.empty) + case None => + Map.empty + + + def parseSelectCondition(using Quotes)(condition: quotes.reflect.Term, + entries:Map[String,quotes.reflect.Term]): Map[String,quotes.reflect.Term] = + import quotes.reflect._ + condition match + case Apply(quotes.reflect.Select(Ident(v1),"=="),List(expr)) => + entries.updated(v1, expr) + case Apply(quotes.reflect.Select(frs, "&&" ), List(snd)) => + parseSelectCondition(snd, parseSelectCondition(frs, entries)) + case _ => + reportError( + s"""Invalid select guard form, expected one of + channelName == channelEpxr + writeBind == writeExpresion + condition && condition + we have + ${condition.show} + """, + condition.asExpr) + + + def makeLambda(using Quotes)(argName: String, + argType: quotes.reflect.TypeRepr, + oldArgSymbol: quotes.reflect.Symbol, + body: quotes.reflect.Term): quotes.reflect.Term = + import quotes.reflect._ + val widenReturnType = TransformUtil.veryWiden(body.tpe) + val mt = MethodType(List(argName))(_ => List(argType.widen), _ => widenReturnType) + Lambda(Symbol.spliceOwner, mt, (owner,args) => + substIdent(body,oldArgSymbol, args.head.asInstanceOf[Term], owner).changeOwner(owner)) + + + def substIdent(using Quotes)(term: quotes.reflect.Term, + fromSym: quotes.reflect.Symbol, + toTerm: quotes.reflect.Term, + owner: quotes.reflect.Symbol): quotes.reflect.Term = + import quotes.reflect._ + val argTransformer = new TreeMap() { + override def transformTerm(tree: Term)(owner: Symbol):Term = + tree match + case Ident(name) if tree.symbol == fromSym => toTerm + case _ => super.transformTerm(tree)(owner) + } + argTransformer.transformTerm(term)(owner) + + + def reportError(message: String, posExpr: Expr[?])(using Quotes): Nothing = + import quotes.reflect._ + report.error(message, posExpr) + throw new RuntimeException(s"Error in macro: $message") + + + diff --git a/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala index 0a5fa69a..5a9f64a5 100644 --- a/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala +++ b/shared/src/test/scala/gopher/channels/ForeverTerminationSuite.scala @@ -19,11 +19,14 @@ class ForeverSuite extends FunSuite test("forevr not propagate signals after exit") { + implicit val printCode = cps.macros.flags.PrintCode val channel = makeChannel[Int](100) var sum = 0 - val f0 = select.aforever { - case x: channel.read => sum += x - throw ChannelClosedException() + val f0: Future[Unit] = select.aforever{ + case x: channel.read => { + sum += x + throw ChannelClosedException() + } } for {r2 <- channel.awrite(1) r3 <- channel.awrite(2) From 4e24cb098dd9843155733a4467c02828bf334e05 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:37:24 +0300 Subject: [PATCH 28/92] scaladoc for Select.fold updated --- shared/src/main/scala/gopher/SelectFold.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shared/src/main/scala/gopher/SelectFold.scala b/shared/src/main/scala/gopher/SelectFold.scala index 7dd2c63e..2dee2981 100644 --- a/shared/src/main/scala/gopher/SelectFold.scala +++ b/shared/src/main/scala/gopher/SelectFold.scala @@ -1,6 +1,13 @@ package gopher +/** + * Helper namespace for Select.Fold return value + * @see [Select.fold] + **/ object SelectFold: + /** + * return value in Select.Fold which means that we should stop folding + **/ case class Done[S](s: S) \ No newline at end of file From cdb9e813e8cd27cad4fffd1b19a1b0f98b750d01 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:46:10 +0300 Subject: [PATCH 29/92] 2.0.4 as last published version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56822878..970cf86d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.3" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.4" For scala2: From 87413ff250fca2aa42146efa0515d4bcd2423692 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 13 Jul 2021 19:50:33 +0300 Subject: [PATCH 30/92] 2.0.5-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d9494fe9..1c2eecad 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.4" +ThisBuild/version := "2.0.5-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From d84605c2d8e0b470816a7b906ff89da39da6ff62 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 14 Jul 2021 07:52:26 +0300 Subject: [PATCH 31/92] more documentation --- shared/src/main/scala/gopher/Select.scala | 8 -------- .../src/main/scala/gopher/SelectForever.scala | 6 +----- .../src/main/scala/gopher/SelectGroup.scala | 20 ++++++++++++++----- .../src/main/scala/gopher/SelectMacro.scala | 6 +++--- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index f94531f7..221a85cd 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -107,14 +107,6 @@ class Select[F[_]](api: Gopher[F]): transparent inline def aforever(inline pf: PartialFunction[Any,Unit]): F[Unit] = ${ SelectMacro.aforeverImpl('pf, 'api) } - /* - transparent inline def aforever_async(inline pf: PartialFunction[Any,F[Unit]]): F[Unit] = - given CpsSchedulingMonad[F] = api.asyncMonad - async(using api.asyncMonad).apply { - val runner = new SelectForever[F](api) - runner.applyAsync(pf) - } - */ diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index bf26c1c1..8f1b2352 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -7,7 +7,7 @@ import scala.concurrent.duration._ /** - * forever Apply + * Result of `select.forever`: apply method accept partial pseudofunction which evalueated forever. **/ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): @@ -17,10 +17,6 @@ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Uni SelectMacro.foreverImpl('pf,'api) } - transparent inline def applyAsync(inline pf: PartialFunction[Any,Unit]): F[Unit] = - ${ - SelectMacro.aforeverImpl('pf, 'api) - } def runAsync(): F[Unit] = given CpsSchedulingMonad[F] = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index a1cbaa98..0404c847 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -14,8 +14,18 @@ import java.util.logging.{Level => LogLevel} /** - * Select group is a virtual 'lock' object, where only - * ne fro rieader and writer can exists at the sae time. + * Select group is a virtual 'lock' object. + * Readers and writers are grouped into select groups. When + * event about avaiability to read or to write is arrived and + * no current event group members is running, than run of one of the members + * is triggered. + * I.e. only one from group can run. + * + * Note, that application develeper usually not work with `SelectGroup` directly, + * it is created internally by `select` pseudostatement. + * + *@see [gopher.Select] + *@see [gopher.select] **/ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: @@ -28,10 +38,10 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: * 2 - expired **/ val waitState: AtomicInteger = new AtomicInteger(0) - var call: Try[S] => Unit = { _ => () } + private var call: Try[S] => Unit = { _ => () } private inline def m = api.asyncMonad - val retval = m.adoptCallbackStyle[S](f => call=f) - val startTime = new AtomicLong(0L) + private val retval = m.adoptCallbackStyle[S](f => call=f) + private val startTime = new AtomicLong(0L) var timeoutScheduled: Option[Time.Scheduled] = None override def asyncMonad = api.asyncMonad diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 5bcd5863..9fd6b10b 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -38,7 +38,7 @@ object SelectMacro: '{ $base.onRead($ch.done)($f) } - def selectListenerBuilder1[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( + def selectListenerBuilder[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]])(using Quotes): Expr[L] = val s0 = constructor @@ -51,7 +51,7 @@ object SelectMacro: constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[R] = - val g = selectListenerBuilder1(constructor, caseDefs) + val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } r.asExprOf[R] @@ -60,7 +60,7 @@ object SelectMacro: constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], api:Expr[Gopher[F]])(using Quotes): Expr[F[R]] = - val g = selectListenerBuilder1(constructor, caseDefs) + val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run val r = '{ $g.runAsync() } r.asExprOf[F[R]] From 7499cba685004cd8a19c0e963a6b3cf7f2bcd216 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 25 Jul 2021 10:39:43 +0300 Subject: [PATCH 32/92] adopted to dotty-cps-async 0.9.1 (async fir future starts with spawn) --- build.sbt | 4 +- .../scala/gopher/impl/PromiseChannel.scala | 27 ++++++++------ .../src/main/scala/gopher/ReadChannel.scala | 26 +++++++++++++ .../gopher/monads/ReadChannelCpsMonad.scala | 2 +- .../gopher/channels/MacroSelectSuite.scala | 1 + .../src/test/scala/gopher/monads/Queens.scala | 37 +++++++++++-------- 6 files changed, 67 insertions(+), 30 deletions(-) diff --git a/build.sbt b/build.sbt index 1c2eecad..2a704cbe 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.5-SNAPSHOT" +ThisBuild/version := "2.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.0", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, testFrameworks += new TestFramework("munit.Framework") ) diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index bb048e99..4f2783ba 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -34,8 +34,10 @@ import scala.util.Failure case Some((a,f)) => val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then - closed.lazySet(true) - taskExecutor.execute(()=> f(Success(()))) + closed.set(true) + taskExecutor.execute{ ()=> + f(Success(())) + } writer.markUsed() step() else @@ -48,21 +50,24 @@ import scala.util.Failure def addDoneReader(reader: Reader[Unit]): Unit = - if (!closed.get()) then + if (!closed.get() || !readed.get) then doneReaders.add(reader) + if (closed.get()) then + step() else var done = false while(!done & !reader.isExpired) { - reader.capture() match - case Some(f) => - reader.markUsed() - taskExecutor.execute(()=>f(Success(()))) - done = true - case None => - if (!reader.isExpired) - Thread.onSpinWait() + reader.capture() match + case Some(f) => + reader.markUsed() + taskExecutor.execute(()=>f(Success(()))) + done = true + case None => + if (!reader.isExpired) + Thread.onSpinWait() } + def close(): Unit = diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index d1b8b5a6..b00af8b9 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -214,6 +214,10 @@ object ReadChannel: retval.close() retval + /** + *@param c - iteratable to read from. + *@return channel, which will emit all elements from 'c' and then close. + **/ def fromIterable[F[_],A](c: IterableOnce[A])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[A]() @@ -227,6 +231,28 @@ object ReadChannel: }) retval + /** + *@return one copy of `a` and close. + **/ + def once[F[_],A](a: A)(using Gopher[F]): ReadChannel[F,A] = + fromIterable(List(a)) + + /** + *@param a - value to produce + *@return channel which emit value of a in loop and never close + **/ + def always[F[_],A](a: A)(using Gopher[F]): ReadChannel[F,A] = + given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad + val retval = makeChannel[A]() + summon[Gopher[F]].spawnAndLogFail( + async{ + while(true) { + retval.write(a) + } + } + ) + retval + def fromFuture[F[_],A](f: F[A])(using Gopher[F]): ReadChannel[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index 98c91f6a..f1a35596 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -23,6 +23,6 @@ given futureToReadChannel[F[_]](using Gopher[F]): CpsMonadConversion[F, [A] =>> def apply[T](ft: F[T]): ReadChannel[F,T] = futureInput(ft) - + diff --git a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala index 4b872d8a..82a40bac 100644 --- a/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala +++ b/shared/src/test/scala/gopher/channels/MacroSelectSuite.scala @@ -419,6 +419,7 @@ class MacroSelectSuite extends FunSuite } val f1 = ch.awrite(1) async { + await(f1) val r = await(sf) assert(r==1) } diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index d7d53519..917b444d 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -22,7 +22,25 @@ class QueensSuite extends FunSuite { busyLRDiagonals:Set[Int], busyRLDiagonals:Set[Int], queens: Vector[(Int,Int)] - ); + ) { + + def isBusy(i:Int, j:Int): Boolean = + busyRows.contains(i) || + busyColumns.contains(j) || + busyLRDiagonals.contains(i-j) || + busyRLDiagonals.contains(i+j) + + + def put(i:Int, j:Int): State = + copy( busyRows = busyRows + i, + busyColumns = busyColumns + j, + busyLRDiagonals = busyLRDiagonals + (i-j), + busyRLDiagonals = busyRLDiagonals + (i+j), + queens = queens :+ (i,j) + ) + + + } val N = 8 @@ -31,19 +49,8 @@ class QueensSuite extends FunSuite { async[Future] { val i = state.queens.length if i < N then - for{ - j <- 0 until N if !state.busyColumns.contains(j) && - !state.busyLRDiagonals.contains(i-j) && - !state.busyRLDiagonals.contains(i+j) - } { - val newPos = (i,j) - val nState = state.copy( busyRows = state.busyRows + i, - busyColumns = state.busyColumns + j, - busyLRDiagonals = state.busyLRDiagonals + (i-j), - busyRLDiagonals = state.busyRLDiagonals + (i+j), - queens = state.queens :+ newPos ) - ch.write(nState) - } + for{ j <- 0 until N if !state.isBusy(i,j) } + ch.write(state.put(i,j)) ch.close() } ch @@ -51,9 +58,7 @@ class QueensSuite extends FunSuite { def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { if(state.queens.size < N) then - //println("state:"+state.queens) val nextState = await(putQueen(state)) - //println("next-state:"+state.queens) await(solutions(nextState)) else state From b0596c800af60c5d808a465e9a929bba6d9d1ef9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 19:58:38 +0300 Subject: [PATCH 33/92] dotty-cps-async-0.9.2, scalajs-1.7.0 --- README.md | 2 +- build.sbt | 7 +++---- project/plugins.sbt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 970cf86d..f9d9ed50 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.4" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.5" For scala2: diff --git a/build.sbt b/build.sbt index 2a704cbe..47579481 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.5" +ThisBuild/version := "2.0.6" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,9 +10,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.1", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.26" % Test, - testFrameworks += new TestFramework("munit.Framework") + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.2", + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) lazy val root = project diff --git a/project/plugins.sbt b/project/plugins.sbt index 9ed7f66a..a5c778e6 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.5.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") From 2edd251ef87df6c5f61372f22200b51c63bb910f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 20:50:41 +0300 Subject: [PATCH 34/92] 2.0.6 in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f9d9ed50..2ad690a6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.5" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.6" For scala2: From 7e28e3248366ffeb7db6edac0eb24ca53f962b5a Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 5 Aug 2021 20:51:22 +0300 Subject: [PATCH 35/92] 2.0.7-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 47579481..95d1fd88 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.6" +ThisBuild/version := "2.0.7-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From f3186fb51d7448265043cab0eb83303be21866f8 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 15 Aug 2021 11:18:07 +0300 Subject: [PATCH 36/92] fixed possible lost of event in situation many readers, few writers in unbuffered streams. --- .../main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala | 1 - .../main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 641de118..e9bfa209 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -81,7 +81,6 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con while(progress) { progress = false if !state.isEmpty() then - val a = state.startRead() progress |= processReadsStep() else if isClosed then diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index a439989c..56998859 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -54,6 +54,10 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( progress = true progressWaitReader(reader) } + if !writersLoopDone then + // we have reader, should return one back if we want to start again + // TODO: write test for this case + readers.addFirst(reader) } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then progress |= processWriteClose() From 4c87d52989e5232da51debca4fe0ee5abf164265 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 3 Sep 2021 07:18:54 +0300 Subject: [PATCH 37/92] refactored Expirable.caputure to return 3-variant value; added support of cps generators --- build.sbt | 10 +- js/src/main/scala/gopher/JSGopher.scala | 11 ++- .../main/scala/gopher/impl/BaseChannel.scala | 22 +++-- .../scala/gopher/impl/BufferedChannel.scala | 10 +- .../scala/gopher/impl/PromiseChannel.scala | 19 ++-- .../scala/gopher/impl/UnbufferedChannel.scala | 12 ++- js/src/test/scala/gopher/util/Debug.scala | 41 ++++++++ jvm/src/main/scala/gopher/JVMGopher.scala | 5 +- .../gopher/impl/GuardedSPSCBaseChannel.scala | 88 ++++++++++++----- .../impl/GuardedSPSCBufferedChannel.scala | 31 +++--- .../impl/GuardedSPSCUnbufferedChannel.scala | 29 +++--- .../scala/gopher/impl/PromiseChannel.scala | 54 +++++------ .../DuppedChannelsMultipleSuite.scala | 46 +++------ .../stream/JVMBasicGeneratorSuite.scala | 76 +++++++++++++++ jvm/src/test/scala/gopher/util/Debug.scala | 47 +++++++++ .../scala/gopher/ChannelClosedException.scala | 4 +- shared/src/main/scala/gopher/Gopher.scala | 9 ++ .../src/main/scala/gopher/ReadChannel.scala | 21 +++- .../src/main/scala/gopher/SelectGroup.scala | 66 +++++++++---- .../scala/gopher/impl/AppendReadChannel.scala | 2 +- .../main/scala/gopher/impl/DuppedInput.scala | 26 ++--- .../main/scala/gopher/impl/Expirable.scala | 23 ++++- .../gopher/impl/FilteredReadChannel.scala | 4 +- .../scala/gopher/impl/MappedReadChannel.scala | 4 +- .../scala/gopher/impl/OrReadChannel.scala | 15 +-- .../src/main/scala/gopher/impl/Writer.scala | 2 +- .../gopher/impl/WriterWithExpireTime.scala | 21 ++-- .../gopher/channels/DuppedChannelsSuite.scala | 2 +- .../gopher/stream/BasicGeneratorSuite.scala | 95 +++++++++++++++++++ 29 files changed, 598 insertions(+), 197 deletions(-) create mode 100644 js/src/test/scala/gopher/util/Debug.scala create mode 100644 jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala create mode 100644 jvm/src/test/scala/gopher/util/Debug.scala create mode 100644 shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala diff --git a/build.sbt b/build.sbt index 95d1fd88..3bc2f492 100644 --- a/build.sbt +++ b/build.sbt @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.2", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) @@ -34,6 +34,14 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePreviewPlugin) .jvmSettings( scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), + fork := true, + /* + javaOptions ++= Seq( + "--add-opens", + "java.base/java.lang=ALL-UNNAMED", + s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" + ) + */ ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/js/src/main/scala/gopher/JSGopher.scala b/js/src/main/scala/gopher/JSGopher.scala index 5cc73cff..e0db6454 100644 --- a/js/src/main/scala/gopher/JSGopher.scala +++ b/js/src/main/scala/gopher/JSGopher.scala @@ -1,9 +1,11 @@ package gopher -import cps._ +import cps.* import java.util.Timer -import java.util.logging._ -import scala.concurrent.duration._ +import java.util.logging.* +import scala.concurrent.ExecutionContext +import scala.concurrent.duration.* +import scala.scalajs.concurrent.* class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: @@ -28,6 +30,8 @@ class JSGopher[F[_]:CpsSchedulingMonad](cfg: JSGopherConfig) extends Gopher[F]: def log(level: Level, message: String, ex: Throwable| Null): Unit = currentLogFun.apply(level,message,ex) + def taskExecutionContext: ExecutionContext = JSExecutionContext.queue + private var currentLogFun: (Level, String, Throwable|Null )=> Unit = { (level,message,ex) => System.err.println(s"${level}:${message}"); if !(ex eq null) then @@ -46,5 +50,6 @@ object JSGopher extends GopherAPI: val timer = new Timer("gopher") + val Gopher = JSGopher diff --git a/js/src/main/scala/gopher/impl/BaseChannel.scala b/js/src/main/scala/gopher/impl/BaseChannel.scala index e40c2250..6f96d6ff 100644 --- a/js/src/main/scala/gopher/impl/BaseChannel.scala +++ b/js/src/main/scala/gopher/impl/BaseChannel.scala @@ -42,12 +42,13 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends def addWriter(writer: Writer[A]): Unit = if (closed) { - writer.capture().foreach{ (a,f) => - writer.markUsed() - submitTask( () => - f(Failure(new ChannelClosedException())) - ) - } + writer.capture() match + case Expirable.Capture.Ready((a,f)) => + writer.markUsed() + submitTask( () => + f(Failure(new ChannelClosedException())) + ) + case _ => } else { writers.enqueue(writer) process() @@ -56,13 +57,14 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends def addDoneReader(reader: Reader[Unit]): Unit = if (closed && isEmpty) { reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => reader.markUsed() submitTask( () => f(Success(()))) - case None => + case Expirable.Capture.WaitChangeComplete => // mb is blocked and will be evaluated in doneReaders.enqueue(reader) process() + case Expirable.Capture.Expired => } else { doneReaders.enqueue(reader) process() @@ -79,10 +81,10 @@ abstract class BaseChannel[F[_],A](override val gopherApi: JSGopher[F]) extends val v = queue.dequeue() if (!v.isExpired) then v.capture() match - case Some(a) => + case Expirable.Capture.Ready(a) => v.markUsed() action(a) - case None => + case _ => // do nothing. // exists case, when this is possible: wheb we close channel from // select-group callback, which is evaluated now. diff --git a/js/src/main/scala/gopher/impl/BufferedChannel.scala b/js/src/main/scala/gopher/impl/BufferedChannel.scala index cf545ffb..497c34b8 100644 --- a/js/src/main/scala/gopher/impl/BufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/BufferedChannel.scala @@ -71,15 +71,15 @@ class BufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F], bufSize: In if (!writers.isEmpty && !isFull) then val writer = writers.dequeue() writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => internalEnqueue(a) writer.markUsed() submitTask( () => f(Success(())) ) progress = true - case None => - if (!writer.isExpired) then - // impossible, we have no parallel execution - throw DeadlockDetected() + case Expirable.Capture.WaitChangeComplete => + // impossible, we have no parallel execution + throw DeadlockDetected() + case Expirable.Capture.Expired => progress diff --git a/js/src/main/scala/gopher/impl/PromiseChannel.scala b/js/src/main/scala/gopher/impl/PromiseChannel.scala index 08b0721d..83bd539d 100644 --- a/js/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/js/src/main/scala/gopher/impl/PromiseChannel.scala @@ -21,31 +21,30 @@ class PromiseChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends Base // we have only one writer. while (!writers.isEmpty && value.isEmpty) { val w = writers.dequeue() - if (!w.isExpired) then - w.capture() match - case Some((a,f)) => + w.capture() match + case Expirable.Capture.Ready((a,f)) => w.markUsed() submitTask(()=>f(Success(()))) value = Some(a) closed = true // we can't havw more than one unexpired - case None => - if (!w.isExpired) then + case Expirable.Capture.WaitChangeComplete => // impossible in js, + // (mb processNextTick()?) throw new DeadlockDetected() + case Expirable.Capture.Expired => } if (!readers.isEmpty && value.isDefined) { while(!readers.isEmpty && !readed) { val r = readers.dequeue() - if (!r.isExpired) then - r.capture() match - case Some(f) => + r.capture() match + case Expirable.Capture.Ready(f) => r.markUsed() submitTask(()=>f(Success(value.get))) readed = true - case None => - if (!r.isExpired) + case Expirable.Capture.WaitChangeComplete => throw new DeadlockDetected() + case Expirable.Capture.Expired => } //if (readed) { // processCloseDone() diff --git a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala index 9656aaec..a73dc134 100644 --- a/js/src/main/scala/gopher/impl/UnbufferedChannel.scala +++ b/js/src/main/scala/gopher/impl/UnbufferedChannel.scala @@ -25,21 +25,25 @@ class UnbufferedChannel[F[_]:CpsAsyncMonad, A](gopherApi: JSGopher[F]) extends B findWriter() match case Some(writer) => reader.capture() match - case Some(readFun) => + case Expirable.Capture.Ready(readFun) => writer.capture() match - case Some((a,writeFun)) => + case Expirable.Capture.Ready((a,writeFun)) => submitTask( () => readFun(Success(a))) submitTask( () => writeFun(Success(())) ) progress = true done = true writer.markUsed() reader.markUsed() - case None => + case _ => // impossible, because in js we have-no interleavinf, bug anyway // let's fallback reader.markFree() readers.prepend(reader) - case None => + case Expirable.Capture.WaitChangeComplete => + // impossible, but let's fallback + // TODO: prepend reader and skip event + writers.prepend(writer) + case Expirable.Capture.Expired => // impossible, but let's fallback writers.prepend(writer) case None => diff --git a/js/src/test/scala/gopher/util/Debug.scala b/js/src/test/scala/gopher/util/Debug.scala new file mode 100644 index 00000000..9569d0dc --- /dev/null +++ b/js/src/test/scala/gopher/util/Debug.scala @@ -0,0 +1,41 @@ +package gopher.util + +import java.util.logging.{Level => LogLevel} + +object Debug { + + type InMemoryLog = java.util.concurrent.ConcurrentLinkedQueue[(Long, String, Throwable)] + + def inMemoryLogFun(inMemoryLog: InMemoryLog): (LogLevel, String, Throwable|Null) => Unit = + (level,msg, ex) => inMemoryLog.add((Thread.currentThread().getId(), msg,ex)) + + def showInMemoryLog(inMemoryLog: InMemoryLog): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + +} \ No newline at end of file diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index e6b0c7a0..eac8bab6 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -9,6 +9,7 @@ import java.util.concurrent.ForkJoinPool import java.util.concurrent.atomic.AtomicReference import java.util.Timer import java.util.logging._ +import scala.concurrent.ExecutionContext import scala.concurrent.duration._ @@ -18,7 +19,7 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false) = if autoClose then - PromiseChannel[F,A](this, taskExecutor) + PromiseChannel[F,A](this, cfg.taskExecutor) else if (bufSize == 0) GuardedSPSCUnbufferedChannel[F,A](this, cfg.controlExecutor,cfg.taskExecutor) @@ -35,7 +36,7 @@ class JVMGopher[F[_]:CpsSchedulingMonad](cfg: JVMGopherConfig) extends Gopher[F] def log(level: Level, message: String, ex: Throwable| Null): Unit = currentLogFun.get().apply(level,message,ex) - def taskExecutor = cfg.taskExecutor + lazy val taskExecutionContext = ExecutionContext.fromExecutor(cfg.taskExecutor) def scheduledExecutor = JVMGopher.scheduledExecutor diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala index 9265d6b8..dc6ed404 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBaseChannel.scala @@ -38,22 +38,30 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA def addReader(reader: Reader[A]): Unit = if (reader.canExpire) then - readers.removeIf( _.isExpired ) + readers.removeIf( _.isExpired ) + // if (publishedClosed.get()) then + // tryClosedRead() + // else readers.add(reader) controlExecutor.submit(stepRunnable) def addWriter(writer: Writer[A]): Unit = if (writer.canExpire) then - writers.removeIf( _.isExpired ) + writers.removeIf( _.isExpired ) if (publishedClosed.get()) then - closeWriter(writer) - else - writers.add(writer) - controlExecutor.submit(stepRunnable) + closeWriter(writer) + else + writers.add(writer) + controlExecutor.submit(stepRunnable) def addDoneReader(reader: Reader[Unit]): Unit = - doneReaders.add(reader) - controlExecutor.submit(stepRunnable) + if (reader.canExpire) + doneReaders.removeIf( _.isExpired ) + if (publishedClosed.get()) then + closeDoneReader(reader) + else + doneReaders.add(reader) + controlExecutor.submit(stepRunnable) def close(): Unit = publishedClosed.set(true) @@ -96,38 +104,55 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA // impossible, let'a r false - + // precondition: writers are empty protected def processReadClose(): Boolean = + require(writers.isEmpty) var progress = false while(!readers.isEmpty) { val r = readers.poll() if (!(r eq null) && !r.isExpired) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => progress = true - taskExecutor.execute(() => f(Failure(new ChannelClosedException())) ) + //println("sending signal in processReadClose"); + //val prevEx = new RuntimeException("prev") + taskExecutor.execute(() => { + //try + //println(s"calling $f, channel = ${GuardedSPSCBaseChannel.this}") + // prevEx.printStackTrace() + val debugInfo = s"channel=${this}, writersEmpty=${writers.isEmpty}, readersEmpty=${readers.isEmpty}, r=$r, f=$f" + f(Failure(new ChannelClosedException(debugInfo))) + //catch + // case ex: Exception => + // println(s"exception in close-reader, channel=${GuardedSPSCBaseChannel.this}, f=$f, r=$r") + // throw ex + }) r.markUsed() - case None => - progress = true + case Expirable.Capture.WaitChangeComplete => progressWaitReader(r) + case Expirable.Capture.Expired => + progress = true } progress + // TODO: remove. If we have writers in queue, protected def processWriteClose(): Boolean = var progress = false while(!writers.isEmpty) { val w = writers.poll() if !(w eq null) && !w.isExpired then w.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => progress = true taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) w.markUsed() - case None => - progress = true + case Expirable.Capture.WaitChangeComplete => progressWaitWriter(w) + case Expirable.Capture.Expired => + progress = true } progress + protected def processDoneClose(): Boolean = { var progress = false @@ -135,28 +160,45 @@ abstract class GuardedSPSCBaseChannel[F[_]:CpsAsyncMonad,A](override val gopherA val r = doneReaders.poll() if !(r eq null) && !r.isExpired then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => progress = true taskExecutor.execute(() => f(Success(()))) - r.markUsed() - case None => + r.markUsed() + case Expirable.Capture.WaitChangeComplete => progressWaitDoneReader(r) + case Expirable.Capture.Expired => + progress = true } progress } + protected def closeDoneReader(r: Reader[Unit]): Unit = { + while + r.capture() match + case Expirable.Capture.Ready(f) => + taskExecutor.execute(()=>f(Success(()))) + r.markUsed() + false + case Expirable.Capture.WaitChangeComplete => + progressWaitDoneReader(r) + true + case Expirable.Capture.Expired => + false + do () + } protected def closeWriter(w: Writer[A]): Unit = { var done = false while (!done && !w.isExpired) w.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => taskExecutor.execute(() => f(Failure(new ChannelClosedException)) ) w.markUsed() done = true - case None => - if (!w.isExpired) then - Thread.onSpinWait() + case Expirable.Capture.WaitChangeComplete => + Thread.onSpinWait() + case Expirable.Capture.Expired => + done = true } diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index e9bfa209..96b2d176 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -85,11 +85,12 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con else if isClosed then progress |= processDoneClose() - progress |= processReadClose() - if (!state.isFull() && !isClosed) then + if (writers.isEmpty) then + progress |= processReadClose() + if (!state.isFull()) then progress |= processWriteStep() - if (isClosed) - progress |= processWriteClose() + //if (isClosed) + // progress |= processWriteClose() if (!progress) { state.publish() if (! checkLeaveStep()) { @@ -111,16 +112,17 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con val reader = readers.poll() if !(reader eq null) && !reader.isExpired then reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => // try/cath arround f is a reader reponsability taskExecutor.execute(() => f(Success(a))) reader.markUsed() state.finishRead() progress = true done = true - case None => - if !reader.isExpired then - nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) + case Expirable.Capture.WaitChangeComplete => + nonExpiredBusyReads = nonExpiredBusyReads.enqueue(reader) + case Expirable.Capture.Expired => + progress = true } while(nonExpiredBusyReads.nonEmpty) { // not in this thread, but progress. @@ -140,10 +142,12 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con val writer = writers.poll() if !(writer eq null ) && ! writer.isExpired then writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => done = true if (state.write(a)) then - taskExecutor.execute(() => f(Success(()))) + taskExecutor.execute( + () => f(Success(())) + ) progress = true writer.markUsed() else @@ -152,9 +156,10 @@ taskExecutor: ExecutorService) extends GuardedSPSCBaseChannel[F,A](gopherApi,con //log("impossibe,unsuccesfull write after !isFull") writer.markFree() writers.addFirst(writer) - case None => - if (!writer.isExpired) - nonExpiredBusyWriters = nonExpiredBusyWriters.enqueue(writer) + case Expirable.Capture.WaitChangeComplete => + nonExpiredBusyWriters = nonExpiredBusyWriters.enqueue(writer) + case Expirable.Capture.Expired => + progress = true } while(nonExpiredBusyWriters.nonEmpty) { progress = true diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala index 56998859..8d0f4eeb 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCUnbufferedChannel.scala @@ -33,38 +33,39 @@ class GuardedSPSCUnbufferedChannel[F[_]:CpsAsyncMonad,A]( if (!(writer eq null) && !writer.isExpired) then // now we have reader and writer reader.capture() match - case Some(readFun) => + case Expirable.Capture.Ready(readFun) => + progress = true writer.capture() match - case Some((a,writeFun)) => + case Expirable.Capture.Ready((a,writeFun)) => // great, now we have all taskExecutor.execute(()=>readFun(Success(a))) taskExecutor.execute(()=>writeFun(Success(()))) - progress = true reader.markUsed() writer.markUsed() writersLoopDone = true - case None => - // reader same, other writer + case Expirable.Capture.WaitChangeComplete => reader.markFree() - progress = true // return when progressWaitWriter(writer) - case None => + case Expirable.Capture.Expired => + reader.markFree() + case Expirable.Capture.WaitChangeComplete => writers.addFirst(writer) writersLoopDone = true - progress = true + progress = true // TODO: ??? progressWaitReader(reader) + case Expirable.Capture.Expired => + writers.addFirst(writer) + writersLoopDone = true + progress = true } - if !writersLoopDone then - // we have reader, should return one back if we want to start again - // TODO: write test for this case - readers.addFirst(reader) } if (isClosed && (readers.isEmpty || writers.isEmpty) ) then - progress |= processWriteClose() + // progress |= processWriteClose() while(! doneReaders.isEmpty) { progress |= processDoneClose() } - progress |= processReadClose() + if (writers.isEmpty) + progress |= processReadClose() if (!progress) then if !checkLeaveStep() then progress = true diff --git a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala index 4f2783ba..f88a6b00 100644 --- a/jvm/src/main/scala/gopher/impl/PromiseChannel.scala +++ b/jvm/src/main/scala/gopher/impl/PromiseChannel.scala @@ -4,6 +4,7 @@ import cps._ import gopher._ import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ExecutorService +import java.util.concurrent.Executor import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicBoolean @@ -15,7 +16,7 @@ import scala.util.Failure /** * Channel is closed immediatly after successfull write. **/ - class PromiseChannel[F[_],A](override val gopherApi: JVMGopher[F], taskExecutor: ExecutorService) extends Channel[F,A,A]: + class PromiseChannel[F[_],A](override val gopherApi: JVMGopher[F], taskExecutor: Executor) extends Channel[F,A,A]: protected val readers = new ConcurrentLinkedDeque[Reader[A]]() protected val doneReaders = new ConcurrentLinkedDeque[Reader[Unit]]() @@ -31,7 +32,7 @@ import scala.util.Failure var done = false while(!done && !writer.isExpired) writer.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => val ar: AnyRef = a.asInstanceOf[AnyRef] // if (ref.compareAndSet(null,ar) && !closed.get() ) then closed.set(true) @@ -44,9 +45,9 @@ import scala.util.Failure taskExecutor.execute(() => f(Failure(new ChannelClosedException()))) writer.markUsed() done = true - case None => - if (!writer.isExpired) then + case Expirable.Capture.WaitChangeComplete => Thread.onSpinWait() + case Expirable.Capture.Expired => def addDoneReader(reader: Reader[Unit]): Unit = @@ -58,13 +59,13 @@ import scala.util.Failure var done = false while(!done & !reader.isExpired) { reader.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => reader.markUsed() taskExecutor.execute(()=>f(Success(()))) done = true - case None => - if (!reader.isExpired) - Thread.onSpinWait() + case Expirable.Capture.WaitChangeComplete => + Thread.onSpinWait() + case Expirable.Capture.Expired => } @@ -87,7 +88,7 @@ import scala.util.Failure if ! (r eq null) then while (!done && !r.isExpired) { r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => done = true if (readed.compareAndSet(false,true)) then r.markUsed() @@ -101,12 +102,11 @@ import scala.util.Failure else r.markFree() readers.addLast(r) // called later after done - case None => - if (!r.isExpired) { - if (readers.isEmpty) + case Expirable.Capture.WaitChangeComplete => + if (readers.isEmpty) then Thread.onSpinWait() readers.addLast(r) - } + case Expirable.Capture.Expired => } } else if (closed.get()) then @@ -115,29 +115,29 @@ import scala.util.Failure def closeAll(): Unit = while(!doneReaders.isEmpty) { val r = doneReaders.poll() - if !((r eq null) || r.isExpired) then + if !(r eq null) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => r.markUsed() taskExecutor.execute(()=>f(Success(()))) - case None => - if (!r.isExpired) then - if (doneReaders.isEmpty) then - Thread.onSpinWait() - doneReaders.addLast(r) + case Expirable.Capture.WaitChangeComplete => + if (doneReaders.isEmpty) then + Thread.onSpinWait() + doneReaders.addLast(r) + case Expirable.Capture.Expired => } while(!readers.isEmpty) { val r = readers.poll() - if (!(r eq null) && !r.isExpired) then + if !(r eq null) then r.capture() match - case Some(f) => + case Expirable.Capture.Ready(f) => r.markUsed() taskExecutor.execute(() => f(Failure(new ChannelClosedException))) - case None => - if (!r.isExpired) then - if (readers.isEmpty) then - Thread.onSpinWait() - readers.addLast(r) + case Expirable.Capture.WaitChangeComplete => + if (readers.isEmpty) then + Thread.onSpinWait() + readers.addLast(r) + case Expirable.Capture.Expired => } diff --git a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala index b598437f..b15f20ff 100644 --- a/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala +++ b/jvm/src/test/scala/gopher/channels/DuppedChannelsMultipleSuite.scala @@ -10,24 +10,32 @@ import scala.concurrent.duration._ import scala.language.postfixOps import scala.util._ +import gopher.util.Debug + +import java.util.logging.{Level => LogLevel} + class DuppedChannelsMultipleSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given gopherApi: Gopher[Future] = SharedGopherAPI.apply[Future]() - val inMemoryLog = new java.util.concurrent.ConcurrentLinkedQueue[(Int, Long, String, Throwable)]() + val inMemoryLog = new Debug.InMemoryLog() test("on closing of main stream dupped outputs also closed N times.") { val N = 1000 var logIndex = 0 - gopherApi.setLogFun( (level,msg, ex) => inMemoryLog.add((logIndex, Thread.currentThread().getId(), msg,ex)) ) + gopherApi.setLogFun(Debug.inMemoryLogFun(inMemoryLog)) for(i <- 1 to N) { + inMemoryLog.clear() logIndex = i val ch = makeChannel[Int](1) + gopherApi.log(LogLevel.FINE, s"created origin ch=${ch}") val (in1, in2) = ch.dup() val f1 = async{ + gopherApi.log(LogLevel.FINE, s"before ch.write(1), ch=${ch}") ch.write(1) + gopherApi.log(LogLevel.FINE, s"before ch.close, ch=${ch}") ch.close() } val f = for{ fx <- f1 @@ -44,44 +52,16 @@ class DuppedChannelsMultipleSuite extends FunSuite { try { val r = Await.result(f, 30 seconds); }catch{ - case ex: TimeoutException => - showTraces(20) + case ex: Throwable => //: TimeoutException => + Debug.showTraces(20) println("---") - showInMemoryLog() + Debug.showInMemoryLog(inMemoryLog) throw ex } } } - def showTraces(maxTracesToShow: Int): Unit = { - val traces = Thread.getAllStackTraces(); - val it = traces.entrySet().iterator() - while(it.hasNext()) { - val e = it.next(); - println(e.getKey()); - val elements = e.getValue() - var sti = 0 - var wasPark = false - while(sti < elements.length && sti < maxTracesToShow && !wasPark) { - val st = elements(sti) - println(" "*10 + st) - sti = sti + 1; - wasPark = (st.getMethodName == "park") - } - } - } - - def showInMemoryLog(): Unit = { - while(!inMemoryLog.isEmpty) { - val r = inMemoryLog.poll() - if (r != null) { - println(r) - } - } - } - - } diff --git a/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala b/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala new file mode 100644 index 00000000..0e6ed3ef --- /dev/null +++ b/jvm/src/test/scala/gopher/stream/JVMBasicGeneratorSuite.scala @@ -0,0 +1,76 @@ +package gopher.stream + +import scala.concurrent.* +import scala.concurrent.duration.* +import scala.concurrent.ExecutionContext.Implicits.global + +import cps.* +import cps.monads.given + +import gopher.* +import gopher.util.Debug +import java.util.logging.{Level => LogLevel} + + +import munit.* + + +class JVMBasicGeneratorSuite extends FunSuite { + + val N = 10000 + + given Gopher[Future] = SharedGopherAPI[Future]() + + val inMemoryLog = new Debug.InMemoryLog() + + + summon[Gopher[Future]].setLogFun( Debug.inMemoryLogFun(inMemoryLog) ) + + + test("M small loop in gopher ReadChannel") { + + val M = 1000 + val N = 100 + + val folds: Seq[Future[Int]] = for(k <- 1 to M) yield { + val channel = asyncStream[ReadChannel[Future,Int]] { out => + var last = 0 + for(i <- 1 to N) { + out.emit(i) + last = i + //println("emitted: "+i) + //summon[Gopher[Future]].log(LogLevel.FINE, s"emitted $i in $k") + } + summon[Gopher[Future]].log(LogLevel.FINE, s"last $last in $k") + } + async[Future]{ + channel.fold(0)(_ + _) + } + } + + val expected = (1 to N).sum + + + val f = folds.foldLeft(Future.successful(())){ (s,e) => + s.flatMap{ r => + e.map{ x => + assert(x == expected) + } } + } + + try { + val r = Await.result(f, 30.seconds); + }catch{ + case ex: Throwable => //: TimeoutException => + Debug.showTraces(20) + println("---") + Debug.showInMemoryLog(inMemoryLog) + throw ex + } + + + } + + + +} \ No newline at end of file diff --git a/jvm/src/test/scala/gopher/util/Debug.scala b/jvm/src/test/scala/gopher/util/Debug.scala new file mode 100644 index 00000000..0950f80a --- /dev/null +++ b/jvm/src/test/scala/gopher/util/Debug.scala @@ -0,0 +1,47 @@ +package gopher.util + + +import java.util.logging.{Level => LogLevel} + + +object Debug { + + export java.util.logging.Level as LogLevel + + type InMemoryLog = java.util.concurrent.ConcurrentLinkedQueue[(Long, String, Throwable)] + + def inMemoryLogFun(inMemoryLog: InMemoryLog): (LogLevel, String, Throwable|Null) => Unit = + (level,msg, ex) => inMemoryLog.add((Thread.currentThread().getId(), msg,ex)) + + def showInMemoryLog(inMemoryLog: InMemoryLog): Unit = { + while(!inMemoryLog.isEmpty) { + val r = inMemoryLog.poll() + if (r != null) { + println(r) + } + } + } + + + def showTraces(maxTracesToShow: Int): Unit = { + val traces = Thread.getAllStackTraces(); + val it = traces.entrySet().iterator() + while(it.hasNext()) { + val e = it.next(); + println(e.getKey()); + val elements = e.getValue() + var sti = 0 + var wasPark = false + while(sti < elements.length && sti < maxTracesToShow && !wasPark) { + val st = elements(sti) + println(" "*10 + st) + sti = sti + 1; + wasPark = (st.getMethodName == "park") + } + } + } + + + + +} \ No newline at end of file diff --git a/shared/src/main/scala/gopher/ChannelClosedException.scala b/shared/src/main/scala/gopher/ChannelClosedException.scala index b59b7aee..3ffbda63 100644 --- a/shared/src/main/scala/gopher/ChannelClosedException.scala +++ b/shared/src/main/scala/gopher/ChannelClosedException.scala @@ -1,3 +1,5 @@ package gopher -class ChannelClosedException extends RuntimeException("channel is closed") +class ChannelClosedException( + debugInfo: String = "" +) extends RuntimeException(s"channel is closed. ${debugInfo}") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index b30034e1..3e023bd8 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -1,12 +1,18 @@ package gopher import cps._ +import scala.concurrent.ExecutionContext import scala.concurrent.duration.Duration import scala.util._ import java.util.logging.{Level => LogLevel} +import java.util.concurrent.Executor +/** + * core of GopherAPI. + *Given instance of Gopher[F] need for using most of Gopher operations. + **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] @@ -30,6 +36,8 @@ trait Gopher[F[_]:CpsSchedulingMonad]: def log(level: LogLevel, message: String): Unit = log(level,message, null) + def taskExecutionContext: ExecutionContext + protected[gopher] def logImpossible(ex: Throwable): Unit = log(LogLevel.WARNING, "impossible", ex) @@ -42,6 +50,7 @@ trait Gopher[F[_]:CpsSchedulingMonad]: } + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize, autoClose) diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index b00af8b9..90b0b51e 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -6,6 +6,7 @@ import scala.util.Try import scala.util.Success import scala.util.Failure import scala.util.control.NonFatal +import scala.concurrent.ExecutionContext import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} @@ -197,7 +198,7 @@ trait ReadChannel[F[_], A]: def canExpire: Boolean = false def isExpired: Boolean = false - def capture(): Option[Try[A]=>Unit] = Some(f) + def capture(): Expirable.Capture[Try[A]=>Unit] = Expirable.Capture.Ready(f) def markUsed(): Unit = () def markFree(): Unit = () @@ -264,7 +265,7 @@ object ReadChannel: def unfold[S,F[_],A](s:S)(f:S => Option[(A,S)])(using Gopher[F]): ReadChannel[F,A] = unfoldAsync[S,F,A](s)( state => summon[Gopher[F]].asyncMonad.tryPure(f(state)) ) - def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A]= + def unfoldAsync[S,F[_],A](s:S)(f:S => F[Option[(A,S)]])(using Gopher[F]): ReadChannel[F,A] = given asyncMonad: CpsSchedulingMonad[F] = summon[Gopher[F]].asyncMonad val retval = makeChannel[Try[A]]() summon[Gopher[F]].spawnAndLogFail(async{ @@ -281,8 +282,10 @@ object ReadChannel: } catch case NonFatal(ex) => + summon[Gopher[F]].log(LogLevel.FINE, s"exception (ch: $retval)", ex) retval.write(Failure(ex)) finally + summon[Gopher[F]].log(LogLevel.FINE, s"closing $retval") retval.close(); }) retval.map{ @@ -291,6 +294,20 @@ object ReadChannel: throw ex } + + + import cps.stream._ + + given emitAbsorber[F[_]: CpsSchedulingMonad,T](using gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,T]( + using gopherApi.asyncMonad, gopherApi.taskExecutionContext) with + + override type Element = T + + def unfold[S](s0:S)(f: S => F[Option[(T,S)]]): ReadChannel[F,T] = + val r: ReadChannel[F,T] = unfoldAsync(s0)(f) + r + + end ReadChannel diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 0404c847..9f3b7306 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -175,16 +175,34 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: type Element = A type State = S - override def capture(): Option[Try[A]=>Unit] = + val ready = Expirable.Capture.Ready[Try[A]=>Unit](v => { + timeoutScheduled.foreach(_.cancel()) + api.spawnAndLogFail(m.mapTry(action(v))(x => call(x))) + }) + + override def capture(): Expirable.Capture[Try[A]=>Unit] = + // fast path if waitState.compareAndSet(0,1) then - Some(v => { - timeoutScheduled.foreach(_.cancel()) - api.spawnAndLogFail( - m.mapTry(action(v))(x => call(x)) - ) - }) + ready else - None + var retval: Expirable.Capture[Try[A]=>Unit] = Expirable.Capture.Expired + while { + waitState.get() match + case 2 => retval = Expirable.Capture.Expired + false + case 1 => retval = Expirable.Capture.WaitChangeComplete + false + case 0 => // was just freed + if (waitState.compareAndSet(0,1)) then + retval = ready + false + else + true + case _ => // impossible. + throw new IllegalStateException("Imposible state of busy flag") + } do () + retval + @@ -195,16 +213,32 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: type Element = A type State = S - override def capture(): Option[(A,Try[Unit]=>Unit)] = + val ready: Expirable.Capture.Ready[(A,Try[Unit]=>Unit)] = + Expirable.Capture.Ready((element, + (v:Try[Unit]) => { + timeoutScheduled.foreach(_.cancel()) + api.spawnAndLogFail(m.mapTry(action(v))(x=>call(x))) + } + )) + + override def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = if waitState.compareAndSet(0,1) then - Some((element, (v:Try[Unit]) => { - timeoutScheduled.foreach(_.cancel()) - api.spawnAndLogFail( - m.mapTry(action(v))(x=>call(x)) - )} - )) + ready else - None + var retval: Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Expired + while{ + waitState.get() match + case 2 => false + case 1 => retval = Expirable.Capture.WaitChangeComplete + false + case 0 => + if (waitState.compareAndSet(0,1)) then + retval = ready + false + else + true + } do () + retval case class TimeoutRecord(duration: FiniteDuration, diff --git a/shared/src/main/scala/gopher/impl/AppendReadChannel.scala b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala index 8a01bd26..e58cb812 100644 --- a/shared/src/main/scala/gopher/impl/AppendReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/AppendReadChannel.scala @@ -33,7 +33,7 @@ case class AppendReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) e def isExpired: Boolean = nested.isExpired - def capture():Option[Try[A]=>Unit] = + def capture():Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ readFun => { case r@Success(a) => if (inUsage.get()) then diff --git a/shared/src/main/scala/gopher/impl/DuppedInput.scala b/shared/src/main/scala/gopher/impl/DuppedInput.scala index 2485c76b..04e1632c 100644 --- a/shared/src/main/scala/gopher/impl/DuppedInput.scala +++ b/shared/src/main/scala/gopher/impl/DuppedInput.scala @@ -22,16 +22,20 @@ class DuppedInput[F[_],A](origin:ReadChannel[F,A], bufSize: Int=1)(using api:Gop given CpsSchedulingMonad[F] = api.asyncMonad - val runner = SelectLoop[F](api). - onRead(origin.done){ _ => - sink1.close() - sink2.close() - false - }.onReadAsync(origin){a => async{ - val f1 = sink1.write(a) - val f2 = sink2.write(a) - true - }}.runAsync() - api.spawnAndLogFail(runner) + val runner = async{ + while + origin.optRead() match + case Some(a) => + sink1.write(a) + sink2.write(a) + true + case None => + false + do () + sink1.close() + sink2.close() + } + + api.spawnAndLogFail(runner) } diff --git a/shared/src/main/scala/gopher/impl/Expirable.scala b/shared/src/main/scala/gopher/impl/Expirable.scala index 6a6e626c..0a5a3d08 100644 --- a/shared/src/main/scala/gopher/impl/Expirable.scala +++ b/shared/src/main/scala/gopher/impl/Expirable.scala @@ -26,7 +26,7 @@ trait Expirable[A]: /** * capture object, and after this we can or use one (markUsed will be called) or abandon (markFree) **/ - def capture(): Option[A] + def capture(): Expirable.Capture[A] /** * Called when we submitt to task executor readFunction and now is safe to make exprire all other readers/writers in the @@ -35,7 +35,26 @@ trait Expirable[A]: def markUsed(): Unit /** - * Called when it was a race condition and we can't use captured function. + * Called when we can't use captured function (i.e. get function but ). **/ def markFree(): Unit + + +object Expirable: + + enum Capture[+A]: + case Ready(value: A) + case WaitChangeComplete + case Expired + + def map[B](f: A=>B): Capture[B] = + this match + case Ready(a) => Ready(f(a)) + case WaitChangeComplete => WaitChangeComplete + case Expired => Expired + + def foreach(f: A=>Unit): Unit = + this match + case Ready(a) => f(a) + case _ => \ No newline at end of file diff --git a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala index 4c0a221f..916debaa 100644 --- a/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/FilteredReadChannel.scala @@ -27,7 +27,7 @@ class FilteredReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>Boolean) ext fun(Failure(ex)) } - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } @@ -82,7 +82,7 @@ class FilteredAsyncReadChannel[F[_],A](internal: ReadChannel[F,A], p: A=>F[Boole fun(Failure(ex)) } - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } diff --git a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala index 8641d663..8fe111c4 100644 --- a/shared/src/main/scala/gopher/impl/MappedReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/MappedReadChannel.scala @@ -22,7 +22,7 @@ class MappedReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> B) extends } //TODO: think, are we want to pass error to the next level ? - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } @@ -67,7 +67,7 @@ class MappedAsyncReadChannel[F[_],A, B](internal: ReadChannel[F,A], f: A=> F[B]) } //TODO: think, are we want to pass error to the next level ? - override def capture(): Option[Try[A]=>Unit] = + override def capture(): Expirable.Capture[Try[A]=>Unit] = nested.capture().map{ fun => wrappedFun(fun) } diff --git a/shared/src/main/scala/gopher/impl/OrReadChannel.scala b/shared/src/main/scala/gopher/impl/OrReadChannel.scala index f3ed1f8d..3d6367c6 100644 --- a/shared/src/main/scala/gopher/impl/OrReadChannel.scala +++ b/shared/src/main/scala/gopher/impl/OrReadChannel.scala @@ -60,14 +60,17 @@ case class OrReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) exten } } - def capture(fromChannel: ReadChannel[F,A]): Option[Try[B]=>Unit] = + def capture(fromChannel: ReadChannel[F,A]): Expirable.Capture[Try[B]=>Unit] = if inUse.compareAndSet(null,fromChannel) then nested.capture() match - case Some(readFun) => Some(intercept(readFun)) - case None => inUse.set(null) - None + case Expirable.Capture.Ready(readFun) => Expirable.Capture.Ready(intercept(readFun)) + case Expirable.Capture.WaitChangeComplete => + inUse.set(null) + Expirable.Capture.WaitChangeComplete + case Expirable.Capture.Expired => inUse.set(null) + Expirable.Capture.Expired else - None + Expirable.Capture.WaitChangeComplete def markFree(fromChannel: ReadChannel[F,A]): Unit = if(inUse.get() eq fromChannel) then @@ -103,7 +106,7 @@ case class OrReadChannel[F[_],A](x: ReadChannel[F,A], y: ReadChannel[F,A]) exten class WrappedReader[B](common: CommonBase[B], owner: ReadChannel[F,A]) extends Reader[B] { - def capture(): Option[Try[B]=>Unit] = + def capture(): Expirable.Capture[Try[B]=>Unit] = common.capture(owner) def canExpire: Boolean = common.canExpire diff --git a/shared/src/main/scala/gopher/impl/Writer.scala b/shared/src/main/scala/gopher/impl/Writer.scala index 13e162c0..e8d4b8b1 100644 --- a/shared/src/main/scala/gopher/impl/Writer.scala +++ b/shared/src/main/scala/gopher/impl/Writer.scala @@ -11,7 +11,7 @@ class SimpleWriter[A](a:A, f: Try[Unit]=>Unit) extends Writer[A]: def isExpired: Boolean = false - def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Ready((a,f)) def markUsed(): Unit = () diff --git a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala index e3950895..c01341e1 100644 --- a/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala +++ b/shared/src/main/scala/gopher/impl/WriterWithExpireTime.scala @@ -15,7 +15,7 @@ class SimpleWriterWithExpireTime[A](a:A, f: Try[Unit] => Unit, expireTimeMillis: //TODO: way to mock current time System.currentTimeMillis >= expireTimeMillis - def capture(): Option[(A,Try[Unit]=>Unit)] = Some((a,f)) + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = Expirable.Capture.Ready((a,f)) def markUsed(): Unit = () @@ -29,8 +29,8 @@ class NesteWriterWithExpireTime[A](nested: Writer[A], expireTimeMillis: Long) ex def isExpired: Boolean = (System.currentTimeMillis >= expireTimeMillis) || nested.isExpired - def capture(): Option[(A,Try[Unit]=>Unit)] = - if (isExpired) None else nested.capture() + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = + if (isExpired) Expirable.Capture.Expired else nested.capture() def markUsed(): Unit = nested.markUsed() @@ -48,8 +48,11 @@ class NestedWriterWithExpireTimeThrowing[F[_],A](nested: Writer[A], expireTimeMi def isExpired: Boolean = (gopherApi.time.now().toMillis >= expireTimeMillis) || nested.isExpired - def capture(): Option[(A,Try[Unit]=>Unit)] = - nested.capture() + def capture(): Expirable.Capture[(A,Try[Unit]=>Unit)] = + if (gopherApi.time.now().toMillis > expireTimeMillis) then + Expirable.Capture.Expired + else + nested.capture() def markUsed(): Unit = scheduledThrow.cancel() @@ -63,14 +66,18 @@ class NestedWriterWithExpireTimeThrowing[F[_],A](nested: Writer[A], expireTimeMi if (gopherApi.time.now().toMillis > expireTimeMillis) then if (!nested.isExpired) then nested.capture() match - case Some((a,f)) => + case Expirable.Capture.Ready((a,f)) => nested.markUsed() try f(Failure(new TimeoutException())) catch case ex: Throwable => ex.printStackTrace() - case None => + case Expirable.Capture.WaitChangeComplete => + gopherApi.time.schedule( + () => checkExpire(), + FiniteDuration(100, TimeUnit.MILLISECONDS) ) + case Expirable.Capture.Expired => // none, will be colled after markFree is needed. diff --git a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala index 93c392a0..9656e724 100644 --- a/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala +++ b/shared/src/test/scala/gopher/channels/DuppedChannelsSuite.scala @@ -58,7 +58,7 @@ class DuppedChannelsSuite extends FunSuite { x <- in1.aread() r <- in1.aread().transformWith { case Success(u) => - Future failed new IllegalStateException("Mist be closed") + Future failed new IllegalStateException("Must be closed") case Failure(u) => Future successful (assert(x == 1)) } diff --git a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala new file mode 100644 index 00000000..ec1c7115 --- /dev/null +++ b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala @@ -0,0 +1,95 @@ +package gopher.stream + +import scala.concurrent.* +import scala.concurrent.ExecutionContext.Implicits.global + +import cps.* +import cps.monads.given + +import gopher.* +import gopher.util.Debug + +import munit.* + + +class BasicGeneratorSuite extends FunSuite { + + val N = 10000 + + given Gopher[Future] = SharedGopherAPI[Future]() + + val inMemoryLog = new Debug.InMemoryLog() + + + summon[Gopher[Future]].setLogFun( Debug.inMemoryLogFun(inMemoryLog) ) + + test("simple loop in gopher ReadChannel") { + + val channel = asyncStream[ReadChannel[Future,Int]] { out => + for(i <- 1 to N) { + out.emit(i) + } + } + + + async[Future] { + val r = channel.fold(0)(_ + _) + assert(r == (1 to N).sum) + } + + } + + test("M small loop in gopher ReadChannel") { + + val M = 1000 + val N = 100 + + val folds: Seq[Future[Int]] = for(i <- 1 to M) yield { + val channel = asyncStream[ReadChannel[Future,Int]] { out => + for(i <- 1 to N) { + out.emit(i) + //println("emitted: "+i) + } + } + async[Future]{ + channel.fold(0)(_ + _) + } + } + + val expected = (1 to N).sum + + + folds.foldLeft(Future.successful(())){ (s,e) => + s.flatMap{ r => + e.map{ x => + assert(x == expected) + } } + } + + + + + } + + + /* + test("exception should break loop in gopher generator") { + val channel = asyncStream[ReadChannel[Future, Int]] { out => + for(i <- 1 to N) { + if (i == N/2) then + throw new RuntimeException("bye") + out.emit(i) + } + } + + val res = async[Future] { + val r = channel.fold(0)(_ + _) + assert(r == (1 to N).sum) + } + + res.failed.map(ex => assert(ex.getMessage()=="bye")) + + }*/ + + +} \ No newline at end of file From 90cdad3b03ab2372a48a21bb43a1f46485050fd5 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:53:26 +0300 Subject: [PATCH 38/92] added mima plugin, updated depemdencies --- build.sbt | 5 ++-- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/Channel.scala | 3 +++ .../src/main/scala/gopher/ReadChannel.scala | 26 +++++++++++++++++-- .../src/main/scala/gopher/WriteChannel.scala | 1 + .../gopher/stream/BasicGeneratorSuite.scala | 9 +++---- 6 files changed, 35 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index 3bc2f492..b5b5dccf 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.0.7-SNAPSHOT" +ThisBuild/version := "2.1.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) @@ -42,6 +42,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.0.6") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index a5c778e6..e0e123cc 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") - +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.0") diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index a281add7..817765e2 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -47,6 +47,9 @@ object Channel: case class FRead[F[_],A](a:A, ch: F[A]) case class Write[F[_],A](a: A, ch: WriteChannel[F,A]) + import cps.stream._ + + end Channel diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 90b0b51e..53099e88 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -12,13 +12,17 @@ import scala.concurrent.duration.Duration import java.util.logging.{Level => LogLevel} /** - * ReadChannel: Interface providing reading API. + * ReadChannel: Interface providing asynchronous reading API. * **/ trait ReadChannel[F[_], A]: thisReadChannel => + /** + * Special type which is used in select statement. + *@see [gopher.Select] + **/ type read = A def gopherApi: Gopher[F] @@ -45,7 +49,8 @@ trait ReadChannel[F[_], A]: /** * blocked read: if currently not element available - wait for one. - * Can be used only inside async block + * Can be used only inside async block. + * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ transparent inline def read(): A = await(aread())(using rAsyncMonad) @@ -74,9 +79,18 @@ trait ReadChannel[F[_], A]: b.result() } + /** + * take first `n` elements. + * should be called inside async block. + **/ transparent inline def take(n: Int): IndexedSeq[A] = await(atake(n))(using rAsyncMonad) + /** + * read value and return future with + * - Some(value) if value is available to read + * - None if stream is closed. + **/ def aOptRead(): F[Option[A]] = asyncMonad.adoptCallbackStyle( f => addReader(SimpleReader{ x => x match @@ -86,6 +100,13 @@ trait ReadChannel[F[_], A]: }) ) + /** + * read value and return + * - Some(value) if value is available to read + * - None if stream is closed. + * + * should be called inside async block. + **/ transparent inline def optRead(): Option[A] = await(aOptRead())(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = @@ -112,6 +133,7 @@ trait ReadChannel[F[_], A]: transparent inline def foreach(inline f: A=>Unit): Unit = await(aforeach(f))(using rAsyncMonad) + def map[B](f: A=>B): ReadChannel[F,B] = new MappedReadChannel(this, f) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index b222b270..a0e25cd6 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -7,6 +7,7 @@ import scala.annotation.targetName import scala.concurrent.duration.FiniteDuration import scala.util.Try + trait WriteChannel[F[_], A]: type write = A diff --git a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala index ec1c7115..5e49b682 100644 --- a/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala +++ b/shared/src/test/scala/gopher/stream/BasicGeneratorSuite.scala @@ -66,13 +66,9 @@ class BasicGeneratorSuite extends FunSuite { } } } - - - + } - - /* test("exception should break loop in gopher generator") { val channel = asyncStream[ReadChannel[Future, Int]] { out => for(i <- 1 to N) { @@ -89,7 +85,8 @@ class BasicGeneratorSuite extends FunSuite { res.failed.map(ex => assert(ex.getMessage()=="bye")) - }*/ + } + } \ No newline at end of file From 371f1cf9c9ca3317edae57a75df2971d7157edf4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:58:15 +0300 Subject: [PATCH 39/92] updated published version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ad690a6..c6e75a80 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.0.6" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" For scala2: From 7cabe7f795e98c5fdbc762086cc28ba43f018700 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 5 Sep 2021 13:58:28 +0300 Subject: [PATCH 40/92] updated dotty version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index b5b5dccf..9b64dbf7 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.1" +val dottyVersion = "3.0.2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.0" From 6455367555f25850bfa3e47a7123bf94ed917390 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 9 Oct 2021 18:46:46 +0300 Subject: [PATCH 41/92] removed unused variable --- build.sbt | 4 ++-- jvm/src/main/scala/gopher/JVMGopher.scala | 2 +- shared/src/main/scala/gopher/Gopher.scala | 8 +++++++- shared/src/main/scala/gopher/Time.scala | 4 ++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 9b64dbf7..ae8f5482 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.0.2" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.0" +ThisBuild/version := "2.1.1-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -42,7 +42,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.0.6") + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index eac8bab6..01fdc146 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -54,7 +54,7 @@ object JVMGopher extends GopherAPI: case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) - lazy val timer = new Timer("gopher") + //lazy val timer = new Timer("gopher") lazy val scheduledExecutor = Executors.newScheduledThreadPool(1) diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index 3e023bd8..fa749783 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -11,13 +11,19 @@ import java.util.concurrent.Executor /** * core of GopherAPI. - *Given instance of Gopher[F] need for using most of Gopher operations. + * Given instance of Gopher[F] need for using most of Gopher operations. + * + * Gopehr API ia a framework, which implements CSP (Communication Sequence Process). + * Process here - scala units of execution (i.e. ) **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] + + def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] + def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false): Channel[F,A,A] diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index 6038c613..cdf89d06 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -21,6 +21,10 @@ import java.util.TimerTask */ abstract class Time[F[_]](gopherAPI: Gopher[F]) { + /** + * type for using in `select` paterns. + * @see [gopher.Select] + **/ type after = FiniteDuration def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = From f8225ac66b830b622c18e5dd052f67408a0c3588 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:25:47 +0300 Subject: [PATCH 42/92] better scaladoc --- build.sbt | 8 +++- shared/src/main/scala/gopher/Gopher.scala | 52 ++++++++++++++++++++--- shared/src/main/scala/gopher/Select.scala | 5 ++- shared/src/main/scala/gopher/Time.scala | 22 +++++++++- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index ae8f5482..9716523b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.0.2" +val dottyVersion = "3.1.0-RC2" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.1-SNAPSHOT" @@ -42,12 +42,18 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) s"-javaagent:${System.getProperty("user.home")}/.ivy2/local/com.github.rssh/trackedfuture_3/0.5.0/jars/trackedfuture_3-assembly.jar" ) */ + Compile / doc / scalacOptions := Seq("-groups", + "-source-links:shared=github://rssh/scala-gopher/master#shared", + "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? scalaJSLinkerConfig ~= { _.withModuleKind(ModuleKind.CommonJSModule) }, scalaJSUseMainModuleInitializer := true, + Compile / doc / scalacOptions := Seq("-groups", + "-source-links:shared=github://rssh/scala-gopher/master#shared", + "-source-links:js=github://rssh/scala-gopher/master#js"), ) lazy val GopherJVM = config("gopher.jvm") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index fa749783..ef12067f 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -10,31 +10,57 @@ import java.util.concurrent.Executor /** - * core of GopherAPI. - * Given instance of Gopher[F] need for using most of Gopher operations. + * core of Gopher API. Given instance of Gopher[F] need for using most of Gopher operations. * - * Gopehr API ia a framework, which implements CSP (Communication Sequence Process). - * Process here - scala units of execution (i.e. ) + * Gopher is a framework, which implements CSP (Communication Sequence Process). + * Process here - scala units of execution (i.e. functions, blok of code, etc). + * Communication channels represented by [gopher.Channel] + * + * @see [gopher.Channel] + * @see [gopher#select] **/ trait Gopher[F[_]:CpsSchedulingMonad]: type Monad[X] = F[X] - + /** + * Monad which control asynchronic execution. + * The main is scheduling: i.e. ability to submit monadic expression to scheduler + * and know that this monadic expression will be evaluated. + **/ def asyncMonad: CpsSchedulingMonad[F] = summon[CpsSchedulingMonad[F]] - + /** + * Create Read/Write channel. + * @param bufSize - size of buffer. If it is zero, the channel is unbuffered. (i.e. writer is blocked until reader start processing). + * @param autoClose - close after first message was written to channel. + * @see [gopher.Channel] + **/ def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false): Channel[F,A,A] + /** + * Create channel where you can write only one element. + * @see [gopher.Channel] + **/ def makeOnceChannel[A](): Channel[F,A,A] = makeChannel[A](1,true) + /*** + *Create a select statement, which used for choosing one action from a set of potentially concurrent asynchronics events. + *[@see gopher.Select] + **/ def select: Select[F] = new Select[F](this) + /** + * get an object with time operations. + **/ def time: Time[F] + /** + * set logging function, which output internal diagnostics and errors from spawned processes. + **/ def setLogFun(logFun:(LogLevel, String, Throwable|Null) => Unit): ((LogLevel, String, Throwable|Null) => Unit) def log(level: LogLevel, message: String, ex: Throwable| Null): Unit @@ -55,18 +81,30 @@ trait Gopher[F[_]:CpsSchedulingMonad]: () } +end Gopher - + +/** +* Create Read/Write channel. +* @param bufSize - size of buffer. If it is zero, the channel is unbuffered. (i.e. writer is blocked until reader start processing). +* @param autoClose - close after first message was written to channel. +* @see [gopher.Channel] +**/ def makeChannel[A](bufSize:Int = 0, autoClose: Boolean = false)(using g:Gopher[?]):Channel[g.Monad,A,A] = g.makeChannel(bufSize, autoClose) + def makeOnceChannel[A]()(using g:Gopher[?]): Channel[g.Monad,A,A] = g.makeOnceChannel[A]() + def select(using g:Gopher[?]):Select[g.Monad] = g.select +/** + * represent `F[_]` as read channel. + **/ def futureInput[F[_],A](f: F[A])(using g: Gopher[F]): ReadChannel[F,A] = val ch = g.makeOnceChannel[Try[A]]() g.spawnAndLogFail{ diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index 221a85cd..e4984087 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -31,7 +31,7 @@ class Select[F[_]](api: Gopher[F]): SelectMacro.onceImpl[F,A]('pf, 'api ) } - /*** + /** * create select groop *@see [gopher.SelectGroup] **/ @@ -39,6 +39,9 @@ class Select[F[_]](api: Gopher[F]): def once[S]: SelectGroup[F,S] = new SelectGroup[F,S](api) + /** + * create Select Loop. + **/ def loop: SelectLoop[F] = new SelectLoop[F](api) diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index cdf89d06..010af07f 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -27,6 +27,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { **/ type after = FiniteDuration + /** + * return channel, then after `duration` ellapses, send signal to this channel. + **/ def after(duration: FiniteDuration): ReadChannel[F,FiniteDuration] = { val ch = gopherAPI.makeOnceChannel[FiniteDuration]() @@ -39,6 +42,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { ch } + /** + * return future which will be filled after time will ellapse. + **/ def asleep(duration: FiniteDuration): F[FiniteDuration] = { var fun: Try[FiniteDuration] => Unit = _ => () @@ -51,6 +57,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { retval } + /** + * synonim for `await(asleep(duration))`. Should be used inside async block. + **/ transparent inline def sleep(duration: FiniteDuration): FiniteDuration = given CpsSchedulingMonad[F] = gopherAPI.asyncMonad await(asleep(duration)) @@ -66,7 +75,9 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { newTicker(duration).channel } - + /** + * ticker which hold channel with expirable tick messages and iterface to stop one. + **/ class Ticker(duration: FiniteDuration) { val channel = gopherAPI.makeChannel[FiniteDuration](0).withExpiration(duration, false) @@ -97,6 +108,10 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { } + /** + * create ticker with given `duration` between ticks. + *@see [gopher.Time.Ticker] + **/ def newTicker(duration: FiniteDuration): Ticker = { new Ticker(duration) @@ -108,7 +123,7 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { /** - * Low lwvel interface for scheduler + * Low level interface for scheduler */ def schedule(fun: () => Unit, delay: FiniteDuration): Time.Scheduled @@ -131,6 +146,9 @@ object Time: type after = FiniteDuration + /** + * return channl on which event will be delivered after `duration` + **/ def after[F[_]](duration: FiniteDuration)(using Gopher[F]): ReadChannel[F,FiniteDuration] = summon[Gopher[F]].time.after(duration) From 4a9fc0d96a6efad652f43b238b10da5f223491d4 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:30:23 +0300 Subject: [PATCH 43/92] publish scaladoc on github --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c6e75a80..c1921f77 100644 --- a/README.md +++ b/README.md @@ -250,6 +250,7 @@ val multiplexed = select amap { ## 2.0.x implementation * source code: https://github.com/rssh/scala-gopher +* scaladoc: https://rssh.github.io/scala-gopher/api/jvm/index.html ## [0.99.x] implementation: * source code: https://github.com/rssh/scala-gopher/tree/0.99x From af1a4926e845d68e49d7c5cd1abe98fda7b40a89 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 10 Oct 2021 10:35:58 +0300 Subject: [PATCH 44/92] fix reference format in scaladoc --- jvm/src/main/scala/gopher/JVMGopher.scala | 2 -- shared/src/main/scala/gopher/Channel.scala | 5 +++++ shared/src/main/scala/gopher/Gopher.scala | 7 ++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 01fdc146..4598eeb0 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -54,8 +54,6 @@ object JVMGopher extends GopherAPI: case jcfg:JVMGopherConfig => jcfg new JVMGopher[F](jvmConfig) - //lazy val timer = new Timer("gopher") - lazy val scheduledExecutor = Executors.newScheduledThreadPool(1) lazy val defaultConfig=JVMGopherConfig( diff --git a/shared/src/main/scala/gopher/Channel.scala b/shared/src/main/scala/gopher/Channel.scala index 817765e2..94b20e8e 100644 --- a/shared/src/main/scala/gopher/Channel.scala +++ b/shared/src/main/scala/gopher/Channel.scala @@ -6,6 +6,11 @@ import scala.concurrent.duration.FiniteDuration import gopher.impl._ +/** + * Channel with ability to read and to write. + * @see [[gopher.ReadChannel]] + * @see [[gopher.WriteChannel]] + **/ trait Channel[F[_],W,R] extends WriteChannel[F,W] with ReadChannel[F,R] with Closeable: override def gopherApi: Gopher[F] diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index ef12067f..de7891c4 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -16,8 +16,8 @@ import java.util.concurrent.Executor * Process here - scala units of execution (i.e. functions, blok of code, etc). * Communication channels represented by [gopher.Channel] * - * @see [gopher.Channel] - * @see [gopher#select] + * @see [[gopher.Channel]] + * @see [[gopher#select]] **/ trait Gopher[F[_]:CpsSchedulingMonad]: @@ -48,13 +48,14 @@ trait Gopher[F[_]:CpsSchedulingMonad]: /*** *Create a select statement, which used for choosing one action from a set of potentially concurrent asynchronics events. - *[@see gopher.Select] + *[@see [[gopher.Select#apply]] **/ def select: Select[F] = new Select[F](this) /** * get an object with time operations. + * @see [[gopher.Time]] **/ def time: Time[F] From 36c9541ecddd02eb52c027ff5c33750e8ebde473 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 18 Oct 2021 21:41:29 +0300 Subject: [PATCH 45/92] addopted to dotty 3.1.0, dotty-cps-async 0.9.4 --- build.sbt | 4 ++-- .../gopher/impl/GuardedSPSCBufferedChannel.scala | 4 ++-- project/plugins.sbt | 4 ++-- shared/src/main/scala/gopher/SelectMacro.scala | 13 ++++++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 9716523b..ebbfa750 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.0-RC2" +val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "2.1.1-SNAPSHOT" @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.3", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, ) diff --git a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala index 96b2d176..bc4d94c7 100644 --- a/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala +++ b/jvm/src/main/scala/gopher/impl/GuardedSPSCBufferedChannel.scala @@ -1,7 +1,7 @@ package gopher.impl -import cps._ -import gopher._ +import cps.* +import gopher.* import java.util.concurrent.ExecutorService import java.util.concurrent.atomic.AtomicReferenceArray import java.util.concurrent.atomic.AtomicInteger diff --git a/project/plugins.sbt b/project/plugins.sbt index e0e123cc..ef25e945 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 9fd6b10b..4ed5ec4a 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -6,6 +6,7 @@ import cps._ import scala.quoted._ import scala.compiletime._ import scala.concurrent.duration._ +import scala.util.control.NonFatal @@ -194,7 +195,13 @@ object SelectMacro: else reportError("Incorrect select pattern, expected or x:channel.{read,write} or Channel.{Read,Write}",chObj.asExpr) - + def safeShow(t:Tree): String = + try + t.show + catch + case NonFatal(ex) => + ex.printStackTrace() + s"(exception durign show:${ex.getMessage()})" caseDef.pattern match case Inlined(_,List(),body) => @@ -232,7 +239,7 @@ object SelectMacro: "unapply"),targs), impl,List(b@Bind(e,ePat),Bind(ch,chPat))) => handleUnapply(chObj, nameReadOrWrite, b, e, ePat, ch) - case pat@Typed(Unapply(TypeApply(quotes.reflect.Select( + case pat@TypedOrTest(Unapply(TypeApply(quotes.reflect.Select( quotes.reflect.Select(chobj,nameReadOrWrite), "unapply"),targs), impl,List(b@Bind(e,ePat),Bind(ch,chPat))),a) => @@ -245,7 +252,7 @@ object SelectMacro: v: channel.write if v == expr v: Time.after if v == expr we have - ${caseDef.pattern.show} + ${safeShow(caseDef.pattern)} (tree: ${caseDef.pattern}) """, caseDef.pattern.asExpr) reportError(s"unparsed caseDef pattern: ${caseDef.pattern}", caseDef.pattern.asExpr) From 42ea86d762348c6b2e51cae7b722f55aa59f8d60 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 18 Oct 2021 21:50:00 +0300 Subject: [PATCH 46/92] restored binary compability with 2.1.0 --- build.sbt | 2 +- jvm/src/main/scala/gopher/JVMGopher.scala | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ebbfa750..d146f656 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.1-SNAPSHOT" +ThisBuild/version := "2.1.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( diff --git a/jvm/src/main/scala/gopher/JVMGopher.scala b/jvm/src/main/scala/gopher/JVMGopher.scala index 4598eeb0..178bfb77 100644 --- a/jvm/src/main/scala/gopher/JVMGopher.scala +++ b/jvm/src/main/scala/gopher/JVMGopher.scala @@ -61,6 +61,10 @@ object JVMGopher extends GopherAPI: taskExecutor=ForkJoinPool.commonPool(), ) + // need for binary compability + @deprecated("use summon[Gopher].time instead") + lazy val timer = new Timer("gopher") + val logger = Logger.getLogger("JVMGopher") def defaultLogFun(level: Level, message:String, ex: Throwable|Null): Unit = From 909906f5c1ea48237a1d4ce1381f6b7a99952ce2 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 19 Oct 2021 07:29:49 +0300 Subject: [PATCH 47/92] 2.1.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d146f656..75e8eddf 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.0" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.1" +ThisBuild/version := "2.1.2-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 6245794a0139e0fe5323681d0ef45f9ccb6f8abf Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 11 Dec 2021 08:03:17 +0200 Subject: [PATCH 48/92] sbt-1.5.6, munit-0.7.29 --- build.sbt | 2 +- project/build.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 75e8eddf..3599856d 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.27" % Test, + libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) lazy val root = project diff --git a/project/build.properties b/project/build.properties index 10fd9eee..bb3a9b7d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.5 +sbt.version=1.5.6 From 107ffea0083bbf1df2d712ce3d92e34ee37b9a46 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 24 Jan 2022 17:50:56 +0200 Subject: [PATCH 49/92] adopted do dotty-cps-async-0.9.6 --- README.md | 8 ++++++- build.sbt | 8 +++---- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/Gopher.scala | 2 +- .../src/main/scala/gopher/ReadChannel.scala | 24 +++++++++++-------- shared/src/main/scala/gopher/Select.scala | 7 +++--- .../src/main/scala/gopher/SelectForever.scala | 4 ++-- .../src/main/scala/gopher/SelectGroup.scala | 8 +++---- .../main/scala/gopher/SelectListeners.scala | 2 +- shared/src/main/scala/gopher/SelectLoop.scala | 4 ++-- .../src/main/scala/gopher/SelectMacro.scala | 18 +++++++------- shared/src/main/scala/gopher/Time.scala | 4 ++-- .../src/main/scala/gopher/WriteChannel.scala | 10 ++++---- .../gopher/monads/ReadChannelCpsMonad.scala | 3 ++- .../monads/ReadTryChannelCpsMonad.scala | 2 +- .../src/test/scala/examples/SieveSuite.scala | 1 + 16 files changed, 61 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index c1921f77..92ea923e 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,16 @@ ### Dependences: -For scala 3: +For scala 3.1.1+: + + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.0" + +For scala 3 and 3.1.0: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" +Note, that 3.0.0 have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.6. + For scala2: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "0.99.15" diff --git a/build.sbt b/build.sbt index 3599856d..62bfee0d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.0" +val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "2.1.2-SNAPSHOT" +ThisBuild/version := "3.0.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.4", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.6", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -45,7 +45,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") + mimaPreviousArtifacts := Set() //Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index ef25e945..0b41d0fd 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/Gopher.scala b/shared/src/main/scala/gopher/Gopher.scala index de7891c4..cfe52c26 100644 --- a/shared/src/main/scala/gopher/Gopher.scala +++ b/shared/src/main/scala/gopher/Gopher.scala @@ -83,7 +83,7 @@ trait Gopher[F[_]:CpsSchedulingMonad]: } end Gopher - + /** * Create Read/Write channel. diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 53099e88..a20ef958 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,12 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read(): A = await(aread())(using rAsyncMonad) + transparent inline def read()(using CpsMonadContext[F]): A = await(aread())(using rAsyncMonad) /** * Synonim for read. */ - transparent inline def ? : A = await(aread())(using rAsyncMonad) + transparent inline def ?(using CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad) /** * return F which contains sequence from first `n` elements. @@ -83,7 +83,7 @@ trait ReadChannel[F[_], A]: * take first `n` elements. * should be called inside async block. **/ - transparent inline def take(n: Int): IndexedSeq[A] = + transparent inline def take(n: Int)(using CpsMonadContext[F]): IndexedSeq[A] = await(atake(n))(using rAsyncMonad) /** @@ -107,7 +107,7 @@ trait ReadChannel[F[_], A]: * * should be called inside async block. **/ - transparent inline def optRead(): Option[A] = await(aOptRead())(using rAsyncMonad) + transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead())(using rAsyncMonad) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -130,7 +130,7 @@ trait ReadChannel[F[_], A]: * run code each time when new object is arriced. * until end of stream is not reached **/ - transparent inline def foreach(inline f: A=>Unit): Unit = + transparent inline def foreach(inline f: A=>Unit)(using CpsMonadContext[F]): Unit = await(aforeach(f))(using rAsyncMonad) @@ -170,8 +170,8 @@ trait ReadChannel[F[_], A]: s } - transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S ): S = - await[F,S](afold(s0)(f))(using rAsyncMonad) + transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S )(using mc:CpsMonadContext[F]): S = + await[F,S,F](afold(s0)(f))(using rAsyncMonad, mc) def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad @@ -317,14 +317,18 @@ object ReadChannel: } - + import cps.stream._ - given emitAbsorber[F[_]: CpsSchedulingMonad,T](using gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,T]( - using gopherApi.asyncMonad, gopherApi.taskExecutionContext) with + given emitAbsorber[F[_], C<:CpsMonadContext[F], T](using auxMonad: CpsSchedulingMonad[F]{ type Context = C }, gopherApi: Gopher[F]): BaseUnfoldCpsAsyncEmitAbsorber[ReadChannel[F,T],F,C,T]( + using gopherApi.taskExecutionContext, auxMonad) with override type Element = T + def asSync(fs: F[ReadChannel[F,T]]): ReadChannel[F,T] = + DelayedReadChannel(fs) + + def unfold[S](s0:S)(f: S => F[Option[(T,S)]]): ReadChannel[F,T] = val r: ReadChannel[F,T] = unfoldAsync(s0)(f) r diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index e4984087..fb3d93fe 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -26,9 +26,9 @@ class Select[F[_]](api: Gopher[F]): *} *``` */ - transparent inline def apply[A](inline pf: PartialFunction[Any,A]): A = + transparent inline def apply[A](inline pf: PartialFunction[Any,A])(using mc:CpsMonadContext[F]): A = ${ - SelectMacro.onceImpl[F,A]('pf, 'api ) + SelectMacro.onceImpl[F,A]('pf, 'api, 'mc ) } /** @@ -68,7 +68,8 @@ class Select[F[_]](api: Gopher[F]): } transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = - async[F](using api.asyncMonad).apply{ + given CpsAsyncMonad[F] = api.asyncMonad + async[F]{ fold(s0)(step) } diff --git a/shared/src/main/scala/gopher/SelectForever.scala b/shared/src/main/scala/gopher/SelectForever.scala index 8f1b2352..3fecc3a9 100644 --- a/shared/src/main/scala/gopher/SelectForever.scala +++ b/shared/src/main/scala/gopher/SelectForever.scala @@ -12,9 +12,9 @@ import scala.concurrent.duration._ class SelectForever[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Unit, Unit](api): - transparent inline def apply(inline pf: PartialFunction[Any,Unit]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Unit])(using mc:CpsMonadContext[F]): Unit = ${ - SelectMacro.foreverImpl('pf,'api) + SelectMacro.foreverImpl('pf,'api, 'mc) } diff --git a/shared/src/main/scala/gopher/SelectGroup.scala b/shared/src/main/scala/gopher/SelectGroup.scala index 9f3b7306..6db7cd2c 100644 --- a/shared/src/main/scala/gopher/SelectGroup.scala +++ b/shared/src/main/scala/gopher/SelectGroup.scala @@ -73,14 +73,14 @@ class SelectGroup[F[_], S](api: Gopher[F]) extends SelectListeners[F,S,S]: def runAsync():F[S] = retval - transparent inline def apply(inline pf: PartialFunction[Any,S]): S = + transparent inline def apply(inline pf: PartialFunction[Any,S])(using mc: CpsMonadContext[F]): S = ${ - SelectMacro.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api, 'mc ) } - transparent inline def select(inline pf: PartialFunction[Any,S]): S = + transparent inline def select(inline pf: PartialFunction[Any,S])(using mc: CpsMonadContext[F]): S = ${ - SelectMacro.onceImpl[F,S]('pf, 'api ) + SelectMacro.onceImpl[F,S]('pf, 'api, 'mc ) } /** diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index ae1cde64..14440418 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - transparent inline def run(): R = await(runAsync())(using asyncMonad) + transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync())(using asyncMonad) diff --git a/shared/src/main/scala/gopher/SelectLoop.scala b/shared/src/main/scala/gopher/SelectLoop.scala index c02a1f3c..7393a2b1 100644 --- a/shared/src/main/scala/gopher/SelectLoop.scala +++ b/shared/src/main/scala/gopher/SelectLoop.scala @@ -11,9 +11,9 @@ import java.util.logging.{Level => LogLevel} class SelectLoop[F[_]](api: Gopher[F]) extends SelectGroupBuilder[F,Boolean, Unit](api): - transparent inline def apply(inline pf: PartialFunction[Any,Boolean]): Unit = + transparent inline def apply(inline pf: PartialFunction[Any,Boolean])(using mc: CpsMonadContext[F]): Unit = ${ - SelectMacro.loopImpl[F]('pf, 'api ) + SelectMacro.loopImpl[F]('pf, 'api, 'mc ) } def runAsync(): F[Unit] = diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 4ed5ec4a..23639188 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -51,10 +51,12 @@ object SelectMacro: def buildSelectListenerRun[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( constructor: Expr[L], caseDefs: List[SelectorCaseExpr[F,S,R]], - api:Expr[Gopher[F]])(using Quotes): Expr[R] = + api:Expr[Gopher[F]], + monadContext: Expr[CpsMonadContext[F]], + )(using Quotes): Expr[R] = val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad) } + val r = '{ await($g.runAsync())(using ${api}.asyncMonad, $monadContext) } r.asExprOf[R] def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( @@ -68,31 +70,31 @@ object SelectMacro: - def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]])(using Quotes): Expr[A] = + def onceImpl[F[_]:Type, A:Type](pf: Expr[PartialFunction[Any,A]], api: Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[A] = def builder(caseDefs: List[SelectorCaseExpr[F,A,A]]):Expr[A] = { val s0 = '{ new SelectGroup[F,A]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl(builder, pf) - def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def loopImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Boolean]], api: Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[Unit] = def builder(caseDefs: List[SelectorCaseExpr[F,Boolean,Unit]]):Expr[Unit] = { val s0 = '{ new SelectLoop[F]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl( builder, pf) - def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]])(using Quotes): Expr[Unit] = + def foreverImpl[F[_]:Type](pf: Expr[PartialFunction[Any,Unit]], api:Expr[Gopher[F]], monadContext: Expr[CpsMonadContext[F]])(using Quotes): Expr[Unit] = def builder(caseDefs: List[SelectorCaseExpr[F,Unit,Unit]]):Expr[Unit] = { val s0 = '{ new SelectForever[F]($api) } - buildSelectListenerRun(s0, caseDefs, api) + buildSelectListenerRun(s0, caseDefs, api, monadContext) } runImpl(builder, pf) diff --git a/shared/src/main/scala/gopher/Time.scala b/shared/src/main/scala/gopher/Time.scala index 010af07f..ec4f1309 100644 --- a/shared/src/main/scala/gopher/Time.scala +++ b/shared/src/main/scala/gopher/Time.scala @@ -60,7 +60,7 @@ abstract class Time[F[_]](gopherAPI: Gopher[F]) { /** * synonim for `await(asleep(duration))`. Should be used inside async block. **/ - transparent inline def sleep(duration: FiniteDuration): FiniteDuration = + transparent inline def sleep(duration: FiniteDuration)(using CpsMonadContext[F]): FiniteDuration = given CpsSchedulingMonad[F] = gopherAPI.asyncMonad await(asleep(duration)) @@ -156,7 +156,7 @@ object Time: def asleep[F[_]](duration: FiniteDuration)(using Gopher[F]): F[FiniteDuration] = summon[Gopher[F]].time.asleep(duration) - transparent inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F]): FiniteDuration = + transparent inline def sleep[F[_]](duration: FiniteDuration)(using Gopher[F], CpsMonadContext[F]): FiniteDuration = summon[Gopher[F]].time.sleep(duration) diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index a0e25cd6..758455f8 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -23,13 +23,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - transparent inline def write(inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) @targetName("write1") - transparent inline def <~ (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) @targetName("write2") - transparent inline def ! (inline a:A): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) //def Write(x:A):WritePattern = new WritePattern(x) @@ -51,8 +51,8 @@ trait WriteChannel[F[_], A]: } } - transparent inline def writeAll(inline collection: IterableOnce[A]): Unit = - await(awriteAll(collection))(using asyncMonad) + transparent inline def writeAll(inline collection: IterableOnce[A])(using mc: CpsMonadContext[F]): Unit = + await(awriteAll(collection))(using asyncMonad, mc) def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index f1a35596..a29d9501 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,7 +6,8 @@ import cps._ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[ [A] =>> ReadChannel[F,A]] with +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[[A] =>> ReadChannel[F,A]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with + def pure[T](t:T): ReadChannel[F,T] = ReadChannel.fromValues[F,T](t) diff --git a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala index 2516f735..02fafe69 100644 --- a/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadTryChannelCpsMonad.scala @@ -7,7 +7,7 @@ import cps._ import gopher.impl._ -given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[ [A] =>> ReadChannel[F,Try[A]] ] with +given ReadTryChannelCpsMonad[F[_]](using Gopher[F]): CpsAsyncMonad[[A] =>> ReadChannel[F,Try[A]]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,Try[A]]] with type FW[T] = [A] =>> ReadChannel[F,Try[A]] diff --git a/shared/src/test/scala/examples/SieveSuite.scala b/shared/src/test/scala/examples/SieveSuite.scala index 9ea58d02..a8423bda 100644 --- a/shared/src/test/scala/examples/SieveSuite.scala +++ b/shared/src/test/scala/examples/SieveSuite.scala @@ -48,6 +48,7 @@ object Sieve def filter1(in:Channel[Future,Int,Int]):ReadChannel[Future,Int] = { + //implicit val printCode = cps.macros.flags.PrintCode val q = makeChannel[Int]() val filtered = makeChannel[Int]() select.afold(in){ ch => From 51e112fe1de6cd8596703dac2fa4377873d9ff81 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 24 Jan 2022 17:58:41 +0200 Subject: [PATCH 50/92] 3.0.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 62bfee0d..d350b623 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.0" +ThisBuild/version := "3.0.1-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From de564ae34d51763dabf037b1b1bea6a66c4b2d20 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 12:14:56 +0200 Subject: [PATCH 51/92] prepare for 3.0.1 release with changed dependency --- README.md | 4 ++-- build.sbt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 92ea923e..c47f2d95 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.0" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.1" For scala 3 and 3.1.0: libraryDependencies += "com.github.rssh" %% "scala-gopher" % "2.1.0" -Note, that 3.0.0 have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.6. +Note, that 3.0.x have no new functionality agains 2.1.0 but need to be a next major release because of binary incompability caused by difference between dotty-cps-async-0.9.5 and 0.9.7. For scala2: diff --git a/build.sbt b/build.sbt index d350b623..6a268359 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.1-SNAPSHOT" +ThisBuild/version := "3.0.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -10,7 +10,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.6", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.7", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -45,7 +45,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set() //Set( "com.github.rssh" %% "scala-gopher" % "2.1.0") + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.0" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From 5d2ee29631b08bd06f2732cc70ae8564dca53e23 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 16:17:26 +0200 Subject: [PATCH 52/92] sbt-1.6.1 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index bb3a9b7d..3161d214 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.5.6 +sbt.version=1.6.1 From 17bfcff97c3ad3eb308ee3b4a93a61b666b74f01 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 25 Jan 2022 16:18:07 +0200 Subject: [PATCH 53/92] 3.0.2-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6a268359..3558a38f 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.1" +ThisBuild/version := "3.0.2-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 13f31286d9b389b0bf91ff34eecafc3ce41423a6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:25:54 +0200 Subject: [PATCH 54/92] adopted to dotty-cps-async 0.9.8 --- README.md | 2 +- build.sbt | 9 +++++---- .../src/main/scala/gopher/ReadChannel.scala | 4 ++-- shared/src/main/scala/gopher/Select.scala | 2 +- .../gopher/monads/ReadChannelCpsMonad.scala | 3 ++- .../gopher/monads/ChannelMonadSuite.scala | 19 ++++++++++++------- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c47f2d95..d3bc49bd 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.1" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.2" For scala 3 and 3.1.0: diff --git a/build.sbt b/build.sbt index 3558a38f..f42aa593 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,6 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" val dottyVersion = "3.1.1" +//val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get ThisBuild/version := "3.0.2-SNAPSHOT" @@ -10,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.7", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.8", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -26,14 +27,14 @@ lazy val root = project ).enablePlugins(GhpagesPlugin, SiteScaladocPlugin) - +// for scala-native support we need munit lazy val gopher = crossProject(JSPlatform, JVMPlatform) .in(file(".")) .settings(sharedSettings) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types" ), + scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types", "-explain" ), fork := true, /* javaOptions ++= Seq( @@ -45,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.0" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.1" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index a20ef958..5b124b7a 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,12 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read()(using CpsMonadContext[F]): A = await(aread())(using rAsyncMonad) + transparent inline def read[G[_]]()(using mc:CpsMonadContext[G]): A = await(aread())(using rAsyncMonad, mc) /** * Synonim for read. */ - transparent inline def ?(using CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad) + transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad, mc) /** * return F which contains sequence from first `n` elements. diff --git a/shared/src/main/scala/gopher/Select.scala b/shared/src/main/scala/gopher/Select.scala index fb3d93fe..4ca37b84 100644 --- a/shared/src/main/scala/gopher/Select.scala +++ b/shared/src/main/scala/gopher/Select.scala @@ -67,7 +67,7 @@ class Select[F[_]](api: Gopher[F]): } } - transparent inline def afold[S](s0:S)(inline step: S => S | SelectFold.Done[S]) : F[S] = + transparent inline def afold[S](s0:S)(inline step: CpsMonadContext[F] ?=> S => S | SelectFold.Done[S]) : F[S] = given CpsAsyncMonad[F] = api.asyncMonad async[F]{ fold(s0)(step) diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index a29d9501..b936ae4f 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -6,7 +6,8 @@ import cps._ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonad[[A] =>> ReadChannel[F,A]] with CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with + +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = diff --git a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala index 8760ce74..7cfc1338 100644 --- a/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala +++ b/shared/src/test/scala/gopher/monads/ChannelMonadSuite.scala @@ -8,8 +8,8 @@ import scala.concurrent.* import scala.concurrent.duration.* import scala.collection.SortedSet -import cps.monads.FutureAsyncMonad -import gopher.monads.given +import cps.monads.{given,*} +import gopher.monads.{given,*} class ChannelMonadSuite extends FunSuite { @@ -19,17 +19,21 @@ class ChannelMonadSuite extends FunSuite { test("using channel as monad and read inside") { - val chX = ReadChannel.fromValues(1,2,3,4,5) - val chY = ReadChannel.fromValues(1,2,3,4,5) + + val chX = ReadChannel.fromValues[Future,Int](1,2,3,4,5) + val chY = ReadChannel.fromValues[Future,Int](1,2,3,4,5) - val squares = async[[X] =>> ReadChannel[Future,X]] { - val x = await(chX) + + val squares: ReadChannel[Future,Int] = async[[X] =>> ReadChannel[Future,X]] { + val x: Int = await(chX) //println(s"reading from X $x") - val y = chY.read() + val y: Int = chY.read() //println(s"reading from Y $y") x*y } + + async[Future] { val a1 = squares.read() //println(s"a1==${a1}") @@ -46,6 +50,7 @@ class ChannelMonadSuite extends FunSuite { } } + test("using channel with flatMap") { From 8b14034c5120307f2a85fe6d763868759bfa26d0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:26:29 +0200 Subject: [PATCH 55/92] sbt-1.6.2 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 3161d214..c8fcab54 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.1 +sbt.version=1.6.2 From 6313814f5b62e1e6a9769747c4206c698dee4007 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 13 Feb 2022 14:27:10 +0200 Subject: [PATCH 56/92] 3.0.2 release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f42aa593..9a8c5614 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.1.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.2-SNAPSHOT" +ThisBuild/version := "3.0.2" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 97aa05d174c21f7640894f672660cb24c75edd49 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 24 Feb 2022 23:06:39 +0200 Subject: [PATCH 57/92] added information message --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index d3bc49bd..014801f9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ +# 🇺 🇦 UKRAINE NEEDS YOUR HELP NOW! + + I'm the creator of this project and I'm Ukrainian. +**My country, Ukraine, [is being invaded by the Russian Federation, right now](https://www.bbc.com/news/world-europe-60504334)**. + +**Please, help to save my country!** +Ukrainian National Bank opened [an account to Raise Funds for Ukraine’s Armed Forces](https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi): + + ``` + SWIFT Code NBU: NBUA UA UX + JP MORGAN CHASE BANK, New York + SWIFT Code: CHASUS33 + Account: 400807238 + 383 Madison Avenue, New York, NY 10179, USA + IBAN: UA843000010000000047330992708 + ``` + + You can also donate to [charity supporting Ukrainian army](https://savelife.in.ua/en/donate/). + + +Ask you politicians: +- stop Russia access to SWIFT +- impose no-fly zone over Ukraine + + ## Gopher: asynchronous implementation of go-like channels/selectors in scala ======= From b333be8945d3b4fef7499c7d0c25a122e427deed Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 16 Apr 2022 12:25:21 +0300 Subject: [PATCH 58/92] Update README.md Optimize Help Ukraine banner --- README.md | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 014801f9..bb59d4e2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,9 @@ -# 🇺 🇦 UKRAINE NEEDS YOUR HELP NOW! +# 🇺 🇦 HELP UKRAINE - I'm the creator of this project and I'm Ukrainian. -**My country, Ukraine, [is being invaded by the Russian Federation, right now](https://www.bbc.com/news/world-europe-60504334)**. - -**Please, help to save my country!** -Ukrainian National Bank opened [an account to Raise Funds for Ukraine’s Armed Forces](https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi): - - ``` - SWIFT Code NBU: NBUA UA UX - JP MORGAN CHASE BANK, New York - SWIFT Code: CHASUS33 - Account: 400807238 - 383 Madison Avenue, New York, NY 10179, USA - IBAN: UA843000010000000047330992708 - ``` - - You can also donate to [charity supporting Ukrainian army](https://savelife.in.ua/en/donate/). - - -Ask you politicians: -- stop Russia access to SWIFT -- impose no-fly zone over Ukraine - - - -## Gopher: asynchronous implementation of go-like channels/selectors in scala +I'm the creator of this project. +My country, Ukraine, [is being invaded by the Russian Federation, right now](https://war.ukraine.ua). If you want to help my country to fight, consider donating to [charity supporting Ukrainian army](https://www.comebackalive.in.ua/). More options is described on [support ukraine](https://supportukrainenow.org/) site. + +# Gopher: asynchronous implementation of go-like channels/selectors in scala ======= ### Dependences: From ae864747a1e43391e060a8025e742df3d204bbe0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 6 Jun 2022 08:31:43 +0300 Subject: [PATCH 59/92] adopted to scala compiler 3.1.2 and dotty-cps-async-0.9.10-SNAPSHOT --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 9a8c5614..814a0979 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.1" +val dottyVersion = "3.1.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.8", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -46,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.1" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.2" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From 1dced8ee614ae19999b31a898d4dd4d652144251 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 27 Jun 2022 22:52:38 +0300 Subject: [PATCH 60/92] better State representation in queens --- .../src/test/scala/gopher/monads/Queens.scala | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 917b444d..7b48099d 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -16,29 +16,17 @@ class QueensSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - case class State( - busyRows:Set[Int], - busyColumns:Set[Int], - busyLRDiagonals:Set[Int], - busyRLDiagonals:Set[Int], - queens: Vector[(Int,Int)] - ) { + type State = Vector[(Int,Int)] + + extension(queens:State) { - def isBusy(i:Int, j:Int): Boolean = - busyRows.contains(i) || - busyColumns.contains(j) || - busyLRDiagonals.contains(i-j) || - busyRLDiagonals.contains(i+j) + def isUnderAttack(i:Int, j:Int): Boolean = + queens.exists{ (qi,qj) => + qi == i || qj == j || i-j == qi-qj || i+j == qi+qj + } - def put(i:Int, j:Int): State = - copy( busyRows = busyRows + i, - busyColumns = busyColumns + j, - busyLRDiagonals = busyLRDiagonals + (i-j), - busyRLDiagonals = busyRLDiagonals + (i+j), - queens = queens :+ (i,j) - ) - + queens :+ (i,j) } @@ -47,9 +35,9 @@ class QueensSuite extends FunSuite { def putQueen(state:State): ReadChannel[Future,State] = val ch = makeChannel[State]() async[Future] { - val i = state.queens.length + val i = state.length if i < N then - for{ j <- 0 until N if !state.isBusy(i,j) } + for{ j <- 0 until N if !state.isUnderAttack(i,j) } ch.write(state.put(i,j)) ch.close() } @@ -57,22 +45,21 @@ class QueensSuite extends FunSuite { def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.queens.size < N) then + if(state.size < N) then val nextState = await(putQueen(state)) await(solutions(nextState)) else state } - val emptyState = State(Set.empty, Set.empty, Set.empty, Set.empty, Vector.empty) test("two first solution for 8 queens problem") { async[Future] { - val r = solutions(emptyState).take(2) + val r = solutions(Vector.empty).take(2) assert(!r.isEmpty) - println(r.map(_.queens)) + println(r) } } -} \ No newline at end of file +} From 1229bb51b0f1a26e9d27cd31c817fc0088fdfab0 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 27 Jun 2022 23:27:57 +0300 Subject: [PATCH 61/92] more condenced representation --- shared/src/test/scala/gopher/monads/Queens.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/shared/src/test/scala/gopher/monads/Queens.scala b/shared/src/test/scala/gopher/monads/Queens.scala index 7b48099d..89edc8fc 100644 --- a/shared/src/test/scala/gopher/monads/Queens.scala +++ b/shared/src/test/scala/gopher/monads/Queens.scala @@ -16,17 +16,17 @@ class QueensSuite extends FunSuite { import scala.concurrent.ExecutionContext.Implicits.global given Gopher[Future] = SharedGopherAPI.apply[Future]() - type State = Vector[(Int,Int)] + type State = Vector[Int] extension(queens:State) { def isUnderAttack(i:Int, j:Int): Boolean = - queens.exists{ (qi,qj) => + queens.zipWithIndex.exists{ (qj,qi) => qi == i || qj == j || i-j == qi-qj || i+j == qi+qj } - - def put(i:Int, j:Int): State = - queens :+ (i,j) + + def asPairs:Vector[(Int,Int)] = + queens.zipWithIndex.map(_.swap) } @@ -38,14 +38,14 @@ class QueensSuite extends FunSuite { val i = state.length if i < N then for{ j <- 0 until N if !state.isUnderAttack(i,j) } - ch.write(state.put(i,j)) + ch.write(state appended j) ch.close() } ch def solutions(state: State): ReadChannel[Future,State] = async[[X] =>> ReadChannel[Future,X]] { - if(state.size < N) then + if(state.length < N) then val nextState = await(putQueen(state)) await(solutions(nextState)) else @@ -57,7 +57,7 @@ class QueensSuite extends FunSuite { async[Future] { val r = solutions(Vector.empty).take(2) assert(!r.isEmpty) - println(r) + println(r.map(_.asPairs)) } } From bf02dc8846acc783dbf9265e26fbaa63747ef1ea Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:00:19 +0300 Subject: [PATCH 62/92] dotty-cps-async 0.9.10 --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 814a0979..e98e467d 100644 --- a/build.sbt +++ b/build.sbt @@ -3,15 +3,15 @@ val dottyVersion = "3.1.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.2" +ThisBuild/version := "3.0.3" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( organization := "com.github.rssh", scalaVersion := dottyVersion, name := "scala-gopher", - resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10-SNAPSHOT", + //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 3a5f845589f1a21b3233f8feee1cc8f707115805 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:00:48 +0300 Subject: [PATCH 63/92] scala-3.1.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e98e467d..e77eb805 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.2" +val dottyVersion = "3.1.3" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get From ef461c78093c5b4407a334bc259a94e63d90372f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 12 Jul 2022 08:03:50 +0300 Subject: [PATCH 64/92] scalajs-0.10.1 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 0b41d0fd..77363e3d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.8.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From ae8bcf200ebf31c06514954a34995c9081ec6709 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 17 Sep 2022 18:01:18 +0300 Subject: [PATCH 65/92] adopted to dotty-cps-asymnc 0.9.11 --- build.sbt | 6 +++--- project/build.properties | 2 +- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/SelectMacro.scala | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index e77eb805..ec600462 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.1.3" +val dottyVersion = "3.2.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.3" +ThisBuild/version := "3.0.4" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.10", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) diff --git a/project/build.properties b/project/build.properties index c8fcab54..22af2628 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.6.2 +sbt.version=1.7.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 77363e3d..3fe53801 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 23639188..6d53d3ad 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -13,7 +13,7 @@ import scala.util.control.NonFatal object SelectMacro: - import cps.macros.forest.TransformUtil + import cps.macros.common.TransformUtil sealed trait SelectGroupExpr[F[_],S, R]: def toExprOf[X <: SelectListeners[F,S, R]]: Expr[X] From 9fde6c061093db594215094ae732c78d3aa56a23 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 17 Sep 2022 22:08:56 +0300 Subject: [PATCH 66/92] 3.0.4 with dotty-cps-async 0.9.11 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ec600462..2a81f49e 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 173dd0c89c3d99f7edc2437dd4da93245c8b3961 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:12:34 +0200 Subject: [PATCH 67/92] scaal-js 1.12.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 3fe53801..9b560f4d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 4367720f4877db2f535ba0466f4e139629f3c939 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:13:38 +0200 Subject: [PATCH 68/92] sbt 1.8.0 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 22af2628..8b9a0b0a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.7.1 +sbt.version=1.8.0 From f80cba2ca2d39b1b492e34f823da5b44b949736d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:13:48 +0200 Subject: [PATCH 69/92] dotty-cps-async 0.9.12 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index 2a81f49e..379b1d20 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.0" +val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.11", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.12", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From 317bc0620c54192a06e36da339a6b2e8fd998b8f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 8 Dec 2022 10:14:32 +0200 Subject: [PATCH 70/92] 3.0.5 release --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 379b1d20..d47b57c5 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.4" +ThisBuild/version := "3.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 0bda21dd7374c4b760f818dce63e570c79b02507 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Fri, 23 Dec 2022 21:39:14 +0200 Subject: [PATCH 71/92] make snapshot dependency --- build.sbt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index d47b57c5..ff94fdd4 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.5" +ThisBuild/version := "3.0.6-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.12", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.14-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -34,7 +34,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Ycheck:macros", "-uniqid", "-Xprint:types", "-explain" ), + scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), fork := true, /* javaOptions ++= Seq( @@ -46,7 +46,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.2" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.5" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From c89863cdc2dfad53388bb8f1caaa5904d644fa1b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 23 Jan 2023 14:47:34 +0200 Subject: [PATCH 72/92] dependency updates --- build.sbt | 6 +++--- project/build.properties | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index ff94fdd4..7315b71f 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.1" +val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.6-SNAPSHOT" +ThisBuild/version := "3.0.6" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.14-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.15", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) diff --git a/project/build.properties b/project/build.properties index 8b9a0b0a..46e43a97 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.0 +sbt.version=1.8.2 From 401a5b9fe98049c52e58b860104bfc6e970ba2ab Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 23 Jan 2023 14:52:48 +0200 Subject: [PATCH 73/92] 3.0.7-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 7315b71f..ad71ffd8 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.6" +ThisBuild/version := "3.0.7-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 987728ef44148b8cf4080071d775643cb98ac16e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 14 Feb 2023 22:08:45 +0200 Subject: [PATCH 74/92] scala-js 1.13.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 9b560f4d..bdfe9a05 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,5 +2,5 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.12.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From e99d8bc0b10208286dbbae20a084d2dad8d20efa Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 14 Feb 2023 22:09:18 +0200 Subject: [PATCH 75/92] dotty-cps-async snapshot --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ad71ffd8..1f07a425 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.15", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16-SNAPSHOT", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From dcb32420271bfccacab85c33861f1a8536d8c73f Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 20 Feb 2023 08:01:24 +0200 Subject: [PATCH 76/92] updates dotty-cps-async to 0.9.16 --- README.md | 2 +- build.sbt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bb59d4e2..807c3486 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ My country, Ukraine, [is being invaded by the Russian Federation, right now](htt For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.2" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.7" For scala 3 and 3.1.0: diff --git a/build.sbt b/build.sbt index 1f07a425..9c3b45c6 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.2.2" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.7-SNAPSHOT" +ThisBuild/version := "3.0.7" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16-SNAPSHOT", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) From bd6e00c78d68d8ae3fc1a3fa7bfb94352ace9bfc Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sat, 10 Jun 2023 12:23:21 +0300 Subject: [PATCH 77/92] adopted to upcoming dotty-cps-async release --- build.sbt | 10 ++++++---- shared/src/main/scala/gopher/ReadChannel.scala | 13 +++++++------ shared/src/main/scala/gopher/SelectListeners.scala | 2 +- shared/src/main/scala/gopher/SelectMacro.scala | 2 +- shared/src/main/scala/gopher/WriteChannel.scala | 8 ++++---- .../scala/gopher/monads/ReadChannelCpsMonad.scala | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/build.sbt b/build.sbt index 9c3b45c6..3f707e1d 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.2.2" +val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.7" +ThisBuild/version := "3.0.8-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.16", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17-RC1", libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, ) @@ -34,7 +34,9 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) .disablePlugins(SitePlugin) .disablePlugins(SitePreviewPlugin) .jvmSettings( - scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), + //scalacOptions ++= Seq( "-unchecked", "-Xcheck-macros", "-Ycheck:macro", "-uniqid", "-Xprint:types", "-explain" ), + // Error in dotty + scalacOptions ++= Seq( "-unchecked", "-Xprint:types" ), fork := true, /* javaOptions ++= Seq( diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 5b124b7a..8daf3eb7 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -52,12 +52,13 @@ trait ReadChannel[F[_], A]: * Can be used only inside async block. * If stream is closed and no values to read left in the stream - throws StreamClosedException **/ - transparent inline def read[G[_]]()(using mc:CpsMonadContext[G]): A = await(aread())(using rAsyncMonad, mc) + transparent inline def read[G[_]]()(using mc:CpsMonadContext[G], fg:CpsMonadConversion[F,G]): A = + await(aread()) /** * Synonim for read. */ - transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread())(using rAsyncMonad, mc) + transparent inline def ?(using mc:CpsMonadContext[F]) : A = await(aread()) /** * return F which contains sequence from first `n` elements. @@ -84,7 +85,7 @@ trait ReadChannel[F[_], A]: * should be called inside async block. **/ transparent inline def take(n: Int)(using CpsMonadContext[F]): IndexedSeq[A] = - await(atake(n))(using rAsyncMonad) + await(atake(n)) /** * read value and return future with @@ -107,7 +108,7 @@ trait ReadChannel[F[_], A]: * * should be called inside async block. **/ - transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead())(using rAsyncMonad) + transparent inline def optRead()(using CpsMonadContext[F]): Option[A] = await(aOptRead()) def foreach_async(f: A=>F[Unit]): F[Unit] = given CpsAsyncMonad[F] = asyncMonad @@ -131,7 +132,7 @@ trait ReadChannel[F[_], A]: * until end of stream is not reached **/ transparent inline def foreach(inline f: A=>Unit)(using CpsMonadContext[F]): Unit = - await(aforeach(f))(using rAsyncMonad) + await(aforeach(f)) def map[B](f: A=>B): ReadChannel[F,B] = @@ -171,7 +172,7 @@ trait ReadChannel[F[_], A]: } transparent inline def fold[S](inline s0:S)(inline f: (S,A) => S )(using mc:CpsMonadContext[F]): S = - await[F,S,F](afold(s0)(f))(using rAsyncMonad, mc) + await[F,S,F](afold(s0)(f)) def zip[B](x: ReadChannel[F,B]): ReadChannel[F,(A,B)] = given CpsSchedulingMonad[F] = asyncMonad diff --git a/shared/src/main/scala/gopher/SelectListeners.scala b/shared/src/main/scala/gopher/SelectListeners.scala index 14440418..c3de322b 100644 --- a/shared/src/main/scala/gopher/SelectListeners.scala +++ b/shared/src/main/scala/gopher/SelectListeners.scala @@ -16,7 +16,7 @@ trait SelectListeners[F[_],S, R]: def runAsync():F[R] - transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync())(using asyncMonad) + transparent inline def run()(using CpsMonadContext[F]): R = await(runAsync()) diff --git a/shared/src/main/scala/gopher/SelectMacro.scala b/shared/src/main/scala/gopher/SelectMacro.scala index 6d53d3ad..fd93363c 100644 --- a/shared/src/main/scala/gopher/SelectMacro.scala +++ b/shared/src/main/scala/gopher/SelectMacro.scala @@ -56,7 +56,7 @@ object SelectMacro: )(using Quotes): Expr[R] = val g = selectListenerBuilder(constructor, caseDefs) // dotty bug if g.run - val r = '{ await($g.runAsync())(using ${api}.asyncMonad, $monadContext) } + val r = '{ await($g.runAsync())(using $monadContext, CpsMonadConversion.identityConversion[F]) } r.asExprOf[R] def buildSelectListenerRunAsync[F[_]:Type, S:Type, R:Type, L <: SelectListeners[F,S,R]:Type]( diff --git a/shared/src/main/scala/gopher/WriteChannel.scala b/shared/src/main/scala/gopher/WriteChannel.scala index 758455f8..a98c0960 100644 --- a/shared/src/main/scala/gopher/WriteChannel.scala +++ b/shared/src/main/scala/gopher/WriteChannel.scala @@ -23,13 +23,13 @@ trait WriteChannel[F[_], A]: // inline def apply(a:A): Unit = await(awrite(a))(using asyncMonad) // inline def unapply(a:A): Some[A] = ??? - transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def write(inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write1") - transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def <~ (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) @targetName("write2") - transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a))(using asyncMonad) + transparent inline def ! (inline a:A)(using CpsMonadContext[F]): Unit = await(awrite(a)) //def Write(x:A):WritePattern = new WritePattern(x) @@ -52,7 +52,7 @@ trait WriteChannel[F[_], A]: } transparent inline def writeAll(inline collection: IterableOnce[A])(using mc: CpsMonadContext[F]): Unit = - await(awriteAll(collection))(using asyncMonad, mc) + await(awriteAll(collection)) def withWriteExpiration(ttl: FiniteDuration, throwTimeouts: Boolean)(using gopherApi: Gopher[F]): WriteChannelWithExpiration[F,A] = diff --git a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala index b936ae4f..24f17f01 100644 --- a/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala +++ b/shared/src/main/scala/gopher/monads/ReadChannelCpsMonad.scala @@ -7,7 +7,7 @@ import gopher.impl._ -given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsMonadInstanceContext[[A] =>> ReadChannel[F,A]] with +given ReadChannelCpsMonad[F[_]](using Gopher[F]): CpsPureMonadInstanceContext[[A] =>> ReadChannel[F,A]] with def pure[T](t:T): ReadChannel[F,T] = From 1a7514be38a701fbd29bdf73efa55fcb5aabe9ca Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:11:21 +0300 Subject: [PATCH 78/92] sbt-1.9.0 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 46e43a97..40b3b8e7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.8.2 +sbt.version=1.9.0 From 7ce8721cdf1d5247b4d0cb0e0d7bbb4611f9e961 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:27:35 +0300 Subject: [PATCH 79/92] 4.0.0 snapshot due to binary incompability caused by dotty-cps-async versions --- build.sbt | 8 ++++---- project/plugins.sbt | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 3f707e1d..9bcf0428 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "3.0.8-SNAPSHOT" +ThisBuild/version := "4.0.0-SNAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17-RC1", - libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.5" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.7" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index bdfe9a05..9e8d881c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,8 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.0") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.1") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 810fcc89aa2637acff429c89d085713dcfed0ad3 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Sun, 11 Jun 2023 10:33:09 +0300 Subject: [PATCH 80/92] 4.0.1-SNAPSHOT --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9bcf0428..f3911ef2 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.0-SNAPSHOT" +ThisBuild/version := "4.0.1-SHAPSHOT" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 1315eb6642cfd3d6a672e4745c5a49961014fdb6 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 12 Jun 2023 23:57:27 +0300 Subject: [PATCH 81/92] rev.to 4.0.0 for tag --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f3911ef2..475bbaad 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.1-SHAPSHOT" +ThisBuild/version := "4.0.0" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From ca68d51ef7c0f3dd3366a24a28f1ac2d9ab0bb28 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 21 Aug 2023 15:15:41 +0300 Subject: [PATCH 82/92] 4.0.1 release for use with scala-gopher 0.4.18 --- build.sbt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index 475bbaad..19b427e8 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.0" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.0" +ThisBuild/version := "4.0.1" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.17", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.18", libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, ) @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "3.0.7" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.0" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? From c27f1dc77643f4cc7604dd3ef84c235e938d5e56 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 2 Oct 2023 09:59:49 +0300 Subject: [PATCH 83/92] adopted to scala-3.3.1, dotty-cps-async 0.9.19 --- build.sbt | 8 ++++---- project/build.properties | 2 +- project/plugins.sbt | 2 +- shared/src/main/scala/gopher/ReadChannel.scala | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.sbt b/build.sbt index 19b427e8..3c1f1859 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.0" +val dottyVersion = "3.3.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.1" +ThisBuild/version := "4.0.2" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.18", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M8" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.19", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, ) lazy val root = project diff --git a/project/build.properties b/project/build.properties index 40b3b8e7..27430827 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.0 +sbt.version=1.9.6 diff --git a/project/plugins.sbt b/project/plugins.sbt index 9e8d881c..e8e217d2 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.1") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.14.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") diff --git a/shared/src/main/scala/gopher/ReadChannel.scala b/shared/src/main/scala/gopher/ReadChannel.scala index 8daf3eb7..e02ae609 100644 --- a/shared/src/main/scala/gopher/ReadChannel.scala +++ b/shared/src/main/scala/gopher/ReadChannel.scala @@ -36,7 +36,7 @@ trait ReadChannel[F[_], A]: def addDoneReader(reader: Reader[Unit]): Unit - lazy val done: ReadChannel[F,Unit] = DoneReadChannel() + final lazy val done: ReadChannel[F,Unit] = DoneReadChannel() type done = Unit From 3c6ffef3fea62433dcb11c57182d937cae8f4513 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Mon, 2 Oct 2023 10:09:40 +0300 Subject: [PATCH 84/92] updated versions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 807c3486..4c9fbc33 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ My country, Ukraine, [is being invaded by the Russian Federation, right now](htt For scala 3.1.1+: - libraryDependencies += "com.github.rssh" %% "scala-gopher" % "3.0.7" + libraryDependencies += "com.github.rssh" %% "scala-gopher" % "4.0.2" For scala 3 and 3.1.0: From aa716e3a8985cfd7056854d8e27d6e47f402485b Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:40:39 +0200 Subject: [PATCH 85/92] scalajs 0.15.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index e8e217d2..d3ea172d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.14.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 7ac03ba9e6149a5d016bc6709213123152f0cdd7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:41:00 +0200 Subject: [PATCH 86/92] dotty-cps-async 0.9.20 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 3c1f1859..769c11bf 100644 --- a/build.sbt +++ b/build.sbt @@ -11,7 +11,7 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.19", + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.20", libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, ) From 4613b67fbbde3db6650d4c63cac41690b039b29d Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:41:45 +0200 Subject: [PATCH 87/92] sbt-1.9.8 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index 27430827..abbbce5d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.6 +sbt.version=1.9.8 From 1c948c5e85e2091c405a7834062866b99af90c89 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 31 Jan 2024 12:42:05 +0200 Subject: [PATCH 88/92] 4.0.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 769c11bf..8d85c7c9 100644 --- a/build.sbt +++ b/build.sbt @@ -3,7 +3,7 @@ val dottyVersion = "3.3.1" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.2" +ThisBuild/version := "4.0.3" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( From 481524a5e37540fdad8dc96a9a9bc8aa78d6bb3e Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Tue, 2 Apr 2024 13:45:56 +0300 Subject: [PATCH 89/92] scalajs-1.16.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d3ea172d..e2a1a0da 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From ed33f56be2ab435c8c2accfac0dfed67667591fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=AA?= Date: Mon, 26 Aug 2024 21:37:15 +1200 Subject: [PATCH 90/92] Fix flag in README.md I noticed that the flag isn't rendering correctly on my browser --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c9fbc33..bcf41272 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 🇺 🇦 HELP UKRAINE +# 🇺🇦 HELP UKRAINE I'm the creator of this project. My country, Ukraine, [is being invaded by the Russian Federation, right now](https://war.ukraine.ua). If you want to help my country to fight, consider donating to [charity supporting Ukrainian army](https://www.comebackalive.in.ua/). More options is described on [support ukraine](https://supportukrainenow.org/) site. From 216c5eeb192eaeadfa3cd70ee95c85bf9166d0b7 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Thu, 26 Dec 2024 15:02:51 +0200 Subject: [PATCH 91/92] update of dependencies --- build.sbt | 10 +++++----- project/build.properties | 2 +- project/plugins.sbt | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 8d85c7c9..43679a5b 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.1" +val dottyVersion = "3.3.4" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.3" +ThisBuild/version := "4.0.5" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.20", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M10" % Test, + libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.23", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M12" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.0" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.3" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/build.properties b/project/build.properties index abbbce5d..081fdbbc 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.10.0 diff --git a/project/plugins.sbt b/project/plugins.sbt index e2a1a0da..fa6da8f3 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,6 +3,6 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1") From 51f0b1533635f63e22d7f140f0f0e3d015261fd9 Mon Sep 17 00:00:00 2001 From: Ruslan Shevchenko Date: Wed, 7 May 2025 09:21:50 +0300 Subject: [PATCH 92/92] dependency updates --- build.sbt | 10 +++++----- project/plugins.sbt | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 43679a5b..ce5add4a 100644 --- a/build.sbt +++ b/build.sbt @@ -1,9 +1,9 @@ //val dottyVersion = "3.0.0-RC2-bin-SNAPSHOT" -val dottyVersion = "3.3.4" +val dottyVersion = "3.3.5" //val dottyVersion = "3.1.3-RC1-bin-SNAPSHOT" //val dottyVersion = dottyLatestNightlyBuild.get -ThisBuild/version := "4.0.5" +ThisBuild/version := "4.0.7" ThisBuild/versionScheme := Some("semver-spec") val sharedSettings = Seq( @@ -11,8 +11,8 @@ val sharedSettings = Seq( scalaVersion := dottyVersion, name := "scala-gopher", //resolvers += "Local Ivy Repository" at "file://"+Path.userHome.absolutePath+"/.ivy2/local", - libraryDependencies += "com.github.rssh" %%% "dotty-cps-async" % "0.9.23", - libraryDependencies += "org.scalameta" %%% "munit" % "1.0.0-M12" % Test, + libraryDependencies += "io.github.dotty-cps-async" %%% "dotty-cps-async" % "1.0.2", + libraryDependencies += "org.scalameta" %%% "munit" % "1.0.4" % Test, ) lazy val root = project @@ -48,7 +48,7 @@ lazy val gopher = crossProject(JSPlatform, JVMPlatform) Compile / doc / scalacOptions := Seq("-groups", "-source-links:shared=github://rssh/scala-gopher/master#shared", "-source-links:jvm=github://rssh/scala-gopher/master#jvm"), - mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.3" ) + mimaPreviousArtifacts := Set( "com.github.rssh" %% "scala-gopher" % "4.0.5" ) ).jsSettings( libraryDependencies += ("org.scala-js" %%% "scalajs-java-logging" % "1.0.0").cross(CrossVersion.for3Use2_13), // TODO: switch to ModuleES ? diff --git a/project/plugins.sbt b/project/plugins.sbt index fa6da8f3..3c903831 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,8 +1,8 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.2") addSbtPlugin("com.typesafe.sbt" % "sbt-site" % "1.4.1") addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.3") -addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.1.0") -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.17.0") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.6") +addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.0.1")