Getting Started

Prerequisites

  • Scala 3.3 or later
  • sbt 1.9 or later

Adding Leonardo to your project

Leonardo is published to Maven Central for Scala 3.3 LTS, and ships as two artifacts. The library is what you almost always want:

libraryDependencies += "it.grypho" %% "leonardo" % "0.0.0+1-f7ef5ec1-SNAPSHOT"

The interactive REPL is a separate artifact, because it is the only part that needs JLine — a terminal library a library consumer would never call. Add it only if you want to embed or extend the REPL:

libraryDependencies += "it.grypho" %% "leonardo-repl" % "0.0.0+1-f7ef5ec1-SNAPSHOT"

leonardo-repl depends on leonardo, so the second line replaces the first rather than supplementing it.

Import conventions

Leonardo is split across focused packages. Import what you need:

import it.grypho.scala.leonardo.core.*      // _Expression, _Value, _Number, _Variable, Environment
import it.grypho.scala.leonardo.scalar.*    // Sum, Product, Power, Sin, derive, simplify, …
import it.grypho.scala.leonardo.matrix.*    // _Matrix, MatSum, MatProduct, Transpose, Determinant, Inverse
import it.grypho.scala.leonardo.equation.*  // _Equation, solve, solveSystem
import it.grypho.scala.leonardo.parser.Parser
import it.grypho.scala.leonardo.cli.Session   // leonardo-repl artifact, not leonardo

Parsing

The Parser converts a string into an expression tree:

Parser.parse("sin(x)^2 + cos(x)^2").get.toString
// res0: String = "((sin(x) ^ 2.0) + (cos(x) ^ 2.0))"

The result is immutable; the same tree object can be evaluated many times with different environments.

Parsing failures return a Failure result rather than throwing:

val bad = Parser.parse("sin(")
// bad: ParseResult[_Expression] = Failure(
//   msg = "'(' expected but end of source found",
//   next = CharSequenceReader()
// )
bad.successful
// res1: Boolean = false

Evaluating

An expression evaluates to either a concrete _Value (wrapped in Right) or a symbolic remainder (wrapped in Left):

val x    = _Variable("x")
val expr = Sum(Sin(x), Cos(x))

Without any binding, the expression stays symbolic:

expr.eval(new Environment())
// res2: Either[_Expression, _Value] = Left(
//   value = Sum(
//     a = Sin(e = _Variable(variable = "x")),
//     b = Cos(e = _Variable(variable = "x"))
//   )
// )

Bind x and the result folds to a number:

val env = new Environment(5, Map("x" -> _Number(0.0)))
// env: Environment = it.grypho.scala.leonardo.core.Environment@1ef1be15
expr.eval(env)
// res3: Either[_Expression, _Value] = Right(value = _Number(d = 1.0))

Precision

Environment carries a decimal precision used for display and rounding. The default is 5; override it per call:

val highPrecision = new Environment(10, Map("x" -> _Number(math.Pi / 4)))
// highPrecision: Environment = it.grypho.scala.leonardo.core.Environment@6bd936d4
expr.eval(highPrecision)
// res4: Either[_Expression, _Value] = Right(
//   value = _Number(d = 1.414213562373095)
// )

Using the REPL

Launch with sbt repl (or sbt "runMain it.grypho.scala.leonardo.cli.repl"). Programmatically, Session.execute is the pure testable core:

val s = Session()
s.execute("x := 1.5708")   // π/2 ≈ 1.5708
s.execute("sin(x) + cos(x)")
// res6: String = "1.0"

This site uses Just the Docs, a documentation theme for Jekyll.