Skip to content

Instantly share code, notes, and snippets.

@oxlade39
Last active December 20, 2015 11:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxlade39/6123535 to your computer and use it in GitHub Desktop.
Save oxlade39/6123535 to your computer and use it in GitHub Desktop.
bencoding using scala parser
object BencodeParser {
def parse(s: String) = parser.parseAll(parser.bvalue, s) match {
case parser.Success(bencoded, _) => Some(bencoded)
case _ => None
}
private[this] object parser extends scala.util.parsing.combinator.RegexParsers {
override type Elem = Char
def positiveInt = """([1-9]\d*)""".r ^^ {
i => Integer.parseInt(i)
}
def integer = """(0|-*[1-9]\d*)""".r ^^ {
i => Integer.parseInt(i)
}
def bint: Parser[BInt] = 'i' ~ integer ~ 'e' ^^ {
case start ~ i ~ end => BInt(i)
}
def length = positiveInt ~ ":"
def bstring: Parser[BString] = length >> {
len => """\w{%d}""".format(len._1).r
} ^^ {
case s => BString(s)
}
def blist: Parser[BList] = 'l' ~ rep(bvalue) ~ 'e' ^^ {
case l ~ b ~ e => BList(b: _*)
}
def bmap: Parser[BMap] = 'd' ~ rep(bstring ~ bvalue) ~ 'e' ^^ {
case d ~ items ~ e => BMap(items.foldLeft(Map[String, BValue]()) {
case (accum, item) =>
val key: BString = item._1
val value: BValue = item._2
accum + (key.value -> value)
})
}
def bvalue: Parser[BValue] = bint | bstring | blist | bmap
}
}
import org.specs2.mutable.Specification
class BencodeParserSpec extends Specification {
"BencodeParser" should {
"parse bencoded integers" in {
val output = BencodeParser.parse("i42e")
output mustEqual Some(BInt(42))
}
"parse bencoded negative integers" in {
val output = BencodeParser.parse("i-42e")
output mustEqual Some(BInt(-42))
}
"not parse bencoded negative zero" in {
val output = BencodeParser.parse("i-e")
output mustEqual None
}
"parse bencoded strings" in {
val output = BencodeParser.parse("4:spam")
output mustEqual Some(BString("spam"))
}
"parse bencoded lists" in {
val output = BencodeParser.parse("l4:spami42ee")
output mustEqual Some(BList(BString("spam"), BInt(42)))
}
"parse bencoded maps" in {
val output = BencodeParser.parse("d3:bar4:spam3:fooi42ee")
output mustEqual Some(BMap(Map("foo" -> BInt(42), "bar" -> BString("spam"))))
}
}
"BValue" should {
"encode" in {
BMap(Map("foo" -> BInt(42), "bar" -> BString("spam"))).encode mustEqual "d3:bar4:spam3:fooi42ee"
}
}
}
sealed trait BValue {
def encode: String
}
case class BInt(value: Int) extends BValue{
lazy val encode = "i" + value + "e"
}
case class BString(value: String) extends BValue{
lazy val encode = value.size + ":" + value
}
case class BList(values: BValue*) extends BValue{
def encode = values.foldLeft("l")((accum, value) => accum + value.encode) + "e"
}
case class BMap(values: Map[String, BValue]) extends BValue{
def encode = values.foldLeft("d")((accum, kv) => accum + kv._1 + kv._2.encode) + "e"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment