Skip to content

Commit

Permalink
Initial set of SExpr work (#1)
Browse files Browse the repository at this point in the history
* Working through s-expression things

* Mirror/sexpr scala2 (#1)

adding rest of files + scala2-ify
  • Loading branch information
DamianReeves authored Jan 11, 2022
1 parent c049eac commit 6396f69
Show file tree
Hide file tree
Showing 13 changed files with 1,224 additions and 8 deletions.
5 changes: 3 additions & 2 deletions project/BuildHelper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ object BuildHelper {
)
case Some((2, 13)) =>
Seq(
"-Xsource:3.0",
"-Ywarn-unused:params,-implicits"
) ++ std2xOptions ++ optimizerOptions(optimize)
case Some((2, 12)) =>
Expand All @@ -135,7 +136,7 @@ object BuildHelper {
"-Ywarn-nullary-unit",
"-Ywarn-unused:params,-implicits",
"-Xfuture",
"-Xsource:2.13",
"-Xsource:3.0",
"-Xmax-classfile-name",
"242"
) ++ std2xOptions ++ optimizerOptions(optimize)
Expand All @@ -150,7 +151,7 @@ object BuildHelper {
"-Xexperimental",
"-Ywarn-unused-import",
"-Xfuture",
"-Xsource:2.13",
"-Xsource:3.0",
"-Xmax-classfile-name",
"242"
) ++ std2xOptions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package zio.morphir.sexpr.ast

import zio.Chunk
import zio.morphir.sexpr.internal.*
import zio.morphir.sexpr.SExprEncoder

sealed trait SExpr { self =>
import SExpr.*
import SExprCase.*
def $case: SExprCase[SExpr]

def fold[Z](f: SExprCase[Z] => Z): Z = self.$case match {
case c @ BoolCase(_) => f(c)
case c @ StrCase(_) => f(c)
case c @ NumCase(_) => f(c)
case NilCase => f(NilCase)
case ConsCase(car, cdr) => f(ConsCase(car.fold(f), cdr.fold(f)))
case QuotedCase(value) => f(QuotedCase(value.fold(f)))
case VectorCase(items) => f(VectorCase(items.map(_.fold(f))))
}

final def widen: SExpr = this
}

object SExpr {
import SExprCase.*

implicit val encoder: SExprEncoder[SExpr] = SExprEncoder.fromFunction {
case (sexpr: Bool, indent, out) => ???
case _ => ???
}

def bool(value: Boolean): Bool = Bool(value)

def vector(items: SExpr*): SVector = SVector(Chunk(items: _*))

final case class Bool private[sexpr] ($case: BoolCase) extends SExpr
object Bool {
def apply(value: Boolean): Bool = Bool(BoolCase(value))
def unapply(arg: SExpr): Option[Boolean] = arg.$case match {
case BoolCase(value) => Some(value)
case _ => None
}
}

final case class Num private[ast] ($case: NumCase) extends SExpr
object Num {
def apply(value: java.math.BigDecimal): Num = Num(NumCase(value))
def unapply(exp: SExpr): Option[java.math.BigDecimal] = exp.$case match {
case NumCase(value) => Some(value)
case _ => None
}
}

final case class Str private[ast] ($case: StrCase) extends SExpr
object Str {
def apply(value: String): Str = Str(StrCase(value))
def unapply(exp: SExpr): Option[String] = exp.$case match {
case StrCase(value) => Some(value)
case _ => None
}
}

final case class SVector private[sexpr] ($case: VectorCase[SExpr]) extends SExpr
object SVector {
def apply(items: Chunk[SExpr]): SVector = SVector(VectorCase(items))
def unapply(arg: SExpr): Option[Chunk[SExpr]] = arg.$case match {
case VectorCase(items) => Some(items)
case _ => None
}
}
}

sealed trait SExprCase[+A] { self =>
import SExprCase.*
def map[B](f: A => B): SExprCase[B] = self match {
case BoolCase(value) => BoolCase(value)
case ConsCase(car, cdr) => ConsCase(f(car), f(cdr))
case StrCase(value) => StrCase(value)
case NilCase => NilCase
case NumCase(value) => NumCase(value)
case QuotedCase(value) => QuotedCase(f(value))
case VectorCase(items) => VectorCase(items.map(f))
}

}
object SExprCase {
sealed trait AtomCase[+A] extends SExprCase[A]
sealed trait CollectionCase[+A] extends SExprCase[A]
sealed trait ListCase[+A] extends CollectionCase[A]
sealed trait SymbolCase[+A] extends AtomCase[A]

// Leaf Cases
final case class BoolCase(value: Boolean) extends SymbolCase[Nothing]
final case class StrCase(value: String) extends AtomCase[Nothing]
final case class NumCase(value: java.math.BigDecimal) extends AtomCase[Nothing]
case object NilCase extends ListCase[Nothing]

// Recursive Cases
final case class ConsCase[+A](car: A, cdr: A) extends ListCase[A]
final case class QuotedCase[+A](value: A) extends SExprCase[A]
final case class VectorCase[+A](items: Chunk[A]) extends CollectionCase[A]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package zio.morphir.sexpr

import zio.morphir.sexpr.ast.SExpr
import zio.morphir.sexpr.internal._

import scala.util.control.NoStackTrace

trait SExprDecoder[A] {
self =>

final def decodeSExpr(str: CharSequence): Either[String, A] =
try Right(unsafeDecode(Nil, new FastStringReader(str)))
catch {
case SExprDecoder.UnsafeSExpr(trace) => Left(SExprError.render(trace))
case _: UnexpectedEnd =>
Left("Unexpected end of input")
case _: StackOverflowError =>
Left("Unexpected structure")
}

/**
* Returns a new decoder whose decoded values will be mapped by the specified function.
*/
def map[B](f: A => B): SExprDecoder[B] =
new SExprDecoder[B] {

def unsafeDecode(trace: List[SExprError], in: RetractReader): B =
f(self.unsafeDecode(trace, in))

override final def fromAST(sexpr: SExpr): Either[String, B] =
self.fromAST(sexpr).map(f)
}

/**
* Returns a new codec whose decoded values will be mapped by the specified function, which may
* itself decide to fail with some type of error.
*/
final def mapOrFail[B](f: A => Either[String, B]): SExprDecoder[B] = new SExprDecoder[B] {

def unsafeDecode(trace: List[SExprError], in: RetractReader): B =
f(self.unsafeDecode(trace, in)) match {
case Left(err) =>
throw SExprDecoder.UnsafeSExpr(SExprError.Message(err) :: trace)
case Right(b) => b
}

override final def fromAST(sexpr: SExpr): Either[String, B] =
self.fromAST(sexpr).flatMap(f)
}

/**
* Low-level, unsafe method to decode a value or throw an exception. This method should not be
* called in application code, although it can be implemented for user-defined data structures.
*/
def unsafeDecode(trace: List[SExprError], in: RetractReader): A

/**
* Returns this decoder but widened to the its given super-type
*/
final def widen[B >: A]: SExprDecoder[B] = self.asInstanceOf[SExprDecoder[B]]

/**
* Decode a value from an already parsed SExpr AST.
*
* The default implementation encodes the Json to a byte stream and uses decode to parse that.
* Override to provide a more performant implementation.
*/
def fromAST(sexpr: SExpr): Either[String, A] =
decodeSExpr(SExpr.encoder.encodeSExpr(sexpr, None))

}

object SExprDecoder {
type SExprError = zio.morphir.sexpr.SExprError
val SExprError = zio.morphir.sexpr.SExprError

def apply[A](implicit decoder: SExprDecoder[A]): SExprDecoder[A] = decoder

final case class UnsafeSExpr(trace: List[SExprError])
extends Exception("If you see this, a developer made a mistake using SExprDecoder")
with NoStackTrace

def peekChar[A](partialFunction: PartialFunction[Char, SExprDecoder[A]]): SExprDecoder[A] = new SExprDecoder[A] {
override def unsafeDecode(trace: List[SExprError], in: RetractReader): A = {
val c = in.nextNonWhitespace()
if (partialFunction.isDefinedAt(c)) {
in.retract()
partialFunction(c).unsafeDecode(trace, in)
} else {
throw UnsafeSExpr(SExprError.Message(s"missing case in `peekChar` for '${c}''") :: trace)
}
}
}

implicit val string: SExprDecoder[String] = new SExprDecoder[String] {

def unsafeDecode(trace: List[SExprError], in: RetractReader): String =
Lexer.string(trace, in).toString

override final def fromAST(sexpr: SExpr): Either[String, String] =
sexpr match {
case SExpr.Str(value) => Right(value)
case _ => Left("Not a string value")
}
}

implicit val boolean: SExprDecoder[Boolean] = new SExprDecoder[Boolean] {

def unsafeDecode(trace: List[SExprError], in: RetractReader): Boolean =
Lexer.boolean(trace, in)

override final def fromAST(sexpr: SExpr): Either[String, Boolean] =
sexpr match {
case SExpr.Bool(value) => Right(value)
case _ => Left("Not a bool value")
}
}

implicit val char: SExprDecoder[Char] = string.mapOrFail {
case str if str.length == 1 => Right(str(0))
case _ => Left("expected one character")
}
}
Loading

0 comments on commit 6396f69

Please sign in to comment.