summaryrefslogtreecommitdiff
path: root/macros/src/main/scala/chisel3/internal/RangeTransform.scala
blob: 0fdbff811bc189846dea56676173f2949ae83196 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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)"
  }
}