-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Working through s-expression things * Mirror/sexpr scala2 (#1) adding rest of files + scala2-ify
- Loading branch information
1 parent
c049eac
commit 6396f69
Showing
13 changed files
with
1,224 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
zio-morphir-sexpr/shared/src/main/scala/zio/morphir/sexpr/ast/ast.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
|
||
} |
123 changes: 123 additions & 0 deletions
123
zio-morphir-sexpr/shared/src/main/scala/zio/morphir/sexpr/decoder.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
Oops, something went wrong.