Skip to content

Commit

Permalink
catch a bunch of bad casts and incomplete matches, and put in proper …
Browse files Browse the repository at this point in the history
…error messages
  • Loading branch information
lihaoyi committed Oct 18, 2018
1 parent 167206a commit 07c2a2a
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 27 deletions.
24 changes: 16 additions & 8 deletions sjsonnet/src/sjsonnet/Evaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr


case BinaryOp(_, lhs, Expr.BinaryOp.`in`, Super(offset)) =>
val key = visitExpr(lhs, scope).asInstanceOf[Val.Str]
val key = visitExpr(lhs, scope).cast[Val.Str]
Val.bool(scope.super0.get.value0.contains(key.value))

case $(offset) => scope.dollar
Expand Down Expand Up @@ -85,7 +85,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
case None => Evaluator.fail("Assertion failed", scope.currentFile, offset, wd)
case Some(msg) =>
Evaluator.fail(
"Assertion failed: " + visitExpr(msg, scope).asInstanceOf[Val.Str].value,
"Assertion failed: " + visitExpr(msg, scope).cast[Val.Str].value,
scope.currentFile,
offset,
wd
Expand Down Expand Up @@ -114,7 +114,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
)
case Apply(offset, value, Args(args)) =>
val lhs = visitExpr(value, scope)
try lhs.asInstanceOf[Val.Func].apply(
try lhs.cast[Val.Func].apply(
args.map{case (k, v) => (k, Lazy(visitExpr(v, scope)))},
scope.currentFile.last,
extVars,
Expand Down Expand Up @@ -143,7 +143,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr

case Lookup(offset, value, index) =>
if (value.isInstanceOf[Super]){
val key = visitExpr(index, scope).asInstanceOf[Val.Str]
val key = visitExpr(index, scope).cast[Val.Str]
scope.super0.get.value(key.value, scope.currentFile, scope.currentRoot, offset, wd, extVars).force
}else (visitExpr(value, scope), visitExpr(index, scope)) match {
case (v: Val.Arr, i: Val.Num) =>
Expand All @@ -165,22 +165,30 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
visitExpr(value, scope) match{
case Val.Arr(a) =>

val range = start.fold(0)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) until end.fold(a.length)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) by stride.fold(1)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt)
val range =
start.fold(0)(visitExpr(_, scope).cast[Val.Num].value.toInt) until
end.fold(a.length)(visitExpr(_, scope).cast[Val.Num].value.toInt) by
stride.fold(1)(visitExpr(_, scope).cast[Val.Num].value.toInt)
Val.Arr(range.dropWhile(_ < 0).takeWhile(_ < a.length).map(a))
case Val.Str(s) =>
val range = start.fold(0)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) until end.fold(s.length)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt) by stride.fold(1)(visitExpr(_, scope).asInstanceOf[Val.Num].value.toInt)
val range =
start.fold(0)(visitExpr(_, scope).cast[Val.Num].value.toInt) until
end.fold(s.length)(visitExpr(_, scope).cast[Val.Num].value.toInt) by
stride.fold(1)(visitExpr(_, scope).cast[Val.Num].value.toInt)
Val.Str(range.dropWhile(_ < 0).takeWhile(_ < s.length).map(s).mkString)
case x => Evaluator.fail("Can only slice array or string, not " + x.prettyName, scope.currentFile, offset, wd)
}
case Function(offset, params, body) => visitMethod(scope, body, params, offset)
case IfElse(offset, cond, then, else0) =>
visitExpr(cond, scope) match{
case Val.True => visitExpr(then, scope)
case Val.False => else0.fold[Val](Val.Null)(visitExpr(_, scope))
case v => Evaluator.fail("Need boolean, found " + v.prettyName, scope.currentFile, offset, wd)
}
case Comp(offset, value, first, rest) =>
Val.Arr(visitComp(first :: rest.toList, Seq(scope)).map(s => Lazy(visitExpr(value, s))))
case ObjExtend(offset, value, ext) => {
val original = visitExpr(value, scope).asInstanceOf[Val.Obj]
val original = visitExpr(value, scope).cast[Val.Obj]
val extension = visitObjBody(ext, scope)
Evaluator.mergeObjects(original, extension)
}
Expand Down Expand Up @@ -349,7 +357,7 @@ class Evaluator(parseCache: collection.mutable.Map[String, fastparse.Parsed[Expr
case None => Evaluator.fail("Assertion failed", scope.currentFile, value.offset, wd)
case Some(msg) =>
Evaluator.fail(
"Assertion failed: " + visitExpr(msg, newScope).asInstanceOf[Val.Str].value,
"Assertion failed: " + visitExpr(msg, newScope).cast[Val.Str].value,
scope.currentFile,
value.offset,
wd
Expand Down
2 changes: 1 addition & 1 deletion sjsonnet/src/sjsonnet/Format.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ object Format{
case _ =>

val value = formatted.label match{
case None => Materializer(values.asInstanceOf[Val.Arr].value(i).force, extVars, wd)
case None => Materializer(values.cast[Val.Arr].value(i).force, extVars, wd)
case Some(key) =>
values match{
case v: Val.Arr => Materializer(v.value(i).force, extVars, wd)
Expand Down
70 changes: 54 additions & 16 deletions sjsonnet/src/sjsonnet/Std.scala
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,15 @@ object Std {
}
},
builtin("codepoint", "str"){ (wd, extVars, v1: Val) =>
v1.asInstanceOf[Val.Str].value.charAt(0).toInt
v1.cast[Val.Str].value.charAt(0).toInt
},
builtin("length", "x"){ (wd, extVars, v1: Val) =>
v1 match{
case Val.Str(s) => s.length
case Val.Arr(s) => s.length
case o: Val.Obj => o.getVisibleKeys().count(!_._2)
case o: Val.Func => o.params.args.length
case _ => throw new DelegateError("Cannot get length of " + v1.prettyName)
}
},
builtin("objectHas", "o", "f"){ (wd, extVars, v1: Val.Obj, v2: String) =>
Expand All @@ -147,8 +148,7 @@ object Std {
},
builtin("objectFields", "o"){ (wd, extVars, v1: Val.Obj) =>
Val.Arr(
v1.asInstanceOf[Val.Obj]
.getVisibleKeys()
v1.getVisibleKeys()
.collect{case (k, false) => k}
.toSeq
.sorted
Expand All @@ -157,8 +157,7 @@ object Std {
},
builtin("objectFieldsAll", "o"){ (wd, extVars, v1: Val.Obj) =>
Val.Arr(
v1.asInstanceOf[Val.Obj]
.getVisibleKeys()
v1.getVisibleKeys()
.collect{case (k, _) => k}
.toSeq
.sorted
Expand All @@ -177,10 +176,17 @@ object Std {
}
},
builtin("lines", "arr"){ (wd, extVars, v1: Val.Arr) =>
v1.value.map(_.force).foreach{
case _: Val.Str | Val.Null => // donothing
case x => throw new DelegateError("Cannot call .lines on " + x.prettyName)
}
Materializer.apply(v1, extVars, wd).asInstanceOf[ujson.Js.Arr]
.value
.filter(_ != ujson.Js.Null)
.map{case ujson.Js.Str(s) => s + "\n"}
.map{
case ujson.Js.Str(s) => s + "\n"
case _ => ??? /* we ensure it's all strings above */
}
.mkString
},
builtin("format", "str", "vals"){ (wd, extVars, v1: String, v2: Val) =>
Expand Down Expand Up @@ -376,7 +382,16 @@ object Std {
builtin("join", "sep", "arr"){ (wd, extVars, sep: Val, arr: Val.Arr) =>
val res: Val = sep match{
case Val.Str(s) =>
Val.Str(arr.value.map(_.force).filter(_ != Val.Null).map{case Val.Str(x) => x}.mkString(s))
Val.Str(
arr.value
.map(_.force)
.filter(_ != Val.Null)
.map{
case Val.Str(x) => x
case x => throw new DelegateError("Cannot join " + x.prettyName)
}
.mkString(s)
)
case Val.Arr(sep) =>
val out = collection.mutable.Buffer.empty[Lazy]
for(x <- arr.value){
Expand All @@ -385,9 +400,11 @@ object Std {
case Val.Arr(v) =>
if (out.nonEmpty) out.appendAll(sep)
out.appendAll(v)
case x => throw new DelegateError("Cannot join " + x.prettyName)
}
}
Val.Arr(out)
case x => throw new DelegateError("Cannot join " + x.prettyName)
}
res
},
Expand All @@ -397,6 +414,7 @@ object Std {
x.force match{
case Val.Null => // do nothing
case Val.Arr(v) => out.appendAll(v)
case x => throw new DelegateError("Cannot call flattenArrays on " + x)
}
}
Val.Arr(out)
Expand All @@ -406,7 +424,12 @@ object Std {
def sect(x: ujson.Js.Obj) = {
x.value.flatMap{
case (k, ujson.Js.Str(v)) => Seq(k + " = " + v)
case (k, ujson.Js.Arr(vs)) => vs.map{case ujson.Js.Str(v) => k + " = " + v}
case (k, ujson.Js.Arr(vs)) =>
vs.map{
case ujson.Js.Str(v) => k + " = " + v
case x => throw new DelegateError("Cannot call manifestIni on " + x.getClass)
}
case (k, x) => throw new DelegateError("Cannot call manifestIni on " + x.getClass)
}
}
val lines = materialized.obj.get("main").fold(Iterable[String]())(x => sect(x.asInstanceOf[ujson.Js.Obj])) ++
Expand Down Expand Up @@ -473,11 +496,16 @@ object Std {
case ujson.Js.Str(s) => s
case ujson.Js.Arr(Seq(ujson.Js.Str(t), attrs: ujson.Js.Obj, children@_*)) =>
tag(t)(
attrs.value.map { case (k, ujson.Js.Str(v)) => attr(k) := v }.toSeq,
attrs.value.map {
case (k, ujson.Js.Str(v)) => attr(k) := v
case (k, v) => throw new DelegateError("Cannot call manifestXmlJsonml on " + v.getClass)
}.toSeq,
children.map(rec)
)
case ujson.Js.Arr(Seq(ujson.Js.Str(t), children@_*)) =>
tag(t)(children.map(rec))
case x =>
throw new DelegateError("Cannot call manifestXmlJsonml on " + x.getClass)
}
}

Expand All @@ -487,7 +515,8 @@ object Std {
builtin("base64", "v"){ (wd, extVars, v: Val) =>
v match{
case Val.Str(value) => Base64.getEncoder().encodeToString(value.getBytes)
case Val.Arr(bytes) => Base64.getEncoder().encodeToString(bytes.map(_.force.asInstanceOf[Val.Num].value.toByte).toArray)
case Val.Arr(bytes) => Base64.getEncoder().encodeToString(bytes.map(_.force.cast[Val.Num].value.toByte).toArray)
case x => throw new DelegateError("Cannot base64 encode " + x.prettyName)
}
},

Expand All @@ -503,14 +532,15 @@ object Std {
Val.Arr(

if (vs.forall(_.force.isInstanceOf[Val.Str])){
vs.map(_.force.asInstanceOf[Val.Str]).sortBy(_.value).map(Lazy(_))
vs.map(_.force.cast[Val.Str]).sortBy(_.value).map(Lazy(_))
}else if (vs.forall(_.force.isInstanceOf[Val.Num])){
vs.map(_.force.asInstanceOf[Val.Num]).sortBy(_.value).map(Lazy(_))
vs.map(_.force.cast[Val.Num]).sortBy(_.value).map(Lazy(_))
}else {
???
}
)
case Val.Str(s) => Val.Arr(s.sorted.map(c => Lazy(Val.Str(c.toString))))
case x => throw new DelegateError("Cannot sort " + x.prettyName)
}
},
builtin("uniq", "arr"){ (wd, extVars, arr: Val.Arr) =>
Expand All @@ -527,7 +557,9 @@ object Std {
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
}else ???
}else {
throw new DelegateError("Every element of the input must be of the same type, string or number")
}

val out = collection.mutable.Buffer.empty[ujson.Js]
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
Expand All @@ -544,7 +576,9 @@ object Std {
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
}else ???
}else {
throw new DelegateError("Every element of the input must be of the same type, string or number")
}

val out = collection.mutable.Buffer.empty[ujson.Js]
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
Expand All @@ -567,7 +601,9 @@ object Std {
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
}else ???
}else {
throw new DelegateError("Every element of the input must be of the same type, string or number")
}

val out = collection.mutable.Buffer.empty[ujson.Js]
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
Expand All @@ -587,7 +623,9 @@ object Std {
vs0.map(_.asInstanceOf[ujson.Js.Str]).sortBy(_.value)
}else if (vs0.forall(_.isInstanceOf[ujson.Js.Num])){
vs0.map(_.asInstanceOf[ujson.Js.Num]).sortBy(_.value)
}else ???
}else {
throw new DelegateError("Every element of the input must be of the same type, string or number")
}

val out = collection.mutable.Buffer.empty[ujson.Js]
for(v <- vs) if (out.isEmpty || out.last != v) out.append(v)
Expand Down
26 changes: 24 additions & 2 deletions sjsonnet/src/sjsonnet/Val.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import ammonite.ops.Path
import sjsonnet.Expr.Member.Visibility
import sjsonnet.Expr.Params

import scala.reflect.ClassTag

object Lazy{
def apply(calc0: => Val) = new Lazy(calc0)
}
Expand All @@ -12,13 +14,33 @@ class Lazy(calc0: => Val){
}
sealed trait Val{
def prettyName: String
def cast[T: ClassTag: PrettyNamed] =
if (implicitly[ClassTag[T]].runtimeClass.isInstance(this)) this.asInstanceOf[T]
else throw new DelegateError(
"Expected " + implicitly[PrettyNamed[T]].s + ", found " + prettyName
)
}
class PrettyNamed[T](val s: String)
object PrettyNamed{
implicit def boolName: PrettyNamed[Val.Bool] = new PrettyNamed("boolean")
implicit def nullName: PrettyNamed[Val.Null.type] = new PrettyNamed("null")
implicit def strName: PrettyNamed[Val.Str] = new PrettyNamed("string")
implicit def numName: PrettyNamed[Val.Num] = new PrettyNamed("number")
implicit def arrName: PrettyNamed[Val.Arr] = new PrettyNamed("array")
implicit def objName: PrettyNamed[Val.Obj] = new PrettyNamed("object")
implicit def funName: PrettyNamed[Val.Func] = new PrettyNamed("function")
}
object Val{
def bool(b: Boolean) = if (b) True else False
case object True extends Val{
sealed trait Bool extends Val{
def value: Boolean
}
case object True extends Bool{
def value = true
def prettyName = "boolean"
}
case object False extends Val{
case object False extends Bool{
def value = false
def prettyName = "boolean"
}
case object Null extends Val{
Expand Down

0 comments on commit 07c2a2a

Please sign in to comment.