All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sjsonnet.Interpreter.scala Maven / Gradle / Ivy

package sjsonnet

import java.io.{PrintWriter, StringWriter}

import scala.collection.mutable

import sjsonnet.Expr.Params

import scala.util.control.NonFatal

/**
  * Wraps all the machinery of evaluating Jsonnet source code, from parsing to
  * evaluation to materialization, into a convenient wrapper class.
  */
class Interpreter(extVars: Map[String, String],
                  tlaVars: Map[String, String],
                  wd: Path,
                  importer: Importer,
                  val parseCache: ParseCache,
                  settings: Settings = Settings.default,
                  storePos: Position => Unit = null,
                  warnLogger: (String => Unit) = null,
                  std: Val.Obj = new Std().Std
                  ) { self =>

  private val internedStrings = new mutable.HashMap[String, String]

  private val internedStaticFieldSets = new mutable.HashMap[Val.StaticObjectFieldSet, java.util.LinkedHashMap[String, java.lang.Boolean]]

  val resolver = new CachedResolver(importer, parseCache, settings.strictImportSyntax, internedStrings, internedStaticFieldSets) {
    override def process(expr: Expr, fs: FileScope): Either[Error, (Expr, FileScope)] =
      handleException(new StaticOptimizer(evaluator, std, internedStrings, internedStaticFieldSets).optimize(expr), fs)
  }

  private def warn(e: Error): Unit = warnLogger("[warning] " + formatError(e))

  def createEvaluator(resolver: CachedResolver, extVars: String => Option[Expr], wd: Path,
                      settings: Settings, warn: Error => Unit): Evaluator =
    new Evaluator(resolver, extVars, wd, settings, warn)


  def parseVar(k: String, v: String) = {
    resolver.parse(wd / s"<$k>", StaticResolvedFile(v))(evaluator).fold(throw _, _._1)
  }

  lazy val evaluator: Evaluator = createEvaluator(
    resolver,
    // parse extVars lazily, because they can refer to each other and be recursive
    k => extVars.get(k).map(v => parseVar(s"ext-var $k", v)),
    wd,
    settings,
    warn
  )

  evaluator // force the lazy val

  def formatError(e: Error): String = {
    val s = new StringWriter()
    val p = new PrintWriter(s)
    e.printStackTrace(p)
    p.close()
    s.toString.replace("\t", "    ")
  }

  def interpret(txt: String, path: Path): Either[String, ujson.Value] =
    interpret0(txt, path, ujson.Value)

  def interpret0[T](txt: String,
                    path: Path,
                    visitor: upickle.core.Visitor[T, T]): Either[String, T] =
    (for{
      v <- evaluate(txt, path)
      r <- materialize(v, visitor)
    } yield r).left.map(formatError)

  private def handleException[T](f: => T): Either[Error, T] = {
    try Right(f) catch {
      case e: Error => Left(e)
      case NonFatal(e) =>
        Left(new Error("Internal error: "+e.toString, Nil, Some(e)))
    }
  }

  def evaluate(txt: String, path: Path): Either[Error, Val] = {
    val resolvedImport = StaticResolvedFile(txt)
    resolver.cache(path) = resolvedImport
    for{
      res <- resolver.parse(path, resolvedImport)(evaluator)
      (parsed, _) = res
      res0 <- handleException(evaluator.visitExpr(parsed)(ValScope.empty))
      res = res0 match{
        case f: Val.Func =>
          val defaults2 = f.params.defaultExprs.clone()
          val tlaExpressions = collection.mutable.Set.empty[Expr]
          var i = 0
          while(i < defaults2.length) {
            val k = f.params.names(i)
            for(v <- tlaVars.get(k)){
              val parsed = parseVar(s"tla-var $k", v)
              defaults2(i) = parsed
              tlaExpressions.add(parsed)
            }
            i += 1
          }
          new Val.Func(f.pos, f.defSiteValScope, Params(f.params.names, defaults2)) {
            def evalRhs(vs: ValScope, es: EvalScope, fs: FileScope, pos: Position) = f.evalRhs(vs, es, fs, pos)
            override def evalDefault(expr: Expr, vs: ValScope, es: EvalScope) = {
              evaluator.visitExpr(expr)(if (tlaExpressions.exists(_ eq expr)) ValScope.empty else vs)
            }
          }
        case x => x
      }
    } yield res
  }

  def materialize[T](res: Val, visitor: upickle.core.Visitor[T, T]): Either[Error, T] = {
    val m =
      if(storePos == null) Materializer
      else new Materializer {
        override def storePos(pos: Position): Unit = self.storePos(pos)
        override def storePos(v: Val): Unit = {
          storePos(
            v match {
              case v: Val.Obj if v.hasKeys => v.pos
              case v: Val.Arr if v.length > 0 => v.pos
              case _ => null
            }
          )
        }
      }
    handleException(m.apply0(res, visitor)(evaluator))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy