summaryrefslogtreecommitdiff
path: root/src/library/scala/util/matching/FixedBitField.scala
blob: 922820244893b17b4c6ea47172cd192489cd7df4 (plain) (blame)
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
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2007-2008, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// $Id$


package scala.util.matching

/** This class provides methods for creating and using FixedBitFields.
 *  If you have variable size fields, see @see BitField.
 *
 *  @author  Thibaud Hottelier
 *  @version 1.0, 3/1/2008
 *
 *  @param f the list of (name, size) pairs
 */
class FixedBitField(f: List[Product2[String, Int]]) {

  /** Create a new FixedBitField.
   *
   *  @param groups the sequence of (name, size) pairs
   */
  def this(groups: (String, Int)*) = this(groups.toList)

  val fields = f.reverse

  val size = fields.foldLeft(0)((s, p) => s + p._2)

  // works around compiler bug (TODO: which one?)
  val t = (fields.zip(fields.indices)) map {p => (p._1._1, p._2 + 1)}
  val groupNames = Map() ++ t

  /** Performs the matching using masks */
  private def buildSeq(res: List[BigInt], a: BigInt, i: int, j: int): List[BigInt] = {
    if (i < 0)
      res
    else {
      var mask = BigInt(0)
      for (k <- List.range(1, fields(i)._2 + 1))
        mask = mask.setBit(j-k)
      buildSeq(((a & mask) >> (j - fields(i)._2))::res, a, i - 1, j - fields(i)._2)
    }
  }

  /** Match an Array[Byte] or an MatchData[BigInt] instance.
   *
   *  @param target the value to be matched. Has to be an Array[Byte]
   *                or a MatchData[BigInt] instance
   *  @return       the field contents
   */
  def unapplySeq(target: Any): Option[List[BigInt]] = {
    if (target.isInstanceOf[Array[Byte]])
      Some(buildSeq(Nil, BigInt(target.asInstanceOf[Array[Byte]]), fields.size - 1, size))
    else if (target.isInstanceOf[MatchData[_]])
      Some(buildSeq(Nil, target.asInstanceOf[MatchData[BigInt]](), fields.size - 1, size).reverse)
    else
      None
  }

  /** This operator is used in for-comprehension to iterate over matches.
   *
   * @param  a the data to be matched
   * @return   the result of the matching
   */
  def ~~ (a: Array[Byte]) = split(a)

  /* Builds a MatchData object from the raw matching results */
  private def splitOne(n: BigInt) = {
    new MatchData(n::buildSeq(Nil, n, fields.size - 1, size), groupNames)
  }

  /** Performs the matching.
   *
   *  @param  the array to be matched
   *  @return the contents of each field
   */
  def parse(a: Array[Byte]): MatchData[BigInt] = splitOne(BigInt(a))

  /** Matches and consumes the same input iteratively */
  private def split(input: Array[Byte]): List[MatchData[BigInt]] = {
    def split0(res: List[BigInt], in: Array[Byte]): List[BigInt] = {
      val bSize = size / 8
      if (in.length <= bSize)
        BigInt(in) :: res
      else {
        val x = in.slice(0, bSize)
        val xs = in.slice(bSize, in.length)
        split0(BigInt(x) :: res, xs)
      }
    }
    split0(Nil, input).reverse map {n => splitOne(n)}
  }
}