diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/library/scala/concurrent/SyncVar.scala | 6 | ||||
-rw-r--r-- | src/library/scala/util/MurmurHash.scala | 199 | ||||
-rw-r--r-- | src/library/scala/util/hashing/MurmurHash3.scala | 8 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Kinds.scala | 176 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Symbols.scala | 2 | ||||
-rw-r--r-- | src/reflect/scala/reflect/internal/Types.scala | 20 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ExprTyper.scala | 15 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ILoop.scala | 13 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/IMain.scala | 2 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/package.scala | 42 |
10 files changed, 473 insertions, 10 deletions
diff --git a/src/library/scala/concurrent/SyncVar.scala b/src/library/scala/concurrent/SyncVar.scala index d22471ac0f..76d21c3dbf 100644 --- a/src/library/scala/concurrent/SyncVar.scala +++ b/src/library/scala/concurrent/SyncVar.scala @@ -79,7 +79,8 @@ class SyncVar[A] { // whether or not the SyncVar is already defined. So, set has been // deprecated in order to eventually be able to make "setting" private @deprecated("Use `put` instead, as `set` is potentionally error-prone", "2.10.0") - private[scala] def set(x: A): Unit = setVal(x) + // NOTE: Used by SBT 0.13.0-M2 and below + def set(x: A): Unit = setVal(x) /** Places a value in the SyncVar. If the SyncVar already has a stored value, * it waits until another thread takes it */ @@ -98,7 +99,8 @@ class SyncVar[A] { // whether or not the SyncVar is already defined. So, unset has been // deprecated in order to eventually be able to make "unsetting" private @deprecated("Use `take` instead, as `unset` is potentionally error-prone", "2.10.0") - private[scala] def unset(): Unit = synchronized { + // NOTE: Used by SBT 0.13.0-M2 and below + def unset(): Unit = synchronized { isDefined = false value = None notifyAll() diff --git a/src/library/scala/util/MurmurHash.scala b/src/library/scala/util/MurmurHash.scala new file mode 100644 index 0000000000..6b306211dc --- /dev/null +++ b/src/library/scala/util/MurmurHash.scala @@ -0,0 +1,199 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala +package util + +/** An implementation of Austin Appleby's MurmurHash 3.0 algorithm + * (32 bit version); reference: http://code.google.com/p/smhasher + * + * This is the hash used by collections and case classes (including + * tuples). + * + * @author Rex Kerr + * @version 2.9 + * @since 2.9 + */ + +import java.lang.Integer.{ rotateLeft => rotl } +import scala.collection.Iterator + +/** A class designed to generate well-distributed non-cryptographic + * hashes. It is designed to be passed to a collection's foreach method, + * or can take individual hash values with append. Its own hash code is + * set equal to the hash code of whatever it is hashing. + */ +@deprecated("Use the object MurmurHash3 instead.", "2.10.0") +class MurmurHash[@specialized(Int,Long,Float,Double) T](seed: Int) extends (T => Unit) { + import MurmurHash._ + + private var h = startHash(seed) + private var c = hiddenMagicA + private var k = hiddenMagicB + private var hashed = false + private var hashvalue = h + + /** Begin a new hash using the same seed. */ + def reset() { + h = startHash(seed) + c = hiddenMagicA + k = hiddenMagicB + hashed = false + } + + /** Incorporate the hash value of one item. */ + def apply(t: T) { + h = extendHash(h,t.##,c,k) + c = nextMagicA(c) + k = nextMagicB(k) + hashed = false + } + + /** Incorporate a known hash value. */ + def append(i: Int) { + h = extendHash(h,i,c,k) + c = nextMagicA(c) + k = nextMagicB(k) + hashed = false + } + + /** Retrieve the hash value */ + def hash = { + if (!hashed) { + hashvalue = finalizeHash(h) + hashed = true + } + hashvalue + } + override def hashCode = hash +} + +/** An object designed to generate well-distributed non-cryptographic + * hashes. It is designed to hash a collection of integers; along with + * the integers to hash, it generates two magic streams of integers to + * increase the distribution of repetitive input sequences. Thus, + * three methods need to be called at each step (to start and to + * incorporate a new integer) to update the values. Only one method + * needs to be called to finalize the hash. + */ +@deprecated("Use the object MurmurHash3 instead.", "2.10.0") +// NOTE: Used by SBT 0.13.0-M2 and below +object MurmurHash { + // Magic values used for MurmurHash's 32 bit hash. + // Don't change these without consulting a hashing expert! + final private val visibleMagic = 0x971e137b + final private val hiddenMagicA = 0x95543787 + final private val hiddenMagicB = 0x2ad7eb25 + final private val visibleMixer = 0x52dce729 + final private val hiddenMixerA = 0x7b7d159c + final private val hiddenMixerB = 0x6bce6396 + final private val finalMixer1 = 0x85ebca6b + final private val finalMixer2 = 0xc2b2ae35 + + // Arbitrary values used for hashing certain classes + final private val seedString = 0xf7ca7fd2 + final private val seedArray = 0x3c074a61 + + /** The first 23 magic integers from the first stream are stored here */ + val storedMagicA = + Iterator.iterate(hiddenMagicA)(nextMagicA).take(23).toArray + + /** The first 23 magic integers from the second stream are stored here */ + val storedMagicB = + Iterator.iterate(hiddenMagicB)(nextMagicB).take(23).toArray + + /** Begin a new hash with a seed value. */ + def startHash(seed: Int) = seed ^ visibleMagic + + /** The initial magic integers in the first stream. */ + def startMagicA = hiddenMagicA + + /** The initial magic integer in the second stream. */ + def startMagicB = hiddenMagicB + + /** Incorporates a new value into an existing hash. + * + * @param hash the prior hash value + * @param value the new value to incorporate + * @param magicA a magic integer from the stream + * @param magicB a magic integer from a different stream + * @return the updated hash value + */ + def extendHash(hash: Int, value: Int, magicA: Int, magicB: Int) = { + (hash ^ rotl(value*magicA,11)*magicB)*3 + visibleMixer + } + + /** Given a magic integer from the first stream, compute the next */ + def nextMagicA(magicA: Int) = magicA*5 + hiddenMixerA + + /** Given a magic integer from the second stream, compute the next */ + def nextMagicB(magicB: Int) = magicB*5 + hiddenMixerB + + /** Once all hashes have been incorporated, this performs a final mixing */ + def finalizeHash(hash: Int) = { + var i = (hash ^ (hash>>>16)) + i *= finalMixer1 + i ^= (i >>> 13) + i *= finalMixer2 + i ^= (i >>> 16) + i + } + + /** Compute a high-quality hash of an array */ + def arrayHash[@specialized T](a: Array[T]) = { + var h = startHash(a.length * seedArray) + var c = hiddenMagicA + var k = hiddenMagicB + var j = 0 + while (j < a.length) { + h = extendHash(h, a(j).##, c, k) + c = nextMagicA(c) + k = nextMagicB(k) + j += 1 + } + finalizeHash(h) + } + + /** Compute a high-quality hash of a string */ + def stringHash(s: String) = { + var h = startHash(s.length * seedString) + var c = hiddenMagicA + var k = hiddenMagicB + var j = 0 + while (j+1 < s.length) { + val i = (s.charAt(j)<<16) + s.charAt(j+1) + h = extendHash(h,i,c,k) + c = nextMagicA(c) + k = nextMagicB(k) + j += 2 + } + if (j < s.length) h = extendHash(h,s.charAt(j),c,k) + finalizeHash(h) + } + + /** Compute a hash that is symmetric in its arguments--that is, + * where the order of appearance of elements does not matter. + * This is useful for hashing sets, for example. + */ + def symmetricHash[T](xs: scala.collection.TraversableOnce[T], seed: Int) = { + var a,b,n = 0 + var c = 1 + xs.seq.foreach(i => { + val h = i.## + a += h + b ^= h + if (h != 0) c *= h + n += 1 + }) + var h = startHash(seed * n) + h = extendHash(h, a, storedMagicA(0), storedMagicB(0)) + h = extendHash(h, b, storedMagicA(1), storedMagicB(1)) + h = extendHash(h, c, storedMagicA(2), storedMagicB(2)) + finalizeHash(h) + } +} diff --git a/src/library/scala/util/hashing/MurmurHash3.scala b/src/library/scala/util/hashing/MurmurHash3.scala index c38d0f4da5..af0b12d8ba 100644 --- a/src/library/scala/util/hashing/MurmurHash3.scala +++ b/src/library/scala/util/hashing/MurmurHash3.scala @@ -275,4 +275,12 @@ object MurmurHash3 extends MurmurHash3 { finalizeHash(h, n) } */ + + @deprecated("Use unorderedHash", "2.10.0") + final def symmetricHash[T](xs: scala.collection.GenTraversableOnce[T], seed: Int = symmetricSeed): Int = + unorderedHash(xs.seq, seed) + + @deprecated("Use orderedHash", "2.10.0") + final def traversableHash[T](xs: scala.collection.GenTraversableOnce[T], seed: Int = traversableSeed): Int = + orderedHash(xs.seq, seed) } diff --git a/src/reflect/scala/reflect/internal/Kinds.scala b/src/reflect/scala/reflect/internal/Kinds.scala index 4612e65e7a..46a95c7d26 100644 --- a/src/reflect/scala/reflect/internal/Kinds.scala +++ b/src/reflect/scala/reflect/internal/Kinds.scala @@ -230,4 +230,180 @@ trait Kinds { } } } + + /** + * The data structure describing the kind of a given type. + * + * Proper types are represented using ProperTypeKind. + * + * Type constructors are reprented using TypeConKind. + */ + abstract class Kind { + import Kind.StringState + def description: String + def order: Int + def bounds: TypeBounds + + /** Scala syntax notation of this kind. + * Proper types are expresses as A. + * Type constructors are expressed as F[k1 >: lo <: hi, k2, ...] where k1, k2, ... are parameter kinds. + * If the bounds exists at any level, it preserves the type variable names. Otherwise, + * it uses prescribed letters for each level: A, F, X, Y, Z. + */ + def scalaNotation: String + + /** Kind notation used in http://adriaanm.github.com/files/higher.pdf. + * Proper types are expressed as *. + * Type constructors are expressed * -> *(lo, hi) -(+)-> *. + */ + def starNotation: String + + /** Contains bounds either as part of itself or its arguments. + */ + def hasBounds: Boolean = !bounds.isEmptyBounds + + private[internal] def buildState(sym: Symbol, v: Variance)(s: StringState): StringState + } + object Kind { + private[internal] sealed trait ScalaNotation + private[internal] sealed case class Head(order: Int, n: Option[Int], alias: Option[String]) extends ScalaNotation { + override def toString: String = { + alias getOrElse { + typeAlias(order) + n.map(_.toString).getOrElse("") + } + } + private def typeAlias(x: Int): String = + x match { + case 0 => "A" + case 1 => "F" + case 2 => "X" + case 3 => "Y" + case 4 => "Z" + case n if n < 12 => ('O'.toInt - 5 + n).toChar.toString + case _ => "V" + } + } + private[internal] sealed case class Text(value: String) extends ScalaNotation { + override def toString: String = value + } + private[internal] case class StringState(tokens: Seq[ScalaNotation]) { + override def toString: String = tokens.mkString + def append(value: String): StringState = StringState(tokens :+ Text(value)) + def appendHead(order: Int, sym: Symbol): StringState = { + val n = countByOrder(order) + 1 + val alias = if (sym eq NoSymbol) None + else Some(sym.nameString) + StringState(tokens :+ Head(order, Some(n), alias)) + } + def countByOrder(o: Int): Int = tokens count { + case Head(`o`, _, _) => true + case t => false + } + // Replace Head(o, Some(1), a) with Head(o, None, a) if countByOrder(o) <= 1, so F1[A] becomes F[A] + def removeOnes: StringState = { + val maxOrder = (tokens map { + case Head(o, _, _) => o + case _ => 0 + }).max + StringState((tokens /: (0 to maxOrder)) { (ts: Seq[ScalaNotation], o: Int) => + if (countByOrder(o) <= 1) + ts map { + case Head(`o`, _, a) => Head(o, None, a) + case t => t + } + else ts + }) + } + // Replace Head(o, n, Some(_)) with Head(o, n, None), so F[F] becomes F[A]. + def removeAlias: StringState = { + StringState(tokens map { + case Head(o, n, Some(_)) => Head(o, n, None) + case t => t + }) + } + } + private[internal] object StringState { + def empty: StringState = StringState(Seq()) + } + } + class ProperTypeKind(val bounds: TypeBounds) extends Kind { + import Kind.StringState + val description: String = "This is a proper type." + val order = 0 + private[internal] def buildState(sym: Symbol, v: Variance)(s: StringState): StringState = { + s.append(v.symbolicString).appendHead(order, sym).append(bounds.scalaNotation(_.toString)) + } + def scalaNotation: String = Kind.Head(order, None, None) + bounds.scalaNotation(_.toString) + def starNotation: String = "*" + bounds.starNotation(_.toString) + } + object ProperTypeKind { + def apply: ProperTypeKind = this(TypeBounds.empty) + def apply(bounds: TypeBounds): ProperTypeKind = new ProperTypeKind(bounds) + def unapply(ptk: ProperTypeKind): Some[TypeBounds] = Some(ptk.bounds) + } + + class TypeConKind(val bounds: TypeBounds, val args: Seq[TypeConKind.Argument]) extends Kind { + import Kind.StringState + val order = (args map {_.kind.order} max) + 1 + def description: String = + if (order == 1) "This is a type constructor: a 1st-order-kinded type." + else "This is a type constructor that takes type constructor(s): a higher-kinded type." + override def hasBounds: Boolean = super.hasBounds || args.exists(_.kind.hasBounds) + def scalaNotation: String = { + val s = buildState(NoSymbol, Variance.Invariant)(StringState.empty).removeOnes + val s2 = if (hasBounds) s + else s.removeAlias + s2.toString + } + private[internal] def buildState(sym: Symbol, v: Variance)(s0: StringState): StringState = { + var s: StringState = s0 + s = s.append(v.symbolicString).appendHead(order, sym).append("[") + args.zipWithIndex foreach { case (arg, i) => + s = arg.kind.buildState(arg.sym, arg.variance)(s) + if (i != args.size - 1) { + s = s.append(",") + } + } + s = s.append("]").append(bounds.scalaNotation(_.toString)) + s + } + def starNotation: String = { + import Variance._ + (args map { arg => + (if (arg.kind.order == 0) arg.kind.starNotation + else "(" + arg.kind.starNotation + ")") + + (if (arg.variance == Invariant) " -> " + else " -(" + arg.variance.symbolicString + ")-> ") + }).mkString + "*" + bounds.starNotation(_.toString) + } + } + object TypeConKind { + def apply(args: Seq[TypeConKind.Argument]): TypeConKind = this(TypeBounds.empty, args) + def apply(bounds: TypeBounds, args: Seq[TypeConKind.Argument]): TypeConKind = new TypeConKind(bounds, args) + def unapply(tck: TypeConKind): Some[(TypeBounds, Seq[TypeConKind.Argument])] = Some(tck.bounds, tck.args) + case class Argument(variance: Variance, kind: Kind)(val sym: Symbol) {} + } + + /** + * Starting from a Symbol (sym) or a Type (tpe), infer the kind that classifies it (sym.tpeHK/tpe). + */ + object inferKind { + import TypeConKind.Argument + + abstract class InferKind { + protected def infer(tpe: Type, owner: Symbol, topLevel: Boolean): Kind + protected def infer(sym: Symbol, topLevel: Boolean): Kind = infer(sym.tpeHK, sym.owner, topLevel) + def apply(sym: Symbol): Kind = infer(sym, true) + def apply(tpe: Type, owner: Symbol): Kind = infer(tpe, owner, true) + } + + def apply(pre: Type): InferKind = new InferKind { + protected def infer(tpe: Type, owner: Symbol, topLevel: Boolean): Kind = { + val bounds = if (topLevel) TypeBounds.empty + else tpe.asSeenFrom(pre, owner).bounds + if(!tpe.isHigherKinded) ProperTypeKind(bounds) + else TypeConKind(bounds, tpe.typeParams map { p => Argument(p.variance, infer(p, false))(p) }) + } + } + } } diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index cf4e210ccf..24bcfbb8d2 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -1506,7 +1506,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => */ def cookJavaRawInfo(): Unit = { // only try once... - if (this hasFlag TRIEDCOOKING) + if (phase.erasedTypes || (this hasFlag TRIEDCOOKING)) return this setFlag TRIEDCOOKING diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 00fd20a46b..3956bbcdec 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1435,13 +1435,27 @@ trait Types case TypeBounds(_, _) => that <:< this case _ => lo <:< that && that <:< hi } - private def lowerString = if (emptyLowerBound) "" else " >: " + lo - private def upperString = if (emptyUpperBound) "" else " <: " + hi private def emptyLowerBound = typeIsNothing(lo) || lo.isWildcard private def emptyUpperBound = typeIsAny(hi) || hi.isWildcard def isEmptyBounds = emptyLowerBound && emptyUpperBound - override def safeToString = lowerString + upperString + override def safeToString = scalaNotation(_.toString) + + /** Bounds notation used in Scala sytanx. + * For example +This <: scala.collection.generic.Sorted[K,This]. + */ + private[internal] def scalaNotation(typeString: Type => String): String = { + (if (emptyLowerBound) "" else " >: " + typeString(lo)) + + (if (emptyUpperBound) "" else " <: " + typeString(hi)) + } + /** Bounds notation used in http://adriaanm.github.com/files/higher.pdf. + * For example *(scala.collection.generic.Sorted[K,This]). + */ + private[internal] def starNotation(typeString: Type => String): String = { + if (emptyLowerBound && emptyUpperBound) "" + else if (emptyLowerBound) "(" + typeString(hi) + ")" + else "(%s, %s)" format (typeString(lo), typeString(hi)) + } override def kind = "TypeBoundsType" } diff --git a/src/repl/scala/tools/nsc/interpreter/ExprTyper.scala b/src/repl/scala/tools/nsc/interpreter/ExprTyper.scala index 55182a4f95..9353215e1e 100644 --- a/src/repl/scala/tools/nsc/interpreter/ExprTyper.scala +++ b/src/repl/scala/tools/nsc/interpreter/ExprTyper.scala @@ -95,4 +95,19 @@ trait ExprTyper { } finally typeOfExpressionDepth -= 1 } + + // This only works for proper types. + def typeOfTypeString(typeString: String): Type = { + def asProperType(): Option[Type] = { + val name = freshInternalVarName() + val line = "def %s: %s = ???" format (name, typeString) + interpretSynthetic(line) match { + case IR.Success => + val sym0 = symbolOfTerm(name) + Some(sym0.asMethod.returnType) + case _ => None + } + } + beSilentDuring(asProperType()) getOrElse NoType + } } diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 197e76d990..f4e06f6bfc 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -222,6 +222,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) shCommand, nullary("silent", "disable/enable automatic printing of results", verbosity), cmd("type", "[-v] <expr>", "display the type of an expression without evaluating it", typeCommand), + cmd("kind", "[-v] <expr>", "display the kind of expression's type", kindCommand), nullary("warnings", "show the suppressed warnings from the most recent line which had any", warningsCommand) ) @@ -287,9 +288,15 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) // Still todo: modules. private def typeCommand(line0: String): Result = { line0.trim match { - case "" => ":type [-v] <expression>" - case s if s startsWith "-v " => intp.typeCommandInternal(s stripPrefix "-v " trim, verbose = true) - case s => intp.typeCommandInternal(s, verbose = false) + case "" => ":type [-v] <expression>" + case s => intp.typeCommandInternal(s stripPrefix "-v " trim, verbose = s startsWith "-v ") + } + } + + private def kindCommand(expr: String): Result = { + expr.trim match { + case "" => ":kind [-v] <expression>" + case s => intp.kindCommandInternal(s stripPrefix "-v " trim, verbose = s startsWith "-v ") } } diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala index 66129e0be8..379751b9a7 100644 --- a/src/repl/scala/tools/nsc/interpreter/IMain.scala +++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala @@ -899,7 +899,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set val preamble = """ |object %s { | %s - | val %s: String = %s { + | lazy val %s: String = %s { | %s | ("" """.stripMargin.format( diff --git a/src/repl/scala/tools/nsc/interpreter/package.scala b/src/repl/scala/tools/nsc/interpreter/package.scala index 52a085080b..f82c38f5e7 100644 --- a/src/repl/scala/tools/nsc/interpreter/package.scala +++ b/src/repl/scala/tools/nsc/interpreter/package.scala @@ -10,6 +10,7 @@ import scala.reflect.{ classTag, ClassTag } import scala.reflect.runtime.{ universe => ru } import scala.reflect.{ClassTag, classTag} import scala.reflect.api.{Mirror, TypeCreator, Universe => ApiUniverse} +import scala.util.control.Exception.catching /** The main REPL related classes and values are as follows. * In addition to standard compiler classes Global and Settings, there are: @@ -127,6 +128,47 @@ package object interpreter extends ReplConfig with ReplStrings { "" } + def kindCommandInternal(expr: String, verbose: Boolean): Unit = { + val catcher = catching(classOf[MissingRequirementError], + classOf[ScalaReflectionException]) + def typeFromTypeString: Option[ClassSymbol] = catcher opt { + exprTyper.typeOfTypeString(expr).typeSymbol.asClass + } + def typeFromNameTreatedAsTerm: Option[ClassSymbol] = catcher opt { + val moduleClass = exprTyper.typeOfExpression(expr).typeSymbol + moduleClass.linkedClassOfClass.asClass + } + def typeFromFullName: Option[ClassSymbol] = catcher opt { + intp.global.rootMirror.staticClass(expr) + } + def typeOfTerm: Option[TypeSymbol] = replInfo(symbolOfLine(expr)).typeSymbol match { + case sym: TypeSymbol => Some(sym) + case _ => None + } + (typeFromTypeString orElse typeFromNameTreatedAsTerm orElse typeFromFullName orElse typeOfTerm) foreach { sym => + val (kind, tpe) = exitingTyper { + val tpe = sym.tpeHK + (intp.global.inferKind(NoPrefix)(tpe, sym.owner), tpe) + } + echoKind(tpe, kind, verbose) + } + } + + def echoKind(tpe: Type, kind: Kind, verbose: Boolean) { + def typeString(tpe: Type): String = { + tpe match { + case TypeRef(_, sym, _) => typeString(sym.typeSignature) + case RefinedType(_, _) => tpe.toString + case _ => tpe.typeSymbol.fullName + } + } + printAfterTyper(typeString(tpe) + "'s kind is " + kind.scalaNotation) + if (verbose) { + echo(kind.starNotation) + echo(kind.description) + } + } + /** TODO - * -n normalize * -l label with case class parameter names |