aboutsummaryrefslogtreecommitdiff
path: root/mavlink-library/src/main/scala/com/github/jodersky
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-03-19 16:08:46 +0100
committerJakob Odersky <jodersky@gmail.com>2015-03-19 16:08:46 +0100
commit1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6 (patch)
tree287a8e4ce18d3a8c299d7b2a91599a7a48c7b59d /mavlink-library/src/main/scala/com/github/jodersky
downloadsbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.tar.gz
sbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.tar.bz2
sbt-mavlink-1cf6e37dc356144f3da2a2dcde75d1ced8bc5ad6.zip
initial commit
Diffstat (limited to 'mavlink-library/src/main/scala/com/github/jodersky')
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Context.scala6
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Crc.scala35
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala45
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/ParseError.scala3
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala110
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala16
-rw-r--r--mavlink-library/src/main/scala/com/github/jodersky/mavlink/trees/package.scala44
7 files changed, 259 insertions, 0 deletions
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 <field>{_*}</field> =>
+ 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 <entry>{_*}</entry> =>
+ 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 <enum>{_*}</enum> =>
+ 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 <message>{_*}</message> =>
+ 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 <mavlink>{_*}</mavlink> =>
+ 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