diff options
Diffstat (limited to 'macros/src/main/scala/chisel3/internal/RangeTransform.scala')
| -rw-r--r-- | macros/src/main/scala/chisel3/internal/RangeTransform.scala | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/macros/src/main/scala/chisel3/internal/RangeTransform.scala b/macros/src/main/scala/chisel3/internal/RangeTransform.scala new file mode 100644 index 00000000..0fdbff81 --- /dev/null +++ b/macros/src/main/scala/chisel3/internal/RangeTransform.scala @@ -0,0 +1,197 @@ +// 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 +import scala.util.matching.Regex + +// Workaround for https://github.com/sbt/sbt/issues/3966 +object RangeTransform { + val UnspecifiedNumber: Regex = """(\?).*""".r + val IntegerNumber: Regex = """(-?\d+).*""".r + val DecimalNumber: Regex = """(-?\d+\.\d+).*""".r +} + +/** Convert the string to IntervalRange, with unknown, open or closed endpoints and a binary point + * ranges looks like + * range"[0,4].1" range starts at 0 inclusive ends at 4.inclusively with a binary point of 1 + * range"(0,4).1" range starts at 0 exclusive ends at 4.exclusively with a binary point of 1 + * + * the min and max of the range are the actually min and max values, thus the binary point + * becomes a sort of multiplier for the number of bits. + * E.g. range"[0,3].2" will require at least 4 bits two provide the two decimal places + * + * @param c contains the string context to be parsed + */ +//scalastyle:off cyclomatic.complexity method.length +class RangeTransform(val c: blackbox.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 { + case Literal(Constant(string: String)) => string + case tree => + 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.head + + /** Mutably gets the next numeric value in the range specifier. + */ + def computeNextValue(): c.Tree = { + currString = currString.dropWhile(_ == ' ') // allow whitespace + if (currString.isEmpty) { + if (nextArgIndex >= args.length) { + c.abort(c.enclosingPosition, s"Incomplete range specifier") + } + 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 match { + case RangeTransform.DecimalNumber(numberString) => numberString + case RangeTransform.IntegerNumber(numberString) => numberString + case RangeTransform.UnspecifiedNumber(_) => "?" + case _ => + c.abort( + c.enclosingPosition, + s"Bad number or unspecified bound $currString" + ) + } + currString = currString.substring(nextStringVal.length) + + if (nextStringVal == "?") { + Literal(Constant("?")) + } else { + c.parse(nextStringVal) + } + } + } + + // Currently, not allowed to have the end stops (inclusive / exclusive) be interpolated. + currString = currString.dropWhile(_ == ' ') + val startInclusive = currString.headOption match { + case Some('[') => true + case Some('(') => false + case Some('?') => + c.abort( + c.enclosingPosition, + s"start of range as unknown s must be '[?' or '(?' not '?'" + ) + case Some(other) => + c.abort( + c.enclosingPosition, + s"Unknown start inclusive/exclusive specifier, got: '$other'" + ) + case None => + c.abort( + c.enclosingPosition, + s"No initial inclusive/exclusive specifier" + ) + } + + currString = currString.substring(1) // eat the inclusive/exclusive specifier + val minArg = computeNextValue() + currString = currString.dropWhile(_ == ' ') + if (currString(0) != ',') { + c.abort(c.enclosingPosition, s"Incomplete range specifier, expected ','") + } + if (currString.head != ',') { + c.abort( + c.enclosingPosition, + s"Incomplete range specifier, expected ',', got $currString" + ) + } + + currString = currString.substring(1) // eat the comma + + val maxArg = computeNextValue() + currString = currString.dropWhile(_ == ' ') + + val endInclusive = currString.headOption match { + case Some(']') => true + case Some(')') => false + case Some('?') => + c.abort( + c.enclosingPosition, + s"start of range as unknown s must be '[?' or '(?' not '?'" + ) + case Some(other) => + c.abort( + c.enclosingPosition, + s"Unknown end inclusive/exclusive specifier, got: '$other' expecting ')' or ']'" + ) + case None => + c.abort( + c.enclosingPosition, + s"Incomplete range specifier, missing end inclusive/exclusive specifier" + ) + } + currString = currString.substring(1) // eat the inclusive/exclusive specifier + currString = currString.dropWhile(_ == ' ') + + val binaryPointString = currString.headOption match { + case Some('.') => + currString = currString.substring(1) + computeNextValue() + case Some(other) => + c.abort( + c.enclosingPosition, + s"Unknown end binary point prefix, got: '$other' was expecting '.'" + ) + case None => + Literal(Constant(0)) + } + + 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'" + ) + } + + val startBound = + q"_root_.chisel3.internal.firrtl.IntervalRange.getBound($startInclusive, $minArg)" + + val endBound = + q"_root_.chisel3.internal.firrtl.IntervalRange.getBound($endInclusive, $maxArg)" + + val binaryPoint = + q"_root_.chisel3.internal.firrtl.IntervalRange.getBinaryPoint($binaryPointString)" + + q"_root_.chisel3.internal.firrtl.IntervalRange($startBound, $endBound, $binaryPoint)" + } +} |
