Skip to content

Commit

Permalink
Maps and more work
Browse files Browse the repository at this point in the history
  • Loading branch information
Greg Zoller committed Oct 27, 2023
1 parent 242eafb commit 3dfcea0
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 28 deletions.
42 changes: 39 additions & 3 deletions src/main/scala/co.blocke.scalajack/json/JsonParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ case class JsonParser(js: String, cache: Map[TypedName, (JsonConfig, JsonParser)
@inline def eatWhitespace: Either[ParseError, Unit] =
while i < max && jsChars(i).isWhitespace do i += 1
Right(())
@inline def expectQuote: Either[ParseError, Unit] =
if jsChars(i) == '\"' then
i += 1
Right(())
else Left(ParseError(s"Quote expected at position [$i]"))
@inline def expectComma: Either[ParseError, Unit] = // Note: this consumes whitespace before/after the ','
for {
_ <- eatWhitespace
Expand Down Expand Up @@ -130,7 +135,7 @@ case class JsonParser(js: String, cache: Map[TypedName, (JsonConfig, JsonParser)
_ = acc.addOne(el)
_ <- expectComma
} yield el) match
case Left(_) if jsChars(i) == ']' =>
case Left(CommaExpected(_)) if jsChars(i) == ']' =>
i += 1
eatWhitespace
done = Some(Right(acc.toList))
Expand All @@ -139,13 +144,42 @@ case class JsonParser(js: String, cache: Map[TypedName, (JsonConfig, JsonParser)
case Right(_) =>
done.get

def expectObject[K, V](
cfg: JsonConfig,
keyElement: (JsonConfig, JsonParser) => Either[ParseError, K],
valueElement: (JsonConfig, JsonParser) => Either[ParseError, V]
): Either[ParseError, Map[K, V]] =
if jsChars(i) != '{' then Left(JsonParseError(s"Beginning of object expected at position [$i]"))
else
i += 1
eatWhitespace
val acc = scala.collection.mutable.Map.empty[K, V]
var done: Option[Either[ParseError, Map[K, V]]] = None
while done.isEmpty do
(for {
keyLabel <- keyElement(cfg, this)
_ <- expectColon
mapVal <- valueElement(cfg, this)
_ = acc.put(keyLabel, mapVal)
_ <- expectComma
} yield (keyLabel, mapVal)) match
case Left(CommaExpected(_)) if jsChars(i) == '}' =>
i += 1
eatWhitespace
done = Some(Right(acc.toMap))
case Left(e) =>
done = Some(Left(e))
case Right(_) =>
done.get

// Special case of JSON object where each entry is a field of a class
def expectClass[T](
cfg: JsonConfig,
fieldMap: Map[String, (JsonConfig, JsonParser) => Either[ParseError, ?]],
instantiator: Map[String, ?] => T,
fieldValues: scala.collection.mutable.HashMap[String, Any] // pre-set values (Option:None, default values)
): Either[ParseError, T] =
if jsChars(i) != '{' then Left(JsonParseError(s"Beginning of object expected at position [$i]"))
if jsChars(i) != '{' then Left(JsonParseError(s"Beginning of class object expected at position [$i]"))
else
i += 1
eatWhitespace
Expand All @@ -161,7 +195,9 @@ case class JsonParser(js: String, cache: Map[TypedName, (JsonConfig, JsonParser)
case Left(CommaExpected(_)) if jsChars(i) == '}' =>
i += 1
eatWhitespace
done = Some(Right(instantiator(fieldValues.toMap))) // instantiate the class here!!!
done = Try(instantiator(fieldValues.toMap)) match // instantiate the class here!!!
case Success(v) => Some(Right(v))
case Failure(e) => Some(Left(ParseError(s"Unable to instantiate class at position [${i - 1}] with message ${e.getMessage}")))
case Left(e) =>
done = Some(Left(e))
case Right(_) =>
Expand Down
112 changes: 97 additions & 15 deletions src/main/scala/co.blocke.scalajack/json/JsonReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,31 @@ object JsonReader:
}
}

def refFn[T](ref: RTypeRef[T])(using q: Quotes, tt: Type[T])(using cache: HashMap[Expr[TypedName], Expr[(JsonConfig, JsonParser) => Either[ParseError, ?]]]): Expr[(JsonConfig, JsonParser) => Either[ParseError, T]] =
def refFn[T](ref: RTypeRef[T], isMapKey: Boolean = false)(using q: Quotes, tt: Type[T])(using cache: HashMap[Expr[TypedName], Expr[(JsonConfig, JsonParser) => Either[ParseError, ?]]]): Expr[(JsonConfig, JsonParser) => Either[ParseError, T]] =
import Clazzes.*
import quotes.reflect.*

ref match
case t: PrimitiveRef[?] if t.name == BOOLEAN_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectBoolean(j, p).map(_.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectBoolean(j, p).map(_.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectBoolean(j, p).map(_.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == BYTE_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toByte.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectLong(j, p).map(_.toByte.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toByte.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == CHAR_CLASS =>
'{ (j: JsonConfig, p: JsonParser) =>
p.expectString(j, p)
Expand All @@ -51,19 +67,60 @@ object JsonReader:
)
}
case t: PrimitiveRef[?] if t.name == DOUBLE_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectDouble(j, p).map(_.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectDouble(j, p).map(_.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectDouble(j, p).map(_.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == FLOAT_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectDouble(j, p).map(_.toFloat.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectDouble(j, p).map(_.toFloat.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectDouble(j, p).map(_.toFloat.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == INT_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toInt.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectLong(j, p).map(_.toInt.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toInt.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == LONG_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectLong(j, p).map(_.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.asInstanceOf[T]) }
case t: PrimitiveRef[?] if t.name == SHORT_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toShort.asInstanceOf[T]) }
if isMapKey then
'{ (j: JsonConfig, p: JsonParser) =>
(for {
_ <- p.expectQuote
v <- p.expectLong(j, p).map(_.toShort.asInstanceOf[T])
_ <- p.expectQuote
} yield v)
}
else '{ (j: JsonConfig, p: JsonParser) => p.expectLong(j, p).map(_.toShort.asInstanceOf[T]) }
case t: PrimitiveRef[T] if t.name == STRING_CLASS =>
'{ (j: JsonConfig, p: JsonParser) => p.expectString(j, p).map(_.asInstanceOf[T]) }

case t: SeqRef[T] =>
if isMapKey then throw new JsonError("Seq types cannot be map keys.")
t.refType match
case '[s] =>
t.elementRef.refType match
Expand All @@ -72,7 +129,23 @@ object JsonReader:
'{ (j: JsonConfig, p: JsonParser) =>
p.expectList[e](j, $subFn).map(_.to(${ Expr.summon[Factory[e, T]].get })) // Convert List to whatever the target type should be
}
case t: MapRef[T] =>
if isMapKey then throw new JsonError("Map types cannot be map keys.")
t.refType match
case '[m] =>
t.elementRef.refType match
case '[k] =>
t.elementRef2.refType match
case '[v] =>
val keyFn = refFn[k](t.elementRef.asInstanceOf[RTypeRef[k]], true).asInstanceOf[Expr[(JsonConfig, JsonParser) => Either[ParseError, k]]]
val valFn = refFn[v](t.elementRef2.asInstanceOf[RTypeRef[v]]).asInstanceOf[Expr[(JsonConfig, JsonParser) => Either[ParseError, v]]]
'{ (j: JsonConfig, p: JsonParser) =>
val z = p.expectObject[k, v](j, $keyFn, $valFn).map(_.to(${ Expr.summon[Factory[(k, v), T]].get })) // Convert List to whatever the target type should be
println(s"Z ${p.getPos}: " + z)
z
}
case t: ArrayRef[T] =>
if isMapKey then throw new JsonError("Arrays cannot be map keys.")
t.refType match
case '[s] =>
t.elementRef.refType match
Expand All @@ -83,6 +156,7 @@ object JsonReader:
}

case t: ScalaOptionRef[T] =>
if isMapKey then throw new JsonError("Options cannot be map keys.")
t.refType match
case '[s] =>
t.optionParamType.refType match
Expand All @@ -103,23 +177,31 @@ object JsonReader:
t.refType match
case '[s] =>
val rtypeExpr = t.expr
val wrappedMapKey = Expr(isMapKey)
'{ (j: JsonConfig, p: JsonParser) =>
val rtype = $rtypeExpr.asInstanceOf[ScalaEnumRType[T]]
j.enumsAsIds match
case '*' =>
p.expectLong(j, p).flatMap { v =>
def readNumericEnum =
if $wrappedMapKey then
(for {
_ <- p.expectQuote
enumVal <- p.expectLong(j, p)
_ <- p.expectQuote
} yield enumVal).flatMap { v =>
val fromOrdinalMethod = Class.forName(rtype.name).getMethod("fromOrdinal", classOf[Int])
scala.util.Try(fromOrdinalMethod.invoke(null, v.toInt).asInstanceOf[T]) match
case Success(v2) => Right(v2)
case Failure(e) => Left(JsonParseError(s"No enum value in ${rtype.name} for ordinal value '$v'"))
}
case enumList: List[String] if enumList.contains(rtype.name) =>
else
p.expectLong(j, p).flatMap { v =>
val fromOrdinalMethod = Class.forName(rtype.name).getMethod("fromOrdinal", classOf[Int])
scala.util.Try(fromOrdinalMethod.invoke(null, v.toInt).asInstanceOf[T]) match
case Success(v2) => Right(v2)
case Failure(e) => Left(JsonParseError(s"No enum value in ${rtype.name} for ordinal value '$v'"))
}
j.enumsAsIds match
case '*' => readNumericEnum
case enumList: List[String] if enumList.contains(rtype.name) => readNumericEnum
case _ =>
p.expectString(j, p).flatMap { v =>
val valueOfMethod = Class.forName(rtype.name).getMethod("valueOf", classOf[String])
Expand All @@ -130,6 +212,7 @@ object JsonReader:
}

case t: ScalaClassRef[T] =>
if isMapKey then throw new JsonError("Class types cannot be map keys.")
t.refType match
case '[s] =>
// IDEA: Somewhere at this level (globally tho) create a seenBefore cache. Pass this cache to classParseMap() and don't
Expand Down Expand Up @@ -167,10 +250,11 @@ object JsonReader:
case '[s] =>
t.unwrappedType.refType match
case '[e] =>
val subFn = refFn[e](t.unwrappedType.asInstanceOf[RTypeRef[e]]).asInstanceOf[Expr[(JsonConfig, JsonParser) => Either[ParseError, e]]]
val subFn = refFn[e](t.unwrappedType.asInstanceOf[RTypeRef[e]], isMapKey).asInstanceOf[Expr[(JsonConfig, JsonParser) => Either[ParseError, e]]]
'{ (j: JsonConfig, p: JsonParser) => $subFn(j, p).asInstanceOf[Either[co.blocke.scalajack.json.ParseError, T]] }

case t: SelfRefRef[T] =>
if isMapKey then throw new JsonError("Class or trait types cannot be map keys.")
t.refType match
case '[s] =>
val className = Expr(t.typedName.toString)
Expand All @@ -188,10 +272,8 @@ object JsonReader:
// * Java Collections
// * Java Enums
// * Non-case Scala classes
// * Map
// * Scala2Ref
// * SealedTraitRef
// * SelfRefRef
// * TraitRef
// * TryRef
// * TupleRef
Expand Down
Loading

0 comments on commit 3dfcea0

Please sign in to comment.