summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorducky2016-10-25 13:46:44 -0700
committerducky2016-11-21 12:48:10 -0800
commit667a26bddb6133e8b243061f8a5fc5fe586cc1ae (patch)
tree25fd7452e45a1b15355e2fb91009290d47aeb89e
parent822160cc8e76e70643fb56707bb39f6f7526b6fd (diff)
Range macro initial impl
-rw-r--r--coreMacros/src/main/scala/chisel3/internal/RangeTransform.scala97
-rw-r--r--src/test/scala/chiselTests/RangeMacroTest.scala31
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}] "
+ }
+}