├── .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 | [](https://travis-ci.org/japgolly/microlibs-scala)
3 | [](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_(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 |
--------------------------------------------------------------------------------