diff options
-rw-r--r-- | stage1/Stage1.scala | 1 | ||||
-rw-r--r-- | stage1/Stage1Lib.scala | 16 | ||||
-rw-r--r-- | stage2/AdminStage2.scala | 2 | ||||
-rw-r--r-- | stage2/BasicBuild.scala | 4 | ||||
-rw-r--r-- | stage2/Lib.scala | 100 | ||||
-rw-r--r-- | stage2/NameTransformer.scala | 161 | ||||
-rw-r--r-- | stage2/Stage2.scala | 5 | ||||
-rw-r--r-- | test/test.scala | 10 |
8 files changed, 223 insertions, 76 deletions
diff --git a/stage1/Stage1.scala b/stage1/Stage1.scala index 7278fcf..36921be 100644 --- a/stage1/Stage1.scala +++ b/stage1/Stage1.scala @@ -55,7 +55,6 @@ object Stage1{ val deps = Dependencies( JavaDependency("net.incongru.watchservice","barbary-watchservice","1.0"), - JavaDependency("org.scala-lang","scala-reflect",constants.scalaVersion), JavaDependency("org.eclipse.jgit", "org.eclipse.jgit", "4.2.0.201601211800-r") ) diff --git a/stage1/Stage1Lib.scala b/stage1/Stage1Lib.scala index c8af672..69b828b 100644 --- a/stage1/Stage1Lib.scala +++ b/stage1/Stage1Lib.scala @@ -42,22 +42,6 @@ class Stage1Lib( val logger: Logger ) extends BaseLib{ def scalaMajorVersion(scalaMinorVersion: String) = scalaMinorVersion.split("\\.").take(2).mkString(".") - // ========== reflection ========== - - /** Create instance of the given class via reflection */ - /* - def create(cls: String)(args: Any*)(classLoader: ClassLoader): Any = { - logger.composition( logger.showInvocation("Stage1Lib.create", (cls,args,classLoader)) ) - import scala.reflect.runtime.universe._ - val m = runtimeMirror(classLoader) - val sym = m.classSymbol(classLoader.loadClass(cls)) - val cm = m.reflectClass( sym.asClass ) - val tpe = sym.toType - val ctorm = cm.reflectConstructor( tpe.decl(termNames.CONSTRUCTOR).asMethod ) - ctorm(args:_*) - } - */ - // ========== file system / net ========== def array2hex(padTo: Int, array: Array[Byte]): String = { diff --git a/stage2/AdminStage2.scala b/stage2/AdminStage2.scala index d923b22..cd35cfe 100644 --- a/stage2/AdminStage2.scala +++ b/stage2/AdminStage2.scala @@ -7,7 +7,7 @@ object AdminStage2{ val lib = new Lib(init.logger) val adminTasks = new AdminTasks(lib, args, new File(_args(0))) new lib.ReflectObject(adminTasks){ - def usage: String = "Available methods: " ++ lib.taskNames(subclassType).mkString(" ") + def usage: String = "Available methods: " ++ lib.taskNames(adminTasks.getClass).mkString(" ") }.callNullary(args.lift(0)) } } diff --git a/stage2/BasicBuild.scala b/stage2/BasicBuild.scala index acd49af..28360aa 100644 --- a/stage2/BasicBuild.scala +++ b/stage2/BasicBuild.scala @@ -2,7 +2,6 @@ package cbt import cbt.paths._ import java.io._ -import java.lang.reflect.InvocationTargetException import java.net._ import java.nio.file.{Path =>_,_} import java.nio.file.Files.readAllBytes @@ -10,7 +9,6 @@ import java.security.MessageDigest import java.util.jar._ import scala.collection.immutable.Seq -import scala.reflect.runtime.{universe => ru} import scala.util._ class BasicBuild( context: Context ) extends Build( context ) @@ -25,7 +23,7 @@ class Build(val context: Context) extends Dependency with TriggerLoop{ def enableConcurrency = false final def projectDirectory: File = lib.realpath(context.cwd) assert( projectDirectory.exists, "projectDirectory does not exist: " ++ projectDirectory.string ) - final def usage: Unit = new lib.ReflectBuild(this).usage + final def usage: String = lib.usage(this.getClass, context) // ========== meta data ========== diff --git a/stage2/Lib.scala b/stage2/Lib.scala index 99f578a..d917e8b 100644 --- a/stage2/Lib.scala +++ b/stage2/Lib.scala @@ -8,9 +8,9 @@ import java.nio.file.{Path =>_,_} import java.nio.file.Files.readAllBytes import java.security.MessageDigest import java.util.jar._ +import java.lang.reflect.Method import scala.collection.immutable.Seq -import scala.reflect.runtime.{universe => ru} import scala.util._ // pom model @@ -125,60 +125,63 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ } // task reflection helpers - import ru._ - private lazy val anyRefMembers: Set[String] = ru.typeOf[AnyRef].members.toSet.map(taskName) - def taskNames(tpe: Type): Seq[String] = tpe.members.toVector.flatMap(lib.toTask).map(taskName).sorted - private def taskName(method: Symbol): String = method.name.decodedName.toString - def toTask(symbol: Symbol): Option[MethodSymbol] = { - Option(symbol) - .filter(_.isPublic) - .filter(_.isMethod) - .map(_.asMethod) - .filter(_.paramLists.flatten.size == 0) - .filterNot(taskName(_) contains "$") - .filterNot(t => anyRefMembers contains taskName(t)) - } + def tasks(cls:Class[_]): Map[String, Method] = + Stream + .iterate(cls.asInstanceOf[Class[Any]])(_.getSuperclass) + .takeWhile(_ != null) + .toVector + .dropRight(1) // drop Object + .reverse + .flatMap( + c => + c + .getDeclaredMethods + .filterNot( _.getName contains "$" ) + .filter{ m => + java.lang.reflect.Modifier.isPublic(m.getModifiers) + } + .filter( _.getParameterCount == 0 ) + .map(m => NameTransformer.decode(m.getName) -> m) + ).toMap + + def taskNames(cls: Class[_]): Seq[String] = tasks(cls).keys.toVector.sorted - class ReflectBuild(val build: Build) extends ReflectObject(build){ - def usage: String = { - val baseTasks = lib.taskNames(ru.typeOf[Build]) - val thisTasks = lib.taskNames(subclassType) diff baseTasks + def usage(buildClass: Class[_], context: Context): String = { + val baseTasks = lib.taskNames(classOf[Build]) + val thisTasks = lib.taskNames(buildClass) diff baseTasks + ( ( - ( - if( thisTasks.nonEmpty ){ - s"""Methods provided by Build ${build.context.cwd} + if( thisTasks.nonEmpty ){ + s"""Methods provided by Build ${context} ${thisTasks.mkString(" ")} """ - } else "" - ) ++ s"""Methods provided by CBT (but possibly overwritten) + } else "" + ) ++ s"""Methods provided by CBT (but possibly overwritten) ${baseTasks.mkString(" ")}""" ) ++ "\n" - } } + class ReflectBuild[T:scala.reflect.ClassTag](build: Build) extends ReflectObject(build){ + def usage = lib.usage(build.getClass, build.context) + } abstract class ReflectObject[T:scala.reflect.ClassTag](obj: T){ - lazy val mirror = ru.runtimeMirror(obj.getClass.getClassLoader) - lazy val subclassType = mirror.classSymbol(obj.getClass).toType def usage: String def callNullary( taskName: Option[String] ): Unit = { - taskName - .map{ n => subclassType.member(ru.TermName(n).encodedName) } - .filter(_ != ru.NoSymbol) - .flatMap(toTask _) - .map{ methodSymbol => - val result = mirror.reflect(obj).reflectMethod(methodSymbol)() - + val ts = tasks(obj.getClass) + taskName.map( NameTransformer.encode ).flatMap(ts.get).map{ method => + val result: Option[Any] = Option(method.invoke(obj)) // null in case of Unit + result.map{ + value => // Try to render console representation. Probably not the best way to do this. - scala.util.Try( result.getClass.getDeclaredMethod("toConsole") ) match { - case scala.util.Success(m) => - println(m.invoke(result)) + scala.util.Try( value.getClass.getDeclaredMethod("toConsole") ) match { + case scala.util.Success(toConsole) => + println(toConsole.invoke(value)) - case scala.util.Failure(e) if e.getMessage contains "toConsole" => - result match { - case () => "" + case scala.util.Failure(e) if Option(e.getMessage).getOrElse("") contains "toConsole" => + value match { case ExitCode(code) => System.exit(code) case other => println( other.toString ) // no method .toConsole, using to String } @@ -186,16 +189,17 @@ final class Lib(logger: Logger) extends Stage1Lib(logger) with Scaffold{ case scala.util.Failure(e) => throw e } - }.getOrElse{ - taskName.foreach{ n => - System.err.println(s"Method not found: $n") - System.err.println("") - } - System.err.println(usage) - taskName.foreach{ _ => - ExitCode.Failure - } + }.getOrElse("") + }.getOrElse{ + taskName.foreach{ n => + System.err.println(s"Method not found: $n") + System.err.println("") } + System.err.println(usage) + taskName.foreach{ _ => + ExitCode.Failure + } + } } } diff --git a/stage2/NameTransformer.scala b/stage2/NameTransformer.scala new file mode 100644 index 0000000..33489ca --- /dev/null +++ b/stage2/NameTransformer.scala @@ -0,0 +1,161 @@ +// Adapted from https://github.com/scala/scala/blob/5cb3d4ec14488ce2fc5a1cc8ebdd12845859c57d/src/library/scala/reflect/NameTransformer.scala +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package cbt + +/** Provides functions to encode and decode Scala symbolic names. + * Also provides some constants. + */ +object NameTransformer { + // XXX Short term: providing a way to alter these without having to recompile + // the compiler before recompiling the compiler. + val MODULE_SUFFIX_STRING = sys.props.getOrElse("SCALA_MODULE_SUFFIX_STRING", "$") + val NAME_JOIN_STRING = sys.props.getOrElse("SCALA_NAME_JOIN_STRING", "$") + val MODULE_INSTANCE_NAME = "MODULE$" + val LOCAL_SUFFIX_STRING = " " + val SETTER_SUFFIX_STRING = "_$eq" + val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$" + + private val nops = 128 + private val ncodes = 26 * 26 + + private class OpCodes(val op: Char, val code: String, val next: OpCodes) + + private val op2code = new Array[String](nops) + private val code2op = new Array[OpCodes](ncodes) + private def enterOp(op: Char, code: String) = { + op2code(op.toInt) = code + val c = (code.charAt(1) - 'a') * 26 + code.charAt(2) - 'a' + code2op(c.toInt) = new OpCodes(op, code, code2op(c)) + } + + /* Note: decoding assumes opcodes are only ever lowercase. */ + enterOp('~', "$tilde") + enterOp('=', "$eq") + enterOp('<', "$less") + enterOp('>', "$greater") + enterOp('!', "$bang") + enterOp('#', "$hash") + enterOp('%', "$percent") + enterOp('^', "$up") + enterOp('&', "$amp") + enterOp('|', "$bar") + enterOp('*', "$times") + enterOp('/', "$div") + enterOp('+', "$plus") + enterOp('-', "$minus") + enterOp(':', "$colon") + enterOp('\\', "$bslash") + enterOp('?', "$qmark") + enterOp('@', "$at") + + /** Replace operator symbols by corresponding `\$opname`. + * + * @param name the string to encode + * @return the string with all recognized opchars replaced with their encoding + */ + def encode(name: String): String = { + var buf: StringBuilder = null + val len = name.length() + var i = 0 + while (i < len) { + val c = name charAt i + if (c < nops && (op2code(c.toInt) ne null)) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(op2code(c.toInt)) + /* Handle glyphs that are not valid Java/JVM identifiers */ + } + else if (!Character.isJavaIdentifierPart(c)) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append("$u%04X".format(c.toInt)) + } + else if (buf ne null) { + buf.append(c) + } + i += 1 + } + if (buf eq null) name else buf.toString() + } + + /** Replace `\$opname` by corresponding operator symbol. + * + * @param name0 the string to decode + * @return the string with all recognized operator symbol encodings replaced with their name + */ + def decode(name0: String): String = { + //System.out.println("decode: " + name);//DEBUG + val name = if (name0.endsWith("<init>")) name0.stripSuffix("<init>") + "this" + else name0 + var buf: StringBuilder = null + val len = name.length() + var i = 0 + while (i < len) { + var ops: OpCodes = null + var unicode = false + val c = name charAt i + if (c == '$' && i + 2 < len) { + val ch1 = name.charAt(i+1) + if ('a' <= ch1 && ch1 <= 'z') { + val ch2 = name.charAt(i+2) + if ('a' <= ch2 && ch2 <= 'z') { + ops = code2op((ch1 - 'a') * 26 + ch2 - 'a') + while ((ops ne null) && !name.startsWith(ops.code, i)) ops = ops.next + if (ops ne null) { + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(ops.op) + i += ops.code.length() + } + /* Handle the decoding of Unicode glyphs that are + * not valid Java/JVM identifiers */ + } else if ((len - i) >= 6 && // Check that there are enough characters left + ch1 == 'u' && + ((Character.isDigit(ch2)) || + ('A' <= ch2 && ch2 <= 'F'))) { + /* Skip past "$u", next four should be hexadecimal */ + val hex = name.substring(i+2, i+6) + try { + val str = Integer.parseInt(hex, 16).toChar + if (buf eq null) { + buf = new StringBuilder() + buf.append(name.substring(0, i)) + } + buf.append(str) + /* 2 for "$u", 4 for hexadecimal number */ + i += 6 + unicode = true + } catch { + case _:NumberFormatException => + /* `hex` did not decode to a hexadecimal number, so + * do nothing. */ + } + } + } + } + /* If we didn't see an opcode or encoded Unicode glyph, and the + buffer is non-empty, write the current character and advance + one */ + if ((ops eq null) && !unicode) { + if (buf ne null) + buf.append(c) + i += 1 + } + } + //System.out.println("= " + (if (buf == null) name else buf.toString()));//DEBUG + if (buf eq null) name else buf.toString() + } +} diff --git a/stage2/Stage2.scala b/stage2/Stage2.scala index a0a2b57..75be3c7 100644 --- a/stage2/Stage2.scala +++ b/stage2/Stage2.scala @@ -47,8 +47,9 @@ object Stage2{ scala.util.control.Breaks.break case file if triggerFiles.exists(file.toString startsWith _.toString) => - val reflectBuild = new lib.ReflectBuild( lib.loadDynamic(context) ) - logger.loop(s"Re-running $task for " ++ reflectBuild.build.projectDirectory.toString) + val build = lib.loadDynamic(context) + val reflectBuild = new lib.ReflectBuild( build ) + logger.loop(s"Re-running $task for " ++ build.projectDirectory.toString) reflectBuild.callNullary(task) } } else { diff --git a/test/test.scala b/test/test.scala index ebcaaa1..e623d34 100644 --- a/test/test.scala +++ b/test/test.scala @@ -41,7 +41,7 @@ object Main{ } case class Result(exit0: Boolean, out: String, err: String) def assertSuccess(res: Result, msg: => String)(implicit logger: Logger) = { - assert(res.exit0, msg + res.toString) + assert(res.exit0, msg ++ res.toString) } // tests @@ -49,14 +49,14 @@ object Main{ val usageString = "Methods provided by CBT" val res = runCbt(path, Seq()) logger.test(res.toString) - val debugToken = "usage " + path +" " + val debugToken = "usage " ++ path ++ " " assertSuccess(res,debugToken) - assert(res.out == "", debugToken+ res.toString) - assert(res.err contains usageString, debugToken+res.toString) + assert(res.out == "", debugToken ++ res.toString) + assert(res.err contains usageString, debugToken ++ res.toString) } def compile(path: String)(implicit logger: Logger) = { val res = runCbt(path, Seq("compile")) - val debugToken = "compile " + path +" " + val debugToken = "compile " ++ path ++ " " assertSuccess(res,debugToken) // assert(res.err == "", res.err) // FIXME: enable this } |