diff options
| author | ducky | 2016-10-25 13:46:44 -0700 |
|---|---|---|
| committer | ducky | 2016-11-21 12:48:10 -0800 |
| commit | 667a26bddb6133e8b243061f8a5fc5fe586cc1ae (patch) | |
| tree | 25fd7452e45a1b15355e2fb91009290d47aeb89e | |
| parent | 822160cc8e76e70643fb56707bb39f6f7526b6fd (diff) | |
Range macro initial impl
| -rw-r--r-- | coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala | 97 | ||||
| -rw-r--r-- | src/test/scala/chiselTests/RangeMacroTest.scala | 31 |
2 files changed, 128 insertions, 0 deletions
diff --git a/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala b/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala new file mode 100644 index 00000000..bbb36190 --- /dev/null +++ b/coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala @@ -0,0 +1,97 @@ +// See LICENSE for license details. + +// Macro transforms that statically (at compile time) parse range specifiers and emit the raw +// (non-human-friendly) range constructor calls. + +package chisel3.internal + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox.Context +import scala.reflect.macros.whitebox + +class RangeTransform(val c: Context) { + import c.universe._ + def apply(args: c.Tree*): c.Tree = { + val stringTrees = c.prefix.tree match { + case q"$_(scala.StringContext.apply(..$strings))" => strings + case _ => c.abort(c.enclosingPosition, s"Range macro unable to parse StringContext, got: ${showCode(c.prefix.tree)}") + } + val strings = stringTrees.map { tree => tree match { + case Literal(Constant(string: String)) => string + case _ => c.abort(c.enclosingPosition, s"Range macro unable to parse StringContext element, got: ${showRaw(tree)}") + } } + + var nextStringIndex: Int = 1 + var nextArgIndex: Int = 0 + var currString: String = strings(0) + + /** Mutably gets the next numeric value in the range specifier. + */ + def getNextValue(): c.Tree = { + currString = currString.dropWhile(_ == ' ') // allow whitespace + if (currString.isEmpty()) { + if (nextArgIndex >= args.length) { + c.abort(c.enclosingPosition, s"Incomplete range specifier, expected interpolated value") + } + val nextArg = args(nextArgIndex) + nextArgIndex += 1 + + if (nextStringIndex >= strings.length) { + c.abort(c.enclosingPosition, s"Incomplete range specifier") + } + currString = strings(nextStringIndex) + nextStringIndex += 1 + + nextArg + } else { + val nextStringVal = currString.takeWhile(!Set('[', '(', ' ', ',', ')', ']').contains(_)) + currString = currString.substring(nextStringVal.length) + if (currString.isEmpty()) { + if (nextStringIndex >= strings.length) { + c.abort(c.enclosingPosition, s"Incomplete range specifier") + } + currString = strings(nextStringIndex) + nextStringIndex += 1 + } + c.parse(nextStringVal) + } + } + + // Currently, not allowed to have the end stops (inclusive / exclusive) be interpolated. + currString = currString.dropWhile(_ == ' ') + val startInclusive = currString(0) match { + case '[' => true + case '(' => false + case _ => c.abort(c.enclosingPosition, s"Unknown start inclusive/exclusive specifier, got: '${currString(0)}'") + } + currString = currString.substring(1) // eat the inclusive/exclusive specifier + val minArg = getNextValue() + currString = currString.dropWhile(_ == ' ') + if (currString(0) != ',') { + c.abort(c.enclosingPosition, s"Incomplete range specifier, expected ','") + } + currString = currString.substring(1) // eat the comma + val maxArg = getNextValue() + currString = currString.dropWhile(_ == ' ') + val endInclusive = currString(0) match { + case ']' => true + case ')' => false + case _ => c.abort(c.enclosingPosition, s"Unknown end inclusive/exclusive specifier, got: '${currString(0)}'") + } + currString = currString.substring(1) // eat the inclusive/exclusive specifier + currString = currString.dropWhile(_ == ' ') + + if (nextArgIndex < args.length) { + val unused = args.mkString("") + c.abort(c.enclosingPosition, s"Unused interpolated values in range specifier: '$unused'") + } + if (!currString.isEmpty || nextStringIndex < strings.length) { + val unused = currString + strings.slice(nextStringIndex, strings.length).mkString(", ") + c.abort(c.enclosingPosition, s"Unused characters in range specifier: '$unused'") + } + + c.warning(c.enclosingPosition, s"$startInclusive ${showRaw(minArg)} ${showRaw(maxArg)} $endInclusive") + + q"" + } +} diff --git a/src/test/scala/chiselTests/RangeMacroTest.scala b/src/test/scala/chiselTests/RangeMacroTest.scala new file mode 100644 index 00000000..797c75c4 --- /dev/null +++ b/src/test/scala/chiselTests/RangeMacroTest.scala @@ -0,0 +1,31 @@ +// See LICENSE for license details. + +package chiselTests + +import chisel3._ +import scala.language.experimental.macros +import org.scalatest._ +import org.scalatest.prop._ +import chisel3.testers.BasicTester + +package object rangeMacroTest { + +implicit class ChiselRange(val sc: StringContext) extends AnyVal { + def range(args: Any*): Unit = macro chisel3.internal.RangeTransform.apply +} + +} + +import rangeMacroTest._ + +/** Comprehensive test of static range parsing functionality. + * Note: negative (failure) conditions can't be tested because they will fail at compile time, + * before the testing environment is entered. + */ +@dump +class RangeMacroTest extends ChiselPropSpec { + property("Range macros should work") { + def ducks() = {2} + range" (0, ${ducks}] " + } +} |
