From 19175b88e9a61d3c197a4627aee270de49df5269 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Mon, 23 Mar 2015 22:38:17 +0100 Subject: implement enum support --- README.md | 13 ++++--- .../com/github/jodersky/mavlink/Generator.scala | 25 ++++++++++--- .../scala/com/github/jodersky/mavlink/Parser.scala | 3 +- .../com/github/jodersky/mavlink/StringUtils.scala | 42 ++++++++++++++++++++-- .../github/jodersky/mavlink/trees/package.scala | 2 +- .../main/twirl/org/mavlink/enums/enums.scala.txt | 14 ++++++++ .../github/jodersky/mavlink/sbt/SbtMavlink.scala | 32 +++++++++-------- project/Build.scala | 2 +- 8 files changed, 104 insertions(+), 29 deletions(-) create mode 100644 mavlink-library/src/main/twirl/org/mavlink/enums/enums.scala.txt diff --git a/README.md b/README.md index 8a20272..ec8e7b1 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,11 @@ parser.push(data) The concrete message implementations are generated according to the selected dialect (see section Keys). ### Types +#### Messages Every message is mapped to a Scala case class, its name converted to CamelCase. The fields of the case class correspond to the fields defined in the dialect definition (names are converted to camelCase). +#### Fields Field types are mapped according to the following table | Definition Type | Scala Type | @@ -62,11 +64,17 @@ Field types are mapped according to the following table *Note that since Scala only supports signed integer types, it is up to the client to interpret the values of fields correctly.* +#### Enums +Enums are mapped to Scala objects, their fields defined as final vals (CamelCase) of type Int. + +*Note that since many MAVLink messages that use enums do not define a dependency on them in XML (no 'enum=' attribute), no type safety +can be guaranteed when generating messages.* + ## Usage Add the following to your plugins: ```scala - addSbtPlugin("com.github.jodersky" % "sbt-mavlink" % "0.2")` + addSbtPlugin("com.github.jodersky" % "sbt-mavlink" % "0.4")` ``` Set a MAVLink dialect @@ -85,9 +93,6 @@ All keys are defined in ```com.github.jodersky.mavlink.sbt.MavlinkKeys``` - ```mavlinkTarget``` - [Setting] - Output directory of generated Scala sources. This should be within ```sourceManaged```. -## Limitations - - Enums are not used, instead their corresponding integer value is substituted - ## Credits Copyright (c) 2015 Jakob Odersky 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 index 4f8b207..2aeb9de 100644 --- a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Generator.scala @@ -12,6 +12,7 @@ import trees._ * @param dialect a specific MAVLink dialect for which to generate code */ class Generator(dialect: Dialect) { + import Generator._ lazy val maxPayloadLength = dialect.messages.map(_.length).max @@ -30,12 +31,26 @@ class Generator(dialect: Dialect) { def targets: List[Target] = { val context = Context(dialect.version) List( - Target("org/mavlink/Assembler.scala", () => org.mavlink.txt.Assembler(context).body), - Target("org/mavlink/Crc.scala", () => org.mavlink.txt.Crc(context).body), - Target("org/mavlink/Packet.scala", () => org.mavlink.txt.Packet(context, maxPayloadLength, extraCrcs).body), - Target("org/mavlink/Parser.scala", () => org.mavlink.txt.Parser(context).body), - Target("org/mavlink/messages/messages.scala", () => org.mavlink.messages.txt.messages(context, dialect.messages).body) + Target(targetFiles(0), () => org.mavlink.txt.Assembler(context).body), + Target(targetFiles(1), () => org.mavlink.txt.Crc(context).body), + Target(targetFiles(2), () => org.mavlink.txt.Packet(context, maxPayloadLength, extraCrcs).body), + Target(targetFiles(3), () => org.mavlink.txt.Parser(context).body), + Target(targetFiles(4), () => org.mavlink.messages.txt.messages(context, dialect.messages).body), + Target(targetFiles(5), () => org.mavlink.enums.txt.enums(context, dialect.enums).body) ) } +} + +object Generator { + + val targetFiles: Seq[String] = Array( + "org/mavlink/Assembler.scala", + "org/mavlink/Crc.scala", + "org/mavlink/Packet.scala", + "org/mavlink/Parser.scala", + "org/mavlink/messages/messages.scala", + "org/mavlink/enums/enums.scala" + ) + } \ 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 index ca1b772..3010121 100644 --- a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/Parser.scala @@ -44,6 +44,7 @@ class Parser(reporter: Reporter) { case {_*} => val name = (node \ "@name").map(_.text).headOption getOrElse fatal("no name defined for enum", node) + val description = (node \ "description").map(_.text).headOption getOrElse "" val entries = (node \ "entry").zipWithIndex map { case (n, i) => //FIXME: some official MAVLink dialects don't define values in enums @@ -59,7 +60,7 @@ class Parser(reporter: Reporter) { case _ => fatal("illegal definition in enum, only entries are allowed", n) } } - Enum(name, entries) + Enum(name, entries, description) case {_*} => val id = (node \ "@id").map(_.text).headOption map { str => 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 index 38b87e7..29f262f 100644 --- a/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala +++ b/mavlink-library/src/main/scala/com/github/jodersky/mavlink/StringUtils.scala @@ -2,15 +2,53 @@ package com.github.jodersky.mavlink object StringUtils { + private final val Keywords = Set( + "class", + "object", + "trait", + "extends", + "type", + "import", + "package", + "val", + "var", + "def", + "implicit", + "private", + "protected", + "abstract", + "override", + "class", + "case", + "match", + "final", + "this", + "super", + "throw", + "catch", + "finally", + "if", + "else", + "for", + "while", + "do" + ) + + private def escape(str: String) = if (Keywords.contains(str)) { + "`" + str + "`" + } else { + str + } + def camelify(str: String) = { val lower = str.toLowerCase - "_([a-z\\d])".r.replaceAllIn(lower, {m => m.group(1).toUpperCase()}) + escape("_([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 + escape(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 index 75c1753..c1e2993 100644 --- 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 @@ -5,7 +5,7 @@ package trees { sealed trait Tree case class Dialect(version: String, enums: Set[Enum], messages: Set[Message]) extends Tree - case class Enum(name: String, entries: Seq[EnumEntry]) extends Tree + case class Enum(name: String, entries: Seq[EnumEntry], description: String) 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 { diff --git a/mavlink-library/src/main/twirl/org/mavlink/enums/enums.scala.txt b/mavlink-library/src/main/twirl/org/mavlink/enums/enums.scala.txt new file mode 100644 index 0000000..453ba2f --- /dev/null +++ b/mavlink-library/src/main/twirl/org/mavlink/enums/enums.scala.txt @@ -0,0 +1,14 @@ +@(__context: Context, __enums: Set[Enum])@org.mavlink.txt._header(__context) +package org.mavlink.enums + +@__commentParagraphs(paragraphs: Seq[String]) = {@paragraphs.mkString("/**\n * ", "\n * ", "\n */")} + +@for(__enum <- __enums) { +@__commentParagraphs(__enum.description.grouped(100).toList) +object @{StringUtils.Camelify(__enum.name)} { + @for(__entry <- __enum.entries) { + @__commentParagraphs(__entry.description.grouped(100).toList) + final val @{StringUtils.Camelify(__entry.name)}: Int = @__entry.value.toString + } +} +} diff --git a/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala index e1b044b..03beaaa 100644 --- a/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala +++ b/mavlink-plugin/src/main/scala/com/github/jodersky/mavlink/sbt/SbtMavlink.scala @@ -27,6 +27,7 @@ object SbtMavlink extends AutoPlugin { lazy val generationTask = Def.task[Seq[File]] { val dialectDefinitionFile = mavlinkDialect.value + val outDirectory = mavlinkTarget.value if (!dialectDefinitionFile.exists) sys.error( "Dialect definition " + dialectDefinitionFile.getAbsolutePath + " does not exist." @@ -36,23 +37,24 @@ object SbtMavlink extends AutoPlugin { def printWarning(msg: String) = streams.value.log.warn(msg) } - val dialectDefinition = XML.loadFile(dialectDefinitionFile) - val dialect = (new Parser(reporter)).parseDialect(dialectDefinition) - val targets = (new Generator(dialect)).targets - - val outDirectory = mavlinkTarget.value - - val files = for (tgt <- targets) yield { - val file = outDirectory / tgt.path - - if (dialectDefinitionFile.lastModified > file.lastModified) { - streams.value.log.info("Generating mavlink binding " + file) - IO.write(file, tgt.generate()) + val targetFiles = Generator.targetFiles map (outDirectory / _) + + if (targetFiles forall (_.lastModified > dialectDefinitionFile.lastModified)) { + targetFiles map (_.getAbsoluteFile) + } else { + val dialectDefinition = XML.loadFile(dialectDefinitionFile) + val dialect = (new Parser(reporter)).parseDialect(dialectDefinition) + val targets = (new Generator(dialect)).targets + for (tgt <- targets) yield { + val file = (outDirectory / tgt.path) + + if (dialectDefinitionFile.lastModified > file.lastModified) { + streams.value.log.info("Generating mavlink binding " + file) + IO.write(file, tgt.generate()) + } + file.getAbsoluteFile } - file.getAbsoluteFile } - - files } } diff --git a/project/Build.scala b/project/Build.scala index 0f7acc2..cf15776 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -10,7 +10,7 @@ object ApplicationBuild extends Build { scalaVersion := "2.10.4", scalacOptions ++= Seq("-feature", "-deprecation"), organization := "com.github.jodersky", - version := "0.3-SNAPSHOT" + version := "0.4-SNAPSHOT" ) ++ publishSettings lazy val root = ( -- cgit v1.2.3