literally
                                
                                 literally copied to clipboard
                                
                                    literally copied to clipboard
                            
                            
                            
                        Compile time validation of literal values built from strings
Literally
Compile time validation of literal values built from strings.
Rationale
Consider a type like Port:
case class Port private (value: Int)
object Port {
  val MinValue = 0
  val MaxValue = 65535
  def fromInt(i: Int): Option[Port] =
    if (i < MinValue || i > MaxValue) None else Some(new Port(i))
}
This library simplifies the definition of literal values which are validated at compilation time:
val p: Port = port"8080"
// p: Port = Port(8080)
val q: Port = port"100000"
// <console>:17: error: invalid port - must be integer between 0 and 65535
Validation is performed at compile time. This library provides the macro implementations for both Scala 2 and Scala 3 which powers custom string literals.
Quick Start
To get started with literally in an SBT project add the following dependency to your build.sbt:
libraryDependencies += "org.typelevel" %% "literally" % "<version>"
where <version> is the most recent version of literally.
Usage
Defining a custom string literal for a type A involves:
- Implementing an org.typelevel.literally.Literally[A]instance
- Defining an extension method on a StringContextwhich uses the definedLiterally[A]instance
import org.typelevel.literally.Literally
object literals:      
  extension (inline ctx: StringContext)
    inline def port(inline args: Any*): Port =
      ${PortLiteral('ctx, 'args)}
  object PortLiteral extends Literally[Port]:
    def validate(s: String)(using Quotes) =
      s.toIntOption.flatMap(Port.fromInt) match
        case None => Left(s"invalid port - must be integer between ${Port.MinValue} and ${Port.MaxValue}")
        case Some(_) => Right('{Port.fromInt(${Expr(s)}.toInt).get})
The same pattern is used for Scala 2, though the syntax for extension methods and macros are a bit different:
import scala.util.Try
import org.typelevel.literally.Literally
object literals {
  implicit class short(val sc: StringContext) extends AnyVal {
    def port(args: Any*): Port = macro PortLiteral.make
  }
  object PortLiteral extends Literally[Port] {
    def validate(c: Context)(s: String): Either[String, c.Expr[Port]] = {
      import c.universe.{Try => _, _}
      Try(s.toInt).toOption.flatMap(Port.fromInt) match {
        case None => Left(s"invalid port - must be integer between ${Port.MinValue} and ${Port.MaxValue}")
        case Some(_) => Right(c.Expr(q"Port.fromInt($s.toInt).get"))
      }
    }
    def make(c: Context)(args: c.Expr[Any]*): c.Expr[Port] = apply(c)(args: _*)
  }
}
The tests directory in this project has more examples.