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

org.tresql.resources.ResourceLoader.scala Maven / Gradle / Ivy

The newest version!
package org.tresql.resources

import org.tresql.ast.CompilerAst.ExprType
import org.tresql.{Expr, QueryBuilder}
import org.tresql.metadata.{FixedReturnType, Par, ParameterReturnType, Procedure, ReturnType, TypeMapper}
import org.tresql.ast.{Cast, Exp, Fun, Ident, Obj}
import org.tresql.parsing.QueryParsers

import java.io.InputStream
import java.lang.reflect.{Method, ParameterizedType}
import scala.io.BufferedSource
import scala.reflect.ManifestFactory
import scala.util.Try
import scala.util.matching.Regex

case class FunctionSignatures(signatures: Map[String, List[Procedure]]) {
  def merge(fs: FunctionSignatures): FunctionSignatures = {
    FunctionSignatures(
      ResourceMerger.mergeResources(signatures, fs.signatures)
        .map { case (n, l) => (n, l.toList) }
    )
  }
}
object FunctionSignatures {
  def empty = FunctionSignatures(Map())
}

class FunctionSignaturesLoader(typeMapper: TypeMapper) extends ResourceLoader {
  protected val ResourceFile        = "/tresql-function-signatures.txt"
  protected val DefaultResourceFile = "/tresql-default-function-signatures.txt"

  private val ParTypeDefRegex = """(\w*)(\*?)""".r
  private val RetTypeDefRegex = """(\$?)(\w+)""".r

  protected val qp: QueryParsers = new QueryParsers {
    override val reserved: Set[String] = Set()
  }

  protected def tryParseSignature(signature: String) = {
    var repeatedPars = false
    def parseParType(t: String) = {
      val ParTypeDefRegex(pt, isRepeated) = t
      (pt, isRepeated.nonEmpty)
    }
    def parseRetType(t: String) = {
      val RetTypeDefRegex(ref, pt) = t
      (ref.nonEmpty, pt)
    }
    def createPar[T](pn: String, pt: ExprType) = Par(pn, null, pt)
    def parse_params(params: List[Exp]) =
      params.map {
        case Obj(Ident(List(pn)), null, null, null, false) => createPar(pn, ExprType())
        case Cast(Obj(Ident(List(pn)), null, null, null, false), type_) =>
          val (pt, isRepeated) = parseParType(type_)
          if (isRepeated) repeatedPars = true
          createPar(
            pn,
            if (pt.isEmpty || typeMapper == null) ExprType()
            else ExprType(pt)
          )
        case x => sys.error(s"Invalid function '$signature' paramater - '${x.tresql}'. " +
          s"Expected identifier or identifier with cast.")
      }
    def createFun(fn: String, pars: List[Par], rt: ReturnType, repPars: Boolean) =
      Procedure(fn, null, -1, pars, rt, repPars)
    Try(qp.parseExp(signature))
      .map {
        case Fun(name, parameters, _, None, None) =>
          createFun(name, parse_params(parameters), FixedReturnType(ExprType.Any), repeatedPars)
        case Cast(Fun(name, parameters, _, None, None), type_) =>
          val pars = parse_params(parameters)
          val (isRef, pt) = parseRetType(type_)
          val rt =
            if (isRef) {
              val idx = pars.indexWhere(_.name == pt)
              assert(idx != -1, s"Invalid function $name return type '$type_'. Parameter $pt does not exist.")
              ParameterReturnType(idx)
            }
            else FixedReturnType(
              if (typeMapper!= null) ExprType(pt)
              else ExprType.Any
            )
          createFun(name, pars, rt, repeatedPars)
        case _ => sys.error(s"function signature must be function, instead found - '$signature'")
      }
  }

  protected def parseErr(signatureDef: String) =
    sys.error(s"Error in function signature definition '$signatureDef'. " +
      s"Format - ([::type], ...)")

  def parseSignature(signatureDef: String): Procedure =
    tryParseSignature(signatureDef).get

  protected def trySignatureDef(signatureDef: String): Try[Boolean] = {
    Try(qp.parseExp(signatureDef))
      .map {
        case _: Fun | Cast(_: Fun, _) => true
        case _ => false /* maybe comment */
      }
      .recover { case _ => parseErr(signatureDef) }
  }

  def isSignatureDef(signatureDef: String): Boolean = {
    trySignatureDef(signatureDef).get
  }

  def parseSignature(m: Method): Procedure = {
    var repeatedPars = false
    // parameter types and return types are Any since this is considered
    // macro parsing where arguments are expressions. if macro function needs
    // parameter or return types during tresql compilation, put it into
    // function signatures
    val pars: List[Par] = m.getGenericParameterTypes.map {
      case par: ParameterizedType =>
        //consider parameterized type as a Seq[T] of repeated args
        //isVarArgs method of java reflection api does not work on scala repeated args
        repeatedPars = true
        par.getActualTypeArguments match {
          case scala.Array(_) => ExprType.Any
          case _ => sys.error(s"Multiple type parameters not supported! Method: $m, parameter: $par")
        }
      case _ => ExprType.Any
    }.zipWithIndex.map { case (et, i) =>
      Par(s"_$i", null, et)
    }.toList.drop(1) // drop builder or parser argument
    val returnType = m.getGenericReturnType match {
      case _: ParameterizedType | _: Class[_] => FixedReturnType(ExprType.Any)
      case x =>
        val idx  = pars.indexWhere(_.parType.toString == x.toString)
        if (idx == -1) FixedReturnType(ExprType.Any) else ParameterReturnType(idx)
    }
    Procedure(m.getName, null, -1, pars, returnType, repeatedPars)
  }

  def loadFunctionSignatures(signatures: Seq[String]): FunctionSignatures = {
    val fs = signatures
      .collect { case s if isSignatureDef(s) => parseSignature(s) }
      .toList
      .groupBy(_.name)
    FunctionSignatures(fs)
  }

  def loadFunctionSignaturesFromClass(clazz: Class[_]): FunctionSignatures = {
    if (clazz == null) FunctionSignatures.empty
    else {
      val signatures =
        clazz.getMethods
          .collect { case m if m.getParameterCount > 0 => parseSignature(m) } // must have at least one par - parser or builder
          .toList
          .groupBy(_.name)
      FunctionSignatures(signatures)
    }
  }
}

trait TresqlMacro[A, B] {
  def invoke(env: A, params: IndexedSeq[_]): B
  def signature: Procedure
}

case class TresqlMacros(parserMacros: Map[String, Seq[TresqlMacro[QueryParsers, Exp]]],
                        builderMacros: Map[String, Seq[TresqlMacro[QueryBuilder, Expr]]],
                        builderDeferredMacros: Map[String, Seq[TresqlMacro[QueryBuilder, Expr]]]) {
  def merge(tresqlMacros: TresqlMacros): TresqlMacros = {
    import ResourceMerger._
    TresqlMacros(parserMacros = mergeResources(parserMacros, tresqlMacros.parserMacros),
      builderMacros = mergeResources(builderMacros, tresqlMacros.builderMacros),
      builderDeferredMacros = mergeResources(builderDeferredMacros, tresqlMacros.builderDeferredMacros))
  }
}
object TresqlMacros {
  def empty = TresqlMacros(Map(), Map(), Map())
}

class MacrosLoader(typeMapper: TypeMapper) extends FunctionSignaturesLoader(typeMapper) {
  override protected val ResourceFile        = "/tresql-macros.txt"
  override protected val DefaultResourceFile = "/tresql-default-macros.txt"

  private case class MacroBody(parts: Seq[MacroBodyPart], suffix: String)
  private case class MacroBodyPart(prefix: String, parIdx: Int)
  private class TresqlResourcesMacro(val signature: Procedure, body: MacroBody)
    extends TresqlMacro[QueryParsers, Exp] {
    private val sigParCount = signature.pars.size
    override def invoke(env: QueryParsers, params: IndexedSeq[_]): Exp = {
      val parCount = params.size
      def mayBeLiftToArr(idx: Int) = {
        if (signature.hasRepeatedPar && idx == sigParCount - 1 && sigParCount < parCount)
          params.slice(idx, parCount).map(_.asInstanceOf[Exp].tresql).mkString("[", ",", "]")
        else params(idx).asInstanceOf[Exp].tresql
      }
      val res =
        body.parts.foldLeft(new StringBuilder()) { (res, bp) =>
          res.append(bp.prefix).append(mayBeLiftToArr(bp.parIdx))
        }
        .append(body.suffix)
        .toString()
      env.parseExp(res)
    }
    override def toString() = s"Signature - $signature, body - $body"
  }
  private case class TresqlScalaMacro[A, B](signature: Procedure, method: Method, invocationTarget: Any)
    extends TresqlMacro[A, B] {
    override def invoke(env: A, params: IndexedSeq[_]): B = {
      val p = method.getParameterTypes
      try {
          val _args =
            (if (signature.hasRepeatedPar && params.nonEmpty && p.last.isAssignableFrom(classOf[Seq[_]])) {
              val idx = signature.pars.size - 1
              params.slice(0, idx)
                .:+(params.slice(idx, params.length))
            } else {
              params
            }).+:(env).asInstanceOf[Seq[Object]] //must cast for scala verion 2.12
          method.invoke(invocationTarget, _args: _*).asInstanceOf[B]
      } catch {
        case e: Exception =>
          def msg(e: Throwable): List[String] = {
            if (e == null) Nil
            else s"""${e.getClass}${if (e.getMessage != null) ": " + e.getMessage else ""}""" :: msg(e.getCause)
          }
          throw new RuntimeException(s"Error invoking macro function - ${signature.name} (${msg(e).mkString(" ")})", e)
      }
    }
  }

  override protected def parseErr(macroDef: String) =
    sys.error(s"Error in macro definition '$macroDef'. " +
      s"Macro def format -  = ")

  def parseMacro(macroDef: String): TresqlMacro[QueryParsers, Exp] = {
    def parseBody(body: String, params: Seq[String]) = {
      val pattern = params.sortBy(- _.length).map("\\$" + _).mkString("|")
      val regex = new Regex(pattern)
      if (pattern.nonEmpty) {
        val (suffixIdx, bodyPartsReversed) =
          regex.findAllMatchIn(body).foldLeft(0 -> List[MacroBodyPart]()) { case ((suffIdx, res), m) =>
            val par = m.matched.substring(1)
            val idx = params.indexWhere(_ == par)
            assert(idx != -1, sys.error(s"Unknown parameter reference '$par' in macro: ($body)"))
            m.end -> (MacroBodyPart(body.substring(suffIdx, m.start), idx) :: res)
          }
        MacroBody(bodyPartsReversed.reverse, body.substring(suffixIdx))
      }
      else MacroBody(Nil, body)
    }
    macroDef.split("=", 2) match {
      case Array(signature, body) =>
        tryParseSignature(signature)
          .map { sign =>
            new TresqlResourcesMacro(sign, parseBody(body, sign.pars.map(_.name)))
          }.get
      case _ => parseErr(macroDef)
    }
  }

  def isMacroDef(macroDef: String): Boolean = {
    val idx = macroDef.indexOf("=")
    if (idx == -1) isSignatureDef(macroDef) // maybe comment
    else {
      trySignatureDef(macroDef.substring(0, idx))
        .recoverWith { case _ => trySignatureDef(macroDef) /* maybe comment containing '=' sign */ }
        .get
    }
  }

  def loadTresqlMacros(macros: Seq[String]): TresqlMacros = {
    val parserMacros =
      macros.collect {
        case m if isMacroDef(m) => parseMacro(m)
      }.foldLeft(Map[String, Seq[TresqlMacro[QueryParsers, Exp]]]()) { (res, m) =>
        val n = m.signature.name
        res.get(n)
          .map { ml =>
            res + (n -> (ml :+ m))
          }.getOrElse(res + (n -> Seq(m)))
      }
    TresqlMacros(parserMacros = parserMacros, builderMacros = Map(), builderDeferredMacros = Map())
  }

  def loadTresqlScalaMacros(obj: Any): TresqlMacros = {
    def macroMethods(mobj: Any): TresqlMacros = mobj match {
      case null => TresqlMacros.empty
      case Some(o) => macroMethods(o)
      case None => macroMethods(null)
      case x =>
        def isMacro(m: java.lang.reflect.Method) =
          m.getParameterTypes.nonEmpty && (isParserMacro(m) || isBuilderMacro(m))
        def isParserMacro(m: java.lang.reflect.Method) =
          classOf[QueryParsers].isAssignableFrom(m.getParameterTypes()(0)) &&
            classOf[Exp].isAssignableFrom(m.getReturnType)
        def isBuilderMacro(m: java.lang.reflect.Method) =
          classOf[QueryBuilder].isAssignableFrom(m.getParameterTypes()(0)) &&
            classOf[Expr].isAssignableFrom(m.getReturnType)
        def hasAllExpPars(m: java.lang.reflect.Method) =
          m.getParameterTypes.size > 1 && m.getParameterTypes.tail.forall(p => classOf[Exp].isAssignableFrom(p))
        val macros = x.getClass.getMethods.collect {
          case m if isMacro(m) => m
        }.foldLeft(TresqlMacros.empty) { (res, m) =>
          def app[A, B](map: Map[String, Seq[TresqlMacro[A, B]]]) = {
            val sign = parseSignature(m)
            val macr = TresqlScalaMacro[A, B](sign, m, mobj)
            val n = sign.name
            map.get(n)
              .map { ml =>
                map + (n -> (ml :+ macr))
              }.getOrElse(map + (n -> Seq(macr)))
          }
          if (isBuilderMacro(m))
            if (hasAllExpPars(m)) res.copy(builderDeferredMacros = app(res.builderDeferredMacros))
            else res.copy(builderMacros = app(res.builderMacros))
          else res.copy(parserMacros = app(res.parserMacros))
        }
        if (macros.builderMacros.isEmpty && macros.parserMacros.isEmpty)
          sys.error(s"No macro methods found in object $mobj. " +
            s"If you do not want to use macros pass null as a parameter")
        macros
    }
    macroMethods(obj)
  }

  override def loadFunctionSignatures(macro_def: Seq[String]): FunctionSignatures = {
    val fs = macro_def
      .collect { case s if isMacroDef(s) => parseSignature(s.substring(0, s.indexOf("="))) }
      .toList
      .groupBy(_.name)
    FunctionSignatures(fs)
  }
}

trait ResourceLoader {
  private val SeparatorPattern = """\R+(?=[^\s]|$)"""
  private val IncludePattern   = """include\s+(.+)""".r
  protected def ResourceFile: String
  protected def DefaultResourceFile: String
  protected def getResourceAsStream(r: String): InputStream = getClass.getResourceAsStream(r)

  def load(res: String): Option[Seq[String]] = {
    def l(r: String)(loaded: Set[String]): Option[Seq[String]] = {
      if (loaded(r)) None
      else {
        val in = getResourceAsStream(r)
        if (in == null) {
          if (loaded.isEmpty) None
          else sys.error(s"Resource not found: $r (referenced from ${loaded mkString " or "})")
        }
        else {
          val res =
            new BufferedSource(in).mkString.split(SeparatorPattern).toIndexedSeq
              .flatMap { token =>
                if (IncludePattern.pattern.matcher(token).matches) {
                  val IncludePattern(nr) = token
                  l(nr)(loaded + r).getOrElse(Nil)
                } else {
                  List(token)
                }
              }
          Option(res)
        }
      }
    }
    l(res)(Set())
  }

  def load(): Seq[String] = {
    (load(ResourceFile) orElse load(DefaultResourceFile)).getOrElse(Nil)
  }
}

private object ResourceMerger {
  def mergeResources[A](m1: Map[String, Seq[A]], m2: Map[String, Seq[A]]): Map[String, Seq[A]] = {
    m1.foldLeft(m2) { case (res, (n, ml1)) =>
      res.get(n).map { ml2 =>
        res + (n -> (ml2 ++: ml1)) // put right resources in front of left
      }.getOrElse(res + (n -> ml1))
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy