├── .jvmopts ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── release.yml │ └── ci.yml ├── project ├── build.properties ├── plugins.sbt ├── Dependencies.scala └── Lib.scala ├── disjunction └── shared │ └── src │ ├── main │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── disjunction │ │ ├── package.scala │ │ └── Exports.scala │ └── test │ └── scala │ └── DisjunctionCompilationTest.scala ├── utils ├── js │ └── src │ │ └── main │ │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── utils │ │ ├── package.scala │ │ └── PlatformJS.scala ├── jvm │ └── src │ │ └── main │ │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── utils │ │ ├── package.scala │ │ ├── PlatformJVM.scala │ │ └── FileUtils.scala └── shared │ └── src │ ├── main │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── utils │ │ ├── PackageShared.scala │ │ ├── Enabled.scala │ │ ├── Platform.scala │ │ ├── EqualsByRef.scala │ │ ├── Validity.scala │ │ ├── Impossible.scala │ │ ├── SetOnceVar.scala │ │ ├── Ref.scala │ │ ├── Aligner.scala │ │ ├── ConciseIntSetFormat.scala │ │ ├── Variable.scala │ │ ├── IMapBase.scala │ │ ├── IntIncrementer.scala │ │ ├── LongIncrementer.scala │ │ ├── RomanNumeral.scala │ │ ├── MutableFn0.scala │ │ ├── Memo.scala │ │ ├── OptionalBoolFn.scala │ │ ├── AsciiTable.scala │ │ ├── IMap.scala │ │ ├── IndexLabel.scala │ │ ├── FnWithFallback.scala │ │ ├── SetDiff.scala │ │ ├── IMapBaseV.scala │ │ ├── TransitiveClosure.scala │ │ ├── BiMap.scala │ │ ├── StaticLookupFn.scala │ │ └── SafeBool.scala │ └── test │ └── scala │ └── japgolly │ └── microlibs │ └── utils │ ├── BiMapTest.scala │ ├── ConciseIntSetFormatTest.scala │ ├── MemoTest.scala │ ├── IndexLabelTest.scala │ ├── UtilsTest.scala │ ├── ConsolidatedSeqTest.scala │ └── StaticLookupFnTest.scala ├── test-util └── shared │ └── src │ ├── main │ ├── scala-2 │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── testutil │ │ │ └── ScalaVerSpecificTestUtil.scala │ ├── scala │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── testutil │ │ │ ├── TypeTestingUtil.scala │ │ │ ├── TestUtilImplicits.scala │ │ │ └── TestUtilInternals.scala │ └── scala-3 │ │ └── japgolly │ │ └── microlibs │ │ └── testutil │ │ └── ScalaVerSpecificTestUtil.scala │ └── test │ └── scala │ └── japgolly │ └── microlibs │ └── testutil │ └── TestUtilTest.scala ├── recursion ├── shared │ └── src │ │ ├── main │ │ ├── scala-3 │ │ │ └── ScalaVerSpecific.scala │ │ ├── scala-2 │ │ │ └── japgolly │ │ │ │ └── microlibs │ │ │ │ └── recursion │ │ │ │ └── ScalaVerSpecific.scala │ │ └── scala │ │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── recursion │ │ │ ├── Fix.scala │ │ │ ├── EasyRecursion.scala │ │ │ ├── package.scala │ │ │ ├── Algebras.scala │ │ │ ├── AtomOrComposite.scala │ │ │ └── Recursion.scala │ │ └── test │ │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── recursion │ │ ├── MathExpr.scala │ │ ├── ListF.scala │ │ └── RecursionTest.scala └── README.md ├── stdlib-ext ├── jvm │ └── src │ │ └── main │ │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── stdlib_ext │ │ ├── PlatformSpecificStdlibExt.scala │ │ └── PlatformSpecificEscapeUtils.scala ├── shared │ └── src │ │ ├── main │ │ ├── scala-3 │ │ │ └── japgolly │ │ │ │ └── microlibs │ │ │ │ └── stdlib_ext │ │ │ │ ├── StdlibSpecific.scala │ │ │ │ └── MutableArray.scala │ │ ├── scala-2.13 │ │ │ └── japgolly │ │ │ │ └── microlibs │ │ │ │ └── stdlib_ext │ │ │ │ ├── StdlibSpecific.scala │ │ │ │ └── MutableArray.scala │ │ ├── scala │ │ │ └── japgolly │ │ │ │ └── microlibs │ │ │ │ └── stdlib_ext │ │ │ │ ├── EscapeUtils.scala │ │ │ │ └── Extractors.scala │ │ └── scala-2.12 │ │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── stdlib_ext │ │ │ ├── StdlibSpecific.scala │ │ │ └── MutableArray.scala │ │ └── test │ │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── stdlib_ext │ │ ├── ExtractorsTest.scala │ │ ├── MutableArrayTest.scala │ │ ├── EscapeUtilsTest.scala │ │ └── StdlibExtTest.scala └── js │ └── src │ └── main │ └── scala │ └── japgolly │ └── microlibs │ └── stdlib_ext │ ├── PlatformSpecificStdlibExt.scala │ └── PlatformSpecificEscapeUtils.scala ├── .gitignore ├── compile-time └── shared │ └── src │ ├── test │ └── scala-3 │ │ └── japgolly │ │ └── microlibs │ │ └── compiletime │ │ ├── Scala3CompilationTestsHelpers.scala │ │ └── Scala3CompilationTests.scala │ └── main │ ├── scala-3 │ └── japgolly │ │ └── microlibs │ │ └── compiletime │ │ ├── ExprSet.scala │ │ ├── ExprMap.scala │ │ ├── TransparentInlineUtils.scala │ │ ├── Init.scala │ │ ├── InlineUtils.scala │ │ ├── Field.scala │ │ ├── QuotingUtils.scala │ │ ├── EasierValDef.scala │ │ └── CompileTimeInfo.scala │ └── scala-2 │ └── japgolly │ └── microlibs │ └── compiletime │ ├── WhiteboxMacros.scala │ └── CompileTimeInfo.scala ├── cats-ext └── shared │ └── src │ ├── main │ ├── scala │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── cats_ext │ │ │ └── CatsUtil.scala │ ├── scala-3 │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── cats_ext │ │ │ └── CatsMacros.scala │ └── scala-2 │ │ └── japgolly │ │ └── microlibs │ │ └── cats_ext │ │ └── CatsMacros.scala │ └── test │ └── scala │ └── japgolly │ └── microlibs │ └── scalaz_ext │ └── CatsMacrosTest.scala ├── scalafix.sbt ├── .scalafix.conf ├── adt-macros └── shared │ └── src │ ├── main │ └── scala-2 │ │ └── japgolly │ │ └── microlibs │ │ └── adt_macros │ │ └── JapgollyAccess.scala │ └── test │ ├── scala-2 │ └── japgolly │ │ └── microlibs │ │ └── adt_macros │ │ └── Scala2AdtMacroTest.scala │ ├── scala │ ├── a │ │ └── AA.scala │ └── japgolly │ │ └── microlibs │ │ └── adt_macros │ │ └── AdtMacroTest.scala │ └── scala-3 │ └── japgolly │ └── microlibs │ └── adt_macros │ └── Scala3AdtMacroTest.scala ├── name-fn └── shared │ └── src │ ├── main │ ├── scala │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── name_fn │ │ │ ├── NameFn.scala │ │ │ └── Name.scala │ ├── scala-3 │ │ └── japgolly │ │ │ └── microlibs │ │ │ └── name_fn │ │ │ └── NameMacros.scala │ └── scala-2 │ │ └── japgolly │ │ └── microlibs │ │ └── name_fn │ │ └── NameMacros.scala │ └── test │ └── scala │ └── japgolly │ └── microlibs │ └── name_fn │ └── NameTest.scala ├── bench └── src │ └── main │ └── scala │ └── japgolly │ └── microlibs │ └── recursion │ └── RecursionBM.scala ├── nonempty └── shared │ └── src │ ├── test │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── nonempty │ │ └── NonEmptyTest.scala │ └── main │ └── scala │ └── japgolly │ └── microlibs │ └── nonempty │ ├── NonEmpty.scala │ └── NonEmptySet.scala ├── types └── shared │ └── src │ ├── test │ └── scala │ │ └── japgolly │ │ └── microlibs │ │ └── types │ │ └── NaturalCompositionTest.scala │ └── main │ └── scala │ └── japgolly │ └── microlibs │ └── types │ └── NaturalComposition.scala ├── README.md ├── bin ├── gen-function_ext └── gen-tuple_ext └── multimap └── shared └── src ├── main └── scala │ └── japgolly │ └── microlibs │ └── multimap │ └── MultiValues.scala └── test └── scala └── japgolly └── microlibs └── multimap └── MultimapTest.scala /.jvmopts: -------------------------------------------------------------------------------- 1 | -Xmx2G 2 | 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: japgolly 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.7.1 2 | -------------------------------------------------------------------------------- /disjunction/shared/src/main/scala/japgolly/microlibs/disjunction/package.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs 2 | 3 | package object disjunction 4 | extends japgolly.microlibs.disjunction.Exports 5 | -------------------------------------------------------------------------------- /utils/js/src/main/scala/japgolly/microlibs/utils/package.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs 2 | 3 | package object utils extends utils.PackageShared { 4 | val Platform: Platform = PlatformJS 5 | } 6 | -------------------------------------------------------------------------------- /utils/jvm/src/main/scala/japgolly/microlibs/utils/package.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs 2 | 3 | package object utils extends utils.PackageShared { 4 | val Platform: Platform = PlatformJVM 5 | } 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | open-pull-requests-limit: 10 9 | 10 | -------------------------------------------------------------------------------- /test-util/shared/src/main/scala-2/japgolly/microlibs/testutil/ScalaVerSpecificTestUtil.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | // Scala 2 4 | 5 | trait ScalaVerSpecificTestUtil { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala-3/ScalaVerSpecific.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.~> 4 | 5 | object ScalaVerSpecific { 6 | 7 | private[recursion] type Coseq[F[_], G[_]] = ([A] =>> F[G[A]]) ~> ([A] =>> G[F[A]]) 8 | } -------------------------------------------------------------------------------- /stdlib-ext/jvm/src/main/scala/japgolly/microlibs/stdlib_ext/PlatformSpecificStdlibExt.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | /* 4 | +-----+ 5 | | JVM | 6 | +-----+ 7 | */ 8 | 9 | trait PlatformSpecificStdlibExt { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-3/japgolly/microlibs/stdlib_ext/StdlibSpecific.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | /* 4 | +-----------+ 5 | | Scala 3.x | 6 | +-----------+ 7 | */ 8 | 9 | trait ScalaSpecificStdlibExt { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-2.13/japgolly/microlibs/stdlib_ext/StdlibSpecific.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | /* 4 | +------------+ 5 | | Scala 2.13 | 6 | +------------+ 7 | */ 8 | 9 | trait ScalaSpecificStdlibExt { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala-2/japgolly/microlibs/recursion/ScalaVerSpecific.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.~> 4 | 5 | object ScalaVerSpecific { 6 | private[recursion] type Coseq[F[_], G[_]] = Lambda[A => F[G[A]]] ~> Lambda[A => G[F[A]]] 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ~* 2 | .~* 3 | *.bak 4 | *.log 5 | *.tmp 6 | .*.swp 7 | .*.swo 8 | .*.swn 9 | 10 | .project 11 | target 12 | .target 13 | *.pid 14 | *.out 15 | *.err 16 | 17 | .idea 18 | .idea_modules 19 | .cache 20 | .classpath 21 | .metadata 22 | .bloop 23 | .metals 24 | .vscode 25 | project/metals.sbt 26 | .bsp 27 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/PackageShared.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import java.time.Instant 4 | 5 | trait PackageShared { 6 | 7 | final type MutableClock = MutableFn0[Instant] 8 | object MutableClock extends MutableFn0.DslWithDefaultSelector[Instant](() => Instant.now()) 9 | 10 | } 11 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.34") 2 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") 3 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") 4 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") 5 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3") 6 | -------------------------------------------------------------------------------- /compile-time/shared/src/test/scala-3/japgolly/microlibs/compiletime/Scala3CompilationTestsHelpers.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | 5 | object Scala3CompilationTestsHelpers { 6 | inline def newInstance[A]: A = 7 | ${ _newInstance[A] } 8 | 9 | private def _newInstance[A](using Quotes, Type[A]): Expr[A] = 10 | NewInstance.of[A]() 11 | } 12 | 13 | -------------------------------------------------------------------------------- /cats-ext/shared/src/main/scala/japgolly/microlibs/cats_ext/CatsUtil.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.cats_ext 2 | 3 | import cats.Eq 4 | import java.time.{Duration, Instant} 5 | 6 | object CatsUtil { 7 | 8 | def equalInstantWithTolerance(tolerance: Duration): Eq[Instant] = 9 | Eq((a, b) => { 10 | val d = Duration.between(b, a).abs() 11 | tolerance.compareTo(d) > 0 12 | }) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Enabled.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | sealed trait Enabled extends SafeBool.WithBoolOps[Enabled] { 4 | override final def companion = Enabled 5 | } 6 | 7 | case object Enabled extends Enabled with SafeBool.Object[Enabled] { 8 | override def positive = Enabled 9 | override def negative = Disabled 10 | } 11 | 12 | case object Disabled extends Enabled 13 | -------------------------------------------------------------------------------- /scalafix.sbt: -------------------------------------------------------------------------------- 1 | ThisBuild / scalafixScalaBinaryVersion := "2.13" 2 | ThisBuild / semanticdbEnabled := true 3 | ThisBuild / semanticdbVersion := "4.6.0" 4 | 5 | ThisBuild / scalacOptions ++= { 6 | if (scalaVersion.value startsWith "2") 7 | "-Yrangepos" :: Nil 8 | else 9 | Nil 10 | } 11 | 12 | ThisBuild / scalafixDependencies ++= Seq( 13 | "com.github.liancheng" %% "organize-imports" % "0.6.0" 14 | ) 15 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Platform.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | 5 | trait Platform { 6 | 7 | def memo[A: UnivEq, B](f: A => B): A => B 8 | 9 | final type LooseMemo[A, B] = (A, => B) => B 10 | 11 | def looseMemo[A: UnivEq, B](): LooseMemo[A, B] 12 | 13 | def memoInt[A](f: Int => A): Int => A 14 | 15 | def memoThunk[A](f: () => A): () => A 16 | } 17 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/BiMapTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import utest._ 4 | 5 | object BiMapTest extends TestSuite { 6 | override def tests = Tests { 7 | "Adding & retrieving" - { 8 | val b = BiMap.newBuilder[String, Int] 9 | b += ("Three" -> 3) 10 | b("Two") = 2 11 | val m = b.result() 12 | assert(m.backward(3) == "Three") 13 | assert(m.forward("Two") == 2) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/EqualsByRef.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | 5 | /** Implements .equals using reference equality */ 6 | trait EqualsByRef { self: AnyRef => 7 | 8 | final override def equals(obj: Any): Boolean = 9 | obj match { 10 | case o: AnyRef => this eq o 11 | case _ => false 12 | } 13 | } 14 | 15 | object EqualsByRef { 16 | implicit def univEq[A <: EqualsByRef]: UnivEq[A] = 17 | UnivEq.force 18 | } -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | OrganizeImports, 3 | RemoveUnused, 4 | ] 5 | 6 | RemoveUnused { 7 | imports = false 8 | privates = true 9 | locals = true 10 | } 11 | 12 | OrganizeImports { 13 | expandRelative = true 14 | groupedImports = Merge 15 | groupExplicitlyImportedImplicitsSeparately = false 16 | groups = ["*"] 17 | importSelectorsOrder = Ascii 18 | removeUnused = true 19 | } 20 | -------------------------------------------------------------------------------- /adt-macros/shared/src/main/scala-2/japgolly/microlibs/adt_macros/JapgollyAccess.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.adt_macros 2 | 3 | trait JapgollyAccess { 4 | val c: scala.reflect.macros.blackbox.Context 5 | import c.universe._ 6 | 7 | lazy val SelectNonemptyPkg = Select(Select(Select(Ident(termNames.ROOTPKG), TermName("japgolly")), TermName("microlibs")), TermName("nonempty")) 8 | lazy val SelectNonEmptyVector = Select(SelectNonemptyPkg, TermName("NonEmptyVector")) 9 | lazy val SelectNonEmptySet = Select(SelectNonemptyPkg, TermName("NonEmptySet")) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/Fix.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import japgolly.microlibs.recursion 4 | 5 | sealed trait FixModule { 6 | type Fix[F[_]] 7 | 8 | def apply[F[_]](f: F[recursion.Fix[F]]): Fix[F] 9 | def unfix[F[_]](f: Fix[F]): F[recursion.Fix[F]] 10 | } 11 | 12 | private[recursion] object FixImpl extends FixModule { 13 | override type Fix[F[_]] = F[recursion.Fix[F]] 14 | 15 | override def apply[F[_]](f: F[recursion.Fix[F]]): Fix[F] = f 16 | override def unfix[F[_]](f: Fix[F]): F[recursion.Fix[F]] = f 17 | } 18 | -------------------------------------------------------------------------------- /disjunction/shared/src/test/scala/DisjunctionCompilationTest.scala: -------------------------------------------------------------------------------- 1 | import japgolly.microlibs.disjunction._ 2 | import scala.annotation.nowarn 3 | 4 | @nowarn 5 | object DisjunctionCompilationTest { 6 | 7 | locally { 8 | val a = \/-(1) 9 | val b: Right[String, Int] = a 10 | val c: \/-[Int] = b 11 | } 12 | 13 | locally { 14 | val a = -\/(1) 15 | val b: Left[Int, String] = a 16 | val c: -\/[Int] = b 17 | } 18 | 19 | locally { 20 | val e: Either[Int, String] = Right("") 21 | e.leftMap(_ + 1) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Validity.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | sealed trait Validity extends SafeBool[Validity] with SafeBool.WithBoolOps[Validity] { 4 | override final def companion = Validity 5 | } 6 | 7 | object Validity extends SafeBool.Object[Validity] { 8 | override def positive = Valid 9 | override def negative = Invalid 10 | 11 | def apply(d: Either[Any, Any]): Validity = 12 | Valid when d.isRight 13 | } 14 | 15 | case object Valid extends Validity { 16 | val always: Any => Validity = _ => Valid 17 | } 18 | 19 | case object Invalid extends Validity 20 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/ExprSet.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | 5 | object ExprSet: 6 | def empty[A](using Quotes): ExprSet[A] = 7 | new ExprSet[A] 8 | 9 | final class ExprSet[A](using Quotes): 10 | private var exprs = List.empty[Expr[A]] 11 | 12 | def +=(e: Expr[A]): this.type = 13 | if !this.contains(e) then exprs ::= e 14 | this 15 | 16 | def -=(e: Expr[A]): this.type = 17 | exprs = exprs.filterNot(e.matches) 18 | this 19 | 20 | def contains(e: Expr[A]): Boolean = 21 | exprs.exists(e.matches) 22 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Impossible.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import scala.annotation.elidable 5 | 6 | /** Unfortunately using `Nothing` results in Scala 2 behaving differently and having lots of problems wrt implicit 7 | * resolution and type inference. 8 | * 9 | * This is an alternative. 10 | */ 11 | sealed trait Impossible { 12 | 13 | @elidable(elidable.ALL) 14 | final def impossible: Nothing = 15 | throw new RuntimeException("Impossible.impossible called!") 16 | } 17 | 18 | object Impossible { 19 | @inline implicit def univEq: UnivEq[Impossible] = UnivEq.force 20 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v*.*.* 7 | 8 | jobs: 9 | 10 | release: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - name: Git checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Scala 18 | uses: japgolly/setup-everything-scala@v3.1 19 | 20 | - name: Release 21 | run: sbt ci-release 22 | env: 23 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 24 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 25 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 26 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 27 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala/japgolly/microlibs/stdlib_ext/EscapeUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import java.lang.{StringBuilder => JStringBuilder} 4 | 5 | trait EscapeUtils { 6 | 7 | /** `"a\n"` becomes `"\"a\\n\""` */ 8 | def quote(s: String): String 9 | 10 | /** `"a\n"` becomes `"a\\n"` */ 11 | def escape(s: String): String 12 | 13 | def appendQuoted(sb: JStringBuilder, s: String): Unit 14 | def appendEscaped(sb: JStringBuilder, s: String): Unit 15 | 16 | def appendQuoted(sb: StringBuilder, s: String): Unit 17 | def appendEscaped(sb: StringBuilder, s: String): Unit 18 | } 19 | 20 | object EscapeUtils extends EscapeUtils with PlatformSpecificEscapeUtils 21 | -------------------------------------------------------------------------------- /name-fn/shared/src/main/scala/japgolly/microlibs/name_fn/NameFn.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.name_fn 2 | 3 | final case class NameFn[-A](fn: Option[A] => Name) extends AnyVal { 4 | @inline def apply(i: Option[A]) = 5 | fn(i) 6 | 7 | def map(f: Name => Name): NameFn[A] = 8 | NameFn(f compose fn) 9 | 10 | def cmap[B](f: B => A): NameFn[B] = 11 | NameFn(ob => apply(ob map f)) 12 | 13 | def comap[B](f: B => Option[A]): NameFn[B] = 14 | NameFn(ob => apply(ob flatMap f)) 15 | 16 | def mapContextFree(n: Name): NameFn[A] = 17 | NameFn { 18 | case s@Some(_) => fn(s) 19 | case None => n 20 | } 21 | } 22 | 23 | object NameFn { 24 | def const(n: Name): NameFn[Any] = 25 | NameFn(_ => n) 26 | } 27 | -------------------------------------------------------------------------------- /utils/js/src/main/scala/japgolly/microlibs/utils/PlatformJS.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import scala.collection.mutable 5 | 6 | object PlatformJS extends Platform { 7 | 8 | override def memo[A: UnivEq, B](f: A => B): A => B = { 9 | val cache = new mutable.HashMap[A, B]() 10 | a => cache.getOrElseUpdate(a, f(a)) 11 | } 12 | 13 | override def looseMemo[A: UnivEq, B](): LooseMemo[A, B] = { 14 | val cache = new mutable.HashMap[A, B]() 15 | cache.getOrElseUpdate 16 | } 17 | 18 | override def memoInt[A](f: Int => A): Int => A = 19 | memo(f) // Could be better 20 | 21 | override def memoThunk[A](f: () => A): () => A = { 22 | lazy val a = f() 23 | () => a 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /name-fn/shared/src/main/scala-3/japgolly/microlibs/name_fn/NameMacros.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.name_fn 2 | 3 | import scala.compiletime.* 4 | import scala.quoted.* 5 | 6 | trait NameImplicits { 7 | 8 | inline implicit def materializeNameFromString(inline body: String): Name = 9 | ${ NameMacros.name('body) } 10 | 11 | inline implicit def materializeNameFnFromString(inline body: String): NameFn[Any] = 12 | NameFn.const(body) 13 | 14 | inline implicit def nameFnFromString[A](a: A)(using ev: A => Name): NameFn[Any] = 15 | NameFn const ev(a) 16 | } 17 | 18 | object NameMacros { 19 | def name(expr: Expr[String])(using Quotes): Expr[Name] = 20 | if expr.value.isDefined then 21 | '{ Name.now($expr) } 22 | else 23 | '{ Name($expr) } 24 | } 25 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/SetOnceVar.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | final class SetOnceVar[A] { 4 | private var instance = Option.empty[A] 5 | 6 | def getOrSet(value: => A): A = 7 | synchronized { 8 | if (instance.isDefined) 9 | instance.get 10 | else { 11 | val a = value 12 | instance = Some(a) 13 | a 14 | } 15 | } 16 | 17 | def getOption(): Option[A] = 18 | synchronized(instance) 19 | 20 | def getOrThrow(): A = 21 | getOrThrow("SetOnceVar not yet set.") 22 | 23 | def getOrThrow(errMsg: => String): A = 24 | getOption().getOrElse(throw new RuntimeException(errMsg)) 25 | } 26 | 27 | object SetOnceVar { 28 | def apply[A]: SetOnceVar[A] = 29 | new SetOnceVar 30 | } 31 | -------------------------------------------------------------------------------- /compile-time/shared/src/test/scala-3/japgolly/microlibs/compiletime/Scala3CompilationTests.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import Scala3CompilationTestsHelpers._ 4 | 5 | object Scala3CompilationTests { 6 | 7 | class X 8 | InlineUtils.printCode(new X) 9 | InlineUtils.printTasty(new X) 10 | newInstance[X] 11 | 12 | class P[A] 13 | class M[A] { newInstance[P[A]] } 14 | newInstance[P[Int]] 15 | 16 | type PI = P[Int] 17 | newInstance[PI] 18 | 19 | implicit val i: Int = 123 20 | implicit val lli: List[List[Int]] = Nil :: Nil 21 | class I1(implicit j: Int) 22 | class I2[A](implicit a: A) 23 | class I3[A]()(implicit a: List[List[A]]) 24 | newInstance[I1] 25 | newInstance[I2[Int]] 26 | // newInstance[I3[Int]] 27 | // try implicit defaults too 28 | 29 | } -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Ref.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import scala.annotation.nowarn 5 | 6 | /** 7 | * Wraps a value such that reference equality holds. 8 | * 9 | * Allows values to be used as map-keys, in sets, etc with uniqueness being by reference. 10 | */ 11 | final class Ref[A <: AnyRef](val value: A) { 12 | override def hashCode = value.## 13 | 14 | @nowarn 15 | override def equals(other: Any) = 16 | other match { 17 | case r: Ref[A] => value eq r.value 18 | case _ => false 19 | } 20 | } 21 | 22 | object Ref { 23 | def apply[A <: AnyRef](a: A): Ref[A] = 24 | new Ref(a) 25 | 26 | implicit def refEquality[A <: AnyRef]: UnivEq[Ref[A]] = 27 | UnivEq.force 28 | } 29 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/ConciseIntSetFormatTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import utest._ 4 | 5 | object ConciseIntSetFormatTest extends TestSuite { 6 | 7 | def test(expect: String)(i: Int, is: Int*): Unit = { 8 | val nes = is.toSet + i 9 | val actual = ConciseIntSetFormat(nes) 10 | assert(actual == expect) 11 | } 12 | 13 | override def tests = Tests { 14 | "single" - test("3") (3) 15 | "range" - test("5-8") (5,6,7,8) 16 | "range2" - test("1-3,7-9") (1,2,3,7,8,9) 17 | "jump" - test("2,4,9") (2,9,4) 18 | "pair" - test("2,3") (2,3) 19 | "combo1" - test("2,3,5-9,20") (20,9,8,7,6,5,2,3) 20 | "combo2" - test("1-4,6,7,10,13-15")(1,2,3,4,6,7,10,13,14,15) 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - '**' 8 | tags-ignore: 9 | - v*.*.* 10 | 11 | jobs: 12 | 13 | ci: 14 | name: Scala v${{ matrix.scala }} / Java v${{ matrix.java }} 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | include: 20 | - java: 14 21 | scala: 2 22 | - java: 8 23 | scala: 3 24 | 25 | steps: 26 | - name: Git checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Setup Scala 30 | uses: japgolly/setup-everything-scala@v3.1 31 | with: 32 | java-version: adopt@1.${{ matrix.java }} 33 | 34 | - name: Build and test 35 | shell: bash 36 | run: sbt++field scala${{ matrix.scala }} clean test 37 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/ExprMap.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | 5 | object ExprMap: 6 | def empty[K, V](using Quotes): ExprMap[K, V] = 7 | new ExprMap[K, V] 8 | 9 | final class ExprMap[K, V](using Quotes): 10 | private var exprs = List.empty[(Expr[K], V)] 11 | 12 | def update(k: Expr[K], v: V): this.type = 13 | this += ((k, v)) 14 | 15 | def +=(kv: (Expr[K], V)): this.type = 16 | this -= kv._1 17 | exprs ::= kv 18 | this 19 | 20 | def -=(k: Expr[K]): this.type = 21 | exprs = exprs.filterNot(x => k.matches(x._1)) 22 | this 23 | 24 | def contains(k: Expr[K]): Boolean = 25 | exprs.exists(x => k.matches(x._1)) 26 | 27 | def get(k: Expr[K]): Option[V] = 28 | exprs.find(x => k.matches(x._1)).map(_._2) 29 | -------------------------------------------------------------------------------- /bench/src/main/scala/japgolly/microlibs/recursion/RecursionBM.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import java.util.concurrent.TimeUnit 4 | import org.openjdk.jmh.annotations._ 5 | 6 | @Warmup(iterations = 10) 7 | @Measurement(iterations = 10) 8 | @Fork(1) 9 | @BenchmarkMode(Array(Mode.AverageTime)) 10 | @OutputTimeUnit(TimeUnit.MICROSECONDS) 11 | @State(Scope.Benchmark) 12 | class RecursionBM { 13 | 14 | @Param(Array("10", "100", "1000", "10000")) 15 | var size: Int = _ 16 | 17 | var s: Fix[MathExpr] = _ 18 | 19 | @Setup def setup = { 20 | s = Recursion.ana(MathExpr.plusOnes)(size) 21 | } 22 | 23 | @Benchmark def cata: Int = Recursion.cata(MathExpr.eval)(s) 24 | @Benchmark def ana: Fix[MathExpr] = Recursion.ana(MathExpr.plusOnes)(size) 25 | @Benchmark def hylo: Int = Recursion.hylo(MathExpr.plusOnes, MathExpr.eval)(size) 26 | } 27 | -------------------------------------------------------------------------------- /nonempty/shared/src/test/scala/japgolly/microlibs/nonempty/NonEmptyTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.nonempty 2 | 3 | import utest._ 4 | 5 | object NonEmptyTest extends TestSuite { 6 | 7 | case class X() 8 | case class Y() 9 | 10 | override def tests = Tests { 11 | "vector" - { 12 | "empty" - assert(NonEmpty(Vector()).isEmpty) 13 | "one" - assert(NonEmpty(Vector(1)) == Some(NonEmptyVector(1))) 14 | "two" - assert(NonEmpty(Vector(1, 2)) == Some(NonEmptyVector(1, 2))) 15 | } 16 | "set" - { 17 | "empty" - assert(NonEmpty(Set.empty[Int]).isEmpty) 18 | "one" - assert(NonEmpty(Set(1)) == Some(NonEmptySet(1))) 19 | "two" - assert(NonEmpty(Set(1, 2)) == Some(NonEmptySet(1, 2))) 20 | } 21 | "map" - { 22 | "empty" - assert(NonEmpty(Map.empty[X, Y]).isEmpty) 23 | val ne = Map(X() -> Y()) 24 | "nonEmpty" - assert(NonEmpty(ne) == Some(NonEmpty.force(ne))) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stdlib-ext/js/src/main/scala/japgolly/microlibs/stdlib_ext/PlatformSpecificStdlibExt.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import scala.scalajs.js 4 | 5 | /* 6 | +----+ 7 | | JS | 8 | +----+ 9 | */ 10 | 11 | object PlatformSpecificStdlibExt { 12 | 13 | final implicit class JSLE_JsIteratorUndefOr[A](private val as: Iterator[js.UndefOr[A]]) extends AnyVal { 14 | def nextOptionU: js.UndefOr[A] = 15 | if (as.hasNext) 16 | as.next() 17 | else 18 | js.undefined 19 | 20 | def firstDefined: js.UndefOr[A] = 21 | as.filter(_.isDefined).nextOptionU 22 | 23 | def filterDefined: Iterator[A] = 24 | as.filter(_.isDefined).map(_.get) 25 | } 26 | 27 | } 28 | 29 | trait PlatformSpecificStdlibExt { 30 | import PlatformSpecificStdlibExt._ 31 | 32 | final implicit def JSLE_JsIteratorUndefOr[A](as: Iterator[js.UndefOr[A]]): JSLE_JsIteratorUndefOr[A] = 33 | new JSLE_JsIteratorUndefOr(as) 34 | } 35 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-2/japgolly/microlibs/compiletime/WhiteboxMacros.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.annotation.nowarn 4 | 5 | abstract class WhiteboxMacroUtils extends MacroUtils { 6 | val c: scala.reflect.macros.whitebox.Context 7 | import c.universe._ 8 | 9 | def extractStaticAnnotationArgs: List[Tree] = 10 | c.macroApplication match { 11 | case Apply(Select(Apply(_, args), _), _) => args 12 | case x => fail(s"Unable to determine annotation args.\n${showRaw(x)}") 13 | } 14 | 15 | @nowarn("cat=unused") 16 | def replaceEmptyBodyInAnnotatedObject(annottees: Seq[c.Expr[Any]])(newBody: List[Tree]): Tree = 17 | annottees.map(_.tree) match { 18 | case List(q"object $objName extends $parent { ..$body }") if body.isEmpty => 19 | q"object $objName extends $parent { ..$newBody }" 20 | case _ => fail("You must annotate an object definition with an empty body.") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cats-ext/shared/src/test/scala/japgolly/microlibs/scalaz_ext/CatsMacrosTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.cats_ext 2 | 3 | import cats.Eq 4 | import scala.annotation.nowarn 5 | import utest._ 6 | 7 | @nowarn("cat=unused") 8 | object CatsMacrosTest extends TestSuite { 9 | 10 | sealed abstract class X[+E, +A] 11 | object X { 12 | sealed abstract class N[+A] extends X[Nothing, A] 13 | final case class S[+A](a: A) extends N[A] 14 | final case class F[+E](e: E) extends X[E, Nothing] 15 | case object U extends N[Nothing] 16 | 17 | implicit def eS[A: Eq]: Eq[S[A]] = CatsMacros.deriveEq 18 | implicit def eF[E: Eq]: Eq[F[E]] = CatsMacros.deriveEq 19 | implicit def eU : Eq[U.type] = CatsMacros.deriveEq 20 | } 21 | 22 | override def tests = Tests { 23 | // TODO Pending https://github.com/lampepfl/dotty/issues/11765 24 | // "X" - { 25 | // CatsMacros.deriveEqual[X[Int, Int]] 26 | // () 27 | // } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /name-fn/shared/src/main/scala/japgolly/microlibs/name_fn/Name.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.name_fn 2 | 3 | abstract class Name { 4 | def value: String 5 | def map(f: String => String): Name 6 | } 7 | 8 | object Name { 9 | 10 | final class Now(override val value: String) extends Name { 11 | override def map(f: String => String): Name = 12 | now(f(value)) 13 | } 14 | 15 | final class Later(init: () => String) extends Name { 16 | private[this] var thunk = init 17 | 18 | override lazy val value: String = { 19 | val n = thunk() 20 | thunk = null // dereference 21 | n 22 | } 23 | 24 | override def map(f: String => String): Name = 25 | Name(f(value)) 26 | } 27 | 28 | def now(value: String): Now = 29 | new Now(value) 30 | 31 | def apply(n: => String): Name = 32 | new Later(() => n) 33 | 34 | def lazily(n: => Name): Name = 35 | new Later(() => n.value) 36 | 37 | object Implicits extends NameImplicits 38 | } 39 | -------------------------------------------------------------------------------- /types/shared/src/test/scala/japgolly/microlibs/types/NaturalCompositionTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.types 2 | 3 | import japgolly.microlibs.testutil.TestUtil._ 4 | 5 | object NaturalCompositionTest { 6 | import NaturalComposition.{Merge, Split} 7 | 8 | def assertSplit[A, B](implicit m: Split[A, B]) = assertType[m.In] 9 | def assertMerge[A, B](implicit m: Merge[A, B]) = assertType[m.Out] 10 | 11 | type U = Unit 12 | type I = Int 13 | type S = String 14 | 15 | assertSplit[I, U].is[I] 16 | assertSplit[I, I].is[I] // different 17 | assertSplit[I, S].is[(I, S)] 18 | assertSplit[3, 3].is[3] 19 | assertSplit[3, 4].is[(3, 4)] 20 | assertSplit[3, I].is[(3, I)] 21 | assertSplit[U, I].is[I] 22 | assertSplit[U, U].is[U] 23 | 24 | assertMerge[I, U].is[I] 25 | assertMerge[I, I].is[(I, I)] // different 26 | assertMerge[I, S].is[(I, S)] 27 | assertMerge[3, 3].is[3] 28 | assertMerge[3, 4].is[(3, 4)] 29 | assertMerge[3, I].is[(3, I)] 30 | assertMerge[U, I].is[I] 31 | assertMerge[U, U].is[U] 32 | } 33 | -------------------------------------------------------------------------------- /name-fn/shared/src/main/scala-2/japgolly/microlibs/name_fn/NameMacros.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.name_fn 2 | 3 | trait NameImplicits { 4 | implicit def materializeNameFromString(body: String): Name = 5 | macro NameMacros.name 6 | 7 | implicit def materializeNameFnFromString(body: String): NameFn[Any] = 8 | macro NameMacros.nameFn 9 | 10 | implicit def nameFnFromString[A](a: A)(implicit ev: A => Name): NameFn[Any] = 11 | NameFn const ev(a) 12 | } 13 | 14 | final class NameMacros(val c: scala.reflect.macros.blackbox.Context) { 15 | import c.universe.{Name => _, _} 16 | 17 | def pkg = q"_root_.japgolly.microlibs.name_fn" 18 | 19 | def name(body: c.Expr[String]): c.Expr[Name] = 20 | body match { 21 | case Expr(Literal(Constant(s: String))) => 22 | c.Expr[Name](q"$pkg.Name.now($s)") 23 | case _ => 24 | c.Expr[Name](q"$pkg.Name($body)") 25 | } 26 | 27 | def nameFn(body: c.Expr[String]): c.Expr[NameFn[Any]] = 28 | c.Expr[NameFn[Any]](q"$pkg.NameFn.const($body)") 29 | } 30 | -------------------------------------------------------------------------------- /test-util/shared/src/main/scala/japgolly/microlibs/testutil/TypeTestingUtil.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | import scala.annotation.nowarn 4 | 5 | object TypeTestingUtil extends TypeTestingUtil 6 | 7 | trait TypeTestingUtil { 8 | 9 | def assertType[A]: TypeTestingUtilDsl[A] = 10 | new TypeTestingUtilDsl[A] 11 | 12 | @nowarn("cat=unused") 13 | def assertTypeOf[A](a: => A): TypeTestingUtilDsl[A] = 14 | assertType[A] 15 | 16 | def assertTypeOfImplicit[A](implicit a: A): TypeTestingUtilDsl[a.type] = 17 | assertType[a.type] 18 | } 19 | 20 | @nowarn("cat=unused") 21 | class TypeTestingUtilDsl[A] { 22 | def map[B](f: A => B): TypeTestingUtilDsl[B] = 23 | TypeTestingUtil.assertType[B] 24 | 25 | def map[F[_]]: TypeTestingUtilDsl[F[A]] = 26 | TypeTestingUtil.assertType[F[A]] 27 | 28 | def is [B](implicit ev: A =:= B): Unit = () 29 | def is_< [B](implicit ev: A <:< B): Unit = () 30 | def is_> [B](implicit ev: B <:< A): Unit = () 31 | def isImplicitly[B](implicit ev: A => B) : Unit = () 32 | } 33 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/EasyRecursion.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.{Functor, Monad, Traverse} 4 | 5 | /** 6 | * Beginner-friendly. No Greek. 7 | */ 8 | object EasyRecursion { 9 | 10 | 11 | def fold[F[_]: Functor, A](alg: FAlgebra[F, A])(f: Fix[F]): A = 12 | Recursion.cata(alg)(f) 13 | 14 | def unfold[F[_]: Functor, A](coalg: FCoalgebra[F, A])(a: A): Fix[F] = 15 | Recursion.ana(coalg)(a) 16 | 17 | def unfoldIntoFold[F[_]: Functor, A, B](coalg: FCoalgebra[F, A], alg: FAlgebra[F, B])(a: A): B = 18 | Recursion.hylo(coalg, alg)(a) 19 | 20 | 21 | def monadicFold[M[_]: Monad, F[_]: Traverse, A](alg: FAlgebraM[M, F, A])(f: Fix[F]): M[A] = 22 | Recursion.cataM(alg)(f) 23 | 24 | def monadicUnfold[M[_]: Monad, F[_]: Traverse, A](coalg: FCoalgebraM[M, F, A])(a: A): M[Fix[F]] = 25 | Recursion.anaM(coalg)(a) 26 | 27 | def monadicUnfoldIntoFold[M[_]: Monad, F[_]: Traverse, A, B](coalg: FCoalgebraM[M, F, A], alg: FAlgebraM[M, F, B])(a: A): M[B] = 28 | Recursion.hyloM(coalg, alg)(a) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/test/scala/japgolly/microlibs/stdlib_ext/ExtractorsTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import japgolly.microlibs.testutil.TestUtil._ 4 | import java.time.Duration 5 | import utest._ 6 | 7 | object ExtractorsTest extends TestSuite { 8 | // TODO Workaround for https://github.com/lampepfl/dotty/issues/11798 9 | import japgolly.microlibs.testutil.TestUtil.catsEqFromUnivEq 10 | 11 | override def tests = Tests { 12 | 13 | "duration" - { 14 | "2d" - assertEq(ParseDuration.unapply("2d"), Option(Duration.ofDays(2))) 15 | "2d9s" - assertEq(ParseDuration.unapply("2d9s"), Option(Duration.ofDays(2) plus Duration.ofSeconds(9))) 16 | "2D9S" - assertEq(ParseDuration.unapply("2D9S"), Option(Duration.ofDays(2) plus Duration.ofSeconds(9))) 17 | "2 days, 8 minutes" - assertEq(ParseDuration.unapply("2 days, 8 minutes"), Option(Duration.ofDays(2) plus Duration.ofMinutes(8))) 18 | "2 DAYS, 8 MINUTES" - assertEq(ParseDuration.unapply("2 DAYS, 8 MINUTES"), Option(Duration.ofDays(2) plus Duration.ofMinutes(8))) 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Aligner.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | object Aligner { 4 | 5 | def forStrings(): Mutable[String] = 6 | new Mutable(_.length) 7 | 8 | final class Mutable[I](f: I => Int) { 9 | private[this] var maxLen = 0 10 | 11 | def consider(i: I, plus: Int = 0): Unit = { 12 | val len = f(i) + plus 13 | if (len > maxLen) 14 | maxLen = len 15 | } 16 | 17 | def paddingSize(len: Int): Int = 18 | if (len >= maxLen) 0 else maxLen - len 19 | 20 | def padLeft(s: String): String = { 21 | var p = paddingSize(s.length) 22 | if (p == 0) 23 | s 24 | else { 25 | val sb = new StringBuilder(maxLen) 26 | sb.append(s) 27 | while (p > 0) { 28 | p -= 1 29 | sb append ' ' 30 | } 31 | sb.toString 32 | } 33 | } 34 | 35 | def padLeft(sb: StringBuilder, s: String): Unit = { 36 | var p = paddingSize(s.length) 37 | sb.append(s) 38 | while (p > 0) { 39 | p -= 1 40 | sb append ' ' 41 | } 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /stdlib-ext/js/src/main/scala/japgolly/microlibs/stdlib_ext/PlatformSpecificEscapeUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | // ********** 4 | // * * 5 | // * JS * 6 | // * * 7 | // ********** 8 | 9 | import java.lang.{StringBuilder => JStringBuilder} 10 | 11 | trait PlatformSpecificEscapeUtils { self: EscapeUtils.type => 12 | 13 | override def quote(s: String): String = 14 | scala.scalajs.js.JSON.stringify(s) 15 | 16 | override def escape(s: String): String = 17 | if (s == null) 18 | null 19 | else { 20 | val q = quote(s) 21 | q.substring(1, q.length - 1) 22 | } 23 | 24 | override def appendQuoted(sb: JStringBuilder, s: String): Unit = { 25 | sb.append(quote(s)) 26 | () 27 | } 28 | 29 | override def appendEscaped(sb: JStringBuilder, s: String): Unit = { 30 | sb.append(escape(s)) 31 | () 32 | } 33 | 34 | override def appendQuoted(sb: StringBuilder, s: String): Unit = { 35 | sb.append(quote(s)) 36 | () 37 | } 38 | 39 | override def appendEscaped(sb: StringBuilder, s: String): Unit = { 40 | sb.append(escape(s)) 41 | () 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/package.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs 2 | 3 | import cats.free.{Cofree, Free} 4 | 5 | package object recursion { 6 | 7 | val Fix: FixModule = FixImpl 8 | type Fix[F[_]] = Fix.Fix[F] 9 | 10 | type FAlgebra[F[_], A] = F[A] => A 11 | type FCoalgebra[F[_], A] = A => F[A] 12 | 13 | type FAlgebraM[M[_], F[_], A] = F[A] => M[A] 14 | type FCoalgebraM[M[_], F[_], A] = A => M[F[A]] 15 | 16 | type RAlgebra[F[_], A] = F[(Fix[F], A)] => A 17 | type RCoalgebra[F[_], A] = A => F[Either[Fix[F], A]] 18 | 19 | /** Course-of-values algebra */ 20 | type CVAlgebra[F[_], A] = F[Cofree[F, A]] => A 21 | 22 | /** Course-of-values co-algebra */ 23 | type CVCoalgebra[F[_], A] = A => F[Free[F, A]] 24 | 25 | @inline implicit class FixOps[F[_]](private val self: Fix[F]) extends AnyVal { 26 | @inline def unfix: F[Fix[F]] = 27 | Fix.unfix(self) 28 | } 29 | 30 | @inline implicit def fAlgebraOps[F[_], A](self: F[A] => A): FAlgebraOps[F, A] = 31 | new FAlgebraOps(self) 32 | 33 | @inline implicit def fCoalgebraOps[F[_], A](self: A => F[A]): FCoalgebraOps[F, A] = 34 | new FCoalgebraOps(self) 35 | 36 | } 37 | -------------------------------------------------------------------------------- /test-util/shared/src/test/scala/japgolly/microlibs/testutil/TestUtilTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | import java.time.{Duration, Instant} 4 | import utest._ 5 | 6 | object TestUtilTest extends TestSuite { 7 | import TestUtil._ 8 | 9 | override def tests = Tests { 10 | 11 | "assertMultiline" - { 12 | // java.util.MissingFormatWidthException: %-0s 13 | val all = Seq("", " ", "\n", " \n ") 14 | for { 15 | a <- all 16 | b <- all 17 | } { 18 | try 19 | assertMultiline(a, b) 20 | catch { 21 | case _: java.lang.AssertionError => () 22 | } 23 | } 24 | } 25 | 26 | "assertEqWithToleranceDouble" - { 27 | assertEqWithTolerance(10, 12, 2) 28 | assertEqWithTolerance(10, 12, 3) 29 | assertThrows(assertEqWithTolerance(10, 12, 1)) 30 | } 31 | 32 | "assertEqWithToleranceInstant" - { 33 | val a = Instant.now() 34 | val b = a.plus(Duration.ofSeconds(2)) 35 | assertEqWithTolerance(a, b, Duration.ofSeconds(2)) 36 | assertEqWithTolerance(a, b, Duration.ofSeconds(3)) 37 | assertThrows(assertEqWithTolerance(a, b, Duration.ofSeconds(1))) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/test/scala/japgolly/microlibs/stdlib_ext/MutableArrayTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import utest._ 4 | 5 | object MutableArrayTest extends TestSuite { 6 | 7 | def ji(i: Int) = java.lang.Integer.valueOf(i) 8 | 9 | override def tests = Tests { 10 | 11 | "anyRef" - { 12 | def m = MutableArray(List(2, 1, 3).map(ji)) 13 | "init" - assert(m.to(List).map(_.intValue) == List(2, 1, 3)) 14 | "map" - assert(m.map(_.toString).to(List) == List("2", "1", "3")) 15 | "map2" - assert(m.map(_ + 1).map(_.toString).to(List) == List("3", "2", "4")) 16 | "sort" - assert(m.sort.to(List).map(_.intValue) == List(1, 2, 3)) 17 | "mapAV" - assert(m.map(_.intValue).to(List) == List(2, 1, 3)) 18 | } 19 | 20 | "int" - { 21 | def m = MutableArray(List(2, 1, 3)) 22 | "init" - assert(m.to(List) == List(2, 1, 3)) 23 | "map" - assert(m.map(_.toString).to(List) == List("2", "1", "3")) 24 | "map2" - assert(m.map(_ + 1).map(_.toString).to(List) == List("3", "2", "4")) 25 | "sort" - assert(m.sort.to(List) == List(1, 2, 3)) 26 | "mapAR" - assert(m.map(ji).to(List).map(_.intValue) == List(2, 1, 3)) 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/ConciseIntSetFormat.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | /** 4 | * Format a set of ints into a concise textual description. 5 | * 6 | * Example: 7 | * "2, 5, 7-14, 20" 8 | */ 9 | object ConciseIntSetFormat { 10 | private sealed trait Tmp 11 | private final case class N(n: Int) extends Tmp 12 | private final case class R(from: Int, to: Int) extends Tmp 13 | 14 | def apply(ints: Set[Int], sep: String = ",", rangeSep: String = "-"): String = 15 | if (ints.isEmpty) 16 | "" 17 | else { 18 | 19 | val comps = ints.toArray.sorted.foldRight(List.empty[Tmp])((i, cs) => 20 | cs match { 21 | case N(a) :: N(b) :: t if i == a - 1 && a == b - 1 => 22 | R(i, b) :: t 23 | case R(a, b) :: t if i == a - 1 => 24 | R(i, b) :: t 25 | case _ => 26 | N(i) :: cs 27 | }) 28 | 29 | val sb = new StringBuilder 30 | for (c <- comps) { 31 | if (sb.nonEmpty) sb.append(sep) 32 | c match { 33 | case n: N => sb append n.n 34 | case r: R => sb append r.from; sb append rangeSep; sb append r.to 35 | } 36 | } 37 | sb.toString 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microlibraries! 2 | [![Build Status](https://travis-ci.org/japgolly/microlibs-scala.svg?branch=master)](https://travis-ci.org/japgolly/microlibs-scala) 3 | [![Latest Version](https://maven-badges.herokuapp.com/maven-central/com.github.japgolly.microlibs/utils_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.japgolly.microlibs/utils_2.13) 4 | 5 | 6 | ```scala 7 | val VerMicrolibs = "2.x" 8 | 9 | "com.github.japgolly.microlibs" %%% "adt-macros" % VerMicrolibs 10 | "com.github.japgolly.microlibs" %%% "cats-ext" % VerMicrolibs 11 | "com.github.japgolly.microlibs" %%% "compile-time" % VerMicrolibs 12 | "com.github.japgolly.microlibs" %%% "disjunction" % VerMicrolibs 13 | "com.github.japgolly.microlibs" %%% "multimap" % VerMicrolibs 14 | "com.github.japgolly.microlibs" %%% "name-fn" % VerMicrolibs 15 | "com.github.japgolly.microlibs" %%% "nonempty" % VerMicrolibs 16 | "com.github.japgolly.microlibs" %%% "recursion" % VerMicrolibs 17 | "com.github.japgolly.microlibs" %%% "stdlib-ext" % VerMicrolibs 18 | "com.github.japgolly.microlibs" %%% "test-util" % VerMicrolibs % Test 19 | "com.github.japgolly.microlibs" %%% "types" % VerMicrolibs 20 | "com.github.japgolly.microlibs" %%% "utils" % VerMicrolibs 21 | ``` 22 | -------------------------------------------------------------------------------- /bin/gen-function_ext: -------------------------------------------------------------------------------- 1 | #!/bin/env scala 2 | // vim: set ft=scala : 3 | 4 | val comma = ", " 5 | def T(i: Int) = (64+i).toChar.toString 6 | 7 | var boo = Seq.empty[String] 8 | 9 | def mergeFn(name: String, i: String, is: String, o: String, op: String) = 10 | s" @inline def $name(y: $i=>$o): $i=>$o = ($is) => x($is) $op y($is)" 11 | def and(i: String, is: String) = mergeFn("&&", i, is, "Boolean", "&&") 12 | def or (i: String, is: String) = mergeFn("||", i, is, "Boolean", "||") 13 | 14 | for (i <- (1 to 22)) { 15 | // def m (f: Int => String): List[String] = (1 to a).toList.map(f) 16 | // def mc(f: Int => String): String = m(f).mkString(comma) 17 | // def mt(f: Int => String): String = m(f).mkString("(",comma,")") 18 | 19 | def A(j: Int) = ('A'+j).toChar.toString 20 | val As = (0 until i) map A 21 | val Ac = As mkString "," 22 | val ac = Ac.toLowerCase 23 | val _s = List.fill(i)("_") mkString "," 24 | 25 | boo :+= s"@inline final implicit class JSLE_Function${i}ToBool[$Ac](private val x: ($Ac) => Boolean) extends AnyVal {" 26 | boo :+= s" @inline def unary_! : ($Ac) => Boolean = !x(${_s})" 27 | boo :+= and(s"($Ac)", ac) 28 | boo :+= or(s"($Ac)", ac) 29 | boo :+= "}" 30 | } 31 | // println((rt++ra) mkString "\n") 32 | println(boo mkString "\n") 33 | println() 34 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Variable.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | trait Variable[@specialized A] { self => 4 | def get(): A 5 | def mod(f: A => A): A 6 | 7 | def set(a: A): A = 8 | mod(_ => a) 9 | 10 | def xmap[B](f: A => B)(g: B => A): Variable[B] = 11 | new Variable[B] { 12 | override def get() = f(self.get()) 13 | override def mod(h: B => B) = f(self.mod(a => g(h(f(a))))) 14 | } 15 | } 16 | 17 | object Variable { 18 | 19 | def apply[@specialized A](init: A): Variable[A] = 20 | new Variable[A] { 21 | private[this] var value = init 22 | override def get() = value 23 | override def mod(f: A => A) = { value = f(value); value } 24 | } 25 | 26 | def volatile[@specialized A](init: A): Variable[A] = 27 | new Variable[A] { 28 | @volatile private[this] var value = init 29 | override def get() = value 30 | override def mod(f: A => A) = { value = f(value); value } 31 | } 32 | 33 | def syncOn[@specialized A](lock: AnyRef)(init: A): Variable[A] = 34 | new Variable[A] { 35 | private[this] var value = init 36 | override def get() = lock.synchronized(value) 37 | override def mod(f: A => A) = lock.synchronized { value = f(value); value } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/MemoTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import utest._ 4 | 5 | object MemoTest extends TestSuite { 6 | 7 | override def tests = Tests { 8 | var i = 0 9 | def inc() = {i += 1; i} 10 | 11 | def test(actual: Int, expect: Int): Unit = 12 | assert(actual == expect) 13 | 14 | "fn1" - { 15 | val f = Memo((_: Int) + inc()) 16 | test(f(0), 1) 17 | test(f(0), 1) 18 | test(f(8), 10) 19 | test(f(8), 10) 20 | test(f(5), 8) 21 | test(f(0), 1) 22 | test(f(8), 10) 23 | test(f(5), 8) 24 | } 25 | 26 | "int" - { 27 | val f = Memo.int(_ + inc()) 28 | test(f(0), 1) 29 | test(f(0), 1) 30 | test(f(8), 10) 31 | test(f(8), 10) 32 | test(f(5), 8) 33 | test(f(0), 1) 34 | test(f(8), 10) 35 | test(f(5), 8) 36 | } 37 | 38 | "curry" - { 39 | val f = Memo.curry2((m: Int) => { 40 | val x = inc() 41 | (n: Int) => 1000*m +100*n + x*10 + inc() 42 | }) 43 | test(f(7)(4), 7412) 44 | test(f(7)(4), 7412) 45 | test(f(7)(6), 7613) 46 | test(f(7)(6), 7613) 47 | test(f(4)(7), 4745) 48 | test(f(4)(6), 4646) 49 | test(f(4)(6), 4646) 50 | test(f(7)(4), 7412) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /name-fn/shared/src/test/scala/japgolly/microlibs/name_fn/NameTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.name_fn 2 | 3 | import japgolly.microlibs.name_fn.Name.Implicits._ 4 | import utest._ 5 | 6 | object NameTest extends TestSuite { 7 | 8 | override def tests = Tests { 9 | 10 | "nonStrict" - { 11 | "name" - { 12 | var i = 0 13 | val n: Name = s"i = ${ i += 1; i.toString }" 14 | assert(i == 0) 15 | assert(n.value == "i = 1") 16 | assert(i == 1) 17 | assert(n.value == "i = 1") 18 | assert(i == 1) 19 | } 20 | 21 | "nameFn" - { 22 | var i = 0 23 | val f: NameFn[Unit] = s"i = ${ i += 1; i.toString }" 24 | assert(i == 0) 25 | assert(f(None).value == "i = 1") 26 | assert(i == 1) 27 | assert(f(None).value == "i = 1") 28 | assert(i == 1) 29 | assert(f(Some(())).value == "i = 1") 30 | assert(i == 1) 31 | } 32 | } 33 | 34 | "pure" - { 35 | "name" - { 36 | val n: Name = "good" 37 | assertMatch(n) { case _: Name.Now => () } 38 | } 39 | "nameFn" - { 40 | val fn: NameFn[Unit] = "good" 41 | val a = fn(None) 42 | val b = fn(Some(())) 43 | assertMatch(a) { case _: Name.Now => () } 44 | assert(a eq b) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test-util/shared/src/main/scala-3/japgolly/microlibs/testutil/ScalaVerSpecificTestUtil.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | import japgolly.microlibs.compiletime.InlineUtils 4 | import japgolly.microlibs.compiletime.MacroEnv.* 5 | import scala.quoted.* 6 | 7 | // Scala 3 8 | 9 | object ScalaVerSpecificTestUtil { 10 | 11 | def assertExprsEq[A](actual: Expr[A], expect: Expr[A], show: Boolean | Expr[Boolean] = false)(using Quotes): Expr[Unit] = 12 | if (actual matches expect) { 13 | val showValue: Boolean = 14 | show match { 15 | case b: Boolean => b 16 | case e: Expr[Boolean] => e.value.getOrElse(false) 17 | } 18 | if showValue then println(actual.show) 19 | '{ () } 20 | } else { 21 | val a = Expr.inlineConst(actual.show) 22 | val e = Expr.inlineConst(expect.show) 23 | '{ TestUtil.assertMultiline($a, $e) } 24 | } 25 | 26 | } 27 | 28 | trait ScalaVerSpecificTestUtil { 29 | import TestUtil.* 30 | 31 | export InlineUtils.{ 32 | printCode, 33 | printTasty, 34 | showCode, 35 | showTasty, 36 | } 37 | 38 | inline def assertTastyEq[A](inline actual: A, inline expect: A, inline show: Boolean = false): Unit = 39 | ${ ScalaVerSpecificTestUtil.assertExprsEq[A]( 40 | actual = 'actual, 41 | expect = 'expect, 42 | show = 'show, 43 | ) } 44 | } 45 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/IMapBase.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import cats.Eq 4 | import japgolly.univeq.UnivEq 5 | 6 | object IMapBase { 7 | @inline def catsEq[K, V: Eq, M <: IMapBase[K, V, M]]: Eq[M] = 8 | IMapBaseV.catsEq[K, V, V, M] 9 | 10 | @inline def univEq[K, V, I <: IMapBase[K, V, I]](implicit u: UnivEq[Map[K, V]]): UnivEq[I] = 11 | IMapBaseV.univEq[K, V, V, I] 12 | } 13 | 14 | abstract class IMapBase[K: UnivEq, V, This_ <: IMapBase[K, V, This_]] private[utils] (m: Map[K, V]) extends IMapBaseV[K, V, V, This_](m) { 15 | final override protected def _values(v: V) = v :: Nil 16 | final override protected def _add(to: Map[K, V], k: K, v: V) = to.updated(k, v) 17 | 18 | final def isEmpty = m.isEmpty 19 | final def nonEmpty = !isEmpty 20 | 21 | final def filter (f: (K, V) => Boolean): This = mapUnderlying(_ filter f.tupled) 22 | final def filterKeys (f: K => Boolean): This = mapUnderlying(_ filter(kv => f(kv._1))) 23 | final def filterValues(f: V => Boolean): This = mapUnderlying(_.filter(kv => f(kv._2))) 24 | 25 | final def filterNot (f: (K, V) => Boolean): This = mapUnderlying(_ filterNot f.tupled) 26 | final def filterNotKeys (f: K => Boolean): This = mapUnderlying(_.filterNot(kv => f(kv._1))) 27 | final def filterNotValues(f: V => Boolean): This = mapUnderlying(_.filterNot(kv => f(kv._2))) 28 | } 29 | -------------------------------------------------------------------------------- /adt-macros/shared/src/test/scala-2/japgolly/microlibs/adt_macros/Scala2AdtMacroTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.adt_macros 2 | 3 | import aa._ 4 | import japgolly.microlibs.adt_macros.AdtMacros._ 5 | import utest._ 6 | 7 | object Scala2AdtMacroTest extends TestSuite { 8 | import AdtMacroTest._ 9 | 10 | override def tests = Tests { 11 | 12 | "adtValuesManually" - { 13 | "simple" - { 14 | // 's1i - assertOrderedNEV(MonoS1.ValuesM)(MonoS1.A) 15 | // 's3i - assertOrderedNEV(MonoS3.ValuesM)(MonoS3.A, MonoS3.B, MonoS3.C) 16 | "s1" - assertOrderedNEV(adtValuesManually[MonoS1](MonoS1.A))(MonoS1.A) 17 | "s3" - assertOrderedNEV(adtValuesManually[MonoS3](MonoS3.A, MonoS3.B, MonoS3.C))(MonoS3.A, MonoS3.B, MonoS3.C) 18 | } 19 | "dupTypes" - { 20 | "ok" - assertOrderedNEV(adtValuesManually[MonoD2](MonoD2.A, MonoD2.B(true), MonoD2.B(false)))(MonoD2.A, MonoD2.B(true), MonoD2.B(false)) 21 | } 22 | "dupValues" - { 23 | "S1" - assertFail(compileError("adtValuesManually[MonoS1](MonoS1.A, MonoS1.A)")) 24 | "S3" - assertFail(compileError("adtValuesManually[MonoS3](MonoS3.A, MonoS3.B, MonoS3.B, MonoS3.C)")) 25 | } 26 | "incomplete" - { 27 | "O" - assertFail(compileError("adtValuesManually[MonoS3](MonoS3.A, MonoS3.C)")) 28 | "C" - assertFail(compileError("adtValuesManually[MonoD2](MonoD2.A)")) 29 | } 30 | } 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/Algebras.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.free.Free 4 | import cats.{Functor, Monad} 5 | 6 | final class FAlgebraOps[F[_], A](private val self: FAlgebra[F, A]) extends AnyVal { 7 | 8 | def toFAlgebraM[M[_]](implicit M: Monad[M]): FAlgebraM[M, F, A] = 9 | fa => M.point(self(fa)) 10 | 11 | def toRAlgebra(implicit F: Functor[F]): RAlgebra[F, A] = 12 | ffa => self(F.map(ffa)(_._2)) 13 | 14 | def toCVAlgebra(implicit F: Functor[F]): CVAlgebra[F, A] = 15 | fa => self(F.map(fa)(_.head)) 16 | 17 | def zip[B](that: FAlgebra[F, B])(implicit F: Functor[F]): FAlgebra[F, (A, B)] = 18 | fab => { 19 | val a = self(F.map(fab)(_._1)) 20 | val b = that(F.map(fab)(_._2)) 21 | (a, b) 22 | } 23 | } 24 | 25 | final class FCoalgebraOps[F[_], A](private val self: FCoalgebra[F, A]) extends AnyVal { 26 | 27 | def toFCoalgebraM[M[_]](implicit M: Monad[M]): FCoalgebraM[M, F, A] = 28 | a => M.point(self(a)) 29 | 30 | def toRCoalgebra(implicit F: Functor[F]): RCoalgebra[F, A] = 31 | a => F.map(self(a))(Right(_)) 32 | 33 | def toCVCoalgebra(implicit F: Functor[F]): CVCoalgebra[F, A] = 34 | a => F.map(self(a))(Free.pure) 35 | 36 | def cozip[B](that: FCoalgebra[F, B])(implicit F: Functor[F]): FCoalgebra[F, Either[A, B]] = { 37 | case Left (a) => F.map(self(a))(Left(_)) 38 | case Right(b) => F.map(that(b))(Right(_)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /recursion/shared/src/test/scala/japgolly/microlibs/recursion/MathExpr.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.{Functor, ~>} 4 | 5 | sealed abstract class MathExpr[+A] 6 | object MathExpr { 7 | case class Num(value: Int) extends MathExpr[Nothing] 8 | case class Add[+A](a: A, b: A) extends MathExpr[A] 9 | 10 | val eval: FAlgebra[MathExpr, Int] = { 11 | case Num(i) => i 12 | case Add(a, b) => a + b 13 | } 14 | 15 | val print: FAlgebra[MathExpr, String] = { 16 | case Num(i) => i.toString 17 | case Add(a, b) => s"($a + $b)" 18 | } 19 | 20 | val plusOnes: FCoalgebra[MathExpr, Int] = 21 | i => if (i < 2) MathExpr.Num(i) else MathExpr.Add(1, i - 1) 22 | 23 | implicit val functor: Functor[MathExpr] = 24 | new Functor[MathExpr] { 25 | override def map[A, B](fa: MathExpr[A])(f: A => B) = fa match { 26 | case n: Num => n 27 | case Add(a, b) => Add(f(a), f(b)) 28 | } 29 | } 30 | 31 | val add10 = new (MathExpr ~> MathExpr) { 32 | override def apply[A](fa: MathExpr[A]) = fa match { 33 | case MathExpr.Num(n) => MathExpr.Num(n + 10) 34 | case e@ MathExpr.Add(_, _) => e 35 | } 36 | } 37 | 38 | object Helpers { 39 | type FM = Fix[MathExpr] 40 | type MF = MathExpr[Fix[MathExpr]] 41 | implicit def autoFix[A](a: A)(implicit f: A => MF): FM = Fix(f(a)) 42 | implicit def num(i: Int): MF = Num(i) 43 | def add(a: FM, b: FM): FM = Add(a, b) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test-util/shared/src/main/scala/japgolly/microlibs/testutil/TestUtilImplicits.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | import japgolly.microlibs.testutil.TestUtil.fail 4 | import sourcecode.Line 5 | 6 | trait TestUtilImplicits { 7 | import TestUtilImplicits._ 8 | 9 | implicit def toTestUtilEitherExt[A, B](x: Either[A, B])(implicit l: Line): EitherExt[A, B] = 10 | new EitherExt(x) 11 | 12 | implicit def toTestUtilOptionExt[A](x: Option[A])(implicit l: Line): OptionExt[A] = 13 | new OptionExt(x) 14 | } 15 | 16 | object TestUtilImplicits { 17 | 18 | implicit class EitherExt[A, B](private val self: Either[A, B])(implicit l: Line) { 19 | 20 | def getOrThrow(): B = 21 | self.fold(e => fail(s"Expected Right(_), found Left($e)", clearStackTrace = false), identity) 22 | 23 | def getOrThrow(moreInfo: => String): B = 24 | self.fold(e => fail(s"${moreInfo.replaceFirst("\\.?$", ".")} Expected Right(_), found Left($e)"), identity) 25 | 26 | def getLeftOrThrow(): A = 27 | self.fold(identity, e => fail(s"Expected Left(_), found Right($e)", clearStackTrace = false)) 28 | 29 | def getLeftOrThrow(moreInfo: => String): A = 30 | self.fold(identity, e => fail(s"${moreInfo.replaceFirst("\\.?$", ".")} Expected Left(_), found Right($e)")) 31 | } 32 | 33 | implicit class OptionExt[A](private val self: Option[A])(implicit l: Line) { 34 | def getOrThrow(moreInfo: => String): A = 35 | self.getOrElse(fail(s"${moreInfo.replaceFirst("\\.?$", ".")} Expected Some(_), found None")) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /bin/gen-tuple_ext: -------------------------------------------------------------------------------- 1 | #!/bin/env scala 2 | // vim: set ft=scala : 3 | 4 | val comma = ", " 5 | def T(i: Int) = (64+i).toChar.toString 6 | def member(i: Int) = "t._"+i 7 | 8 | for (a <- (2 to 9)) { 9 | def m (f: Int => String): List[String] = (1 to a).toList.map(f) 10 | def mc(f: Int => String): String = m(f).mkString(comma) 11 | def mt(f: Int => String): String = m(f).mkString("(",comma,")") 12 | 13 | var o = List.empty[String] 14 | for (i <- (1 to a)) { 15 | val c = T(i) 16 | 17 | def l (hit: String, miss: Int => String): List[String] = (1 to a).toList.map(j => if (i==j) hit else miss(j)) 18 | def lc(hit: String, miss: Int => String): String = l(hit,miss).mkString(comma) 19 | def lt(hit: String, miss: Int => String): String = l(hit,miss).mkString("(",comma,")") 20 | 21 | o ::= s"@inline def map$i[X](f: $c => X): (${lc("X",T)}) = ${lt(s"f(t._$i)", member)}" 22 | o ::= s"@inline def put$i[X](x: X): (${lc("X",T)}) = ${lt("x", "t._"+_)}" 23 | 24 | if (a <= 4) { 25 | def wrap(l: List[String]) = { 26 | val x = l.filter(_.nonEmpty) 27 | val s = x mkString comma 28 | if (x.length > 1) s"($s)" else s 29 | } 30 | val rt = wrap(l("",T)) 31 | val rv = wrap(l("",member)) 32 | o ::= s"@inline def consume$i[U](f: ${T(i)} => U): $rt = {f(t._$i); $rv}" 33 | } 34 | } 35 | 36 | println(s"@inline final implicit class JSLE_Tuple${a}[${mc(T)}](private val t: ${mt(T)}) extends AnyVal {") 37 | println(o.sorted.map(" "+_) mkString "\n") 38 | println("}") 39 | } 40 | println() 41 | -------------------------------------------------------------------------------- /disjunction/shared/src/main/scala/japgolly/microlibs/disjunction/Exports.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.disjunction 2 | 3 | import scala.util.control.NonFatal 4 | 5 | trait Exports { 6 | 7 | final type -\/[+A] = scala.util.Left[A, Nothing] 8 | final type \/-[+A] = scala.util.Right[Nothing, A] 9 | 10 | @scala.annotation.showAsInfix 11 | final type \/[+A, +B] = Either[A, B] 12 | 13 | @inline final def -\/[A](a: A): -\/[A] = Left(a) 14 | @inline final def \/-[A](a: A): \/-[A] = Right(a) 15 | @inline final def \/ = Exports.\/ 16 | 17 | @inline final implicit def implicitIgnoreLeftTypeOnRight[A, B, R](r: Right[A, R]): Right[B, R] = 18 | r.asInstanceOf[Right[B, R]] 19 | 20 | @inline final implicit def implicitIgnoreRightTypeOnLeft[A, B, L](r: Left[L, A]): Left[L, B] = 21 | r.asInstanceOf[Left[L, B]] 22 | 23 | @inline final implicit def implicitDisjEitherOps[E, A](a: Either[E, A]): Exports.EitherOps[E, A] = 24 | new Exports.EitherOps(a) 25 | } 26 | 27 | object Exports { 28 | 29 | object \/ { 30 | def fromTryCatchNonFatal[A](a: => A): Either[Throwable, A] = 31 | try Right(a) catch { case NonFatal(e) => Left(e) } 32 | 33 | def fromTryCatch[A](a: => A): Either[Throwable, A] = 34 | try Right(a) catch { case e: Throwable => Left(e) } 35 | } 36 | 37 | final class EitherOps[E, A](private val e: Either[E, A]) extends AnyVal { 38 | def leftMap[B](f: E => B): Either[B, A] = 39 | e match { 40 | case r: Right[E, A] => r.asInstanceOf[Right[B, A]] 41 | case Left(e) => Left(f(e)) 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /utils/jvm/src/main/scala/japgolly/microlibs/utils/PlatformJVM.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import java.util.concurrent.ConcurrentHashMap 5 | import java.util.function.{Function => J8Fn} 6 | import scala.collection.immutable.IntMap 7 | 8 | object PlatformJVM extends Platform { 9 | 10 | override def memo[A: UnivEq, B](f: A => B): A => B = { 11 | val cache = new ConcurrentHashMap[A, B](32) 12 | val mf = new J8Fn[A, B] { override def apply(a: A): B = f(a) } 13 | a => cache.computeIfAbsent(a, mf) 14 | } 15 | 16 | override def looseMemo[A: UnivEq, B](): LooseMemo[A, B] = { 17 | val cache = new ConcurrentHashMap[A, B](32) 18 | (a, b) => cache.computeIfAbsent(a, new J8Fn[A, B] { override def apply(a: A): B = b }) 19 | } 20 | 21 | override def memoInt[A](f: Int => A): Int => A = { 22 | val lock = new AnyRef 23 | var m = IntMap.empty[A] 24 | i => { 25 | 26 | def cacheMiss() = { 27 | val a = f(i) 28 | m = m.updated(i, a) 29 | a 30 | } 31 | 32 | def withLock = 33 | lock.synchronized(m.getOrElse(i, cacheMiss())) 34 | 35 | m.getOrElse(i, withLock) 36 | } 37 | } 38 | 39 | override def memoThunk[A](f: () => A): () => A = { 40 | val lock = new AnyRef 41 | var oa: Option[A] = None 42 | () => { 43 | def cacheMiss() = { 44 | val a = f() 45 | oa = Some(a) 46 | a 47 | } 48 | 49 | def withLock = 50 | lock.synchronized(oa.getOrElse(cacheMiss())) 51 | 52 | oa.getOrElse(withLock) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /adt-macros/shared/src/test/scala/a/AA.scala: -------------------------------------------------------------------------------- 1 | package aa 2 | 3 | //import japgolly.microlibs.adt_macros.AdtMacros 4 | 5 | sealed abstract class MonoS1 6 | object MonoS1 { 7 | case object A extends MonoS1 8 | // val Values = AdtMacros.adtValues[MonoS1] // SI-7046 9 | // val ValuesM = AdtMacros.adtValuesManually[MonoS1](A) // SI-7046 10 | } 11 | 12 | sealed trait MonoS3 13 | object MonoS3 { 14 | case object A extends MonoS3 15 | case object B extends MonoS3 16 | case object C extends MonoS3 17 | // // val Values = AdtMacros.adtValues[MonoS3] // SI-7046 18 | // // val ValuesM = AdtMacros.adtValuesManually[MonoS3](A, B, C) // SI-7046 19 | } 20 | 21 | trait Unsealed 22 | object Unsealed { 23 | case object A extends Unsealed 24 | } 25 | 26 | sealed abstract class MonoD1 27 | object MonoD1 { 28 | case class I(i: Int) extends MonoD1 29 | } 30 | 31 | sealed abstract class MonoD2 32 | object MonoD2 { 33 | case object A extends MonoD2 34 | case class B(i: Boolean) extends MonoD2 35 | } 36 | 37 | sealed abstract class MonoD 38 | object MonoD { 39 | case class A() extends MonoD 40 | case class B(int: Int) extends MonoD 41 | case object C extends MonoD 42 | case class D(a: MonoD, b: MonoD) extends MonoD 43 | } 44 | 45 | sealed trait MonoSub 46 | object MonoSub { 47 | case object A extends MonoSub 48 | sealed abstract class B extends MonoSub 49 | case object B1 extends B 50 | case object B2 extends B 51 | } 52 | 53 | object EmptySubType { 54 | sealed trait A 55 | sealed trait B extends A 56 | sealed trait C extends A 57 | case object D extends C 58 | sealed abstract class E extends A 59 | } -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/IntIncrementer.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import java.util.concurrent.atomic.AtomicInteger 4 | 5 | object IntIncrementer { 6 | 7 | trait Dsl[F[_]] { 8 | 9 | def apply(): F[Int] = 10 | apply(1) 11 | 12 | def apply(startAt: Int): F[Int] = 13 | apply(identity, startAt) 14 | 15 | def apply[A](f: Int => A): F[A] = 16 | apply(f, 1) 17 | 18 | def apply[A](f: Int => A, startAt: Int): F[A] 19 | } 20 | 21 | // =================================================================================================================== 22 | 23 | object threadUnsafe extends Dsl[Function0] { 24 | override def apply[A](f: Int => A, startAt: Int): () => A = { 25 | var prev = startAt 26 | () => { 27 | prev += 1 28 | f(prev) 29 | } 30 | } 31 | } 32 | 33 | object volatile extends Dsl[Function0] { 34 | override def apply[A](f: Int => A, startAt: Int): () => A = { 35 | @volatile var prev = startAt 36 | () => { 37 | prev += 1 38 | f(prev) 39 | } 40 | } 41 | } 42 | 43 | object cas extends Dsl[Function0] { 44 | override def apply[A](f: Int => A, startAt: Int): () => A = { 45 | val prev = new AtomicInteger(startAt) 46 | () => f(prev.incrementAndGet()) 47 | } 48 | } 49 | 50 | def syncOn(lock: AnyRef): Dsl[Function0] = 51 | new Dsl[Function0] { 52 | override def apply[A](f: Int => A, startAt: Int): () => A = { 53 | var prev = startAt 54 | () => { 55 | val n = lock.synchronized { prev += 1; prev } 56 | f(n) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/LongIncrementer.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import java.util.concurrent.atomic.AtomicLong 4 | 5 | object LongIncrementer { 6 | 7 | trait Dsl[F[_]] { 8 | 9 | def apply(): F[Long] = 10 | apply(1L) 11 | 12 | def apply(startAt: Long): F[Long] = 13 | apply(identity, startAt) 14 | 15 | def apply[A](f: Long => A): F[A] = 16 | apply(f, 1L) 17 | 18 | def apply[A](f: Long => A, startAt: Long): F[A] 19 | } 20 | 21 | // =================================================================================================================== 22 | 23 | object threadUnsafe extends Dsl[Function0] { 24 | override def apply[A](f: Long => A, startAt: Long): () => A = { 25 | var prev = startAt 26 | () => { 27 | prev += 1 28 | f(prev) 29 | } 30 | } 31 | } 32 | 33 | object volatile extends Dsl[Function0] { 34 | override def apply[A](f: Long => A, startAt: Long): () => A = { 35 | @volatile var prev = startAt 36 | () => { 37 | prev += 1 38 | f(prev) 39 | } 40 | } 41 | } 42 | 43 | object cas extends Dsl[Function0] { 44 | override def apply[A](f: Long => A, startAt: Long): () => A = { 45 | val prev = new AtomicLong(startAt) 46 | () => f(prev.incrementAndGet()) 47 | } 48 | } 49 | 50 | def syncOn(lock: AnyRef): Dsl[Function0] = 51 | new Dsl[Function0] { 52 | override def apply[A](f: Long => A, startAt: Long): () => A = { 53 | var prev = startAt 54 | () => { 55 | val n = lock.synchronized { prev += 1; prev } 56 | f(n) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/RomanNumeral.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import scala.annotation.switch 4 | 5 | /** 6 | * Conversion to and from roman numerals. 7 | */ 8 | object RomanNumeral { 9 | 10 | private[this] val genData: List[(String, Int)] = 11 | List( 12 | ("M", 1000), ("CM", 900), ("D", 500), ("CD", 400), ("C", 100), ("XC", 90), 13 | ("L", 50), ("XL", 40), ("X", 10), ("IX", 9), ("V", 5), ("IV", 4), ("I", 1)) 14 | 15 | def apply(number: Int): String = { 16 | assert(number > 0) 17 | val sb = new StringBuilder 18 | var n = number 19 | for (d <- genData) { 20 | val r = d._1 21 | for (_ <- 0 until (n / d._2)) 22 | sb append r 23 | n = n % d._2 24 | } 25 | sb.result() 26 | } 27 | 28 | private def parseChar(c: Char): Option[Int] = 29 | (c.toUpper: @switch) match { 30 | case 'I' => Some(1) 31 | case 'V' => Some(5) 32 | case 'X' => Some(10) 33 | case 'L' => Some(50) 34 | case 'C' => Some(100) 35 | case 'D' => Some(500) 36 | case 'M' => Some(1000) 37 | case _ => None 38 | } 39 | 40 | def parse(s: String): Option[Int] = 41 | if (s.isEmpty) 42 | None 43 | else { 44 | var ok = true 45 | var sum = 0 46 | var last = 0 47 | for (c <- s) { 48 | parseChar(c) match { 49 | case Some(r) => 50 | sum += r 51 | if (last < r) sum -= last << 1 52 | last = r 53 | case None => 54 | ok = false 55 | } 56 | } 57 | if (ok && s.compareToIgnoreCase(apply(sum)) == 0) 58 | Some(sum) 59 | else 60 | None 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-2.12/japgolly/microlibs/stdlib_ext/StdlibSpecific.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | /* 4 | +------------+ 5 | | Scala 2.12 | 6 | +------------+ 7 | */ 8 | 9 | trait ScalaSpecificStdlibExt { 10 | import ScalaSpecificStdlibExt._ 11 | 12 | @inline final implicit def JSLE12_TraversableOnce[A](as: TraversableOnce[A]) = new JSLE12_TraversableOnce(as) 13 | @inline final implicit def JSLE12_OptionObj (self: Option.type) = new JSLE12_OptionObj(self) 14 | } 15 | 16 | object ScalaSpecificStdlibExt { 17 | 18 | final class JSLE12_OptionObj(private val self: Option.type) extends AnyVal { 19 | def when[A](cond: Boolean)(a: => A): Option[A] = 20 | if (cond) Some(a) else None 21 | 22 | @inline def unless[A](cond: Boolean)(a: => A): Option[A] = 23 | when(!cond)(a) 24 | } 25 | 26 | final class JSLE12_TraversableOnce[A](private val as: TraversableOnce[A]) extends AnyVal { 27 | def minOption[B >: A: Ordering]: Option[A] = 28 | if (as.isEmpty) None else Some(as.min[B]) 29 | 30 | def maxOption[B >: A: Ordering]: Option[A] = 31 | if (as.isEmpty) None else Some(as.max[B]) 32 | 33 | @deprecated("Use minByOption", "2.0 (and Scala 2.13)") 34 | def minOptionBy[B: Ordering](f: A => B): Option[A] = 35 | minByOption(f) 36 | 37 | @deprecated("Use maxByOption", "2.0 (and Scala 2.13)") 38 | def maxOptionBy[B: Ordering](f: A => B): Option[A] = 39 | maxByOption(f) 40 | 41 | def minByOption[B: Ordering](f: A => B): Option[A] = 42 | if (as.isEmpty) None else Some(as.minBy(f)) 43 | 44 | def maxByOption[B: Ordering](f: A => B): Option[A] = 45 | if (as.isEmpty) None else Some(as.maxBy(f)) 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /utils/jvm/src/main/scala/japgolly/microlibs/utils/FileUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import java.io.File 4 | import java.nio.charset.StandardCharsets 5 | import java.nio.file.{Files, Paths} 6 | import scala.io.{Codec, Source} 7 | 8 | object FileUtils { 9 | 10 | /** Add to SBT: 11 | * 12 | * {{{ 13 | * javaOptions += ("-DbaseDirectory=" + baseDirectory.value.getAbsolutePath) 14 | * }}} 15 | */ 16 | lazy val baseDirectory: File = { 17 | val p = "baseDirectory" 18 | val d = Option(System.getProperty(p)).getOrElse(sys.error(s"Property [$p] isn't specified.")) 19 | val f = new File(d) 20 | require(f.exists() && f.isDirectory(), s"Directory not found: ${f.getAbsolutePath}") 21 | f 22 | } 23 | 24 | def baseDirectoryFile(suffix: String): File = { 25 | val f = baseDirectory 26 | new File(s"${f.getAbsolutePath}/$suffix") 27 | } 28 | 29 | def testResourceFile(path: String): File = 30 | baseDirectoryFile(s"src/test/resources/$path") 31 | 32 | def write(filename: String, content: String): Unit = { 33 | Files.write(Paths.get(filename), content.getBytes(StandardCharsets.UTF_8)) 34 | () 35 | } 36 | 37 | def read(filename: String): String = { 38 | val src = Source.fromFile(filename)(Codec.UTF8) 39 | try src.mkString finally src.close() 40 | } 41 | 42 | def readResource(filename: String, tryAlt: Boolean = true): String = 43 | try { 44 | val src = Source.fromResource(filename)(Codec.UTF8) 45 | try src.mkString finally src.close() 46 | } catch { 47 | case _: NullPointerException if tryAlt => 48 | val alt = 49 | if (filename startsWith "/") 50 | filename.drop(1) 51 | else 52 | "/" + filename 53 | readResource(alt, tryAlt = false) 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /recursion/shared/src/test/scala/japgolly/microlibs/recursion/ListF.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.{Functor, ~>} 4 | 5 | sealed trait ListF[+A, +F] 6 | case class ConsF[+A, +F](head: A, tail: F) extends ListF[A, F] 7 | case object NilF extends ListF[Nothing, Nothing] 8 | object ListF { 9 | implicit def functor[A]: Functor[ListF[A, *]] = new Functor[ListF[A, *]] { 10 | override def map[B, C](fa: ListF[A, B])(f: B => C): ListF[A, C] = 11 | fa match { 12 | case ConsF(h, t) => ConsF(h, f(t)) 13 | case NilF => NilF 14 | } 15 | } 16 | } 17 | 18 | object FixList { 19 | type FixList[A] = Fix[ListF[A, *]] 20 | 21 | def apply[A](as: A*): FixList[A] = 22 | Recursion.ana[ListF[A, *], List[A]](listCoalg)(as.toList) 23 | 24 | def toList[A](f: FixList[A]): List[A] = 25 | Recursion.cata[ListF[A, *], List[A]](listAlg)(f) 26 | 27 | def listAlg[A]: FAlgebra[ListF[A, *], List[A]] = { 28 | case NilF => Nil 29 | case ConsF(h, t) => h :: t 30 | } 31 | 32 | def listCoalg[A]: FCoalgebra[ListF[A, *], List[A]] = { 33 | case Nil => NilF 34 | case h :: t => ConsF(h, t) 35 | } 36 | 37 | val zeroOutOdds = new (ListF[Int, *] ~> ListF[Int, *]) { 38 | override def apply[A](fa: ListF[Int, A]) = fa match { 39 | case ConsF(n, t) if n % 2 == 1 => ConsF(0, t) 40 | case e => e 41 | } 42 | } 43 | 44 | val stopAboveFive = new (ListF[Int, *] ~> ListF[Int, *]) { 45 | override def apply[A](fa: ListF[Int, A]) = fa match { 46 | case ConsF(n, _) if n > 5 => NilF 47 | case e => e 48 | } 49 | } 50 | 51 | val sum: FAlgebra[ListF[Int, *], Int] = { 52 | case ConsF(h, t) => h + t 53 | case NilF => 0 54 | } 55 | 56 | val ascStream: FCoalgebra[ListF[Int, *], Int] = 57 | i => if (i > 100) NilF else ConsF(i, i + 1) 58 | } 59 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/MutableFn0.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import scala.runtime.AbstractFunction0 4 | 5 | trait MutableFn0[A] extends AbstractFunction0[A] { 6 | def getFn: () => A 7 | def setFn(f: () => A): this.type 8 | 9 | final def set(f: => A): this.type = 10 | setFn(() => f) 11 | } 12 | 13 | object MutableFn0 { 14 | 15 | def fromVariable[A](v: Variable[() => A]): MutableFn0[A] = 16 | new MutableFn0[A] { 17 | 18 | override def apply(): A = 19 | v.get()() 20 | 21 | override def getFn: () => A = 22 | v.get() 23 | 24 | override def setFn(f: () => A): this.type = { 25 | v.set(f) 26 | this 27 | } 28 | } 29 | 30 | class Dsl[A](make: (() => A) => Variable[() => A]) { 31 | 32 | def apply(a: => A): MutableFn0[A] = 33 | withFn(() => a) 34 | 35 | def withFn(f: () => A): MutableFn0[A] = 36 | MutableFn0.fromVariable(make(f)) 37 | } 38 | 39 | class DslWithDefault[A](default: () => A, make: (() => A) => Variable[() => A]) extends Dsl(make) { 40 | def apply(): MutableFn0[A] = 41 | withFn(default) 42 | } 43 | 44 | class DslWithDefaultSelector[A](default: () => A) { 45 | def threadUnsafe = new DslWithDefault[A](default, Variable(_)) 46 | def volatile = new DslWithDefault[A](default, Variable.volatile(_)) 47 | def syncOn(lock: AnyRef) = new DslWithDefault[A](default, Variable.syncOn(lock)) 48 | } 49 | 50 | // =================================================================================================================== 51 | 52 | def withDefault[A](a: => A): DslWithDefaultSelector[A] = 53 | new DslWithDefaultSelector[A](() => a) 54 | 55 | def threadUnsafe[A] = new Dsl[A](Variable(_)) 56 | def volatile[A] = new Dsl[A](Variable.volatile(_)) 57 | def syncOn[A](lock: AnyRef) = new Dsl[A](Variable.syncOn(lock)) 58 | } 59 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/TransparentInlineUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | object TransparentInlineUtils: 4 | 5 | transparent inline def nonBlank(inline str: String): Option[String] = 6 | inline trim(str) match 7 | case "" => None 8 | case v => Some[v.type](v) 9 | 10 | transparent inline def replaceFirst(str: String, regex: String, repl: String): String = 11 | ${ QuotingUtils.replaceFirst('str, 'regex, 'repl) } 12 | 13 | transparent inline def replaceAll(str: String, regex: String, repl: String): String = 14 | ${ QuotingUtils.replaceAll('str, 'regex, 'repl) } 15 | 16 | transparent inline def trim(str: String): String = 17 | ${ QuotingUtils.trim('str) } 18 | 19 | transparent inline def trimLowerCaseNonBlank(inline s: String): Option[String] = 20 | inline trim(toLowerCase(s)) match 21 | case "" => None 22 | case v => Some[v.type](v) 23 | 24 | transparent inline def trimLowerCaseNonBlank(inline o: Option[String]): Option[String] = 25 | inline o match 26 | case Some(v) => trimLowerCaseNonBlank(v) 27 | case None => None 28 | 29 | transparent inline def toLowerCase(str: String): String = 30 | ${ QuotingUtils.toLowerCase('str) } 31 | 32 | transparent inline def toUpperCase(str: String): String = 33 | ${ QuotingUtils.toUpperCase('str) } 34 | 35 | transparent inline def toInt(str: String): Int = 36 | ${ QuotingUtils.toInt('str) } 37 | 38 | transparent inline def toLong(str: String): Long = 39 | ${ QuotingUtils.toLong('str) } 40 | 41 | transparent inline def toBoolean(str: String): Boolean = 42 | ${ QuotingUtils.toBoolean('str) } 43 | 44 | transparent inline def showCode(inline body: Any): String = 45 | ${ QuotingUtils.showCode('body) } 46 | 47 | transparent inline def showTasty(inline body: Any): String = 48 | ${ QuotingUtils.showTasty('body) } 49 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/Memo.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import scala.annotation.nowarn 5 | 6 | object Memo { 7 | // Because of annoying Intellij IDEA 8 | private def platform: Platform = japgolly.microlibs.utils.Platform 9 | 10 | def apply[A: UnivEq, B](f: A => B): A => B = 11 | platform memo f 12 | 13 | def apply[A: UnivEq, B: UnivEq, Z](f: (A, B) => Z): (A, B) => Z = { 14 | val m = apply[(A, B), Z](f.tupled) 15 | (a, b) => m((a, b)) 16 | } 17 | 18 | def bool[A](f: Boolean => A): Boolean => A = { 19 | val t = f(true) 20 | val z = f(false) 21 | b => if (b) t else z 22 | } 23 | 24 | def int[A](f: Int => A): Int => A = 25 | platform memoInt f 26 | 27 | def thunk[A](a: => A): () => A = 28 | platform.memoThunk(() => a) 29 | 30 | def curry2[A: UnivEq, B: UnivEq, Z](f: A => B => Z): A => B => Z = 31 | Memo[A, B => Z](a => Memo(f(a))) 32 | 33 | def curry3[A: UnivEq, B: UnivEq, C: UnivEq, Z](f: A => B => C => Z): A => B => C => Z = 34 | Memo[A, B => C => Z](a => curry2(f(a))) 35 | 36 | def by[I, K](memoKey: I => K) = new By(memoKey) 37 | final class By[I, K] private[Memo] (private val memoKey: I => K) extends AnyVal { 38 | def apply[O](value: I => O)(implicit ev: UnivEq[K]): I => O = { 39 | val m = platform.looseMemo[K, O]() 40 | i => m(memoKey(i), value(i)) 41 | } 42 | } 43 | 44 | def byRef[A <: AnyRef, B](f: A => B): A => B = 45 | by[A, Ref[A]](Ref.apply)(f) 46 | 47 | // Immutable maps are optimised at low values to not even create a hash map 48 | @nowarn("cat=unused") 49 | def withUnsyncronizedMap[K: UnivEq, V](f: K => V): K => V = { 50 | var m = Map.empty[K, V] 51 | k => 52 | if (m.contains(k)) 53 | m(k) 54 | else { 55 | val v = f(k) 56 | m = m.updated(k, v) 57 | v 58 | } 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /adt-macros/shared/src/test/scala-3/japgolly/microlibs/adt_macros/Scala3AdtMacroTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.adt_macros 2 | 3 | import japgolly.microlibs.nonempty.{NonEmptySet, NonEmptyVector} 4 | import utest._ 5 | import aa._ 6 | import AdtMacros._ 7 | import japgolly.univeq.UnivEq 8 | 9 | object Scala3AdtMacroTest extends TestSuite { 10 | import AdtMacroTest._ 11 | 12 | override def tests = Tests { 13 | 14 | "adtValuesManually" - { 15 | "simple" - { 16 | // 's1i - assertOrderedNEV(MonoS1.ValuesM)(MonoS1.A) 17 | // 's3i - assertOrderedNEV(MonoS3.ValuesM)(MonoS3.A, MonoS3.B, MonoS3.C) 18 | "s1" - assertOrderedNEV(adtValuesManually[MonoS1]()(MonoS1.A))(MonoS1.A) 19 | "s3" - assertOrderedNEV(adtValuesManually[MonoS3]()(MonoS3.A, MonoS3.B, MonoS3.C))(MonoS3.A, MonoS3.B, MonoS3.C) 20 | } 21 | "dupTypes" - { 22 | "ok" - assertOrderedNEV(adtValuesManually[MonoD2](allowDuplicateTypes = true)(MonoD2.A, MonoD2.B(true), MonoD2.B(false)))(MonoD2.A, MonoD2.B(true), MonoD2.B(false)) 23 | "ko" - assertFail(compileError("adtValuesManually[MonoD2]()(MonoD2.A, MonoD2.B(true), MonoD2.B(false))")) 24 | } 25 | "dupValues" - { 26 | "ok" - assertOrderedNEV(adtValuesManually[MonoS3](allowDuplicateValues = true)(MonoS3.A, MonoS3.B, MonoS3.C, MonoS3.B))(MonoS3.A, MonoS3.B, MonoS3.C, MonoS3.B) 27 | "S1" - assertFail(compileError("adtValuesManually[MonoS1]()(MonoS1.A, MonoS1.A)")) 28 | "S3" - assertFail(compileError("adtValuesManually[MonoS3]()(MonoS3.A, MonoS3.B, MonoS3.B, MonoS3.C)")) 29 | "dupD2" - assertFail(compileError("adtValuesManually[MonoD2](allowDuplicateTypes = true)(MonoD2.B(true), MonoD2.A, MonoD2.B(true), MonoD2.B(false))")) 30 | } 31 | "incomplete" - { 32 | "O" - assertFail(compileError("adtValuesManually[MonoS3]()(MonoS3.A, MonoS3.C)")) 33 | "C" - assertFail(compileError("adtValuesManually[MonoD2]()(MonoD2.A)")) 34 | } 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/Init.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | import MacroEnv.* 5 | 6 | object Init: 7 | def apply(using qq : Quotes) 8 | (freshNameFn: Int => String = "i" + _, 9 | flags : qq.reflect.Flags = qq.reflect.Flags.EmptyFlags): Init { val q: qq.type } = 10 | new Init(using qq)(freshNameFn, flags) 11 | .asInstanceOf[Init { val q: qq.type }] // TODO: S3 bug 12 | 13 | class Init(using val q: Quotes) 14 | (freshNameFn: Int => String, 15 | flags : q.reflect.Flags 16 | ) { 17 | import q.reflect.* 18 | 19 | var seen = ExprMap.empty[Any, TypedValDef[Any]] 20 | var stmts = Vector.empty[Statement] 21 | 22 | private var vars = 0 23 | def newName(): String = 24 | vars += 1 25 | freshNameFn(vars) 26 | 27 | def +=(t: Statement): Unit = 28 | stmts :+= t 29 | 30 | def ++=(ts: IterableOnce[Statement]): Unit = 31 | ts.iterator.foreach(this.+=) 32 | 33 | def valDef[A: Type](expr : Expr[A], 34 | name : String = newName(), 35 | reuse : Boolean = true, 36 | extraFlags: Flags = Flags.EmptyFlags, 37 | onInit : Boolean = true, 38 | ): TypedValDef.WithQuotes[A, q.type] = 39 | val allFlags = flags | extraFlags 40 | val allowReuse = reuse && !allFlags.is(Flags.Mutable) 41 | val alreadySeen = if allowReuse then seen.get(expr) else None 42 | alreadySeen match 43 | case None => 44 | val vd = typedValDef(name, allFlags)(expr) 45 | if onInit then this += vd.valDef 46 | seen += ((expr, vd.subst[Any])) 47 | vd 48 | case Some(vd) => 49 | vd.subst[A].substQ 50 | 51 | def wrapTerm(term: Term): Block = 52 | Block(stmts.toList, term) 53 | 54 | def wrapExpr[A: Type](expr: Expr[A]): Expr[A] = 55 | wrapTerm(expr.asTerm).asExprOf[A] 56 | } 57 | -------------------------------------------------------------------------------- /recursion/README.md: -------------------------------------------------------------------------------- 1 | # Recursion Scheme Cheatsheet 2 | 3 | ## Basic 4 | 5 | | Name | Type | Desc | 6 | |--|--|--| 7 | | cata | `(f a -> a) -> Fix f -> a` | Fold | 8 | | ana | `(a -> f a) -> a -> Fix f` | Unfold | 9 | | hylo | `(a -> f a) -> (f b -> b) -> a -> b` | Efficiently unfold & fold | 10 | 11 | ## Promorphisms 12 | 13 | Transform (expand/prune/update) subtrees during morphism. 14 | 15 | | Name | Type | Desc | 16 | |--|--|--| 17 | | prepro | `(f ~> f) -> (f a -> a) -> Fix f -> a` | cata that transforms children before folding.
Top-most structure (i.e. the input) is not transformed.
Outside to inside. | 18 | | postpro | `(f ~> f) -> (a -> f a) -> a -> Fix f` | ana that creates a structure, transforming each new child
(i.e. the entire structure as exists at the end of a pass).
Top-most structure (i.e. the end result) is not transformed.
Inside to outside. | 19 | 20 | ## Elgot 21 | 22 | Hylos that can short-circuit (in terms of recursion depth). 23 | 24 | | Name | Type | Desc | 25 | |--|--|--| 26 | | elgot | `(a -> b \/ f a) -> (f b -> b) -> a -> b` | short-circuit during ana | 27 | | coelgot | `(a -> f a) -> ((a, () -> f b) -> b) -> a -> b` | short-circuit during cata | 28 | 29 | ## Para/apo 30 | 31 | Basic morphisms with read/write access to the subtree, (read for para/cata, write for apo/ana). 32 | 33 | | Name | Type | Desc | 34 | |--|--|--| 35 | | para | `(f (Fix f, a) -> a) -> Fix f -> a` | cata that has access to current subtree (`Fix f`)
as well as that subtree's folded result (`a`) | 36 | | apo | `(a -> f (Fix f \/ a)) -> a -> Fix f` | ana that can branch / short-circuit | 37 | 38 | ## Course-of-values 39 | 40 | | Name | Type | Desc | 41 | |--|--|--| 42 | | histo | `(f (Cofree f a) -> a) -> Fix f -> a` | cata that retains values of all previous (i.e. child) steps | 43 | | futu | `(a -> f (Free f a)) -> a -> Fix f` | ana that can build multiple levels in a single pass | 44 | | chrono | `(a -> f (Free f a)) ->`
`(f (Cofree f b) -> b) ->`
` a -> b` | hylo of above | 45 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/IndexLabelTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.utils.IndexLabel._ 4 | import utest._ 5 | 6 | object IndexLabelTest extends TestSuite { 7 | 8 | def testFn(ll: IndexLabel): (Int, String) => Unit = 9 | (i, s) => { 10 | val al = ll.label(i) 11 | val ap = ll.parse(s) 12 | assert(al == s) 13 | assert(ap == Option(i)) 14 | } 15 | 16 | override def tests = Tests { 17 | 18 | "numeric0" - { 19 | val test = testFn(NumericFrom0) 20 | " 0 ↔ 0" - test( 0, "0") 21 | " 1 ↔ 1" - test( 1, "1") 22 | "50 ↔ 50" - test(50, "50") 23 | } 24 | 25 | "numeric1" - { 26 | val test = testFn(NumericFrom1) 27 | " 0 ↔ 1" - test( 0, "1") 28 | " 1 ↔ 2" - test( 1, "2") 29 | "50 ↔ 51" - test(50, "51") 30 | } 31 | 32 | "alpha" - { 33 | val test = testFn(Alpha) 34 | " 0 ↔ a" - test( 0, "a" ) 35 | " 1 ↔ b" - test( 1, "b" ) 36 | " 4 ↔ e" - test( 4, "e" ) 37 | "25 ↔ z" - test(25, "z" ) 38 | "26 ↔ aa" - test(26, "aa") 39 | "27 ↔ ab" - test(27, "ab") 40 | "51 ↔ az" - test(51, "az") 41 | "52 ↔ ba" - test(52, "ba") 42 | } 43 | 44 | "roman" - { 45 | val test = testFn(Roman) 46 | " 0 ↔ i" - test( 0, "i" ) 47 | " 1 ↔ ii" - test( 1, "ii" ) 48 | " 2 ↔ iii" - test( 2, "iii" ) 49 | " 3 ↔ iv" - test( 3, "iv" ) 50 | " 4 ↔ v" - test( 4, "v" ) 51 | " 5 ↔ vi" - test( 5, "vi" ) 52 | " 6 ↔ vii" - test( 6, "vii" ) 53 | " 7 ↔ viii" - test( 7, "viii" ) 54 | " 8 ↔ ix" - test( 8, "ix" ) 55 | " 9 ↔ x" - test( 9, "x" ) 56 | "10 ↔ xi" - test(10, "xi" ) 57 | "13 ↔ xiv" - test(13, "xiv" ) 58 | "18 ↔ xix" - test(18, "xix" ) 59 | "19 ↔ xx" - test(19, "xx" ) 60 | "37 ↔ xxxviii" - test(37, "xxxviii") 61 | } 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/InlineUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | object InlineUtils: 4 | 5 | inline def warn(inline warning: String): Unit = 6 | ${ QuotingUtils.warn('warning) } 7 | 8 | transparent inline def printCode[A](inline body: A): A = 9 | println(showCode(body)) 10 | body 11 | 12 | transparent inline def printTasty[A](inline body: A): A = 13 | println(showTasty(body)) 14 | body 15 | 16 | // All of this is doulbed in TransparentInlineUtils 17 | 18 | inline def nonBlank(inline str: String): Option[String] = 19 | inline trim(str) match 20 | case "" => None 21 | case v => Some[v.type](v) 22 | 23 | inline def replaceFirst(str: String, regex: String, repl: String): String = 24 | ${ QuotingUtils.replaceFirst('str, 'regex, 'repl) } 25 | 26 | inline def replaceAll(str: String, regex: String, repl: String): String = 27 | ${ QuotingUtils.replaceAll('str, 'regex, 'repl) } 28 | 29 | inline def trim(str: String): String = 30 | ${ QuotingUtils.trim('str) } 31 | 32 | inline def trimLowerCaseNonBlank(inline s: String): Option[String] = 33 | inline trim(toLowerCase(s)) match 34 | case "" => None 35 | case v => Some[v.type](v) 36 | 37 | inline def trimLowerCaseNonBlank(inline o: Option[String]): Option[String] = 38 | inline o match 39 | case Some(v) => trimLowerCaseNonBlank(v) 40 | case None => None 41 | 42 | inline def toLowerCase(str: String): String = 43 | ${ QuotingUtils.toLowerCase('str) } 44 | 45 | inline def toUpperCase(str: String): String = 46 | ${ QuotingUtils.toUpperCase('str) } 47 | 48 | inline def toInt(str: String): Int = 49 | ${ QuotingUtils.toInt('str) } 50 | 51 | inline def toLong(str: String): Long = 52 | ${ QuotingUtils.toLong('str) } 53 | 54 | inline def toBoolean(str: String): Boolean = 55 | ${ QuotingUtils.toBoolean('str) } 56 | 57 | inline def showCode(inline body: Any): String = 58 | ${ QuotingUtils.showCode('body) } 59 | 60 | inline def showTasty(inline body: Any): String = 61 | ${ QuotingUtils.showTasty('body) } 62 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/test/scala/japgolly/microlibs/stdlib_ext/EscapeUtilsTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import japgolly.microlibs.testutil.TestUtil._ 4 | import java.lang.{StringBuilder => JStringBuilder} 5 | import scala.util.Random 6 | import sourcecode.Line 7 | import utest._ 8 | 9 | object EscapeUtilsTest extends TestSuite { 10 | 11 | private def withSB(f: JStringBuilder => Unit): String = { 12 | val sb = new JStringBuilder 13 | f(sb) 14 | sb.toString 15 | } 16 | 17 | override def tests = Tests { 18 | 19 | "quoteAndEscape" - { 20 | def test(input: String, expected: String = null)(implicit l: Line): Unit = { 21 | val expect = Option(expected).getOrElse(input) 22 | val expectQ = "\"" + expect + "\"" 23 | assertEq("escape", EscapeUtils.escape(input), expect) 24 | assertEq("quote", EscapeUtils.quote(input), expectQ) 25 | assertEq("escape sb", withSB(EscapeUtils.appendEscaped(_, input)), expect) 26 | assertEq("quote sb", withSB(EscapeUtils.appendQuoted(_, input)), expectQ) 27 | } 28 | "noNeed" - { 29 | "empty" - test("") 30 | "asciiMid" - { 31 | val exclude = "\\\"".toCharArray() 32 | val chars = (32 to 126).map(_.toChar).filterNot(exclude.contains).toVector 33 | test(Random.shuffle(chars).mkString) 34 | } 35 | } 36 | "needed" - { 37 | "whitelist" - test(" \" \\ \r \n \t \b ", " \\\" \\\\ \\r \\n \\t \\b ") 38 | "asciiLo" - { 39 | val actual = (0 to 31).map(_.toChar).mkString 40 | val expect = "\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" 41 | test(actual, expect) 42 | } 43 | "asciiHi" - { 44 | val chars = (127 to 255).map(_.toChar).toVector 45 | test(Random.shuffle(chars).mkString) 46 | } 47 | // "unicode" - test(Gen.unicode.map(c => if (c < 256) (c + 1000).toChar else c).string(128).sample()) 48 | "unicode" - test("もうどうでもいいや") 49 | } 50 | } 51 | 52 | } 53 | } -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/Field.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.deriving.* 4 | import scala.quoted.* 5 | import MacroEnv.* 6 | 7 | trait Field { 8 | type Name 9 | type Type 10 | 11 | val idx: Int 12 | val name: String 13 | def showType: String 14 | implicit val typeInstance: scala.quoted.Type[Type] 15 | 16 | override def toString = s"[Field $idx] $name: $showType" 17 | 18 | final def typeRepr(using q: Quotes): q.reflect.TypeRepr = 19 | q.reflect.TypeRepr.of(using typeInstance) 20 | 21 | // TODO: Do this without .productElement 22 | final def onProduct[P](p: Expr[P])(using Quotes, scala.quoted.Type[P]): Expr[Type] = 23 | '{ $p.asInstanceOf[Product].productElement(${Expr(idx)}).asInstanceOf[Type] } 24 | } 25 | 26 | object Fields { 27 | 28 | def summonFromMirror[A: Type](using Quotes): List[Field] = 29 | fromMirror(Expr.summonOrError[Mirror.Of[A]]) 30 | 31 | def fromMirror[A: Type](m: Expr[Mirror.Of[A]])(using Quotes): List[Field] = { 32 | import quotes.reflect.* 33 | 34 | def go[Ls: Type, Ts: Type](idx: Int): List[Field] = 35 | (Type.of[Ls], Type.of[Ts]) match 36 | case ('[l *: ll], '[t *: tt]) => 37 | val t = Type.of[t] 38 | val _idx = idx 39 | val _name = TypeRepr.of[l] match 40 | case ConstantType(StringConstant(n)) => n 41 | case _ => "?" 42 | val f: Field = new Field { 43 | override type Name = l 44 | override type Type = t 45 | override val idx = _idx 46 | override val name = _name 47 | override def showType = Type.show[t] 48 | override implicit val typeInstance = t 49 | } 50 | f :: go[ll, tt](idx + 1) 51 | 52 | case ('[EmptyTuple], _) => 53 | Nil 54 | 55 | m match 56 | case '{ $m: Mirror.ProductOf[A] { type MirroredElemLabels = ls; type MirroredElemTypes = ts }} => 57 | go[ls, ts](0) 58 | case '{ $m: Mirror.SumOf[A] { type MirroredElemLabels = ls; type MirroredElemTypes = ts }} => 59 | go[ls, ts](0) 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /nonempty/shared/src/main/scala/japgolly/microlibs/nonempty/NonEmpty.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.nonempty 2 | 3 | import cats.Eq 4 | import japgolly.univeq.UnivEq 5 | import scala.annotation.nowarn 6 | 7 | /** 8 | * Type indicating that its value has been proven to be non-empty. 9 | */ 10 | final class NonEmpty[A] private[NonEmpty] (val value: A) extends AnyVal { 11 | override def toString = s"NonEmpty($value)" 12 | } 13 | 14 | object NonEmpty { 15 | 16 | @inline implicit def autoNonEmptyValue[A](n: NonEmpty[A]): A = 17 | n.value 18 | 19 | /* 20 | * scala> class X(val i: Int) extends AnyVal 21 | * defined class X 22 | * 23 | * scala> new X(3) == new X(3) 24 | * res0: Boolean = true 25 | */ 26 | @nowarn("cat=unused") 27 | @inline implicit def nonEmptyUnivEq[A: UnivEq]: UnivEq[NonEmpty[A]] = 28 | UnivEq.force 29 | 30 | @inline def nonEmptyEqual[A](implicit e: Eq[A]): Eq[NonEmpty[A]] = 31 | Eq.by(_.value) 32 | 33 | @inline def force[A](a: A): NonEmpty[A] = 34 | new NonEmpty(a) 35 | 36 | @inline def apply[I, O](i: I)(implicit proof: Proof[I, O]): Option[O] = 37 | proof.tryProve(i) 38 | 39 | @inline def require_![I, O](i: I)(implicit proof: Proof[I, O]): O = 40 | NonEmpty(i) getOrElse sys.error(s"Data is empty: $i") 41 | 42 | // def disj[I, O](i: I)(implicit proof: Proof[I, O]): I \/ O = 43 | // NonEmpty(i).fold[I \/ O](-\/(i))(\/-.apply) 44 | 45 | // ------------------------------------------------------------------------------------------------------------------- 46 | // Proofs 47 | 48 | final case class Proof[I, O](tryProve: I => Option[O]) extends AnyVal 49 | 50 | type ProofMono[A] = Proof[A, NonEmpty[A]] 51 | 52 | trait ProofImplicitsLo { 53 | def testEmptiness[A](isEmpty: A => Boolean): ProofMono[A] = 54 | Proof(a => if (isEmpty(a)) None else Some(new NonEmpty(a))) 55 | 56 | implicit def proveTraversable[A <: Iterable[_]]: ProofMono[A] = 57 | testEmptiness(_.isEmpty) 58 | } 59 | 60 | object Proof extends ProofImplicitsLo { 61 | implicit def proveNES[A: UnivEq]: Proof[Set[A], NonEmptySet[A]] = 62 | Proof(NonEmptySet.option[A]) 63 | 64 | implicit def proveNEV[A]: Proof[Vector[A], NonEmptyVector[A]] = 65 | Proof(NonEmptyVector.option[A]) 66 | } 67 | } -------------------------------------------------------------------------------- /cats-ext/shared/src/main/scala-3/japgolly/microlibs/cats_ext/CatsMacros.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.cats_ext 2 | 3 | import japgolly.microlibs.compiletime.MacroEnv.* 4 | import scala.compiletime.* 5 | import scala.deriving.* 6 | import scala.quoted.* 7 | import cats.Eq 8 | 9 | object CatsMacros: 10 | 11 | inline def deriveEq[A]: Eq[A] = 12 | ${ deriveEqImpl[A](false) } 13 | 14 | inline def _deriveEq[A]: Eq[A] = 15 | ${ deriveEqImpl[A](true) } 16 | 17 | private def deriveEqImpl[A](debug: Boolean)(using Quotes, Type[A]): Expr[Eq[A]] = 18 | var result: Expr[Eq[A]] = null 19 | def log(msg: => Any) = if debug then println(msg) 20 | log("="*120) 21 | log(s"Beginning derivation of cats.Eq[${Type.show[A]}]") 22 | 23 | type Clause = MacroUtils.Fn2Clause[A, A, Boolean] 24 | 25 | def newInstance(f: Clause): Quotes ?=> Expr[Eq[A]] = '{ 26 | Eq.instance[A]((x, y) => ${f('x, 'y)}) 27 | } 28 | 29 | Expr.summon[Mirror.Of[A]] match 30 | 31 | case Some('{ $m: Mirror.ProductOf[A] }) => 32 | result = MacroUtils.CachedGivens[Eq].mirror(m).summonGivens().use[Eq[A]] { ctx => 33 | 34 | val clauses = 35 | ctx.fields.map[Clause](f => (x, y) => { 36 | val eq = ctx.lookup(f).expr 37 | '{ $eq.eqv(${f.onProduct(x)}, ${f.onProduct(y)}) } 38 | }) 39 | 40 | MacroUtils.mergeFn2s( 41 | fs = clauses, 42 | empty = Left(Expr(true)), 43 | merge = (x, y) => '{ $x && $y }, 44 | outer = newInstance, 45 | ) 46 | } 47 | 48 | case Some('{ $m: Mirror.SumOf[A] }) => 49 | result = MacroUtils.CachedGivens[Eq].mirror(m).summonGivens().forSumType(m) { f => 50 | newInstance { (x, y) => '{ 51 | val o = ${f.ordinalOf(x)} 52 | (o == ${f.ordinalOf(y)}) && ${f.typeclassForOrd('o)}.eqv($x, $y) 53 | } 54 | } 55 | } 56 | 57 | case _ => 58 | 59 | if result == null then 60 | val err = s"Don't know how to derive an Eq instance for ${Type.show[A]}: Mirror not found." 61 | log(err) 62 | log("="*120) 63 | quotes.reflect.report.throwError(err) 64 | else 65 | log(result.show) 66 | log("="*120) 67 | result 68 | -------------------------------------------------------------------------------- /project/Dependencies.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import org.portablescala.sbtplatformdeps.PlatformDepsPlugin.autoImport._ 4 | 5 | object Dependencies { 6 | 7 | object Ver { 8 | 9 | // Exported 10 | def cats = "2.8.0" 11 | def scala2 = "2.13.9" 12 | def scala3 = "3.2.0" 13 | def sourceCode = "0.3.0" 14 | def univEq = "2.0.1" 15 | 16 | // Internal 17 | def jamm = "0.3.3" 18 | def kindProjector = "0.13.2" 19 | def nyaya = "1.1.0" 20 | def scalaCheck = "1.17.0" 21 | def scalaJsJavaTime = "2.4.0" 22 | def utest = "0.8.1" 23 | } 24 | 25 | object Dep { 26 | val catsCore = Def.setting("org.typelevel" %%% "cats-core" % Ver.cats) 27 | val catsFree = Def.setting("org.typelevel" %%% "cats-free" % Ver.cats) 28 | val jamm = Def.setting("com.github.jbellis" % "jamm" % Ver.jamm) 29 | val nyayaGen = Def.setting("com.github.japgolly.nyaya" %%% "nyaya-gen" % Ver.nyaya) 30 | val nyayaProp = Def.setting("com.github.japgolly.nyaya" %%% "nyaya-prop" % Ver.nyaya) 31 | val nyayaTest = Def.setting("com.github.japgolly.nyaya" %%% "nyaya-test" % Ver.nyaya) 32 | val scalaCheck = Def.setting("org.scalacheck" %%% "scalacheck" % Ver.scalaCheck) 33 | val scalaCompiler = Def.setting("org.scala-lang" % "scala-compiler" % scalaVersion.value) 34 | val scalaJsJavaTime = Def.setting("io.github.cquiroz" %%% "scala-java-time" % Ver.scalaJsJavaTime) 35 | val sourceCode = Def.setting("com.lihaoyi" %%% "sourcecode" % Ver.sourceCode) 36 | val univEq = Def.setting("com.github.japgolly.univeq" %%% "univeq" % Ver.univEq) 37 | val univEqCats = Def.setting("com.github.japgolly.univeq" %%% "univeq-cats" % Ver.univEq) 38 | val utest = Def.setting("com.lihaoyi" %%% "utest" % Ver.utest) 39 | 40 | // Compiler plugins 41 | val kindProjector = compilerPlugin("org.typelevel" %% "kind-projector" % Ver.kindProjector cross CrossVersion.full) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/OptionalBoolFn.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.stdlib_ext.StdlibExt._ 4 | import scala.collection.Factory 5 | 6 | final class OptionalBoolFn[A](val value: Option[A => Boolean]) extends AnyVal { 7 | 8 | def apply(a: A): Boolean = 9 | value.fold(true)(_(a)) 10 | 11 | def collection[C[x] <: Iterable[x], B](cb: C[B])(a: B => A)(implicit cbf: Factory[B, C[B]]): C[B] = 12 | value.fold(cb) { f => 13 | val b = cbf.newBuilder 14 | b ++= cb.iterator.filter(f compose a) 15 | b.result() 16 | } 17 | 18 | def setFilter: Set[A] => Set[A] = 19 | value.fold[Set[A] => Set[A]](identity)(f => _.filter(f)) 20 | 21 | def iterator(as: Iterator[A]): Iterator[A] = 22 | value.fold(as)(as.filter) 23 | 24 | def iteratorBy[B](bs: Iterator[B])(a: B => A): Iterator[B] = 25 | value.fold(bs)(f => bs.filter(f compose a)) 26 | 27 | def exists(as: IterableOnce[A]): Boolean = 28 | value.fold(as.iterator.nonEmpty)(as.iterator.exists) 29 | 30 | def toFn: A => Boolean = 31 | value getOrElse OptionalBoolFn.alwaysTrue 32 | 33 | @inline def isEmpty = value.isEmpty 34 | 35 | def unary_! : OptionalBoolFn[A] = 36 | new OptionalBoolFn(value.map(!_)) 37 | 38 | def &&(that: OptionalBoolFn[A]): OptionalBoolFn[A] = 39 | merge(that, _ && _) 40 | 41 | def ||(that: OptionalBoolFn[A]): OptionalBoolFn[A] = 42 | merge(that, _ || _) 43 | 44 | private def merge(that: OptionalBoolFn[A], m: (A => Boolean, A => Boolean) => A => Boolean): OptionalBoolFn[A] = 45 | if (this.isEmpty) 46 | that 47 | else if (that.isEmpty) 48 | this 49 | else 50 | OptionalBoolFn(m(this.value.get, that.value.get)) 51 | 52 | def map[B](f: (A => Boolean) => B => Boolean): OptionalBoolFn[B] = 53 | OptionalBoolFn(value map f) 54 | 55 | def contramap[B](f: B => A): OptionalBoolFn[B] = 56 | OptionalBoolFn(value.map(f.andThen)) 57 | } 58 | 59 | object OptionalBoolFn { 60 | private val alwaysTrue = (_: Any) => true 61 | 62 | def apply[A](f: A => Boolean): OptionalBoolFn[A] = 63 | new OptionalBoolFn(Some(f)) 64 | 65 | def apply[A](f: Option[A => Boolean]): OptionalBoolFn[A] = 66 | new OptionalBoolFn(f) 67 | 68 | def empty[A]: OptionalBoolFn[A] = 69 | new OptionalBoolFn(None) 70 | 71 | def fail[A]: OptionalBoolFn[A] = 72 | new OptionalBoolFn(Some(_ => false)) 73 | } 74 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/AtomOrComposite.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.{Functor, Monad, Traverse} 4 | 5 | /** Useful for situations where composite items need to be wrapped when nested, but not at the top level. 6 | * 7 | * Allows "2 * (1 + 1)" instead of "(2 * (1 + 1))". 8 | */ 9 | sealed abstract class AtomOrComposite[A] { 10 | def atom: A 11 | } 12 | 13 | object AtomOrComposite { 14 | 15 | final case class Atom[A](atom: A) extends AtomOrComposite[A] 16 | 17 | final case class Composite[A](composite: A, toAtom: A => A) extends AtomOrComposite[A] { 18 | override def atom = toAtom(composite) 19 | } 20 | 21 | def cata[F[_] : Functor, A](alg: FAlgebra[F, AtomOrComposite[A]])(f: Fix[F]): A = 22 | Recursion.cata(alg)(f) match { 23 | case Atom(a) => a 24 | case Composite(a, _) => a 25 | } 26 | 27 | def cataM[M[_] : Monad, F[_] : Traverse, A](alg: FAlgebraM[M, F, AtomOrComposite[A]])(f: Fix[F]): M[A] = 28 | Monad[M].map(Recursion.cataM(alg)(f)) { 29 | case Atom(a) => a 30 | case Composite(a, _) => a 31 | } 32 | 33 | // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 34 | 35 | object string { 36 | def atom(a: String): Atom[String] = 37 | Atom(a) 38 | 39 | def composite(before: String, content: String, after: String): Composite[String] = 40 | Composite(content, before + _ + after) 41 | 42 | def composite(content: String): Composite[String] = 43 | composite("(", content, ")") 44 | } 45 | 46 | // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 47 | 48 | object stringBuilder { 49 | def atom(a: StringBuilder => Unit): Atom[StringBuilder => Unit] = 50 | Atom(a) 51 | 52 | def composite(before: StringBuilder => Unit, content: StringBuilder => Unit, after: StringBuilder => Unit): Composite[StringBuilder => Unit] = 53 | Composite(content, c => sb => { 54 | before(sb) 55 | c(sb) 56 | after(sb) 57 | }) 58 | 59 | private val parenL: StringBuilder => Unit = sb => {sb append '('; ()} 60 | private val parenR: StringBuilder => Unit = sb => {sb append ')'; ()} 61 | 62 | def composite(content: StringBuilder => Unit): Composite[StringBuilder => Unit] = 63 | composite(parenL, content, parenR) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cats-ext/shared/src/main/scala-2/japgolly/microlibs/cats_ext/CatsMacros.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.cats_ext 2 | 3 | import cats.Eq 4 | import japgolly.microlibs.compiletime.MacroUtils 5 | import scala.reflect.macros.blackbox 6 | 7 | object CatsMacros { 8 | def deriveEq[A]: Eq[A] = macro CatsMacros.quietDeriveEq[A] 9 | def _deriveEq[A]: Eq[A] = macro CatsMacros.debugDeriveEq[A] 10 | } 11 | 12 | 13 | class CatsMacros(val c: blackbox.Context) extends MacroUtils { 14 | import c.universe._ 15 | 16 | private val equal = c.typeOf[Eq[_]] 17 | 18 | def quietDeriveEq[T: c.WeakTypeTag]: c.Expr[Eq[T]] = implDeriveEq(false) 19 | def debugDeriveEq[T: c.WeakTypeTag]: c.Expr[Eq[T]] = implDeriveEq(true ) 20 | def implDeriveEq[T: c.WeakTypeTag](debug: Boolean): c.Expr[Eq[T]] = { 21 | if (debug) println() 22 | val T = weakTypeOf[T] 23 | val t = T.typeSymbol 24 | 25 | def caseClass0: Tree = 26 | q"_root_.cats.Eq.instance[$T]((_, _) => true)" 27 | 28 | def caseClass1up(params: List[Symbol]): Tree = { 29 | val init = new Init("i$" + _) 30 | var cmps = Vector.empty[Tree] 31 | for (p <- params) { 32 | val (pn, pt) = nameAndType(T, p) 33 | val e = init.valImp(appliedType(equal, pt)) 34 | cmps :+= q"$e.eqv(a.$pn,b.$pn)" 35 | } 36 | val expr = cmps.reduce((a, b) => q"$a && $b") 37 | q""" 38 | ..$init 39 | _root_.cats.Eq.instance[$T]((a, b) => $expr) 40 | """ 41 | } 42 | 43 | def adt: Tree = { 44 | val init = new Init("i$" + _) 45 | val cases = crawlADT[CaseDef](T, (_, pt) => { 46 | val equalP = appliedType(equal, pt) 47 | tryInferImplicit(equalP).map { et => 48 | val e = init.valDef(equalP, et) 49 | cq"x: $pt => b match {case y: $pt => $e.eqv(x,y); case _ => false}" 50 | } 51 | }, (_, pt) => { 52 | val u = appliedType(equal, pt) 53 | fail(s"Implicit not found: $u") 54 | }) 55 | init wrap q"_root_.cats.Eq.instance[$T]((a,b) => a match {case ..$cases})" 56 | } 57 | 58 | val impl = 59 | if (t.isClass && t.asClass.isCaseClass) { 60 | ensureConcrete(T) 61 | val params = primaryConstructorParams(T) 62 | if (params.isEmpty) 63 | caseClass0 64 | else 65 | caseClass1up(params) 66 | } else 67 | adt 68 | 69 | if (debug) println("\n" + showCode(impl) + "\n") 70 | c.Expr[Eq[T]](impl) 71 | } 72 | } -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/UtilsTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.testutil.TestUtil._ 4 | import nyaya.gen.Gen 5 | import org.scalacheck._ 6 | import utest._ 7 | 8 | object UtilsTest extends TestSuite { 9 | 10 | override def tests = Tests { 11 | 12 | "flattenArraySeqs" - { 13 | val gen = Gen.ascii.arraySeq(0 to 4) 14 | for { 15 | n <- 0 to 4 16 | s <- gen.arraySeq(n).samples().take(Math.pow(n, 2).toInt + 1) 17 | } { 18 | val actual = s.flatten 19 | val expect = Utils.flattenArraySeqs(s) 20 | assertEq(actual, expect) 21 | } 22 | } 23 | 24 | "partitionConsecutive" - { 25 | def test(in: Int*)(a: Int*)(b: Int*) = 26 | assertEq(Utils.partitionConsecutive(in.toList), (a.toList, b.toList)) 27 | "1" - test()()() 28 | "2" - test(3)(3)() 29 | "3" - test(3, 4)(3, 4)() 30 | "4" - test(3, 5)(3)(5) 31 | "5" - test(3, 5, 6)(3)(5, 6) 32 | "6" - test(3, 4, 6)(3, 4)(6) 33 | "7" - test(3, 4, 5, 6)(3, 4, 5, 6)() 34 | } 35 | 36 | "separateByWhitespaceOrCommas" - { 37 | 38 | "manual" - assertEq( 39 | Utils.separateByWhitespaceOrCommas("omg , k qq"), 40 | Vector(Right("omg"), Left(" , "), Right("k"), Left(" "), Right("qq"))) 41 | 42 | "prop" - { 43 | val gen = Gen.chooseChar(' ', ",ab") 44 | for { 45 | n <- 0 to 6 46 | s <- gen.string(n).samples().take(Math.pow(n, 2).toInt + 1) 47 | } { 48 | val r = Utils.separateByWhitespaceOrCommas(s) 49 | assertEq(r.iterator.map(_.merge).mkString, s) 50 | } 51 | } 52 | } 53 | 54 | "quickStringExists" - { 55 | val x = "x" 56 | Prop.forAll { (ss: Set[String]) => 57 | val f = Utils.quickStringExists(ss) 58 | (ss + "" + "123").toList 59 | .flatMap(s => s.drop(1) :: (s + x) :: (x + s + x) :: s :: Nil) 60 | .forall(s => { 61 | // println(s"${ss.contains(s)} / ${f(s)} -- [$s]") 62 | ss.contains(s) == f(s) 63 | }) 64 | }.check() 65 | } 66 | 67 | "quickStringLookup" - { 68 | val x = "x" 69 | Prop.forAll { (m: Map[String, Int]) => 70 | val f = Utils.quickStringLookup(m) 71 | (m.keySet + "" + "123").toList 72 | .flatMap(s => s.drop(1) :: (s + x) :: (x + s + x) :: s :: Nil) 73 | .forall(s => m.get(s) == f(s)) 74 | }.check() 75 | } 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /types/shared/src/main/scala/japgolly/microlibs/types/NaturalComposition.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.types 2 | 3 | import scala.annotation.nowarn 4 | 5 | object NaturalComposition { 6 | 7 | final case class Discardable[A](value: A) extends AnyVal 8 | implicit val discardableUnit: Discardable[Unit] = Discardable(()) 9 | 10 | // =================================================================================================================== 11 | 12 | trait Merge[A, B] { 13 | type Out 14 | def merge: (A, B) => Out 15 | } 16 | 17 | trait Merge0 { 18 | implicit def fallback[A, B]: Merge.To[A, B, (A, B)] = 19 | Merge[A, B, (A, B)]((_, _)) 20 | } 21 | 22 | trait Merge1 extends Merge0 { 23 | @nowarn("cat=unused") 24 | implicit def discardRight[A, B: Discardable]: Merge.To[A, B, A] = 25 | Merge[A, B, A]((a, _) => a) 26 | } 27 | 28 | trait Merge2 extends Merge1 { 29 | @nowarn("cat=unused") 30 | implicit def discardLeft[A: Discardable, B]: Merge.To[A, B, B] = 31 | Merge[A, B, B]((_, b) => b) 32 | } 33 | 34 | object Merge extends Merge2 { 35 | type To[A, B, O] = Merge[A, B] { type Out = O } 36 | 37 | def apply[A, B, O](f: (A, B) => O): To[A, B, O] = 38 | new Merge[A, B] { 39 | override type Out = O 40 | override def merge = f 41 | } 42 | 43 | implicit def sameSingleton[A <: Singleton]: To[A, A, A] = 44 | Merge[A, A, A]((a, _) => a) 45 | } 46 | 47 | // =================================================================================================================== 48 | 49 | trait Split[A, B] { 50 | type In 51 | def split: In => (A, B) 52 | } 53 | 54 | trait Split0 { 55 | implicit def fallback[A, B]: Split.To[(A, B), A, B] = 56 | Split[(A, B), A, B](identity) 57 | } 58 | 59 | trait Split1 extends Split0 { 60 | implicit def discardRight[A, B](implicit b: Discardable[B]): Split.To[A, A, B] = 61 | Split[A, A, B](a => (a, b.value)) 62 | } 63 | 64 | trait Split2 extends Split1 { 65 | implicit def discardLeft[A, B](implicit a: Discardable[A]): Split.To[B, A, B] = 66 | Split[B, A, B](b => (a.value, b)) 67 | } 68 | 69 | object Split extends Split2 { 70 | type To[I, A, B] = Split[A, B] { type In = I } 71 | 72 | def apply[I, A, B](g: I => (A, B)): To[I, A, B] = 73 | new Split[A, B] { 74 | override type In = I 75 | override def split = g 76 | } 77 | 78 | implicit def same[A]: To[A, A, A] = 79 | Split[A, A, A](a => (a, a)) 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-2.12/japgolly/microlibs/stdlib_ext/MutableArray.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import scala.collection.Factory 4 | 5 | /** 6 | * Scala arrays don't support in-place modification. 7 | */ 8 | final class MutableArray[A](underlying: Array[Any]) { 9 | override def toString = underlying.mkString("MutableArray[", ", ", "]") 10 | 11 | def length = underlying.length 12 | def isEmpty = underlying.isEmpty 13 | def nonEmpty = underlying.nonEmpty 14 | 15 | private[this] var pendingMap: Option[Any => Any] = None 16 | 17 | def array: Array[A] = { 18 | pendingMap.foreach { f => 19 | pendingMap = None 20 | var i = length 21 | while (i > 0) { 22 | i -= 1 23 | underlying(i) = f(underlying(i)) 24 | } 25 | } 26 | underlying.asInstanceOf[Array[A]] 27 | } 28 | 29 | def widen[B >: A]: MutableArray[B] = 30 | this.asInstanceOf[MutableArray[B]] 31 | 32 | def iterator(): Iterator[A] = 33 | pendingMap match { 34 | case None => array.iterator 35 | case Some(f) => underlying.iterator.map(f(_).asInstanceOf[A]) 36 | } 37 | 38 | def map[B](f: A => B): MutableArray[B] = { 39 | val g = f.asInstanceOf[Any => Any] 40 | pendingMap = Some(pendingMap.fold(g)(g.compose)) 41 | this.asInstanceOf[MutableArray[B]] 42 | } 43 | 44 | def sort(implicit o: Ordering[A]): MutableArray[A] = { 45 | scala.util.Sorting.quickSort(array)(o) 46 | this 47 | } 48 | 49 | def sortBy[B: Ordering](f: A => B): MutableArray[A] = 50 | sort(Ordering by f) 51 | 52 | def sortBySchwartzian[B: Ordering](f: A => B): MutableArray[A] = 53 | map(a => (f(a), a)) 54 | .sort(Ordering.by((_: (B, A))._1)) 55 | .map(_._2) 56 | 57 | def to[B](f: Factory[A, B]): B = { 58 | val b = f.newBuilder 59 | b.sizeHint(length) 60 | iterator().foreach(b += _) 61 | b.result() 62 | } 63 | 64 | def mkString(start: String, sep: String, end: String): String = 65 | array.mkString(start, sep, end) 66 | 67 | def mkString(sep: String): String = 68 | array.mkString(sep) 69 | 70 | def mkString: String = 71 | array.mkString 72 | } 73 | 74 | // ===================================================================================================================== 75 | 76 | object MutableArray { 77 | 78 | def apply[A](as: IterableOnce[A]): MutableArray[A] = 79 | new MutableArray(as.iterator.toArray[Any]) 80 | 81 | def map[A, B](as: Iterable[A])(f: A => B): MutableArray[B] = 82 | apply(as.iterator.map(f)) 83 | } 84 | -------------------------------------------------------------------------------- /project/Lib.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import com.jsuereth.sbtpgp.PgpKeys._ 4 | import org.scalajs.sbtplugin.ScalaJSPlugin._ 5 | import sbtcrossproject.CrossProject 6 | import sbtcrossproject.CrossPlugin.autoImport._ 7 | import scalajscrossproject.ScalaJSCrossPlugin.autoImport._ 8 | import xerial.sbt.Sonatype.autoImport._ 9 | 10 | object Lib { 11 | type CPE = CrossProject => CrossProject 12 | type PE = Project => Project 13 | 14 | class ConfigureBoth(val jvm: PE, val js: PE) { 15 | def jvmConfigure(f: PE) = new ConfigureBoth(f compose jvm, js) 16 | def jsConfigure(f: PE) = new ConfigureBoth(jvm, f compose js) 17 | } 18 | 19 | def ConfigureBoth(both: PE) = new ConfigureBoth(both, both) 20 | 21 | implicit def _configureBothToCPE(p: ConfigureBoth): CPE = 22 | _.jvmConfigure(p.jvm).jsConfigure(p.js) 23 | 24 | implicit class CrossProjectExt(val cp: CrossProject) extends AnyVal { 25 | def bothConfigure(fs: PE*): CrossProject = 26 | fs.foldLeft(cp)((q, f) => 27 | q.jvmConfigure(f).jsConfigure(f)) 28 | } 29 | implicit def CrossProjectExtB(b: CrossProject.Builder) = 30 | new CrossProjectExt(b) 31 | 32 | def publicationSettings(ghProject: String) = 33 | ConfigureBoth( 34 | _.settings( 35 | developers := List( 36 | Developer("japgolly", "David Barri", "japgolly@gmail.com", url("https://japgolly.github.io/japgolly/")), 37 | ), 38 | ) 39 | ) 40 | .jsConfigure( 41 | sourceMapsToGithub(ghProject)) 42 | 43 | def sourceMapsToGithub(ghProject: String): PE = 44 | p => p.settings( 45 | scalacOptions ++= { 46 | val isDotty = scalaVersion.value startsWith "3" 47 | val ver = version.value 48 | if (isSnapshot.value) 49 | Nil 50 | else { 51 | val a = p.base.toURI.toString.replaceFirst("[^/]+/?$", "") 52 | val g = s"https://raw.githubusercontent.com/japgolly/$ghProject" 53 | val flag = if (isDotty) "-scalajs-mapSourceURI" else "-P:scalajs:mapSourceURI" 54 | s"$flag:$a->$g/v$ver/" :: Nil 55 | } 56 | } 57 | ) 58 | 59 | def preventPublication: PE = 60 | _.settings(publish / skip := true) 61 | 62 | def disableScalaDoc3: PE = 63 | _.settings( 64 | Compile / doc / sources := { if (scalaVersion.value startsWith "3") Seq.empty else (Compile / doc / sources ).value }, 65 | // Compile / packageDoc / publishArtifact := { if (scalaVersion.value startsWith "3") false else (Compile / packageDoc / publishArtifact).value }, 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/ConsolidatedSeqTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.nonempty.NonEmptyVector 4 | import japgolly.microlibs.testutil.TestUtil._ 5 | import utest._ 6 | 7 | object ConsolidatedSeqTest extends TestSuite { 8 | 9 | override def tests = Tests { 10 | 11 | "noConsolidation" - { 12 | def test(size: Int) = { 13 | val logic = ConsolidatedSeq.Logic[Int](_ => false)(identity) 14 | val as = Vector.tabulate(size)(100 + _) 15 | val indices = 0.until(size).toList 16 | val c = logic(as) 17 | def el(i: Int) = Some(ConsolidatedSeq.Group(NonEmptyVector.one(i + 100), i, i)) 18 | assertEq("length", size, c.length) 19 | assertEq("groups", indices, c.groupIterator().toList) 20 | assertEq("values", indices.map(el), c.elementIterator().toList) 21 | c.map(_.values.mkString("[", ",", "]")) 22 | } 23 | 24 | "0" - test(0) 25 | "1" - test(1) 26 | "2" - test(2) 27 | "3" - test(3) 28 | "9" - test(9) 29 | } 30 | 31 | "consolidateAll" - { 32 | def test(size: Int) = { 33 | val logic = ConsolidatedSeq.Logic[Int](_ => true)(identity) 34 | val as = Vector.tabulate(size)(100 + _) 35 | val indices = 0.until(size).toList 36 | val c = logic(as) 37 | def el(i: Int) = if (i > 0) None else Some(ConsolidatedSeq.Group(NonEmptyVector.force(as), 0, 0)) 38 | assertEq("length", size, c.length) 39 | assertEq("groups", List.fill(size)(0), c.groupIterator().toList) 40 | assertEq("values", indices.map(el), c.elementIterator().toList) 41 | c.map(_.values.mkString("[", ",", "]")) 42 | } 43 | 44 | "0" - test(0) 45 | "1" - test(1) 46 | "2" - test(2) 47 | "3" - test(3) 48 | "9" - test(9) 49 | } 50 | 51 | "consolidateOdd" - { 52 | val logic = ConsolidatedSeq.Logic[Int](i => (i.cur & 1) != 0)(identity) 53 | val as = Vector(10, 11, 12, 13, 14) 54 | val c = logic(as) 55 | val expect = List( 56 | Some(ConsolidatedSeq.Group(NonEmptyVector(10, 11), 0, 0)), 57 | None, 58 | Some(ConsolidatedSeq.Group(NonEmptyVector(12, 13), 2, 1)), 59 | None, 60 | Some(ConsolidatedSeq.Group(NonEmptyVector(14), 4, 2))) 61 | 62 | assertEq("length", 5, c.length) 63 | assertEq("groups", List(0, 0, 1, 1, 2), c.groupIterator().toList) 64 | assertEq("values", expect, c.elementIterator().toList) 65 | c.map(_.values.mkString("[", ",", "]")) 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/AsciiTable.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.stdlib_ext.StdlibExt._ 4 | import scala.annotation.tailrec 5 | 6 | /** 7 | * Taken from 8 | * https://github.com/dsl-platform/101-dsl-examples/blob/master/001-periodic-table-of-elements/scala/src/main/scala/com/dslplatform/examples/AsciiTable.scala 9 | */ 10 | object AsciiTable { 11 | // Characters that make up the table. 12 | private val CrossBorder = '+' 13 | private val RowBorder = '-' 14 | private val ColumnBorder = '|' 15 | private val HeaderBorder = '-' 16 | private val NL = System.getProperty("line.separator") 17 | 18 | case class Cell(row: Int, col: Int) 19 | 20 | 21 | /** An entry (a row) is a list of strings (representing columns). A table is 22 | * a list of rows. 23 | */ 24 | def apply(table: Seq[Seq[String]], 25 | centre: Cell => Boolean = _ => false, 26 | separateDataRows: Boolean = false): String = { 27 | 28 | val rowCount = table.size 29 | val maxColLengths = table.transpose.map(_.map(s => if (s eq null) 4 else s.removeAnsiEscapeCodes.length).max) 30 | val sb = new StringBuilder 31 | 32 | @tailrec 33 | def doMake(rowNum: Int): Unit = 34 | if (rowNum == rowCount) 35 | addSeparator(true) 36 | else { 37 | if (rowNum <= 1) 38 | addSeparator(true) 39 | else if (separateDataRows) 40 | addSeparator(false) 41 | addRow(rowNum) 42 | doMake(rowNum + 1) 43 | } 44 | 45 | def addSeparator(isHeader: Boolean): Unit = { 46 | val border = if (isHeader) HeaderBorder else RowBorder 47 | sb.append(CrossBorder) 48 | maxColLengths foreach { length => 49 | sb.append(border.toString * (length + 2)) 50 | sb.append(CrossBorder) 51 | } 52 | sb.append(NL) 53 | () 54 | } 55 | 56 | def addRow(rowNo: Int): Unit = { 57 | val row = table(rowNo) 58 | sb.append(ColumnBorder) 59 | for (column <- maxColLengths.indices) { 60 | val c = Cell(rowNo, column) 61 | var cell = row(column) 62 | if (cell eq null) 63 | cell = "null" 64 | val ws = maxColLengths(column) - cell.removeAnsiEscapeCodes.length + 2 65 | if (centre(c)) { 66 | sb.append(" " * (ws/2)) 67 | sb.append(cell) 68 | sb.append(" " * (ws/2 + ws%2)) 69 | } else { 70 | sb.append(' ') 71 | sb.append(cell) 72 | sb.append(" " * (ws - 1)) 73 | } 74 | sb.append(ColumnBorder) 75 | } 76 | sb.append(NL) 77 | () 78 | } 79 | 80 | doMake(0) 81 | sb.dropRight(NL.length).toString() 82 | } 83 | } -------------------------------------------------------------------------------- /recursion/shared/src/test/scala/japgolly/microlibs/recursion/RecursionTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import japgolly.microlibs.recursion.MathExpr.Helpers._ 4 | import utest._ 5 | 6 | object RecursionTest extends TestSuite { 7 | 8 | val eg1: FM = add(2, add(3, 11)) 9 | 10 | override def tests = Tests { 11 | 12 | "cata" - { 13 | val r = Recursion.cata(MathExpr.eval)(eg1) 14 | assert(r == 16) 15 | } 16 | 17 | "ana" - { 18 | val expr = Recursion.ana(MathExpr.plusOnes)(5) 19 | assert(expr == add(1, add(1, add(1, add(1, 1))))) 20 | } 21 | 22 | "hylo" - { 23 | val n = 8 24 | val m = Recursion.hylo(MathExpr.plusOnes, MathExpr.eval)(n) 25 | assert(n == m) 26 | } 27 | 28 | "prepro" - { 29 | "stopAboveFive" - { 30 | val l = 1 to 10 toList 31 | val t = FixList(l: _*) 32 | val a = Recursion.prepro[ListF[Int, *], Int](FixList.stopAboveFive, FixList.sum)(t) 33 | val expect = l.takeWhile(_ <= 5).sum 34 | assert(a == expect) 35 | } 36 | 37 | "zeroOutOdds" - { 38 | val l = 1 to 10 toList 39 | val t = FixList(l: _*) 40 | val a = Recursion.prepro[ListF[Int, *], Int](FixList.zeroOutOdds, FixList.sum)(t) 41 | // l.head because prepro doesn't transform it's input, only children 42 | val expect = l.head + l.tail.filter(_ % 2 == 0).sum 43 | assert(a == expect) 44 | } 45 | } 46 | 47 | "postpro" - { 48 | "stopAboveFive" - { 49 | val i = -3 50 | val a = Recursion.postpro[ListF[Int, *], Int](FixList.ascStream, FixList.stopAboveFive)(i) 51 | val expect = (-3 to 5).toList 52 | assert(a == FixList(expect: _*)) 53 | } 54 | 55 | "zeroOutOdds" - { 56 | val i = 93 57 | val a = Recursion.postpro[ListF[Int, *], Int](FixList.ascStream, FixList.zeroOutOdds)(i) 58 | val aa = FixList.toList(a) 59 | val expect = List(93, 94, 0, 96, 0, 98, 0, 100) 60 | assert(aa == expect) 61 | } 62 | } 63 | 64 | "coelgot" - { 65 | "shortCircuit" - shortCircuitTest() 66 | } 67 | 68 | } 69 | 70 | private def shortCircuitTest() = { 71 | var coalgs = Vector.empty[Int] 72 | var algs = Vector.empty[Int] 73 | val str = Recursion.coelgot[MathExpr, Int, String]( 74 | i => {coalgs :+= i; MathExpr.plusOnes(i)}, 75 | (i, f) => { 76 | algs :+= i 77 | if (i == 87) 78 | "stop!" 79 | else 80 | f() match { 81 | case MathExpr.Num(s) => s.toString 82 | case MathExpr.Add(a, b) => s"$a+$b" 83 | } 84 | })(90) 85 | assert(str == "1+1+1+stop!", coalgs.length < 10, algs.length < 10) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-2.13/japgolly/microlibs/stdlib_ext/MutableArray.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import scala.collection.immutable.ArraySeq 4 | import scala.collection.{Factory, View} 5 | 6 | /** 7 | * Scala arrays don't support in-place modification. 8 | */ 9 | final class MutableArray[A](underlying: Array[Any]) { 10 | override def toString = underlying.mkString("MutableArray[", ", ", "]") 11 | 12 | def length = underlying.length 13 | def isEmpty = underlying.isEmpty 14 | def nonEmpty = underlying.nonEmpty 15 | 16 | private[this] var pendingMap: Option[Any => Any] = None 17 | 18 | def array: Array[A] = { 19 | pendingMap.foreach { f => 20 | pendingMap = None 21 | var i = length 22 | while (i > 0) { 23 | i -= 1 24 | underlying(i) = f(underlying(i)) 25 | } 26 | } 27 | underlying.asInstanceOf[Array[A]] 28 | } 29 | 30 | def widen[B >: A]: MutableArray[B] = 31 | this.asInstanceOf[MutableArray[B]] 32 | 33 | def iterator(): Iterator[A] = 34 | pendingMap match { 35 | case None => array.iterator 36 | case Some(f) => underlying.iterator.map(f(_).asInstanceOf[A]) 37 | } 38 | 39 | def map[B](f: A => B): MutableArray[B] = { 40 | val g = f.asInstanceOf[Any => Any] 41 | pendingMap = Some(pendingMap.fold(g)(g.compose)) 42 | this.asInstanceOf[MutableArray[B]] 43 | } 44 | 45 | def sort(implicit o: Ordering[A]): MutableArray[A] = { 46 | scala.util.Sorting.quickSort(array)(o) 47 | this 48 | } 49 | 50 | def sortBy[B: Ordering](f: A => B): MutableArray[A] = 51 | sort(Ordering by f) 52 | 53 | def sortBySchwartzian[B: Ordering](f: A => B): MutableArray[A] = 54 | map(a => (f(a), a)) 55 | .sort(Ordering.by((_: (B, A))._1)) 56 | .map(_._2) 57 | 58 | def to[B](f: Factory[A, B]): B = { 59 | val b = f.newBuilder 60 | b.sizeHint(length) 61 | iterator().foreach(b += _) 62 | b.result() 63 | } 64 | 65 | def arraySeq: ArraySeq[A] = 66 | ArraySeq.unsafeWrapArray(array) 67 | 68 | def view: View[A] = 69 | View.fromIteratorProvider(() => iterator()) 70 | 71 | def mkString(start: String, sep: String, end: String): String = 72 | array.mkString(start, sep, end) 73 | 74 | def mkString(sep: String): String = 75 | array.mkString(sep) 76 | 77 | def mkString: String = 78 | array.mkString 79 | } 80 | 81 | // ===================================================================================================================== 82 | 83 | object MutableArray { 84 | 85 | def apply[A](as: IterableOnce[A]): MutableArray[A] = 86 | new MutableArray(as.iterator.toArray[Any]) 87 | 88 | def map[A, B](as: Iterable[A])(f: A => B): MutableArray[B] = 89 | apply(as.iterator.map(f)) 90 | } 91 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/IMap.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import cats.Eq 4 | import japgolly.microlibs.nonempty.NonEmpty 5 | import japgolly.microlibs.stdlib_ext.MutableArray 6 | import japgolly.univeq.UnivEq 7 | 8 | object IMap { 9 | @inline implicit def catsEq[K, V: Eq]: Eq[IMap[K, V]] = 10 | IMapBase.catsEq[K, V, IMap[K, V]] 11 | 12 | @inline implicit def univEq[K, V](implicit u: UnivEq[Map[K, V]]): UnivEq[IMap[K, V]] = 13 | IMapBase.univEq[K, V, IMap[K, V]](u) 14 | 15 | implicit def nonEmptyProof[K, V]: NonEmpty.ProofMono[IMap[K, V]] = 16 | NonEmpty.Proof.testEmptiness(_.isEmpty) 17 | 18 | def empty[K: UnivEq, V](k: V => K): IMap[K, V] = 19 | new IMap(k, Map.empty) 20 | } 21 | 22 | /** 23 | * Intrinsic-Invariant Map. 24 | * 25 | * A map with automatically-managed intrinsic-invariants. 26 | * The relationship between map-key and value is guaranteed to be consistent. 27 | * 28 | * Values are mapped by a subset of themselves. 29 | */ 30 | final class IMap[K: UnivEq, V] private (key: V => K, m: Map[K, V]) extends IMapBase[K, V, IMap[K, V]](m) { 31 | 32 | override protected def stringPrefix = "IMap" 33 | override protected def setmap(n: M) = new IMap(key, n) 34 | override protected def _gkey(v: V) = key(v) 35 | 36 | def get(k: K): Option[V] = 37 | m.get(k) 38 | 39 | def need(k: K): V = { 40 | val o = get(k) 41 | if (o.isEmpty) 42 | throw new RuntimeException(badKeyMsg(k)) 43 | else 44 | o.get 45 | } 46 | 47 | def getAttempt(k: K): Either[String, V] = 48 | get(k).toRight(badKeyMsg(k)) 49 | 50 | private def badKeyMsg(k: K): String = { 51 | val keyArray = MutableArray(keysIterator().map(_.toString)).sort 52 | val max = 10 53 | val keyDesc = 54 | if (keyArray.length > max) 55 | keyArray.iterator().take(max).mkString("{", ", ", ", ... }") 56 | else 57 | keyArray.iterator().mkString("{", ", ", "}") 58 | s"Value not found for $k.\nKeys = $keyDesc" 59 | } 60 | 61 | def modifyValues(f: V => V): This = 62 | new IMap(key, m.valuesIterator.map(f).map(v => key(v) -> v).toMap) 63 | 64 | def modify(k: K, f: V => V)(implicit ev: V <:< AnyRef): This = 65 | _mod(k, f, this) 66 | 67 | def modifyOrPut(k: K, f: V => V, put: => V)(implicit ev: V <:< AnyRef): This = 68 | _mod(k, f, this add put) 69 | 70 | private def _mod(k: K, f: V => V, nomod: => IMap[K, V])(implicit ev: V <:< AnyRef): This = 71 | m.get(k).fold(nomod)(v => { 72 | val v2 = f(v) 73 | if (ev(v) eq ev(v2)) 74 | this 75 | else { 76 | val k2 = key(v2) 77 | var n = m.updated(k2, v2) 78 | if (k != k2) n -= k 79 | setmap(n) 80 | } 81 | }) 82 | 83 | def empty: This = 84 | IMap.empty(key) 85 | } 86 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/IndexLabel.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.stdlib_ext._ 4 | import scala.annotation.tailrec 5 | /** 6 | * Provides labels based on index for items in an ordered sequence. 7 | */ 8 | trait IndexLabel { 9 | 10 | /** 11 | * @param index ≥ 0 12 | */ 13 | def label(index: Int): String 14 | 15 | /** 16 | * Attempt to interpret a label. 17 | * 18 | * Parsing should be lenient and accept differences in case, trailing zeros, etc. 19 | * Whitespace on the other hand shouldn't be considered. 20 | * 21 | * @return Option(_ ≥ 0) 22 | */ 23 | def parse(label: String): Option[Int] 24 | } 25 | 26 | object IndexLabel { 27 | 28 | /** 29 | * 0. Index 0 30 | * 1. Index 1 31 | * 2. Index 2 32 | * ... 33 | */ 34 | object NumericFrom0 extends IndexLabel { 35 | override def label(index: Int) = index.toString 36 | override def parse(label: String) = ParseInt.unapply(label).filter(_ >= 0) 37 | } 38 | 39 | /** 40 | * 1. Index 0 41 | * 2. Index 1 42 | * 3. Index 2 43 | * ... 44 | */ 45 | object NumericFrom1 extends IndexLabel { 46 | override def label(index: Int) = (index + 1).toString 47 | override def parse(label: String) = ParseInt.unapply(label).map(_ - 1).filter(_ >= 0) 48 | } 49 | 50 | /** 51 | * i. Index 0 52 | * ii. Index 1 53 | * iii. Index 2 54 | * ... 55 | */ 56 | object Roman extends IndexLabel { 57 | override def label(index: Int) = RomanNumeral(index + 1).toLowerCase 58 | override def parse(label: String) = RomanNumeral.parse(label).map(_ - 1).filter(_ >= 0) 59 | } 60 | 61 | /** 62 | * a. Index 0 63 | * b. Index 1 64 | * c. Index 2 65 | * ... 66 | */ 67 | object Alpha extends IndexLabel { 68 | private final val First = 'a' 69 | 70 | override def label(index: Int) = { 71 | assert(index >= 0, s"Alpha.label($index)") 72 | @tailrec 73 | def go(n: Int, s: String): String = { 74 | val q = n / 26 75 | val r = n % 26 76 | val cur = (r + First).toChar.toString 77 | val s2 = if (s eq null) cur else cur + s 78 | if (q == 0) 79 | s2 80 | else 81 | go(q - 1, s2) 82 | } 83 | go(index, null) 84 | } 85 | 86 | override def parse(label: String) = { 87 | var ok = true 88 | var sum = 0 89 | for (c <- label) { 90 | val v = c.toLower - First + 1 91 | if (v <= 0 || v > 26) 92 | ok = false 93 | else 94 | sum = sum * 26 + v 95 | } 96 | if (ok) 97 | Some(sum - 1) 98 | else 99 | None 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala-3/japgolly/microlibs/stdlib_ext/MutableArray.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import scala.collection.immutable.ArraySeq 4 | import scala.collection.{Factory, View} 5 | import scala.collection.Factory 6 | 7 | /** 8 | * Scala arrays don't support in-place modification. 9 | */ 10 | final class MutableArray[A](underlying: Array[Any]) { 11 | override def toString = underlying.mkString("MutableArray[", ", ", "]") 12 | 13 | inline def length = underlying.length 14 | inline def isEmpty = underlying.isEmpty 15 | inline def nonEmpty = underlying.nonEmpty 16 | 17 | private[this] var pendingMap: Option[Any => Any] = None 18 | 19 | def array: Array[A] = { 20 | pendingMap.foreach { f => 21 | pendingMap = None 22 | var i = length 23 | while (i > 0) { 24 | i -= 1 25 | underlying(i) = f(underlying(i)) 26 | } 27 | } 28 | underlying.asInstanceOf[Array[A]] 29 | } 30 | 31 | inline def widen[B >: A]: MutableArray[B] = 32 | this.asInstanceOf[MutableArray[B]] 33 | 34 | def iterator(): Iterator[A] = 35 | pendingMap match { 36 | case None => array.iterator 37 | case Some(f) => underlying.iterator.map(f(_).asInstanceOf[A]) 38 | } 39 | 40 | def map[B](f: A => B): MutableArray[B] = { 41 | val g = f.asInstanceOf[Any => Any] 42 | pendingMap = Some(pendingMap.fold(g)(g.compose)) 43 | this.asInstanceOf[MutableArray[B]] 44 | } 45 | 46 | inline def sort(implicit o: Ordering[A]): MutableArray[A] = { 47 | scala.util.Sorting.quickSort(array)(o) 48 | this 49 | } 50 | 51 | inline def sortBy[B: Ordering](f: A => B): MutableArray[A] = 52 | sort(Ordering by f) 53 | 54 | def sortBySchwartzian[B: Ordering](f: A => B): MutableArray[A] = 55 | map(a => (f(a), a)) 56 | .sort(Ordering.by((_: (B, A))._1)) 57 | .map(_._2) 58 | 59 | def to[B](f: Factory[A, B]): B = { 60 | val b = f.newBuilder 61 | b.sizeHint(length) 62 | iterator().foreach(b += _) 63 | b.result() 64 | } 65 | 66 | inline def arraySeq: ArraySeq[A] = 67 | ArraySeq.unsafeWrapArray(array) 68 | 69 | inline def view: View[A] = 70 | View.fromIteratorProvider(() => iterator()) 71 | 72 | inline def mkString(start: String, sep: String, end: String): String = 73 | array.mkString(start, sep, end) 74 | 75 | inline def mkString(sep: String): String = 76 | array.mkString(sep) 77 | 78 | inline def mkString: String = 79 | array.mkString 80 | } 81 | 82 | // ===================================================================================================================== 83 | 84 | object MutableArray { 85 | 86 | inline def apply[A](as: IterableOnce[A]): MutableArray[A] = 87 | new MutableArray(as.iterator.toArray[Any]) 88 | 89 | inline def map[A, B](as: Iterable[A])(f: A => B): MutableArray[B] = 90 | apply(as.iterator.map(f)) 91 | } 92 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/FnWithFallback.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | /** 4 | * A partial function that, given a fallback, can efficiently become a total function. 5 | */ 6 | final case class FnWithFallback[A, B](withFallback: (A => B) => A => B) extends AnyVal { 7 | 8 | def withFallbackByValue(b: B): A => B = 9 | withFallback(_ => b) 10 | 11 | def withFallbackByNeed(b: B): A => B = { 12 | lazy val bb = b 13 | withFallback(_ => bb) 14 | } 15 | 16 | def withFallbackByName(b: => B): A => B = 17 | withFallback(_ => b) 18 | 19 | def when(cond: A => Boolean): FnWithFallback[A, B] = 20 | FnWithFallback(fallback => { 21 | val attempt = withFallback(fallback) 22 | a => (if (cond(a)) attempt else fallback)(a) 23 | }) 24 | 25 | def embed(cond: A => Boolean, update: A => A): FnWithFallback[A, B] = 26 | FnWithFallback(fallback => { 27 | val attempt = withFallback(fallback) 28 | a => if (cond(a)) attempt(update(a)) else fallback(a) 29 | }) 30 | 31 | def embed(f: A => Option[A]): FnWithFallback[A, B] = 32 | FnWithFallback(fallback => { 33 | val attempt = withFallback(fallback) 34 | a => f(a).fold(fallback(a))(attempt) 35 | }) 36 | 37 | /** 38 | * Attempt this partial function and if it doesn't produce a `B`, use the `next` argument. 39 | * Like boolean OR, or `Option#orElse`. 40 | */ 41 | def |(next: FnWithFallback[A, B]): FnWithFallback[A, B] = 42 | FnWithFallback(f => withFallback(next.withFallback(f))) 43 | 44 | def |(next: Option[FnWithFallback[A, B]]): FnWithFallback[A, B] = 45 | next.fold(this)(this | _) 46 | 47 | def partial(implicit ev: Null <:< B): A => Option[B] = { 48 | val n = ev(null) 49 | withFallback(_ => n).andThen(Option(_)) 50 | } 51 | 52 | def mapWithInput[C](f: (A, B) => C)(implicit ev: Null <:< B): FnWithFallback[A, C] = { 53 | val emptyB = ev(null) 54 | val ab = withFallback(_ => emptyB) 55 | FnWithFallback[A, C] { ac => 56 | a => { 57 | val b: B = ab(a) 58 | if (b == null) 59 | ac(a) 60 | else 61 | f(a, b) 62 | } 63 | } 64 | } 65 | } 66 | 67 | object FnWithFallback { 68 | def when[A, B](cond: A => Boolean)(ok: A => B): FnWithFallback[A, B] = 69 | apply(f => a => if (cond(a)) ok(a) else f(a)) 70 | 71 | def whenByValue[A, B](cond: A => Boolean)(ok: B): FnWithFallback[A, B] = 72 | apply(f => a => if (cond(a)) ok else f(a)) 73 | 74 | def whenByNeed[A, B](cond: A => Boolean)(ok: => B): FnWithFallback[A, B] = { 75 | lazy val b = ok 76 | apply(f => a => if (cond(a)) b else f(a)) 77 | } 78 | 79 | def whenByName[A, B](cond: A => Boolean)(ok: => B): FnWithFallback[A, B] = 80 | apply(f => a => if (cond(a)) ok else f(a)) 81 | 82 | def extract[A, B, E](cond: A => Option[E])(ok: A => E => B): FnWithFallback[A, B] = 83 | apply(f => a => cond(a).fold(f(a))(ok(a))) 84 | 85 | def optionKleisli[A, B](g: A => Option[B]): FnWithFallback[A, B] = 86 | apply(f => a => g(a) getOrElse f(a)) 87 | 88 | def choose[A, B](g: A => (FnWithFallback[A, B])): FnWithFallback[A, B] = 89 | apply(f => a => g(a).withFallback(f)(a)) 90 | } 91 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/main/scala/japgolly/microlibs/stdlib_ext/Extractors.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import java.time.Duration 4 | import java.time.temporal.ChronoUnit 5 | 6 | object ParseDouble { 7 | def unapply(s: String): Option[Double] = 8 | try { 9 | Some(s.toDouble) 10 | } catch { 11 | case _: java.lang.NumberFormatException => None 12 | } 13 | } 14 | 15 | object ParseLong { 16 | def unapply(s: String): Option[Long] = 17 | try { 18 | Some(s.toLong) 19 | } catch { 20 | case _: java.lang.NumberFormatException => None 21 | } 22 | } 23 | 24 | object ParseInt { 25 | def unapply(s: String): Option[Int] = 26 | try { 27 | Some(s.toInt) 28 | } catch { 29 | case _: java.lang.NumberFormatException => None 30 | } 31 | } 32 | 33 | object ParseChronoUnit { 34 | private def normalise(s: String): String = 35 | s.toLowerCase 36 | 37 | private val TextToChronoUnitMap: Map[String, ChronoUnit] = { 38 | var m = Map.empty[String, ChronoUnit] 39 | def add(u: ChronoUnit, others: String*): Unit = 40 | (others.iterator ++ Iterator.single(u.toString)) 41 | .map(normalise) 42 | .foreach(s => m = m.updated(s, u)) 43 | ChronoUnit.values() foreach { 44 | case u@ ChronoUnit.NANOS => add(u, "ns", "nano", "nanosecond", "nanoseconds") 45 | case u@ ChronoUnit.MICROS => add(u, "μs", "micro", "microsecond", "microseconds") 46 | case u@ ChronoUnit.MILLIS => add(u, "ms", "milli", "millisecond", "milliseconds") 47 | case u@ ChronoUnit.SECONDS => add(u, "s", "sec", "second") 48 | case u@ ChronoUnit.MINUTES => add(u, "min", "minute") 49 | case u@ ChronoUnit.HOURS => add(u, "hr", "hour") 50 | case u@ ChronoUnit.HALF_DAYS => add(u, "halfday") 51 | case u@ ChronoUnit.DAYS => add(u, "d", "day") 52 | case u@ ChronoUnit.WEEKS => add(u, "w", "week") 53 | case u@ ChronoUnit.MONTHS => add(u, "month") 54 | case u@ ChronoUnit.YEARS => add(u, "y", "yr", "year") 55 | case u@ ChronoUnit.DECADES => add(u, "decade") 56 | case u@ ChronoUnit.CENTURIES => add(u, "century") 57 | case u@ ChronoUnit.MILLENNIA => add(u, "millennium") 58 | case u@ ChronoUnit.ERAS => add(u, "era") 59 | case u@ ChronoUnit.FOREVER => add(u) 60 | } 61 | m 62 | } 63 | 64 | def unapply(s: String): Option[ChronoUnit] = 65 | TextToChronoUnitMap get normalise(s) 66 | } 67 | 68 | object ParseDuration { 69 | 70 | private val eachS = "(?:(-?\\d+)\\s*([a-zA-Z]+))" 71 | private val each = eachS.r 72 | private val all = s"$eachS(?:(?:\\s|,)*$eachS)*".r.pattern 73 | 74 | def unapply(s: String): Option[Duration] = 75 | if (all.matcher(s).matches) 76 | each.findAllMatchIn(s) 77 | .map(m => (m.group(1), m.group(2)) match { 78 | case (ParseLong(n), ParseChronoUnit(u)) => Some(u.getDuration multipliedBy n) 79 | // case (ParseDouble(n), ParseChronoUnit(u)) => Some(Duration ofNanos (u.getDuration.toNanos * n).toLong) 80 | case _ => None 81 | }) 82 | .reduce[Option[Duration]] { 83 | case (Some(d1), Some(d2)) => Some(d1 plus d2) 84 | case _ => None 85 | } 86 | else 87 | None 88 | } 89 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/QuotingUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import java.util.regex.Pattern 4 | import scala.quoted.* 5 | import MacroEnv.* 6 | 7 | object QuotingUtils: 8 | 9 | def warn(warning: Expr[String])(using Quotes): Expr[Unit] = 10 | import quotes.reflect.* 11 | report.warning(warning.valueOrError) 12 | Expr.inlineConstUnit 13 | 14 | def replaceFirst(str: Expr[String], regex: Expr[String], repl: Expr[String])(using Quotes): Expr[String] = 15 | (str.value, regex.value, repl.value) match 16 | case (Some(n), Some(r), Some(p)) => Expr.inlineConst(n.replaceFirst(r, p)) 17 | case _ => '{ $str.replaceFirst($regex, $repl) } 18 | 19 | def replaceAll(str: Expr[String], regex: Expr[String], repl: Expr[String])(using Quotes): Expr[String] = 20 | (str.value, regex.value, repl.value) match 21 | case (Some(n), Some(r), Some(p)) => Expr.inlineConst(n.replaceAll(r, p)) 22 | case _ => '{ $str.replaceAll($regex, $repl) } 23 | 24 | def trim(str: Expr[String])(using Quotes): Expr[String] = 25 | str.value match 26 | case Some(s) => Expr.inlineConst(s.trim) 27 | case None => '{ $str.trim } 28 | 29 | def toLowerCase(str: Expr[String])(using Quotes): Expr[String] = 30 | str.value match 31 | case Some(s) => Expr.inlineConst(s.toLowerCase) 32 | case None => '{ $str.toLowerCase } 33 | 34 | def toUpperCase(str: Expr[String])(using Quotes): Expr[String] = 35 | str.value match 36 | case Some(s) => Expr.inlineConst(s.toUpperCase) 37 | case None => '{ $str.toUpperCase } 38 | 39 | def toInt(str: Expr[String])(using Quotes): Expr[Int] = 40 | str.value match 41 | case Some(s) => 42 | try 43 | Expr.inlineConst(s.toInt) 44 | catch 45 | case _: Throwable => fail(s"Can't convert \"$s\" to an Int") 46 | case None => 47 | '{ $str.toInt } 48 | 49 | def toLong(str: Expr[String])(using Quotes): Expr[Long] = 50 | str.value match 51 | case Some(s) => 52 | try 53 | Expr.inlineConst(s.toLong) 54 | catch 55 | case _: Throwable => fail(s"Can't convert \"$s\" to a Long") 56 | case None => 57 | '{ $str.toLong } 58 | 59 | def toBoolean(str: Expr[String])(using Quotes): Expr[Boolean] = 60 | str.value match 61 | case Some(s) => 62 | try 63 | Expr.inlineConst(parseBooleanOrThrow(s)) 64 | catch 65 | case t: Throwable => fail(t.getMessage) 66 | case None => 67 | '{ parseBooleanOrThrow($str) } 68 | 69 | private val RegexTrue = Pattern.compile("^(?:t(?:rue)?|y(?:es)?|1|on|enabled?)$", Pattern.CASE_INSENSITIVE) 70 | private val RegexFalse = Pattern.compile("^(?:f(?:alse)?|n(?:o)?|0|off|disabled?)$", Pattern.CASE_INSENSITIVE) 71 | 72 | def parseBooleanOrThrow(s: String): Boolean = 73 | if (RegexTrue.matcher(s).matches) 74 | true 75 | else if (RegexFalse.matcher(s).matches) 76 | false 77 | else 78 | throw new RuntimeException(s"Can't parse \"$s\" as a Boolean") 79 | 80 | def showCode(e: Expr[Any])(using Quotes): Expr[String] = 81 | import quotes.reflect.* 82 | Expr.inlineConst(e.show) 83 | 84 | def showTasty(e: Expr[Any])(using Quotes): Expr[String] = 85 | import quotes.reflect.* 86 | Expr.inlineConst("" + e.asTerm) 87 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/SetDiff.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import cats.Functor 4 | import japgolly.microlibs.multimap.Multimap 5 | import japgolly.microlibs.nonempty.NonEmpty 6 | import japgolly.univeq._ 7 | import scala.annotation.nowarn 8 | 9 | /** 10 | * The difference between two sets. 11 | */ 12 | final class SetDiff[A](val removed: Set[A], val added: Set[A]) { 13 | assert((removed & added).isEmpty, s"Same item(s) found in removed & added: ${removed & added}") 14 | 15 | override def toString = 16 | s"SetDiff(removed = $removed, added = $added)" 17 | 18 | override def hashCode = 19 | removed.## * 31 + added.## 20 | 21 | @nowarn 22 | override def equals(o: Any) = o match { 23 | case b: SetDiff[A] => (removed == b.removed) && (added == b.added) 24 | case _ => false 25 | } 26 | 27 | def isEmpty: Boolean = 28 | removed.isEmpty && added.isEmpty 29 | 30 | def nonEmpty = !isEmpty 31 | 32 | def ++(as: IterableOnce[A]): SetDiff[A] = 33 | new SetDiff(removed, added ++ as) 34 | 35 | def --(as: IterableOnce[A]): SetDiff[A] = 36 | new SetDiff(removed ++ as, added) 37 | 38 | def inverse: SetDiff[A] = 39 | new SetDiff(added, removed) 40 | 41 | def allValues: Set[A] = 42 | added ++ removed 43 | 44 | def apply(to: Set[A]): Set[A] = 45 | (to -- removed) ++ added 46 | 47 | def mapApply[B](f: A => B, to: Set[B]): Set[B] = 48 | (to -- removed.iterator.map(f)) ++ added.iterator.map(f) 49 | 50 | def applyToMultimapKeys[V](mm: Multimap[A, Set, V])(v: V): Multimap[A, Set, V] = { 51 | var tmp = mm 52 | removed.foreach(a => tmp = tmp.del(a, v)) 53 | added .foreach(a => tmp = tmp.add(a, v)) 54 | tmp 55 | } 56 | 57 | def applyToMultimapValues[K](mm: Multimap[K, Set, A])(k: K): Multimap[K, Set, A] = 58 | mm.mod(k, apply) 59 | 60 | def map[B: UnivEq](f: A => B): SetDiff[B] = 61 | SetDiff(removed = removed.map(f), added = added.map(f)) 62 | } 63 | 64 | object SetDiff { 65 | type NE[A] = NonEmpty[SetDiff[A]] 66 | 67 | @nowarn("cat=unused") 68 | implicit def equality[A: UnivEq]: UnivEq[SetDiff[A]] = 69 | UnivEq.force 70 | 71 | implicit def nonEmptiness[A]: NonEmpty.ProofMono[SetDiff[A]] = 72 | NonEmpty.Proof.testEmptiness(_.isEmpty) 73 | 74 | def empty[A: UnivEq]: SetDiff[A] = { 75 | val e = UnivEq.emptySet[A] 76 | apply(e, e) 77 | } 78 | 79 | @nowarn("cat=unused") 80 | def apply[A: UnivEq](removed: Set[A], added: Set[A]): SetDiff[A] = 81 | new SetDiff(removed, added) 82 | 83 | def compare[A: UnivEq](before: Set[A], after: Set[A]): SetDiff[A] = 84 | SetDiff(before -- after, after -- before) 85 | 86 | def compareOption[A: UnivEq](before: Option[Set[A]], after: Set[A]): SetDiff[A] = 87 | before match { 88 | case Some(b) => compare(b, after) 89 | case None => SetDiff(removed = Set.empty, added = after) 90 | } 91 | 92 | def compareFn[A: UnivEq](before: Set[A]): Set[A] => SetDiff[A] = 93 | compare(before, _) 94 | 95 | def xor[A: UnivEq](current: Set[A], xor: Set[A]): SetDiff[A] = { 96 | val (del, add) = xor.partition(current.contains) 97 | SetDiff(del, add) 98 | } 99 | 100 | implicit def functor: Functor[SetDiff] = 101 | new Functor[SetDiff] { 102 | override def map[A, B](fa: SetDiff[A])(f: A => B) = fa.map(f)(UnivEq.force) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/IMapBaseV.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import cats.instances.map._ 4 | import cats.syntax.foldable._ 5 | import cats.{Eq, Foldable} 6 | import japgolly.microlibs.stdlib_ext.StdlibExt._ 7 | import japgolly.univeq.UnivEq 8 | import scala.annotation.{elidable, nowarn} 9 | import scala.collection.IterableOnce 10 | 11 | object IMapBaseV { 12 | def catsEq[K, VI, VO: Eq, M <: IMapBaseV[K, VI, VO, M]]: Eq[M] = 13 | Eq.by(_.underlyingMap) 14 | 15 | @inline def univEq[K, VI, VO, I <: IMapBaseV[K, VI, VO, I]](implicit @nowarn("cat=unused") u: UnivEq[Map[K, VO]]): UnivEq[I] = 16 | UnivEq.force 17 | } 18 | 19 | abstract class IMapBaseV[K, VI, VO, This_ <: IMapBaseV[K, VI, VO, This_]] private[utils](m: Map[K, VO])(implicit @nowarn("cat=unused") u: UnivEq[K]) { 20 | final type This = This_ 21 | final type M = Map[K, VO] 22 | 23 | override final def hashCode = m.## 24 | override final def equals(o: Any) = o match { 25 | case n: IMapBaseV[_, _, _, _] => m equals n.underlyingMap 26 | case n: Map[_, _] => m equals n 27 | case _ => false 28 | } 29 | 30 | override final def toString = { 31 | val s = m.toString 32 | if (s startsWith "Map") 33 | stringPrefix + s.drop(3) 34 | else 35 | s"$stringPrefix($s)" 36 | } 37 | 38 | protected def stringPrefix: String 39 | protected def setmap(n: M): This 40 | protected def _gkey(v: VI): K 41 | protected def _values(v: VO): IterableOnce[VI] 42 | protected def _add(to: M, k: K, v: VI): M 43 | 44 | final protected def __add(to: M, v: VI): M = _add(to, _gkey(v), v) 45 | 46 | @inline final def underlyingMap = m 47 | @inline final def size = m.size 48 | 49 | @inline final def iterator() : Iterator[(K, VO)] = m.iterator 50 | @inline final def keys : Iterable[K] = m.keys 51 | @inline final def keysIterator() : Iterator[K] = m.keysIterator 52 | @inline final def keySet : Set[K] = m.keySet 53 | @inline final def values : Iterable[VO] = m.values 54 | @inline final def valuesIterator(): Iterator[VO] = m.valuesIterator 55 | 56 | final def containsKey(k: K): Boolean = 57 | m.contains(k) 58 | 59 | final def containsValue(v: VI): Boolean = 60 | containsKey(_gkey(v)) 61 | 62 | @inline final def mapValues[A](f: VO => A): Map[K, A] = 63 | m mapValuesNow f 64 | 65 | final def -(k: K) = 66 | setmap(m - k) 67 | 68 | @inline final def +(v: VI) = add(v) 69 | 70 | final def add(v: VI) = 71 | setmap(__add(m, v)) 72 | 73 | final def addAll(vs: VI*) = 74 | addAllInFoldable(vs) 75 | 76 | final def addAllInFoldable[F[_]: Foldable](vs: F[VI]) = 77 | setmap(vs.foldLeft(m)(__add)) 78 | 79 | final def ++(vs: IterableOnce[VI]) = 80 | setmap(vs.iterator.foldLeft(m)(__add)) 81 | 82 | final def --(ks: IterableOnce[K]): This = 83 | setmap(m -- ks) 84 | 85 | @elidable(elidable.ASSERTION) 86 | final def assertValidKeys(map: M): Unit = 87 | for { 88 | (k1, vo) <- map 89 | v <- _values(vo).iterator 90 | } assert(_gkey(v) == k1, s"Expected key for [$v] is [${_gkey(v)}] but [$k1] was found.") 91 | 92 | final def replaceUnderlying(map: M): This = { 93 | assertValidKeys(map) 94 | setmap(map) 95 | } 96 | 97 | final def mapUnderlying(f: M => M): This = 98 | replaceUnderlying(f(m)) 99 | } 100 | -------------------------------------------------------------------------------- /utils/shared/src/test/scala/japgolly/microlibs/utils/StaticLookupFnTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import utest._ 4 | 5 | object StaticLookupFnTest extends TestSuite { 6 | 7 | private sealed abstract class A(val key: Int) 8 | private case object A0 extends A(0) 9 | private case object A1 extends A(1) 10 | private case object A3 extends A(3) 11 | private case object A4 extends A(4) 12 | private case object A6 extends A(6) 13 | private case object Dup extends A(1) 14 | 15 | private val values = List[A](A0, A1, A3, A4, A6) 16 | 17 | private val badKeys = List[Int](-1, 2, 5, 7, 500) 18 | 19 | private def assertNoKey[@specialized(Int) K, V](f: K => V)(k: K): Unit = { 20 | intercept[NoSuchElementException]({f(k); ()}) 21 | () 22 | } 23 | 24 | private def assertDup[@specialized(Int) K, V](dsl: StaticLookupFn.DslBase[K, V]): Unit = { 25 | intercept[ExceptionInInitializerError]({dsl.total; ()}) 26 | intercept[ExceptionInInitializerError]({dsl.toOption; ()}) 27 | intercept[ExceptionInInitializerError]({dsl.toEither(_ => ()); ()}) 28 | () 29 | } 30 | 31 | override def tests = Tests { 32 | 33 | "array" - { 34 | val dsl = StaticLookupFn.useArrayBy(values)(_.key) 35 | 36 | "dup" - assertDup(StaticLookupFn.useArrayBy(Dup :: values)(_.key)) 37 | 38 | "total" - { 39 | val f = dsl.total 40 | values.foreach(a => f(a.key) ==> a) 41 | badKeys.foreach(assertNoKey(f)) 42 | } 43 | 44 | "option" - { 45 | val f = dsl.toOption 46 | values.foreach(a => f(a.key) ==> Some(a)) 47 | badKeys.foreach(f(_) ==> None) 48 | } 49 | 50 | "either" - { 51 | val f = dsl.toEither(identity) 52 | values.foreach(a => f(a.key) ==> Right(a)) 53 | badKeys.foreach(k => f(k) ==> Left(k)) 54 | } 55 | } 56 | 57 | "map" - { 58 | val dsl = StaticLookupFn.useMapBy(values)(_.key) 59 | 60 | "dup" - assertDup(StaticLookupFn.useArrayBy(Dup :: values)(_.key)) 61 | 62 | "total" - { 63 | val f = dsl.total 64 | values.foreach(a => f(a.key) ==> a) 65 | badKeys.foreach(assertNoKey(f)) 66 | } 67 | 68 | "option" - { 69 | val f = dsl.toOption 70 | values.foreach(a => f(a.key) ==> Some(a)) 71 | badKeys.foreach(f(_) ==> None) 72 | } 73 | 74 | "either" - { 75 | val f = dsl.toEither(identity) 76 | values.foreach(a => f(a.key) ==> Right(a)) 77 | badKeys.foreach(k => f(k) ==> Left(k)) 78 | } 79 | } 80 | 81 | "mapTiny" - { 82 | // Because I may have some optimisations to match Scala's optimised representations of Maps with <= 4 keys 83 | val values = StaticLookupFnTest.this.values.take(3) 84 | val dsl = StaticLookupFn.useMapBy(values)(_.key) 85 | 86 | "dup" - assertDup(StaticLookupFn.useArrayBy(Dup :: values)(_.key)) 87 | 88 | "total" - { 89 | val f = dsl.total 90 | values.foreach(a => f(a.key) ==> a) 91 | badKeys.foreach(assertNoKey(f)) 92 | } 93 | 94 | "option" - { 95 | val f = dsl.toOption 96 | values.foreach(a => f(a.key) ==> Some(a)) 97 | badKeys.foreach(f(_) ==> None) 98 | } 99 | 100 | "either" - { 101 | val f = dsl.toEither(identity) 102 | values.foreach(a => f(a.key) ==> Right(a)) 103 | badKeys.foreach(k => f(k) ==> Left(k)) 104 | } 105 | } 106 | 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-2/japgolly/microlibs/compiletime/CompileTimeInfo.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.reflect.macros.blackbox.Context 4 | 5 | object CompileTimeInfo { 6 | 7 | // Null versions 8 | 9 | def envVarOrNull(key: String): String = 10 | macro CompileTimeInfoMacros.envVarOrNull 11 | 12 | def sysPropOrNull(key: String): String = 13 | macro CompileTimeInfoMacros.sysPropOrNull 14 | 15 | def envVarOrSysPropOrNull(key: String): String = 16 | macro CompileTimeInfoMacros.envVarOrSysPropOrNull 17 | 18 | def sysPropOrEnvVarOrNull(key: String): String = 19 | macro CompileTimeInfoMacros.sysPropOrEnvVarOrNull 20 | 21 | // Option versions 22 | 23 | def envVar(key: String): Option[String] = 24 | macro CompileTimeInfoMacros.envVar 25 | 26 | def sysProp(key: String): Option[String] = 27 | macro CompileTimeInfoMacros.sysProp 28 | 29 | def envVarOrSysProp(key: String): Option[String] = 30 | macro CompileTimeInfoMacros.envVarOrSysProp 31 | 32 | def sysPropOrEnvVar(key: String): Option[String] = 33 | macro CompileTimeInfoMacros.sysPropOrEnvVar 34 | } 35 | 36 | // ===================================================================================================================== 37 | 38 | final class CompileTimeInfoMacros(val c: Context) extends MacroUtils { 39 | import c.universe._ 40 | 41 | private def _envVar(key: String): Option[String] = 42 | Option(System.getenv(key)) 43 | 44 | private def _sysProp(key: String): Option[String] = 45 | Option(System.getProperty(key, null)) 46 | 47 | // Null versions 48 | 49 | private def lit(o: Option[String]): c.Expr[String] = 50 | c.Expr[String](Literal(Constant(o.orNull))) 51 | 52 | def envVarOrNull(key: c.Expr[String]): c.Expr[String] = { 53 | val k = readMacroArg_string(key) 54 | val v = _envVar(k) 55 | lit(v) 56 | } 57 | 58 | def sysPropOrNull(key: c.Expr[String]): c.Expr[String] = { 59 | val k = readMacroArg_string(key) 60 | val v = _sysProp(k) 61 | lit(v) 62 | } 63 | 64 | def envVarOrSysPropOrNull(key: c.Expr[String]): c.Expr[String] = { 65 | val k = readMacroArg_string(key) 66 | val v = _envVar(k) orElse _sysProp(k) 67 | lit(v) 68 | } 69 | 70 | def sysPropOrEnvVarOrNull(key: c.Expr[String]): c.Expr[String] = { 71 | val k = readMacroArg_string(key) 72 | val v = _sysProp(k) orElse _envVar(k) 73 | lit(v) 74 | } 75 | 76 | // Option versions 77 | 78 | private def opt(o: Option[String]): c.Expr[Option[String]] = 79 | c.Expr[Option[String]]( 80 | o match { 81 | case Some(s) => q"_root_.scala.Some(${Literal(Constant(s))})" 82 | case None => q"_root_.scala.Option.empty[String]" 83 | } 84 | ) 85 | 86 | def envVar(key: c.Expr[String]): c.Expr[Option[String]] = { 87 | val k = readMacroArg_string(key) 88 | val v = _envVar(k) 89 | opt(v) 90 | } 91 | 92 | def sysProp(key: c.Expr[String]): c.Expr[Option[String]] = { 93 | val k = readMacroArg_string(key) 94 | val v = _sysProp(k) 95 | opt(v) 96 | } 97 | 98 | def envVarOrSysProp(key: c.Expr[String]): c.Expr[Option[String]] = { 99 | val k = readMacroArg_string(key) 100 | val v = _envVar(k) orElse _sysProp(k) 101 | opt(v) 102 | } 103 | 104 | def sysPropOrEnvVar(key: c.Expr[String]): c.Expr[Option[String]] = { 105 | val k = readMacroArg_string(key) 106 | val v = _sysProp(k) orElse _envVar(k) 107 | opt(v) 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /stdlib-ext/jvm/src/main/scala/japgolly/microlibs/stdlib_ext/PlatformSpecificEscapeUtils.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | // *********** 4 | // * * 5 | // * JVM * 6 | // * * 7 | // *********** 8 | 9 | import java.lang.{StringBuilder => JStringBuilder} 10 | 11 | trait PlatformSpecificEscapeUtils { self: EscapeUtils.type => 12 | 13 | override def quote(s: String): String = { 14 | val sb = new JStringBuilder(s.length + (s.length >> 1) + 2) 15 | appendQuoted(sb, s) 16 | sb.toString 17 | } 18 | 19 | override def appendQuoted(sb: JStringBuilder, s: String): Unit = { 20 | sb.append('"') 21 | appendEscaped(sb, s) 22 | sb.append('"') 23 | () 24 | } 25 | override def appendQuoted(sb: StringBuilder, s: String): Unit = { 26 | sb.append('"') 27 | appendEscaped(sb, s) 28 | sb.append('"') 29 | () 30 | } 31 | 32 | override def escape(s: String): String = { 33 | val sb = new JStringBuilder(s.length + (s.length >> 1)) 34 | appendEscaped(sb, s) 35 | sb.toString 36 | } 37 | 38 | override def appendEscaped(sb: JStringBuilder, s: String): Unit = { 39 | val chars = s.toCharArray() 40 | var i = 0 41 | var c = 'x' 42 | while (i < chars.length) { 43 | c = chars(i) 44 | if (c == '\\') sb.append("\\\\") 45 | else if (c == '\"') sb.append("\\\"") 46 | else if (c == '\r') sb.append("\\r") 47 | else if (c == '\n') sb.append("\\n") 48 | else if (c == '\t') sb.append("\\t") 49 | else if (c == '\b') sb.append("\\b") 50 | else if (c == '\f') sb.append("\\f") 51 | else if (c < 32) sb.append("\\u%04x".format(c.toInt)) 52 | else sb.append(c) 53 | i += 1 54 | } 55 | } 56 | override def appendEscaped(sb: StringBuilder, s: String): Unit = { 57 | val chars = s.toCharArray() 58 | var i = 0 59 | var c = 'x' 60 | while (i < chars.length) { 61 | c = chars(i) 62 | if (c == '\\') sb.append("\\\\") 63 | else if (c == '\"') sb.append("\\\"") 64 | else if (c == '\r') sb.append("\\r") 65 | else if (c == '\n') sb.append("\\n") 66 | else if (c == '\t') sb.append("\\t") 67 | else if (c == '\b') sb.append("\\b") 68 | else if (c == '\f') sb.append("\\f") 69 | else if (c < 32) sb.append("\\u%04x".format(c.toInt)) 70 | else sb.append(c) 71 | i += 1 72 | } 73 | } 74 | 75 | def htmlEscape(s: String): String = { 76 | val sb = new JStringBuilder(s.length << 1) 77 | appendQuoted(sb, s) 78 | sb.toString 79 | } 80 | 81 | def htmlAppendEscaped(sb: JStringBuilder, s: String): Unit = { 82 | val chars = s.toCharArray() 83 | var i = 0 84 | var c = 'x' 85 | while (i < chars.length) { 86 | c = chars(i) 87 | if (c == '\"') sb.append(""") 88 | else if (c == '<') sb.append("<") 89 | else if (c == '>') sb.append(">") 90 | else if (c == '&') sb.append("&") 91 | else if (c == '\'') sb.append("'") 92 | else sb.append(c) 93 | i += 1 94 | } 95 | } 96 | def htmlAppendEscaped(sb: StringBuilder, s: String): Unit = { 97 | val chars = s.toCharArray() 98 | var i = 0 99 | var c = 'x' 100 | while (i < chars.length) { 101 | c = chars(i) 102 | if (c == '\"') sb.append(""") 103 | else if (c == '<') sb.append("<") 104 | else if (c == '>') sb.append(">") 105 | else if (c == '&') sb.append("&") 106 | else if (c == '\'') sb.append("'") 107 | else sb.append(c) 108 | i += 1 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/TransitiveClosure.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import cats.Eval 4 | import japgolly.microlibs.utils.TransitiveClosure.Filter 5 | import japgolly.univeq.UnivEq 6 | import scala.collection.immutable.BitSet 7 | import scala.reflect.ClassTag 8 | 9 | object TransitiveClosure { 10 | def auto[A: UnivEq: ClassTag](as : IterableOnce[A]) 11 | (directChildren: A => Iterable[A], 12 | filter : A => Filter = Filter.followAll) = { 13 | 14 | val map = as.iterator.zipWithIndex.toMap 15 | val array = new Array[A](map.size) 16 | for ((k,v) <- map) 17 | array(v) = k 18 | new TransitiveClosure(map.apply, array.apply, array.length, directChildren, filter) 19 | } 20 | 21 | sealed abstract class Filter 22 | object Filter { 23 | 24 | /** Include the subject node and follow its vertices. */ 25 | case object Follow extends Filter 26 | 27 | /** Include the subject node but do not follow its vertices. */ 28 | case object Terminal extends Filter 29 | 30 | /** Exclude the subject node and its vertices. */ 31 | case object Exclude extends Filter 32 | 33 | val followAll: Any => Filter = 34 | _ => Follow 35 | 36 | def terminalSet[A](terminals: Set[A]): A => Filter = 37 | a => if (terminals.contains(a)) Terminal else Follow 38 | } 39 | } 40 | 41 | /** 42 | * Only works with acyclic digraphs. 43 | * Closure is also reflexive. 44 | * 45 | * Laws 46 | * ==== 47 | * i ∈ [0,size) 48 | * a2i.i2a = id 49 | * 50 | * @param a2i Global index of a node. 51 | * @param i2a Node at global index. 52 | * @param directChildren Each direct child of a given node. Non-transitive; don't return self. 53 | */ 54 | final class TransitiveClosure[A: UnivEq](a2i : A => Int, 55 | i2a : Int => A, 56 | size : Int, 57 | directChildren: A => Iterable[A], 58 | filter : A => Filter) { 59 | 60 | private var traversing: BitSet = 61 | BitSet.empty 62 | 63 | private val closure: Array[Eval[BitSet]] = 64 | new Array(size) 65 | 66 | // Init 67 | for (i <- 0 until size) { 68 | closure(i) = Eval.later[BitSet] { 69 | val a = i2a(i) 70 | val z = BitSet.empty + i 71 | traversing += i 72 | try 73 | directChildren(a).foldLeft(z) { (q, c) => 74 | filter(c) match { 75 | case Filter.Follow => 76 | val ci = a2i(c) 77 | if (traversing(ci)) 78 | q // cycle found 79 | else 80 | q ++ tc(ci) 81 | case Filter.Terminal => q + a2i(c) 82 | case Filter.Exclude => q 83 | } 84 | } 85 | finally 86 | traversing -= i 87 | } 88 | } 89 | 90 | @inline private def tc(i: Int): BitSet = 91 | closure(i).value 92 | 93 | // private def a2io(a: A): Option[Int] = 94 | // try Some(a2i(a)) catch { 95 | // case _: Throwable => None 96 | // } 97 | 98 | private def a2is(a: A)(f: Int => Set[A]): Set[A] = 99 | // a2io(a).fold(empty)(f) 100 | f(a2i(a)) 101 | 102 | @inline private def empty = UnivEq.emptySet[A] 103 | 104 | /** Reflexive */ 105 | def apply(a: A): Set[A] = 106 | a2is(a)(i => 107 | tc(i).foldLeft(empty)(_ + i2a(_))) 108 | 109 | /** Non-reflexive */ 110 | def nonRefl(a: A): Set[A] = 111 | a2is(a)(i => 112 | tc(i).foldLeft(empty)((q, j) => 113 | if (i == j) q else q + i2a(j))) 114 | } 115 | -------------------------------------------------------------------------------- /recursion/shared/src/main/scala/japgolly/microlibs/recursion/Recursion.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.recursion 2 | 3 | import cats.{Functor, Monad, Traverse, ~>} 4 | 5 | object Recursion { 6 | 7 | def cata[F[_], A](alg: FAlgebra[F, A])(f: Fix[F])(implicit F: Functor[F]): A = 8 | RecursionFn.cata(alg).apply(f) 9 | 10 | def cataM[M[_], F[_], A](alg: FAlgebraM[M, F, A])(f: Fix[F])(implicit M: Monad[M], F: Traverse[F]): M[A] = 11 | RecursionFn.cataM(alg).apply(f) 12 | 13 | def ana[F[_], A](coalg: FCoalgebra[F, A])(a: A)(implicit F: Functor[F]): Fix[F] = 14 | RecursionFn.ana(coalg).apply(a) 15 | 16 | def anaM[M[_], F[_], A](coalg: FCoalgebraM[M, F, A])(a: A)(implicit M: Monad[M], F: Traverse[F]): M[Fix[F]] = 17 | RecursionFn.anaM(coalg).apply(a) 18 | 19 | /** ana with immediate cata */ 20 | def hylo[F[_], A, B](coalg: FCoalgebra[F, A], alg: FAlgebra[F, B])(a: A)(implicit F: Functor[F]): B = 21 | RecursionFn.hylo(coalg, alg).apply(a) 22 | 23 | def hyloM[M[_], F[_], A, B](coalg: FCoalgebraM[M, F, A], alg: FAlgebraM[M, F, B])(a: A)(implicit M: Monad[M], F: Traverse[F]): M[B] = 24 | RecursionFn.hyloM(coalg, alg).apply(a) 25 | 26 | /** cata that transforms children before folding. 27 | * Top-most structure (i.e. the input) is not transformed. 28 | * Outside to inside. 29 | */ 30 | def prepro[F[_], A](pre: F ~> F, alg: FAlgebra[F, A])(f: Fix[F])(implicit F: Functor[F]): A = 31 | RecursionFn.prepro(pre, alg).apply(f) 32 | 33 | /** ana that creates a structure, transforming each new child (i.e. the entire structure as exists at the end of a pass). 34 | * Top-most structure (i.e. the end result) is not transformed. 35 | * Inside to outside. 36 | */ 37 | def postpro[F[_], A](coalg: FCoalgebra[F, A], pro: F ~> F)(a: A)(implicit F: Functor[F]): Fix[F] = 38 | RecursionFn.postpro(coalg, pro).apply(a) 39 | 40 | /** hylo that can short-circuit on construction */ 41 | def elgot[F[_], A, B](elcoalg: A => B Either F[A], alg: FAlgebra[F, B])(a: A)(implicit F: Functor[F]): B = 42 | RecursionFn.elgot(elcoalg, alg).apply(a) 43 | 44 | /** hylo that can short-circuit on reduction */ 45 | def coelgot[F[_], A, B](coalg: FCoalgebra[F, A], elalg: (A, () => F[B]) => B)(a: A)(implicit F: Functor[F]): B = 46 | RecursionFn.coelgot(coalg, elalg).apply(a) 47 | 48 | /** cata that has access to current subtree (Fix[F]) as well as that subtree's folded result (A) */ 49 | def para[F[_], A](alg: RAlgebra[F, A])(f: Fix[F])(implicit F: Functor[F]): A = 50 | RecursionFn.para(alg).apply(f) 51 | 52 | /** ana that can branch / short-circuit */ 53 | def apo[F[_], A](coalg: RCoalgebra[F, A])(a: A)(implicit F: Functor[F]): Fix[F] = 54 | RecursionFn.apo(coalg).apply(a) 55 | 56 | /** cata that retains values of all previous (i.e. child) steps */ 57 | def histo[F[_], A](alg: CVAlgebra[F, A])(f: Fix[F])(implicit F: Functor[F]): A = 58 | RecursionFn.histo(alg).apply(f) 59 | 60 | /** ana that can build multiple levels in a single pass */ 61 | def futu[F[_], A](coalg: CVCoalgebra[F, A])(a: A)(implicit F: Functor[F]): Fix[F] = 62 | RecursionFn.futu(coalg).apply(a) 63 | 64 | /** hylo of futu into histo */ 65 | def chrono[F[_], A, B](coalg: CVCoalgebra[F, A], alg: CVAlgebra[F, B])(a: A)(implicit F: Functor[F]): B = 66 | RecursionFn.chrono(coalg, alg).apply(a) 67 | 68 | /** See "Abstracting Definitional Interpreters". */ 69 | def adi[F[_], A](alg: FAlgebra[F, A], f: (Fix[F] => A) => Fix[F] => A)(ff: Fix[F])(implicit F: Functor[F]): A = 70 | RecursionFn.adi(alg, f).apply(ff) 71 | 72 | /** See "Abstracting Definitional Interpreters". */ 73 | def adiM[M[_], F[_], A](alg: FAlgebraM[M, F, A], f: (Fix[F] => M[A]) => Fix[F] => M[A])(ff: Fix[F])(implicit M: Monad[M], F: Traverse[F]): M[A] = 74 | RecursionFn.adiM(alg, f).apply(ff) 75 | } 76 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/EasierValDef.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | 5 | object EasierValDef: 6 | 7 | def untypedValDef(using q: Quotes) 8 | (name : String, 9 | tpe : q.reflect.TypeRepr, 10 | flags: q.reflect.Flags = q.reflect.Flags.EmptyFlags, 11 | )(rhs: q.reflect.Term): UntypedValDef.WithQuotes[q.type] = 12 | import quotes.reflect.* 13 | val sym = Symbol.newVal(Symbol.spliceOwner, name, tpe, flags, Symbol.noSymbol) 14 | val vd = ValDef(sym, Some(rhs)) 15 | Ref(sym) match 16 | case ref: Ident => UntypedValDef(using q)(sym, vd, ref) 17 | 18 | object UntypedValDef: 19 | type WithQuotes[Q <: Quotes] = UntypedValDef { val q: Q } 20 | 21 | def apply(using q: Quotes) 22 | (symbol: q.reflect.Symbol, 23 | valDef: q.reflect.ValDef, 24 | ref : q.reflect.Ident): WithQuotes[q.type] = 25 | new UntypedValDef(using q)(symbol, valDef, ref) 26 | .asInstanceOf[WithQuotes[q.type]] // TODO: S3 27 | 28 | final class UntypedValDef(using val q: Quotes) 29 | (val symbol: q.reflect.Symbol, 30 | val valDef: q.reflect.ValDef, 31 | val ref : q.reflect.Ident) { 32 | import q.reflect.* 33 | 34 | def substQ(using qq: Quotes) = 35 | this.asInstanceOf[UntypedValDef.WithQuotes[qq.type]] 36 | 37 | def as[A: Type]: TypedValDef.WithQuotes[A, q.type] = 38 | import quotes.reflect.* 39 | TypedValDef(symbol, valDef, ref.asExprOf[A]) 40 | 41 | def assign(rhs: Term): Assign = 42 | Assign(ref, rhs) 43 | 44 | def modify(f: Ident => Term): Assign = 45 | assign(f(ref)) 46 | } 47 | 48 | // =================================================================================================================== 49 | 50 | def typedValDef[A: Type](using q: Quotes) 51 | (name : String, 52 | flags: q.reflect.Flags = q.reflect.Flags.EmptyFlags, 53 | )(rhs: Expr[A]): TypedValDef.WithQuotes[A, q.type] = 54 | import quotes.reflect.* 55 | val u = untypedValDef(using q)(name, TypeRepr.of[A], flags)(rhs.asTerm) 56 | u.as[A] 57 | 58 | object TypedValDef: 59 | type WithQuotes[A, Q <: Quotes] = TypedValDef[A] { val q: Q } 60 | 61 | def apply[A](using q: Quotes) 62 | (symbol: q.reflect.Symbol, 63 | valDef: q.reflect.ValDef, 64 | ref : Expr[A]): WithQuotes[A, q.type] = 65 | new TypedValDef(using q)(symbol, valDef, ref) 66 | .asInstanceOf[WithQuotes[A, q.type]] // TODO: S3 67 | 68 | final class TypedValDef[A](using val q: Quotes) 69 | (val symbol: q.reflect.Symbol, 70 | val valDef: q.reflect.ValDef, 71 | val ref : Expr[A]) { self => 72 | import q.reflect.* 73 | 74 | lazy val untyped: UntypedValDef.WithQuotes[q.type] = 75 | ref.asTerm match 76 | case i: Ident => (new UntypedValDef(using q)(symbol, valDef, i)).substQ 77 | 78 | def subst[B] = 79 | this.asInstanceOf[TypedValDef.WithQuotes[B, self.q.type]] 80 | 81 | def substQ(using qq: Quotes) = 82 | this.asInstanceOf[TypedValDef.WithQuotes[A, qq.type]] 83 | 84 | def use[B: Type](f: Expr[A] => Expr[B]): Expr[B] = 85 | Block(valDef :: Nil, f(ref).asTerm).asExprOf[B] 86 | 87 | def assignTerm(rhs: Expr[A]): Assign = 88 | untyped.assign(rhs.asTerm) 89 | 90 | def assign(rhs: Expr[A]): Expr[Unit] = 91 | assignTerm(rhs).asExprOf[Unit] 92 | 93 | def modify(f: Expr[A] => Expr[A]): Expr[Unit] = 94 | assign(f(ref)) 95 | } 96 | -------------------------------------------------------------------------------- /test-util/shared/src/main/scala/japgolly/microlibs/testutil/TestUtilInternals.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.testutil 2 | 3 | import scala.annotation.nowarn 4 | import scala.io.AnsiColor._ 5 | import sourcecode.Line 6 | 7 | object TestUtilInternals { 8 | 9 | // scala.Console.BOLD exists but not BRIGHT 10 | // The difference affects OS X 11 | final val BRIGHT_BLACK = "\u001b[90m" 12 | final val BRIGHT_RED = "\u001b[91m" 13 | final val BRIGHT_GREEN = "\u001b[92m" 14 | final val BRIGHT_YELLOW = "\u001b[93m" 15 | final val BRIGHT_BLUE = "\u001b[94m" 16 | final val BRIGHT_MAGENTA = "\u001b[95m" 17 | final val BRIGHT_CYAN = "\u001b[96m" 18 | final val BRIGHT_WHITE = "\u001b[97m" 19 | 20 | final val BOLD_BRIGHT_BLACK = "\u001b[90;1m" 21 | final val BOLD_BRIGHT_RED = "\u001b[91;1m" 22 | final val BOLD_BRIGHT_GREEN = "\u001b[92;1m" 23 | final val BOLD_BRIGHT_YELLOW = "\u001b[93;1m" 24 | final val BOLD_BRIGHT_BLUE = "\u001b[94;1m" 25 | final val BOLD_BRIGHT_MAGENTA = "\u001b[95;1m" 26 | final val BOLD_BRIGHT_CYAN = "\u001b[96;1m" 27 | final val BOLD_BRIGHT_WHITE = "\u001b[97;1m" 28 | 29 | object Poison 30 | type Poison = Poison.type 31 | 32 | private[testutil] val printMutex = new AnyRef 33 | 34 | def lead(s: String) = s"$RED_B$s$RESET " 35 | 36 | def descMethod(method: String, desc: Option[String]): String = 37 | s"$method${desc.fold("")("(" + _ + ")")}" 38 | 39 | def failureStart(name: Option[String], leadSize: Int): Unit = { 40 | println() 41 | name.foreach(n => println(lead(">" * leadSize) + BRIGHT_YELLOW + n + RESET)) 42 | } 43 | 44 | def printFailEA(name: Option[String], actual: Any, expect: Any): Unit = 45 | printFail2(name)("expect", BOLD_BRIGHT_GREEN, expect)("actual", BOLD_BRIGHT_RED, actual) 46 | 47 | def printFail2(name: Option[String]) 48 | (title1: String, colour1: String, value1: Any) 49 | (title2: String, colour2: String, value2: Any): Unit = { 50 | 51 | val titleLen = title1.length max title2.length 52 | val leadFmt = s"%${titleLen}s:" 53 | 54 | failureStart(name, titleLen + 1) 55 | 56 | @nowarn 57 | val toString: Any => String = { 58 | case s: Stream[_] => s.force.toString() // SI-9266 59 | case a => a.toString 60 | } 61 | 62 | val show1 = toString(value1) 63 | val show2 = toString(value2) 64 | val ss = show2 :: show1 :: Nil 65 | var pre = "[" 66 | var post = "]" 67 | val htChars = ss.flatMap(s => s.headOption :: s.lastOption :: Nil) 68 | if (htChars.forall(_.exists(c => !Character.isWhitespace(c)))) { 69 | pre = "" 70 | post = "" 71 | } 72 | if (ss.exists(_ contains "\n")) { 73 | pre = "↙[\n" 74 | } 75 | println(lead(leadFmt.format(title1)) + pre + colour1 + show1 + RESET + post) 76 | println(lead(leadFmt.format(title2)) + pre + colour2 + show2 + RESET + post) 77 | } 78 | 79 | def addSrcHint(msg: String)(implicit q: Line): String = 80 | s"$msg [L${q.value}]" 81 | 82 | def quoteStringForDisplay(s: String): String = { 83 | val sb = new StringBuilder 84 | sb append '⟪' 85 | s foreach { 86 | case '\b' => sb append '\\'; sb append 'b' 87 | case '\f' => sb append '\\'; sb append 'f' 88 | case '\n' => sb append '\\'; sb append 'n' 89 | case '\r' => sb append '\\'; sb append 'r' 90 | case '\t' => sb append '\\'; sb append 't' 91 | case '\\' => sb append '\\'; sb append '\\' 92 | case c => 93 | if (c >= ' ' && c <= '~') 94 | sb append c 95 | else { 96 | val hex = Integer.toHexString(c.toInt) 97 | sb append "\\u" 98 | hex.length match { 99 | case 1 => sb append "000" 100 | case 2 => sb append "00" 101 | case 3 => sb append '0' 102 | case _ => 103 | } 104 | sb append hex 105 | } 106 | } 107 | sb append '⟫' 108 | sb.toString() 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/BiMap.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.univeq.UnivEq 4 | import scala.annotation.nowarn 5 | import scala.collection.immutable.IntMap 6 | 7 | /** 8 | * Bidirectional maps between values of two key types. 9 | * 10 | * @since 31/05/2013 11 | */ 12 | final class BiMap[A, B] private (val forward: Map[A, B], val backward: Map[B, A]) { 13 | assert(forward.size == backward.size, s"forward.size (${forward.size}) ≠ backward.size (${backward.size})") 14 | 15 | override def toString = s"BiMap($forward, …)" 16 | 17 | override def hashCode = forward.## 18 | 19 | @nowarn 20 | override def equals(obj: Any): Boolean = 21 | obj match { 22 | case x: BiMap[A, B] => this.forward == x.forward 23 | case _ => false 24 | } 25 | 26 | def isEmpty = forward.isEmpty 27 | 28 | @inline def nonEmpty = !isEmpty 29 | 30 | def size = forward.size 31 | 32 | def toMap[C](forwards: Boolean) 33 | (implicit ev1: Map[A, B] =:= Map[C, C], ev2: Map[B, A] =:= Map[C, C]): Map[C, C] = 34 | if (forwards) forward else backward 35 | 36 | def toSet(implicit ev: B =:= A): Set[A] = { 37 | val b = Set.newBuilder[A] 38 | forward.foreach { t => 39 | b += t._1 40 | b += t._2 41 | } 42 | b.result() 43 | } 44 | 45 | def flip: BiMap[B, A] = 46 | new BiMap(backward, forward) 47 | } 48 | 49 | object BiMap { 50 | implicit def univEq[A, B]: UnivEq[BiMap[A, B]] = 51 | UnivEq.force 52 | 53 | @nowarn("cat=unused") 54 | def force[A: UnivEq, B: UnivEq](forward: Map[A, B])(backward: Map[B, A]): BiMap[A, B] = 55 | new BiMap(forward, backward) 56 | 57 | def apply[A: UnivEq, B: UnivEq](forward: Map[A, B]): BiMap[A, B] = 58 | force(forward) { 59 | var m = Map.empty[B, A] 60 | forward.foreach(t => m = m.updated(t._2, t._1)) 61 | m 62 | } 63 | 64 | def empty[A: UnivEq, B: UnivEq]: BiMap[A, B] = 65 | force[A, B](Map.empty)(Map.empty) 66 | 67 | def singleton[A: UnivEq, B: UnivEq](a: A, b: B): BiMap[A, B] = 68 | force[A, B](Map.empty.updated(a, b))(Map.empty.updated(b, a)) 69 | 70 | def index[A: UnivEq](as: IterableOnce[A]): BiMap[A, Int] = { 71 | var i = 0 72 | val b = newBuilderInt[A] 73 | as.iterator.foreach { a => 74 | b.update(a, i) 75 | i += 1 76 | } 77 | b.result() 78 | } 79 | 80 | // =================================================================================================================== 81 | 82 | @nowarn("cat=unused") 83 | abstract class AbstractBuilder[A: UnivEq, @specialized(Int) B: UnivEq] { 84 | protected def updateAB(a: A, b: B): Unit 85 | protected def updateBA(b: B, a: A): Unit 86 | def result(): BiMap[A, B] 87 | 88 | final def update(a: A, b: B): Unit = { 89 | updateAB(a, b) 90 | updateBA(b, a) 91 | } 92 | 93 | @inline final def +=(ab: (A, B)): Unit = 94 | update(ab._1, ab._2) 95 | 96 | final def ++=(abs: IterableOnce[(A, B)]): Unit = 97 | abs.iterator.foreach(t => update(t._1, t._2)) 98 | } 99 | 100 | final class Builder[A: UnivEq, B: UnivEq] extends AbstractBuilder[A, B] { 101 | private[this] var ab = Map.empty[A, B] 102 | private[this] var ba = Map.empty[B, A] 103 | override protected def updateAB(a: A, b: B) = ab = ab.updated(a, b) 104 | override protected def updateBA(b: B, a: A) = ba = ba.updated(b, a) 105 | override def result() = force(ab)(ba) 106 | } 107 | 108 | final class BuilderInt[A: UnivEq] extends AbstractBuilder[A, Int] { 109 | private[this] var ab = Map.empty[A, Int] 110 | private[this] var ba = IntMap.empty[A] 111 | override protected def updateAB(a: A, i: Int) = ab = ab.updated(a, i) 112 | override protected def updateBA(i: Int, a: A) = ba = ba.updated(i, a) 113 | override def result() = force(ab)(ba) 114 | } 115 | 116 | @inline def newBuilder[A: UnivEq, B: UnivEq] = new Builder[A, B] 117 | @inline def newBuilderInt[A: UnivEq] = new BuilderInt[A] 118 | } -------------------------------------------------------------------------------- /multimap/shared/src/main/scala/japgolly/microlibs/multimap/MultiValues.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.multimap 2 | 3 | import scala.collection.immutable.ArraySeq 4 | 5 | trait MultiValues[L[_]] { 6 | def empty[A]: L[A] 7 | def add1[A](a: L[A], b: A): L[A] 8 | def del1[A](a: L[A], b: A): L[A] 9 | def addn[A](a: L[A], b: L[A]): L[A] 10 | def deln[A](a: L[A], b: L[A]): L[A] 11 | def foldl[A, B](a: A, b: L[B])(f: (A, B) => A): A 12 | def foldr[A, B](a: A, b: L[B])(f: (A, B) => A): A 13 | def iterator[A](a: L[A]): Iterator[A] 14 | def isEmpty[A](a: L[A]): Boolean 15 | } 16 | 17 | object MultiValues { 18 | @inline def apply[L[_]](implicit F: MultiValues[L]): MultiValues[L] = F 19 | 20 | trait Commutative[L[_]] 21 | 22 | implicit object ListMultiValues extends MultiValues[List] { 23 | override def empty [A] = List.empty[A] 24 | override def add1 [A] (a: List[A], b: A) = b :: a 25 | override def del1 [A] (a: List[A], b: A) = a.filterNot(_ == b) 26 | override def addn [A] (a: List[A], b: List[A]) = b ::: a 27 | override def deln [A] (a: List[A], b: List[A]) = {val s = b.toSet; a filterNot s.contains} 28 | override def foldl [A,B](a: A, b: List[B])(f: (A, B) => A) = b.foldLeft(a)(f) 29 | override def foldr [A,B](a: A, b: List[B])(f: (A, B) => A) = b.foldRight(a)((x, y) => f(y, x)) 30 | override def iterator[A] (a: List[A]) = a.iterator 31 | override def isEmpty [A] (a: List[A]) = a.isEmpty 32 | } 33 | 34 | implicit object SetMultiValues extends MultiValues[Set] with Commutative[Set] { 35 | override def empty [A] = Set.empty[A] 36 | override def add1 [A] (a: Set[A], b: A) = a + b 37 | override def del1 [A] (a: Set[A], b: A) = a - b 38 | override def addn [A] (a: Set[A], b: Set[A]) = a ++ b 39 | override def deln [A] (a: Set[A], b: Set[A]) = a -- b 40 | override def foldl [A,B](a: A, b: Set[B])(f: (A, B) => A) = b.foldLeft(a)(f) 41 | override def foldr [A,B](a: A, b: Set[B])(f: (A, B) => A) = b.foldRight(a)((x, y) => f(y, x)) 42 | override def iterator[A] (a: Set[A]) = a.iterator 43 | override def isEmpty [A] (a: Set[A]) = a.isEmpty 44 | } 45 | 46 | implicit object VectorMultiValues extends MultiValues[Vector] { 47 | override def empty [A] = Vector.empty[A] 48 | override def add1 [A] (a: Vector[A], b: A) = a :+ b 49 | override def del1 [A] (a: Vector[A], b: A) = a.filterNot(_ == b) 50 | override def addn [A] (a: Vector[A], b: Vector[A]) = a ++ b 51 | override def deln [A] (a: Vector[A], b: Vector[A]) = {val s = b.toSet; a filterNot s.contains} 52 | override def foldl [A,B](a: A, b: Vector[B])(f: (A, B) => A) = b.foldLeft(a)(f) 53 | override def foldr [A,B](a: A, b: Vector[B])(f: (A, B) => A) = b.foldRight(a)((x, y) => f(y, x)) 54 | override def iterator[A] (a: Vector[A]) = a.iterator 55 | override def isEmpty [A] (a: Vector[A]) = a.isEmpty 56 | } 57 | 58 | implicit object ArraySeqMultiValues extends MultiValues[ArraySeq] { 59 | override def empty [A] = ArraySeq.empty[Any].asInstanceOf[ArraySeq[A]] 60 | override def add1 [A] (a: ArraySeq[A], b: A) = a :+ b 61 | override def del1 [A] (a: ArraySeq[A], b: A) = a.filterNot(_ == b) 62 | override def addn [A] (a: ArraySeq[A], b: ArraySeq[A]) = a ++ b 63 | override def deln [A] (a: ArraySeq[A], b: ArraySeq[A]) = {val s = b.toSet; a filterNot s.contains} 64 | override def foldl [A,B](a: A, b: ArraySeq[B])(f: (A, B) => A) = b.foldLeft(a)(f) 65 | override def foldr [A,B](a: A, b: ArraySeq[B])(f: (A, B) => A) = b.foldRight(a)((x, y) => f(y, x)) 66 | override def iterator[A] (a: ArraySeq[A]) = a.iterator 67 | override def isEmpty [A] (a: ArraySeq[A]) = a.isEmpty 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /adt-macros/shared/src/test/scala/japgolly/microlibs/adt_macros/AdtMacroTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.adt_macros 2 | 3 | import aa._ 4 | import japgolly.microlibs.adt_macros.AdtMacros._ 5 | import japgolly.microlibs.nonempty.{NonEmptySet, NonEmptyVector} 6 | import japgolly.univeq.UnivEq 7 | import utest._ 8 | 9 | object AdtMacroTest extends TestSuite { 10 | 11 | def assertOrderedNEV[A](actual: NonEmptyVector[A], expect: NonEmptyVector[A]): Unit = 12 | assert(actual == expect) 13 | 14 | def assertOrderedNEV[A](actual: NonEmptyVector[A])(e1: A, eN: A*): Unit = 15 | assertOrderedNEV(actual, NonEmptyVector(e1, eN.toVector)) 16 | 17 | def assertUnorderedNEV[A](actual: NonEmptyVector[A], expect: NonEmptyVector[A]): Unit = { 18 | val norm: NonEmptyVector[A] => NonEmptyVector[A] = _.sortBy(_.toString) 19 | assertOrderedNEV(norm(actual), norm(expect)) 20 | } 21 | 22 | def assertUnorderedNEV[A](actual: NonEmptyVector[A])(e1: A, eN: A*): Unit = 23 | assertUnorderedNEV(actual, NonEmptyVector(e1, eN.toVector)) 24 | 25 | def assertFail(e: CompileError) = e.msg 26 | 27 | implicit def univEqS3: UnivEq[MonoS3] = UnivEq.derive[MonoS3] 28 | 29 | override def tests = Tests { 30 | 31 | "adtValues" - { 32 | // 's1i - assertUnorderedNEV(MonoS1.Values)(MonoS1.A) 33 | // 's3i - assertUnorderedNEV(MonoS3.Values)(MonoS3.A, MonoS3.B, MonoS3.C) 34 | "s1" - assertUnorderedNEV(adtValues[MonoS1])(MonoS1.A) 35 | "s3" - assertUnorderedNEV(adtValues[MonoS3])(MonoS3.A, MonoS3.B, MonoS3.C) 36 | "d1" - assertFail(compileError("adtValues[MonoD1]")) 37 | "d2" - assertFail(compileError("adtValues[MonoD2]")) 38 | "unsealed" - assertFail(compileError("adtValues[Unsealed]")) 39 | 40 | // TODO Pending https://github.com/lampepfl/dotty/issues/11765 41 | // "emptySubtype" - { 42 | // import EmptySubType._ 43 | // assertUnorderedNEV(adtValues[A])(D) 44 | // } 45 | } 46 | 47 | "valuesForAdt" - { 48 | "ok" - { 49 | import MonoD._ 50 | assertUnorderedNEV(valuesForAdt[MonoD, String] { 51 | case _: A => "A" 52 | case _: B => "B" 53 | case C => "C" 54 | case _: D => "D" 55 | })("A", "B", "C", "D") 56 | } 57 | // TODO Pending https://github.com/lampepfl/dotty/issues/11765 58 | // "sub" - { 59 | // import MonoSub._ 60 | // assertUnorderedNEV(valuesForAdt[MonoSub, String] { 61 | // case A => "A" 62 | // case _: B => "B" 63 | // })("A", "B") 64 | // } 65 | "missing" - { 66 | import MonoD._ 67 | assertFail(compileError("valuesForAdt[MonoD, String] {case _: A => \"A\"}")) 68 | } 69 | "dup" - { 70 | import MonoD._ 71 | assertFail(compileError( 72 | """ 73 | valuesForAdt[MonoD, String] { 74 | case _: A => "A1" 75 | case _: A => "A2" 76 | case _: B => "B" 77 | case C => "C" 78 | case _: D => "D" 79 | } 80 | """)) 81 | } 82 | "extra" - { 83 | import MonoD._ 84 | assertFail(compileError( 85 | """ 86 | valuesForAdt[MonoD, String] { 87 | case _: A => "A" 88 | case _: Int => "I" 89 | case _: B => "B" 90 | case C => "C" 91 | case _: D => "D" 92 | } 93 | """)) 94 | } 95 | } 96 | 97 | "adtIso" - { 98 | val (mc, cm, ms, cs) = adtIso[MonoS3, Char] { 99 | case MonoS3.A => 'a' 100 | case MonoS3.B => 'b' 101 | case MonoS3.C => 'c' 102 | } 103 | assertUnorderedNEV(ms)(MonoS3.A, MonoS3.B, MonoS3.C) 104 | assertUnorderedNEV(cs)('a', 'b', 'c') 105 | for (m <- ms) 106 | assert(cm(mc(m)) == m) 107 | } 108 | 109 | "adtIsoSet" - { 110 | val (mc, cm, ms, cs) = adtIsoSet[MonoS3, Char] { 111 | case MonoS3.A => 'a' 112 | case MonoS3.B => 'b' 113 | case MonoS3.C => 'c' 114 | } 115 | assert(ms == NonEmptySet[MonoS3](MonoS3.A, MonoS3.B, MonoS3.C)) 116 | assert(cs == NonEmptySet('a', 'b', 'c')) 117 | for (m <- ms) 118 | assert(cm(mc(m)) == m) 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /nonempty/shared/src/main/scala/japgolly/microlibs/nonempty/NonEmptySet.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.nonempty 2 | 3 | import cats.Semigroup 4 | import japgolly.univeq.UnivEq 5 | import scala.annotation.nowarn 6 | import scala.collection.Factory 7 | 8 | /** 9 | * @param tail Does NOT contain head. 10 | */ 11 | final class NonEmptySet[A] private[nonempty] (val head: A, val tail: Set[A]) { 12 | private[this] implicit def univEq: UnivEq[A] = UnivEq.force 13 | 14 | override def toString = "NonEmpty" + whole.toString 15 | 16 | override def hashCode = head.## * 31 + tail.## 17 | 18 | override def equals(o: Any) = o match { 19 | case that: NonEmptySet[_] => this.whole == that.whole 20 | case _ => false 21 | } 22 | 23 | def size: Int = 24 | tail.size + 1 25 | 26 | def whole: Set[A] = 27 | tail + head 28 | 29 | def contains(a: A): Boolean = 30 | (head == a) || (tail contains a) 31 | 32 | def lacks(a: A): Boolean = 33 | !contains(a) 34 | 35 | def map[B: UnivEq](f: A => B): NonEmptySet[B] = 36 | NonEmptySet(f(head), tail map f) 37 | 38 | @nowarn("cat=unused") 39 | def flatMap[B: UnivEq](f: A => NonEmptySet[B]): NonEmptySet[B] = 40 | reduceMapLeft1(f)(_ ++ _) 41 | 42 | def foreach[U](f: A => U): Unit = { 43 | f(head) 44 | tail foreach f 45 | } 46 | 47 | def forall(f: A => Boolean): Boolean = 48 | f(head) && tail.forall(f) 49 | 50 | def exists(f: A => Boolean): Boolean = 51 | f(head) || tail.exists(f) 52 | 53 | def +(a: A): NonEmptySet[A] = 54 | if (contains(a)) 55 | this 56 | else 57 | new NonEmptySet(head, tail + a) 58 | 59 | def ++(as: IterableOnce[A]): NonEmptySet[A] = 60 | NonEmptySet(head, tail ++ as) 61 | 62 | def ++(as: NonEmptySet[A]): NonEmptySet[A] = 63 | ++(as.whole) 64 | 65 | def last: A = 66 | if (tail.isEmpty) head else tail.last 67 | 68 | def foldLeft[B](z: B)(f: (B, A) => B): B = 69 | tail.foldLeft(f(z, head))(f) 70 | 71 | def foldMapLeft1[B](g: A => B)(f: (B, A) => B): B = 72 | tail.foldLeft(g(head))(f) 73 | 74 | def reduceMapLeft1[B](f: A => B)(g: (B, B) => B): B = 75 | foldMapLeft1(f)((b, a) => g(b, f(a))) 76 | 77 | def reduce[B >: A](f: (B, B) => B): B = 78 | reduceMapLeft1[B](a => a)(f) 79 | 80 | def toVector = whole.toVector 81 | 82 | def toNEV: NonEmptyVector[A] = 83 | NonEmptyVector(head, tail.toVector) 84 | 85 | def mapV[B](f: A => B): NonEmptyVector[B] = { 86 | val b = implicitly[Factory[B, Vector[B]]].newBuilder 87 | tail.foreach(b += f(_)) 88 | NonEmptyVector(f(head), b.result()) 89 | } 90 | 91 | def iterator: Iterator[A] = 92 | whole.iterator 93 | 94 | def to[B](factory: Factory[A, B]): B = 95 | factory.fromSpecific(whole) 96 | } 97 | 98 | // ===================================================================================================================== 99 | 100 | object NonEmptySet { 101 | 102 | @nowarn("cat=unused") 103 | def one[A: UnivEq](h: A): NonEmptySet[A] = 104 | new NonEmptySet(h, Set.empty) 105 | 106 | def apply[A: UnivEq](h: A, t: A*): NonEmptySet[A] = 107 | apply(h, t.toSet) 108 | 109 | @nowarn("cat=unused") 110 | def apply[A: UnivEq](h: A, t: Set[A]): NonEmptySet[A] = 111 | new NonEmptySet(h, t - h) 112 | 113 | def maybe[A: UnivEq, B](s: Set[A], empty: => B)(f: NonEmptySet[A] => B): B = 114 | if (s.isEmpty) 115 | empty 116 | else 117 | f(force(s)) 118 | 119 | def option[A: UnivEq](s: Set[A]): Option[NonEmptySet[A]] = 120 | maybe[A, Option[NonEmptySet[A]]](s, None)(Some.apply) 121 | 122 | def force[A: UnivEq](s: Set[A]): NonEmptySet[A] = { 123 | val h = s.head 124 | // Until Scala 2.12, s-h is faster than .tail 125 | // apply() also performs s-h so we don't bother here 126 | apply(h, s) 127 | } 128 | 129 | def unwrapOption[A](o: Option[NonEmptySet[A]]): Set[A] = 130 | o.fold(Set.empty[A])(_.whole) 131 | 132 | @nowarn("cat=unused") 133 | implicit def univEq[A: UnivEq]: UnivEq[NonEmptySet[A]] = 134 | UnivEq.force 135 | 136 | implicit def semigroup[A]: Semigroup[NonEmptySet[A]] = 137 | new Semigroup[NonEmptySet[A]] { 138 | override def combine(a: NonEmptySet[A], b: NonEmptySet[A]) = a ++ b 139 | } 140 | 141 | object Sole { 142 | def unapply[A](v: NonEmptySet[A]) = new Unapply(v) 143 | final class Unapply[A](val v: NonEmptySet[A]) extends AnyVal { 144 | def isEmpty = v.tail.nonEmpty 145 | def get = v.head 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /multimap/shared/src/test/scala/japgolly/microlibs/multimap/MultimapTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.multimap 2 | 3 | // import cats.{Order, Eq} 4 | // import cats.instances.all._ 5 | // import org.scalacheck._ 6 | // import Multimap.Internal._ 7 | // import MultiValues.Commutative 8 | import utest._ 9 | 10 | object MultimapTest extends TestSuite { 11 | 12 | // // Here we are going to test Multimap 13 | // case class PropInputs[L[_] : MultiValues, A](mm: Multimap[A, L, A], a: A, b: A, as: L[A], 14 | // commutative: Option[Commutative[L]]) 15 | // (implicit A: Order[A], L: Eq[L[A]]) { 16 | 17 | // // This is a helper that facilitates easy creation, composition & logic 18 | // val E = EvalOver(this) 19 | 20 | // // Helper function for a quick L[A] of just the given A 21 | // def l(x: A): L[A] = el add1 x 22 | 23 | // // Let's cache a few repeated values that we will use throughout the test 24 | // val em = Multimap.empty[A, L, A] 25 | // val el = MultiValues[L].empty[A] 26 | // lazy val ab = l(a) add1 b 27 | // lazy val bigas = as add1 a add1 b 28 | // lazy val bigm = mm.addks(bigas, a).addvs(b, bigas) 29 | 30 | // // This will test that two operations (f and g) commute 31 | // type MM = Multimap[A, L, A] 32 | // def comm[R: Eq](name: => String, z: MM, r: MM => R, f: MM => MM, g: MM => MM) = 33 | // E.equal(name, r(f(g(z))), r(g(f(z)))) 34 | 35 | // // And now the propositions begin... 36 | // // First the propositions that are only tested when L is commutative 37 | // def commutativeProps = commutative.fold(E.pass)(c => { 38 | // implicit def cc: Commutative[L] = c 39 | // ( E.equal("reverse.reverse = id" , mm.reverse.reverse , mm) 40 | // ∧ E.equal("addvs symmetrical to addks", mm.addvs(a, as).reverse , mm.reverse.addks(as, a)) 41 | // ∧ E.equal("delvs symmetrical to delks", bigm.delvs(ab).reverse , bigm.reverse.delks(ab)) 42 | // ∧ E.equal("delv symmetrical to delk", bigm.delv(b).reverse , bigm.reverse.delk(b)) 43 | // ∧ E.equal("get⁻¹.setks.add = ks", em.add(b, a).setks(as, a).reverse(c)(a), as) 44 | // ) 45 | // }) 46 | 47 | // // Then all the other propositions. 48 | // def eval = 49 | // ( commutativeProps 50 | // ∧ E.equal("get.setvs.add = vs", em.add(a, b).setvs(a, as)(a), as) 51 | // ∧ E.equal("get.delk.add = ∅", em.add(a, b).delk(a)(a) , el) 52 | // ∧ E.equal("get.add.delk = v", em.delk(a).add(a, b)(a) , l(b)) 53 | // ∧ E.equal("get.delk.addvs = ∅", em.addvs(a, as).delk(a)(a) , el) 54 | // ∧ E.equal("get.addvs.delk = vs", em.delk(a).addvs(a, as)(a) , as) 55 | // ∧ E.equal("get₁.delk₂.addvs₁ = vs", em.addvs(a, as).delk(b)(a) , as) 56 | // ∧ E.equal("delk.delk = delks", bigm.delk(a).delk(b) , bigm.delks(ab)) 57 | // ∧ E.equal("delkv == delv.delk", bigm.delk(a).delv(a) , bigm.delkv(a)) 58 | // ∧ E.equal("unlink == del(kv).del(vk)", bigm.del(a, b).del(b, a) , bigm.unlink(a, b)) 59 | // ∧ comm("addvs.add ⊇⊆ add.addvs", em, _(a).set, _.addvs(a, as), _.add(a, b)) 60 | // ∧ comm("addvs₁₂ ⊇⊆ addvs₂₁", em, _(a).set, _.addvs(a, as), _.addvs(a, l(b))) 61 | // ) rename "Multimap" 62 | // } 63 | 64 | // implicit def commutativeO[L[_]](implicit c: Commutative[L] = null): Option[Commutative[L]] = Option(c) 65 | 66 | // def gen[L[_] : MultiValues, A: Order](ga: Gen[A], gl: Gen[A] => Gen[L[A]]) 67 | // (implicit c: Option[Commutative[L]], E: Eq[L[A]]): Gen[PropInputs[L, A]] = { 68 | // val gla = gl(ga) 69 | // for { 70 | // kvs <- Gen.tuple2(ga, gla).list 71 | // mm = Multimap(kvs.toMap) 72 | // a <- ga 73 | // b <- ga 74 | // as <- gla 75 | // } yield PropInputs[L, A](mm, a, b, as, c) 76 | // } 77 | 78 | // val genList : Gen[PropInputs[List, Int ]] = gen(Gen.int, _.list) 79 | // val genSet : Gen[PropInputs[Set, Int ]] = gen(Gen.int, _.set) 80 | // val genVector : Gen[PropInputs[Vector, Long]] = gen(Gen.long, _.vector) 81 | // val genArraySeq: Gen[PropInputs[ArraySeq, Long]] = gen(Gen.long, _.arraySeq) 82 | 83 | override def tests = Tests { 84 | // "list" - genList .mustSatisfyE(_.eval) 85 | // "set" - genSet .mustSatisfyE(_.eval) 86 | // "vector" - genVector .mustSatisfyE(_.eval) 87 | // "arraySeq" - genArraySeq.mustSatisfyE(_.eval) 88 | // TODO: Port MultimapTest to ScalaCheck 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /compile-time/shared/src/main/scala-3/japgolly/microlibs/compiletime/CompileTimeInfo.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.compiletime 2 | 3 | import scala.quoted.* 4 | import MacroEnv.* 5 | 6 | object CompileTimeInfo { 7 | 8 | // Null versions 9 | 10 | transparent inline def envVarOrNull(inline key: String): String = 11 | ${ quoted.envVarOrNull('key) } 12 | 13 | transparent inline def sysPropOrNull(inline key: String): String = 14 | ${ quoted.sysPropOrNull('key) } 15 | 16 | transparent inline def envVarOrSysPropOrNull(inline key: String): String = 17 | ${ quoted.envVarOrSysPropOrNull('key) } 18 | 19 | transparent inline def sysPropOrEnvVarOrNull(inline key: String): String = 20 | ${ quoted.sysPropOrEnvVarOrNull('key) } 21 | 22 | // Option versions 23 | 24 | transparent inline def envVar(inline key: String): Option[String] = 25 | ${ quoted.envVar('key) } 26 | 27 | transparent inline def sysProp(inline key: String): Option[String] = 28 | ${ quoted.sysProp('key) } 29 | 30 | transparent inline def envVarOrSysProp(inline key: String): Option[String] = 31 | ${ quoted.envVarOrSysProp('key) } 32 | 33 | transparent inline def sysPropOrEnvVar(inline key: String): Option[String] = 34 | ${ quoted.sysPropOrEnvVar('key) } 35 | 36 | // =================================================================================================================== 37 | 38 | object nonTransparent { 39 | 40 | // Null versions 41 | 42 | inline def envVarOrNull(inline key: String): String = 43 | ${ quoted.envVarOrNull('key) } 44 | 45 | inline def sysPropOrNull(inline key: String): String = 46 | ${ quoted.sysPropOrNull('key) } 47 | 48 | inline def envVarOrSysPropOrNull(inline key: String): String = 49 | ${ quoted.envVarOrSysPropOrNull('key) } 50 | 51 | inline def sysPropOrEnvVarOrNull(inline key: String): String = 52 | ${ quoted.sysPropOrEnvVarOrNull('key) } 53 | 54 | // Option versions 55 | 56 | inline def envVar(inline key: String): Option[String] = 57 | ${ quoted.envVar('key) } 58 | 59 | inline def sysProp(inline key: String): Option[String] = 60 | ${ quoted.sysProp('key) } 61 | 62 | inline def envVarOrSysProp(inline key: String): Option[String] = 63 | ${ quoted.envVarOrSysProp('key) } 64 | 65 | inline def sysPropOrEnvVar(inline key: String): Option[String] = 66 | ${ quoted.sysPropOrEnvVar('key) } 67 | } 68 | 69 | // =================================================================================================================== 70 | 71 | object quoted { 72 | 73 | private def getEnvVar(key: String): Option[String] = 74 | Option(System.getenv(key)) 75 | 76 | private def getSysProp(key: String): Option[String] = 77 | Option(System.getProperty(key, null)) 78 | 79 | // Null versions 80 | 81 | def envVarOrNull(key: Expr[String])(using Quotes): Expr[String] = { 82 | import quotes.reflect.* 83 | val k = key.valueOrError 84 | val v = getEnvVar(k) 85 | Expr(v.orNull) 86 | } 87 | 88 | def sysPropOrNull(key: Expr[String])(using Quotes): Expr[String] = { 89 | import quotes.reflect.* 90 | val k = key.valueOrError 91 | val v = getSysProp(k) 92 | Expr(v.orNull) 93 | } 94 | 95 | def envVarOrSysPropOrNull(key: Expr[String])(using Quotes): Expr[String] = { 96 | import quotes.reflect.* 97 | val k = key.valueOrError 98 | val v = getEnvVar(k) orElse getSysProp(k) 99 | Expr(v.orNull) 100 | } 101 | 102 | def sysPropOrEnvVarOrNull(key: Expr[String])(using Quotes): Expr[String] = { 103 | import quotes.reflect.* 104 | val k = key.valueOrError 105 | val v = getSysProp(k) orElse getEnvVar(k) 106 | Expr(v.orNull) 107 | } 108 | 109 | // Option versions 110 | 111 | def envVar(key: Expr[String])(using Quotes): Expr[Option[String]] = { 112 | import quotes.reflect.* 113 | val k = key.valueOrError 114 | val v = getEnvVar(k) 115 | Expr(v) 116 | } 117 | 118 | def sysProp(key: Expr[String])(using Quotes): Expr[Option[String]] = { 119 | import quotes.reflect.* 120 | val k = key.valueOrError 121 | val v = getSysProp(k) 122 | Expr(v) 123 | } 124 | 125 | def envVarOrSysProp(key: Expr[String])(using Quotes): Expr[Option[String]] = { 126 | import quotes.reflect.* 127 | val k = key.valueOrError 128 | val v = getEnvVar(k) orElse getSysProp(k) 129 | Expr(v) 130 | } 131 | 132 | def sysPropOrEnvVar(key: Expr[String])(using Quotes): Expr[Option[String]] = { 133 | import quotes.reflect.* 134 | val k = key.valueOrError 135 | val v = getSysProp(k) orElse getEnvVar(k) 136 | Expr(v) 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /stdlib-ext/shared/src/test/scala/japgolly/microlibs/stdlib_ext/StdlibExtTest.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.stdlib_ext 2 | 3 | import java.time.Duration 4 | import utest._ 5 | 6 | object StdlibExtTest extends TestSuite { 7 | import StdlibExt._ 8 | 9 | private def assertEq[A](a: A, e: A) = 10 | assert(a == e) 11 | 12 | override def tests = Tests { 13 | 14 | "indent(int)" - { 15 | assert("a".indentLines(2) == " a") 16 | assert("a\nb".indentLines(2) == " a\n b") 17 | assert("a\n b".indentLines(2) == " a\n b") 18 | } 19 | 20 | "unindent" - { 21 | 22 | "equal" - { 23 | assertEq( 24 | """ omfg 25 | | 26 | | good 27 | |""".stripMargin.unindent(2), 28 | """omfg 29 | | 30 | |good 31 | |""".stripMargin, 32 | ) 33 | } 34 | 35 | "oneUnder" - { 36 | assertEq( 37 | """ omfg 38 | | 39 | | good 40 | |""".stripMargin.unindent(2), 41 | """ omfg 42 | | 43 | |good 44 | |""".stripMargin, 45 | ) 46 | } 47 | 48 | "oneOver" - { 49 | assertEq( 50 | """ omfg 51 | | 52 | | good 53 | |""".stripMargin.unindent(2), 54 | """omfg 55 | | 56 | | good 57 | |""".stripMargin, 58 | ) 59 | } 60 | 61 | "allUnder" - { 62 | assertEq( 63 | """ omfg 64 | | 65 | | good 66 | |""".stripMargin.unindent(2), 67 | """omfg 68 | | 69 | |good 70 | |""".stripMargin, 71 | ) 72 | } 73 | 74 | "allOver" - { 75 | assertEq( 76 | """ omfg 77 | | 78 | | good 79 | |""".stripMargin.unindent(2), 80 | """ omfg 81 | | 82 | | good 83 | |""".stripMargin, 84 | ) 85 | } 86 | 87 | } 88 | 89 | "vectorInsertBefore" - { 90 | for { 91 | vs <- List(Vector(), Vector(1), Vector(1, 2, 3)) 92 | i <- -2 to vs.length + 2 93 | } { 94 | val r = vs.insertBefore(i, 666) 95 | if (i >= 0 && i <= vs.length) { 96 | assert(r.isDefined) 97 | val n = r.get 98 | assert(n(i) == 666) 99 | assert(n.filterNot(_ == 666) == vs) 100 | } else 101 | assert(r.isEmpty) 102 | } 103 | } 104 | 105 | "regex.collectAllIn" - { 106 | val r = "[0-9]".r 107 | def test(input: String)(expectedMatches: Int*): Unit = { 108 | val (init, last) = r.collectAllIn(input) 109 | val all = (init.flatMap(x => x._1 :: x._2.group(0) :: Nil) :+ last).mkString("") 110 | val matches = init.map(_._2.group(0).toInt) 111 | assert(all == input, matches == expectedMatches.toList) 112 | } 113 | 114 | "a" - test("a")() 115 | "1" - test("1")(1) 116 | "1 2" - test("1 2")(1, 2) 117 | "1 a 2" - test("1 a 2")(1, 2) 118 | "1 a 2 y" - test("1 a 2 y")(1, 2) 119 | "x 1 a 2" - test("x 1 a 2")(1, 2) 120 | "x 1 a 2 y" - test("x 1 a 2 y")(1, 2) 121 | "xx 11 aa 22 yy" - test("xx 11 aa 22 yy")(1, 1, 2, 2) 122 | } 123 | 124 | "duration" - { 125 | "toSeconds" - { 126 | "pos" - { 127 | val d = Duration.ofMillis(1100) plus Duration.ofNanos(9002003L) 128 | d.asSeconds ==> 1.109002003 129 | } 130 | "neg" - { 131 | val d = Duration.ofMillis(-1100) minus Duration.ofNanos(9002003L) 132 | d.asSeconds ==> -1.109002003 133 | } 134 | } 135 | 136 | "conciseDesc" - { 137 | def test(sec: Double, ns: Long, expect: String): Unit = { 138 | val pos = Duration.ofSeconds(sec.toLong).plus(Duration.ofNanos(ns)) 139 | pos.conciseDesc ==> expect 140 | 141 | val neg = pos.negated() 142 | neg.conciseDesc ==> s"-$expect" 143 | } 144 | 145 | "ns1" - test(0, 1, "1 ns") 146 | "ns2" - test(0, 11, "11 ns") 147 | "ns3" - test(0, 111, "111 ns") 148 | 149 | "us1" - test(0, 1001, "1 us") 150 | "us2" - test(0, 11001, "11 us") 151 | "us3" - test(0, 111001, "111 us") 152 | 153 | "ms1" - test(0, 1001001, "1 ms") 154 | "ms2" - test(0, 11001001, "11 ms") 155 | "ms3" - test(0, 111001001, "111 ms") 156 | 157 | "sec1" - test( 0, 1001001001, "1.0 sec") 158 | "sec2" - test( 0, 1901001001, "1.9 sec") 159 | "sec3" - test(59, 901001001, "59.9 sec") 160 | 161 | "min1" - test( 1.09 * 60, 3, "1.1 min") 162 | "min2" - test(12.94 * 60, 3, "12.9 min") 163 | "min3" - test(59.88 * 60, 3, "59.9 min") 164 | 165 | "hr1" - test(1.02 * 3600, 3, "1.02 hr") 166 | "hr2" - test(23 * 3600, 3, "23.00 hr") 167 | 168 | "day1" - test(36 * 3600, 3, "1.50 days") 169 | "day2" - test(48 * 3600, 3, "2.00 days") 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/StaticLookupFn.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.stdlib_ext.MutableArray 4 | import japgolly.univeq.UnivEq 5 | import scala.annotation.nowarn 6 | import scala.reflect.ClassTag 7 | 8 | /** Fast, efficient lookup functions for static data. 9 | * 10 | * Creation verifies key uniqueness and throws runtime exceptions on failure. 11 | */ 12 | object StaticLookupFn { 13 | 14 | def useArray[A >: Null : ClassTag](as: Iterable[(Int, A)]): ArrayDsl[A] = 15 | if (as.isEmpty) 16 | new ArrayDsl[A] with Dsl.EmptyBase[Int, A] { 17 | override def to[V >: Null : ClassTag](ok: A => V, ko: Int => V) = ko 18 | } 19 | else 20 | new ArrayDsl[A] { 21 | override def total = 22 | to(identity, keyFail) 23 | 24 | override def toOption = 25 | to(Some(_), _ => None) 26 | 27 | override def toEither[E](e: Int => E) = 28 | to(Right(_), i => Left(e(i))) 29 | 30 | override def to[V >: Null : ClassTag](ok: A => V, ko: Int => V): Int => V = { 31 | val array = mkArray(ok) 32 | i => if (i >= 0 && i < array.length) { 33 | val a = array(i) 34 | if (null != a) a else ko(i) 35 | } else 36 | ko(i) 37 | } 38 | 39 | private def mkArray[X >: Null : ClassTag](toX: A => X): Array[X] = { 40 | val len = as.iterator.map(_._1).max + 1 41 | val aa = Array.fill[A](len)(null) 42 | val ax = Array.fill[X](len)(null) 43 | for ((i, a) <- as) { 44 | assert(i >= 0, s"Indices can't be negative. Found: $i") 45 | val a2 = aa(i) 46 | if (null != a2) 47 | fail(s"Duplicates for index $i: $a and $a2") 48 | aa(i) = a 49 | ax(i) = toX(a) 50 | } 51 | ax 52 | } 53 | 54 | override protected def iterator() = as.iterator 55 | } 56 | 57 | def useArrayBy[A >: Null : ClassTag](as: Iterable[A])(key: A => Int): DslBase[Int, A] = 58 | useArray(as.map(a => (key(a), a))) 59 | 60 | trait ArrayDsl[A] extends DslBase[Int, A] { 61 | def to[V >: Null : ClassTag](ok: A => V, ko: Int => V): Int => V 62 | } 63 | 64 | // =================================================================================================================== 65 | 66 | @nowarn("cat=unused") 67 | def useMap[K: UnivEq, V](kvs: Iterable[(K, V)]): Dsl[K, V] = 68 | if (kvs.isEmpty) 69 | Dsl.empty 70 | else 71 | new Dsl[K, V] { 72 | override def toOption: K => Option[V] = 73 | if (kvs.size <= 4) 74 | super.toOption 75 | else 76 | mkMap(identity).get 77 | 78 | override def to[A](ok: V => A, ko: K => A): K => A = { 79 | val m = mkMap(ok) 80 | k => m.getOrElse(k, ko(k)) 81 | } 82 | 83 | private def mkMap[X](toX: V => X): Map[K, X] = { 84 | var mv = Map.empty[K, V] 85 | var mx = Map.empty[K, X] 86 | for ((k, v) <- kvs) 87 | mv.get(k) match { 88 | case None => mv = mv.updated(k, v); mx = mx.updated(k, toX(v)) 89 | case Some(v2) => fail(s"Duplicates for $k: ${v2} and $v") 90 | } 91 | mx 92 | } 93 | 94 | override protected def iterator() = kvs.iterator 95 | } 96 | 97 | def useMapBy[K: UnivEq, V](vs: Iterable[V])(k: V => K) = 98 | useMap(vs.map(v => k(v) -> v)) 99 | 100 | 101 | // =================================================================================================================== 102 | 103 | trait DslBase[@specialized(Int) K, V] { 104 | def total: K => V 105 | def toOption: K => Option[V] 106 | def toEither[E](e: K => E): K => Either[E, V] 107 | 108 | def toEitherWithHelp[H, E](h: V => String, sep: String = ",")(e: (K, String) => E): K => Either[E, V] = { 109 | val help = MutableArray(iterator().map(_._2).map(h)).sort.mkString(sep) 110 | toEither(e(_, help)) 111 | } 112 | 113 | protected def iterator(): Iterator[(K, V)] 114 | } 115 | 116 | trait Dsl[@specialized(Int) K, V] extends DslBase[K, V] { 117 | override def total: K => V = 118 | to(identity, keyFail) 119 | 120 | override def toOption: K => Option[V] = 121 | to(Some(_), _ => None) 122 | 123 | override def toEither[E](e: K => E): K => Either[E, V] = 124 | to(Right(_), k => Left(e(k))) 125 | 126 | def to[A](ok: V => A, ko: K => A): K => A 127 | } 128 | 129 | object Dsl { 130 | def empty[@specialized(Int) K, V]: Dsl[K, V] = 131 | new Dsl[K, V] with EmptyBase[K, V] { 132 | override def to[A](ok: V => A, ko: K => A) = ko 133 | } 134 | 135 | trait EmptyBase[@specialized(Int) K, V] extends DslBase[K, V] { 136 | override def total = keyFail 137 | override def toOption = _ => None 138 | override def toEither[E](e: K => E) = k => Left(e(k)) 139 | override protected def iterator() = Iterator.empty 140 | } 141 | } 142 | 143 | private def assert(t: Boolean, e: => String): Unit = 144 | if (!t) fail(e) 145 | 146 | private def fail(e: String): Nothing = 147 | throw new ExceptionInInitializerError(e) 148 | 149 | private val keyFail: Any => Nothing = 150 | k => throw new NoSuchElementException("key not found: " + k) 151 | } 152 | -------------------------------------------------------------------------------- /utils/shared/src/main/scala/japgolly/microlibs/utils/SafeBool.scala: -------------------------------------------------------------------------------- 1 | package japgolly.microlibs.utils 2 | 3 | import japgolly.microlibs.utils.SafeBool._ 4 | import japgolly.univeq.UnivEq 5 | import scala.annotation.nowarn 6 | import scala.collection.Factory 7 | 8 | /** Boolean isomorphism. 9 | * 10 | * Mix into the base type and override [[this.companion]] there. 11 | * 12 | * Examples: 13 | * 14 | * {{{ 15 | * sealed trait Enabled extends SafeBool[Enabled] { 16 | * override final def companion = Enabled 17 | * } 18 | * 19 | * case object Enabled extends Enabled with SafeBool.Object[Enabled] { 20 | * override def positive = Enabled 21 | * override def negative = Disabled 22 | * } 23 | * 24 | * case object Disabled extends Enabled 25 | * }}} 26 | * 27 | * {{{ 28 | * sealed abstract class Permission extends SafeBool.WithBoolOps[Permission] { 29 | * override final def companion = Permission 30 | * } 31 | * 32 | * case object Allow extends Permission 33 | * case object Deny extends Permission 34 | * 35 | * object Permission extends SafeBool.Object[Permission] { 36 | * override def positive = Allow 37 | * override def negative = Deny 38 | * } 39 | * }}} 40 | */ 41 | trait SafeBool[B <: SafeBool[B]] extends Product with Serializable { 42 | this: B => 43 | 44 | def companion: Object[B] 45 | 46 | final def unary_! : B = 47 | if (this == companion.positive) 48 | companion.negative 49 | else 50 | companion.positive 51 | 52 | @inline final def is(b: B): Boolean = 53 | b == this 54 | 55 | @inline final def when(cond: Boolean): B = 56 | if (cond) this else !this 57 | 58 | final def fnToThisWhen[A](f: A => Boolean): A => B = 59 | a => when(f(a)) 60 | 61 | final def whenAllAre(bs: B*): B = 62 | this when bs.forall(is) 63 | 64 | final def whenAnyAre(bs: B*): B = 65 | this when bs.exists(is) 66 | } 67 | 68 | object SafeBool { 69 | 70 | /** 71 | * Mix into the companion object for the type. 72 | */ 73 | trait Object[B <: SafeBool[B]] { 74 | implicit final def equality: UnivEq[B] = UnivEq.force 75 | 76 | def positive: B with SafeBool[B] 77 | def negative: B with SafeBool[B] 78 | 79 | final def memo[A](f: B => A): B => A = { 80 | val p = f(positive) 81 | val n = f(negative) 82 | b => if (b is positive) p else n 83 | } 84 | 85 | final def memoLazy[A](f: B => A): B => A = { 86 | lazy val p = f(positive) 87 | lazy val n = f(negative) 88 | b => if (b is positive) p else n 89 | } 90 | 91 | final def fold[A](a: A)(f: (A, B) => A): A = 92 | f(f(a, positive), negative) 93 | 94 | final def mapReduce[X, Y](m: B => X)(r: (X, X) => Y): Y = 95 | r(m(positive), m(negative)) 96 | 97 | final def forall(f: B => Boolean): Boolean = 98 | f(positive) && f(negative) 99 | 100 | final def exists(f: B => Boolean): Boolean = 101 | f(positive) || f(negative) 102 | 103 | final type Values[+A] = SafeBool.Values[B, A] 104 | 105 | object Values { 106 | def apply[A](f: B => A): Values[A] = 107 | SafeBool.Values(pos = f(positive), neg = f(negative)) 108 | 109 | def both[A](a: A): Values[A] = 110 | SafeBool.Values(a, a) 111 | 112 | def partition[C[_], A](as: IterableOnce[A])(f: A => B)(implicit factory: Factory[A, C[A]]): Values[C[A]] = { 113 | type Bldr = scala.collection.mutable.Builder[A, C[A]] 114 | val b = new SafeBool.Values[B, Bldr](factory.newBuilder, factory.newBuilder) 115 | for (a <- as.iterator) b(f(a)) += a 116 | b.map(_.result()) 117 | } 118 | } 119 | } 120 | 121 | /** 122 | * Adds boolean ops with `companion.positive` being the equivalent of `true`. 123 | */ 124 | trait WithBoolOps[B <: SafeBool[B]] extends SafeBool[B] { 125 | this: B => 126 | 127 | final def &(that: => B): B = { 128 | val pos = companion.positive 129 | pos when ((this is pos) && (that is pos)) 130 | } 131 | 132 | final def &&(that: => Boolean): B = { 133 | val pos = companion.positive 134 | pos when ((this is pos) && that) 135 | } 136 | 137 | final def |(that: => B): B = { 138 | val pos = companion.positive 139 | pos when ((this is pos) || (that is pos)) 140 | } 141 | 142 | final def ||(that: => Boolean): B = { 143 | val pos = companion.positive 144 | pos when ((this is pos) || that) 145 | } 146 | } 147 | 148 | // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 149 | 150 | final case class Values[B <: SafeBool[B], +A](pos: A, neg: A) { 151 | def apply(b: B): A = 152 | if (b is b.companion.positive) pos else neg 153 | 154 | def set[AA >: A](b: B, a: AA): Values[B, AA] = 155 | if (b is b.companion.positive) copy(pos = a) else copy(neg = a) 156 | 157 | def mod[AA >: A](b: B, f: A => AA): Values[B, AA] = 158 | if (b is b.companion.positive) copy(pos = f(pos)) else copy(neg = f(neg)) 159 | 160 | def map[C](f: A => C): Values[B, C] = 161 | Values(pos = f(pos), neg = f(neg)) 162 | 163 | def ap[C, D](other: Values[B, C])(f: (A, C) => D): Values[B, D] = 164 | Values(pos = f(pos, other.pos), neg = f(neg, other.neg)) 165 | 166 | def exists(f: A => Boolean): Boolean = 167 | f(pos) || f(neg) 168 | 169 | def forall(f: A => Boolean): Boolean = 170 | f(pos) && f(neg) 171 | } 172 | 173 | object Values { 174 | @nowarn("cat=unused") 175 | implicit def univEq[B <: SafeBool[B], A: UnivEq]: UnivEq[Values[B, A]] = 176 | UnivEq.derive 177 | } 178 | } 179 | --------------------------------------------------------------------------------