From 1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Thu, 19 Mar 2015 16:08:46 +0100 Subject: initial commit --- .../com/github/jodersky/mavlink/Context.scala | 6 ++ .../scala/com/github/jodersky/mavlink/Crc.scala | 35 +++++++ .../com/github/jodersky/mavlink/Generator.scala | 45 +++++++++ .../com/github/jodersky/mavlink/ParseError.scala | 3 + .../scala/com/github/jodersky/mavlink/Parser.scala | 110 +++++++++++++++++++++ .../com/github/jodersky/mavlink/StringUtils.scala | 16 +++ .../github/jodersky/mavlink/trees/package.scala | 44 +++++++++ 7 files changed, 259 insertions(+) create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala create mode 100644 mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala (limited to 'mavlink-library/src/main/scala/com/github/jodersky') diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala new file mode 100644 index 0000000..d025285 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala @@ -0,0 +1,6 @@ +package com.github.jodersky.mavlink + +/** Represents the context under which MAVLink scala code was generated. */ +case class Context( + version: String +) \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala new file mode 100644 index 0000000..6150e48 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala @@ -0,0 +1,35 @@ +package com.github.jodersky.mavlink + +/** + * X.25 CRC calculation for MAVlink messages. The checksum must be initialized, + * updated with each field of a message, and then finished with the message + * id. + */ +case class Crc(val crc: Int = 0xffff) extends AnyVal { + + /** + * Accumulates data into a new checksum. + */ + def accumulate(datum: Byte): Crc = { + val d = datum & 0xff + var tmp = d ^ (crc & 0xff) + tmp ^= (tmp << 4) & 0xff; + Crc( + ((crc >> 8) & 0xff) ^ (tmp << 8) ^ (tmp << 3) ^ ((tmp >> 4) & 0xff)) + } + + def accumulate(data: Seq[Byte]): Crc = { + var next = this + for (d <- data) { + next = next.accumulate(d) + } + next + } + + /** Least significant byte of checksum. */ + def lsb: Byte = crc.toByte + + /** Most significant byte of checksum. */ + def msb: Byte = (crc >> 8).toByte + +} \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala new file mode 100644 index 0000000..06a4909 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala @@ -0,0 +1,45 @@ +package com.github.jodersky.mavlink + +import scala.xml.XML +import java.io.FileWriter +import java.io.BufferedWriter +import scalax.file.Path +import java.io.File + +import trees._ + +/** + * Generates Scala code implementing the MAVLink protocol. + * @param dialect a specific MAVLink dialect for which to generate code + */ +class Generator(dialect: Dialect) { + + lazy val maxPayloadLength = { + val widths = dialect.messages map { msg => + msg.fields.map(_.tpe.sizeof).sum + } + widths.max + } + + lazy val extraCrcs = Array.tabulate[Byte](255){i => + val message = dialect.messages.find(_.id == i) + message.map(_.checksum).getOrElse(0) + } + + /** + * Generates Scala code implementing MAVLink. + * @return a list containing proposed Scala file names pointing to their contents + */ + def generate(): List[(String, String)] = { + val context = Context(dialect.version) + + List( + "org/mavlink/Assembler.scala" -> org.mavlink.txt.Assembler(context).body, + "org/mavlink/Crc.scala" -> org.mavlink.txt.Crc(context).body, + "org/mavlink/MavlinkBuffer.scala" -> org.mavlink.txt.MavlinkBuffer(context).body, + "org/mavlink/Packet.scala" -> org.mavlink.txt.Packet(context, maxPayloadLength, extraCrcs).body, + "org/mavlink/Parser.scala" -> org.mavlink.txt.Parser(context).body, + "org/mavlink/messages/messages.scala" -> org.mavlink.messages.txt.messages(context, dialect.messages).body + ) + } +} \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala new file mode 100644 index 0000000..e224f39 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala @@ -0,0 +1,3 @@ +package com.github.jodersky.mavlink + +class ParseError(message: String) extends RuntimeException(message) \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala new file mode 100644 index 0000000..e2dda42 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala @@ -0,0 +1,110 @@ +package com.github.jodersky.mavlink + +import scala.language.postfixOps + +import scala.xml.Node +import scala.util.Try + +import trees._ + +/** + * Provides means to parse a MAVLink dialect definition into a + * scala object representation. + */ +object Parser { + + def fatal(error: String, node: Node) = throw new ParseError("Parse error at " + node + ": " + error) + def warn(warning: String, node: Node) = Console.err.println("Warning " + node + ": " + warning) + + def parseDialect(node: Node): Dialect = parse(node) match { + case p: Dialect => p + case _ => fatal("expected mavlink protocol definition", node) + } + + def parse(node: Node): Tree = node match { + case {_*} => + val description = node.text + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for field", node) + val enum = (node \ "@enum").map(_.text).headOption + val (tpe, native) = (node \ "@type") map (_.text) headOption match { + case Some(t) => (parseType(t, node), t) + case None => fatal("no field type specified", node) + } + Field(tpe, native, name, enum, description) + + case {_*} => + val value = (node \ "@value").map(_.text).headOption map { str => + Try { Integer.parseInt(str) } getOrElse fatal("value must be an integer", node) + } getOrElse fatal("no value defined", node) + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum entry", node) + val description = (node \ "description").text + EnumEntry(value, name, description) + + case {_*} => + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum", node) + val entries = (node \ "entry").toSet map { n: Node => + parse(n) match { + case e: EnumEntry => e + case _ => fatal("illegal definition in enum, only entries are allowed", n) + } + } + Enum(name, entries) + + case {_*} => + val id = (node \ "@id").map(_.text).headOption map { str => + val id = Try { Integer.parseInt(str) } getOrElse fatal("id must be an integer", node) + if (id < 0 || id > 255) warn("message id is not in the range [0-255]", node) + id.toByte + } getOrElse fatal("no id defined", node) + val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for message", node) + val description = (node \ "description").text + + val fields = (node \ "field") map { n: Node => + parse(n) match { + case e: Field => e + case _ => fatal("illegal definition in message, only fields are allowed", n) + } + } + Message(id, name, description, fields) + + case {_*} => + val version = (node \ "version").text + + val enums = (node \ "enums" \ "_").toSet map { n: Node => + parse(n) match { + case e: Enum => e + case _ => fatal("illegal definition in enums, only enum declarations are allowed", n) + } + } + + val messages = (node \ "messages" \ "_").toSet map { n: Node => + parse(n) match { + case e: Message => e + case e => fatal("illegal definition in messages, only message declarations are allowed", n) + } + } + Dialect(version, enums, messages) + + case x => fatal("unknown", x) + + } + + val ArrayPattern = """(.*)\[(\d+)\]""".r + def parseType(typeStr: String, node: Node): Type = typeStr match { + case "int8_t" => IntType(1, true) + case "int16_t" => IntType(2, true) + case "int32_t" => IntType(4, true) + case "int64_t" => IntType(8, true) + case "uint8_t" => IntType(1, false) + case "uint16_t" => IntType(2, false) + case "uint32_t" => IntType(4, false) + case "uint64_t" => IntType(8, false) + case "float" => FloatType(4) + case "double" => FloatType(8) + case "char" => IntType(1, true) + case ArrayPattern("char", l) => StringType(l.toInt) + case ArrayPattern(u, l) => ArrayType(parseType(u, node), l.toInt) + case unknown => fatal("unknown field type " + unknown, node) + } + +} \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala new file mode 100644 index 0000000..38b87e7 --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala @@ -0,0 +1,16 @@ +package com.github.jodersky.mavlink + +object StringUtils { + + def camelify(str: String) = { + val lower = str.toLowerCase + "_([a-z\\d])".r.replaceAllIn(lower, {m => m.group(1).toUpperCase()}) + } + + def Camelify(str: String) = { + val camel = camelify(str) + val (head, tail) = camel.splitAt(1) + head.toUpperCase + tail + } + +} \ No newline at end of file diff --git a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala new file mode 100644 index 0000000..49b697b --- /dev/null +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala @@ -0,0 +1,44 @@ +package com.github.jodersky.mavlink + +package trees { + + sealed trait Tree + + case class Dialect(version: String, enums: Set[Enum], messages: Set[Message]) extends Tree + case class Enum(name: String, entries: Set[EnumEntry]) extends Tree + case class EnumEntry(value: Int, name: String, description: String) extends Tree + case class Field(tpe: Type, nativeType: String, name: String, enum: Option[String], description: String) extends Tree + case class Message(id: Byte, name: String, description: String, fields: Seq[Field]) extends Tree { + def orderedFields = fields.toSeq.sortBy(_.tpe.width)(Ordering[Int].reverse) + + lazy val checksum = { + var c = new Crc() + c = c.accumulate((name + " ").getBytes) + for (field <- orderedFields) { + c = c.accumulate((field.nativeType + " ").getBytes) + c = c.accumulate((field.name + " ").getBytes) + } + (c.lsb ^ c.msb).toByte + } + } + + trait Type extends Tree { + def width: Int // width in bytes of the type + def sizeof: Int = width // size of bytes of the type + } + + case class IntType(width: Int, signed: Boolean) extends Type + case class FloatType(width: Int) extends Type + case class ArrayType(underlying: Type, length: Int) extends Type { + def width = underlying.width + override def sizeof = width * length + } + case object CharType extends Type { + def width = 1 + } + case class StringType(maxLength: Int) extends Type { + val width = 1 + override def sizeof = width * maxLength + } + +} \ No newline at end of file -- cgit v1.2.3